2 Commits

59 changed files with 535 additions and 1919 deletions

View File

@@ -1,6 +1,6 @@
# [HamsterBall](https://git.airgame.net/MiniDay/hamster-ball)
基于 Redis 的 Minecraft 服务端通用消息中间件
仓鼠球:一个基于 Redis 的 Minecraft 服务端通用消息中间件原HamsterService
该插件依赖于 [仓鼠核心](https://git.airgame.net/MiniDay/hamster-core)
@@ -27,10 +27,12 @@
为了适配 docker 环境,本插件除了从 `config.yml` 中配置服务器信息以外,还支持从环境变量中读取
| 环境变量 | 描述 | 对应 config 值 |
|:-----------------|:-------------------|:-----------------|
| BALL_SERVER_ID | 本服务器唯一识别码,最长 32 字符 | server-info.id |
| BALL_SERVER_NAME | 本服务端名称,用于展示给玩家看 | server-info.name |
| 环境变量 | 描述 | 对应 config 值 |
|:----------------------------|:-------------------|:-----------------|
| BALL_LOCAL_SERVER_IP | 本服务器 IP | server-info.host |
| BALL_LOCAL_SERVER_PORT | 本服务器端口 | server-info.port |
| BALL_LOCAL_SERVER_INFO_ID | 本服务器唯一识别码,最长 32 字符 | server-info.id |
| BALL_LOCAL_SERVER_INFO_NAME | 本服务端名称,用于展示给玩家看 | server-info.name |
# 开发
@@ -49,9 +51,9 @@ repositories {
dependencies {
// 对于 Bukkit 插件
compileOnly("cn.hamster3.mc.plugin:ball-bukkit:1.7.0")
compileOnly("cn.hamster3.mc.plugin:ball-bukkit:1.5.7")
// 对于 BungeeCord 插件
compileOnly("cn.hamster3.mc.plugin:ball-bungee:1.7.0")
compileOnly("cn.hamster3.mc.plugin:ball-bungee:1.5.7")
}
```
@@ -77,13 +79,13 @@ dependencies {
<dependency>
<groupId>cn.hamster3.mc.plugin</groupId>
<artifactId>ball-bukkit</artifactId>
<version>1.7.0</version>
<version>1.5.7</version>
</dependency>
<!--对于 BungeeCord 插件-->
<dependency>
<groupId>cn.hamster3.mc.plugin</groupId>
<artifactId>ball-bungee</artifactId>
<version>1.7.0</version>
<version>1.5.7</version>
</dependency>
</dependencies>
</project>

View File

@@ -3,11 +3,15 @@
evaluationDependsOn(":ball-common")
dependencies {
api(project(":ball-common")) { isTransitive = false }
implementation(project(":ball-common")) {
isTransitive = false
}
compileOnly("org.spigotmc:spigot-api:1.18.2-R0.1-SNAPSHOT")
compileOnly("cn.hamster3.mc.plugin:core-bukkit:+")
compileOnly("me.clip:placeholderapi:2.11.5") { isTransitive = false }
compileOnly("cn.hamster3.mc.plugin:core-bukkit:1.2.2")
compileOnly("me.clip:placeholderapi:2.11.5") {
isTransitive = false
}
}
tasks {

View File

@@ -6,30 +6,17 @@ 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;
@SuppressWarnings("CallToPrintStackTrace")
public class HamsterBallPlugin extends JavaPlugin {
@Getter
private static HamsterBallPlugin instance;
@@ -52,25 +39,10 @@ public class HamsterBallPlugin extends JavaPlugin {
reloadConfig();
logger.info("已读取配置文件");
try {
File dataFolder = getDataFolder();
if (dataFolder.mkdir()) {
logger.info("已生成插件存档文件夹");
}
File configFile = new File(dataFolder, "config.yml");
if (!configFile.exists()) {
Files.copy(
Objects.requireNonNull(getResource("config.yml")),
configFile.toPath(),
StandardCopyOption.REPLACE_EXISTING
);
}
YamlConfig config = YamlConfig.load(configFile);
BallBukkitAPI.init(config);
BallBukkitAPI.init();
logger.info("已初始化 BallAPI");
} catch (Exception e) {
logger.log(Level.SEVERE, "BallAPI 初始化失败", e);
Bukkit.shutdown();
return;
e.printStackTrace();
}
long time = System.currentTimeMillis() - start;
logger.info("仓鼠球初始化完成,总计耗时 " + time + " ms");
@@ -84,8 +56,9 @@ public class HamsterBallPlugin extends JavaPlugin {
try {
BallBukkitAPI.getInstance().enable();
} catch (Exception e) {
logger.log(Level.SEVERE, "仓鼠球启动失败", e);
logger.info("由于仓鼠球启动失败,服务器将立即关闭");
getLogger().info("仓鼠球启动失败,原因:" + e.getMessage());
e.printStackTrace();
getLogger().info("由于仓鼠球启动失败,服务器将立即关闭");
Bukkit.shutdown();
return;
}
@@ -108,7 +81,7 @@ public class HamsterBallPlugin extends JavaPlugin {
BallBukkitUtils.uploadPlayerInfo(playerInfo);
});
} else {
BallAPI.getInstance().subscribeRaw(BallAPI.PLAYER_INFO_CHANNEL);
BallAPI.getInstance().subscribeIgnorePrefix(BallAPI.PLAYER_INFO_CHANNEL);
}
sync(() -> {
if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) {
@@ -132,40 +105,9 @@ public class HamsterBallPlugin extends JavaPlugin {
try {
BallBukkitAPI.getInstance().disable();
} catch (Exception e) {
logger.log(Level.SEVERE, "关闭仓鼠球时遇到了一个异常", e);
e.printStackTrace();
}
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<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

@@ -1,36 +1,73 @@
package cn.hamster3.mc.plugin.ball.bukkit.api;
import cn.hamster3.mc.plugin.ball.bukkit.HamsterBallPlugin;
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.config.BallConfig;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerInfo;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerType;
import cn.hamster3.mc.plugin.core.common.config.ConfigSection;
import cn.hamster3.mc.plugin.core.common.config.YamlConfig;
import cn.hamster3.mc.plugin.core.common.api.CoreAPI;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration;
import org.jetbrains.annotations.NotNull;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.Map;
import java.util.logging.Logger;
public class BallBukkitAPI extends BallAPI {
public BallBukkitAPI(@NotNull ConfigSection config) {
super(config, BallServerType.GAME);
public BallBukkitAPI(@NotNull BallConfig ballConfig) {
super(ballConfig);
}
public static BallBukkitAPI getInstance() {
return (BallBukkitAPI) instance;
}
public static void init(@NotNull YamlConfig config) {
public static void init() {
if (instance != null) {
return;
}
instance = new BallBukkitAPI(config);
HamsterBallPlugin plugin = HamsterBallPlugin.getInstance();
plugin.saveDefaultConfig();
plugin.reloadConfig();
FileConfiguration config = plugin.getConfig();
Map<String, String> env = System.getenv();
BallServerInfo serverInfo = new BallServerInfo(
env.getOrDefault("BALL_LOCAL_SERVER_INFO_ID", config.getString("server-info.id")),
env.getOrDefault("BALL_LOCAL_SERVER_INFO_NAME", config.getString("server-info.name")),
BallServerType.GAME,
env.getOrDefault("BALL_LOCAL_SERVER_IP", config.getString("server-info.host", Bukkit.getIp())),
Integer.parseInt(
env.getOrDefault("BALL_LOCAL_SERVER_PORT", String.valueOf(config.getInt("server-info.port", Bukkit.getPort())))
)
);
DataSource datasource;
if (config.contains("datasource")) {
plugin.getLogger().info("启用仓鼠球自定义数据库连接池");
datasource = BallBukkitUtils.getDataSource(config.getConfigurationSection("datasource"));
} else {
plugin.getLogger().info("复用 HamsterCore 的数据库连接池");
datasource = CoreAPI.getInstance().getDataSource();
}
BallConfig ballConfig = new BallConfig(
config.getBoolean("debug", false),
config.getString("channel-prefix", "") + ":",
config.getBoolean("game-server-update-player-info", false),
config.getStringList("load-player-info-filter"),
serverInfo,
datasource
);
instance = new BallBukkitAPI(ballConfig);
}
@Override
public void enable() throws SQLException, InterruptedException {
instance.getLocalServerInfo().setHost(Bukkit.getIp().isEmpty() ? "127.0.0.1" : Bukkit.getIp());
instance.getLocalServerInfo().setPort(Bukkit.getPort());
super.enable();
}

View File

@@ -3,14 +3,15 @@ package cn.hamster3.mc.plugin.ball.bukkit.listener;
import cn.hamster3.mc.plugin.ball.bukkit.HamsterBallPlugin;
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.entity.BallServerType;
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.data.DisplayMessage;
import cn.hamster3.mc.plugin.core.lib.net.kyori.adventure.audience.Audience;
import cn.hamster3.mc.plugin.core.lib.net.kyori.adventure.text.TextReplacementConfig;
import com.google.common.eventbus.Subscribe;
import lombok.AllArgsConstructor;
import lombok.Data;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.TextReplacementConfig;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
@@ -29,8 +30,8 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
@SuppressWarnings("CallToPrintStackTrace")
public class BallBukkitListener implements Listener {
public static final BallBukkitListener INSTANCE = new BallBukkitListener();
@@ -80,7 +81,7 @@ public class BallBukkitListener implements Listener {
}
removeCachedPlayerMessage(uuid);
} catch (SQLException e) {
BallAPI.getInstance().getLogger().log(Level.SEVERE, "发送玩家缓存消息时出现了一个异常", e);
e.printStackTrace();
}
});
}
@@ -94,6 +95,9 @@ public class BallBukkitListener implements Listener {
@Subscribe
public void onDispatchConsoleCommand(DispatchConsoleCommandEvent event) {
if (event.getType() != null && event.getType() != BallServerType.GAME) {
return;
}
if (event.getServerID() != null && !BallAPI.getInstance().isLocalServer(event.getServerID())) {
return;
}
@@ -102,6 +106,9 @@ public class BallBukkitListener implements Listener {
@Subscribe
public void onDispatchPlayerCommand(DispatchPlayerCommandEvent event) {
if (event.getType() != null && event.getType() != BallServerType.GAME) {
return;
}
if (event.getUuid() != null) {
Player player = Bukkit.getPlayer(event.getUuid());
if (player == null) {

View File

@@ -5,17 +5,50 @@ 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.player.BallPlayerInfoUpdateEvent;
import cn.hamster3.mc.plugin.core.common.api.CoreAPI;
import cn.hamster3.mc.plugin.core.lib.com.zaxxer.hikari.HikariConfig;
import cn.hamster3.mc.plugin.core.lib.com.zaxxer.hikari.HikariDataSource;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.logging.Level;
@SuppressWarnings("CallToPrintStackTrace")
public final class BallBukkitUtils {
private BallBukkitUtils() {
}
@Nullable
public static DataSource getDataSource(@Nullable ConfigurationSection datasourceConfig) {
if (datasourceConfig == null) {
return null;
}
try {
HikariConfig hikariConfig = new HikariConfig();
String driver = datasourceConfig.getString("driver");
hikariConfig.setDriverClassName(driver);
hikariConfig.setJdbcUrl(datasourceConfig.getString("url"));
hikariConfig.setUsername(datasourceConfig.getString("username"));
hikariConfig.setPassword(datasourceConfig.getString("password"));
hikariConfig.setMaximumPoolSize(datasourceConfig.getInt("maximum-pool-size", 3));
hikariConfig.setMinimumIdle(datasourceConfig.getInt("minimum-idle", 1));
long keepAliveTime = datasourceConfig.getLong("keep-alive-time", 0);
if (keepAliveTime > 5000) {
hikariConfig.setKeepaliveTime(keepAliveTime);
}
hikariConfig.setIdleTimeout(datasourceConfig.getLong("idle-timeout", 10 * 60 * 1000));
hikariConfig.setMaxLifetime(datasourceConfig.getLong("max-lifetime", 30 * 60 * 1000));
hikariConfig.setValidationTimeout(datasourceConfig.getLong("validation-timeout", 5000));
hikariConfig.setPoolName("HamsterBall-Pool");
return new HikariDataSource(hikariConfig);
} catch (Exception | Error e) {
return null;
}
}
public static void uploadPlayerInfo(@NotNull BallPlayerInfo playerInfo) {
CoreAPI.getInstance().getExecutorService().execute(() -> {
try (Connection connection = BallAPI.getInstance().getDatasource().getConnection()) {
@@ -30,7 +63,7 @@ public final class BallBukkitUtils {
statement.executeUpdate();
}
} catch (SQLException e) {
BallAPI.getInstance().getLogger().log(Level.SEVERE, "更新玩家数据时遇到了一个异常", e);
e.printStackTrace();
}
BallAPI.getInstance().sendBallMessage(
BallAPI.PLAYER_INFO_CHANNEL,

View File

@@ -1,13 +1,6 @@
# 是否允许在控制台输出调试信息
debug: false
# 是否启用服务器 ID 单例模式
# 启用后,当一个服务器启动后将会占用 服务器唯一识别码
# 其他使用相同 服务器唯一识别码 的服务器将无法启动
# 测试端中可关闭该功能
# 推荐在正式服中开启该功能以防止服务器 ID 重复
singleton-server-id: false
# 频道名前缀
# 使用这个配置选项可以划分子服消息通信分组
# 只有在同一个频道名的子服才能互相通信
@@ -18,7 +11,7 @@ channel-prefix: ""
# 如果一个群组服同时拥有多个 BC 入口
# 且每个 BC 入口为不同的玩家名称分配不同的 UUID
# (例如正版、盗版双入口,或网易多入口接同一个子服)
# 则可以启用该功能以防止同一个名称占用多个 UUID 的问题
# 则可以启用该功能以防止 UUID 紊乱的问题
game-server-update-player-info: false
# 该选项仅在 game-server-update-player-info 为 true 时有效
@@ -48,18 +41,16 @@ server-info:
# 如果你需要让每个服务器单独存储仓鼠球信息
# 这个选项就会很有用
#datasource:
# # 数据库链接驱动地址旧版服务端低于1.13请使用com.mysql.jdbc.Driver
# driver: "com.mysql.cj.jdbc.Driver"
# # MySQL数据库链接填写格式:
# # 数据库链接驱动地址
# driver: "com.mysql.jdbc.Driver"
# # 数据库链接填写格式:
# # jdbc:mysql://{数据库地址}:{数据库端口}/{使用的库名}?参数
# url: "jdbc:mysql://localhost:3306/Test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true"
# # 如果你不需要做多端跨服,那么请使用 sqlite 作本地数据库 ↓
# # driver: "org.sqlite.JDBC"
# # url: "jdbc:sqlite:./plugins/HamsterCore/database.db"
# # 除非你知道自己在做什么,否则不建议随意更改参数
# url: "jdbc:mysql://localhost:3306/Test1?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true"
# # 用户名
# username: "root"
# username: "Test"
# # 密码
# password: "Root123.."
# password: "Test123.."
# # 最小闲置链接数
# # 推荐值1~3
# minimum-idle: 0

View File

@@ -4,15 +4,8 @@ version: ${version}
api-version: 1.13
author: MiniDay
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/
description: 仓鼠球:一个基于 Redis 的 Minecraft 服务端通用消息中间件原HamsterService
load: STARTUP
@@ -21,11 +14,3 @@ depend:
softdepend:
- PlaceholderAPI
commands:
hamster-ball:
aliases: [ ball ]
permissions:
hamster.ball.admin:
default: op

View File

@@ -3,9 +3,12 @@
evaluationDependsOn(":ball-common")
dependencies {
api(project(":ball-common")) { isTransitive = false }
implementation(project(":ball-common")) {
isTransitive = false
}
compileOnly("net.md-5:bungeecord-api:1.20-R0.1")
compileOnly("cn.hamster3.mc.plugin:core-bungee:+")
compileOnly("cn.hamster3.mc.plugin:core-bungee:1.2.2")
}
tasks {

View File

@@ -1,32 +1,24 @@
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;
import cn.hamster3.mc.plugin.ball.bungee.listener.UpdatePlayerInfoListener;
import cn.hamster3.mc.plugin.ball.bungee.util.BallBungeeCordUtils;
import cn.hamster3.mc.plugin.ball.common.api.BallAPI;
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.common.config.YamlConfig;
import lombok.Getter;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.plugin.Plugin;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.logging.Level;
import java.util.logging.Logger;
@SuppressWarnings("CallToPrintStackTrace")
public class HamsterBallPlugin extends Plugin {
@Getter
private static HamsterBallPlugin instance;
@Getter
private YamlConfig config;
@Override
public void onLoad() {
@@ -35,25 +27,11 @@ public class HamsterBallPlugin extends Plugin {
logger.info("仓鼠球正在初始化");
instance = this;
try {
File dataFolder = getDataFolder();
if (dataFolder.mkdir()) {
logger.info("已生成插件存档文件夹");
}
File configFile = new File(dataFolder, "config.yml");
if (!configFile.exists()) {
Files.copy(
getResourceAsStream("config.yml"),
configFile.toPath(),
StandardCopyOption.REPLACE_EXISTING
);
}
config = YamlConfig.load(configFile);
BallBungeeCordAPI.init(config);
BallBungeeCordAPI.init();
logger.info("已初始化 BallAPI");
} catch (Exception e) {
logger.log(Level.SEVERE, "BallAPI 初始化失败", e);
ProxyServer.getInstance().stop("由于 HamsterBall 初始化失败, 服务器将立即关闭");
return;
e.printStackTrace();
ProxyServer.getInstance().stop("由于 HamsterBall 未能成功连接, 服务器将立即关闭");
}
long time = System.currentTimeMillis() - start;
logger.info("仓鼠球初始化完成,总计耗时 " + time + " ms");
@@ -67,29 +45,19 @@ public class HamsterBallPlugin extends Plugin {
try {
BallBungeeCordAPI.getInstance().enable();
} catch (Exception e) {
logger.log(Level.SEVERE, "仓鼠球启动失败", e);
logger.info("由于仓鼠球启动失败,服务器将立即关闭");
ProxyServer.getInstance().stop("仓鼠球启动失败");
return;
e.printStackTrace();
}
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);
logger.info("已注册监听器 BallBungeeMainListener");
ProxyServer.getInstance().getPluginManager().registerListener(this, UpdatePlayerInfoListener.INSTANCE);
logger.info("已注册监听器 UpdatePlayerInfoListener");
if (config.getBoolean("auto-register-game-server", false)) {
BallAPI.getInstance().getEventBus().register(BungeeServerListener.INSTANCE);
logger.info("已注册监听器 BungeeServerListener");
BungeeServerListener.INSTANCE.onEnable();
}
if (BallAPI.getInstance().getBallConfig().isGameServerUpdatePlayerInfo()) {
BallAPI.getInstance().subscribePatterns("*" + BallAPI.PLAYER_INFO_CHANNEL);
} else {
BallAPI.getInstance().subscribeRaw(BallAPI.PLAYER_INFO_CHANNEL);
BallAPI.getInstance().subscribeIgnorePrefix(BallAPI.PLAYER_INFO_CHANNEL);
}
BallAPI.getInstance().sendRawBallMessage(
BallAPI.BALL_CHANNEL, BallActions.ServerOnline.name(),
@@ -116,7 +84,9 @@ public class HamsterBallPlugin extends Plugin {
try {
BallBungeeCordAPI.getInstance().disable();
} catch (Exception e) {
logger.log(Level.SEVERE, "关闭仓鼠球时遇到了一个异常", e);
getLogger().info("仓鼠球启动失败,原因:" + e.getMessage());
e.printStackTrace();
ProxyServer.getInstance().stop("由于仓鼠球启动失败,服务器将立即关闭");
}
long time = System.currentTimeMillis() - start;
logger.info("仓鼠球已关闭,总计耗时 " + time + " ms");

View File

@@ -1,44 +1,70 @@
package cn.hamster3.mc.plugin.ball.bungee.api;
import cn.hamster3.mc.plugin.ball.bungee.HamsterBallPlugin;
import cn.hamster3.mc.plugin.ball.bungee.util.BallBungeeCordUtils;
import cn.hamster3.mc.plugin.ball.common.api.BallAPI;
import cn.hamster3.mc.plugin.ball.common.config.BallConfig;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerInfo;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerType;
import cn.hamster3.mc.plugin.core.common.config.ConfigSection;
import cn.hamster3.mc.plugin.core.common.config.YamlConfig;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ListenerInfo;
import cn.hamster3.mc.plugin.core.bungee.util.CoreBungeeCordUtils;
import cn.hamster3.mc.plugin.core.common.api.CoreAPI;
import net.md_5.bungee.config.Configuration;
import org.jetbrains.annotations.NotNull;
import java.net.InetSocketAddress;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.Map;
import java.util.logging.Logger;
public class BallBungeeCordAPI extends BallAPI {
public BallBungeeCordAPI(@NotNull ConfigSection config) {
super(config, BallServerType.PROXY);
public BallBungeeCordAPI(@NotNull BallConfig ballConfig) {
super(ballConfig);
}
public static BallBungeeCordAPI getInstance() {
return (BallBungeeCordAPI) instance;
}
public static void init(@NotNull YamlConfig config) {
public static void init() {
if (instance != null) {
return;
}
instance = new BallBungeeCordAPI(config);
HamsterBallPlugin plugin = HamsterBallPlugin.getInstance();
Configuration config = CoreBungeeCordUtils.getPluginConfig(plugin);
Map<String, String> env = System.getenv();
BallServerInfo serverInfo = new BallServerInfo(
env.getOrDefault("BALL_LOCAL_SERVER_INFO_ID", config.getString("server-info.id")),
env.getOrDefault("BALL_LOCAL_SERVER_INFO_NAME", config.getString("server-info.name")),
BallServerType.PROXY,
env.getOrDefault("BALL_LOCAL_SERVER_IP", config.getString("server-info.host", "0.0.0.0")),
Integer.parseInt(
env.getOrDefault("BALL_LOCAL_SERVER_PORT", config.getString("server-info.port", "25577"))
)
);
DataSource datasource;
if (config.contains("datasource")) {
plugin.getLogger().info("启用仓鼠球自定义数据库连接池");
datasource = BallBungeeCordUtils.getDataSource(config.getSection("datasource"));
} else {
plugin.getLogger().info("复用 HamsterCore 的数据库连接池");
datasource = CoreAPI.getInstance().getDataSource();
}
BallConfig ballConfig = new BallConfig(
config.getBoolean("debug", false),
config.getString("channel-prefix", "") + ":",
config.getBoolean("game-server-update-player-info", false),
config.getStringList("load-player-info-filter"),
serverInfo,
datasource
);
instance = new BallBungeeCordAPI(ballConfig);
}
@Override
public void enable() throws SQLException, InterruptedException {
for (ListenerInfo listenerInfo : ProxyServer.getInstance().getConfig().getListeners()) {
if (!(listenerInfo.getSocketAddress() instanceof InetSocketAddress)) {
continue;
}
InetSocketAddress address = (InetSocketAddress) listenerInfo.getSocketAddress();
instance.getLocalServerInfo().setHost(address.getHostString());
instance.getLocalServerInfo().setPort(address.getPort());
}
super.enable();
}

View File

@@ -1,32 +0,0 @@
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,11 +2,12 @@ package cn.hamster3.mc.plugin.ball.bungee.listener;
import cn.hamster3.mc.plugin.ball.bungee.HamsterBallPlugin;
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.core.common.api.CoreAPI;
import cn.hamster3.mc.plugin.core.lib.net.kyori.adventure.audience.Audience;
import cn.hamster3.mc.plugin.core.lib.net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
import com.google.common.eventbus.Subscribe;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.config.ServerInfo;
@@ -22,6 +23,9 @@ public class BallBungeeListener {
@Subscribe
public void onDispatchConsoleCommand(DispatchConsoleCommandEvent event) {
if (event.getType() != null && event.getType() != BallServerType.PROXY) {
return;
}
if (event.getServerID() != null && !BallAPI.getInstance().isLocalServer(event.getServerID())) {
return;
}
@@ -31,6 +35,9 @@ public class BallBungeeListener {
@Subscribe
public void onDispatchPlayerCommandEvent(DispatchPlayerCommandEvent event) {
if (event.getType() != null && event.getType() != BallServerType.GAME) {
return;
}
ProxyServer server = ProxyServer.getInstance();
if (event.getUuid() != null) {
ProxiedPlayer player = server.getPlayer(event.getUuid());
@@ -48,9 +55,6 @@ public class BallBungeeListener {
@Subscribe
public void onKickPlayerEvent(KickPlayerEvent event) {
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(event.getUuid());
if (player == null) {
return;
}
BaseComponent[] components = BungeeComponentSerializer.get().serialize(event.getReason());
player.disconnect(components);
}
@@ -66,8 +70,8 @@ public class BallBungeeListener {
@Subscribe
public void onSendPlayerToLocationEvent(SendPlayerToLocationEvent event) {
String serverID = event.getLocation().getServerID();
ServerInfo toServer = ProxyServer.getInstance().getServerInfo(serverID);
if (toServer == null) {
ServerInfo serverInfo = ProxyServer.getInstance().getServerInfo(serverID);
if (serverInfo == null) {
HamsterBallPlugin.getInstance().getLogger().warning("试图传送玩家时失败: 服务器 " + serverID + " 不在线");
return;
}
@@ -79,12 +83,12 @@ public class BallBungeeListener {
if (player.getServer().getInfo().getName().equals(serverID)) {
continue;
}
player.connect(toServer);
player.connect(serverInfo);
}
}
@Subscribe
public void onSendPlayerToPlayer(SendPlayerToPlayerEvent event) {
public void onSendPlayerToPlayerEvent(SendPlayerToPlayerEvent event) {
UUID toPlayerUUID = event.getToPlayerUUID();
ProxiedPlayer toPlayer = ProxyServer.getInstance().getPlayer(toPlayerUUID);
if (toPlayer == null) {
@@ -94,9 +98,6 @@ public class BallBungeeListener {
ServerInfo toServer = toPlayer.getServer().getInfo();
for (UUID uuid : event.getSendPlayerUUID()) {
ProxiedPlayer sendPlayer = ProxyServer.getInstance().getPlayer(uuid);
if (sendPlayer == null) {
continue;
}
if (sendPlayer.getServer().getInfo().getName().equals(toServer.getName())) {
continue;
}

View File

@@ -1,59 +0,0 @@
package cn.hamster3.mc.plugin.ball.bungee.listener;
import cn.hamster3.mc.plugin.ball.common.api.BallAPI;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerInfo;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerType;
import cn.hamster3.mc.plugin.ball.common.event.server.ServerOfflineEvent;
import cn.hamster3.mc.plugin.ball.common.event.server.ServerOnlineEvent;
import com.google.common.eventbus.Subscribe;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ServerInfo;
import java.net.InetSocketAddress;
import java.util.Map;
public class BungeeServerListener {
public static final BungeeServerListener INSTANCE = new BungeeServerListener();
private BungeeServerListener() {
}
public void onEnable() {
for (BallServerInfo info : BallAPI.getInstance().getAllServerInfo().values()) {
if (info.getType() != BallServerType.GAME) {
continue;
}
ProxyServer.getInstance().getServers().put(info.getId(), getServerInfo(info));
BallAPI.getInstance().getLogger().info("已添加服务器入口: " + info.getId());
}
}
@Subscribe
public void onServerOnline(ServerOnlineEvent event) {
if (event.getType() != BallServerType.GAME) {
return;
}
ProxyServer.getInstance().getServers().put(event.getId(), getServerInfo(event));
BallAPI.getInstance().getLogger().info("已添加服务器入口: " + event.getId());
}
@Subscribe
public void onServerOffline(ServerOfflineEvent event) {
if (event.getType() != BallServerType.GAME) {
return;
}
Map<String, ServerInfo> map = ProxyServer.getInstance().getServers();
if (map.remove(event.getId()) != null) {
BallAPI.getInstance().getLogger().info("已移除服务器入口: " + event.getId());
}
}
private ServerInfo getServerInfo(BallServerInfo serverInfo) {
return ProxyServer.getInstance().constructServerInfo(
serverInfo.getId(),
new InetSocketAddress(serverInfo.getHost(), serverInfo.getPort()),
serverInfo.getName(),
false
);
}
}

View File

@@ -5,15 +5,20 @@ 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.player.BallPlayerInfoUpdateEvent;
import cn.hamster3.mc.plugin.core.common.api.CoreAPI;
import cn.hamster3.mc.plugin.core.lib.com.zaxxer.hikari.HikariConfig;
import cn.hamster3.mc.plugin.core.lib.com.zaxxer.hikari.HikariDataSource;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.config.Configuration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.logging.Level;
@SuppressWarnings("CallToPrintStackTrace")
public final class BallBungeeCordUtils {
private BallBungeeCordUtils() {
}
@@ -44,7 +49,7 @@ public final class BallBungeeCordUtils {
statement.executeUpdate();
}
} catch (SQLException e) {
BallAPI.getInstance().getLogger().log(Level.SEVERE, "更新玩家数据时遇到了一个异常", e);
e.printStackTrace();
}
if (!BallAPI.getInstance().getBallConfig().isGameServerUpdatePlayerInfo()) {
BallAPI.getInstance().sendRawBallMessage(
@@ -55,4 +60,32 @@ public final class BallBungeeCordUtils {
}
});
}
@Nullable
public static DataSource getDataSource(@Nullable Configuration datasourceConfig) {
if (datasourceConfig == null) {
return null;
}
try {
HikariConfig hikariConfig = new HikariConfig();
String driver = datasourceConfig.getString("driver");
hikariConfig.setDriverClassName(driver);
hikariConfig.setJdbcUrl(datasourceConfig.getString("url"));
hikariConfig.setUsername(datasourceConfig.getString("username"));
hikariConfig.setPassword(datasourceConfig.getString("password"));
hikariConfig.setMaximumPoolSize(datasourceConfig.getInt("maximum-pool-size", 3));
hikariConfig.setMinimumIdle(datasourceConfig.getInt("minimum-idle", 1));
long keepAliveTime = datasourceConfig.getLong("keep-alive-time", 0);
if (keepAliveTime > 5000) {
hikariConfig.setKeepaliveTime(keepAliveTime);
}
hikariConfig.setIdleTimeout(datasourceConfig.getLong("idle-timeout", 10 * 60 * 1000));
hikariConfig.setMaxLifetime(datasourceConfig.getLong("max-lifetime", 30 * 60 * 1000));
hikariConfig.setValidationTimeout(datasourceConfig.getLong("validation-timeout", 5000));
hikariConfig.setPoolName("HamsterBall-Pool");
return new HikariDataSource(hikariConfig);
} catch (Exception | Error e) {
return null;
}
}
}

View File

@@ -3,15 +3,7 @@ main: cn.hamster3.mc.plugin.ball.bungee.HamsterBallPlugin
version: ${version}
author: MiniDay
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/
description: 仓鼠球:一个基于 Redis 的 Minecraft 服务端通用消息中间件原HamsterService
depend:
- HamsterCore

View File

@@ -1,10 +1,6 @@
# 是否允许在控制台输出调试信息
debug: false
# 是否启用服务器 ID 单例模式
# 启用后,服务器唯一识别码 相同的服务器将无法启动
singleton-server-id: false
# 频道名前缀
# 使用这个配置选项可以划分子服消息通信分组
# 只有在同一个频道名的子服才能互相通信
@@ -12,14 +8,12 @@ channel-prefix: ""
# 是否在子服端更新玩家信息
# 默认情况下BC 统一管理玩家信息,包括记录 UUID 和玩家名称
# 如果一个群组服同时拥有多个 BC 入口,且每个 BC 入口为不同的玩家名称分配不同的 UUID
# 如果一个群组服同时拥有多个 BC 入口
# 且每个 BC 入口为不同的玩家名称分配不同的 UUID
# (例如正版、盗版双入口,或网易多入口接同一个子服)
# 则可以启用该功能以防止同一个名称占用多个 UUID 的问题
# 则可以启用该功能以防止 UUID 紊乱的问题
game-server-update-player-info: false
# 启用后,子服启动时会自动注册该子服的入口配置,关闭时也会自动移除该子服的入口配置
auto-register-game-server: false
# 本服务器信息
server-info:
# 服务器唯一识别码,最长 32 字符
@@ -40,20 +34,21 @@ server-info:
# 这个选项就会很有用
#datasource:
# # 数据库链接驱动地址
# driver: "com.mysql.cj.jdbc.Driver"
# # MySQL数据库链接填写格式:
# driver: "com.mysql.jdbc.Driver"
# # 数据库链接填写格式:
# # jdbc:mysql://{数据库地址}:{数据库端口}/{使用的库名}?参数
# url: "jdbc:mysql://localhost:3306/Test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true"
# # 除非你知道自己在做什么,否则不建议随意更改参数
# url: "jdbc:mysql://localhost:3306/Test1?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true"
# # 用户名
# username: "root"
# username: "Test"
# # 密码
# password: "Root123.."
# password: "Test123.."
# # 最小闲置链接数
# # 推荐值1~3
# minimum-idle: 0
# # 最大链接数
# # 推荐值:不低于5
# maximum-pool-size: 5
# # 推荐值:不低于3
# maximum-pool-size: 3
# # 保持连接池可用的间隔
# # 除非你的服务器数据库连接经常断开,否则不建议启用该选项
# # 单位:毫秒

View File

@@ -1,7 +1,7 @@
@file:Suppress("VulnerableLibrariesLocal")
@file:Suppress("VulnerableLibrariesLocal", "GradlePackageVersionRange", "GradlePackageUpdate")
dependencies {
compileOnly("cn.hamster3.mc.plugin:core-common:+")
compileOnly("cn.hamster3.mc.plugin:core-common:1.2.0")
compileOnly("com.google.code.gson:gson:2.8.0")
compileOnly("com.google.guava:guava:31.0-jre")

View File

@@ -13,30 +13,24 @@ import cn.hamster3.mc.plugin.ball.common.event.server.ServerOfflineEvent;
import cn.hamster3.mc.plugin.ball.common.listener.BallCommonListener;
import cn.hamster3.mc.plugin.ball.common.listener.BallDebugListener;
import cn.hamster3.mc.plugin.ball.common.listener.BallRedisListener;
import cn.hamster3.mc.plugin.ball.common.thread.LockUpdateThread;
import cn.hamster3.mc.plugin.core.common.api.CoreAPI;
import cn.hamster3.mc.plugin.core.common.config.ConfigSection;
import cn.hamster3.mc.plugin.core.common.data.DisplayMessage;
import cn.hamster3.mc.plugin.core.common.util.CoreUtils;
import cn.hamster3.mc.plugin.core.lib.io.lettuce.core.pubsub.StatefulRedisPubSubConnection;
import cn.hamster3.mc.plugin.core.lib.net.kyori.adventure.text.Component;
import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.EventBus;
import lombok.Getter;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import redis.clients.jedis.Jedis;
import javax.sql.DataSource;
import java.sql.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
@SuppressWarnings("unused")
@Getter
@SuppressWarnings({"unused", "CallToPrintStackTrace"})
public abstract class BallAPI {
/**
* API 使用的通信频道
@@ -46,53 +40,32 @@ public abstract class BallAPI {
* API 使用的玩家信息更新通信频道
*/
public static final String PLAYER_INFO_CHANNEL = "HamsterBall:PlayerInfo";
/**
* API 实例
*/
@Getter
protected static BallAPI instance;
@NotNull
private final BallConfig ballConfig;
@NotNull
private final DataSource datasource;
@NotNull
private final BallServerInfo localServerInfo;
@NotNull
private final EventBus eventBus;
@NotNull
private final Map<String, BallServerInfo> allServerInfo;
@NotNull
private final Map<UUID, BallPlayerInfo> allPlayerInfo;
@NotNull
private final Jedis redisSub;
@Nullable
private ScheduledFuture<?> lockUpdater;
private final StatefulRedisPubSubConnection<String, BallMessage> redisPub;
@NotNull
private final StatefulRedisPubSubConnection<String, BallMessage> redisSub;
public BallAPI(@NotNull ConfigSection config, @NotNull BallServerType type) {
ConfigSection serverInfoConfig = config.getSection("server-info");
if (serverInfoConfig == null) {
throw new IllegalArgumentException("配置文件中未找到 server-info 节点");
}
localServerInfo = new BallServerInfo(serverInfoConfig, type);
ConfigSection section = config.getSection("datasource");
if (section != null) {
getLogger().info("启用仓鼠球自定义数据库连接池");
datasource = CoreUtils.getDataSource(section);
} else {
getLogger().info("复用 HamsterCore 的数据库连接池");
datasource = CoreAPI.getInstance().getDataSource();
}
ballConfig = new BallConfig(config);
eventBus = new AsyncEventBus("HamsterBall", CoreAPI.getInstance().getExecutorService());
eventBus.register(BallCommonListener.INSTANCE);
public BallAPI(@NotNull BallConfig ballConfig) {
this.ballConfig = ballConfig;
redisPub = CoreAPI.getInstance().getRedisClient().connectPubSub(BallMessage.REDIS_CODEC);
redisSub = CoreAPI.getInstance().getRedisClient().connectPubSub(BallMessage.REDIS_CODEC);
allServerInfo = new ConcurrentHashMap<>();
allPlayerInfo = new ConcurrentHashMap<>();
redisSub = CoreAPI.getInstance().getJedisPool().getResource();
eventBus = new AsyncEventBus("HamsterBall - EventBus", CoreAPI.getInstance().getExecutorService());
eventBus.register(BallCommonListener.INSTANCE);
getLogger().info("频道前缀: " + ballConfig.getChannelPrefix());
getLogger().info("启用子服更新玩家状态: " + ballConfig.isGameServerUpdatePlayerInfo());
if (ballConfig.isGameServerUpdatePlayerInfo()) {
@@ -102,46 +75,57 @@ public abstract class BallAPI {
getLogger().warning("已启用调试模式");
eventBus.register(BallDebugListener.INSTANCE);
}
CoreAPI.getInstance().getExecutorService().submit(() -> redisSub.subscribe(BallRedisListener.INSTANCE, BALL_CHANNEL));
}
protected void enable() throws SQLException, InterruptedException {
try (Jedis jedis = CoreAPI.getInstance().getJedisPool().getResource()) {
String key = "HamsterBall:ServerInfo:" + localServerInfo.getId();
if (jedis.exists(key) && ballConfig.isSingletonServerID()) {
throw new IllegalStateException("已经有一个服务器占用了该 ID");
}
jedis.hset(key, "id", localServerInfo.getId());
jedis.hset(key, "name", localServerInfo.getName());
jedis.hset(key, "type", localServerInfo.getType().name());
jedis.hset(key, "host", localServerInfo.getHost());
jedis.hset(key, "port", String.valueOf(localServerInfo.getPort()));
jedis.expire(key, 180);
lockUpdater = CoreAPI.getInstance().getScheduledService().scheduleAtFixedRate(LockUpdateThread.INSTANCE, 1, 1, TimeUnit.MINUTES);
for (String serverInfoKey : jedis.keys("HamsterBall:ServerInfo:*")) {
BallServerInfo info = new BallServerInfo(
jedis.hget(serverInfoKey, "id"),
jedis.hget(serverInfoKey, "name"),
BallServerType.valueOf(jedis.hget(serverInfoKey, "type")),
jedis.hget(serverInfoKey, "host"),
Integer.parseInt(jedis.hget(serverInfoKey, "port"))
);
allServerInfo.put(info.getId(), info);
}
}
BallServerInfo localInfo = getLocalServerInfo();
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_server_info`(" +
"`id` VARCHAR(32) PRIMARY KEY NOT NULL," +
"`name` VARCHAR(32) NOT NULL," +
"`type` VARCHAR(16) NOT NULL," +
"`host` VARCHAR(32) NOT NULL," +
"`port` INT 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;");
}
try (PreparedStatement statement = connection.prepareStatement(
"REPLACE INTO `hamster_ball_server_info` VALUES(?, ?, ?, ?, ?);"
)) {
statement.setString(1, localInfo.getId());
statement.setString(2, localInfo.getName());
statement.setString(3, localInfo.getType().name());
statement.setString(4, localInfo.getHost());
statement.setInt(5, localInfo.getPort());
statement.executeUpdate();
}
try (PreparedStatement statement = connection.prepareStatement(
"SELECT * FROM `hamster_ball_server_info`;"
)) {
try (ResultSet set = statement.executeQuery()) {
while (set.next()) {
String serverID = set.getString("id");
allServerInfo.put(serverID, new BallServerInfo(
serverID,
set.getString("name"),
BallServerType.valueOf(set.getString("type")),
set.getString("host"),
set.getInt("port")
));
}
}
}
if (getBallConfig().isGameServerUpdatePlayerInfo()) {
try (Statement statement = connection.createStatement()) {
@@ -180,21 +164,22 @@ public abstract class BallAPI {
}
getLogger().info("从数据库中加载了 " + allServerInfo.size() + " 条服务器信息");
getLogger().info("从数据库中加载了 " + allPlayerInfo.size() + " 条玩家信息");
redisPub.addListener(BallRedisListener.INSTANCE);
subscribeIgnorePrefix(BALL_CHANNEL);
}
protected void disable() throws SQLException, InterruptedException {
sendBallMessage(BallAPI.BALL_CHANNEL, new BallMessage(
BallActions.ServerOffline.name(), new ServerOfflineEvent(getLocalServerInfo())
), false, true);
if (lockUpdater != null) {
lockUpdater.cancel(true);
lockUpdater = null;
try (Jedis jedis = CoreAPI.getInstance().getJedisPool().getResource()) {
String key = "HamsterBall:ServerInfo:" + localServerInfo.getId();
jedis.del(key);
}
}
try (Connection connection = getDatasource().getConnection()) {
try (PreparedStatement statement = connection.prepareStatement(
"DELETE FROM `hamster_ball_server_info` WHERE `id`=?;"
)) {
statement.setString(1, getLocalServerId());
statement.executeUpdate();
}
try (PreparedStatement statement = connection.prepareStatement(
"UPDATE `hamster_ball_player_info` SET `online`=false WHERE `game_server`=? OR `proxy_server`=?"
)) {
@@ -203,7 +188,6 @@ public abstract class BallAPI {
statement.executeUpdate();
}
}
redisSub.close();
}
/**
@@ -257,24 +241,25 @@ public abstract class BallAPI {
*/
public void dispatchConsoleCommand(@Nullable BallServerType type, @Nullable String serverID, @NotNull String command) {
sendBallMessage(BALL_CHANNEL, new BallMessage(
getLocalServerId(), null, type,
getLocalServerId(), null, BallServerType.GAME,
BallActions.DispatchConsoleCommand.name(),
CoreAPI.getInstance().getGson().toJsonTree(new DispatchConsoleCommandEvent(serverID, command))
CoreAPI.getInstance().getGson().toJsonTree(new DispatchConsoleCommandEvent(type, serverID, command))
), false);
}
/**
* 强制玩家执行命令
*
* @param type 执行对象的服务端类型null代表所有类型
* @param uuid 执行对象的 UUIDnull代表所有玩家
* @param type 执行对象的服务端类型
* @param uuid 执行对象的 UUID
* @param command 命令内容
*/
public void dispatchPlayerCommand(@Nullable BallServerType type, @Nullable UUID uuid, @NotNull String command) {
sendBallMessage(BALL_CHANNEL, new BallMessage(
getLocalServerId(), null, type,
getLocalServerId(), null, BallServerType.GAME,
BallActions.DispatchPlayerCommand.name(),
CoreAPI.getInstance().getGson().toJsonTree(new DispatchPlayerCommandEvent(uuid, command))
CoreAPI.getInstance().getGson().toJsonTree(new DispatchPlayerCommandEvent(type, uuid, command))
), false);
}
@@ -336,7 +321,7 @@ public abstract class BallAPI {
statement.executeUpdate();
}
} catch (Exception e) {
BallAPI.getInstance().getLogger().log(Level.SEVERE, "更新玩家缓存消息时遇到了一个异常", e);
e.printStackTrace();
}
}
}
@@ -485,15 +470,13 @@ public abstract class BallAPI {
channel = ballConfig.getChannelPrefix() + channel;
}
if (block) {
try (Jedis jedis = CoreAPI.getInstance().getJedisPool().getResource()) {
jedis.publish(channel, CoreAPI.getInstance().getGson().toJson(message));
}
redisSub.sync().publish(channel, message);
eventBus.post(new MessageSentEvent(channel, message));
} else {
@NotNull String finalChannel = channel;
CoreAPI.getInstance().getExecutorService().submit(() -> {
try (Jedis jedis = CoreAPI.getInstance().getJedisPool().getResource()) {
jedis.publish(finalChannel, CoreAPI.getInstance().getGson().toJson(message));
redisSub.async().publish(channel, message).whenComplete((aLong, throwable) -> {
if (throwable != null) {
return;
}
eventBus.post(new MessageSentEvent(finalChannel, message));
});
@@ -505,29 +488,22 @@ public abstract class BallAPI {
* <p>
* 会自动加上 config 中设置的频道前缀
*
* @param channels 频道名称
* @param channel 频道名称
*/
public void subscribe(@NotNull String... channels) {
for (int i = 0; i < channels.length; i++) {
channels[i] = ballConfig.getChannelPrefix() + channels[i];
public void subscribe(@NotNull String... channel) {
for (int i = 0; i < channel.length; i++) {
channel[i] = ballConfig.getChannelPrefix() + channel[i];
}
subscribeRaw(channels);
redisPub.sync().subscribe(channel);
}
/**
* 忽略频道前缀配置,订阅 redis 消息频道
*
* @param channels 频道名称
* @param channel 频道名称
*/
public void subscribeRaw(@NotNull String... channels) {
BallRedisListener.INSTANCE.subscribe(channels);
// CoreAPI.getInstance().getExecutorService().submit(() -> {
// try {
// redisSub.subscribe(BallRedisListener.INSTANCE, channels);
// } catch (Exception | Error e) {
// e.printStackTrace();
// }
// });
public void subscribeIgnorePrefix(@NotNull String... channel) {
redisPub.sync().subscribe(channel);
}
/**
@@ -536,33 +512,28 @@ public abstract class BallAPI {
* @param patterns 频道名称正则表达式
*/
public void subscribePatterns(@NotNull String patterns) {
BallRedisListener.INSTANCE.psubscribe(patterns);
// CoreAPI.getInstance().getExecutorService().submit(
// () -> redisSub.psubscribe(BallRedisListener.INSTANCE, patterns)
// );
redisPub.sync().psubscribe(patterns);
}
/**
* 取消订阅 redis 消息频道
* <p>
* 会自动加上 config 中设置的频道前缀
* 取消订阅 redis 频道
*
* @param channels 频道名称
* @param channel 频道名称
*/
public void unsubscribe(@NotNull String... channels) {
for (int i = 0; i < channels.length; i++) {
channels[i] = ballConfig.getChannelPrefix() + channels[i];
public void unsubscribe(@NotNull String... channel) {
for (int i = 0; i < channel.length; i++) {
channel[i] = ballConfig.getChannelPrefix() + channel[i];
}
unsubscribeRaw(channels);
redisPub.sync().unsubscribe(channel);
}
/**
* 忽略频道前缀配置,取消订阅 redis 消息频道
* 忽略仓鼠球频道前缀配置,取消订阅 redis 频道
*
* @param channels 频道名称
* @param channel 频道名称
*/
public void unsubscribeRaw(@NotNull String... channels) {
BallRedisListener.INSTANCE.unsubscribe(channels);
public void unsubscribeIgnorePrefix(@NotNull String... channel) {
redisPub.sync().unsubscribe(channel);
}
/**
@@ -571,12 +542,22 @@ public abstract class BallAPI {
* @param patterns 频道名称正则表达式
*/
public void unsubscribePatterns(@NotNull String patterns) {
BallRedisListener.INSTANCE.punsubscribe(patterns);
redisPub.sync().punsubscribe(patterns);
}
/**
* 获取本地服务器ID
*
* @return 服务器ID
*/
@NotNull
public BallServerInfo getLocalServerInfo() {
return ballConfig.getServerInfo();
}
@NotNull
public String getLocalServerId() {
return localServerInfo.getId();
return ballConfig.getServerInfo().getId();
}
/**
@@ -693,4 +674,9 @@ public abstract class BallAPI {
@NotNull
public abstract Logger getLogger();
@NotNull
public DataSource getDatasource() {
return ballConfig.getDatasource() == null ? CoreAPI.getInstance().getDataSource() : ballConfig.getDatasource();
}
}

View File

@@ -1,21 +0,0 @@
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

@@ -1,77 +0,0 @@
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

@@ -1,75 +0,0 @@
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

@@ -1,75 +0,0 @@
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

@@ -1,72 +0,0 @@
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

@@ -1,101 +0,0 @@
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

@@ -1,14 +0,0 @@
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

@@ -1,24 +0,0 @@
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

@@ -1,151 +0,0 @@
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

@@ -1,29 +1,24 @@
package cn.hamster3.mc.plugin.ball.common.config;
import cn.hamster3.mc.plugin.core.common.config.ConfigSection;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerInfo;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.sql.DataSource;
import java.util.List;
@Getter
@AllArgsConstructor
public class BallConfig {
private boolean debug;
private boolean singletonServerID;
@NotNull
private String channelPrefix;
private boolean gameServerUpdatePlayerInfo;
@NotNull
private List<String> loadPlayerInfoFilter;
public BallConfig(@NotNull ConfigSection config) {
debug = config.getBoolean("debug", false);
singletonServerID = config.getBoolean("singleton-server-id", false);
channelPrefix = config.getString("channel-prefix", "");
channelPrefix = channelPrefix.isEmpty() ? channelPrefix : channelPrefix + ":";
gameServerUpdatePlayerInfo = config.getBoolean("game-server-update-player-info", false);
loadPlayerInfoFilter = config.getStringList("load-player-info-filter");
}
@NotNull
private BallServerInfo serverInfo;
@Nullable
private DataSource datasource;
}

View File

@@ -3,10 +3,11 @@ package cn.hamster3.mc.plugin.ball.common.data;
import cn.hamster3.mc.plugin.ball.common.api.BallAPI;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerType;
import cn.hamster3.mc.plugin.core.common.api.CoreAPI;
import cn.hamster3.mc.plugin.core.lib.io.lettuce.core.codec.RedisCodec;
import cn.hamster3.mc.plugin.core.lib.io.lettuce.core.codec.StringCodec;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@@ -15,6 +16,7 @@ import org.jetbrains.annotations.Nullable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.UUID;
/**
@@ -23,9 +25,33 @@ import java.util.UUID;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@SuppressWarnings("unused")
public class BallMessage {
/**
* lettuce 编解码器
*/
public static final RedisCodec<String, BallMessage> REDIS_CODEC = new RedisCodec<String, BallMessage>() {
@Override
public String decodeKey(ByteBuffer bytes) {
return StringCodec.UTF8.decodeKey(bytes);
}
@Override
public BallMessage decodeValue(ByteBuffer bytes) {
String string = StringCodec.UTF8.decodeValue(bytes);
return CoreAPI.getInstance().getGson().fromJson(string, BallMessage.class);
}
@Override
public ByteBuffer encodeKey(String key) {
return StringCodec.UTF8.encodeKey(key);
}
@Override
public ByteBuffer encodeValue(BallMessage value) {
return StringCodec.UTF8.encodeValue(CoreAPI.getInstance().getGson().toJson(value));
}
};
/**
* 消息发送者
*/
@@ -74,6 +100,34 @@ public class BallMessage {
this.content = CoreAPI.getInstance().getGson().toJsonTree(content);
}
public BallMessage(@NotNull String senderID, @Nullable String receiverID, @Nullable BallServerType receiverType, @NotNull String action, @Nullable JsonElement content) {
this.senderID = senderID;
this.receiverID = receiverID;
this.receiverType = receiverType;
this.action = action;
this.content = content;
}
/**
* 序列化至 Json
*
* @return json对象
*/
@NotNull
public JsonObject toJson() {
JsonObject object = new JsonObject();
object.addProperty("senderID", senderID);
if (receiverID != null) {
object.addProperty("toServer", receiverID);
}
if (receiverType != null) {
object.addProperty("toServer", receiverType.name());
}
object.addProperty("action", action);
object.add("content", content);
return object;
}
/**
* 以 Java 对象获取消息内容
*
@@ -145,6 +199,6 @@ public class BallMessage {
@Override
public String toString() {
return CoreAPI.getInstance().getGson().toJson(this);
return toJson().toString();
}
}

View File

@@ -1,12 +1,9 @@
package cn.hamster3.mc.plugin.ball.common.entity;
import cn.hamster3.mc.plugin.core.common.config.ConfigSection;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
import java.util.Objects;
/**
@@ -43,15 +40,6 @@ public class BallServerInfo {
*/
private int port;
public BallServerInfo(@NotNull ConfigSection config, @NotNull BallServerType type) {
Map<String, String> env = System.getenv();
id = env.getOrDefault("BALL_SERVER_ID", config.getString("id"));
name = env.getOrDefault("BALL_SERVER_NAME", config.getString("name"));
this.type = type;
host = "0.0.0.0";
port = 0;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View File

@@ -13,6 +13,7 @@ public enum BallActions {
BallPlayerLogin,
BallPlayerPostLogin,
BallPlayerPreConnectServer,
BallPlayerConnectServer,
BallPlayerPostConnectServer,
BallPlayerLogout,

View File

@@ -1,6 +1,7 @@
package cn.hamster3.mc.plugin.ball.common.event.message;
import cn.hamster3.mc.plugin.ball.common.data.BallMessage;
import com.google.gson.JsonObject;
import lombok.Getter;
import lombok.Setter;
import org.jetbrains.annotations.NotNull;
@@ -22,4 +23,11 @@ public class MessageEvent extends BallMessage {
setAction(message.getAction());
setContent(message.getContent());
}
@Override
public @NotNull JsonObject toJson() {
JsonObject object = super.toJson();
object.addProperty("channel", channel);
return object;
}
}

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
package cn.hamster3.mc.plugin.ball.common.event.operate;
import cn.hamster3.mc.plugin.core.lib.net.kyori.adventure.text.Component;
import lombok.AllArgsConstructor;
import lombok.Data;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;

View File

@@ -0,0 +1,27 @@
package cn.hamster3.mc.plugin.ball.common.event.player;
import cn.hamster3.mc.plugin.ball.common.entity.BallPlayerInfo;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* 玩家进入子服
* <p>
* 仅在使用 velocity 代理端时才会触发这个事件
*
* @see BallPlayerPreConnectServerEvent 玩家准备进入子服
* @see BallPlayerConnectServerEvent 玩家进入子服
* @see BallPlayerPostConnectServerEvent 玩家已经进入子服
*/
@Data
@AllArgsConstructor
public class BallPlayerConnectServerEvent {
@NotNull
private final BallPlayerInfo playerInfo;
@Nullable
private final String from;
@NotNull
private final String to;
}

View File

@@ -9,6 +9,7 @@ import org.jetbrains.annotations.NotNull;
* 玩家已经进入子服
*
* @see BallPlayerPreConnectServerEvent 玩家准备进入子服
* @see BallPlayerConnectServerEvent 玩家进入子服
* @see BallPlayerPostConnectServerEvent 玩家已经进入子服
*/
@Data

View File

@@ -10,6 +10,7 @@ import org.jetbrains.annotations.Nullable;
* 玩家准备进入子服
*
* @see BallPlayerPreConnectServerEvent 玩家准备进入子服
* @see BallPlayerConnectServerEvent 玩家进入子服
* @see BallPlayerPostConnectServerEvent 玩家已经进入子服
*/
@Data

View File

@@ -1,18 +0,0 @@
package cn.hamster3.mc.plugin.ball.common.event.server;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerInfo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 服务器上线
*/
@Data
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class BallServerEvent extends BallServerInfo {
public BallServerEvent(BallServerInfo info) {
super(info.getId(), info.getName(), info.getType(), info.getHost(), info.getPort());
}
}

View File

@@ -3,16 +3,19 @@ package cn.hamster3.mc.plugin.ball.common.event.server;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerInfo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.jetbrains.annotations.NotNull;
/**
* 服务器离线
*/
@Data
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class ServerOfflineEvent extends BallServerEvent {
public ServerOfflineEvent(BallServerInfo info) {
super(info);
public class ServerOfflineEvent {
@NotNull
private final BallServerInfo serverInfo;
@NotNull
public String getServerID() {
return serverInfo.getId();
}
}

View File

@@ -3,16 +3,15 @@ package cn.hamster3.mc.plugin.ball.common.event.server;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerInfo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.jetbrains.annotations.NotNull;
/**
* 服务器上线
*/
@Data
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class ServerOnlineEvent extends BallServerEvent {
public ServerOnlineEvent(BallServerInfo info) {
super(info);
}
public class ServerOnlineEvent {
@NotNull
private final BallServerInfo serverInfo;
}

View File

@@ -83,6 +83,11 @@ public class BallCommonListener {
BallAPI.getInstance().getEventBus().post(event);
break;
}
case BallPlayerConnectServer: {
BallPlayerConnectServerEvent event = CoreAPI.getInstance().getGson().fromJson(message.getContent(), BallPlayerConnectServerEvent.class);
BallAPI.getInstance().getEventBus().post(event);
break;
}
case BallPlayerPostConnectServer: {
BallPlayerPostConnectServerEvent event = CoreAPI.getInstance().getGson().fromJson(message.getContent(), BallPlayerPostConnectServerEvent.class);
BallAPI.getInstance().getEventBus().post(event);
@@ -120,6 +125,12 @@ public class BallCommonListener {
BallAPI.getInstance().getEventBus().post(event);
}
@Subscribe
public void onBallPlayerConnectServer(BallPlayerConnectServerEvent event) {
BallPlayerInfo info = event.getPlayerInfo();
BallAPI.getInstance().getAllPlayerInfo().put(info.getUuid(), info);
}
@Subscribe
public void onBallPlayerInfoUpdate(BallPlayerInfoUpdateEvent event) {
BallPlayerInfo info = event.getPlayerInfo();
@@ -128,11 +139,12 @@ public class BallCommonListener {
@Subscribe
public void onServerOnline(ServerOnlineEvent event) {
BallAPI.getInstance().getAllServerInfo().put(event.getId(), event);
switch (event.getType()) {
BallServerInfo info = event.getServerInfo();
BallAPI.getInstance().getAllServerInfo().put(info.getId(), info);
switch (info.getType()) {
case GAME: {
BallAPI.getInstance().getAllPlayerInfo().forEach((uuid, playerInfo) -> {
if (playerInfo.getGameServer().equals(event.getId())) {
if (playerInfo.getGameServer().equals(info.getId())) {
playerInfo.setOnline(false);
}
});
@@ -140,7 +152,7 @@ public class BallCommonListener {
}
case PROXY: {
BallAPI.getInstance().getAllPlayerInfo().forEach((uuid, playerInfo) -> {
if (playerInfo.getProxyServer().equals(event.getId())) {
if (playerInfo.getProxyServer().equals(info.getId())) {
playerInfo.setOnline(false);
}
});
@@ -151,7 +163,7 @@ public class BallCommonListener {
@Subscribe
public void onServerOffline(ServerOfflineEvent event) {
String serverID = event.getId();
String serverID = event.getServerID();
BallServerInfo info = BallAPI.getInstance().getAllServerInfo().remove(serverID);
if (info == null) {
return;

View File

@@ -3,62 +3,57 @@ package cn.hamster3.mc.plugin.ball.common.listener;
import cn.hamster3.mc.plugin.ball.common.api.BallAPI;
import cn.hamster3.mc.plugin.ball.common.data.BallMessage;
import cn.hamster3.mc.plugin.ball.common.event.message.MessageReceivedEvent;
import cn.hamster3.mc.plugin.core.common.api.CoreAPI;
import cn.hamster3.mc.plugin.core.lib.io.lettuce.core.pubsub.RedisPubSubListener;
import com.google.common.eventbus.EventBus;
import redis.clients.jedis.JedisPubSub;
public class BallRedisListener extends JedisPubSub {
public class BallRedisListener implements RedisPubSubListener<String, BallMessage> {
public static final BallRedisListener INSTANCE = new BallRedisListener();
private BallRedisListener() {
}
@Override
public void onMessage(String channel, String message) {
CoreAPI.getInstance().getExecutorService().submit(() -> {
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);
BallAPI ballAPI = BallAPI.getInstance();
EventBus eventBus = ballAPI.getEventBus();
if (ballMessage.getReceiverType() != null && ballMessage.getReceiverType() != ballAPI.getLocalServerInfo().getType()) {
return;
}
if (ballMessage.getReceiverID() != null && !ballAPI.isLocalServer(ballMessage.getReceiverID())) {
return;
}
eventBus.post(new MessageReceivedEvent(finalChannel, ballMessage));
} catch (Exception | Error e) {
e.printStackTrace();
}
});
public void message(String channel, BallMessage ballMessage) {
if (channel.startsWith(BallAPI.getInstance().getBallConfig().getChannelPrefix())) {
channel = channel.substring(BallAPI.getInstance().getBallConfig().getChannelPrefix().length());
}
BallAPI ballAPI = BallAPI.getInstance();
EventBus eventBus = ballAPI.getEventBus();
if (ballMessage.getReceiverType() != null && ballMessage.getReceiverType() != ballAPI.getLocalServerInfo().getType()) {
return;
}
if (ballMessage.getReceiverID() != null && !ballAPI.isLocalServer(ballMessage.getReceiverID())) {
return;
}
try {
eventBus.post(new MessageReceivedEvent(channel, ballMessage));
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onPMessage(String pattern, String channel, String message) {
onMessage(channel, message);
public void message(String pattern, String channel, BallMessage info) {
message(channel, info);
}
@Override
public void onSubscribe(String channel, int subscribedChannels) {
public void subscribed(String channel, long count) {
BallAPI.getInstance().getLogger().info("已订阅 redis 频道 " + channel);
}
@Override
public void onUnsubscribe(String channel, int subscribedChannels) {
public void psubscribed(String pattern, long count) {
BallAPI.getInstance().getLogger().info("已取消订阅 redis 频道(正则) " + pattern);
}
@Override
public void unsubscribed(String channel, long count) {
BallAPI.getInstance().getLogger().info("已取消订阅 redis 频道 " + channel);
}
@Override
public void onPSubscribe(String pattern, int subscribedChannels) {
BallAPI.getInstance().getLogger().info("已订阅 redis 频道(正则) " + pattern);
}
@Override
public void onPUnsubscribe(String pattern, int subscribedChannels) {
public void punsubscribed(String pattern, long count) {
BallAPI.getInstance().getLogger().info("已取消订阅 redis 频道(正则) " + pattern);
}
}

View File

@@ -1,20 +0,0 @@
package cn.hamster3.mc.plugin.ball.common.thread;
import cn.hamster3.mc.plugin.ball.common.api.BallAPI;
import cn.hamster3.mc.plugin.core.common.api.CoreAPI;
import redis.clients.jedis.Jedis;
public class LockUpdateThread implements Runnable {
public static final LockUpdateThread INSTANCE = new LockUpdateThread();
private LockUpdateThread() {
}
@Override
public void run() {
String key = "HamsterBall:ServerInfo:" + BallAPI.getInstance().getLocalServerInfo().getId();
try (Jedis jedis = CoreAPI.getInstance().getJedisPool().getResource()) {
jedis.expire(key, 180);
}
}
}

View File

@@ -1,41 +0,0 @@
@file:Suppress("VulnerableLibrariesLocal")
evaluationDependsOn(":ball-common")
dependencies {
api(project(":ball-common")) { isTransitive = false }
compileOnly("com.velocitypowered:velocity-api:3.3.0-SNAPSHOT")
annotationProcessor("com.velocitypowered:velocity-api:3.3.0-SNAPSHOT")
compileOnly("cn.hamster3.mc.plugin:core-common:+")
}
sourceSets.create("templates") {
java {
srcDir("src/main/templates")
}
}
val templateSource = file("src/main/templates")
val templateDest = layout.buildDirectory.dir("generated/sources/templates")
val generateTemplates = tasks.register<Copy>("generateTemplates") {
from(templateSource)
into(templateDest)
expand(project.properties)
}
sourceSets.main.get().java.srcDir(generateTemplates.map { it.outputs })
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
withSourcesJar()
}
tasks {
withType<Jar> {
archiveBaseName = "HamsterBall-Velocity"
}
shadowJar {
destinationDirectory = rootProject.layout.buildDirectory
}
}

View File

@@ -1,154 +0,0 @@
package cn.hamster3.mc.plugin.ball.velocity;
import cn.hamster3.mc.plugin.ball.common.api.BallAPI;
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;
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;
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
import com.velocitypowered.api.plugin.Dependency;
import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer;
import lombok.Getter;
import net.kyori.adventure.text.Component;
import org.slf4j.Logger;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Objects;
@Plugin(
id = "hamster-ball",
name = "HamsterBall",
version = BuildConstants.VERSION,
description = BuildConstants.DESCRIPTION,
authors = {"MiniDay"},
dependencies = @Dependency(id = "hamster-core")
)
public class HamsterBallPlugin {
@Getter
private static HamsterBallPlugin instance;
@Getter
private final java.util.logging.Logger logger;
@Getter
private final Logger slf4jLogger;
@Getter
private final ProxyServer proxyServer;
@Getter
private final File dataFolder;
@Getter
private YamlConfig config;
@Inject
public HamsterBallPlugin(Logger slf4jLogger, ProxyServer proxyServer, @DataDirectory Path dataPath) {
long start = System.currentTimeMillis();
logger = java.util.logging.Logger.getLogger("hamster-ball");
this.slf4jLogger = slf4jLogger;
this.proxyServer = proxyServer;
dataFolder = dataPath.toFile();
slf4jLogger.info("仓鼠球正在初始化");
instance = this;
try {
File dataFolder = getDataFolder();
if (dataFolder.mkdir()) {
slf4jLogger.info("已生成插件存档文件夹");
}
File configFile = new File(dataFolder, "config.yml");
if (!configFile.exists()) {
Files.copy(
Objects.requireNonNull(getClass().getResourceAsStream("/config.yml")),
configFile.toPath(),
StandardCopyOption.REPLACE_EXISTING
);
}
config = YamlConfig.load(configFile);
CoreVelocityAPI.init(config);
slf4jLogger.info("已初始化 BallAPI");
} catch (Exception e) {
slf4jLogger.error("BallAPI 初始化失败", e);
proxyServer.shutdown(Component.text("由于 HamsterBall 初始化失败, 服务器将立即关闭"));
}
long time = System.currentTimeMillis() - start;
slf4jLogger.info("仓鼠球初始化完成,总计耗时 " + time + " ms");
}
@Subscribe(order = PostOrder.EARLY)
public void onProxyInitialization(ProxyInitializeEvent event) {
long start = System.currentTimeMillis();
slf4jLogger.info("仓鼠球正在启动");
try {
CoreVelocityAPI.getInstance().enable();
} catch (Exception e) {
slf4jLogger.error("仓鼠球启动失败", e);
slf4jLogger.info("由于仓鼠球启动失败,服务器将立即关闭");
proxyServer.shutdown(Component.text("仓鼠球启动失败"));
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);
slf4jLogger.info("已注册监听器 BallVelocityListener");
proxyServer.getEventManager().register(this, BallVelocityMainListener.INSTANCE);
slf4jLogger.info("已注册监听器 BallVelocityMainListener");
proxyServer.getEventManager().register(this, UpdatePlayerInfoListener.INSTANCE);
slf4jLogger.info("已注册监听器 UpdatePlayerInfoListener");
if (config.getBoolean("auto-register-game-server", false)) {
BallAPI.getInstance().getEventBus().register(VelocityServerListener.INSTANCE);
slf4jLogger.info("已注册监听器 VelocityServerListener");
VelocityServerListener.INSTANCE.onEnable();
}
if (BallAPI.getInstance().getBallConfig().isGameServerUpdatePlayerInfo()) {
BallAPI.getInstance().subscribePatterns("*" + BallAPI.PLAYER_INFO_CHANNEL);
} else {
BallAPI.getInstance().subscribeRaw(BallAPI.PLAYER_INFO_CHANNEL);
}
BallAPI.getInstance().sendRawBallMessage(
BallAPI.BALL_CHANNEL, BallActions.ServerOnline.name(),
new ServerOnlineEvent(BallAPI.getInstance().getLocalServerInfo())
);
// 移除失效的在线玩家
BallAPI.getInstance().getAllPlayerInfo().values()
.stream()
.filter(BallPlayerInfo::isOnline)
.filter(o -> BallAPI.getInstance().isLocalServer(o.getProxyServer()))
.forEach(playerInfo -> {
playerInfo.setOnline(false);
BallVelocityUtils.uploadPlayerInfo(playerInfo);
});
long time = System.currentTimeMillis() - start;
slf4jLogger.info("仓鼠球启动完成,总计耗时 {} ms", time);
}
@Subscribe(order = PostOrder.LATE)
public void onProxyShutdown(ProxyShutdownEvent event) {
long start = System.currentTimeMillis();
slf4jLogger.info("仓鼠球正在关闭");
try {
CoreVelocityAPI.getInstance().disable();
} catch (Exception e) {
slf4jLogger.error("关闭仓鼠球时遇到了一个异常", e);
}
long time = System.currentTimeMillis() - start;
slf4jLogger.info("仓鼠球已关闭,总计耗时 {} ms", time);
}
}

View File

@@ -1,58 +0,0 @@
package cn.hamster3.mc.plugin.ball.velocity.api;
import cn.hamster3.mc.plugin.ball.common.api.BallAPI;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerType;
import cn.hamster3.mc.plugin.ball.velocity.HamsterBallPlugin;
import cn.hamster3.mc.plugin.core.common.config.ConfigSection;
import cn.hamster3.mc.plugin.core.common.config.YamlConfig;
import com.velocitypowered.api.proxy.config.ProxyConfig;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.util.logging.Logger;
@SuppressWarnings("unused")
public final class CoreVelocityAPI extends BallAPI {
public CoreVelocityAPI(@NotNull ConfigSection config) {
super(config, BallServerType.PROXY);
}
public static CoreVelocityAPI getInstance() {
return (CoreVelocityAPI) instance;
}
public static void init(@NotNull YamlConfig config) {
if (instance != null) {
return;
}
instance = new CoreVelocityAPI(config);
}
@Override
public void enable() throws SQLException, InterruptedException {
ProxyConfig config = HamsterBallPlugin.getInstance().getProxyServer().getConfiguration();
try {
Field field = config.getClass().getDeclaredField("bind");
field.setAccessible(true);
String bind = (String) field.get(config);
int i = bind.lastIndexOf(":");
String substring = bind.substring(i + 1);
instance.getLocalServerInfo().setHost(bind.substring(0, i));
instance.getLocalServerInfo().setPort(Integer.parseInt(substring));
} catch (Exception e) {
HamsterBallPlugin.getInstance().getSlf4jLogger().error("获取 Velocity 监听端口时遇到了一个异常", e);
}
super.enable();
}
@Override
public void disable() throws SQLException, InterruptedException {
super.disable();
}
@Override
public @NotNull Logger getLogger() {
return HamsterBallPlugin.getInstance().getLogger();
}
}

View File

@@ -1,46 +0,0 @@
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,114 +0,0 @@
package cn.hamster3.mc.plugin.ball.velocity.listener;
import cn.hamster3.mc.plugin.ball.common.api.BallAPI;
import cn.hamster3.mc.plugin.ball.common.event.operate.*;
import cn.hamster3.mc.plugin.ball.velocity.HamsterBallPlugin;
import cn.hamster3.mc.plugin.core.common.api.CoreAPI;
import com.google.common.eventbus.Subscribe;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import net.kyori.adventure.audience.Audience;
import java.util.UUID;
public class BallVelocityListener {
public static final BallVelocityListener INSTANCE = new BallVelocityListener();
private BallVelocityListener() {
}
@Subscribe
public void onDispatchConsoleCommand(DispatchConsoleCommandEvent event) {
if (event.getServerID() != null && !BallAPI.getInstance().isLocalServer(event.getServerID())) {
return;
}
ProxyServer server = HamsterBallPlugin.getInstance().getProxyServer();
server.getCommandManager().executeAsync(server.getConsoleCommandSource(), event.getCommand());
}
@Subscribe
public void onDispatchPlayerCommand(DispatchPlayerCommandEvent event) {
ProxyServer server = HamsterBallPlugin.getInstance().getProxyServer();
if (event.getUuid() != null) {
Player player = server.getPlayer(event.getUuid()).orElse(null);
if (player == null) {
return;
}
server.getCommandManager().executeAsync(player, event.getCommand());
return;
}
for (Player player : server.getAllPlayers()) {
server.getCommandManager().executeAsync(player, event.getCommand());
}
}
@Subscribe
public void onKickPlayer(KickPlayerEvent event) {
ProxyServer server = HamsterBallPlugin.getInstance().getProxyServer();
Player player = server.getPlayer(event.getUuid()).orElse(null);
if (player == null) {
return;
}
player.disconnect(event.getReason());
}
@Subscribe
public void onSendMessageToPlayer(SendMessageToPlayerEvent event) {
for (UUID receiver : event.getReceivers()) {
Audience audience = CoreAPI.getInstance().getAudienceProvider().player(receiver);
event.getMessage().show(audience);
}
}
@Subscribe
public void onSendPlayerToLocation(SendPlayerToLocationEvent event) {
ProxyServer proxyServer = HamsterBallPlugin.getInstance().getProxyServer();
String serverID = event.getLocation().getServerID();
RegisteredServer toServer = proxyServer.getServer(serverID).orElse(null);
if (toServer == null) {
HamsterBallPlugin.getInstance().getLogger().warning("试图传送玩家时失败: 服务器 " + serverID + " 不在线");
return;
}
for (UUID uuid : event.getSendPlayerUUID()) {
Player player = proxyServer.getPlayer(uuid).orElse(null);
if (player == null) {
continue;
}
RegisteredServer currentServer = player.getCurrentServer().map(ServerConnection::getServer).orElse(null);
if (currentServer != null && currentServer.getServerInfo().getName().equals(serverID)) {
continue;
}
player.createConnectionRequest(toServer).fireAndForget();
}
}
@Subscribe
public void onSendPlayerToPlayer(SendPlayerToPlayerEvent event) {
ProxyServer proxyServer = HamsterBallPlugin.getInstance().getProxyServer();
UUID toPlayerUUID = event.getToPlayerUUID();
Player toPlayer = proxyServer.getPlayer(toPlayerUUID).orElse(null);
if (toPlayer == null) {
HamsterBallPlugin.getInstance().getLogger().warning("试图传送玩家时失败: 目标玩家 " + toPlayerUUID + " 不在线");
return;
}
RegisteredServer toServer = toPlayer.getCurrentServer().map(ServerConnection::getServer).orElse(null);
if (toServer == null) {
HamsterBallPlugin.getInstance().getLogger().warning("试图传送玩家时失败: 目标玩家 " + toPlayerUUID + " 不在任何服务器中");
return;
}
for (UUID uuid : event.getSendPlayerUUID()) {
Player sendPlayer = proxyServer.getPlayer(uuid).orElse(null);
if (sendPlayer == null) {
continue;
}
ServerInfo currentServer = sendPlayer.getCurrentServer().map(ServerConnection::getServerInfo).orElse(null);
if (currentServer != null && currentServer.getName().equals(toServer.getServerInfo().getName())) {
continue;
}
sendPlayer.createConnectionRequest(toServer).fireAndForget();
}
}
}

View File

@@ -1,58 +0,0 @@
package cn.hamster3.mc.plugin.ball.velocity.listener;
import cn.hamster3.mc.plugin.ball.common.api.BallAPI;
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.player.BallPlayerLoginEvent;
import cn.hamster3.mc.plugin.ball.common.event.player.BallPlayerPostLoginEvent;
import cn.hamster3.mc.plugin.ball.common.event.player.BallPlayerPreLoginEvent;
import cn.hamster3.mc.plugin.ball.velocity.util.BallVelocityUtils;
import com.velocitypowered.api.event.PostOrder;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.LoginEvent;
import com.velocitypowered.api.event.connection.PostLoginEvent;
import com.velocitypowered.api.event.connection.PreLoginEvent;
import com.velocitypowered.api.proxy.Player;
public final class BallVelocityMainListener {
public static final BallVelocityMainListener INSTANCE = new BallVelocityMainListener();
private BallVelocityMainListener() {
}
@Subscribe(order = PostOrder.LATE)
public void onPreLogin(PreLoginEvent event) {
BallAPI.getInstance().sendRawBallMessage(
BallAPI.BALL_CHANNEL,
BallActions.BallPlayerPreLogin.name(),
new BallPlayerPreLoginEvent(event.getUsername())
);
}
@Subscribe(order = PostOrder.LATE)
public void onLogin(LoginEvent event) {
if (!event.getResult().isAllowed()) {
return;
}
Player player = event.getPlayer();
BallAPI.getInstance().sendRawBallMessage(
BallAPI.BALL_CHANNEL,
BallActions.BallPlayerLogin.name(),
new BallPlayerLoginEvent(new BallPlayerInfo(
player.getUniqueId(), player.getUsername(), "connecting",
BallAPI.getInstance().getLocalServerId(), true
))
);
}
@Subscribe(order = PostOrder.LATE)
public void onPostLogin(PostLoginEvent event) {
Player player = event.getPlayer();
BallPlayerInfo playerInfo = BallVelocityUtils.getPlayerInfo(player, true);
BallAPI.getInstance().sendRawBallMessage(
BallAPI.BALL_CHANNEL,
BallActions.BallPlayerPostLogin.name(),
new BallPlayerPostLoginEvent(playerInfo)
);
}
}

View File

@@ -1,67 +0,0 @@
package cn.hamster3.mc.plugin.ball.velocity.listener;
import cn.hamster3.mc.plugin.ball.common.api.BallAPI;
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.player.BallPlayerLogoutEvent;
import cn.hamster3.mc.plugin.ball.common.event.player.BallPlayerPostConnectServerEvent;
import cn.hamster3.mc.plugin.ball.common.event.player.BallPlayerPreConnectServerEvent;
import cn.hamster3.mc.plugin.ball.velocity.util.BallVelocityUtils;
import com.velocitypowered.api.event.PostOrder;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.player.ServerPostConnectEvent;
import com.velocitypowered.api.event.player.ServerPreConnectEvent;
import com.velocitypowered.api.proxy.Player;
public class UpdatePlayerInfoListener {
public static final UpdatePlayerInfoListener INSTANCE = new UpdatePlayerInfoListener();
private UpdatePlayerInfoListener() {
}
@Subscribe(order = PostOrder.LATE)
public void onServerConnect(ServerPreConnectEvent event) {
if (event.getResult().isAllowed()) {
return;
}
String name = event.getResult().getServer()
.map(o -> o.getServerInfo().getName())
.orElse(event.getOriginalServer().getServerInfo().getName());
Player player = event.getPlayer();
BallPlayerInfo playerInfo = BallVelocityUtils.getPlayerInfo(player, true);
playerInfo.setGameServer(name);
BallAPI.getInstance().sendRawBallMessage(
BallAPI.BALL_CHANNEL,
BallActions.BallPlayerPreConnectServer.name(),
new BallPlayerPreConnectServerEvent(playerInfo, playerInfo.getGameServer(), name)
);
BallVelocityUtils.uploadPlayerInfo(playerInfo);
}
@SuppressWarnings("UnstableApiUsage")
@Subscribe(order = PostOrder.LATE)
public void onServerPostConnect(ServerPostConnectEvent event) {
Player player = event.getPlayer();
BallPlayerInfo playerInfo = BallVelocityUtils.getPlayerInfo(player, true);
playerInfo.setGameServer(player.getCurrentServer().map(o -> o.getServerInfo().getName()).orElse(""));
BallAPI.getInstance().sendRawBallMessage(
BallAPI.BALL_CHANNEL,
BallActions.BallPlayerPostConnectServer.name(),
new BallPlayerPostConnectServerEvent(playerInfo)
);
BallVelocityUtils.uploadPlayerInfo(playerInfo);
}
@Subscribe(order = PostOrder.LATE)
public void onDisconnect(DisconnectEvent event) {
Player player = event.getPlayer();
BallPlayerInfo playerInfo = BallVelocityUtils.getPlayerInfo(player, false);
BallAPI.getInstance().sendRawBallMessage(
BallAPI.BALL_CHANNEL,
BallActions.BallPlayerLogout.name(),
new BallPlayerLogoutEvent(playerInfo)
);
BallVelocityUtils.uploadPlayerInfo(playerInfo);
}
}

View File

@@ -1,65 +0,0 @@
package cn.hamster3.mc.plugin.ball.velocity.listener;
import cn.hamster3.mc.plugin.ball.common.api.BallAPI;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerInfo;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerType;
import cn.hamster3.mc.plugin.ball.common.event.server.ServerOfflineEvent;
import cn.hamster3.mc.plugin.ball.common.event.server.ServerOnlineEvent;
import cn.hamster3.mc.plugin.ball.velocity.HamsterBallPlugin;
import com.google.common.eventbus.Subscribe;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import java.net.InetSocketAddress;
public class VelocityServerListener {
public static final VelocityServerListener INSTANCE = new VelocityServerListener();
private VelocityServerListener() {
}
public void onEnable() {
for (BallServerInfo info : BallAPI.getInstance().getAllServerInfo().values()) {
if (info.getType() != BallServerType.GAME) {
continue;
}
ProxyServer server = HamsterBallPlugin.getInstance().getProxyServer();
server.getServer(info.getId())
.map(RegisteredServer::getServerInfo)
.ifPresent(server::unregisterServer);
ServerInfo serverInfo = new ServerInfo(info.getId(), new InetSocketAddress(info.getHost(), info.getPort()));
server.registerServer(serverInfo);
BallAPI.getInstance().getLogger().info("已添加服务器入口: " + info.getId());
}
}
@Subscribe
public void onServerOnline(ServerOnlineEvent event) {
if (event.getType() != BallServerType.GAME) {
return;
}
ProxyServer server = HamsterBallPlugin.getInstance().getProxyServer();
server.getServer(event.getId())
.map(RegisteredServer::getServerInfo)
.ifPresent(server::unregisterServer);
ServerInfo serverInfo = new ServerInfo(event.getId(), new InetSocketAddress(event.getHost(), event.getPort()));
server.registerServer(serverInfo);
BallAPI.getInstance().getLogger().info("已添加服务器入口: " + event.getId());
}
@Subscribe
public void onServerOffline(ServerOfflineEvent event) {
if (event.getType() != BallServerType.GAME) {
return;
}
ProxyServer server = HamsterBallPlugin.getInstance().getProxyServer();
ServerInfo serverInfo = server.getServer(event.getId())
.map(RegisteredServer::getServerInfo)
.orElse(null);
if (serverInfo != null) {
server.unregisterServer(serverInfo);
BallAPI.getInstance().getLogger().info("已移除服务器入口: " + event.getId());
}
}
}

View File

@@ -1,58 +0,0 @@
package cn.hamster3.mc.plugin.ball.velocity.util;
import cn.hamster3.mc.plugin.ball.common.api.BallAPI;
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.player.BallPlayerInfoUpdateEvent;
import cn.hamster3.mc.plugin.core.common.api.CoreAPI;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
import org.jetbrains.annotations.NotNull;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.logging.Level;
public final class BallVelocityUtils {
private BallVelocityUtils() {
}
@NotNull
public static BallPlayerInfo getPlayerInfo(@NotNull Player player, boolean online) {
ServerConnection server = player.getCurrentServer().orElse(null);
return new BallPlayerInfo(
player.getUniqueId(),
player.getUsername(),
server == null ? "connecting" : server.getServerInfo().getName(),
BallAPI.getInstance().getLocalServerId(),
online
);
}
public static void uploadPlayerInfo(@NotNull BallPlayerInfo playerInfo) {
CoreAPI.getInstance().getExecutorService().execute(() -> {
try (Connection connection = BallAPI.getInstance().getDatasource().getConnection()) {
try (PreparedStatement statement = connection.prepareStatement(
"REPLACE INTO `hamster_ball_player_info` VALUES(?, ?, ?, ?, ?);"
)) {
statement.setString(1, playerInfo.getUuid().toString());
statement.setString(2, playerInfo.getName());
statement.setString(3, playerInfo.getGameServer());
statement.setString(4, playerInfo.getProxyServer());
statement.setBoolean(5, playerInfo.isOnline());
statement.executeUpdate();
}
} catch (SQLException e) {
BallAPI.getInstance().getLogger().log(Level.SEVERE, "更新玩家数据时遇到了一个异常", e);
}
if (!BallAPI.getInstance().getBallConfig().isGameServerUpdatePlayerInfo()) {
BallAPI.getInstance().sendRawBallMessage(
BallAPI.PLAYER_INFO_CHANNEL,
BallActions.BallPlayerInfoUpdate.name(),
new BallPlayerInfoUpdateEvent(playerInfo)
);
}
});
}
}

View File

@@ -1,71 +0,0 @@
# 是否允许在控制台输出调试信息
debug: false
# 是否启用服务器 ID 单例模式
# 启用后,服务器唯一识别码 相同的服务器将无法启动
singleton-server-id: false
# 频道名前缀
# 使用这个配置选项可以划分子服消息通信分组
# 只有在同一个频道名的子服才能互相通信
channel-prefix: ""
# 是否在子服端更新玩家信息
# 默认情况下BC 统一管理玩家信息,包括记录 UUID 和玩家名称
# 如果一个群组服同时拥有多个 BC 入口,且每个 BC 入口为不同的玩家名称分配不同的 UUID
# (例如正版、盗版双入口,或网易多入口接同一个子服)
# 则可以启用该功能以防止同一个名称占用多个 UUID 的问题
game-server-update-player-info: false
# 启用后,子服启动时会自动注册该子服的入口配置,关闭时也会自动移除该子服的入口配置
auto-register-game-server: false
# 本服务器信息
server-info:
# 服务器唯一识别码,最长 32 字符
id: "Velocity"
# 服务端名称,常用于展示给玩家看
name: "代理端"
# 当前子服的地址
# 不填则自动设置为 0.0.0.0
host: 0.0.0.0
# 当前子服端口
# 不填则自动设置为 25577
port: 25577
# 数据库连接池配置
# 如果注释该选项则默认使用 HamsterCore 中的连接池配置
# 否则 HamsterBall 将会使用与 HamsterCore 不同的数据库链接
# 如果你需要让每个服务器单独存储仓鼠球信息
# 这个选项就会很有用
#datasource:
# # 数据库链接驱动地址
# driver: "com.mysql.cj.jdbc.Driver"
# # MySQL数据库链接填写格式:
# # jdbc:mysql://{数据库地址}:{数据库端口}/{使用的库名}?参数
# url: "jdbc:mysql://localhost:3306/Test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true"
# # 用户名
# username: "root"
# # 密码
# password: "Root123.."
# # 最小闲置链接数
# # 推荐值1~3
# minimum-idle: 0
# # 最大链接数
# # 推荐值不低于5
# maximum-pool-size: 5
# # 保持连接池可用的间隔
# # 除非你的服务器数据库连接经常断开,否则不建议启用该选项
# # 单位:毫秒
# # 默认值为0禁用
# keep-alive-time: 0
# # 连接闲置回收时间
# # 单位:毫秒
# # 推荐值60000010分钟
# idle-timeout: 600000
# # 链接最长存活时间
# # 单位:毫秒
# max-lifetime: 1800000
# # 验证连接存活的超时时间
# # 单位:毫秒
# validation-timeout: 5000

View File

@@ -1,5 +0,0 @@
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/

View File

@@ -1,8 +0,0 @@
package cn.hamster3.mc.plugin.ball.velocity;
// The constants are replaced before compilation
@SuppressWarnings("unused")
public class BuildConstants {
public static final String VERSION = "${version}";
public static final String DESCRIPTION = "${description}";
}

View File

@@ -1,12 +1,11 @@
plugins {
id("java-library")
id("java")
id("maven-publish")
id("com.github.johnrengelman.shadow") version "8+"
}
group = "cn.hamster3.mc.plugin"
version = "1.7.0"
description = "基于 Redis 的 Minecraft 服务端通用消息中间件"
version = "1.5.7"
subprojects {
apply {
@@ -17,7 +16,6 @@ subprojects {
group = rootProject.group
version = rootProject.version
description = rootProject.description
repositories {
maven("https://maven.airgame.net/maven-public/")
@@ -45,6 +43,12 @@ subprojects {
from(rootProject.file("LICENSE"))
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
jar {
archiveClassifier = "dev"
}
shadowJar {
archiveClassifier = "FIX"
}
build {
dependsOn(shadowJar)
}
@@ -58,7 +62,7 @@ subprojects {
}
repositories {
maven {
url = uri("https://maven.airgame.net/public")
url = uri("https://maven.airgame.net/maven-airgame")
credentials {
username = rootProject.properties.getOrDefault("maven_username", "").toString()

View File

@@ -9,4 +9,3 @@ rootProject.name = "hamster-ball"
include("ball-common")
include("ball-bukkit")
include("ball-bungee")
include("ball-velocity")