2 Commits

46 changed files with 499 additions and 1150 deletions

View File

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

View File

@@ -3,11 +3,15 @@
evaluationDependsOn(":ball-common") evaluationDependsOn(":ball-common")
dependencies { 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("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 { tasks {

View File

@@ -9,19 +9,14 @@ 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.entity.BallPlayerInfo;
import cn.hamster3.mc.plugin.ball.common.event.BallActions; import cn.hamster3.mc.plugin.ball.common.event.BallActions;
import cn.hamster3.mc.plugin.ball.common.event.server.ServerOnlineEvent; import cn.hamster3.mc.plugin.ball.common.event.server.ServerOnlineEvent;
import cn.hamster3.mc.plugin.core.common.config.YamlConfig;
import lombok.Getter; import lombok.Getter;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@SuppressWarnings("CallToPrintStackTrace")
public class HamsterBallPlugin extends JavaPlugin { public class HamsterBallPlugin extends JavaPlugin {
@Getter @Getter
private static HamsterBallPlugin instance; private static HamsterBallPlugin instance;
@@ -44,25 +39,10 @@ public class HamsterBallPlugin extends JavaPlugin {
reloadConfig(); reloadConfig();
logger.info("已读取配置文件"); logger.info("已读取配置文件");
try { try {
File dataFolder = getDataFolder(); BallBukkitAPI.init();
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);
logger.info("已初始化 BallAPI"); logger.info("已初始化 BallAPI");
} catch (Exception e) { } catch (Exception e) {
logger.log(Level.SEVERE, "BallAPI 初始化失败", e); e.printStackTrace();
Bukkit.shutdown();
return;
} }
long time = System.currentTimeMillis() - start; long time = System.currentTimeMillis() - start;
logger.info("仓鼠球初始化完成,总计耗时 " + time + " ms"); logger.info("仓鼠球初始化完成,总计耗时 " + time + " ms");
@@ -76,8 +56,9 @@ public class HamsterBallPlugin extends JavaPlugin {
try { try {
BallBukkitAPI.getInstance().enable(); BallBukkitAPI.getInstance().enable();
} catch (Exception e) { } catch (Exception e) {
logger.log(Level.SEVERE, "仓鼠球启动失败", e); getLogger().info("仓鼠球启动失败,原因:" + e.getMessage());
logger.info("由于仓鼠球启动失败,服务器将立即关闭"); e.printStackTrace();
getLogger().info("由于仓鼠球启动失败,服务器将立即关闭");
Bukkit.shutdown(); Bukkit.shutdown();
return; return;
} }
@@ -100,7 +81,7 @@ public class HamsterBallPlugin extends JavaPlugin {
BallBukkitUtils.uploadPlayerInfo(playerInfo); BallBukkitUtils.uploadPlayerInfo(playerInfo);
}); });
} else { } else {
BallAPI.getInstance().subscribeRaw(BallAPI.PLAYER_INFO_CHANNEL); BallAPI.getInstance().subscribeIgnorePrefix(BallAPI.PLAYER_INFO_CHANNEL);
} }
sync(() -> { sync(() -> {
if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) { if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) {
@@ -124,7 +105,7 @@ public class HamsterBallPlugin extends JavaPlugin {
try { try {
BallBukkitAPI.getInstance().disable(); BallBukkitAPI.getInstance().disable();
} catch (Exception e) { } catch (Exception e) {
logger.log(Level.SEVERE, "关闭仓鼠球时遇到了一个异常", e); e.printStackTrace();
} }
long time = System.currentTimeMillis() - start; long time = System.currentTimeMillis() - start;
logger.info("仓鼠球已关闭,总计耗时 " + time + " ms"); logger.info("仓鼠球已关闭,总计耗时 " + time + " ms");

View File

@@ -1,36 +1,73 @@
package cn.hamster3.mc.plugin.ball.bukkit.api; package cn.hamster3.mc.plugin.ball.bukkit.api;
import cn.hamster3.mc.plugin.ball.bukkit.HamsterBallPlugin; 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.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.ball.common.entity.BallServerType;
import cn.hamster3.mc.plugin.core.common.config.ConfigSection; import cn.hamster3.mc.plugin.core.common.api.CoreAPI;
import cn.hamster3.mc.plugin.core.common.config.YamlConfig;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import javax.sql.DataSource;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Map;
import java.util.logging.Logger; import java.util.logging.Logger;
public class BallBukkitAPI extends BallAPI { public class BallBukkitAPI extends BallAPI {
public BallBukkitAPI(@NotNull ConfigSection config) { public BallBukkitAPI(@NotNull BallConfig ballConfig) {
super(config, BallServerType.GAME); super(ballConfig);
} }
public static BallBukkitAPI getInstance() { public static BallBukkitAPI getInstance() {
return (BallBukkitAPI) instance; return (BallBukkitAPI) instance;
} }
public static void init(@NotNull YamlConfig config) { public static void init() {
if (instance != null) { if (instance != null) {
return; 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 @Override
public void enable() throws SQLException, InterruptedException { 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(); super.enable();
} }

View File

@@ -7,11 +7,11 @@ import cn.hamster3.mc.plugin.ball.common.entity.BallServerType;
import cn.hamster3.mc.plugin.ball.common.event.operate.*; import cn.hamster3.mc.plugin.ball.common.event.operate.*;
import cn.hamster3.mc.plugin.core.common.api.CoreAPI; import cn.hamster3.mc.plugin.core.common.api.CoreAPI;
import cn.hamster3.mc.plugin.core.common.data.DisplayMessage; import cn.hamster3.mc.plugin.core.common.data.DisplayMessage;
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 com.google.common.eventbus.Subscribe;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.TextReplacementConfig;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -30,8 +30,8 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.logging.Level;
@SuppressWarnings("CallToPrintStackTrace")
public class BallBukkitListener implements Listener { public class BallBukkitListener implements Listener {
public static final BallBukkitListener INSTANCE = new BallBukkitListener(); public static final BallBukkitListener INSTANCE = new BallBukkitListener();
@@ -81,7 +81,7 @@ public class BallBukkitListener implements Listener {
} }
removeCachedPlayerMessage(uuid); removeCachedPlayerMessage(uuid);
} catch (SQLException e) { } catch (SQLException e) {
BallAPI.getInstance().getLogger().log(Level.SEVERE, "发送玩家缓存消息时出现了一个异常", e); e.printStackTrace();
} }
}); });
} }

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.BallActions;
import cn.hamster3.mc.plugin.ball.common.event.player.BallPlayerInfoUpdateEvent; 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.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.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.sql.DataSource;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.logging.Level;
@SuppressWarnings("CallToPrintStackTrace")
public final class BallBukkitUtils { public final class BallBukkitUtils {
private 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) { public static void uploadPlayerInfo(@NotNull BallPlayerInfo playerInfo) {
CoreAPI.getInstance().getExecutorService().execute(() -> { CoreAPI.getInstance().getExecutorService().execute(() -> {
try (Connection connection = BallAPI.getInstance().getDatasource().getConnection()) { try (Connection connection = BallAPI.getInstance().getDatasource().getConnection()) {
@@ -30,7 +63,7 @@ public final class BallBukkitUtils {
statement.executeUpdate(); statement.executeUpdate();
} }
} catch (SQLException e) { } catch (SQLException e) {
BallAPI.getInstance().getLogger().log(Level.SEVERE, "更新玩家数据时遇到了一个异常", e); e.printStackTrace();
} }
BallAPI.getInstance().sendBallMessage( BallAPI.getInstance().sendBallMessage(
BallAPI.PLAYER_INFO_CHANNEL, BallAPI.PLAYER_INFO_CHANNEL,

View File

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

View File

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

View File

@@ -3,9 +3,12 @@
evaluationDependsOn(":ball-common") evaluationDependsOn(":ball-common")
dependencies { dependencies {
api(project(":ball-common")) { isTransitive = false } implementation(project(":ball-common")) {
isTransitive = false
}
compileOnly("net.md-5:bungeecord-api:1.20-R0.1") 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 { tasks {

View File

@@ -3,29 +3,22 @@ package cn.hamster3.mc.plugin.ball.bungee;
import cn.hamster3.mc.plugin.ball.bungee.api.BallBungeeCordAPI; import cn.hamster3.mc.plugin.ball.bungee.api.BallBungeeCordAPI;
import cn.hamster3.mc.plugin.ball.bungee.listener.BallBungeeListener; import cn.hamster3.mc.plugin.ball.bungee.listener.BallBungeeListener;
import cn.hamster3.mc.plugin.ball.bungee.listener.BallBungeeMainListener; import cn.hamster3.mc.plugin.ball.bungee.listener.BallBungeeMainListener;
import cn.hamster3.mc.plugin.ball.bungee.listener.BungeeServerListener;
import cn.hamster3.mc.plugin.ball.bungee.listener.UpdatePlayerInfoListener; import cn.hamster3.mc.plugin.ball.bungee.listener.UpdatePlayerInfoListener;
import cn.hamster3.mc.plugin.ball.bungee.util.BallBungeeCordUtils; 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.api.BallAPI;
import cn.hamster3.mc.plugin.ball.common.entity.BallPlayerInfo; import cn.hamster3.mc.plugin.ball.common.entity.BallPlayerInfo;
import cn.hamster3.mc.plugin.ball.common.event.BallActions; import cn.hamster3.mc.plugin.ball.common.event.BallActions;
import cn.hamster3.mc.plugin.ball.common.event.server.ServerOnlineEvent; import cn.hamster3.mc.plugin.ball.common.event.server.ServerOnlineEvent;
import cn.hamster3.mc.plugin.core.common.config.YamlConfig;
import lombok.Getter; import lombok.Getter;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.plugin.Plugin; 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; import java.util.logging.Logger;
@SuppressWarnings("CallToPrintStackTrace")
public class HamsterBallPlugin extends Plugin { public class HamsterBallPlugin extends Plugin {
@Getter @Getter
private static HamsterBallPlugin instance; private static HamsterBallPlugin instance;
@Getter
private YamlConfig config;
@Override @Override
public void onLoad() { public void onLoad() {
@@ -34,25 +27,11 @@ public class HamsterBallPlugin extends Plugin {
logger.info("仓鼠球正在初始化"); logger.info("仓鼠球正在初始化");
instance = this; instance = this;
try { try {
File dataFolder = getDataFolder(); BallBungeeCordAPI.init();
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);
logger.info("已初始化 BallAPI"); logger.info("已初始化 BallAPI");
} catch (Exception e) { } catch (Exception e) {
logger.log(Level.SEVERE, "BallAPI 初始化失败", e); e.printStackTrace();
ProxyServer.getInstance().stop("由于 HamsterBall 初始化失败, 服务器将立即关闭"); ProxyServer.getInstance().stop("由于 HamsterBall 未能成功连接, 服务器将立即关闭");
return;
} }
long time = System.currentTimeMillis() - start; long time = System.currentTimeMillis() - start;
logger.info("仓鼠球初始化完成,总计耗时 " + time + " ms"); logger.info("仓鼠球初始化完成,总计耗时 " + time + " ms");
@@ -66,10 +45,7 @@ public class HamsterBallPlugin extends Plugin {
try { try {
BallBungeeCordAPI.getInstance().enable(); BallBungeeCordAPI.getInstance().enable();
} catch (Exception e) { } catch (Exception e) {
logger.log(Level.SEVERE, "仓鼠球启动失败", e); e.printStackTrace();
logger.info("由于仓鼠球启动失败,服务器将立即关闭");
ProxyServer.getInstance().stop("仓鼠球启动失败");
return;
} }
BallAPI.getInstance().getEventBus().register(BallBungeeListener.INSTANCE); BallAPI.getInstance().getEventBus().register(BallBungeeListener.INSTANCE);
logger.info("已注册监听器 BallBungeeListener"); logger.info("已注册监听器 BallBungeeListener");
@@ -77,16 +53,11 @@ public class HamsterBallPlugin extends Plugin {
logger.info("已注册监听器 BallBungeeMainListener"); logger.info("已注册监听器 BallBungeeMainListener");
ProxyServer.getInstance().getPluginManager().registerListener(this, UpdatePlayerInfoListener.INSTANCE); ProxyServer.getInstance().getPluginManager().registerListener(this, UpdatePlayerInfoListener.INSTANCE);
logger.info("已注册监听器 UpdatePlayerInfoListener"); 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()) { if (BallAPI.getInstance().getBallConfig().isGameServerUpdatePlayerInfo()) {
BallAPI.getInstance().subscribePatterns("*" + BallAPI.PLAYER_INFO_CHANNEL); BallAPI.getInstance().subscribePatterns("*" + BallAPI.PLAYER_INFO_CHANNEL);
} else { } else {
BallAPI.getInstance().subscribeRaw(BallAPI.PLAYER_INFO_CHANNEL); BallAPI.getInstance().subscribeIgnorePrefix(BallAPI.PLAYER_INFO_CHANNEL);
} }
BallAPI.getInstance().sendRawBallMessage( BallAPI.getInstance().sendRawBallMessage(
BallAPI.BALL_CHANNEL, BallActions.ServerOnline.name(), BallAPI.BALL_CHANNEL, BallActions.ServerOnline.name(),
@@ -113,7 +84,9 @@ public class HamsterBallPlugin extends Plugin {
try { try {
BallBungeeCordAPI.getInstance().disable(); BallBungeeCordAPI.getInstance().disable();
} catch (Exception e) { } catch (Exception e) {
logger.log(Level.SEVERE, "关闭仓鼠球时遇到了一个异常", e); getLogger().info("仓鼠球启动失败,原因:" + e.getMessage());
e.printStackTrace();
ProxyServer.getInstance().stop("由于仓鼠球启动失败,服务器将立即关闭");
} }
long time = System.currentTimeMillis() - start; long time = System.currentTimeMillis() - start;
logger.info("仓鼠球已关闭,总计耗时 " + time + " ms"); logger.info("仓鼠球已关闭,总计耗时 " + time + " ms");

View File

@@ -1,44 +1,70 @@
package cn.hamster3.mc.plugin.ball.bungee.api; package cn.hamster3.mc.plugin.ball.bungee.api;
import cn.hamster3.mc.plugin.ball.bungee.HamsterBallPlugin; 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.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.ball.common.entity.BallServerType;
import cn.hamster3.mc.plugin.core.common.config.ConfigSection; import cn.hamster3.mc.plugin.core.bungee.util.CoreBungeeCordUtils;
import cn.hamster3.mc.plugin.core.common.config.YamlConfig; import cn.hamster3.mc.plugin.core.common.api.CoreAPI;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.api.config.ListenerInfo;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.net.InetSocketAddress; import javax.sql.DataSource;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Map;
import java.util.logging.Logger; import java.util.logging.Logger;
public class BallBungeeCordAPI extends BallAPI { public class BallBungeeCordAPI extends BallAPI {
public BallBungeeCordAPI(@NotNull ConfigSection config) { public BallBungeeCordAPI(@NotNull BallConfig ballConfig) {
super(config, BallServerType.PROXY); super(ballConfig);
} }
public static BallBungeeCordAPI getInstance() { public static BallBungeeCordAPI getInstance() {
return (BallBungeeCordAPI) instance; return (BallBungeeCordAPI) instance;
} }
public static void init(@NotNull YamlConfig config) { public static void init() {
if (instance != null) { if (instance != null) {
return; 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 @Override
public void enable() throws SQLException, InterruptedException { 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(); super.enable();
} }

View File

@@ -5,9 +5,9 @@ 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.entity.BallServerType;
import cn.hamster3.mc.plugin.ball.common.event.operate.*; import cn.hamster3.mc.plugin.ball.common.event.operate.*;
import cn.hamster3.mc.plugin.core.common.api.CoreAPI; import cn.hamster3.mc.plugin.core.common.api.CoreAPI;
import cn.hamster3.mc.plugin.core.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 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.ProxyServer;
import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.config.ServerInfo; import net.md_5.bungee.api.config.ServerInfo;
@@ -55,9 +55,6 @@ public class BallBungeeListener {
@Subscribe @Subscribe
public void onKickPlayerEvent(KickPlayerEvent event) { public void onKickPlayerEvent(KickPlayerEvent event) {
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(event.getUuid()); ProxiedPlayer player = ProxyServer.getInstance().getPlayer(event.getUuid());
if (player == null) {
return;
}
BaseComponent[] components = BungeeComponentSerializer.get().serialize(event.getReason()); BaseComponent[] components = BungeeComponentSerializer.get().serialize(event.getReason());
player.disconnect(components); player.disconnect(components);
} }
@@ -73,8 +70,8 @@ public class BallBungeeListener {
@Subscribe @Subscribe
public void onSendPlayerToLocationEvent(SendPlayerToLocationEvent event) { public void onSendPlayerToLocationEvent(SendPlayerToLocationEvent event) {
String serverID = event.getLocation().getServerID(); String serverID = event.getLocation().getServerID();
ServerInfo toServer = ProxyServer.getInstance().getServerInfo(serverID); ServerInfo serverInfo = ProxyServer.getInstance().getServerInfo(serverID);
if (toServer == null) { if (serverInfo == null) {
HamsterBallPlugin.getInstance().getLogger().warning("试图传送玩家时失败: 服务器 " + serverID + " 不在线"); HamsterBallPlugin.getInstance().getLogger().warning("试图传送玩家时失败: 服务器 " + serverID + " 不在线");
return; return;
} }
@@ -86,12 +83,12 @@ public class BallBungeeListener {
if (player.getServer().getInfo().getName().equals(serverID)) { if (player.getServer().getInfo().getName().equals(serverID)) {
continue; continue;
} }
player.connect(toServer); player.connect(serverInfo);
} }
} }
@Subscribe @Subscribe
public void onSendPlayerToPlayer(SendPlayerToPlayerEvent event) { public void onSendPlayerToPlayerEvent(SendPlayerToPlayerEvent event) {
UUID toPlayerUUID = event.getToPlayerUUID(); UUID toPlayerUUID = event.getToPlayerUUID();
ProxiedPlayer toPlayer = ProxyServer.getInstance().getPlayer(toPlayerUUID); ProxiedPlayer toPlayer = ProxyServer.getInstance().getPlayer(toPlayerUUID);
if (toPlayer == null) { if (toPlayer == null) {
@@ -101,9 +98,6 @@ public class BallBungeeListener {
ServerInfo toServer = toPlayer.getServer().getInfo(); ServerInfo toServer = toPlayer.getServer().getInfo();
for (UUID uuid : event.getSendPlayerUUID()) { for (UUID uuid : event.getSendPlayerUUID()) {
ProxiedPlayer sendPlayer = ProxyServer.getInstance().getPlayer(uuid); ProxiedPlayer sendPlayer = ProxyServer.getInstance().getPlayer(uuid);
if (sendPlayer == null) {
continue;
}
if (sendPlayer.getServer().getInfo().getName().equals(toServer.getName())) { if (sendPlayer.getServer().getInfo().getName().equals(toServer.getName())) {
continue; 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.BallActions;
import cn.hamster3.mc.plugin.ball.common.event.player.BallPlayerInfoUpdateEvent; 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.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.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server; import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.config.Configuration;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.sql.DataSource;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.logging.Level;
@SuppressWarnings("CallToPrintStackTrace")
public final class BallBungeeCordUtils { public final class BallBungeeCordUtils {
private BallBungeeCordUtils() { private BallBungeeCordUtils() {
} }
@@ -44,7 +49,7 @@ public final class BallBungeeCordUtils {
statement.executeUpdate(); statement.executeUpdate();
} }
} catch (SQLException e) { } catch (SQLException e) {
BallAPI.getInstance().getLogger().log(Level.SEVERE, "更新玩家数据时遇到了一个异常", e); e.printStackTrace();
} }
if (!BallAPI.getInstance().getBallConfig().isGameServerUpdatePlayerInfo()) { if (!BallAPI.getInstance().getBallConfig().isGameServerUpdatePlayerInfo()) {
BallAPI.getInstance().sendRawBallMessage( 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} version: ${version}
author: MiniDay author: MiniDay
description: ${description} description: 仓鼠球:一个基于 Redis 的 Minecraft 服务端通用消息中间件原HamsterService
website: https://git.airgame.net/MiniDay/hamster-ball
UPDATE_CHECKER:
VERSION: ${version}
CHECK_TYPE: GITEA_RELEASES
GIT_BASE_URL: https://git.airgame.net
GIT_REPO: MiniDay/hamster-ball
DOWNLOAD_URL: https://jenkins.airgame.net/job/opensource/job/hamster-ball/
depend: depend:
- HamsterCore - HamsterCore

View File

@@ -8,14 +8,12 @@ channel-prefix: ""
# 是否在子服端更新玩家信息 # 是否在子服端更新玩家信息
# 默认情况下BC 统一管理玩家信息,包括记录 UUID 和玩家名称 # 默认情况下BC 统一管理玩家信息,包括记录 UUID 和玩家名称
# 如果一个群组服同时拥有多个 BC 入口,且每个 BC 入口为不同的玩家名称分配不同的 UUID # 如果一个群组服同时拥有多个 BC 入口
# 且每个 BC 入口为不同的玩家名称分配不同的 UUID
# (例如正版、盗版双入口,或网易多入口接同一个子服) # (例如正版、盗版双入口,或网易多入口接同一个子服)
# 则可以启用该功能以防止同一个名称占用多个 UUID 的问题 # 则可以启用该功能以防止 UUID 紊乱的问题
game-server-update-player-info: false game-server-update-player-info: false
# 启用后,子服启动时会自动注册该子服的入口配置,关闭时也会自动移除该子服的入口配置
auto-register-game-server: false
# 本服务器信息 # 本服务器信息
server-info: server-info:
# 服务器唯一识别码,最长 32 字符 # 服务器唯一识别码,最长 32 字符
@@ -36,20 +34,21 @@ server-info:
# 这个选项就会很有用 # 这个选项就会很有用
#datasource: #datasource:
# # 数据库链接驱动地址 # # 数据库链接驱动地址
# driver: "com.mysql.cj.jdbc.Driver" # driver: "com.mysql.jdbc.Driver"
# # MySQL数据库链接填写格式: # # 数据库链接填写格式:
# # jdbc:mysql://{数据库地址}:{数据库端口}/{使用的库名}?参数 # # 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 # # 推荐值1~3
# minimum-idle: 0 # minimum-idle: 0
# # 最大链接数 # # 最大链接数
# # 推荐值:不低于5 # # 推荐值:不低于3
# maximum-pool-size: 5 # maximum-pool-size: 3
# # 保持连接池可用的间隔 # # 保持连接池可用的间隔
# # 除非你的服务器数据库连接经常断开,否则不建议启用该选项 # # 除非你的服务器数据库连接经常断开,否则不建议启用该选项
# # 单位:毫秒 # # 单位:毫秒

View File

@@ -1,7 +1,7 @@
@file:Suppress("VulnerableLibrariesLocal") @file:Suppress("VulnerableLibrariesLocal", "GradlePackageVersionRange", "GradlePackageUpdate")
dependencies { 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.code.gson:gson:2.8.0")
compileOnly("com.google.guava:guava:31.0-jre") 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.BallCommonListener;
import cn.hamster3.mc.plugin.ball.common.listener.BallDebugListener; 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.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.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.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.AsyncEventBus;
import com.google.common.eventbus.EventBus; import com.google.common.eventbus.EventBus;
import lombok.Getter; import lombok.Getter;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import redis.clients.jedis.Jedis;
import javax.sql.DataSource; import javax.sql.DataSource;
import java.sql.*; import java.sql.*;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; 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; import java.util.logging.Logger;
@SuppressWarnings("unused")
@Getter @Getter
@SuppressWarnings({"unused", "CallToPrintStackTrace"})
public abstract class BallAPI { public abstract class BallAPI {
/** /**
* API 使用的通信频道 * API 使用的通信频道
@@ -46,53 +40,32 @@ public abstract class BallAPI {
* API 使用的玩家信息更新通信频道 * API 使用的玩家信息更新通信频道
*/ */
public static final String PLAYER_INFO_CHANNEL = "HamsterBall:PlayerInfo"; public static final String PLAYER_INFO_CHANNEL = "HamsterBall:PlayerInfo";
/** /**
* API 实例 * API 实例
*/ */
@Getter @Getter
protected static BallAPI instance; protected static BallAPI instance;
@NotNull @NotNull
private final BallConfig ballConfig; private final BallConfig ballConfig;
@NotNull
private final DataSource datasource;
@NotNull
private final BallServerInfo localServerInfo;
@NotNull @NotNull
private final EventBus eventBus; private final EventBus eventBus;
@NotNull @NotNull
private final Map<String, BallServerInfo> allServerInfo; private final Map<String, BallServerInfo> allServerInfo;
@NotNull @NotNull
private final Map<UUID, BallPlayerInfo> allPlayerInfo; private final Map<UUID, BallPlayerInfo> allPlayerInfo;
@NotNull @NotNull
private final Jedis redisSub; private final StatefulRedisPubSubConnection<String, BallMessage> redisPub;
@Nullable @NotNull
private ScheduledFuture<?> lockUpdater; private final StatefulRedisPubSubConnection<String, BallMessage> redisSub;
public BallAPI(@NotNull ConfigSection config, @NotNull BallServerType type) { public BallAPI(@NotNull BallConfig ballConfig) {
ConfigSection serverInfoConfig = config.getSection("server-info"); this.ballConfig = ballConfig;
if (serverInfoConfig == null) { redisPub = CoreAPI.getInstance().getRedisClient().connectPubSub(BallMessage.REDIS_CODEC);
throw new IllegalArgumentException("配置文件中未找到 server-info 节点"); redisSub = CoreAPI.getInstance().getRedisClient().connectPubSub(BallMessage.REDIS_CODEC);
}
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);
allServerInfo = new ConcurrentHashMap<>(); allServerInfo = new ConcurrentHashMap<>();
allPlayerInfo = 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.getChannelPrefix());
getLogger().info("启用子服更新玩家状态: " + ballConfig.isGameServerUpdatePlayerInfo()); getLogger().info("启用子服更新玩家状态: " + ballConfig.isGameServerUpdatePlayerInfo());
if (ballConfig.isGameServerUpdatePlayerInfo()) { if (ballConfig.isGameServerUpdatePlayerInfo()) {
@@ -105,30 +78,8 @@ public abstract class BallAPI {
} }
protected void enable() throws SQLException, InterruptedException { protected void enable() throws SQLException, InterruptedException {
CoreAPI.getInstance().getExecutorService().submit(() -> redisSub.subscribe(BallRedisListener.INSTANCE, BALL_CHANNEL)); BallServerInfo localInfo = getLocalServerInfo();
try (Jedis jedis = CoreAPI.getInstance().getJedisPool().getResource()) {
String key = "HamsterBall:ServerInfo:" + localServerInfo.getId();
if (jedis.exists(key)) {
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);
}
}
try (Connection connection = getDatasource().getConnection()) { try (Connection connection = getDatasource().getConnection()) {
try (Statement statement = connection.createStatement()) { try (Statement statement = connection.createStatement()) {
statement.execute("CREATE TABLE IF NOT EXISTS `hamster_ball_player_info`(" + statement.execute("CREATE TABLE IF NOT EXISTS `hamster_ball_player_info`(" +
@@ -138,11 +89,44 @@ public abstract class BallAPI {
"`proxy_server` VARCHAR(32) NOT NULL," + "`proxy_server` VARCHAR(32) NOT NULL," +
"`online` BOOLEAN NOT NULL" + "`online` BOOLEAN NOT NULL" +
") CHARSET utf8mb4;"); ") 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`(" + statement.execute("CREATE TABLE IF NOT EXISTS `hamster_ball_cached_message`(" +
"`uuid` CHAR(36) NOT NULL," + "`uuid` CHAR(36) NOT NULL," +
"`message` TEXT NOT NULL" + "`message` TEXT NOT NULL" +
") CHARSET utf8mb4;"); ") 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()) { if (getBallConfig().isGameServerUpdatePlayerInfo()) {
try (Statement statement = connection.createStatement()) { try (Statement statement = connection.createStatement()) {
try (ResultSet set = statement.executeQuery(String.format( try (ResultSet set = statement.executeQuery(String.format(
@@ -180,21 +164,22 @@ public abstract class BallAPI {
} }
getLogger().info("从数据库中加载了 " + allServerInfo.size() + " 条服务器信息"); getLogger().info("从数据库中加载了 " + allServerInfo.size() + " 条服务器信息");
getLogger().info("从数据库中加载了 " + allPlayerInfo.size() + " 条玩家信息"); getLogger().info("从数据库中加载了 " + allPlayerInfo.size() + " 条玩家信息");
redisPub.addListener(BallRedisListener.INSTANCE);
subscribeIgnorePrefix(BALL_CHANNEL);
} }
protected void disable() throws SQLException, InterruptedException { protected void disable() throws SQLException, InterruptedException {
sendBallMessage(BallAPI.BALL_CHANNEL, new BallMessage( sendBallMessage(BallAPI.BALL_CHANNEL, new BallMessage(
BallActions.ServerOffline.name(), new ServerOfflineEvent(getLocalServerInfo()) BallActions.ServerOffline.name(), new ServerOfflineEvent(getLocalServerInfo())
), false, true); ), 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 (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( try (PreparedStatement statement = connection.prepareStatement(
"UPDATE `hamster_ball_player_info` SET `online`=false WHERE `game_server`=? OR `proxy_server`=?" "UPDATE `hamster_ball_player_info` SET `online`=false WHERE `game_server`=? OR `proxy_server`=?"
)) { )) {
@@ -203,7 +188,6 @@ public abstract class BallAPI {
statement.executeUpdate(); statement.executeUpdate();
} }
} }
redisSub.close();
} }
/** /**
@@ -337,7 +321,7 @@ public abstract class BallAPI {
statement.executeUpdate(); statement.executeUpdate();
} }
} catch (Exception e) { } catch (Exception e) {
BallAPI.getInstance().getLogger().log(Level.SEVERE, "更新玩家缓存消息时遇到了一个异常", e); e.printStackTrace();
} }
} }
} }
@@ -486,15 +470,13 @@ public abstract class BallAPI {
channel = ballConfig.getChannelPrefix() + channel; channel = ballConfig.getChannelPrefix() + channel;
} }
if (block) { if (block) {
try (Jedis jedis = CoreAPI.getInstance().getJedisPool().getResource()) { redisSub.sync().publish(channel, message);
jedis.publish(channel, CoreAPI.getInstance().getGson().toJson(message));
}
eventBus.post(new MessageSentEvent(channel, message)); eventBus.post(new MessageSentEvent(channel, message));
} else { } else {
@NotNull String finalChannel = channel; @NotNull String finalChannel = channel;
CoreAPI.getInstance().getExecutorService().submit(() -> { redisSub.async().publish(channel, message).whenComplete((aLong, throwable) -> {
try (Jedis jedis = CoreAPI.getInstance().getJedisPool().getResource()) { if (throwable != null) {
jedis.publish(finalChannel, CoreAPI.getInstance().getGson().toJson(message)); return;
} }
eventBus.post(new MessageSentEvent(finalChannel, message)); eventBus.post(new MessageSentEvent(finalChannel, message));
}); });
@@ -506,29 +488,22 @@ public abstract class BallAPI {
* <p> * <p>
* 会自动加上 config 中设置的频道前缀 * 会自动加上 config 中设置的频道前缀
* *
* @param channels 频道名称 * @param channel 频道名称
*/ */
public void subscribe(@NotNull String... channels) { public void subscribe(@NotNull String... channel) {
for (int i = 0; i < channels.length; i++) { for (int i = 0; i < channel.length; i++) {
channels[i] = ballConfig.getChannelPrefix() + channels[i]; channel[i] = ballConfig.getChannelPrefix() + channel[i];
} }
subscribeRaw(channels); redisPub.sync().subscribe(channel);
} }
/** /**
* 忽略频道前缀配置,订阅 redis 消息频道 * 忽略频道前缀配置,订阅 redis 消息频道
* *
* @param channels 频道名称 * @param channel 频道名称
*/ */
public void subscribeRaw(@NotNull String... channels) { public void subscribeIgnorePrefix(@NotNull String... channel) {
BallRedisListener.INSTANCE.subscribe(channels); redisPub.sync().subscribe(channel);
// CoreAPI.getInstance().getExecutorService().submit(() -> {
// try {
// redisSub.subscribe(BallRedisListener.INSTANCE, channels);
// } catch (Exception | Error e) {
// e.printStackTrace();
// }
// });
} }
/** /**
@@ -537,33 +512,28 @@ public abstract class BallAPI {
* @param patterns 频道名称正则表达式 * @param patterns 频道名称正则表达式
*/ */
public void subscribePatterns(@NotNull String patterns) { public void subscribePatterns(@NotNull String patterns) {
BallRedisListener.INSTANCE.psubscribe(patterns); redisPub.sync().psubscribe(patterns);
// CoreAPI.getInstance().getExecutorService().submit(
// () -> redisSub.psubscribe(BallRedisListener.INSTANCE, patterns)
// );
} }
/** /**
* 取消订阅 redis 消息频道 * 取消订阅 redis 频道
* <p>
* 会自动加上 config 中设置的频道前缀
* *
* @param channels 频道名称 * @param channel 频道名称
*/ */
public void unsubscribe(@NotNull String... channels) { public void unsubscribe(@NotNull String... channel) {
for (int i = 0; i < channels.length; i++) { for (int i = 0; i < channel.length; i++) {
channels[i] = ballConfig.getChannelPrefix() + channels[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) { public void unsubscribeIgnorePrefix(@NotNull String... channel) {
BallRedisListener.INSTANCE.unsubscribe(channels); redisPub.sync().unsubscribe(channel);
} }
/** /**
@@ -572,12 +542,22 @@ public abstract class BallAPI {
* @param patterns 频道名称正则表达式 * @param patterns 频道名称正则表达式
*/ */
public void unsubscribePatterns(@NotNull String 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 @NotNull
public String getLocalServerId() { public String getLocalServerId() {
return localServerInfo.getId(); return ballConfig.getServerInfo().getId();
} }
/** /**
@@ -694,4 +674,9 @@ public abstract class BallAPI {
@NotNull @NotNull
public abstract Logger getLogger(); public abstract Logger getLogger();
@NotNull
public DataSource getDatasource() {
return ballConfig.getDatasource() == null ? CoreAPI.getInstance().getDataSource() : ballConfig.getDatasource();
}
} }

View File

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

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.api.BallAPI;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerType; import cn.hamster3.mc.plugin.ball.common.entity.BallServerType;
import cn.hamster3.mc.plugin.core.common.api.CoreAPI; 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.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
@@ -15,6 +16,7 @@ import org.jetbrains.annotations.Nullable;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.BigInteger; import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.UUID; import java.util.UUID;
/** /**
@@ -23,9 +25,33 @@ import java.util.UUID;
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class BallMessage { 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); 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 对象获取消息内容 * 以 Java 对象获取消息内容
* *
@@ -145,6 +199,6 @@ public class BallMessage {
@Override @Override
public String toString() { 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; package cn.hamster3.mc.plugin.ball.common.entity;
import cn.hamster3.mc.plugin.core.common.config.ConfigSection;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
/** /**
@@ -43,15 +40,6 @@ public class BallServerInfo {
*/ */
private int port; 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 @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
package cn.hamster3.mc.plugin.ball.common.event.operate; 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.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.UUID; 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 BallPlayerPreConnectServerEvent 玩家准备进入子服
* @see BallPlayerConnectServerEvent 玩家进入子服
* @see BallPlayerPostConnectServerEvent 玩家已经进入子服 * @see BallPlayerPostConnectServerEvent 玩家已经进入子服
*/ */
@Data @Data

View File

@@ -10,6 +10,7 @@ import org.jetbrains.annotations.Nullable;
* 玩家准备进入子服 * 玩家准备进入子服
* *
* @see BallPlayerPreConnectServerEvent 玩家准备进入子服 * @see BallPlayerPreConnectServerEvent 玩家准备进入子服
* @see BallPlayerConnectServerEvent 玩家进入子服
* @see BallPlayerPostConnectServerEvent 玩家已经进入子服 * @see BallPlayerPostConnectServerEvent 玩家已经进入子服
*/ */
@Data @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 cn.hamster3.mc.plugin.ball.common.entity.BallServerInfo;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import org.jetbrains.annotations.NotNull;
/** /**
* 服务器离线 * 服务器离线
*/ */
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@EqualsAndHashCode(callSuper = true) public class ServerOfflineEvent {
public class ServerOfflineEvent extends BallServerEvent { @NotNull
public ServerOfflineEvent(BallServerInfo info) { private final BallServerInfo serverInfo;
super(info);
@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 cn.hamster3.mc.plugin.ball.common.entity.BallServerInfo;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import org.jetbrains.annotations.NotNull;
/** /**
* 服务器上线 * 服务器上线
*/ */
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@EqualsAndHashCode(callSuper = true) public class ServerOnlineEvent {
public class ServerOnlineEvent extends BallServerEvent { @NotNull
public ServerOnlineEvent(BallServerInfo info) { private final BallServerInfo serverInfo;
super(info);
}
} }

View File

@@ -83,6 +83,11 @@ public class BallCommonListener {
BallAPI.getInstance().getEventBus().post(event); BallAPI.getInstance().getEventBus().post(event);
break; break;
} }
case BallPlayerConnectServer: {
BallPlayerConnectServerEvent event = CoreAPI.getInstance().getGson().fromJson(message.getContent(), BallPlayerConnectServerEvent.class);
BallAPI.getInstance().getEventBus().post(event);
break;
}
case BallPlayerPostConnectServer: { case BallPlayerPostConnectServer: {
BallPlayerPostConnectServerEvent event = CoreAPI.getInstance().getGson().fromJson(message.getContent(), BallPlayerPostConnectServerEvent.class); BallPlayerPostConnectServerEvent event = CoreAPI.getInstance().getGson().fromJson(message.getContent(), BallPlayerPostConnectServerEvent.class);
BallAPI.getInstance().getEventBus().post(event); BallAPI.getInstance().getEventBus().post(event);
@@ -120,6 +125,12 @@ public class BallCommonListener {
BallAPI.getInstance().getEventBus().post(event); BallAPI.getInstance().getEventBus().post(event);
} }
@Subscribe
public void onBallPlayerConnectServer(BallPlayerConnectServerEvent event) {
BallPlayerInfo info = event.getPlayerInfo();
BallAPI.getInstance().getAllPlayerInfo().put(info.getUuid(), info);
}
@Subscribe @Subscribe
public void onBallPlayerInfoUpdate(BallPlayerInfoUpdateEvent event) { public void onBallPlayerInfoUpdate(BallPlayerInfoUpdateEvent event) {
BallPlayerInfo info = event.getPlayerInfo(); BallPlayerInfo info = event.getPlayerInfo();
@@ -128,11 +139,12 @@ public class BallCommonListener {
@Subscribe @Subscribe
public void onServerOnline(ServerOnlineEvent event) { public void onServerOnline(ServerOnlineEvent event) {
BallAPI.getInstance().getAllServerInfo().put(event.getId(), event); BallServerInfo info = event.getServerInfo();
switch (event.getType()) { BallAPI.getInstance().getAllServerInfo().put(info.getId(), info);
switch (info.getType()) {
case GAME: { case GAME: {
BallAPI.getInstance().getAllPlayerInfo().forEach((uuid, playerInfo) -> { BallAPI.getInstance().getAllPlayerInfo().forEach((uuid, playerInfo) -> {
if (playerInfo.getGameServer().equals(event.getId())) { if (playerInfo.getGameServer().equals(info.getId())) {
playerInfo.setOnline(false); playerInfo.setOnline(false);
} }
}); });
@@ -140,7 +152,7 @@ public class BallCommonListener {
} }
case PROXY: { case PROXY: {
BallAPI.getInstance().getAllPlayerInfo().forEach((uuid, playerInfo) -> { BallAPI.getInstance().getAllPlayerInfo().forEach((uuid, playerInfo) -> {
if (playerInfo.getProxyServer().equals(event.getId())) { if (playerInfo.getProxyServer().equals(info.getId())) {
playerInfo.setOnline(false); playerInfo.setOnline(false);
} }
}); });
@@ -151,7 +163,7 @@ public class BallCommonListener {
@Subscribe @Subscribe
public void onServerOffline(ServerOfflineEvent event) { public void onServerOffline(ServerOfflineEvent event) {
String serverID = event.getId(); String serverID = event.getServerID();
BallServerInfo info = BallAPI.getInstance().getAllServerInfo().remove(serverID); BallServerInfo info = BallAPI.getInstance().getAllServerInfo().remove(serverID);
if (info == null) { if (info == null) {
return; 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.api.BallAPI;
import cn.hamster3.mc.plugin.ball.common.data.BallMessage; import cn.hamster3.mc.plugin.ball.common.data.BallMessage;
import cn.hamster3.mc.plugin.ball.common.event.message.MessageReceivedEvent; 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 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(); public static final BallRedisListener INSTANCE = new BallRedisListener();
private BallRedisListener() { private BallRedisListener() {
} }
@Override @Override
public void onMessage(String channel, String message) { public void message(String channel, BallMessage ballMessage) {
CoreAPI.getInstance().getExecutorService().submit(() -> { if (channel.startsWith(BallAPI.getInstance().getBallConfig().getChannelPrefix())) {
try { channel = channel.substring(BallAPI.getInstance().getBallConfig().getChannelPrefix().length());
String finalChannel = channel; }
if (finalChannel.startsWith(BallAPI.getInstance().getBallConfig().getChannelPrefix())) { BallAPI ballAPI = BallAPI.getInstance();
finalChannel = finalChannel.substring(BallAPI.getInstance().getBallConfig().getChannelPrefix().length()); EventBus eventBus = ballAPI.getEventBus();
} if (ballMessage.getReceiverType() != null && ballMessage.getReceiverType() != ballAPI.getLocalServerInfo().getType()) {
BallMessage ballMessage = CoreAPI.getInstance().getGson().fromJson(message, BallMessage.class); return;
BallAPI ballAPI = BallAPI.getInstance(); }
EventBus eventBus = ballAPI.getEventBus(); if (ballMessage.getReceiverID() != null && !ballAPI.isLocalServer(ballMessage.getReceiverID())) {
if (ballMessage.getReceiverType() != null && ballMessage.getReceiverType() != ballAPI.getLocalServerInfo().getType()) { return;
return; }
} try {
if (ballMessage.getReceiverID() != null && !ballAPI.isLocalServer(ballMessage.getReceiverID())) { eventBus.post(new MessageReceivedEvent(channel, ballMessage));
return; } catch (Exception e) {
} e.printStackTrace();
eventBus.post(new MessageReceivedEvent(finalChannel, ballMessage)); }
} catch (Exception | Error e) {
e.printStackTrace();
}
});
} }
@Override @Override
public void onPMessage(String pattern, String channel, String message) { public void message(String pattern, String channel, BallMessage info) {
onMessage(channel, message); message(channel, info);
} }
@Override @Override
public void onSubscribe(String channel, int subscribedChannels) { public void subscribed(String channel, long count) {
BallAPI.getInstance().getLogger().info("已订阅 redis 频道 " + channel); BallAPI.getInstance().getLogger().info("已订阅 redis 频道 " + channel);
} }
@Override @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); BallAPI.getInstance().getLogger().info("已取消订阅 redis 频道 " + channel);
} }
@Override @Override
public void onPSubscribe(String pattern, int subscribedChannels) { public void punsubscribed(String pattern, long count) {
BallAPI.getInstance().getLogger().info("已订阅 redis 频道(正则) " + pattern);
}
@Override
public void onPUnsubscribe(String pattern, int subscribedChannels) {
BallAPI.getInstance().getLogger().info("已取消订阅 redis 频道(正则) " + pattern); 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,149 +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.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.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;
import java.util.logging.Level;
@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();
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(
Objects.requireNonNull(getClass().getResourceAsStream("/config.yml")),
configFile.toPath(),
StandardCopyOption.REPLACE_EXISTING
);
}
config = YamlConfig.load(configFile);
CoreVelocityAPI.init(config);
logger.info("已初始化 BallAPI");
} catch (Exception e) {
slf4jLogger.error("BallAPI 初始化失败", e);
proxyServer.shutdown(Component.text("由于 HamsterBall 初始化失败, 服务器将立即关闭"));
}
long time = System.currentTimeMillis() - start;
logger.info("仓鼠球初始化完成,总计耗时 " + time + " ms");
}
@Subscribe(order = PostOrder.EARLY)
public void onProxyInitialization(ProxyInitializeEvent event) {
long start = System.currentTimeMillis();
java.util.logging.Logger logger = getLogger();
logger.info("仓鼠球正在启动");
try {
CoreVelocityAPI.getInstance().enable();
} catch (Exception e) {
logger.log(Level.SEVERE, "仓鼠球启动失败", e);
logger.info("由于仓鼠球启动失败,服务器将立即关闭");
proxyServer.shutdown(Component.text("仓鼠球启动失败"));
return;
}
BallAPI.getInstance().getEventBus().register(BallVelocityListener.INSTANCE);
logger.info("已注册监听器 BallVelocityListener");
proxyServer.getEventManager().register(this, BallVelocityMainListener.INSTANCE);
logger.info("已注册监听器 BallVelocityMainListener");
proxyServer.getEventManager().register(this, UpdatePlayerInfoListener.INSTANCE);
logger.info("已注册监听器 UpdatePlayerInfoListener");
if (config.getBoolean("auto-register-game-server", false)) {
BallAPI.getInstance().getEventBus().register(VelocityServerListener.INSTANCE);
logger.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;
logger.info("仓鼠球启动完成,总计耗时 " + time + " ms");
}
@Subscribe(order = PostOrder.LATE)
public void onProxyShutdown(ProxyShutdownEvent event) {
long start = System.currentTimeMillis();
java.util.logging.Logger logger = getLogger();
logger.info("仓鼠球正在关闭");
try {
CoreVelocityAPI.getInstance().disable();
} catch (Exception e) {
logger.log(Level.SEVERE, "关闭仓鼠球时遇到了一个异常", e);
}
long time = System.currentTimeMillis() - start;
logger.info("仓鼠球已关闭,总计耗时 " + time + " ms");
}
}

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,121 +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.BallServerType;
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.getType() != null && event.getType() != BallServerType.PROXY) {
return;
}
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) {
if (event.getType() != null && event.getType() != BallServerType.GAME) {
return;
}
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,67 +0,0 @@
# 是否允许在控制台输出调试信息
debug: 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 { plugins {
id("java-library") id("java")
id("maven-publish") id("maven-publish")
id("com.github.johnrengelman.shadow") version "8+" id("com.github.johnrengelman.shadow") version "8+"
} }
group = "cn.hamster3.mc.plugin" group = "cn.hamster3.mc.plugin"
version = "1.6.4" version = "1.5.7"
description = "基于 Redis 的 Minecraft 服务端通用消息中间件"
subprojects { subprojects {
apply { apply {
@@ -17,7 +16,6 @@ subprojects {
group = rootProject.group group = rootProject.group
version = rootProject.version version = rootProject.version
description = rootProject.description
repositories { repositories {
maven("https://maven.airgame.net/maven-public/") maven("https://maven.airgame.net/maven-public/")
@@ -45,6 +43,12 @@ subprojects {
from(rootProject.file("LICENSE")) from(rootProject.file("LICENSE"))
duplicatesStrategy = DuplicatesStrategy.EXCLUDE duplicatesStrategy = DuplicatesStrategy.EXCLUDE
} }
jar {
archiveClassifier = "dev"
}
shadowJar {
archiveClassifier = "FIX"
}
build { build {
dependsOn(shadowJar) dependsOn(shadowJar)
} }
@@ -58,7 +62,7 @@ subprojects {
} }
repositories { repositories {
maven { maven {
url = uri("https://maven.airgame.net/public") url = uri("https://maven.airgame.net/maven-airgame")
credentials { credentials {
username = rootProject.properties.getOrDefault("maven_username", "").toString() username = rootProject.properties.getOrDefault("maven_username", "").toString()

View File

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