feat(ball-velocity): 添加 velocity 支持

This commit is contained in:
2024-03-17 22:12:34 +08:00
parent 059864424c
commit 37ef53d635
11 changed files with 626 additions and 0 deletions

View File

@@ -0,0 +1,139 @@
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.util.BallVelocityUtils;
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;
@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
);
}
CoreVelocityAPI.init(configFile);
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("已注册监听器 BallBungeeListener");
proxyServer.getEventManager().register(this, BallVelocityMainListener.INSTANCE);
logger.info("已注册监听器 BallBungeeMainListener");
proxyServer.getEventManager().register(this, UpdatePlayerInfoListener.INSTANCE);
logger.info("已注册监听器 UpdatePlayerInfoListener");
if (BallAPI.getInstance().getBallConfig().isGameServerUpdatePlayerInfo()) {
BallAPI.getInstance().subscribePatterns("*" + BallAPI.PLAYER_INFO_CHANNEL);
} else {
BallAPI.getInstance().subscribeIgnorePrefix(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

@@ -0,0 +1,61 @@
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.io.File;
import java.io.IOException;
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 File configFile) throws IOException {
if (instance != null) {
return;
}
YamlConfig config = YamlConfig.load(configFile);
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

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

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

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

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

@@ -0,0 +1,66 @@
# 是否允许在控制台输出调试信息
debug: false
# 频道名前缀
# 使用这个配置选项可以划分子服消息通信分组
# 只有在同一个频道名的子服才能互相通信
channel-prefix: ""
# 是否在子服端更新玩家信息
# 默认情况下BC 统一管理玩家信息,包括记录 UUID 和玩家名称
# 如果一个群组服同时拥有多个 BC 入口
# 且每个 BC 入口为不同的玩家名称分配不同的 UUID
# (例如正版、盗版双入口,或网易多入口接同一个子服)
# 则可以启用该功能以防止 UUID 紊乱的问题
game-server-update-player-info: 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.jdbc.Driver"
# # 数据库链接填写格式:
# # jdbc:mysql://{数据库地址}:{数据库端口}/{使用的库名}?参数
# # 除非你知道自己在做什么,否则不建议随意更改参数
# url: "jdbc:mysql://localhost: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

View File

@@ -0,0 +1,6 @@
BUILD_ID: ${BUILD_ID}
BUILD_NUMBER: ${BUILD_NUMBER}
BUILD_DISPLAY_NAME: ${BUILD_DISPLAY_NAME}
JOB_URL: ${JOB_URL}
BUILD_URL: ${BUILD_URL}
GIT_COMMIT: ${GIT_COMMIT}

View File

@@ -0,0 +1,8 @@
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}";
}