16 Commits

39 changed files with 917 additions and 432 deletions

View File

@@ -27,12 +27,10 @@
为了适配 docker 环境,本插件除了从 `config.yml` 中配置服务器信息以外,还支持从环境变量中读取
| 环境变量 | 描述 | 对应 config 值 |
|:----------------------------|:-------------------|:-----------------|
| BALL_LOCAL_SERVER_IP | 本服务器 IP | server-info.host |
| BALL_LOCAL_SERVER_PORT | 本服务器端口 | server-info.port |
| BALL_LOCAL_SERVER_INFO_ID | 本服务器唯一识别码,最长 32 字符 | server-info.id |
| BALL_LOCAL_SERVER_INFO_NAME | 本服务端名称,用于展示给玩家看 | server-info.name |
| 环境变量 | 描述 | 对应 config 值 |
|:-----------------|:-------------------|:-----------------|
| BALL_SERVER_ID | 本服务器唯一识别码,最长 32 字符 | server-info.id |
| BALL_SERVER_NAME | 本服务端名称,用于展示给玩家看 | server-info.name |
# 开发
@@ -51,9 +49,9 @@ repositories {
dependencies {
// 对于 Bukkit 插件
compileOnly("cn.hamster3.mc.plugin:ball-bukkit:1.5.7")
compileOnly("cn.hamster3.mc.plugin:ball-bukkit:1.6.1")
// 对于 BungeeCord 插件
compileOnly("cn.hamster3.mc.plugin:ball-bungee:1.5.7")
compileOnly("cn.hamster3.mc.plugin:ball-bungee:1.6.1")
}
```
@@ -79,13 +77,13 @@ dependencies {
<dependency>
<groupId>cn.hamster3.mc.plugin</groupId>
<artifactId>ball-bukkit</artifactId>
<version>1.5.7</version>
<version>1.6.1</version>
</dependency>
<!--对于 BungeeCord 插件-->
<dependency>
<groupId>cn.hamster3.mc.plugin</groupId>
<artifactId>ball-bungee</artifactId>
<version>1.5.7</version>
<version>1.6.1</version>
</dependency>
</dependencies>
</project>

View File

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

View File

@@ -14,9 +14,13 @@ import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
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;
@SuppressWarnings("CallToPrintStackTrace")
public class HamsterBallPlugin extends JavaPlugin {
@Getter
private static HamsterBallPlugin instance;
@@ -39,10 +43,24 @@ public class HamsterBallPlugin extends JavaPlugin {
reloadConfig();
logger.info("已读取配置文件");
try {
BallBukkitAPI.init();
File dataFolder = getDataFolder();
if (dataFolder.mkdir()) {
logger.info("已生成插件存档文件夹");
}
File configFile = new File(dataFolder, "config.yml");
if (!configFile.exists()) {
Files.copy(
Objects.requireNonNull(getResource("config.yml")),
configFile.toPath(),
StandardCopyOption.REPLACE_EXISTING
);
}
BallBukkitAPI.init(configFile);
logger.info("已初始化 BallAPI");
} catch (Exception e) {
e.printStackTrace();
logger.log(Level.SEVERE, "BallAPI 初始化失败", e);
Bukkit.shutdown();
return;
}
long time = System.currentTimeMillis() - start;
logger.info("仓鼠球初始化完成,总计耗时 " + time + " ms");
@@ -56,9 +74,8 @@ public class HamsterBallPlugin extends JavaPlugin {
try {
BallBukkitAPI.getInstance().enable();
} catch (Exception e) {
getLogger().info("仓鼠球启动失败,原因:" + e.getMessage());
e.printStackTrace();
getLogger().info("由于仓鼠球启动失败,服务器将立即关闭");
logger.log(Level.SEVERE, "仓鼠球启动失败", e);
logger.info("由于仓鼠球启动失败,服务器将立即关闭");
Bukkit.shutdown();
return;
}
@@ -105,7 +122,7 @@ public class HamsterBallPlugin extends JavaPlugin {
try {
BallBukkitAPI.getInstance().disable();
} catch (Exception e) {
e.printStackTrace();
logger.log(Level.SEVERE, "关闭仓鼠球时遇到了一个异常", e);
}
long time = System.currentTimeMillis() - start;
logger.info("仓鼠球已关闭,总计耗时 " + time + " ms");

View File

@@ -1,73 +1,39 @@
package cn.hamster3.mc.plugin.ball.bukkit.api;
import cn.hamster3.mc.plugin.ball.bukkit.HamsterBallPlugin;
import cn.hamster3.mc.plugin.ball.bukkit.util.BallBukkitUtils;
import cn.hamster3.mc.plugin.ball.common.api.BallAPI;
import cn.hamster3.mc.plugin.ball.common.config.BallConfig;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerInfo;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerType;
import cn.hamster3.mc.plugin.core.common.api.CoreAPI;
import cn.hamster3.mc.plugin.core.common.config.ConfigSection;
import cn.hamster3.mc.plugin.core.common.config.YamlConfig;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration;
import org.jetbrains.annotations.NotNull;
import javax.sql.DataSource;
import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Map;
import java.util.logging.Logger;
public class BallBukkitAPI extends BallAPI {
public BallBukkitAPI(@NotNull BallConfig ballConfig) {
super(ballConfig);
public BallBukkitAPI(@NotNull ConfigSection config) {
super(config, BallServerType.GAME);
}
public static BallBukkitAPI getInstance() {
return (BallBukkitAPI) instance;
}
public static void init() {
public static void init(@NotNull File configFile) throws IOException {
if (instance != null) {
return;
}
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);
YamlConfig config = YamlConfig.load(configFile);
instance = new BallBukkitAPI(config);
}
@Override
public void enable() throws SQLException, InterruptedException {
instance.getLocalServerInfo().setHost(Bukkit.getIp().isEmpty() ? "127.0.0.1" : Bukkit.getIp());
instance.getLocalServerInfo().setPort(Bukkit.getPort());
super.enable();
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,9 +13,12 @@ import lombok.Getter;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.plugin.Plugin;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.logging.Level;
import java.util.logging.Logger;
@SuppressWarnings("CallToPrintStackTrace")
public class HamsterBallPlugin extends Plugin {
@Getter
private static HamsterBallPlugin instance;
@@ -27,11 +30,24 @@ public class HamsterBallPlugin extends Plugin {
logger.info("仓鼠球正在初始化");
instance = this;
try {
BallBungeeCordAPI.init();
File dataFolder = getDataFolder();
if (dataFolder.mkdir()) {
logger.info("已生成插件存档文件夹");
}
File configFile = new File(dataFolder, "config.yml");
if (!configFile.exists()) {
Files.copy(
getResourceAsStream("config.yml"),
configFile.toPath(),
StandardCopyOption.REPLACE_EXISTING
);
}
BallBungeeCordAPI.init(configFile);
logger.info("已初始化 BallAPI");
} catch (Exception e) {
e.printStackTrace();
ProxyServer.getInstance().stop("由于 HamsterBall 未能成功连接, 服务器将立即关闭");
logger.log(Level.SEVERE, "BallAPI 初始化失败", e);
ProxyServer.getInstance().stop("由于 HamsterBall 初始化失败, 服务器将立即关闭");
return;
}
long time = System.currentTimeMillis() - start;
logger.info("仓鼠球初始化完成,总计耗时 " + time + " ms");
@@ -45,7 +61,10 @@ public class HamsterBallPlugin extends Plugin {
try {
BallBungeeCordAPI.getInstance().enable();
} catch (Exception e) {
e.printStackTrace();
logger.log(Level.SEVERE, "仓鼠球启动失败", e);
logger.info("由于仓鼠球启动失败,服务器将立即关闭");
ProxyServer.getInstance().stop("仓鼠球启动失败");
return;
}
BallAPI.getInstance().getEventBus().register(BallBungeeListener.INSTANCE);
logger.info("已注册监听器 BallBungeeListener");
@@ -84,9 +103,7 @@ public class HamsterBallPlugin extends Plugin {
try {
BallBungeeCordAPI.getInstance().disable();
} catch (Exception e) {
getLogger().info("仓鼠球启动失败,原因:" + e.getMessage());
e.printStackTrace();
ProxyServer.getInstance().stop("由于仓鼠球启动失败,服务器将立即关闭");
logger.log(Level.SEVERE, "关闭仓鼠球时遇到了一个异常", e);
}
long time = System.currentTimeMillis() - start;
logger.info("仓鼠球已关闭,总计耗时 " + time + " ms");

View File

@@ -1,70 +1,47 @@
package cn.hamster3.mc.plugin.ball.bungee.api;
import cn.hamster3.mc.plugin.ball.bungee.HamsterBallPlugin;
import cn.hamster3.mc.plugin.ball.bungee.util.BallBungeeCordUtils;
import cn.hamster3.mc.plugin.ball.common.api.BallAPI;
import cn.hamster3.mc.plugin.ball.common.config.BallConfig;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerInfo;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerType;
import cn.hamster3.mc.plugin.core.bungee.util.CoreBungeeCordUtils;
import cn.hamster3.mc.plugin.core.common.api.CoreAPI;
import net.md_5.bungee.config.Configuration;
import cn.hamster3.mc.plugin.core.common.config.ConfigSection;
import cn.hamster3.mc.plugin.core.common.config.YamlConfig;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ListenerInfo;
import org.jetbrains.annotations.NotNull;
import javax.sql.DataSource;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.sql.SQLException;
import java.util.Map;
import java.util.logging.Logger;
public class BallBungeeCordAPI extends BallAPI {
public BallBungeeCordAPI(@NotNull BallConfig ballConfig) {
super(ballConfig);
public BallBungeeCordAPI(@NotNull ConfigSection config) {
super(config, BallServerType.PROXY);
}
public static BallBungeeCordAPI getInstance() {
return (BallBungeeCordAPI) instance;
}
public static void init() {
public static void init(@NotNull File configFile) throws IOException {
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 = 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);
YamlConfig config = YamlConfig.load(configFile);
instance = new BallBungeeCordAPI(config);
}
@Override
public void enable() throws SQLException, InterruptedException {
for (ListenerInfo listenerInfo : ProxyServer.getInstance().getConfig().getListeners()) {
if (!(listenerInfo.getSocketAddress() instanceof InetSocketAddress)) {
continue;
}
InetSocketAddress address = (InetSocketAddress) listenerInfo.getSocketAddress();
instance.getLocalServerInfo().setHost(address.getHostString());
instance.getLocalServerInfo().setPort(address.getPort());
}
super.enable();
}

View File

@@ -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.event.operate.*;
import cn.hamster3.mc.plugin.core.common.api.CoreAPI;
import cn.hamster3.mc.plugin.core.lib.net.kyori.adventure.audience.Audience;
import cn.hamster3.mc.plugin.core.lib.net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
import com.google.common.eventbus.Subscribe;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.config.ServerInfo;
@@ -55,6 +55,9 @@ public class BallBungeeListener {
@Subscribe
public void onKickPlayerEvent(KickPlayerEvent event) {
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(event.getUuid());
if (player == null) {
return;
}
BaseComponent[] components = BungeeComponentSerializer.get().serialize(event.getReason());
player.disconnect(components);
}
@@ -70,8 +73,8 @@ public class BallBungeeListener {
@Subscribe
public void onSendPlayerToLocationEvent(SendPlayerToLocationEvent event) {
String serverID = event.getLocation().getServerID();
ServerInfo serverInfo = ProxyServer.getInstance().getServerInfo(serverID);
if (serverInfo == null) {
ServerInfo toServer = ProxyServer.getInstance().getServerInfo(serverID);
if (toServer == null) {
HamsterBallPlugin.getInstance().getLogger().warning("试图传送玩家时失败: 服务器 " + serverID + " 不在线");
return;
}
@@ -83,12 +86,12 @@ public class BallBungeeListener {
if (player.getServer().getInfo().getName().equals(serverID)) {
continue;
}
player.connect(serverInfo);
player.connect(toServer);
}
}
@Subscribe
public void onSendPlayerToPlayerEvent(SendPlayerToPlayerEvent event) {
public void onSendPlayerToPlayer(SendPlayerToPlayerEvent event) {
UUID toPlayerUUID = event.getToPlayerUUID();
ProxiedPlayer toPlayer = ProxyServer.getInstance().getPlayer(toPlayerUUID);
if (toPlayer == null) {
@@ -98,6 +101,9 @@ public class BallBungeeListener {
ServerInfo toServer = toPlayer.getServer().getInfo();
for (UUID uuid : event.getSendPlayerUUID()) {
ProxiedPlayer sendPlayer = ProxyServer.getInstance().getPlayer(uuid);
if (sendPlayer == null) {
continue;
}
if (sendPlayer.getServer().getInfo().getName().equals(toServer.getName())) {
continue;
}

View File

@@ -5,20 +5,15 @@ import cn.hamster3.mc.plugin.ball.common.entity.BallPlayerInfo;
import cn.hamster3.mc.plugin.ball.common.event.BallActions;
import cn.hamster3.mc.plugin.ball.common.event.player.BallPlayerInfoUpdateEvent;
import cn.hamster3.mc.plugin.core.common.api.CoreAPI;
import cn.hamster3.mc.plugin.core.lib.com.zaxxer.hikari.HikariConfig;
import cn.hamster3.mc.plugin.core.lib.com.zaxxer.hikari.HikariDataSource;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.config.Configuration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.logging.Level;
@SuppressWarnings("CallToPrintStackTrace")
public final class BallBungeeCordUtils {
private BallBungeeCordUtils() {
}
@@ -49,7 +44,7 @@ public final class BallBungeeCordUtils {
statement.executeUpdate();
}
} catch (SQLException e) {
e.printStackTrace();
BallAPI.getInstance().getLogger().log(Level.SEVERE, "更新玩家数据时遇到了一个异常", e);
}
if (!BallAPI.getInstance().getBallConfig().isGameServerUpdatePlayerInfo()) {
BallAPI.getInstance().sendRawBallMessage(
@@ -60,32 +55,4 @@ 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

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

View File

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

View File

@@ -13,24 +13,30 @@ import cn.hamster3.mc.plugin.ball.common.event.server.ServerOfflineEvent;
import cn.hamster3.mc.plugin.ball.common.listener.BallCommonListener;
import cn.hamster3.mc.plugin.ball.common.listener.BallDebugListener;
import cn.hamster3.mc.plugin.ball.common.listener.BallRedisListener;
import cn.hamster3.mc.plugin.ball.common.thread.LockUpdateThread;
import cn.hamster3.mc.plugin.core.common.api.CoreAPI;
import cn.hamster3.mc.plugin.core.common.config.ConfigSection;
import cn.hamster3.mc.plugin.core.common.data.DisplayMessage;
import cn.hamster3.mc.plugin.core.lib.io.lettuce.core.pubsub.StatefulRedisPubSubConnection;
import cn.hamster3.mc.plugin.core.lib.net.kyori.adventure.text.Component;
import cn.hamster3.mc.plugin.core.common.util.CoreUtils;
import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.EventBus;
import lombok.Getter;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import redis.clients.jedis.Jedis;
import javax.sql.DataSource;
import java.sql.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
@SuppressWarnings("unused")
@Getter
@SuppressWarnings({"unused", "CallToPrintStackTrace"})
public abstract class BallAPI {
/**
* API 使用的通信频道
@@ -40,32 +46,53 @@ public abstract class BallAPI {
* API 使用的玩家信息更新通信频道
*/
public static final String PLAYER_INFO_CHANNEL = "HamsterBall:PlayerInfo";
/**
* API 实例
*/
@Getter
protected static BallAPI instance;
@NotNull
private final BallConfig ballConfig;
@NotNull
private final DataSource datasource;
@NotNull
private final BallServerInfo localServerInfo;
@NotNull
private final EventBus eventBus;
@NotNull
private final Map<String, BallServerInfo> allServerInfo;
@NotNull
private final Map<UUID, BallPlayerInfo> allPlayerInfo;
@NotNull
private final StatefulRedisPubSubConnection<String, BallMessage> redisPub;
@NotNull
private final StatefulRedisPubSubConnection<String, BallMessage> redisSub;
public BallAPI(@NotNull BallConfig ballConfig) {
this.ballConfig = ballConfig;
redisPub = CoreAPI.getInstance().getRedisClient().connectPubSub(BallMessage.REDIS_CODEC);
redisSub = CoreAPI.getInstance().getRedisClient().connectPubSub(BallMessage.REDIS_CODEC);
allServerInfo = new ConcurrentHashMap<>();
allPlayerInfo = new ConcurrentHashMap<>();
@NotNull
private final Jedis redisSub;
@Nullable
private ScheduledFuture<?> lockUpdater;
public BallAPI(@NotNull ConfigSection config, BallServerType type) {
ConfigSection serverInfoConfig = config.getSection("server-info");
if (serverInfoConfig == null) {
throw new IllegalArgumentException("配置文件中未找到 server-info 节点");
}
localServerInfo = new BallServerInfo(serverInfoConfig, type);
ConfigSection section = config.getSection("datasource");
if (section != null) {
getLogger().info("启用仓鼠球自定义数据库连接池");
datasource = CoreUtils.getDataSource(section);
} else {
getLogger().info("复用 HamsterCore 的数据库连接池");
datasource = CoreAPI.getInstance().getDataSource();
}
ballConfig = new BallConfig(config);
eventBus = new AsyncEventBus("HamsterBall - EventBus", CoreAPI.getInstance().getExecutorService());
eventBus.register(BallCommonListener.INSTANCE);
allServerInfo = new ConcurrentHashMap<>();
allPlayerInfo = new ConcurrentHashMap<>();
redisSub = CoreAPI.getInstance().getJedisPool().getResource();
getLogger().info("频道前缀: " + ballConfig.getChannelPrefix());
getLogger().info("启用子服更新玩家状态: " + ballConfig.isGameServerUpdatePlayerInfo());
if (ballConfig.isGameServerUpdatePlayerInfo()) {
@@ -78,8 +105,29 @@ public abstract class BallAPI {
}
protected void enable() throws SQLException, InterruptedException {
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 (Statement statement = connection.createStatement()) {
statement.execute("CREATE TABLE IF NOT EXISTS `hamster_ball_player_info`(" +
@@ -89,44 +137,11 @@ public abstract class BallAPI {
"`proxy_server` VARCHAR(32) NOT NULL," +
"`online` BOOLEAN NOT NULL" +
") CHARSET utf8mb4;");
statement.execute("CREATE TABLE IF NOT EXISTS `hamster_ball_server_info`(" +
"`id` VARCHAR(32) PRIMARY KEY NOT NULL," +
"`name` VARCHAR(32) NOT NULL," +
"`type` VARCHAR(16) NOT NULL," +
"`host` VARCHAR(32) NOT NULL," +
"`port` INT NOT NULL" +
") CHARSET utf8mb4;");
statement.execute("CREATE TABLE IF NOT EXISTS `hamster_ball_cached_message`(" +
"`uuid` CHAR(36) NOT NULL," +
"`message` TEXT NOT NULL" +
") CHARSET utf8mb4;");
}
try (PreparedStatement statement = connection.prepareStatement(
"REPLACE INTO `hamster_ball_server_info` VALUES(?, ?, ?, ?, ?);"
)) {
statement.setString(1, localInfo.getId());
statement.setString(2, localInfo.getName());
statement.setString(3, localInfo.getType().name());
statement.setString(4, localInfo.getHost());
statement.setInt(5, localInfo.getPort());
statement.executeUpdate();
}
try (PreparedStatement statement = connection.prepareStatement(
"SELECT * FROM `hamster_ball_server_info`;"
)) {
try (ResultSet set = statement.executeQuery()) {
while (set.next()) {
String serverID = set.getString("id");
allServerInfo.put(serverID, new BallServerInfo(
serverID,
set.getString("name"),
BallServerType.valueOf(set.getString("type")),
set.getString("host"),
set.getInt("port")
));
}
}
}
if (getBallConfig().isGameServerUpdatePlayerInfo()) {
try (Statement statement = connection.createStatement()) {
try (ResultSet set = statement.executeQuery(String.format(
@@ -164,7 +179,6 @@ public abstract class BallAPI {
}
getLogger().info("从数据库中加载了 " + allServerInfo.size() + " 条服务器信息");
getLogger().info("从数据库中加载了 " + allPlayerInfo.size() + " 条玩家信息");
redisPub.addListener(BallRedisListener.INSTANCE);
subscribeIgnorePrefix(BALL_CHANNEL);
}
@@ -172,14 +186,15 @@ public abstract class BallAPI {
sendBallMessage(BallAPI.BALL_CHANNEL, new BallMessage(
BallActions.ServerOffline.name(), new ServerOfflineEvent(getLocalServerInfo())
), false, true);
try (Connection connection = getDatasource().getConnection()) {
try (PreparedStatement statement = connection.prepareStatement(
"DELETE FROM `hamster_ball_server_info` WHERE `id`=?;"
)) {
statement.setString(1, getLocalServerId());
statement.executeUpdate();
if (lockUpdater != null) {
lockUpdater.cancel(true);
lockUpdater = null;
try (Jedis jedis = CoreAPI.getInstance().getJedisPool().getResource()) {
String key = "HamsterBall:ServerInfo:" + localServerInfo.getId();
jedis.del(key);
}
}
try (Connection connection = getDatasource().getConnection()) {
try (PreparedStatement statement = connection.prepareStatement(
"UPDATE `hamster_ball_player_info` SET `online`=false WHERE `game_server`=? OR `proxy_server`=?"
)) {
@@ -188,6 +203,7 @@ public abstract class BallAPI {
statement.executeUpdate();
}
}
redisSub.close();
}
/**
@@ -321,7 +337,7 @@ public abstract class BallAPI {
statement.executeUpdate();
}
} catch (Exception e) {
e.printStackTrace();
BallAPI.getInstance().getLogger().log(Level.SEVERE, "更新玩家缓存消息时遇到了一个异常", e);
}
}
}
@@ -470,13 +486,15 @@ public abstract class BallAPI {
channel = ballConfig.getChannelPrefix() + channel;
}
if (block) {
redisSub.sync().publish(channel, message);
try (Jedis jedis = CoreAPI.getInstance().getJedisPool().getResource()) {
jedis.publish(channel, CoreAPI.getInstance().getGson().toJson(message));
}
eventBus.post(new MessageSentEvent(channel, message));
} else {
@NotNull String finalChannel = channel;
redisSub.async().publish(channel, message).whenComplete((aLong, throwable) -> {
if (throwable != null) {
return;
CoreAPI.getInstance().getExecutorService().submit(() -> {
try (Jedis jedis = CoreAPI.getInstance().getJedisPool().getResource()) {
jedis.publish(finalChannel, CoreAPI.getInstance().getGson().toJson(message));
}
eventBus.post(new MessageSentEvent(finalChannel, message));
});
@@ -494,7 +512,9 @@ public abstract class BallAPI {
for (int i = 0; i < channel.length; i++) {
channel[i] = ballConfig.getChannelPrefix() + channel[i];
}
redisPub.sync().subscribe(channel);
CoreAPI.getInstance().getExecutorService().submit(
() -> redisSub.subscribe(BallRedisListener.INSTANCE, channel)
);
}
/**
@@ -503,7 +523,9 @@ public abstract class BallAPI {
* @param channel 频道名称
*/
public void subscribeIgnorePrefix(@NotNull String... channel) {
redisPub.sync().subscribe(channel);
CoreAPI.getInstance().getExecutorService().submit(
() -> redisSub.subscribe(BallRedisListener.INSTANCE, channel)
);
}
/**
@@ -512,11 +534,15 @@ public abstract class BallAPI {
* @param patterns 频道名称正则表达式
*/
public void subscribePatterns(@NotNull String patterns) {
redisPub.sync().psubscribe(patterns);
CoreAPI.getInstance().getExecutorService().submit(
() -> redisSub.psubscribe(BallRedisListener.INSTANCE, patterns)
);
}
/**
* 取消订阅 redis 频道
* 取消订阅 redis 消息频道
* <p>
* 会自动加上 config 中设置的频道前缀
*
* @param channel 频道名称
*/
@@ -524,16 +550,16 @@ public abstract class BallAPI {
for (int i = 0; i < channel.length; i++) {
channel[i] = ballConfig.getChannelPrefix() + channel[i];
}
redisPub.sync().unsubscribe(channel);
BallRedisListener.INSTANCE.unsubscribe(channel);
}
/**
* 忽略仓鼠球频道前缀配置,取消订阅 redis 频道
* 忽略频道前缀配置,取消订阅 redis 消息频道
*
* @param channel 频道名称
*/
public void unsubscribeIgnorePrefix(@NotNull String... channel) {
redisPub.sync().unsubscribe(channel);
BallRedisListener.INSTANCE.unsubscribe(channel);
}
/**
@@ -542,22 +568,12 @@ public abstract class BallAPI {
* @param patterns 频道名称正则表达式
*/
public void unsubscribePatterns(@NotNull String patterns) {
redisPub.sync().punsubscribe(patterns);
}
/**
* 获取本地服务器ID
*
* @return 服务器ID
*/
@NotNull
public BallServerInfo getLocalServerInfo() {
return ballConfig.getServerInfo();
BallRedisListener.INSTANCE.punsubscribe(patterns);
}
@NotNull
public String getLocalServerId() {
return ballConfig.getServerInfo().getId();
return localServerInfo.getId();
}
/**
@@ -674,9 +690,4 @@ public abstract class BallAPI {
@NotNull
public abstract Logger getLogger();
@NotNull
public DataSource getDatasource() {
return ballConfig.getDatasource() == null ? CoreAPI.getInstance().getDataSource() : ballConfig.getDatasource();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,27 +0,0 @@
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,7 +9,6 @@ import org.jetbrains.annotations.NotNull;
* 玩家已经进入子服
*
* @see BallPlayerPreConnectServerEvent 玩家准备进入子服
* @see BallPlayerConnectServerEvent 玩家进入子服
* @see BallPlayerPostConnectServerEvent 玩家已经进入子服
*/
@Data

View File

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

View File

@@ -83,11 +83,6 @@ public class BallCommonListener {
BallAPI.getInstance().getEventBus().post(event);
break;
}
case BallPlayerConnectServer: {
BallPlayerConnectServerEvent event = CoreAPI.getInstance().getGson().fromJson(message.getContent(), BallPlayerConnectServerEvent.class);
BallAPI.getInstance().getEventBus().post(event);
break;
}
case BallPlayerPostConnectServer: {
BallPlayerPostConnectServerEvent event = CoreAPI.getInstance().getGson().fromJson(message.getContent(), BallPlayerPostConnectServerEvent.class);
BallAPI.getInstance().getEventBus().post(event);
@@ -125,12 +120,6 @@ public class BallCommonListener {
BallAPI.getInstance().getEventBus().post(event);
}
@Subscribe
public void onBallPlayerConnectServer(BallPlayerConnectServerEvent event) {
BallPlayerInfo info = event.getPlayerInfo();
BallAPI.getInstance().getAllPlayerInfo().put(info.getUuid(), info);
}
@Subscribe
public void onBallPlayerInfoUpdate(BallPlayerInfoUpdateEvent event) {
BallPlayerInfo info = event.getPlayerInfo();

View File

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

View File

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

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

@@ -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 @@
version: ${version}
CHECK_TYPE: GITEA_RELEASES
GIT_BASE_URL: https://git.airgame.net
GIT_REPO: MiniDay/hamster-ball
GIT_TOKEN: a44a69a4d1b8601bf6091403247759cd28764d5e
DOWNLOAD_URL: https://jenkins.airgame.net/job/opensource/job/hamster-ball/

View File

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

View File

@@ -1,11 +1,11 @@
plugins {
id("java")
id("java-library")
id("maven-publish")
id("com.github.johnrengelman.shadow") version "8+"
}
group = "cn.hamster3.mc.plugin"
version = "1.5.7"
version = "1.6.1"
subprojects {
apply {
@@ -43,11 +43,10 @@ subprojects {
from(rootProject.file("LICENSE"))
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
jar {
archiveClassifier = "dev"
}
shadowJar {
archiveClassifier = "FIX"
processResources {
filesMatching("update.yml") {
expand(rootProject.properties)
}
}
build {
dependsOn(shadowJar)
@@ -62,7 +61,7 @@ subprojects {
}
repositories {
maven {
url = uri("https://maven.airgame.net/maven-airgame")
url = uri("https://maven.airgame.net/public")
credentials {
username = rootProject.properties.getOrDefault("maven_username", "").toString()

View File

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