feat: 使用 guava 的 EventBus

This commit is contained in:
2023-11-11 17:54:40 +08:00
parent 167e845bae
commit a5c2eec5e5
41 changed files with 642 additions and 864 deletions

View File

@@ -0,0 +1,83 @@
package cn.hamster3.mc.plugin.core.bungee;
import cn.hamster3.mc.plugin.ball.common.api.BallAPI;
import cn.hamster3.mc.plugin.ball.common.entity.BallPlayerInfo;
import cn.hamster3.mc.plugin.ball.common.event.BallActions;
import cn.hamster3.mc.plugin.ball.common.event.server.ServerOnlineEvent;
import cn.hamster3.mc.plugin.core.bungee.api.BallBungeeCordAPI;
import cn.hamster3.mc.plugin.core.bungee.listener.BallBungeeCordListener;
import cn.hamster3.mc.plugin.core.bungee.util.BallBungeeCordUtils;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.plugin.Plugin;
import java.util.logging.Logger;
public class HamsterBallPlugin extends Plugin {
private static HamsterBallPlugin instance;
public static HamsterBallPlugin getInstance() {
return instance;
}
@Override
public void onLoad() {
long start = System.currentTimeMillis();
Logger logger = getLogger();
logger.info("仓鼠球正在初始化");
instance = this;
try {
BallBungeeCordAPI.init();
logger.info("已初始化 BallAPI");
} catch (Exception e) {
e.printStackTrace();
ProxyServer.getInstance().stop("由于 HamsterBall 未能成功连接, 服务器将立即关闭");
}
long time = System.currentTimeMillis() - start;
logger.info("仓鼠球初始化完成,总计耗时 " + time + " ms");
}
@Override
public void onEnable() {
long start = System.currentTimeMillis();
Logger logger = getLogger();
logger.info("仓鼠球正在启动");
try {
BallBungeeCordAPI.getInstance().enable();
} catch (Exception e) {
e.printStackTrace();
}
ProxyServer.getInstance().getPluginManager().registerListener(this, BallBungeeCordListener.INSTANCE);
BallAPI.getInstance().getEventBus().register(BallBungeeCordListener.INSTANCE);
logger.info("已注册 BallBungeeCordListener");
BallAPI.getInstance().sendBallMessage(
BallAPI.BALL_CHANNEL, BallActions.ServerOnline.name(),
new ServerOnlineEvent(BallAPI.getInstance().getLocalServerInfo())
);
// 移除失效的在线玩家
BallAPI.getInstance().getPlayerInfo().values()
.stream()
.filter(BallPlayerInfo::isOnline)
.filter(o -> BallAPI.getInstance().isLocalServer(o.getProxyServer()))
.forEach(playerInfo -> {
playerInfo.setOnline(false);
BallBungeeCordUtils.uploadPlayerInfo(playerInfo);
});
long time = System.currentTimeMillis() - start;
logger.info("仓鼠球启动完成,总计耗时 " + time + " ms");
}
@Override
public void onDisable() {
long start = System.currentTimeMillis();
Logger logger = getLogger();
logger.info("仓鼠球正在关闭");
try {
BallBungeeCordAPI.getInstance().disable();
} catch (Exception e) {
e.printStackTrace();
}
long time = System.currentTimeMillis() - start;
logger.info("仓鼠球已关闭,总计耗时 " + time + " ms");
}
}

View File

