11 Commits

Author SHA1 Message Date
d16602a06d build: 在 Velocity 中引入 relocate 后的 netty,防止依赖版本冲突
All checks were successful
Publish Project / build (push) Successful in 5m3s
2025-07-10 20:36:26 +08:00
ad216ef89b feat: 在插件卸载时关闭 AudienceProvider 2025-07-06 05:57:57 +08:00
68747f76bb feat: 为 BungeeCord 添加完整的 netty 并重定向
All checks were successful
Publish Project / build (push) Successful in 1m26s
2025-07-03 03:52:50 +08:00
1c83615b7e docs: 修正文档
[skip ci]
2025-07-03 02:33:16 +08:00
1648a56453 feat: 添加 lettuce-core 用于连接 redis
All checks were successful
Publish Project / build (push) Successful in 2m43s
2025-07-03 02:29:02 +08:00
0f942a7687 perf: 优化插件更新检测器 2025-06-16 02:49:28 +08:00
942239be91 ci: 添加自动构建脚本 2025-06-16 02:49:21 +08:00
b393ab96ea feat: 更新版本号 2025-03-11 08:21:55 +08:00
cb736bbbf1 Merge branch 'refs/heads/dev' 2025-03-11 08:21:41 +08:00
c30e468635 fix(core-bukkit): 修复指令参数数量判断错误 2025-03-11 08:21:30 +08:00
571dd34bbd fix(core-bukkit): 修复信息模式开关时消息显示错误 2025-02-12 02:18:05 +08:00
20 changed files with 178 additions and 54 deletions

View File

