From b8d35f3fce278cdf6678056092bbc307dcba5918 Mon Sep 17 00:00:00 2001 From: MiniDay <372403923@qq.com> Date: Sun, 20 Aug 2023 22:17:32 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8=20redission=20?= =?UTF-8?q?=E4=BD=9C=E4=B8=BA=E6=B6=88=E6=81=AF=E4=B8=AD=E9=97=B4=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ball-bukkit/build.gradle | 41 ---- ball-bukkit/build.gradle.kts | 30 +++ .../plugin/ball/bukkit/HamsterBallPlugin.java | 6 +- .../plugin/ball/bukkit/api/BallBukkitAPI.java | 80 ++++--- .../bukkit/listener/BallBukkitListener.java | 69 ++++-- ball-bukkit/src/main/resources/config.yml | 7 - ball-bungeecord/build.gradle | 40 ---- ball-bungeecord/build.gradle.kts | 30 +++ .../plugin/core/bungee/HamsterBallPlugin.java | 6 +- .../core/bungee/api/BallBungeeCordAPI.java | 81 ++++--- ball-common/build.gradle | 19 -- ball-common/build.gradle.kts | 25 +++ .../mc/plugin/ball/common/api/BallAPI.java | 199 ++---------------- .../common/codec/BallMessageInfoCodec.java | 41 ++++ .../common/connector/BallChannelHandler.java | 195 ----------------- .../connector/BallChannelInitializer.java | 28 --- .../connector/BallKeepAliveHandler.java | 18 -- .../ball/common/data/BallMessageInfo.java | 17 +- .../common/listener/BallDebugListener.java | 98 +-------- .../mc/plugin/ball/common/utils/OS.java | 89 -------- ball-common/src/main/resources/redission.yml | 36 ++++ ball-server/build.gradle | 61 ------ .../mc/plugin/ball/server/Bootstrap.java | 77 ------- .../ball/server/command/CommandHandler.java | 79 ------- .../ball/server/config/ServerConfig.java | 66 ------ .../connector/BallServerChannelHandler.java | 49 ----- .../BallServerChannelInitializer.java | 73 ------- .../connector/BallServerKeepAliveHandler.java | 22 -- .../ball/server/constant/ConstantObjects.java | 12 -- ball-server/src/main/resources/config.yml | 17 -- .../resources/log4j2.component.properties | 2 - ball-server/src/main/resources/log4j2.xml | 22 -- build.gradle | 85 -------- build.gradle.kts | 83 ++++++++ gradle.properties | 1 + gradle/wrapper/gradle-wrapper.jar | Bin 59821 -> 60756 bytes gradle/wrapper/gradle-wrapper.properties | 1 + settings.gradle | 6 - settings.gradle.kts | 4 + 39 files changed, 435 insertions(+), 1380 deletions(-) delete mode 100644 ball-bukkit/build.gradle create mode 100644 ball-bukkit/build.gradle.kts delete mode 100644 ball-bungeecord/build.gradle create mode 100644 ball-bungeecord/build.gradle.kts delete mode 100644 ball-common/build.gradle create mode 100644 ball-common/build.gradle.kts create mode 100644 ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/codec/BallMessageInfoCodec.java delete mode 100644 ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/connector/BallChannelHandler.java delete mode 100644 ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/connector/BallChannelInitializer.java delete mode 100644 ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/connector/BallKeepAliveHandler.java delete mode 100644 ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/utils/OS.java create mode 100644 ball-common/src/main/resources/redission.yml delete mode 100644 ball-server/build.gradle delete mode 100644 ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/Bootstrap.java delete mode 100644 ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/command/CommandHandler.java delete mode 100644 ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/config/ServerConfig.java delete mode 100644 ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/connector/BallServerChannelHandler.java delete mode 100644 ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/connector/BallServerChannelInitializer.java delete mode 100644 ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/connector/BallServerKeepAliveHandler.java delete mode 100644 ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/constant/ConstantObjects.java delete mode 100644 ball-server/src/main/resources/config.yml delete mode 100644 ball-server/src/main/resources/log4j2.component.properties delete mode 100644 ball-server/src/main/resources/log4j2.xml delete mode 100644 build.gradle create mode 100644 build.gradle.kts delete mode 100644 settings.gradle create mode 100644 settings.gradle.kts diff --git a/ball-bukkit/build.gradle b/ball-bukkit/build.gradle deleted file mode 100644 index eee95e2..0000000 --- a/ball-bukkit/build.gradle +++ /dev/null @@ -1,41 +0,0 @@ -setArchivesBaseName("HamsterBall-Bukkit") - -evaluationDependsOn(':ball-common') - -dependencies { - apiShade project(":ball-common") transitive false - - //noinspection VulnerableLibrariesLocal - compileOnly 'org.spigotmc:spigot-api:1.20.1-R0.1-SNAPSHOT' - - compileOnly "cn.hamster3.mc.plugin:core-bukkit:${hamster_core_version}" - compileOnly "me.clip:placeholderapi:2.11.2" transitive false -} - -processResources { - filesMatching("plugin.yml") { - expand(project.properties) - } - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) -} - -tasks.compileJava.dependsOn(":ball-common:build") -tasks.register("shadowJar", Jar) { - dependsOn("jar") - from([ - tasks.jar.outputs.files.collect { - it.isDirectory() ? it : zipTree(it) - }, - configurations.shade.collect { - it.isDirectory() ? it : zipTree(it) - }, - configurations.apiShade.collect { - it.isDirectory() ? it : zipTree(it) - }, - configurations.implementationShade.collect { - it.isDirectory() ? it : zipTree(it) - } - ]) - destinationDirectory = rootProject.buildDir -} -tasks.build.dependsOn(shadowJar) diff --git a/ball-bukkit/build.gradle.kts b/ball-bukkit/build.gradle.kts new file mode 100644 index 0000000..e86abbf --- /dev/null +++ b/ball-bukkit/build.gradle.kts @@ -0,0 +1,30 @@ +evaluationDependsOn(":ball-common") + +dependencies { + implementation(project(":ball-common")) { isTransitive = false } + + compileOnly("org.spigotmc:spigot-api:1.20.1-R0.1-SNAPSHOT") + + val hamsterCoreVersion = property("hamster_core_version") + compileOnly("cn.hamster3.mc.plugin:core-bukkit:${hamsterCoreVersion}") + compileOnly("me.clip:placeholderapi:2.11.2") { isTransitive = false } + + val redissionVersion = property("redission_version") + implementation("org.redisson:redisson:${redissionVersion}") { + exclude(group = "io.netty") + exclude(group = "org.yaml") + exclude(group = "org.slf4j") + } +} + + +tasks { + processResources { + filesMatching("plugin.yml") { + expand(project.properties) + } + } + withType() { + archiveBaseName = "HamsterBall-Bukkit" + } +} diff --git a/ball-bukkit/src/main/java/cn/hamster3/mc/plugin/ball/bukkit/HamsterBallPlugin.java b/ball-bukkit/src/main/java/cn/hamster3/mc/plugin/ball/bukkit/HamsterBallPlugin.java index 58435c4..1dbf8cd 100644 --- a/ball-bukkit/src/main/java/cn/hamster3/mc/plugin/ball/bukkit/HamsterBallPlugin.java +++ b/ball-bukkit/src/main/java/cn/hamster3/mc/plugin/ball/bukkit/HamsterBallPlugin.java @@ -30,9 +30,9 @@ public class HamsterBallPlugin extends JavaPlugin { public void onLoad() { instance = this; Logger logger = getLogger(); - BallBukkitAPI.init(); - logger.info("BallBukkitAPI 已初始化."); try { + BallBukkitAPI.init(); + logger.info("BallBukkitAPI 已初始化."); BallBukkitAPI.getInstance().enable(); logger.info("BallBukkitAPI 已启动."); } catch (Exception e) { @@ -43,7 +43,7 @@ public class HamsterBallPlugin extends JavaPlugin { @Override public void onEnable() { Logger logger = getLogger(); - if (!BallAPI.getInstance().isConnected()) { + if (!BallAPI.getInstance().isEnabled()) { sync(() -> { logger.info("由于 HamsterBall 未能成功连接, 服务器将立即关闭."); Bukkit.shutdown(); diff --git a/ball-bukkit/src/main/java/cn/hamster3/mc/plugin/ball/bukkit/api/BallBukkitAPI.java b/ball-bukkit/src/main/java/cn/hamster3/mc/plugin/ball/bukkit/api/BallBukkitAPI.java index e3a4e48..178f441 100644 --- a/ball-bukkit/src/main/java/cn/hamster3/mc/plugin/ball/bukkit/api/BallBukkitAPI.java +++ b/ball-bukkit/src/main/java/cn/hamster3/mc/plugin/ball/bukkit/api/BallBukkitAPI.java @@ -1,10 +1,10 @@ package cn.hamster3.mc.plugin.ball.bukkit.api; import cn.hamster3.mc.plugin.ball.bukkit.HamsterBallPlugin; -import cn.hamster3.mc.plugin.ball.bukkit.listener.BallBukkitListener; 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.codec.BallMessageInfoCodec; +import cn.hamster3.mc.plugin.ball.common.data.BallMessageInfo; import cn.hamster3.mc.plugin.ball.common.entity.BallServerInfo; import cn.hamster3.mc.plugin.ball.common.entity.BallServerType; import cn.hamster3.mc.plugin.ball.common.listener.BallDebugListener; @@ -12,63 +12,74 @@ import cn.hamster3.mc.plugin.core.common.api.CoreAPI; import org.bukkit.Bukkit; import org.bukkit.configuration.file.FileConfiguration; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.redisson.Redisson; +import org.redisson.api.RTopic; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; import javax.sql.DataSource; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.sql.SQLException; import java.util.Map; +import java.util.Objects; import java.util.logging.Logger; public class BallBukkitAPI extends BallAPI { - private DataSource datasource; + @Nullable + private final DataSource datasource; + @NotNull + private final RedissonClient redissonClient; - public BallBukkitAPI(@NotNull BallConfig config) { - super(config); + public BallBukkitAPI(@NotNull BallServerInfo localServerInfo, @Nullable DataSource datasource, @NotNull RedissonClient redissonClient) { + super(localServerInfo); + this.datasource = datasource; + this.redissonClient = redissonClient; } public static BallBukkitAPI getInstance() { return (BallBukkitAPI) instance; } - public static void init() { + public static void init() throws IOException { if (instance != null) { return; } HamsterBallPlugin plugin = HamsterBallPlugin.getInstance(); plugin.saveDefaultConfig(); + plugin.reloadConfig(); - FileConfiguration pluginConfig = plugin.getConfig(); + FileConfiguration config = plugin.getConfig(); Map env = System.getenv(); - String serverInfoID = env.getOrDefault("BALL_LOCAL_SERVER_INFO_ID", - pluginConfig.getString("server-info.id")); - String serverInfoName = env.getOrDefault("BALL_LOCAL_SERVER_INFO_NAME", - pluginConfig.getString("server-info.name")); - String serverInfoHost = env.getOrDefault("BALL_LOCAL_SERVER_IP", - pluginConfig.getString("server-info.host", Bukkit.getIp())); - int serverInfoPort = Integer.parseInt(env.getOrDefault("BALL_LOCAL_SERVER_PORT", - String.valueOf(pluginConfig.getInt("server-info.port", Bukkit.getPort())))); BallServerInfo serverInfo = new BallServerInfo( - serverInfoID, - serverInfoName, + 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, - serverInfoHost.isEmpty() ? "127.0.0.1" : serverInfoHost, - serverInfoPort + 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()))) + ) ); - String serverHost = env.getOrDefault("BALL_SERVER_HOST", - pluginConfig.getString("ball-server.host", "ball.hamster3.cn")); - int serverPort = Integer.parseInt(env.getOrDefault("BALL_SERVER_PORT", - String.valueOf(pluginConfig.getInt("ball-server.port", 58888)))); - int eventLoopThread = Integer.parseInt(env.getOrDefault("BALL_EVENT_LOOP_THREAD", - String.valueOf(pluginConfig.getInt("ball-server.event-loop-thread", 2)))); - BallConfig config = new BallConfig(serverInfo, serverHost, serverPort, eventLoopThread); + DataSource datasource = BallBukkitUtils.getDataSource(config.getConfigurationSection("datasource")); + File redissionConfig = new File(plugin.getDataFolder(), "redission.yml"); + if (!redissionConfig.exists()) { + Files.copy( + Objects.requireNonNull(plugin.getResource("redission.yml")), + redissionConfig.toPath(), + StandardCopyOption.REPLACE_EXISTING + ); + } + RedissonClient redissonClient = Redisson.create(Config.fromYAML(redissionConfig)); + BallBukkitAPI apiInstance = new BallBukkitAPI(serverInfo, datasource, redissonClient); - BallBukkitAPI apiInstance = new BallBukkitAPI(config); - apiInstance.datasource = BallBukkitUtils.getDataSource(pluginConfig.getConfigurationSection("datasource")); - - apiInstance.addListener(BallBukkitListener.INSTANCE); - if (pluginConfig.getBoolean("debug", false)) { - apiInstance.addListener(BallDebugListener.INSTANCE); + RTopic topic = redissonClient.getTopic(BALL_CHANNEL, BallMessageInfoCodec.INSTANCE); + if (config.getBoolean("debug", false)) { + topic.addListener(BallMessageInfo.class, BallDebugListener.INSTANCE); } instance = apiInstance; @@ -93,4 +104,9 @@ public class BallBukkitAPI extends BallAPI { public @NotNull DataSource getDatasource() { return datasource == null ? CoreAPI.getInstance().getDataSource() : datasource; } + + @Override + public @NotNull RedissonClient getRedissonClient() { + return redissonClient; + } } diff --git a/ball-bukkit/src/main/java/cn/hamster3/mc/plugin/ball/bukkit/listener/BallBukkitListener.java b/ball-bukkit/src/main/java/cn/hamster3/mc/plugin/ball/bukkit/listener/BallBukkitListener.java index a54b835..6d83930 100644 --- a/ball-bukkit/src/main/java/cn/hamster3/mc/plugin/ball/bukkit/listener/BallBukkitListener.java +++ b/ball-bukkit/src/main/java/cn/hamster3/mc/plugin/ball/bukkit/listener/BallBukkitListener.java @@ -11,7 +11,6 @@ import cn.hamster3.mc.plugin.core.bukkit.api.CoreBukkitAPI; import cn.hamster3.mc.plugin.core.common.api.CoreAPI; import cn.hamster3.mc.plugin.core.common.data.DisplayMessage; import cn.hamster3.mc.plugin.core.common.util.CoreUtils; -import cn.hamster3.mc.plugin.core.common.util.Pair; 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 lombok.AllArgsConstructor; @@ -26,7 +25,11 @@ import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerTeleportEvent; import org.jetbrains.annotations.NotNull; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.UUID; @@ -34,8 +37,8 @@ import java.util.UUID; public class BallBukkitListener implements Listener, BallListener { public static final BallBukkitListener INSTANCE = new BallBukkitListener(); - private final HashMap> playerToLocation = new HashMap<>(); - private final HashMap playerToPlayer = new HashMap<>(); + private final HashMap playerToLocation = new HashMap<>(); + private final HashMap playerToPlayer = new HashMap<>(); private BallBukkitListener() { } @@ -99,7 +102,7 @@ public class BallBukkitListener implements Listener, BallListener { } }); } else { - playerToLocation.put(uuid, new Pair<>(location.toBukkitLocation(), event.getDoneMessage())); + playerToLocation.put(uuid, new ToLocation(location.toBukkitLocation(), event.getDoneMessage())); } } } @@ -133,7 +136,7 @@ public class BallBukkitListener implements Listener, BallListener { } }); } else { - ToPlayerData data = new ToPlayerData( + ToPlayer data = new ToPlayer( toPlayer.getUniqueId(), toPlayer.getLocation(), event.getDoneMessage(), @@ -151,15 +154,16 @@ public class BallBukkitListener implements Listener, BallListener { public void onPlayerJoin(PlayerJoinEvent event) { Player player = event.getPlayer(); UUID uuid = player.getUniqueId(); - Pair pair = playerToLocation.remove(uuid); - if (pair != null) { - player.teleport(pair.getKey(), PlayerTeleportEvent.TeleportCause.PLUGIN); - if (pair.getValue() != null) { + ToLocation toLocationData = playerToLocation.remove(uuid); + if (toLocationData != null) { + player.teleport(toLocationData.getLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN); + DisplayMessage doneMessage = toLocationData.getDoneMessage(); + if (doneMessage != null) { Audience audience = CoreBukkitAPI.getInstance().getAudienceProvider().player(player); - pair.getValue().show(audience); + doneMessage.show(audience); } } - ToPlayerData toPlayerData = playerToPlayer.remove(uuid); + ToPlayer toPlayerData = playerToPlayer.remove(uuid); if (toPlayerData != null) { player.teleport(toPlayerData.location); if (toPlayerData.doneMessage != null) { @@ -179,21 +183,58 @@ public class BallBukkitListener implements Listener, BallListener { } HamsterBallPlugin.async(() -> { try { - List cachedPlayerMessage = BallAPI.getInstance().getCachedPlayerMessage(uuid); + List cachedPlayerMessage = getCachedPlayerMessage(uuid); Audience audience = CoreAPI.getInstance().getAudienceProvider().player(uuid); for (DisplayMessage message : cachedPlayerMessage) { message.show(audience); } - BallAPI.getInstance().removeCachedPlayerMessage(uuid); + removeCachedPlayerMessage(uuid); } catch (SQLException e) { e.printStackTrace(); } }); } + @NotNull + private List getCachedPlayerMessage(@NotNull UUID uuid) throws SQLException { + ArrayList list = new ArrayList<>(); + try (Connection connection = BallAPI.getInstance().getDatasource().getConnection()) { + try (PreparedStatement statement = connection.prepareStatement( + "SELECT message FROM `hamster_ball_cached_message` WHERE `uuid`=?;" + )) { + statement.setString(1, uuid.toString()); + try (ResultSet set = statement.executeQuery()) { + while (set.next()) { + DisplayMessage msg = CoreUtils.GSON.fromJson(set.getString("message"), DisplayMessage.class); + list.add(msg); + } + } + } + } + return list; + } + + private void removeCachedPlayerMessage(@NotNull UUID uuid) throws SQLException { + try (Connection connection = BallAPI.getInstance().getDatasource().getConnection()) { + try (PreparedStatement statement = connection.prepareStatement( + "DELETE FROM `hamster_ball_cached_message` WHERE `uuid`=?;" + )) { + statement.setString(1, uuid.toString()); + statement.executeUpdate(); + } + } + } + @Data @AllArgsConstructor - private static class ToPlayerData { + private static class ToLocation { + private Location location; + private DisplayMessage doneMessage; + } + + @Data + @AllArgsConstructor + private static class ToPlayer { private UUID toPlayerUUID; private Location location; private DisplayMessage doneMessage; diff --git a/ball-bukkit/src/main/resources/config.yml b/ball-bukkit/src/main/resources/config.yml index 67ab3a5..c47b527 100644 --- a/ball-bukkit/src/main/resources/config.yml +++ b/ball-bukkit/src/main/resources/config.yml @@ -1,11 +1,6 @@ # 是否允许在控制台输出调试信息 debug: false -ball-server: - host: "ball.hamster3.cn" - port: 58888 - event-loop-thread: 2 - server-info: # 服务器唯一识别码,最长 32 字符 # 推荐格式:全小写英文+横杠+数字尾号 @@ -19,8 +14,6 @@ server-info: # 当前子服的内网地址 # 不填则自动获取 server.properties 文件中的设置 # 若都为空,则自动设定为 127.0.0.1 - # BC 端的插件会自动将这个服务器的地址和端口加入到列表 - # 因此只需在这里配置好,以后新增子服时无需每次都手动更改 BC 的配置文件 # host: "127.0.0.1" # 当前子服的内网端口 # 不填则自动获取 server.properties 文件中的设置 diff --git a/ball-bungeecord/build.gradle b/ball-bungeecord/build.gradle deleted file mode 100644 index f76d525..0000000 --- a/ball-bungeecord/build.gradle +++ /dev/null @@ -1,40 +0,0 @@ -setArchivesBaseName("HamsterBall-BungeeCord") - -evaluationDependsOn(':ball-common') - -dependencies { - apiShade project(":ball-common") transitive false - - //noinspection VulnerableLibrariesLocal - compileOnly 'net.md-5:bungeecord-api:1.20-R0.1-SNAPSHOT' exclude group: 'io.netty' - - compileOnly "cn.hamster3.mc.plugin:core-bungeecord:${hamster_core_version}" -} - -processResources { - filesMatching("bungee.yml") { - expand(project.properties) - } - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) -} - -tasks.compileJava.dependsOn(":ball-common:build") -tasks.register("shadowJar", Jar) { - dependsOn("jar") - from([ - tasks.jar.outputs.files.collect { - it.isDirectory() ? it : zipTree(it) - }, - configurations.shade.collect { - it.isDirectory() ? it : zipTree(it) - }, - configurations.apiShade.collect { - it.isDirectory() ? it : zipTree(it) - }, - configurations.implementationShade.collect { - it.isDirectory() ? it : zipTree(it) - } - ]) - destinationDirectory = rootProject.buildDir -} -tasks.build.dependsOn(shadowJar) diff --git a/ball-bungeecord/build.gradle.kts b/ball-bungeecord/build.gradle.kts new file mode 100644 index 0000000..ef774a6 --- /dev/null +++ b/ball-bungeecord/build.gradle.kts @@ -0,0 +1,30 @@ +evaluationDependsOn(":ball-common") + +dependencies { + implementation(project(":ball-common")) { isTransitive = false } + + compileOnly("net.md-5:bungeecord-api:1.20-R0.1-SNAPSHOT") + + val hamsterCoreVersion = property("hamster_core_version") + compileOnly("cn.hamster3.mc.plugin:core-bungeecord:${hamsterCoreVersion}") + compileOnly("me.clip:placeholderapi:2.11.2") { isTransitive = false } + + val redissionVersion = property("redission_version") + implementation("org.redisson:redisson:${redissionVersion}") { + exclude(group = "io.netty") + exclude(group = "org.yaml") + exclude(group = "org.slf4j") + } +} + + +tasks { + processResources { + filesMatching("bungee.yml") { + expand(project.properties) + } + } + withType() { + archiveBaseName = "HamsterBall-BungeeCord" + } +} diff --git a/ball-bungeecord/src/main/java/cn/hamster3/mc/plugin/core/bungee/HamsterBallPlugin.java b/ball-bungeecord/src/main/java/cn/hamster3/mc/plugin/core/bungee/HamsterBallPlugin.java index 327eef5..cf9dcf0 100644 --- a/ball-bungeecord/src/main/java/cn/hamster3/mc/plugin/core/bungee/HamsterBallPlugin.java +++ b/ball-bungeecord/src/main/java/cn/hamster3/mc/plugin/core/bungee/HamsterBallPlugin.java @@ -22,9 +22,9 @@ public class HamsterBallPlugin extends Plugin { public void onLoad() { instance = this; Logger logger = getLogger(); - BallBungeeCordAPI.init(); - logger.info("BallBungeeCordAPI 已初始化."); try { + BallBungeeCordAPI.init(); + logger.info("BallBungeeCordAPI 已初始化."); BallBungeeCordAPI.getInstance().enable(); logger.info("BallBungeeCordAPI 已启动."); } catch (Exception e) { @@ -36,7 +36,7 @@ public class HamsterBallPlugin extends Plugin { @Override public void onEnable() { Logger logger = getLogger(); - if (!BallAPI.getInstance().isConnected()) { + if (!BallAPI.getInstance().isEnabled()) { ProxyServer.getInstance().stop("由于 HamsterBall 未能成功连接, 服务器将立即关闭."); return; } diff --git a/ball-bungeecord/src/main/java/cn/hamster3/mc/plugin/core/bungee/api/BallBungeeCordAPI.java b/ball-bungeecord/src/main/java/cn/hamster3/mc/plugin/core/bungee/api/BallBungeeCordAPI.java index c0657e2..5922ed5 100644 --- a/ball-bungeecord/src/main/java/cn/hamster3/mc/plugin/core/bungee/api/BallBungeeCordAPI.java +++ b/ball-bungeecord/src/main/java/cn/hamster3/mc/plugin/core/bungee/api/BallBungeeCordAPI.java @@ -1,73 +1,83 @@ package cn.hamster3.mc.plugin.core.bungee.api; 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.codec.BallMessageInfoCodec; +import cn.hamster3.mc.plugin.ball.common.data.BallMessageInfo; import cn.hamster3.mc.plugin.ball.common.entity.BallServerInfo; import cn.hamster3.mc.plugin.ball.common.entity.BallServerType; import cn.hamster3.mc.plugin.ball.common.listener.BallDebugListener; import cn.hamster3.mc.plugin.core.bungee.HamsterBallPlugin; -import cn.hamster3.mc.plugin.core.bungee.listener.BallBungeeCordListener; 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 net.md_5.bungee.config.Configuration; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.redisson.Redisson; +import org.redisson.api.RTopic; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; import javax.sql.DataSource; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.sql.SQLException; import java.util.Map; import java.util.logging.Logger; public class BallBungeeCordAPI extends BallAPI { - private DataSource datasource; + @Nullable + private final DataSource datasource; + @NotNull + private final RedissonClient redissonClient; - public BallBungeeCordAPI(@NotNull BallConfig config) { - super(config); + public BallBungeeCordAPI(@NotNull BallServerInfo localServerInfo, @Nullable DataSource datasource, @NotNull RedissonClient redissonClient) { + super(localServerInfo); + this.datasource = datasource; + this.redissonClient = redissonClient; } public static BallBungeeCordAPI getInstance() { return (BallBungeeCordAPI) instance; } - public static void init() { + public static void init() throws IOException { if (instance != null) { return; } HamsterBallPlugin plugin = HamsterBallPlugin.getInstance(); - Configuration pluginConfig = CoreBungeeCordUtils.getPluginConfig(plugin); + Configuration config = CoreBungeeCordUtils.getPluginConfig(plugin); Map env = System.getenv(); - String serverInfoID = env.getOrDefault("BALL_LOCAL_SERVER_INFO_ID", - pluginConfig.getString("server-info.id")); - String serverInfoName = env.getOrDefault("BALL_LOCAL_SERVER_INFO_NAME", - pluginConfig.getString("server-info.name")); - String serverInfoHost = env.getOrDefault("BALL_LOCAL_SERVER_IP", - pluginConfig.getString("server-info.host", "0.0.0.0")); - int serverInfoPort = Integer.parseInt(env.getOrDefault("BALL_LOCAL_SERVER_PORT", - String.valueOf(pluginConfig.getInt("server-info.port", 25577)))); BallServerInfo serverInfo = new BallServerInfo( - serverInfoID, - serverInfoName, + 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, - serverInfoHost, - serverInfoPort + 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")) + ) ); - String serverHost = env.getOrDefault("BALL_SERVER_HOST", - pluginConfig.getString("ball-server.host", "ball.hamster3.cn")); - int serverPort = Integer.parseInt(env.getOrDefault("BALL_SERVER_PORT", - String.valueOf(pluginConfig.getInt("ball-server.port", 58888)))); - int eventLoopThread = Integer.parseInt(env.getOrDefault("BALL_EVENT_LOOP_THREAD", - String.valueOf(pluginConfig.getInt("ball-server.event-loop-thread", 2)))); - BallConfig config = new BallConfig(serverInfo, serverHost, serverPort, eventLoopThread); - - BallBungeeCordAPI apiInstance = new BallBungeeCordAPI(config); - apiInstance.datasource = BallBungeeCordUtils.getDataSource(pluginConfig.getSection("datasource")); - - apiInstance.addListener(BallBungeeCordListener.INSTANCE); - if (pluginConfig.getBoolean("debug", false)) { - apiInstance.addListener(BallDebugListener.INSTANCE); + DataSource datasource = BallBungeeCordUtils.getDataSource(config.getSection("datasource")); + File redissionConfig = new File(plugin.getDataFolder(), "redission.yml"); + if (!redissionConfig.exists()) { + Files.copy( + plugin.getResourceAsStream("redission.yml"), + redissionConfig.toPath(), + StandardCopyOption.REPLACE_EXISTING + ); } + RedissonClient redissonClient = Redisson.create(Config.fromYAML(redissionConfig)); + BallBungeeCordAPI apiInstance = new BallBungeeCordAPI(serverInfo, datasource, redissonClient); + + if (config.getBoolean("debug", false)) { + RTopic topic = redissonClient.getTopic(BALL_CHANNEL, BallMessageInfoCodec.INSTANCE); + topic.addListener(BallMessageInfo.class, BallDebugListener.INSTANCE); + } + instance = apiInstance; } @@ -90,4 +100,9 @@ public class BallBungeeCordAPI extends BallAPI { public @NotNull DataSource getDatasource() { return datasource == null ? CoreAPI.getInstance().getDataSource() : datasource; } + + @Override + public @NotNull RedissonClient getRedissonClient() { + return redissonClient; + } } diff --git a/ball-common/build.gradle b/ball-common/build.gradle deleted file mode 100644 index 31e8104..0000000 --- a/ball-common/build.gradle +++ /dev/null @@ -1,19 +0,0 @@ -setArchivesBaseName("HamsterBall-Common") - -dependencies { - // https://mvnrepository.com/artifact/com.google.code.gson/gson - //noinspection GradlePackageUpdate - //noinspection VulnerableLibrariesLocal - compileOnly 'com.google.code.gson:gson:2.8.0' - // https://mvnrepository.com/artifact/io.netty/netty-all - compileOnly 'io.netty:netty-all:4.1.86.Final' - - compileOnly "cn.hamster3.mc.plugin:core-common:${hamster_core_version}" - - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.0' -} - -test { - useJUnitPlatform() -} diff --git a/ball-common/build.gradle.kts b/ball-common/build.gradle.kts new file mode 100644 index 0000000..186cf21 --- /dev/null +++ b/ball-common/build.gradle.kts @@ -0,0 +1,25 @@ +@file:Suppress("VulnerableLibrariesLocal") + +dependencies { + // https://mvnrepository.com/artifact/com.google.code.gson/gson + compileOnly("com.google.code.gson:gson:2.8.0") + // https://mvnrepository.com/artifact/io.netty/netty-all + compileOnly("io.netty:netty-all:4.1.86.Final") + + val hamsterCoreVersion = property("hamster_core_version") + compileOnly("cn.hamster3.mc.plugin:core-common:${hamsterCoreVersion}") + + val redissionVersion = property("redission_version") + implementation("org.redisson:redisson:${redissionVersion}") { + isTransitive = false + exclude(group = "io.netty") + exclude(group = "org.yaml") + exclude(group = "org.slf4j") + } +} + +tasks { + withType() { + archiveBaseName = "HamsterBall-Common" + } +} diff --git a/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/api/BallAPI.java b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/api/BallAPI.java index 55b92ec..5f5181d 100644 --- a/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/api/BallAPI.java +++ b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/api/BallAPI.java @@ -1,7 +1,5 @@ package cn.hamster3.mc.plugin.ball.common.api; -import cn.hamster3.mc.plugin.ball.common.config.BallConfig; -import cn.hamster3.mc.plugin.ball.common.connector.BallChannelInitializer; import cn.hamster3.mc.plugin.ball.common.data.BallLocation; import cn.hamster3.mc.plugin.ball.common.data.BallMessageInfo; import cn.hamster3.mc.plugin.ball.common.entity.BallPlayerInfo; @@ -14,19 +12,15 @@ import cn.hamster3.mc.plugin.ball.common.event.server.ServerOfflineEvent; import cn.hamster3.mc.plugin.ball.common.event.server.ServerOnlineEvent; import cn.hamster3.mc.plugin.ball.common.listener.BallListener; import cn.hamster3.mc.plugin.ball.common.listener.ListenerPriority; -import cn.hamster3.mc.plugin.ball.common.utils.OS; import cn.hamster3.mc.plugin.core.common.data.DisplayMessage; import cn.hamster3.mc.plugin.core.common.util.CoreUtils; import cn.hamster3.mc.plugin.core.lib.net.kyori.adventure.text.Component; -import com.google.gson.JsonElement; -import com.google.gson.JsonPrimitive; -import io.netty.bootstrap.Bootstrap; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelOption; -import io.netty.channel.EventLoopGroup; +import lombok.Getter; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.redisson.api.RTopic; +import org.redisson.api.RedissonClient; +import org.redisson.client.codec.StringCodec; import javax.sql.DataSource; import java.sql.*; @@ -48,44 +42,21 @@ public abstract class BallAPI { protected final Map serverInfo; @NotNull protected final Map playerInfo; - @NotNull - private final BallConfig config; - - @NotNull - private final Bootstrap bootstrap; - @NotNull - private final EventLoopGroup executors; - protected Channel channel; - - private boolean enabled; - private boolean connected; - + private final BallServerInfo localServerInfo; @NotNull private List listeners; - protected BallAPI(@NotNull BallConfig config) { - this.config = config; - this.enabled = false; - this.connected = false; + @Getter + private boolean enabled; + + public BallAPI(@NotNull BallServerInfo localServerInfo) { + this.localServerInfo = localServerInfo; serverInfo = new ConcurrentHashMap<>(); playerInfo = new ConcurrentHashMap<>(); listeners = new ArrayList<>(); - OS currentOS = OS.getCurrentOS(); - getLogger().info(String.format( - "当前操作系统为: %s. 选择 IO 模式为: %s", - currentOS.name(), currentOS.getIOModeName() - )); - - executors = currentOS.getEventLoopGroup(config.getEventLoopThread()); - bootstrap = new Bootstrap() - .group(executors) - .channel(currentOS.getSocketChannel()) - .option(ChannelOption.TCP_NODELAY, true) - .handler(new BallChannelInitializer()); - addListener(new BallListener() { @Override public ListenerPriority getPriority() { @@ -158,11 +129,10 @@ public abstract class BallAPI { @Override public void onConnectRefused() { enabled = false; - connected = false; - executors.shutdownGracefully(); getLogger().info("连接至服务中心的请求被拒绝,已关闭仓鼠球。"); } }); + enabled = false; } /** @@ -178,11 +148,8 @@ public abstract class BallAPI { if (enabled) { return; } - enabled = true; BallServerInfo localInfo = getLocalServerInfo(); - connect(); - try (Connection connection = BallAPI.getInstance().getDatasource().getConnection()) { try (Statement statement = connection.createStatement()) { statement.execute("CREATE TABLE IF NOT EXISTS `hamster_ball_player_info`(" + @@ -246,68 +213,10 @@ public abstract class BallAPI { } } } - } - - protected void connect() throws InterruptedException { - if (!enabled) { - getLogger().info("仓鼠球已关闭,拒绝启动连接!"); - return; - } - getLogger().info("准备连接至仓鼠球服务中心!"); - ChannelFuture future = bootstrap.connect(config.getHost(), config.getPort()).await(); - if (future.isSuccess()) { - channel = future.channel(); - connected = true; - for (BallListener listener : listeners) { - listener.onConnectActive(); - } - getLogger().info("已连接至仓鼠球服务中心!"); - } else { - getLogger().warning("连接至仓鼠球服务中心失败!"); - } - } - - public void reconnect(int tryCount) { - if (!enabled) { - return; - } - if (channel != null && channel.isOpen() && channel.isRegistered() && channel.isActive() && channel.isWritable()) { - return; - } - channel = null; - connected = false; - if (tryCount <= 0) { - for (BallListener listener : getListeners()) { - try { - listener.onServiceDead(); - } catch (Exception e) { - e.printStackTrace(); - } - } - return; - } - try { - connect(); - } catch (Exception e) { - e.printStackTrace(); - } - if (channel != null) { - return; - } - try { - Thread.sleep(3000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - reconnect(tryCount - 1); + enabled = true; } protected void disable() throws SQLException, InterruptedException { - if (!enabled) { - return; - } - enabled = false; - sendBallMessage(new BallMessageInfo(BALL_CHANNEL, ServerOfflineEvent.ACTION, new ServerOfflineEvent(getLocalServerId())), true); try (Connection connection = BallAPI.getInstance().getDatasource().getConnection()) { @@ -325,10 +234,7 @@ public abstract class BallAPI { statement.executeUpdate(); } } - - channel = null; - connected = false; - executors.shutdownGracefully().await(); + getRedissonClient().shutdown(); } /** @@ -608,28 +514,6 @@ public abstract class BallAPI { sendBallMessage(new BallMessageInfo(channel, action)); } - /** - * 发送一条有附加参数的服务消息 - * - * @param channel 消息标签 - * @param action 执行动作 - * @param content 附加参数 - */ - public void sendBallMessage(@NotNull String channel, @NotNull String action, @NotNull String content) { - sendBallMessage(new BallMessageInfo(channel, action, new JsonPrimitive(content))); - } - - /** - * 发送一条有附加参数的消息 - * - * @param channel 消息频道 - * @param action 执行动作 - * @param content 附加参数 - */ - public void sendBallMessage(@NotNull String channel, @NotNull String action, @NotNull JsonElement content) { - sendBallMessage(new BallMessageInfo(channel, action, content)); - } - /** * 发送一条有附加参数的服务消息 * @@ -657,17 +541,12 @@ public abstract class BallAPI { * @param block 是否阻塞(设置为 true 则必须等待消息写入网络的操作完成后,该方法才会退出) */ public void sendBallMessage(@NotNull BallMessageInfo messageInfo, boolean block) { - if (channel == null || !channel.isWritable()) { - getLogger().warning("由于服务不可用,有一条消息发送失败了: " + messageInfo); - return; - } - ChannelFuture future = channel.writeAndFlush(CoreUtils.GSON.toJson(messageInfo)); + String string = CoreUtils.GSON.toJson(messageInfo); + RTopic topic = getRedissonClient().getTopic(BallAPI.BALL_CHANNEL, StringCodec.INSTANCE); if (block) { - try { - future.await(); - } catch (InterruptedException e) { - e.printStackTrace(); - } + topic.publish(string); + } else { + topic.publishAsync(string); } for (BallListener listener : BallAPI.getInstance().getListeners()) { try { @@ -678,36 +557,6 @@ public abstract class BallAPI { } } - @NotNull - public List getCachedPlayerMessage(@NotNull UUID uuid) throws SQLException { - ArrayList list = new ArrayList<>(); - try (Connection connection = BallAPI.getInstance().getDatasource().getConnection()) { - try (PreparedStatement statement = connection.prepareStatement( - "SELECT message FROM `hamster_ball_cached_message` WHERE `uuid`=?;" - )) { - statement.setString(1, uuid.toString()); - try (ResultSet set = statement.executeQuery()) { - while (set.next()) { - DisplayMessage msg = CoreUtils.GSON.fromJson(set.getString("message"), DisplayMessage.class); - list.add(msg); - } - } - } - } - return list; - } - - public void removeCachedPlayerMessage(@NotNull UUID uuid) throws SQLException { - try (Connection connection = BallAPI.getInstance().getDatasource().getConnection()) { - try (PreparedStatement statement = connection.prepareStatement( - "DELETE FROM `hamster_ball_cached_message` WHERE `uuid`=?;" - )) { - statement.setString(1, uuid.toString()); - statement.executeUpdate(); - } - } - } - public void addListener(@NotNull BallListener listener) { ArrayList newListeners = new ArrayList<>(listeners); newListeners.add(listener); @@ -728,12 +577,12 @@ public abstract class BallAPI { */ @NotNull public BallServerInfo getLocalServerInfo() { - return config.getLocalInfo(); + return localServerInfo; } @NotNull public String getLocalServerId() { - return config.getLocalInfo().getId(); + return localServerInfo.getId(); } /** @@ -848,11 +697,6 @@ public abstract class BallAPI { return info.getName(); } - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - public boolean isConnected() { - return connected; - } - @NotNull public Map getAllServerInfo() { return serverInfo; @@ -873,4 +717,7 @@ public abstract class BallAPI { @NotNull public abstract DataSource getDatasource(); + + @NotNull + public abstract RedissonClient getRedissonClient(); } diff --git a/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/codec/BallMessageInfoCodec.java b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/codec/BallMessageInfoCodec.java new file mode 100644 index 0000000..4a2a28b --- /dev/null +++ b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/codec/BallMessageInfoCodec.java @@ -0,0 +1,41 @@ +package cn.hamster3.mc.plugin.ball.common.codec; + +import cn.hamster3.mc.plugin.ball.common.data.BallMessageInfo; +import cn.hamster3.mc.plugin.core.common.util.CoreUtils; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.util.CharsetUtil; +import org.redisson.client.codec.BaseCodec; +import org.redisson.client.protocol.Decoder; +import org.redisson.client.protocol.Encoder; +import org.redisson.codec.JsonCodec; + +public class BallMessageInfoCodec extends BaseCodec implements JsonCodec { + public static final BallMessageInfoCodec INSTANCE = new BallMessageInfoCodec(); + + private final Encoder encoder = in -> { + ByteBuf out = ByteBufAllocator.DEFAULT.buffer(); + out.writeCharSequence(in.toString(), CharsetUtil.UTF_8); + return out; + }; + + private final Decoder decoder = (buf, state) -> { + String str = buf.toString(CharsetUtil.UTF_8); + buf.readerIndex(buf.readableBytes()); + return CoreUtils.GSON.fromJson(str, BallMessageInfo.class); + }; + + private BallMessageInfoCodec() { + } + + @Override + public Decoder getValueDecoder() { + return decoder; + } + + @Override + public Encoder getValueEncoder() { + return encoder; + } + +} diff --git a/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/connector/BallChannelHandler.java b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/connector/BallChannelHandler.java deleted file mode 100644 index c812d5b..0000000 --- a/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/connector/BallChannelHandler.java +++ /dev/null @@ -1,195 +0,0 @@ -package cn.hamster3.mc.plugin.ball.common.connector; - -import cn.hamster3.mc.plugin.ball.common.api.BallAPI; -import cn.hamster3.mc.plugin.ball.common.data.BallMessageInfo; -import cn.hamster3.mc.plugin.ball.common.event.player.*; -import cn.hamster3.mc.plugin.ball.common.event.server.ServerOfflineEvent; -import cn.hamster3.mc.plugin.ball.common.event.server.ServerOnlineEvent; -import cn.hamster3.mc.plugin.ball.common.listener.BallListener; -import cn.hamster3.mc.plugin.core.common.util.CoreUtils; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.SimpleChannelInboundHandler; -import org.jetbrains.annotations.NotNull; - -import java.net.SocketAddress; -import java.util.logging.Level; - -@ChannelHandler.Sharable -public class BallChannelHandler extends SimpleChannelInboundHandler { - @Override - protected void channelRead0(ChannelHandlerContext context, String message) { - if ("pong".equals(message)) { - return; - } - if ("connection refused".equals(message)) { - for (BallListener listener : BallAPI.getInstance().getListeners()) { - try { - listener.onConnectRefused(); - } catch (Exception | Error e) { - e.printStackTrace(); - } - } - return; - } - BallMessageInfo info = CoreUtils.GSON.fromJson(message, BallMessageInfo.class); - for (BallListener listener : BallAPI.getInstance().getListeners()) { - try { - listener.onMessageReceived(info); - } catch (Exception | Error e) { - e.printStackTrace(); - } - } - if (!BallAPI.BALL_CHANNEL.equals(info.getChannel())) { - return; - } - switch (info.getAction()) { - case BallPlayerPreLoginEvent.ACTION: { - BallPlayerPreLoginEvent event = CoreUtils.GSON.fromJson(info.getContent(), BallPlayerPreLoginEvent.class); - for (BallListener listener : BallAPI.getInstance().getListeners()) { - try { - listener.onBallPlayerPreLogin(event); - } catch (Exception | Error e) { - e.printStackTrace(); - } - } - break; - } - case BallPlayerLoginEvent.ACTION: { - BallPlayerLoginEvent event = CoreUtils.GSON.fromJson(info.getContent(), BallPlayerLoginEvent.class); - for (BallListener listener : BallAPI.getInstance().getListeners()) { - try { - listener.onBallPlayerLogin(event); - } catch (Exception | Error e) { - e.printStackTrace(); - } - } - break; - } - case BallPlayerPostLoginEvent.ACTION: { - BallPlayerPostLoginEvent event = CoreUtils.GSON.fromJson(info.getContent(), BallPlayerPostLoginEvent.class); - for (BallListener listener : BallAPI.getInstance().getListeners()) { - try { - listener.onBallPlayerPostLogin(event); - } catch (Exception | Error e) { - e.printStackTrace(); - } - } - break; - } - case BallPlayerPreConnectServerEvent.ACTION: { - BallPlayerPreConnectServerEvent event = CoreUtils.GSON.fromJson(info.getContent(), BallPlayerPreConnectServerEvent.class); - for (BallListener listener : BallAPI.getInstance().getListeners()) { - try { - listener.onBallPlayerPreConnectServer(event); - } catch (Exception | Error e) { - e.printStackTrace(); - } - } - break; - } - case BallPlayerConnectServerEvent.ACTION: { - BallPlayerConnectServerEvent event = CoreUtils.GSON.fromJson(info.getContent(), BallPlayerConnectServerEvent.class); - for (BallListener listener : BallAPI.getInstance().getListeners()) { - try { - listener.onBallPlayerConnectServer(event); - } catch (Exception | Error e) { - e.printStackTrace(); - } - } - break; - } - case BallPlayerPostConnectServerEvent.ACTION: { - BallPlayerPostConnectServerEvent event = CoreUtils.GSON.fromJson(info.getContent(), BallPlayerPostConnectServerEvent.class); - for (BallListener listener : BallAPI.getInstance().getListeners()) { - try { - listener.onBallPlayerPostConnectServer(event); - } catch (Exception | Error e) { - e.printStackTrace(); - } - } - break; - } - case BallPlayerLogoutEvent.ACTION: { - BallPlayerLogoutEvent event = CoreUtils.GSON.fromJson(info.getContent(), BallPlayerLogoutEvent.class); - for (BallListener listener : BallAPI.getInstance().getListeners()) { - try { - listener.onBallPlayerLogout(event); - } catch (Exception | Error e) { - e.printStackTrace(); - } - } - break; - } - case BallPlayerInfoUpdateEvent.ACTION: { - BallPlayerInfoUpdateEvent event = CoreUtils.GSON.fromJson(info.getContent(), BallPlayerInfoUpdateEvent.class); - for (BallListener listener : BallAPI.getInstance().getListeners()) { - try { - listener.onBallPlayerInfoUpdate(event); - } catch (Exception | Error e) { - e.printStackTrace(); - } - } - break; - } - case BallPlayerChatEvent.ACTION: { - BallPlayerChatEvent event = CoreUtils.GSON.fromJson(info.getContent(), BallPlayerChatEvent.class); - for (BallListener listener : BallAPI.getInstance().getListeners()) { - try { - listener.onBallPlayerChat(event); - } catch (Exception | Error e) { - e.printStackTrace(); - } - } - break; - } - case ServerOfflineEvent.ACTION: { - ServerOfflineEvent event = CoreUtils.GSON.fromJson(info.getContent(), ServerOfflineEvent.class); - for (BallListener listener : BallAPI.getInstance().getListeners()) { - try { - listener.onServerOffline(event); - } catch (Exception | Error e) { - e.printStackTrace(); - } - } - break; - } - case ServerOnlineEvent.ACTION: { - ServerOnlineEvent event = CoreUtils.GSON.fromJson(info.getContent(), ServerOnlineEvent.class); - for (BallListener listener : BallAPI.getInstance().getListeners()) { - try { - listener.onServerOnline(event); - } catch (Exception | Error e) { - e.printStackTrace(); - } - } - break; - } - } - } - - @Override - public void channelActive(@NotNull ChannelHandlerContext context) { - BallAPI.getInstance().getLogger().info("与服务器 " + context.channel().remoteAddress() + " 建立了连接."); - } - - @Override - public void channelInactive(@NotNull ChannelHandlerContext context) { - context.close(); - BallAPI.getInstance().getLogger().warning("与服务器 " + context.channel().remoteAddress() + " 的连接已断开."); - CoreUtils.WORKER_EXECUTOR.submit(() -> BallAPI.getInstance().reconnect(5)); - } - - @Override - public void exceptionCaught(ChannelHandlerContext context, Throwable cause) { - SocketAddress address = context.channel().remoteAddress(); - BallAPI.getInstance().getLogger().log(Level.WARNING, "与服务器 " + address + " 通信时出现了一个错误: ", cause); - for (BallListener listener : BallAPI.getInstance().getListeners()) { - try { - listener.onConnectException(cause); - } catch (Exception | Error e) { - e.printStackTrace(); - } - } - } -} diff --git a/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/connector/BallChannelInitializer.java b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/connector/BallChannelInitializer.java deleted file mode 100644 index f516a90..0000000 --- a/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/connector/BallChannelInitializer.java +++ /dev/null @@ -1,28 +0,0 @@ -package cn.hamster3.mc.plugin.ball.common.connector; - -import io.netty.channel.ChannelInitializer; -import io.netty.channel.socket.SocketChannel; -import io.netty.handler.codec.LengthFieldBasedFrameDecoder; -import io.netty.handler.codec.LengthFieldPrepender; -import io.netty.handler.codec.string.StringDecoder; -import io.netty.handler.codec.string.StringEncoder; -import io.netty.handler.timeout.IdleStateHandler; -import org.jetbrains.annotations.NotNull; - -import java.nio.charset.StandardCharsets; -import java.util.concurrent.TimeUnit; - -public class BallChannelInitializer extends ChannelInitializer { - @Override - protected void initChannel(@NotNull SocketChannel channel) { - channel.pipeline() - .addLast(new IdleStateHandler(0, 5, 0, TimeUnit.SECONDS)) - .addLast(new BallKeepAliveHandler()) - .addLast(new LengthFieldPrepender(8)) - .addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 8, 0, 8)) - .addLast(new StringDecoder(StandardCharsets.UTF_8)) - .addLast(new StringEncoder(StandardCharsets.UTF_8)) - .addLast(new BallChannelHandler()); - } -} - diff --git a/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/connector/BallKeepAliveHandler.java b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/connector/BallKeepAliveHandler.java deleted file mode 100644 index e914800..0000000 --- a/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/connector/BallKeepAliveHandler.java +++ /dev/null @@ -1,18 +0,0 @@ -package cn.hamster3.mc.plugin.ball.common.connector; - -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; -import io.netty.handler.timeout.IdleStateEvent; - -@ChannelHandler.Sharable -public class BallKeepAliveHandler extends ChannelInboundHandlerAdapter { - @Override - public void userEventTriggered(ChannelHandlerContext context, Object event) throws Exception { - if (event instanceof IdleStateEvent) { - context.channel().writeAndFlush("ping"); - return; - } - super.userEventTriggered(context, event); - } -} diff --git a/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/data/BallMessageInfo.java b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/data/BallMessageInfo.java index 73a2e4f..8c9cfd1 100644 --- a/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/data/BallMessageInfo.java +++ b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/data/BallMessageInfo.java @@ -6,7 +6,6 @@ import cn.hamster3.mc.plugin.core.common.util.CoreUtils; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; import lombok.Data; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -69,21 +68,7 @@ public class BallMessageInfo { this.action = action; } - public BallMessageInfo(@NotNull String channel, @NotNull String action, String content) { - senderID = BallAPI.getInstance().getLocalServerId(); - this.channel = channel; - this.action = action; - this.content = new JsonPrimitive(content); - } - - public BallMessageInfo(@NotNull String channel, @NotNull String action, JsonElement content) { - this.channel = channel; - senderID = BallAPI.getInstance().getLocalServerId(); - this.action = action; - this.content = content; - } - - public BallMessageInfo(@NotNull String channel, @NotNull String action, Object content) { + public BallMessageInfo(@NotNull String channel, @NotNull String action, @NotNull Object content) { this.channel = channel; senderID = BallAPI.getInstance().getLocalServerId(); this.action = action; diff --git a/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/listener/BallDebugListener.java b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/listener/BallDebugListener.java index 6a0d32b..5605654 100644 --- a/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/listener/BallDebugListener.java +++ b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/listener/BallDebugListener.java @@ -2,108 +2,16 @@ 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.BallMessageInfo; -import cn.hamster3.mc.plugin.ball.common.event.player.*; -import cn.hamster3.mc.plugin.ball.common.event.server.ServerOfflineEvent; -import cn.hamster3.mc.plugin.ball.common.event.server.ServerOnlineEvent; -import cn.hamster3.mc.plugin.core.lib.net.kyori.adventure.text.serializer.json.JSONComponentSerializer; -import org.jetbrains.annotations.NotNull; +import org.redisson.api.listener.MessageListener; -import java.util.logging.Level; - -public final class BallDebugListener implements BallListener { +public class BallDebugListener implements MessageListener { public static final BallDebugListener INSTANCE = new BallDebugListener(); private BallDebugListener() { } @Override - public ListenerPriority getPriority() { - return ListenerPriority.MONITOR; - } - - @Override - public void onConnectActive() { - BallAPI.getInstance().getLogger().info("连接已可用。"); - } - - @Override - public void onConnectException(Throwable throwable) { - BallAPI.getInstance().getLogger().log(Level.INFO, "连接出现错误!", throwable); - } - - @Override - public void onServiceDead() { - BallAPI.getInstance().getLogger().info("已无法建立与服务中心的连接!"); - } - - @Override - public void onBallPlayerPreLogin(@NotNull BallPlayerPreLoginEvent event) { - BallAPI.getInstance().getLogger().info("BallPlayerPreLoginEvent: " + event.getPlayerName()); - } - - @Override - public void onBallPlayerLogin(@NotNull BallPlayerLoginEvent event) { - BallAPI.getInstance().getLogger().info("BallPlayerLoginEvent: " + event.getPlayerInfo().getName()); - } - - @Override - public void onBallPlayerPostLogin(@NotNull BallPlayerPostLoginEvent event) { - BallAPI.getInstance().getLogger().info("BallPlayerPostLoginEvent: " + event.getPlayerInfo().getName()); - } - - @Override - public void onBallPlayerPreConnectServer(@NotNull BallPlayerPreConnectServerEvent event) { - BallAPI.getInstance().getLogger().info("BallPlayerPreConnectServerEvent: "); - BallAPI.getInstance().getLogger().info("player: " + event.getPlayerInfo().getName()); - BallAPI.getInstance().getLogger().info("from: " + event.getFrom()); - BallAPI.getInstance().getLogger().info("to: " + event.getTo()); - } - - @Override - public void onBallPlayerConnectServer(@NotNull BallPlayerConnectServerEvent event) { - BallAPI.getInstance().getLogger().info("BallPlayerConnectServerEvent: "); - BallAPI.getInstance().getLogger().info("player: " + event.getPlayerInfo().getName()); - BallAPI.getInstance().getLogger().info("from: " + event.getFrom()); - BallAPI.getInstance().getLogger().info("to: " + event.getTo()); - } - - @Override - public void onBallPlayerPostConnectServer(@NotNull BallPlayerPostConnectServerEvent event) { - BallAPI.getInstance().getLogger().info("BallPlayerPostConnectServerEvent: "); - BallAPI.getInstance().getLogger().info("player: " + event.getPlayerInfo().getName()); - } - - @Override - public void onBallPlayerLogout(@NotNull BallPlayerLogoutEvent event) { - BallAPI.getInstance().getLogger().info("BallPlayerLogoutEvent: "); - BallAPI.getInstance().getLogger().info("player: " + event.getPlayerInfo().getName()); - } - - @Override - public void onBallPlayerChat(@NotNull BallPlayerChatEvent event) { - BallAPI.getInstance().getLogger().info("BallPlayerChatEvent: "); - BallAPI.getInstance().getLogger().info("displayName: " + event.getDisplayName()); - BallAPI.getInstance().getLogger().info("playerUUID: " + event.getPlayerUUID()); - BallAPI.getInstance().getLogger().info("message: " + JSONComponentSerializer.json().serialize(event.getMessage())); - } - - @Override - public void onServerOffline(@NotNull ServerOfflineEvent event) { - BallAPI.getInstance().getLogger().info("服务器已离线: " + event.getServerID()); - } - - @Override - public void onServerOnline(@NotNull ServerOnlineEvent event) { - BallAPI.getInstance().getLogger().info("服务器已上线: " + event.getServerInfo().getId()); - } - - @Override - public void onMessageSend(@NotNull BallMessageInfo event) { - BallAPI.getInstance().getLogger().info("发送了一条消息: " + event); - } - - @Override - public void onMessageReceived(@NotNull BallMessageInfo event) { + public void onMessage(CharSequence channel, BallMessageInfo event) { BallAPI.getInstance().getLogger().info("收到了一条消息: " + event); } } diff --git a/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/utils/OS.java b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/utils/OS.java deleted file mode 100644 index 341fa93..0000000 --- a/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/utils/OS.java +++ /dev/null @@ -1,89 +0,0 @@ -package cn.hamster3.mc.plugin.ball.common.utils; - -import cn.hamster3.mc.plugin.core.common.thread.NamedThreadFactory; -import io.netty.channel.Channel; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.ServerChannel; -import io.netty.channel.epoll.EpollEventLoopGroup; -import io.netty.channel.epoll.EpollServerSocketChannel; -import io.netty.channel.epoll.EpollSocketChannel; -import io.netty.channel.kqueue.KQueueEventLoopGroup; -import io.netty.channel.kqueue.KQueueServerSocketChannel; -import io.netty.channel.kqueue.KQueueSocketChannel; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.nio.NioServerSocketChannel; -import io.netty.channel.socket.nio.NioSocketChannel; -import org.jetbrains.annotations.NotNull; - -import java.util.concurrent.ThreadFactory; - -public enum OS { - WINDOWS, - LINUX, - MACOS, - OTHER; - - private static final ThreadFactory THREAD_FACTORY = new NamedThreadFactory("HamsterBall-IO"); - - @NotNull - public static OS getCurrentOS() { - String s = System.getProperties().get("os.name").toString().toLowerCase(); - if (s.contains("windows")) { - return WINDOWS; - } - if (s.contains("linux")) { - return LINUX; - } - if (s.contains("mac")) { - return MACOS; - } - return OTHER; - } - - public String getIOModeName() { - switch (this) { - case LINUX: - return "epoll"; - case MACOS: - return "kqueue"; - default: - return "nio"; - } - } - - @NotNull - public EventLoopGroup getEventLoopGroup(int nThread) { - switch (this) { - case LINUX: - return new EpollEventLoopGroup(nThread, THREAD_FACTORY); - case MACOS: - return new KQueueEventLoopGroup(nThread, THREAD_FACTORY); - default: - return new NioEventLoopGroup(nThread, THREAD_FACTORY); - } - } - - @NotNull - public Class getSocketChannel() { - switch (this) { - case LINUX: - return EpollSocketChannel.class; - case MACOS: - return KQueueSocketChannel.class; - default: - return NioSocketChannel.class; - } - } - - @NotNull - public Class getServerSocketChannel() { - switch (this) { - case LINUX: - return EpollServerSocketChannel.class; - case MACOS: - return KQueueServerSocketChannel.class; - default: - return NioServerSocketChannel.class; - } - } -} diff --git a/ball-common/src/main/resources/redission.yml b/ball-common/src/main/resources/redission.yml new file mode 100644 index 0000000..504c8f8 --- /dev/null +++ b/ball-common/src/main/resources/redission.yml @@ -0,0 +1,36 @@ +singleServerConfig: + address: "redis://localhost:6379" + password: "Reids123.." + username: "default" + database: 1 + clientName: "HamsterBall" + idleConnectionTimeout: 10000 + connectTimeout: 10000 + timeout: 3000 + retryAttempts: 3 + retryInterval: 1500 + subscriptionsPerConnection: 5 + sslEnableEndpointIdentification: true + sslProvider: "JDK" + pingConnectionInterval: 30000 + keepAlive: false + tcpNoDelay: true + subscriptionConnectionMinimumIdleSize: 1 + subscriptionConnectionPoolSize: 50 + connectionMinimumIdleSize: 1 + connectionPoolSize: 10 + dnsMonitoringInterval: 5000 +threads: 4 +nettyThreads: 4 +referenceEnabled: true +lockWatchdogTimeout: 30000 +checkLockSyncedSlaves: true +slavesSyncTimeout: 1000 +reliableTopicWatchdogTimeout: 600000 +keepPubSubOrder: true +useScriptCache: false +minCleanUpDelay: 5 +maxCleanUpDelay: 1800 +cleanUpKeysAmount: 100 +useThreadClassLoader: true +lazyInitialization: false diff --git a/ball-server/build.gradle b/ball-server/build.gradle deleted file mode 100644 index 9a080ab..0000000 --- a/ball-server/build.gradle +++ /dev/null @@ -1,61 +0,0 @@ -setArchivesBaseName("HamsterBall-Server") - -evaluationDependsOn(':ball-common') - -dependencies { - apiShade project(":ball-common") transitive false - shade "cn.hamster3.mc.plugin:core-common:${hamster_core_version}" - -// // https://mvnrepository.com/artifact/org.slf4j/slf4j-api -// implementation 'org.slf4j:slf4j-api:2.0.3' - - // https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core - implementationShade 'org.apache.logging.log4j:log4j-core:2.19.0' - // https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl - implementationShade 'org.apache.logging.log4j:log4j-slf4j-impl:2.19.0' - - // https://mvnrepository.com/artifact/io.netty/netty-all - implementationShade 'io.netty:netty-all:4.1.86.Final' - // https://mvnrepository.com/artifact/org.yaml/snakeyaml - implementationShade 'org.yaml:snakeyaml:2.0' - // https://mvnrepository.com/artifact/com.google.code.gson/gson - //noinspection GradlePackageUpdate - implementationShade 'com.google.code.gson:gson:2.8.9' - - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.0' -} - -test { - useJUnitPlatform() -} - -tasks.register("shadowJar", Jar) { - dependsOn("jar") - manifest.attributes('Main-Class': 'cn.hamster3.mc.plugin.ball.server.Bootstrap') - manifest.attributes('ball-version': project.version) - from([ - tasks.jar.outputs.files.collect { - it.isDirectory() ? it : zipTree(it) - }, - configurations.shade.collect { - it.isDirectory() ? it : zipTree(it) - }, - configurations.apiShade.collect { - it.isDirectory() ? it : zipTree(it) - }, - configurations.implementationShade.collect { - it.isDirectory() ? it : zipTree(it) - } - ]) - destinationDirectory = rootProject.buildDir -} - -tasks { - compileJava { - dependsOn(":ball-common:build") - } - build { - dependsOn(shadowJar) - } -} \ No newline at end of file diff --git a/ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/Bootstrap.java b/ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/Bootstrap.java deleted file mode 100644 index abec97c..0000000 --- a/ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/Bootstrap.java +++ /dev/null @@ -1,77 +0,0 @@ -package cn.hamster3.mc.plugin.ball.server; - -import cn.hamster3.mc.plugin.ball.common.utils.OS; -import cn.hamster3.mc.plugin.ball.server.command.CommandHandler; -import cn.hamster3.mc.plugin.ball.server.config.ServerConfig; -import cn.hamster3.mc.plugin.ball.server.connector.BallServerChannelInitializer; -import io.netty.bootstrap.ServerBootstrap; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelOption; -import io.netty.channel.EventLoopGroup; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.lang.management.ManagementFactory; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; - -public class Bootstrap { - private static final Logger LOGGER = LoggerFactory.getLogger("Bootstrap"); - - public static void main(String[] args) throws IOException, InterruptedException { - if (initDefaultFile()) { - LOGGER.info("请重新启动该程序."); - return; - } - ServerConfig.init(); - LOGGER.info("配置文件加载完成."); - OS currentOS = OS.getCurrentOS(); - LOGGER.info("当前操作系统为: {}. 选择 IO 模式为: {}", currentOS.name(), currentOS.getIOModeName()); - EventLoopGroup loopGroup = currentOS.getEventLoopGroup(ServerConfig.getEventLoopThread()); - ServerBootstrap bootstrap = new ServerBootstrap() - .group(loopGroup) - .channel(currentOS.getServerSocketChannel()) - .childOption(ChannelOption.TCP_NODELAY, true) - .childHandler(new BallServerChannelInitializer()); - ChannelFuture future = bootstrap.bind(ServerConfig.getHost(), ServerConfig.getPort()); - future.await(); - if (future.isSuccess()) { - LOGGER.info("进程信息: {}", ManagementFactory.getRuntimeMXBean().getName()); - LOGGER.info("服务器已启动. 输入 stop 来关闭该程序."); - } else { - LOGGER.error("仓鼠球服务器启动失败!", future.cause()); - loopGroup.shutdownGracefully(); - return; - } - CommandHandler.INSTANCE.start(loopGroup); - } - - private static boolean initDefaultFile() throws IOException { - boolean saved = false; - File log4jFile = new File("log4j2.xml"); - if (!log4jFile.exists()) { - InputStream stream = ServerConfig.class.getResourceAsStream("/log4j2.xml"); - if (stream == null) { - throw new IOException("log4j2.xml 文件损坏!"); - } - Files.copy(stream, log4jFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - LOGGER.info("已生成默认 log4j2.xml 文件!"); - saved = true; - } - - File configFile = new File("config.yml"); - if (!configFile.exists()) { - InputStream stream = ServerConfig.class.getResourceAsStream("/config.yml"); - if (stream == null) { - throw new IOException("config.yml 文件损坏!"); - } - Files.copy(stream, configFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - LOGGER.info("已生成默认 config.yml 文件!"); - saved = true; - } - return saved; - } -} diff --git a/ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/command/CommandHandler.java b/ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/command/CommandHandler.java deleted file mode 100644 index 4dad372..0000000 --- a/ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/command/CommandHandler.java +++ /dev/null @@ -1,79 +0,0 @@ -package cn.hamster3.mc.plugin.ball.server.command; - -import cn.hamster3.mc.plugin.ball.server.config.ServerConfig; -import io.netty.channel.EventLoopGroup; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.util.Scanner; - -public class CommandHandler { - public static final CommandHandler INSTANCE = new CommandHandler(); - - private static final Logger LOGGER = LoggerFactory.getLogger("CommandHandler"); - private EventLoopGroup loopGroup; - - private boolean started; - - public void start(EventLoopGroup loopGroup) { - this.loopGroup = loopGroup; - - started = true; - Scanner scanner = new Scanner(System.in); - LOGGER.info("命令执行器准备就绪. 输入 help 查看命令帮助."); - while (started) { - String command = scanner.nextLine(); - try { - executeCommand(command); - } catch (Exception e) { - LOGGER.error("执行命令 " + command + " 时遇到了一个异常: ", e); - } - } - } - - public void executeCommand(String command) throws Exception { - String[] args = command.split(" "); - switch (args[0].toLowerCase()) { - case "?": - case "help": { - help(); - break; - } - case "reload": { - reload(); - break; - } - case "end": - case "stop": { - stop(); - break; - } - default: { - LOGGER.info("未知指令. 请输入 help 查看帮助."); - break; - } - } - } - - public void help() { - LOGGER.info("==============================================================="); - LOGGER.info("help - 查看帮助."); - LOGGER.info("reload - 重载配置文件."); - LOGGER.info("stop - 关闭该程序."); - LOGGER.info("==============================================================="); - } - - public void reload() throws IOException { - ServerConfig.init(); - LOGGER.info("配置文件加载完成."); - } - - public void stop() throws Exception { - started = false; - LOGGER.info("准备关闭服务器..."); - loopGroup.shutdownGracefully().await(); - LOGGER.info("服务器已关闭!"); - } - -} diff --git a/ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/config/ServerConfig.java b/ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/config/ServerConfig.java deleted file mode 100644 index c15e3b2..0000000 --- a/ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/config/ServerConfig.java +++ /dev/null @@ -1,66 +0,0 @@ -package cn.hamster3.mc.plugin.ball.server.config; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.yaml.snakeyaml.Yaml; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.util.List; -import java.util.Map; - -public final class ServerConfig { - private static final Logger LOGGER = LoggerFactory.getLogger("ServerConfig"); - - private static String host; - private static int port; - private static int eventLoopThread; - private static boolean enableAcceptList; - private static List acceptList; - - private ServerConfig() { - } - - @SuppressWarnings("unchecked") - public static void init() throws IOException { - File configFile = new File("config.yml"); - Map map; - try (InputStream stream = Files.newInputStream(configFile.toPath())) { - map = new Yaml().load(stream); - } - - host = map.getOrDefault("host", "localhost").toString(); - port = (int) map.getOrDefault("port", 58888); - eventLoopThread = (int) map.getOrDefault("event-loop-thread", 5); - enableAcceptList = (boolean) map.get("enable-accept-list"); - acceptList = (List) map.get("accept-list"); - - LOGGER.info("host: {}", host); - LOGGER.info("port: {}", port); - LOGGER.info("eventLoopThread: {}", eventLoopThread); - LOGGER.info("enableAcceptList: {}", enableAcceptList); - LOGGER.info("acceptList: {}", acceptList); - } - - public static String getHost() { - return host; - } - - public static int getPort() { - return port; - } - - public static int getEventLoopThread() { - return eventLoopThread; - } - - public static boolean isEnableAcceptList() { - return enableAcceptList; - } - - public static List getAcceptList() { - return acceptList; - } -} diff --git a/ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/connector/BallServerChannelHandler.java b/ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/connector/BallServerChannelHandler.java deleted file mode 100644 index 8b6af1d..0000000 --- a/ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/connector/BallServerChannelHandler.java +++ /dev/null @@ -1,49 +0,0 @@ -package cn.hamster3.mc.plugin.ball.server.connector; - -import cn.hamster3.mc.plugin.ball.common.data.BallMessageInfo; -import cn.hamster3.mc.plugin.ball.server.constant.ConstantObjects; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.SimpleChannelInboundHandler; -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@ChannelHandler.Sharable -public class BallServerChannelHandler extends SimpleChannelInboundHandler { - private static final Logger LOGGER = LoggerFactory.getLogger("BallServerChannelHandler"); - - @Override - protected void channelRead0(ChannelHandlerContext context, String message) { - if ("ping".equals(message)) { - context.channel().writeAndFlush("pong"); - return; - } - try { - BallMessageInfo messageInfo = ConstantObjects.GSON.fromJson(message, BallMessageInfo.class); - LOGGER.info("从服务器 {} 上收到一条消息: {}", context.channel().remoteAddress(), messageInfo); - BallServerChannelInitializer.broadcastMessage(messageInfo); - } catch (Exception e) { - LOGGER.error(String.format("处理消息 %s 时出现错误: ", message), e); - } - } - - @Override - public void channelActive(@NotNull ChannelHandlerContext context) { - LOGGER.info("与服务器 {} 建立了连接.", context.channel().remoteAddress()); - } - - @Override - public void channelInactive(ChannelHandlerContext context) { - context.close(); - synchronized (BallServerChannelInitializer.CHANNELS) { - BallServerChannelInitializer.CHANNELS.remove(context.channel()); - } - LOGGER.warn("与服务器 {} 的连接已断开.", context.channel().remoteAddress()); - } - - @Override - public void exceptionCaught(ChannelHandlerContext context, Throwable cause) { - LOGGER.warn("与服务器 {} 通信时出现了一个错误: ", context.channel().remoteAddress(), cause); - } -} \ No newline at end of file diff --git a/ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/connector/BallServerChannelInitializer.java b/ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/connector/BallServerChannelInitializer.java deleted file mode 100644 index abb30c8..0000000 --- a/ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/connector/BallServerChannelInitializer.java +++ /dev/null @@ -1,73 +0,0 @@ -package cn.hamster3.mc.plugin.ball.server.connector; - -import cn.hamster3.mc.plugin.ball.common.data.BallMessageInfo; -import cn.hamster3.mc.plugin.ball.server.config.ServerConfig; -import io.netty.channel.Channel; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.socket.SocketChannel; -import io.netty.handler.codec.LengthFieldBasedFrameDecoder; -import io.netty.handler.codec.LengthFieldPrepender; -import io.netty.handler.codec.string.StringDecoder; -import io.netty.handler.codec.string.StringEncoder; -import io.netty.handler.timeout.IdleStateHandler; -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.InetSocketAddress; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - -public class BallServerChannelInitializer extends ChannelInitializer { - public static final List CHANNELS = new ArrayList<>(); - private static final Logger LOGGER = LoggerFactory.getLogger("BallServerChannelInitializer"); - - public static void broadcastMessage(BallMessageInfo messageInfo) { - String string = messageInfo.toString(); - synchronized (CHANNELS) { - for (Channel channel : CHANNELS) { - channel.writeAndFlush(string); - } - } - } - - @Override - protected void initChannel(@NotNull SocketChannel channel) { - InetSocketAddress remoteAddress = channel.remoteAddress(); - LOGGER.info("远程地址 {} 请求建立连接.", remoteAddress.toString()); - - String hostAddress = remoteAddress.getAddress().getHostAddress(); - if (ServerConfig.isEnableAcceptList() && !ServerConfig.getAcceptList().contains(hostAddress)) { - LOGGER.warn("{} 不在白名单列表中, 断开连接!", hostAddress); - - channel.pipeline() - .addLast(new LengthFieldPrepender(8)) - .addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 8, 0, 8)) - .addLast(new StringDecoder(StandardCharsets.UTF_8)) - .addLast(new StringEncoder(StandardCharsets.UTF_8)); - - try { - channel.writeAndFlush("connection refused").await(); - channel.disconnect().await(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - return; - } - - channel.pipeline() - .addLast(new IdleStateHandler(60, 0, 0, TimeUnit.SECONDS)) - .addLast(new BallServerKeepAliveHandler()) - .addLast(new LengthFieldPrepender(8)) - .addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 8, 0, 8)) - .addLast(new StringDecoder(StandardCharsets.UTF_8)) - .addLast(new StringEncoder(StandardCharsets.UTF_8)) - .addLast(new BallServerChannelHandler()); - - synchronized (CHANNELS) { - CHANNELS.add(channel); - } - } -} diff --git a/ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/connector/BallServerKeepAliveHandler.java b/ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/connector/BallServerKeepAliveHandler.java deleted file mode 100644 index d5452ae..0000000 --- a/ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/connector/BallServerKeepAliveHandler.java +++ /dev/null @@ -1,22 +0,0 @@ -package cn.hamster3.mc.plugin.ball.server.connector; - -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.SimpleUserEventChannelHandler; -import io.netty.handler.timeout.IdleStateEvent; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@ChannelHandler.Sharable -public class BallServerKeepAliveHandler extends SimpleUserEventChannelHandler { - private static final Logger LOGGER = LoggerFactory.getLogger("BallServerKeepAliveHandler"); - - @Override - protected void eventReceived(ChannelHandlerContext context, IdleStateEvent event) { - synchronized (BallServerChannelInitializer.CHANNELS) { - BallServerChannelInitializer.CHANNELS.remove(context.channel()); - } - context.close(); - LOGGER.warn("由于无法验证连接存活,与服务器 {} 的连接已断开.", context.channel().remoteAddress()); - } -} diff --git a/ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/constant/ConstantObjects.java b/ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/constant/ConstantObjects.java deleted file mode 100644 index 0996cbb..0000000 --- a/ball-server/src/main/java/cn/hamster3/mc/plugin/ball/server/constant/ConstantObjects.java +++ /dev/null @@ -1,12 +0,0 @@ -package cn.hamster3.mc.plugin.ball.server.constant; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - -public interface ConstantObjects { - /** - * GSON 工具 - */ - Gson GSON = new GsonBuilder().create(); - -} diff --git a/ball-server/src/main/resources/config.yml b/ball-server/src/main/resources/config.yml deleted file mode 100644 index 195701a..0000000 --- a/ball-server/src/main/resources/config.yml +++ /dev/null @@ -1,17 +0,0 @@ -# 绑定网卡地址 -host: "0.0.0.0" -# 绑定端口 -port: 58888 - -# 线程池数量 -# 建议设置为全服最大玩家数 / 20 -# 不建议低于 4 -event-loop-thread: 5 - -# 是否启用 IP 白名单 -enable-accept-list: true - -# 允许连接至服务的 ip 名单 -accept-list: - - "127.0.0.1" - - "0:0:0:0:0:0:0:1" diff --git a/ball-server/src/main/resources/log4j2.component.properties b/ball-server/src/main/resources/log4j2.component.properties deleted file mode 100644 index d1b5fe1..0000000 --- a/ball-server/src/main/resources/log4j2.component.properties +++ /dev/null @@ -1,2 +0,0 @@ -log4j2.loggerContextFactory=org.apache.logging.log4j.core.impl.Log4jContextFactory -log4j.configurationFile=log4j2.xml diff --git a/ball-server/src/main/resources/log4j2.xml b/ball-server/src/main/resources/log4j2.xml deleted file mode 100644 index d93671d..0000000 --- a/ball-server/src/main/resources/log4j2.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/build.gradle b/build.gradle deleted file mode 100644 index beba823..0000000 --- a/build.gradle +++ /dev/null @@ -1,85 +0,0 @@ -plugins { - id 'java' -} - -group 'cn.hamster3.mc.plugin' -version '1.0.0-SNAPSHOT' - -subprojects { - apply plugin: 'java-library' - apply plugin: 'maven-publish' - - group = rootProject.group - version = rootProject.version - - repositories { - maven { - url = "https://maven.airgame.net/maven-public/" - } - } - - configurations { - shade - api.extendsFrom apiShade - implementation.extendsFrom implementationShade - } - - dependencies { - // https://mvnrepository.com/artifact/org.jetbrains/annotations - compileOnly 'org.jetbrains:annotations:23.0.0' - // https://mvnrepository.com/artifact/org.projectlombok/lombok - compileOnly 'org.projectlombok:lombok:1.18.24' - annotationProcessor 'org.projectlombok:lombok:1.18.24' - } - - java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 -// withJavadocJar() - withSourcesJar() - } - - tasks { - withType(JavaCompile).configureEach { - options.setEncoding("UTF-8") - } - withType(Jar).configureEach { - from([rootProject.file("LICENSE")]) - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - } - } - - javadoc { - options.quiet() - options.setEncoding("UTF-8") - options.jFlags("-Dfile.encoding=utf8") - options.addStringOption('Xdoclint:none', '-quiet') - options.links = [ - "https://javadoc.io/doc/org.jetbrains/annotations/23.0.0", - 'https://javadoc.io/doc/com.google.code.gson/gson/2.8.0', - 'http://milkbowl.github.io/VaultAPI', - 'https://bukkit.windit.net/javadoc' - ] - } - - publishing { - publications { - mavenJava(MavenPublication) { - from project.components.java - } - } - repositories { - maven { - def releasesRepoUrl = 'https://maven.airgame.net/maven-releases/' - def snapshotsRepoUrl = 'https://maven.airgame.net/maven-snapshots/' - - url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl - - credentials { - username = rootProject.properties.getOrDefault("maven_username", "") - password = rootProject.properties.getOrDefault("maven_password", "") - } - } - } - } -} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..9999ad1 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,83 @@ +plugins { + id("java") + id("maven-publish") + id("com.github.johnrengelman.shadow") version "8+" +} + +group = "cn.hamster3.mc.plugin" +version = "1.1.0-SNAPSHOT" + +subprojects { + apply { + plugin("java") + plugin("maven-publish") + plugin("com.github.johnrengelman.shadow") + } + + group = rootProject.group + version = rootProject.version + + repositories { + maven { + url = uri("https://maven.airgame.net/maven-public/") + } + } + + dependencies { + // https://mvnrepository.com/artifact/org.jetbrains/annotations + implementation("org.jetbrains:annotations:24.0.1") + // https://mvnrepository.com/artifact/org.projectlombok/lombok + compileOnly("org.projectlombok:lombok:1.18.28") + annotationProcessor("org.projectlombok:lombok:1.18.28") + } + + java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +// withJavadocJar() + withSourcesJar() + } + + tasks { + withType().configureEach { + options.encoding = "UTF-8" + } + withType().configureEach { + from(rootProject.file("LICENSE")) + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } + javadoc { + options.jFlags("-Dfile.encoding=utf8") + options.encoding = "UTF-8" + options.quiet() + val o: StandardJavadocDocletOptions = options as StandardJavadocDocletOptions + o.addStringOption("Xdoclint:none", "-quiet") + o.links( + "https://javadoc.io/doc/org.jetbrains/annotations/23.0.0", + "https://javadoc.io/doc/com.google.code.gson/gson/2.8.0", + "https://bukkit.windit.net/javadoc" + ) + } + } + + publishing { + publications { + create("mavenJava") { + from(components["java"]) + } + } + repositories { + maven { + val releasesRepoUrl = uri("https://maven.airgame.net/maven-releases/") + val snapshotsRepoUrl = uri("https://maven.airgame.net/maven-snapshots/") + + url = if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl + + credentials { + username = rootProject.properties.getOrDefault("maven_username", "").toString() + password = rootProject.properties.getOrDefault("maven_password", "").toString() + } + } + } + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index cdca2f5..ba87704 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,3 @@ org.gradle.jvmargs=-Xmx2G hamster_core_version=1.0.0-SNAPSHOT +redission_version=3.23.2 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 41d9927a4d4fb3f96a785543079b8df6723c946b..249e5832f090a2944b7473328c07c9755baa3196 100644 GIT binary patch delta 10197 zcmaKS1ymhDwk=#NxVyW%y9U<)A-Dv)xI0|j{UX8L-JRg>5ZnnKAh;%chM6~S-g^K4 z>eZ{yK4;gd>gwvXs=Id8Jk-J}R4pT911;+{Jp9@aiz6!p1Oz9z&_kGLA%J5%3Ih@0 zQ|U}%$)3u|G`jIfPzMVfcWs?jV2BO^*3+q2><~>3j+Z`^Z%=;19VWg0XndJ zwJ~;f4$;t6pBKaWn}UNO-wLCFHBd^1)^v%$P)fJk1PbK5<;Z1K&>k~MUod6d%@Bq9 z>(44uiaK&sdhwTTxFJvC$JDnl;f}*Q-^01T508(8{+!WyquuyB7R!d!J)8Ni0p!cV6$CHsLLy6}7C zYv_$eD;)@L)tLj0GkGpBoa727hs%wH$>EhfuFy{_8Q8@1HI%ZAjlpX$ob{=%g6`Ox zLzM!d^zy`VV1dT9U9(^}YvlTO9Bf8v^wMK37`4wFNFzW?HWDY(U(k6@tp(crHD)X5>8S-# zW1qgdaZa*Sh6i%60e1+hty}34dD%vKgb?QmQiZ=-j+isA4={V_*R$oGN#j|#ia@n6 zuZx4e2Xx?^lUwYFn2&Tmbx0qA3Z8;y+zKoeQu;~k~FZGy!FU_TFxYd!Ck;5QvMx9gj5fI2@BLNp~Ps@ zf@k<&Q2GS5Ia9?_D?v~$I%_CLA4x~eiKIZ>9w^c#r|vB?wXxZ(vXd*vH(Fd%Me8p( z=_0)k=iRh%8i`FYRF>E97uOFTBfajv{IOz(7CU zv0Gd84+o&ciHlVtY)wn6yhZTQQO*4Mvc#dxa>h}82mEKKy7arOqU$enb9sgh#E=Lq zU;_RVm{)30{bw+|056%jMVcZRGEBSJ+JZ@jH#~DvaDQm92^TyUq=bY*+AkEakpK>8 zB{)CkK48&nE5AzTqT;WysOG|!y}5fshxR8Ek(^H6i>|Fd&wu?c&Q@N9ZrJ=?ABHI! z`*z8D`w=~AJ!P-9M=T}f`;76$qZRllB&8#9WgbuO$P7lVqdX1=g*t=7z6!0AQ^ux_ z9rcfUv^t}o_l-ZE+TqvqFsA*~W<^78!k;~!i8(eS+(+@u8FxK+Q7;mHZ<1}|4m<}vh@p`t%|@eM_J(P% zI>M7C)Ir{l|J;$G_EGGEhbP4?6{sYzMqBv+x95N&YWFH6UcE@b}B?q)G*4<4mR@sy1#vPnLMK51tb#ED(8TA1nE zYfhK7bo1!R5WJF$5Y?zG21)6+_(_5oSX9sGIW;(O&S?Rh(nydNQYzKjjJ54aDJ-1F zrJ=np8LsN?%?Rt7f~3aAX!2E{`fh_pb?2(;HOB3W+I*~A>W%iY+v45+^e$cE10fA} zXPvw9=Bd+(;+!rl)pkYj0HGB}+3Z!Mr;zr%gz~c-hFMv8b2VRE2R$8V=_XE zq$3=|Yg05(fmwrJ)QK2ptB4no`Y8Dg_vK2QDc6-6sXRQ5k78-+cPi-fH}vpgs|Ive zE=m*XNVs?EWgiNI!5AcD*3QMW)R`EqT!f0e1%hERO&?AT7HWnSf5@#AR{OGuXG3Zb zCnVWg7h|61lGV3k+>L<#d>)InG>ETn1DbOHCfztqzQ_fBiaUt@q6VMy={Fe-w#~2- z0?*f|z$zgjI9>+JVICObBaK=pU}AEOd@q(8d?j7zQFD@=6t`|KmolTr2MfBI$;EGh zD%W0cA_d#V6Lb$us5yIG(|d>r-QleC4;%hEu5W9hyY zY#+ESY&v`8(&mC~?*|e5WEhC!YU2>m_}`K+q9)a(d$bsS<=YkyZGp}YA%TXw>@abA zS_poVPoN+?<6?DAuCNt&5SHV(hp56PJ})swwVFZFXM->F zc|0c8<$H_OV%DR|y7e+s$12@Ac8SUClPg8_O9sTUjpv%6Jsn5vsZCg>wL+db4c+{+ zsg<#wOuV4jeOq`veckdi-1`dz;gvL)bZeH|D*x=8UwRU5&8W1@l>3$)8WzET0%;1J zM3(X<7tKK&9~kWRI{&FmwY5Gg!b5f4kI_vSm)H1#>l6M+OiReDXC{kPy!`%Ecq-+3yZTk=<` zm)pE6xum5q0Qkd#iny0Q-S}@I0;mDhxf>sX)Oiv)FdsAMnpx%oe8OQ`m%Xeozdzx!C1rQR>m1c_}+J4x)K}k{G zo68;oGG&Ox7w^-m7{g4a7NJu-B|~M;oIH~~#`RyUNm##feZH;E?pf}nshmoiIY52n z%pc%lnU4Q#C=RUz)RU6}E_j4#)jh<&a%JyJj$Fufc#&COaxFHtl}zJUGNLBu3~_@1 zn9F^JO9);Duxo&i@>X(kbYga1i>6p1fca8FzQ0>((Lb-aPUbC*d~a03V$y;*RBY!R ziEJ2IF^FjrvO}0Uy{cMn%u<+P5U!UO>pm9#ZYL5i6|xSC+np7IH$GfXs&uI;y4as@ z&AzJh>(S2?3PKKgab3Z(`xbx(C#46XIvVcW8eG_DjT~}Yz_8PWZ`uf6^Xr=vkvL_` zqmvfgJL+Zc`;iq~iP?%@G7}~fal-zqxa0yNyHBJJ5M)9bI>7S_cg?Ya&p(I)C5Ef4 zZ>YAF6x|U=?ec?g*|f2g5Tw3PgxaM_bi_5Az9MO$;_Byw(2d}2%-|bg4ShdQ;)Z|M z4K|tFv)qx*kKGKoyh!DQY<{n&UmAChq@DJrQP>EY7g1JF(ih*D8wCVWyQ z5Jj^|-NVFSh5T0vd1>hUvPV6?=`90^_)t(L9)XOW7jeP45NyA2lzOn&QAPTl&d#6P zSv%36uaN(9i9WlpcH#}rmiP#=L0q(dfhdxvFVaOwM;pY;KvNQ9wMyUKs6{d}29DZQ z{H3&Sosr6)9Z+C>Q5)iHSW~gGoWGgK-0;k~&dyr-bA3O|3PCNzgC?UKS_B=^i8Ri^ zd_*_qI4B07Cayq|p4{`U_E_P=K`N_~{F|+-+`sCgcNxs`%X!$=(?l2aAW}0M=~COb zf19oe^iuAUuDEf)4tgv<=WRPpK@IjToNNC*#&Ykw!)aqWU4h#|U@(cG_=Qx+&xt~a zvCz~Ds3F71dsjNLkfM%TqdVNu=RNMOzh7?b+%hICbFlOAPphrYy>7D-e7{%o_kPFn z;T!?ilE-LcKM0P(GKMseEeW57Vs`=FF}(y@^pQl;rL3fHs8icmA+!6YJt&8 ztSF?%Un35qkv>drkks&BNTJv~xK?vD;aBkp7eIkDYqn+G0%;sT4FcwAoO+vke{8CO z0d76sgg$CannW5T#q`z~L4id)9BCKRU0A!Z-{HpXr)QJrd9@iJB+l32Ql)Z}*v(St zE)Vp=BB=DDB4Pr}B(UHNe31<@!6d{U?XDoxJ@S)9QM)2L%SA0x^~^fb=bdsBy!uh& zU?M_^kvnt%FZzm+>~bEH{2o?v&Iogs`1t-b+Ml`J!ZPS(46YQJKxWE81O$HE5w;** z|8zM%bp`M7J8)4;%DqH`wVTmM0V@D}xd%tRE3_6>ioMJxyi5Hkb>85muF81&EY!73ei zA3e<#ug||EZJ=1GLXNJ)A z791&ge#lF;GVX6IU?iw0jX^1bYaU?+x{zPlpyX6zijyn*nEdZ$fxxkl!a-~*P3bkf zPd*pzu~3GBYkR_>ET`5UM^>>zTV>5m>)f=az{d0sg6a8VzUtXy$ZS?h#Gk-CA?7)c zI%Vu9DN6XSDQn6;?n9`>l$q&>s?K)R8*OsmI+$L_m z_~E`}w694Z*`Xk3Ne=497Si~=RWRqCM?6=88smrxle#s*W znwhTRsMRmg?37GLJ-)%nDZA7r$YG849j8mJWir1bWBy& zZPneYojSbooC8U@tkO`bWx4%E5*;p#Q^1^S3lsfy7(6A{jL0`A__0vm?>xC%1y8_m z57FfWr^@YG2I1K7MGYuYd>JC}@sT2n^rkrY3w%~$J$Y~HSoOHn?zpR$ zjLj_bq@Yj8kd~DXHh30KVbz@K)0S;hPKm+S&-o%IG+@x@MEcrxW2KFh;z^4dJDZix zGRGe&lQD$p)0JVF4NRgGYuh0bYLy)BCy~sbS3^b3 zHixT<%-Vwbht|25T{3^Hk;qZ^3s!OOgljHs+EIf~C%=_>R5%vQI4mQR9qOXThMXlU zS|oSH>0PjnCakb*js2{ObN`}%HYsT6=%(xA| znpUtG_TJ08kHgm5l@G|t?4E3tG2fq?wNtIp*Vqrb{9@bo^~Rx7+J&OnayrX`LDcF~ zd@0m0ZJ#Z@=T>4kTa5e2FjI&5c(F7S{gnRPoGpu9eIqrtSvnT_tk$8T)r%YwZw!gK zj*k@cG)V&@t+mtDi37#>LhVGTfRA^p%x0d#_P|Mktz3*KOoLIqFm`~KGoDDD4OOxe z?}ag_c08u%vu=5Vx=~uoS8Q;}+R2~?Uh|m-+`-2kDo$d6T!nD*hc#dB(*R{LXV=zo z`PJP0V=O!@3l-bw+d`X6(=@fq=4O#ETa8M^fOvO4qja9o3e8ANc9$sI=A4$zUut~w z4+JryRkI{9qWxU1CCMM$@Aj=6)P+z?vqa=UCv_4XyVNoBD{Xb~Oi4cjjhm8fRD!*U z2)zaS;AI78^Wq+5mDInKiMz|z#K`2emQfNH*U;{9^{NqSMVoq?RSo43<8YpJM^+W$ zxy!A5>5Zl16Vi#?nAYywu3w_=KWnd3*QetocWt`3pK67>)ZVwnT3h zbPdD&MZkD?q=-N`MpCCwpM74L+Tr1aa)zJ)8G;(Pg51@U&5W>aNu9rA`bh{vgfE={ zdJ>aKc|2Ayw_bop+dK?Y5$q--WM*+$9&3Q9BBiwU8L<-`T6E?ZC`mT0b}%HR*LPK} z!MCd_Azd{36?Y_>yN{U1w5yrN8q`z(Vh^RnEF+;4b|2+~lfAvPT!`*{MPiDioiix8 zY*GdCwJ{S(5(HId*I%8XF=pHFz<9tAe;!D5$Z(iN#jzSql4sqX5!7Y?q4_%$lH zz8ehZuyl0K=E&gYhlfFWabnSiGty$>md|PpU1VfaC5~kskDnZX&Yu}?-h;OSav=8u z=e3Yq=mi$4A|sB-J00;1d{Sd1+!v0NtU((Nz2;PFFlC}V{@p&4wGcVhU&nI($RAS! zwXn7)?8~1J3*4+VccRSg5JS<(bBhBM&{ELMD4C_NTpvzboH!{Zr*%HP;{UqxI#g&7 zOAqPSW5Qus$8-xtTvD%h{Tw<2!XR(lU54LZG{)Cah*LZbpJkA=PMawg!O>X@&%+5XiyeIf91n2E*hl$k-Y(3iW*E}Mz-h~H~7S9I1I zR#-j`|Hk?$MqFhE4C@=n!hN*o5+M%NxRqP+aLxDdt=wS6rAu6ECK*;AB%Nyg0uyAv zO^DnbVZZo*|Ef{nsYN>cjZC$OHzR_*g%T#oF zCky9HJS;NCi=7(07tQXq?V8I&OA&kPlJ_dfSRdL2bRUt;tA3yKZRMHMXH&#W@$l%-{vQd7y@~i*^qnj^`Z{)V$6@l&!qP_y zg2oOd!Wit#)2A~w-eqw3*Mbe)U?N|q6sXw~E~&$!!@QYX4b@%;3=>)@Z#K^`8~Aki z+LYKJu~Y$;F5%_0aF9$MsbGS9Bz2~VUG@i@3Fi2q(hG^+Ia44LrfSfqtg$4{%qBDM z_9-O#3V+2~W$dW0G)R7l_R_vw(KSkC--u&%Rs^Io&*?R=`)6BN64>6>)`TxyT_(Rd zUn+aIl1mPa#Jse9B3`!T=|e!pIp$(8ZOe0ao?nS7o?oKlj zypC-fMj1DHIDrh1unUI1vp=-Fln;I9e7Jvs3wj*^_1&W|X} zZSL|S|Bb@CV*YC_-T&2!Ht3b6?)d`tHOP?rA;;t#zaXa0Sc;vGnV0BLIf8f-r{QHh z*Zp`4_ItlOR7{u(K+!p_oLDmaAkNag*l4#29F2b_A*0oz0T|#-&f*;c#<`^)(W@gm z#k9k=t%u8<+C1fNUA{Fh7~wgPrEZZ#(6aBI%6bR4RO(e1(ZocjoDek4#MTgZD>1NG zy9~yoZfWYfwe&S-(zk4o6q6o?2*~DOrJ(%5wSnEJMVOKCzHd z=Yhm+HLzoDl{P*Ybro7@sk1!Ez3`hE+&qr7Rw^2glw^M(b(NS2!F|Q!mi|l~lF94o z!QiV)Q{Z>GO5;l1y!$O)=)got;^)%@v#B!ZEVQy1(BJApHr5%Zh&W|gweD+%Ky%CO ztr45vR*y(@*Dg_Qw5v~PJtm^@Lyh*zRuT6~(K+^HWEF{;R#L$vL2!_ndBxCtUvZ(_ zauI7Qq}ERUWjr&XW9SwMbU>*@p)(cuWXCxRK&?ZoOy>2VESII53iPDP64S1pl{NsC zD;@EGPxs&}$W1;P6BB9THF%xfoLX|4?S;cu@$)9OdFst-!A7T{(LXtdNQSx!*GUSIS_lyI`da8>!y_tpJb3Zuf0O*;2y?HCfH z5QT6@nL|%l3&u4;F!~XG9E%1YwF*Fgs5V&uFsx52*iag(?6O|gYCBY3R{qhxT-Etb zq(E%V=MgQnuDGEKOGsmBj9T0-nmI%zys8NSO>gfJT4bP>tI>|ol@ zDt(&SUKrg%cz>AmqtJKEMUM;f47FEOFc%Bbmh~|*#E zDd!Tl(wa)ZZIFwe^*)4>{T+zuRykc3^-=P1aI%0Mh}*x7%SP6wD{_? zisraq`Las#y-6{`y@CU3Ta$tOl|@>4qXcB;1bb)oH9kD6 zKym@d$ zv&PZSSAV1Gwwzqrc?^_1+-ZGY+3_7~a(L+`-WdcJMo>EWZN3%z4y6JyF4NR^urk`c z?osO|J#V}k_6*9*n2?j+`F{B<%?9cdTQyVNm8D}H~T}?HOCXt%r7#2hz97Gx#X%62hyaLbU z_ZepP0<`<;eABrHrJAc!_m?kmu#7j}{empH@iUIEk^jk}^EFwO)vd7NZB=&uk6JG^ zC>xad8X$h|eCAOX&MaX<$tA1~r|hW?-0{t4PkVygTc`yh39c;&efwY(-#;$W)+4Xb z$XFsdG&;@^X`aynAMxsq)J#KZXX!sI@g~YiJdHI~r z$4mj_?S29sIa4c$z)19JmJ;Uj?>Kq=0XuH#k#};I&-6zZ_&>)j>UR0XetRO!-sjF< zd_6b1A2vfi++?>cf}s{@#BvTD|a%{9si7G}T+8ZnwuA z1k8c%lgE<-7f~H`cqgF;qZ|$>R-xNPA$25N1WI3#n%gj}4Ix}vj|e=x)B^roGQpB) zO+^#nO2 zjzJ9kHI6nI5ni&V_#5> z!?<7Qd9{|xwIf4b0bRc;zb}V4>snRg6*wl$Xz`hRDN8laL5tg&+@Dv>U^IjGQ}*=XBnXWrwTy;2nX?<1rkvOs#u(#qJ=A zBy>W`N!?%@Ay=upXFI}%LS9bjw?$h)7Dry0%d}=v0YcCSXf9nnp0tBKT1eqZ-4LU` zyiXglKRX)gtT0VbX1}w0f2ce8{$WH?BQm@$`ua%YP8G@<$n13D#*(Yd5-bHfI8!on zf5q4CPdgJLl;BqIo#>CIkX)G;rh|bzGuz1N%rr+5seP${mEg$;uQ3jC$;TsR&{IX< z;}7j3LnV+xNn^$F1;QarDf6rNYj7He+VsjJk6R@0MAkcwrsq4?(~`GKy|mgkfkd1msc2>%B!HpZ~HOzj}kl|ZF(IqB=D6ZTVcKe=I7)LlAI=!XU?J*i#9VXeKeaG zwx_l@Z(w`)5Cclw`6kQKlS<;_Knj)^Dh2pL`hQo!=GPOMR0iqEtx12ORLpN(KBOm5 zontAH5X5!9WHS_=tJfbACz@Dnkuw|^7t=l&x8yb2a~q|aqE_W&0M|tI7@ilGXqE)MONI8p67OiQGqKEQWw;LGga=ZM1;{pSw1jJK_y$vhY6 ztFrV7-xf>lbeKH1U)j3R=?w*>(Yh~NNEPVmeQ8n}0x01$-o z2Jyjn+sXhgOz>AzcZ zAbJZ@f}MBS0lLKR=IE{z;Fav%tcb+`Yi*!`HTDPqSCsFr>;yt^^&SI2mhKJ8f*%ji zz%JkZGvOn{JFn;)5jf^21AvO-9nRzsg0&CPz;OEn07`CfT@gK4abFBT$Mu?8fCcscmRkK+ zbAVJZ~#_a z{|(FFX}~8d3;DW8zuY9?r#Dt>!aD>} zlYw>D7y#eDy+PLZ&XKIY&Df0hsLDDi(Yrq8O==d30RchrUw8a=Eex>Dd?)3+k=}Q> z-b85lun-V$I}86Vg#l1S@1%=$2BQD5_waAZKQfJ${3{b2SZ#w1u+jMr{dJMvI|Og= zpQ9D={XK|ggbe04zTUd}iF{`GO1dV%zWK~?sM9OM(= zVK9&y4F^w1WFW{$qi|xQk0F`@HG8oLI5|5$j~ci9xTMT69v5KS-Yym--raU5kn2#C z<~5q^Bf0rTXVhctG2%&MG(cUGaz(gC(rcG~>qgO$W6>!#NOVQJ;pIYe-lLy(S=HgI zPh;lkL$l+FfMHItHnw_^bj8}CKM19t(C_2vSrhX2$K@-gFlH};#C?1;kk&U1L%4S~ zR^h%h+O1WE7DI$~dly?-_C7>(!E`~#REJ~Xa7lyrB$T!`&qYV5QreAa^aKr%toUJR zPWh)J3iD`(P6BI5k$oE$us#%!4$>`iH2p-88?WV0M$-K)JDibvA4 zpef%_*txN$Ei3=Lt(BBxZ&mhl|mUz-z*OD1=r9nfN zc5vOMFWpi>K=!$6f{eb?5Ru4M3o;t9xLpry|C%j~`@$f)OFB5+xo8XM8g&US@UU-sB|dAoc20y(F@=-2Ggp_`SWjEb#>IG^@j zuQK}e^>So#W2%|-)~K!+)wdU#6l>w5wnZt2pRL5Dz#~N`*UyC9tYechBTc2`@(OI# zNvcE*+zZZjU-H`QOITK^tZwOyLo)ZCLk>>Wm+flMsr5X{A<|m`Y281n?8H_2Fkz5}X?i%Rfm5s+n`J zDB&->=U+LtOIJ|jdYXjQWSQZFEs>Rm{`knop4Sq)(}O_@gk{14y51)iOcGQ5J=b#e z2Yx^6^*F^F7q_m-AGFFgx5uqyw6_4w?yKCJKDGGprWyekr;X(!4CnM5_5?KgN=3qCm03 z##6k%kIU5%g!cCL(+aK>`Wd;dZ4h$h_jb7n?nqx5&o9cUJfr%h#m4+Bh)>HodKcDcsXDXwzJ3jR(sSFqWV(OKHC*cV8;;&bH=ZI0YbW3PgIHwTjiWy z?2MXWO2u0RAEEq(zv9e%Rsz|0(OKB?_3*kkXwHxEuazIZ7=JhaNV*P~hv57q55LoebmJpfHXA@yuS{Esg+ z*C}0V-`x^=0nOa@SPUJek>td~tJ{U1T&m)~`FLp*4DF77S^{|0g%|JIqd-=5)p6a` zpJOsEkKT(FPS@t^80V!I-YJbLE@{5KmVXjEq{QbCnir%}3 zB)-J379=wrBNK6rbUL7Mh^tVmQYn-BJJP=n?P&m-7)P#OZjQoK0{5?}XqJScV6>QX zPR>G{xvU_P;q!;S9Y7*07=Z!=wxIUorMQP(m?te~6&Z0PXQ@I=EYhD*XomZ^z;`Os z4>Uh4)Cg2_##mUa>i1Dxi+R~g#!!i{?SMj%9rfaBPlWj_Yk)lCV--e^&3INB>I?lu z9YXCY5(9U`3o?w2Xa5ErMbl5+pDVpu8v+KJzI9{KFk1H?(1`_W>Cu903Hg81vEX32l{nP2vROa1Fi!Wou0+ZX7Rp`g;B$*Ni3MC-vZ`f zFTi7}c+D)!4hz6NH2e%%t_;tkA0nfkmhLtRW%){TpIqD_ev>}#mVc)<$-1GKO_oK8 zy$CF^aV#x7>F4-J;P@tqWKG0|D1+7h+{ZHU5OVjh>#aa8+V;6BQ)8L5k9t`>)>7zr zfIlv77^`Fvm<)_+^z@ac%D&hnlUAFt8!x=jdaUo{)M9Ar;Tz5Dcd_|~Hl6CaRnK3R zYn${wZe8_BZ0l0c%qbP}>($jsNDay>8+JG@F!uV4F;#zGsBP0f$f3HqEHDz_sCr^q z1;1}7KJ9&`AX2Qdav1(nNzz+GPdEk5K3;hGXe{Hq13{)c zZy%fFEEH#nlJoG{f*M^#8yXuW%!9svN8ry-Vi7AOFnN~r&D`%6d#lvMXBgZkX^vFj z;tkent^62jUr$Cc^@y31Lka6hS>F?1tE8JW$iXO*n9CQMk}D*At3U(-W1E~z>tG?> z5f`5R5LbrhRNR8kv&5d9SL7ke2a*Xr)Qp#75 z6?-p035n2<7hK;sb>t9GAwG4{9v~iEIG>}7B5zcCgZhu$M0-z8?eUO^E?g)md^XT_ z2^~-u$yak>LBy(=*GsTj6p<>b5PO&un@5hGCxpBQlOB3DpsItKZRC*oXq-r{u}Wb; z&ko>#fbnl2Z;o@KqS-d6DTeCG?m1 z&E>p}SEc*)SD&QjZbs!Csjx~0+$@ekuzV_wAalnQvX3a^n~3ui)|rDO+9HW|JPEeBGP4 z)?zcZ<8qv47`EWA*_X~H^vr(lP|f%=%cWFM;u)OFHruKT<~?>5Y8l?56>&;=WdZU# zZEK4-C8s-3zPMA^&y~e*9z)!ZJghr3N^pJa2A$??Xqx-BR*TytGYor&l8Q+^^r%Yq02xay^f#;;wO6K7G!v>wRd6531WnDI~h$PN( z+4#08uX?r&zVKsQ;?5eBX=FxsXaGyH4Gth4a&L|{8LnNCHFr1M{KjJ!BfBS_aiy-E zxtmNcXq3}WTwQ7Dq-9YS5o758sT(5b`Sg-NcH>M9OH1oW6&sZ@|GYk|cJI`vm zO<$~q!3_$&GfWetudRc*mp8)M)q7DEY-#@8w=ItkApfq3sa)*GRqofuL7)dafznKf zLuembr#8gm*lIqKH)KMxSDqbik*B(1bFt%3Vv|ypehXLCa&wc7#u!cJNlUfWs8iQ` z$66(F=1fkxwg745-8_eqV>nWGY3DjB9gE23$R5g&w|C{|xvT@7j*@aZNB199scGchI7pINb5iyqYn)O=yJJX)Ca3&Ca+{n<=1w|(|f0)h<9gs$pVSV<<9Og-V z8ki@nKwE)x)^wmHBMk?mpMT=g{S#^8W|>&rI#Ceh;9za}io0k@0JxiCqi-jHlxbt3 zjJA?RihhRvhk6%G5-D{ePh1jare*fQS<328P-DcVAxPTrw=n6k?C6EV75f}cnBRPT zMYDqqKu(ND&aOtc!QRV`vzJSVxx8i~WB#5Ml{b#eQqNnSi7l-bS-`ITW<^zyYQA(b zbj4SuRK>q9o`_v%+C=S?h>2e4!66Ij(P5{7Uz$3u6YJJC$W%EoBa{-(=tQ|y1vov%ZkXVOV z##_UVg4V^4ne#4~<-1DkJqkKqgT+E_=&4Ue&eQ-JC+gi?7G@d6= zximz{zE)WW{b@QCJ!7l&N5x=dXS?$5RBU-VvN4Uec-GHK&jPa&P2z+qDdLhIB+HU) zu0CW&uLvE^4I5xtK-$+oe|58)7m6*PO%Xt<+-XEA%jG_BEachkF3e@pn?tl!`8lOF zbi2QOuNXX)YT*MCYflILO{VZ*9GiC%R4FO20zMK?p+&aCMm2oeMK7(aW=UDzr=AO0 z$5mJ%=qRsR8rZ>_YsL+vi{3*J_9Kzq(;ZwRj+4_f0-*wbkSMPWahX#Fj_a8BnrhJ6 zo^ZZ?Vah1@&6#r=JkuaYDBdp;J3@ii+CHM&@9*er&#P}$@wI$bfrH)&c!*|nkvhf%^*Y6b%dKz%QBSIo@U z{?V^qEs4`q<8@n+u8YiB^sc@6g>TncG<|GsmC3egwE6aO=EwLr~3-2 zNr`+)`i+-83?|1Xy0^8ps&pb}YT?w1eWVnC9Ps1=KM;Rw)bH6O!7Did1NwpnqVPZc z*%Qo~qkDL>@^<^fmIBtx$WUWQiNtAB2x-LO^BB=|w~-zTnJNEdm1Ou(?8PF&U88X@ z#8rdaTd||)dG^uJw~N_-%!XNbuAyh4`>Shea=pSj0TqP+w4!`nxsmVSv02kb`DBr% zyX=e>5IJ3JYPtdbCHvKMdhXUO_*E9jc_?se7%VJF#&ZaBD;7+eFN3x+hER7!u&`Wz z7zMvBPR4y`*$a250KYjFhAKS%*XG&c;R-kS0wNY1=836wL6q02mqx;IPcH(6ThA@2 zXKQF|9H>6AW$KUF#^A%l6y5{fel77_+cR_zZ0(7=6bmNXABv}R!B-{(E^O6Y?ZS)n zs1QEmh_Fm7p}oRyT3zxUNr4UV8NGs+2b8|4shO$OGFj3D&7_e?#yDi=TTe%$2QbG5 zk<;q7aQ;p!M-Osm{vFdmXZ@!z9uWh!;*%>(vTRggufuUGP9Hols@vhx z73pn$3u2;vzRvnXuT&$Os7J@6y12*j!{ix%3B4YU1466ItmJs0NsU(4ZYRYh7wEA6q{b*Hs6@k~ zi7Yq@Ax!et0cUMTvk7P%ym){MHpcliHEI~e3HP0NV=}7;xFv#IC?a<=`>~j_sk{e> z7vg-tK*p83HZ0=QK@ zRIHo^r{D8&Ms-^WZp+6US_Quqjh$Q66W^1}=Uz&XJ8AQE9&2}P zY|FXZzZ|0IiaBd2qdt6dIjQr(ZMIOU%NG1F&fu6Po9m^?BvLhI6T0R!H2d8;U(&p2 zYA|MFscMqcO(ye~Jp?F;0>Ke+5hzVr?aBNe>GsGgr$XrpS9uajN2kNQ3o$V5rp0T( z0$6TJC;3)26SNG#XcX7l^MKTn$ga?6r4Jzfb%ZgA(Zbwit0$kY=avSnI$@Gk%+^pu zS5mHrcRS8LFPC*uVWH4DDD1pY$H8N>X?KIJZuZ2SvTqc5Nr0GHdD8TCJcd$zIhOdC zZX0ErnsozQh;t^==4zTfrZO421AL?)O)l#GSxU#|LTTg4#&yeK=^w#;q63!Nv~1(@ zs^-RNRuF&qgcr+bIzc@7$h9L;_yjdifE*$j0Q&Np=1AuHL--zdkv@}`1 zo~LlDl_YAq*z?vmr4M`GjDkl9?p|-tl(DtX76oZv25_DtZutLS9Ez!5~p?th@4 zyc_uax4W#<(#)LMkvo)yp|5tKsC2=p#6PyhpH|449T<9Zdk|%CAb5cw?fhvQtBO&7 zpQ9$24yLqPHP;$N&fe2wm%8qdctwIna<3SwGtQA3{C77s%CW%LYxtK(SBGustL0<( zu~U9r0UOkr(c{OJxZS0Ntu3+cJlF7R`7k-Bsa&q?9Ae5{{|o~?cM+T7{lB1^#vT8R z?>c9fNWey`1dKDY%F3d2O*8^qYhjlB8*7HMKE<*=(A`{>=1%s1}Pm&#_t1xy!FkPk@%SMEka2@*= zxDuM|vJJ5s+xgDls{>*o!7eOcs|xuVBPWX&+y5vEiADK%hi`#Dbd>;;Pbk2H4*-X&R?_-6ZEutSd8hC+sSjhIo z;D(j4P;2EVpEj#UF7IjM6PC+X$C5T&=nL`*!*hm9U)#O?>wqOgC>jXKN3Slk_yaQX zLf|4D8T4k|wHW`;#ZQVocNF|3izi0sOqXzi7@KlYC3CXBG`94wD;tMI1bj|8Vm zY}9`VI9!plSfhAal$M_HlaYOVNU?9Z#0<$o?lXXbX3O(l_?f)i3_~r+GcO-x#+x^X zfsZl0>Rj2iP1rsT;+b;Mr? z4Vu&O)Q5ru4j;qaSP5gA{az@XTS1NpT0d9Xhl_FkkRpcEGA0(QQ~YMh#&zwDUkNzm z6cgkdgl9W{iL6ArJ1TQHqnQ^SQ1WGu?FT|93$Ba}mPCH~!$3}0Y0g zcoG%bdTd$bmBx9Y<`Jc+=Cp4}c@EUfjiz;Rcz101p z=?#i$wo>gBE9|szaZMt-d4nUIhBnYRuBVyx+p?5#aZQgUe(!ah`J#l1$%bl5avL27 zU2~@V`3Ic&!?FhDX@Cw!R4%xtWark#p8DLT)HCZ?VJxf^yr@AD*!ERK3#L$E^*Yr? zzN&uF9Roh4rP+r`Z#7U$tzl6>k!b~HgM$C<_crP=vC>6=q{j?(I}!9>g3rJU(&){o z`R^E*9%+kEa8H_fkD9VT7(Fks&Y-RcHaUJYf-|B+eMXMaRM;{FKRiTB>1(=Iij4k1(X__|WqAd-~t#2@UQ}Z&<1Th0azdXfoll!dd)6>1miA z!&=6sDJm=e$?L&06+Q3`D-HNSkK-3$3DdZMX-6Xjn;wd#9A{~ur!2NcX>(qY_oZL0~H7dnQ9sgLe!W>~2|RSW7|hWn<({Pg*xF$%B-!rKe^_R_vc z(LO!0agxxP;FWPV({8#lEv$&&GVakGus=@!3YVG`y^AO1m{2%Np;>HNA1e{=?ra1C}H zAwT0sbwG|!am;fl?*_t^^#yLDXZ*Nx)_FqueZi0c-G~omtpHW0Cu)mEJ`Z1X8brq$ z%vK##b~o*^b&Hz!hgrD=^6P8}aW40lhzMLB5T5*v`1QH?+L~-@CDi3+C@nRf2{7UE zyDIe{@LKw`Eu=Z%6<<_=#V|yxJIKiq_N?ZJ_v0$c)N4l07ZV_mIXG}glfBSPivOhw z-~+9GdckSpMBNR9eR`Y|9_)sXS+u_OiQ%!9rE(2AFjoxN8lk16Sb~^Sq6kRoEp3yD(mm`HsYIXcag_EAB8MHc}nahxVVUTts~U9P|f;7Ul$_` zStR4v&P4q_$KXOEni$lkxy8=9w8G&47VY0oDb^+jT+>ARe3NHUg~St`$RDxY)?;_F znqTujR&chZd2qHF7y8D$4&E3+e@J~!X3&BW4BF(Ebp#TEjrd+9SU!)j;qH+ZkL@AW z?J6Mj}v0_+D zH0qlbzCkHf|EZ`6c>5ig5NAFF%|La%M-}g(7&}Vx8K)qg30YD;H!S!??{;YivzrH0 z(M%2*b_S-)yh&Aiqai)GF^c!<1Xemj|13>dZ_M#)41SrP;OEMaRJ)bCeX*ZT7W`4Y zQ|8L@NHpD@Tf(5>1U(s5iW~Zdf7$@pAL`a3X@YUv1J>q-uJ_(Dy5nYTCUHC}1(dlI zt;5>DLcHh&jbysqt?G01MhXI3!8wgf){Hv}=0N|L$t8M#L7d6WscO8Om2|NBz2Ga^ zs86y%x$H18)~akOWD7@em7)ldlWgb?_sRN>-EcYQO_}aX@+b$dR{146>{kXWP4$nN{V0_+|3{Lt|8uX_fhKh~i{(x%cj*PU$i{PO(5$uA? zQzO>a6oPj-TUk&{zq?JD2MNb6Mf~V3g$ra+PB;ujLJ2JM(a7N*b`y{MX--!fAd}5C zF$D_b8S;+Np(!cW)(hnv5b@@|EMt*RLKF*wy>ykFhEhlPN~n_Bj>LT9B^_yj>z#fx z3JuE4H&?Cc!;G@}E*3k`HK#8ag`yE3Z1)5JUlSua%qkF zkTu|<9{w9OSi$qr)WD#7EzITnch=xnR63E*d~WGvi*Co9BBE?ETHud;!Z)7&wz+l6 zuKODYG1>I1U#a%&(GNJ`AqRfg=H!BtSl+_;CEeufF-#+*2EMMz-22@>18=8PH{PHd z);mN=aR0MPF>eutLiS#-AOX>#2%+pTGEOj!j4L(m0~&xR=0+g#HNpno6@veLhJp}e zyNVC$a>4;!9&iGvU_dj&xbKt@^t6r%f^)+}eV^suRTLP52+BVs0kOLwg6n`=NUv50E7My8XQUh?y%mW62OT1pMrKI3Q(r`7vU&@93=G~A?b(^pvC-8x=bSk zZ60BQR96WB1Z@9Df(M1IQh+YrU8sEjB=Tc2;(zBn-pete*icZE|M&Uc+oHg`|1o`g zH~m+k=D$o);{Rs)b<9Zo|9_Z6L6QHLNki(N>Dw^^i1LITprZeeqIaT#+)fw)PlllU zldphHC)t!0Gf(i9zgVm>`*TbmITF zH1FZ4{wrjRCx{t^26VK_2srZuWuY*EMAsMrJYFFCH35Ky7bq8<0K|ey2wHnrFMZyr z&^yEgX{{3i@&iE5>xKZ{Ads36G3a!i50D!C4?^~cLB<<|fc1!XN(HJRM)H^21sEs%vv+Mu0h*HkLHaEffMwc0n6)JhNXY#M5w@iO@dfXY z0c6dM2a4Hd1SA*#qYj@jK}uVgAZdaBj8t6uuhUNe>)ne9vfd#C6qLV9+@Q7{MnF#0 zJ7fd-ivG_~u3bVvOzpcw1u~ZSp8-kl(sunnX>L~*K-ByWDM2E8>;Si6kn^58AZQxI xVa^It*?521mj4+UJO?7%w*+`EfEcU=@KhDx-s^WzP+ae~{CgHDE&XryzW}Nww%-5% diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 15de902..a870778 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,3 +1,4 @@ +#Sun Aug 20 16:53:32 CST 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 6b5a392..0000000 --- a/settings.gradle +++ /dev/null @@ -1,6 +0,0 @@ -rootProject.name = 'hamster-ball' -include 'ball-common' -include 'ball-bukkit' -include 'ball-bungeecord' -include 'ball-server' - diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..e4d5a6a --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,4 @@ +rootProject.name = "hamster-ball" +include("ball-bukkit") +include("ball-common") +include("ball-bungeecord")