@@ -0,0 +1,75 @@
package cn.hamster3.mc.plugin.core.bungee.api;
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.core.bungee.HamsterBallPlugin;
import cn.hamster3.mc.plugin.core.bungee.util.BallBungeeCordUtils;
import cn.hamster3.mc.plugin.core.bungee.util.CoreBungeeCordUtils;
import cn.hamster3.mc.plugin.core.common.api.CoreAPI;
import io.lettuce.core.RedisClient;
import net.md_5.bungee.config.Configuration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.Map;
import java.util.logging.Logger;
public class BallBungeeCordAPI extends BallAPI {
public BallBungeeCordAPI(@NotNull BallServerInfo localServerInfo, @Nullable DataSource datasource, @NotNull RedisClient redisClient, boolean debug) {
super(localServerInfo, datasource, redisClient, debug);
}
public static BallBungeeCordAPI getInstance() {
return (BallBungeeCordAPI) instance;
}
public static void init() {
if (instance != null) {
return;
}
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 节点,启用自定义数据库连接");
datasource = BallBungeeCordUtils.getDataSource(config.getSection("datasource"));
} else {
plugin.getLogger().info("未检测到配置文件中的 datasource 节点,复用 HamsterCore 数据库连接");
datasource = CoreAPI.getInstance().getDataSource();
}
RedisClient redisClient = RedisClient.create(config.getString("redis-url", "redis://localhost:6379?clientName=HamsterBall"));
instance = new BallBungeeCordAPI(serverInfo, datasource, redisClient, config.getBoolean("debug", false));
}
@Override
public void enable() throws SQLException, InterruptedException {
super.enable();
}
@Override
public void disable() throws SQLException, InterruptedException {
super.disable();
}
@Override
public @NotNull Logger getLogger() {
return HamsterBallPlugin.getInstance().getLogger();
}
}

View File