@@ -0,0 +1,29 @@
name: Publish Project
on:
push:
tags:
- "*"
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
java-version: 21
distribution: temurin
cache: gradle
cache-dependency-path: gradle/wrapper/gradle-wrapper.properties
- name: Build Project
env:
ORG_GRADLE_PROJECT_MAVEN_AIRGAME_USERNAME: ${{ secrets.MAVEN_AIRGAME_USERNAME }}
ORG_GRADLE_PROJECT_MAVEN_AIRGAME_PASSWORD: ${{ secrets.MAVEN_AIRGAME_PASSWORD }}
run: chmod +x gradlew && ./gradlew build publish --no-daemon
- name: Publish to Release
uses: softprops/action-gh-release@v2
with:
files: build/*.jar

View File

@@ -9,8 +9,6 @@
3. 命令行窗口中执行`./gradlew clean build`
4. 构建成品在 `build` 文件夹
也可访问我的[Jenkins网站](https://jenkins.airgame.net/job/opensource/job/hamster-core/)获取最新版
# 开发
## 添加依赖
@@ -26,9 +24,9 @@ repositories {
dependencies {
// 对于 Bukkit 插件
compileOnly("cn.hamster3.mc.plugin:core-bukkit:1.3.4-SNAPSHOT")
compileOnly("cn.hamster3.mc.plugin:core-bukkit:1.4.1")
// 对于 BungeeCord 插件
compileOnly("cn.hamster3.mc.plugin:core-bungee:1.3.4-SNAPSHOT")
compileOnly("cn.hamster3.mc.plugin:core-bungee:1.4.1")
}
```
@@ -54,13 +52,13 @@ dependencies {
<dependency>
<groupId>cn.hamster3.mc.plugin</groupId>
<artifactId>core-bukkit</artifactId>
<version>1.3.4-SNAPSHOT</version>
<version>1.4.1</version>
</dependency>
<!--对于 BungeeCord 插件-->
<dependency>
<groupId>cn.hamster3.mc.plugin</groupId>
<artifactId>core-bungee</artifactId>
<version>1.3.4-SNAPSHOT</version>
<version>1.4.1</version>
</dependency>
</dependencies>
</project>
@@ -74,10 +72,4 @@ dependencies {
- 使用方法为:`CoreAPI.getInstance().xxx()`
- 部分通用的工具代码在 `cn.hamster3.mc.plugin.core.common.util` 包中
- 部分Bukkit的工具代码在 `cn.hamster3.mc.plugin.core.bukkit.util` 包中
- 部分BungeeCord的工具代码在 `cn.hamster3.mc.plugin.core.bungee.util` 包中
# 已知问题
部分 Windows 服务器在启动时偶尔会遇到 Redis 链接失败的问题
如果服务器启动时遇到报错 `Caused by: java.io.IOException: Unable to establish loopback connection`
可以通过在启动脚本中添加 `-Djava.net.preferIPv4Stack=true` 参数解决
- 部分BungeeCord的工具代码在 `cn.hamster3.mc.plugin.core.bungee.util` 包中

View File

@@ -1,18 +1,18 @@
plugins {
id("java-library")
id("maven-publish")
id("com.github.johnrengelman.shadow") version "8+"
id("com.gradleup.shadow") version "8.3.6"
}
group = "cn.hamster3.mc.plugin"
version = "1.3.4-SNAPSHOT"
version = "1.4.1"
description = "叁只仓鼠的 Minecraft 插件开发通用工具包"
subprojects {
apply {
plugin("java-library")
plugin("maven-publish")
plugin("com.github.johnrengelman.shadow")
plugin("com.gradleup.shadow")
}
group = rootProject.group
@@ -64,10 +64,9 @@ subprojects {
repositories {
maven {
url = uri("https://maven.airgame.net/public")
credentials {
username = rootProject.properties.getOrDefault("maven_username", "").toString()
password = rootProject.properties.getOrDefault("maven_password", "").toString()
username = findProperty("MAVEN_AIRGAME_USERNAME")?.toString() ?: ""
password = findProperty("MAVEN_AIRGAME_PASSWORD")?.toString() ?: ""
}
}
}

View File

@@ -1,6 +1,9 @@
@file:Suppress("VulnerableLibrariesLocal")
evaluationDependsOn(":core-common")
evaluationDependsOn(":core-relocate-lettuce")
val shade = configurations.create("shade")
dependencies {
api(project(":core-common")) { isTransitive = false }
@@ -10,26 +13,31 @@ dependencies {
compileOnly("org.black_ixx:playerpoints:3.2.6") { isTransitive = false }
compileOnly("me.clip:placeholderapi:2.11.5") { isTransitive = false }
// https://www.spigotmc.org/resources/nbt-api.7939/
implementation("de.tr7zw:item-nbt-api:2.15.1")
api("net.kyori:adventure-platform-bukkit:4.3.2") {
exclude(group = "org.jetbrains")
exclude(group = "com.google.code.gson")
exclude(group = "io.netty")
}
api("net.kyori:adventure-text-minimessage:4.15.0") {
exclude(module = "adventure-api")
exclude(group = "org.jetbrains")
exclude(group = "io.netty")
}
// https://mvnrepository.com/artifact/redis.clients/jedis
implementation("com.zaxxer:HikariCP:4.0.3") { exclude(group = "org.slf4j") }
api("redis.clients:jedis:5.1.4") {
exclude(group = "com.google.code.gson")
exclude(group = "org.slf4j")
}
// https://www.spigotmc.org/resources/nbt-api.7939/
implementation("de.tr7zw:item-nbt-api:2.13.2")
// https://mvnrepository.com/artifact/com.zaxxer/HikariCP
implementation("com.zaxxer:HikariCP:4.0.3") { exclude(group = "org.slf4j") }
compileOnlyApi("io.lettuce:lettuce-core:6.7.1.RELEASE") {
exclude(group = "org.slf4j")
exclude(group = "io.netty")
}
shade(project(":core-relocate-lettuce"))
}
tasks {
processResources {
filesMatching("plugin.yml") {
@@ -40,6 +48,13 @@ tasks {
archiveBaseName = "HamsterCore-Bukkit"
}
shadowJar {
dependsOn(":core-relocate-lettuce:shadowJar")
val task = project(":core-relocate-lettuce").tasks.shadowJar.get()
from(task.outputs.files.map {
if (it.isDirectory) it else zipTree(
it
)
})
destinationDirectory = rootProject.layout.buildDirectory
relocate("de.tr7zw.changeme.nbtapi", "cn.hamster3.mc.plugin.core.lib.de.tr7zw.nbtapi")
relocate("de.tr7zw.annotations", "cn.hamster3.mc.plugin.core.lib.de.tr7zw.nbtapi.annotations")

View File

@@ -130,8 +130,11 @@ public class HamsterCorePlugin extends JavaPlugin {
@Override
public void onDisable() {
long start = System.currentTimeMillis();
audienceProvider.close();
simpleLogger.info("已关闭 AudienceProvider.");
if (CoreAPI.getInstance().isEnableRedis()) {
CoreAPI.getInstance().getJedisPool().close();
CoreAPI.getInstance().getRedisClient().close();
simpleLogger.info("已关闭 Redis 连接池");
}
if (CoreAPI.getInstance().isEnableDatabase()) {

View File

@@ -113,15 +113,13 @@ public abstract class ParentCommand extends ChildCommand {
}
public void sendHelp(@NotNull CommandSender sender) {
sender.sendMessage("§e==================== [ " + getName() + " 使用帮助] ====================");
sender.sendMessage("§2§l<<< 命令 [" + getUsage() + "] 帮助 >>>");
Map<String, String> map = getCommandHelp(sender);
int maxLength = map.keySet().stream()
.map(String::length)
.max(Integer::compareTo)
.orElse(-1);
for (Map.Entry<String, String> entry : map.entrySet()) {
sender.sendMessage(String.format("§a%-" + maxLength + "s - %s", entry.getKey(), entry.getValue()));
sender.sendMessage(" §3" + entry.getKey());
sender.sendMessage(" §7" + entry.getValue());
}
sender.sendMessage("§2§l<<< 由插件 " + getPlugin().getName() + "-" + getPlugin().getDescription().getVersion() + " 提供 >>>");
}
@Override
@@ -142,7 +140,7 @@ public abstract class ParentCommand extends ChildCommand {
CoreMessage.COMMAND_NOT_HAS_PERMISSION.show(sender);
return true;
}
if (args.length - 1 < getArgumentCount()) {
if (args.length - 1 < childCommand.getArgumentCount()) {
sender.sendMessage(getUsage() + " " + childCommand.getUsage());
return true;
}

View File

@@ -69,9 +69,9 @@ public class InfoModeCommand extends ChildCommand {
}
if (DebugListener.INFO_MODE_PLAYERS.contains(uuid)) {
CoreMessage.COMMAND_DEBUG_INFO_MODE_OFF.show(player);
} else {
CoreMessage.COMMAND_DEBUG_INFO_MODE_ON.show(player);
} else {
CoreMessage.COMMAND_DEBUG_INFO_MODE_OFF.show(player);
}
return true;
}

View File

@@ -4,7 +4,8 @@ import cn.hamster3.mc.plugin.core.bukkit.HamsterCorePlugin;
import cn.hamster3.mc.plugin.core.bukkit.command.ParentCommand;
import cn.hamster3.mc.plugin.core.bukkit.constant.CoreMessage;
import cn.hamster3.mc.plugin.core.bukkit.util.CoreBukkitUtils;
import de.tr7zw.changeme.nbtapi.*;
import de.tr7zw.changeme.nbtapi.NBT;
import de.tr7zw.changeme.nbtapi.NBTType;
import de.tr7zw.changeme.nbtapi.iface.ReadWriteNBT;
import de.tr7zw.changeme.nbtapi.iface.ReadWriteNBTCompoundList;
import de.tr7zw.changeme.nbtapi.iface.ReadWriteNBTList;

View File

@@ -12,7 +12,6 @@ UPDATE_CHECKER:
CHECK_TYPE: GITEA_RELEASES
GIT_BASE_URL: https://git.airgame.net
GIT_REPO: MiniDay/hamster-core
DOWNLOAD_URL: https://jenkins.airgame.net/job/opensource/job/hamster-core/
load: STARTUP

View File

@@ -1,6 +1,9 @@
@file:Suppress("VulnerableLibrariesLocal")
evaluationDependsOn(":core-common")
evaluationDependsOn(":core-relocate-lettuce")
val shade = configurations.create("shade")
dependencies {
api(project(":core-common")) { isTransitive = false }
@@ -14,13 +17,17 @@ dependencies {
exclude(module = "adventure-api")
exclude(group = "org.jetbrains")
}
// https://mvnrepository.com/artifact/redis.clients/jedis
implementation("com.zaxxer:HikariCP:4.0.3") { exclude(group = "org.slf4j") }
api("redis.clients:jedis:5.1.4") {
exclude(group = "com.google.code.gson")
exclude(group = "org.slf4j")
}
implementation("com.zaxxer:HikariCP:4.0.3") { exclude(group = "org.slf4j") }
compileOnlyApi("io.lettuce:lettuce-core:6.7.1.RELEASE") {
exclude(group = "org.slf4j")
exclude(group = "io.netty")
}
shade(project(":core-relocate-lettuce"))
}
tasks {
@@ -33,6 +40,13 @@ tasks {
archiveBaseName = "HamsterCore-BungeeCord"
}
shadowJar {
dependsOn(":core-relocate-lettuce:shadowJar")
val task = project(":core-relocate-lettuce").tasks.shadowJar.get()
from(task.outputs.files.map {
if (it.isDirectory) it else zipTree(
it
)
})
destinationDirectory = rootProject.layout.buildDirectory
}
}

View File

@@ -70,8 +70,11 @@ public class HamsterCorePlugin extends Plugin {
@Override
public void onDisable() {
long start = System.currentTimeMillis();
audienceProvider.close();
simpleLogger.info("已关闭 AudienceProvider.");
if (CoreAPI.getInstance().isEnableRedis()) {
CoreAPI.getInstance().getJedisPool().close();
CoreAPI.getInstance().getRedisClient().close();
simpleLogger.info("已关闭 Redis 连接池");
}
if (CoreAPI.getInstance().isEnableDatabase()) {

View File

@@ -11,4 +11,3 @@ UPDATE_CHECKER:
CHECK_TYPE: GITEA_RELEASES
GIT_BASE_URL: https://git.airgame.net
GIT_REPO: MiniDay/hamster-core
DOWNLOAD_URL: https://jenkins.airgame.net/job/opensource/job/hamster-core/

View File

@@ -17,14 +17,18 @@ dependencies {
exclude(group = "com.google.code.gson")
}
// https://mvnrepository.com/artifact/com.zaxxer/HikariCP
compileOnly("com.zaxxer:HikariCP:4.0.3") { isTransitive = false }
// https://mvnrepository.com/artifact/redis.clients/jedis
compileOnlyApi("redis.clients:jedis:5.1.4") {
exclude(group = "com.google.code.gson")
exclude(group = "org.slf4j")
}
// https://mvnrepository.com/artifact/com.zaxxer/HikariCP
compileOnly("com.zaxxer:HikariCP:4.0.3") { isTransitive = false }
// https://mvnrepository.com/artifact/io.lettuce/lettuce-core
compileOnlyApi("io.lettuce:lettuce-core:6.7.1.RELEASE") {
exclude(group = "io.netty")
exclude(group = "org.slf4j")
}
}
tasks {

View File

@@ -6,6 +6,9 @@ import cn.hamster3.mc.plugin.core.common.util.CoreUtils;
import cn.hamster3.mc.plugin.core.common.util.SimpleLogger;
import com.google.gson.Gson;
import com.zaxxer.hikari.HikariDataSource;
import io.lettuce.core.RedisClient;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.pubsub.StatefulRedisPubSubConnection;
import lombok.Getter;
import net.kyori.adventure.platform.AudienceProvider;
import org.jetbrains.annotations.NotNull;
@@ -41,6 +44,11 @@ public abstract class CoreAPI {
*/
@Nullable
private JedisPool jedisPool;
/**
* Lettuce Redis 连接池
*/
@Nullable
private RedisClient redisClient;
/**
* 公用数据库连接池
*/
@@ -57,6 +65,7 @@ public abstract class CoreAPI {
if (enableRedis) {
logger.info("正在创建 Redis 连接池");
jedisPool = new JedisPool(config.getString("redis-url"));
redisClient = RedisClient.create(config.getString("redis-url"));
logger.info("Redis 连接池创建完成");
} else {
logger.info("未启用 Redis 功能");
@@ -108,6 +117,24 @@ public abstract class CoreAPI {
throw new IllegalStateException("仓鼠核心未启用 Redis 功能");
}
@NotNull
public RedisClient getRedisClient() {
if (redisClient != null) {
return redisClient;
}
throw new IllegalStateException("仓鼠核心未启用 Redis 功能");
}
@NotNull
public StatefulRedisConnection<String, String> getRedisConnect() {
return getRedisClient().connect();
}
@NotNull
public StatefulRedisPubSubConnection<String, String> getRedisPubSub() {
return getRedisClient().connectPubSub();
}
@NotNull
public abstract SimpleLogger getLogger();

View File

@@ -15,6 +15,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
@@ -45,6 +46,9 @@ public final class UpdateCheckUtils {
switch (checkType) {
case "GITEA_RELEASES": {
lastRelease = getGiteaLastRelease(baseUrl, gitRepo, gitToken);
if (downloadUrl == null) {
downloadUrl = URI.create(baseUrl).resolve(gitRepo).toString();
}
break;
}
case "GITLAB_RELEASES": {
@@ -53,6 +57,9 @@ public final class UpdateCheckUtils {
break;
}
lastRelease = getGitlabLastRelease(baseUrl, projectID, gitToken);
if (downloadUrl == null) {
downloadUrl = URI.create(baseUrl).resolve(gitRepo).toString();
}
break;
}
}
@@ -104,7 +111,6 @@ public final class UpdateCheckUtils {
JsonArray array = JSON_PARSER.parse(reader).getAsJsonArray();
for (JsonElement element : array) {
JsonObject object = element.getAsJsonObject();
//noinspection SpellCheckingInspection
if (object.get("prerelease").getAsBoolean()) {
continue;
}

View File

@@ -0,0 +1,21 @@
// lettuce-core 需要使用 netty但低版本 bukkit 服务端核心自带的 netty 版本 lettuce 需要的版本有冲突
// 所以必须 shadow 并 relocate 依赖 io.netty才能保证两个版本互不冲突
// 但由于 core-bukkit 同时也 shadow 了 adventure-platform-bukkit且 adventure 内部通过反射调用 netty
// 如果在 core-bukkit 中直接 relocate netty会导致 adventure 的反射调用也指向 relocate 后的 netty
// 此时会导致 shadow 后的 adventure 与服务端中其他同样需要使用 netty 的插件关联时发生冲突
// 例如:服务端上安装了 ViaVersion 时core-bukkit 内置的 adventure 将会与其发生冲突
// 因为 adventure 使用 relocate 之后的类路径ViaVersion 在将类转换成原版 netty 类时,会发生 ClassCastException
// 所以只能先将 lettuce-core 与其需要的 netty 一起打包,并 relocate netty然后再使 core-bukkit 依赖这个打包后的版本
// 才能使得 adventure 与 lettuce-core 正常运行
dependencies {
implementation("io.lettuce:lettuce-core:6.7.1.RELEASE") {
exclude(group = "org.slf4j")
}
}
tasks {
shadowJar {
relocate("io.netty", "cn.hamster3.mc.plugin.core.lib.io.netty")
}
}

View File

@@ -1,6 +1,9 @@
@file:Suppress("VulnerableLibrariesLocal")
evaluationDependsOn(":core-common")
evaluationDependsOn(":core-relocate-lettuce")
val shade = configurations.create("shade")
dependencies {
api(project(":core-common")) { isTransitive = false }
@@ -9,17 +12,19 @@ dependencies {
compileOnlyApi("net.kyori:adventure-platform-api:4.3.2") { isTransitive = false }
// https://mvnrepository.com/artifact/redis.clients/jedis
implementation("com.zaxxer:HikariCP:5.1.0") { isTransitive = false }
api("redis.clients:jedis:5.1.4") {
exclude(group = "com.google.code.gson")
exclude(group = "org.slf4j")
}
// https://mvnrepository.com/artifact/com.zaxxer/HikariCP
implementation("com.zaxxer:HikariCP:5.1.0") { isTransitive = false }
compileOnlyApi("io.lettuce:lettuce-core:6.7.1.RELEASE") {
exclude(group = "org.slf4j")
exclude(group = "io.netty")
}
shade(project(":core-relocate-lettuce"))
// https://mvnrepository.com/artifact/com.mysql/mysql-connector-j
runtimeOnly("com.mysql:mysql-connector-j:8.3.0")
runtimeOnly("com.mysql:mysql-connector-j:8.4.0")
}
sourceSets.create("templates") {
@@ -49,6 +54,13 @@ tasks {
archiveBaseName = "HamsterCore-Velocity"
}
shadowJar {
dependsOn(":core-relocate-lettuce:shadowJar")
val task = project(":core-relocate-lettuce").tasks.shadowJar.get()
from(task.outputs.files.map {
if (it.isDirectory) it else zipTree(
it
)
})
destinationDirectory = rootProject.layout.buildDirectory
}
}

View File

@@ -4,6 +4,7 @@ import cn.hamster3.mc.plugin.core.common.api.CoreAPI;
import cn.hamster3.mc.plugin.core.common.config.YamlConfig;
import cn.hamster3.mc.plugin.core.common.util.UpdateCheckUtils;
import cn.hamster3.mc.plugin.core.velocity.api.CoreVelocityAPI;
import cn.hamster3.mc.plugin.core.velocity.impl.AudienceProviderImpl;
import cn.hamster3.mc.plugin.core.velocity.util.VelocitySimpleLogger;
import com.google.inject.Inject;
import com.velocitypowered.api.event.PostOrder;
@@ -83,8 +84,11 @@ public class HamsterCorePlugin {
@Subscribe(order = PostOrder.LAST)
public void onProxyShutdown(ProxyShutdownEvent event) {
long start = System.currentTimeMillis();
AudienceProviderImpl.INSTANCE.close();
simpleLogger.info("已关闭 AudienceProvider.");
if (CoreAPI.getInstance().isEnableRedis()) {
CoreAPI.getInstance().getJedisPool().close();
CoreAPI.getInstance().getRedisClient().close();
simpleLogger.info("已关闭 Redis 连接池");
}
if (CoreAPI.getInstance().isEnableDatabase()) {

View File

@@ -2,4 +2,3 @@ VERSION: ${version}
CHECK_TYPE: GITEA_RELEASES
GIT_BASE_URL: https://git.airgame.net
GIT_REPO: MiniDay/hamster-core
DOWNLOAD_URL: https://jenkins.airgame.net/job/opensource/job/hamster-core/

View File

@@ -1,8 +1,6 @@
pluginManagement {
repositories {
maven {
url = uri("https://maven.airgame.net/maven-public/")
}
maven("https://maven.airgame.net/maven-public/")
}
}
rootProject.name = "hamster-core"
@@ -10,3 +8,4 @@ include("core-common")
include("core-bukkit")
include("core-bungee")
include("core-velocity")
include("core-relocate-lettuce")