@@ -0,0 +1,184 @@
package cn.hamster3.mc.plugin.core.bungee.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.entity.BallServerType;
import cn.hamster3.mc.plugin.ball.common.event.BallActions;
import cn.hamster3.mc.plugin.ball.common.event.operate.*;
import cn.hamster3.mc.plugin.ball.common.event.player.*;
import cn.hamster3.mc.plugin.core.bungee.HamsterBallPlugin;
import cn.hamster3.mc.plugin.core.bungee.util.BallBungeeCordUtils;
import cn.hamster3.mc.plugin.core.common.api.CoreAPI;
import cn.hamster3.mc.plugin.core.lib.net.kyori.adventure.audience.Audience;
import cn.hamster3.mc.plugin.core.lib.net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
import com.google.common.eventbus.Subscribe;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.*;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority;
import java.util.UUID;
public final class BallBungeeCordListener implements Listener {
public static final BallBungeeCordListener INSTANCE = new BallBungeeCordListener();
private BallBungeeCordListener() {
}
@EventHandler(priority = EventPriority.HIGH)
public void onPreLogin(PreLoginEvent event) {
BallAPI.getInstance().sendBallMessage(
BallAPI.BALL_CHANNEL, BallActions.BallPlayerPreLogin.name(),
new BallPlayerPreLoginEvent(event.getConnection().getName())
);
}
@EventHandler(priority = EventPriority.HIGH)
public void onLogin(LoginEvent event) {
if (event.isCancelled()) {
return;
}
PendingConnection connection = event.getConnection();
BallAPI.getInstance().sendBallMessage(
BallAPI.BALL_CHANNEL, BallActions.BallPlayerLogin.name(),
new BallPlayerLoginEvent(new BallPlayerInfo(
connection.getUniqueId(), connection.getName(), "connecting",
BallAPI.getInstance().getLocalServerId(), true
))
);
}
@EventHandler(priority = EventPriority.HIGH)
public void onPostLogin(PostLoginEvent event) {
ProxiedPlayer player = event.getPlayer();
BallPlayerInfo playerInfo = BallBungeeCordUtils.getPlayerInfo(player, true);
BallAPI.getInstance().sendBallMessage(
BallAPI.BALL_CHANNEL, BallActions.BallPlayerPostLogin.name(),
new BallPlayerPostLoginEvent(playerInfo)
);
}
@EventHandler(priority = EventPriority.HIGH)
public void onServerConnect(ServerConnectEvent event) {
ProxiedPlayer player = event.getPlayer();
BallPlayerInfo playerInfo = BallBungeeCordUtils.getPlayerInfo(player, true);
playerInfo.setGameServer(event.getTarget().getName());
BallAPI.getInstance().sendBallMessage(
BallAPI.BALL_CHANNEL, BallActions.BallPlayerPreConnectServer.name(),
new BallPlayerPreConnectServerEvent(playerInfo, playerInfo.getGameServer(), event.getTarget().getName())
);
BallBungeeCordUtils.uploadPlayerInfo(playerInfo);
}
@EventHandler(priority = EventPriority.HIGH)
public void onServerConnected(ServerConnectedEvent event) {
ProxiedPlayer player = event.getPlayer();
BallPlayerInfo playerInfo = BallBungeeCordUtils.getPlayerInfo(player, true);
playerInfo.setGameServer(event.getServer().getInfo().getName());
BallAPI.getInstance().sendBallMessage(
BallAPI.BALL_CHANNEL,
BallActions.BallPlayerPostConnectServer.name(),
new BallPlayerPostConnectServerEvent(playerInfo)
);
BallBungeeCordUtils.uploadPlayerInfo(playerInfo);
}
@EventHandler(priority = EventPriority.HIGH)
public void onPlayerDisconnect(PlayerDisconnectEvent event) {
ProxiedPlayer player = event.getPlayer();
BallPlayerInfo playerInfo = BallBungeeCordUtils.getPlayerInfo(player, false);
BallAPI.getInstance().sendBallMessage(BallAPI.BALL_CHANNEL, BallActions.BallPlayerLogout.name(),
new BallPlayerLogoutEvent(playerInfo)
);
BallBungeeCordUtils.uploadPlayerInfo(playerInfo);
}
@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 = ProxyServer.getInstance();
server.getPluginManager().dispatchCommand(server.getConsole(), event.getCommand());
}
@Subscribe
public void onDispatchPlayerCommandEvent(DispatchPlayerCommandEvent event) {
if (event.getType() != null && event.getType() != BallServerType.GAME) {
return;
}
ProxyServer server = ProxyServer.getInstance();
if (event.getUuid() != null) {
ProxiedPlayer player = server.getPlayer(event.getUuid());
if (player == null) {
return;
}
server.getPluginManager().dispatchCommand(player, event.getCommand());
return;
}
for (ProxiedPlayer player : server.getPlayers()) {
server.getPluginManager().dispatchCommand(player, event.getCommand());
}
}
@Subscribe
public void onKickPlayerEvent(KickPlayerEvent event) {
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(event.getUuid());
BaseComponent[] components = BungeeComponentSerializer.get().serialize(event.getReason());
player.disconnect(components);
}
@Subscribe
public void onSendMessageToPlayerEvent(SendMessageToPlayerEvent event) {
for (UUID receiver : event.getReceivers()) {
Audience audience = CoreAPI.getInstance().getAudienceProvider().player(receiver);
event.getMessage().show(audience);
}
}
@Subscribe
public void onSendPlayerToLocationEvent(SendPlayerToLocationEvent event) {
String serverID = event.getLocation().getServerID();
ServerInfo serverInfo = ProxyServer.getInstance().getServerInfo(serverID);
if (serverInfo == null) {
HamsterBallPlugin.getInstance().getLogger().warning("试图传送玩家时失败: 服务器 " + serverID + " 不在线");
return;
}
for (UUID uuid : event.getSendPlayerUUID()) {
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(uuid);
if (player == null) {
continue;
}
if (player.getServer().getInfo().getName().equals(serverID)) {
continue;
}
player.connect(serverInfo);
}
}
@Subscribe
public void onSendPlayerToPlayerEvent(SendPlayerToPlayerEvent event) {
UUID toPlayerUUID = event.getToPlayerUUID();
ProxiedPlayer toPlayer = ProxyServer.getInstance().getPlayer(toPlayerUUID);
if (toPlayer == null) {
HamsterBallPlugin.getInstance().getLogger().warning("试图传送玩家时失败: 目标玩家 " + toPlayerUUID + " 不在线");
return;
}
ServerInfo toServer = toPlayer.getServer().getInfo();
for (UUID uuid : event.getSendPlayerUUID()) {
ProxiedPlayer sendPlayer = ProxyServer.getInstance().getPlayer(uuid);
if (sendPlayer.getServer().getInfo().getName().equals(toServer.getName())) {
continue;
}
sendPlayer.connect(toServer);
}
}
}

View File

@@ -0,0 +1,88 @@
package cn.hamster3.mc.plugin.core.bungee.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 cn.hamster3.mc.plugin.core.lib.com.zaxxer.hikari.HikariConfig;
import cn.hamster3.mc.plugin.core.lib.com.zaxxer.hikari.HikariDataSource;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.config.Configuration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public final class BallBungeeCordUtils {
private BallBungeeCordUtils() {
}
@NotNull
public static BallPlayerInfo getPlayerInfo(@NotNull ProxiedPlayer player, boolean online) {
Server server = player.getServer();
return new BallPlayerInfo(
player.getUniqueId(),
player.getName(),
server == null ? "connecting" : server.getInfo().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) {
e.printStackTrace();
}
BallAPI.getInstance().sendBallMessage(
BallAPI.BALL_CHANNEL,
BallActions.BallPlayerInfoUpdate.name(),
new BallPlayerInfoUpdateEvent(playerInfo)
);
});
}
@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

@@ -0,0 +1,9 @@
name: HamsterBall
main: cn.hamster3.mc.plugin.core.bungee.HamsterBallPlugin
version: ${version}
author: MiniDay
description: 仓鼠球:一个基于 Redis 的 Minecraft 服务端通用消息中间件原HamsterService
depend:
- HamsterCore

View File

@@ -0,0 +1,62 @@
# 是否允许在控制台输出调试信息
debug: false
# redis 连接配置,连接格式如下:
# redis://用户名:密码@主机名:端口/数据库索引?参数名=参数值&参数名=参数值
# 若没有设置 redis 用户名,则可以省略,以下方法三选一:
# 使用默认用户名: redis://default:密码@localhost:6379?clientName=HamsterBall
# 不写用户名: redis://:密码@localhost:6379?clientName=HamsterBall
# 只写密码: redis://密码@localhost:6379?clientName=HamsterBall
# 若没有设置 redis 用户名和密码,则可以省略:
# redis://localhost:6379?clientName=HamsterBall
# 若不设置数据库,则默认使用 0
# 详细信息https://github.com/lettuce-io/lettuce-core/wiki/Redis-URI-and-connection-details
redis-url: "redis://localhost:6379?clientName=HamsterBall"
# 本服务器信息
server-info:
# 服务器唯一识别码,最长 32 字符
id: "BungeeCord"
# 服务端名称,常用于展示给玩家看
name: "代理端"
# 当前子服的地址
# 不填则自动设置为 0.0.0.0
host: 0.0.0.0
# 当前子服端口
# 不填则自动设置为 25577
port: 25577
# 数据库连接池配置
# 如果注释该选项则默认使用 HamsterCore 中的连接池配置
#datasource:
# # 数据库链接驱动地址
# driver: "com.mysql.jdbc.Driver"
# # 数据库链接填写格式:
# # jdbc:mysql://{数据库地址}:{数据库端口}/{使用的库名}?参数
# # 除非你知道自己在做什么,否则不建议随意更改参数
# url: "jdbc:mysql://sql.hamster3.cn:3306/Test1?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true"
# # 用户名
# username: "Test"
# # 密码
# password: "Test123.."
# # 最小闲置链接数
# # 推荐值1~3
# minimum-idle: 0
# # 最大链接数
# # 推荐值不低于3
# maximum-pool-size: 3
# # 保持连接池可用的间隔
# # 除非你的服务器数据库连接经常断开,否则不建议启用该选项
# # 单位:毫秒
# # 默认值为0禁用
# keep-alive-time: 0
# # 连接闲置回收时间
# # 单位:毫秒
# # 推荐值60000010分钟
# idle-timeout: 600000
# # 链接最长存活时间
# # 单位:毫秒
# max-lifetime: 1800000
# # 验证连接存活的超时时间
# # 单位:毫秒
# validation-timeout: 5000