From d2d351b87aed74e963e4aa27022be35d760a9885 Mon Sep 17 00:00:00 2001 From: BaizhouziYou <111175934+BaizhouziYou@users.noreply.github.com> Date: Fri, 2 Oct 2026 19:47:03 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat(mod):=20=E6=B7=BB=E5=8A=A0=E8=BF=9B?= =?UTF-8?q?=E9=80=80=E6=9C=8D=E5=8F=8A=E6=AD=BB=E4=BA=A1=E6=92=AD=E6=8A=A5?= =?UTF-8?q?=E5=86=B7=E5=8D=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为 Fabric、NeoForge 和 Forge 接入统一的播报冷却逻辑 - 添加进服、退服和死亡冷却配置,默认关闭 - 按玩家和事件类型计时,死亡播报进一步区分伤害类型 - 补充 README 配置说明 --- README.md | 21 +++++++++++++++++- .../easybot/config/EasyBotConfig.java | 3 +++ .../features/LoginEventSyncFeature.java | 5 +++++ .../features/PlayerDeathSyncFeature.java | 5 +++++ .../features/LoginEventSyncFeature.java | 5 +++++ .../features/PlayerDeathSyncFeature.java | 4 ++++ .../features/LoginEventSyncFeature.java | 5 +++++ .../features/PlayerDeathSyncFeature.java | 5 +++++ .../easybot/utils/SyncCooldown.java | 22 +++++++++++++++++++ src/main/resources/config.json | 5 ++++- 10 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/springwater/easybot/utils/SyncCooldown.java diff --git a/README.md b/README.md index f860d2f..024ba7b 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,25 @@ EasyBot的Minecraft模组实现端,基于[Stonecutter](https://stonecutter.kikug - 使用 Gradle 任务中的 `"Set active project to ..."` 来更新 `src/` 目录下类文件可用的 Minecraft 版本。 - 使用 `buildAndCollect` Gradle 任务将模组发布文件存储在 `build/libs/` 目录。 +## 播报冷却 + +在 `config/easybot/config.json` 的 `sync` 中配置冷却秒数,例如: + +```json +"sync": { + "chatImageSupport": true, + "joinCooldownSeconds": 60, + "quitCooldownSeconds": 60, + "deathCooldownSeconds": 60 +} +``` + +- 三项默认均为 `0`,`0` 或负数表示关闭;旧配置无需修改。 +- 进服、退服按玩家 UUID 分别计时;死亡按玩家 UUID 和伤害类型分别计时。 +- 首次正常播报,冷却中的重复事件不延长时间;切换死因不会清除原死因的冷却。 +- 只跳过发往主程序的对应播报,不取消游戏事件或改变登录验证。`skipOptions` 仍优先生效。 +- 修改某项时长并重载后,该类事件下次发生时清空原冷却;冷却不写入磁盘,重启后重新计时。 + ## 实用链接 - [Stonecutter 新手指南](https://stonecutter.kikugie.dev/wiki/start/):*提示:您必须理解其运作原理!* @@ -15,4 +34,4 @@ EasyBot的Minecraft模组实现端,基于[Stonecutter](https://stonecutter.kikug ## 特别说明 -不要使用IDE的全局代码优化功能,由于框架特性 部分“未使用”的代码,只会在实际打包时被引用,你应该选择性忽视他们 (如果你觉得烦可以加上if注释来消除这个警告,目前还没有做优化) \ No newline at end of file +不要使用IDE的全局代码优化功能,由于框架特性 部分“未使用”的代码,只会在实际打包时被引用,你应该选择性忽视他们 (如果你觉得烦可以加上if注释来消除这个警告,目前还没有做优化) diff --git a/src/main/java/com/springwater/easybot/config/EasyBotConfig.java b/src/main/java/com/springwater/easybot/config/EasyBotConfig.java index 4a43392..07dd971 100644 --- a/src/main/java/com/springwater/easybot/config/EasyBotConfig.java +++ b/src/main/java/com/springwater/easybot/config/EasyBotConfig.java @@ -84,5 +84,8 @@ public static class Event{ @ToString public static class Sync { private boolean chatImageSupport = true; + private int joinCooldownSeconds = 0; + private int quitCooldownSeconds = 0; + private int deathCooldownSeconds = 0; } } \ No newline at end of file diff --git a/src/main/java/com/springwater/easybot/platforms/fabric/features/LoginEventSyncFeature.java b/src/main/java/com/springwater/easybot/platforms/fabric/features/LoginEventSyncFeature.java index 4c90e1e..0ec214b 100644 --- a/src/main/java/com/springwater/easybot/platforms/fabric/features/LoginEventSyncFeature.java +++ b/src/main/java/com/springwater/easybot/platforms/fabric/features/LoginEventSyncFeature.java @@ -8,9 +8,12 @@ import com.springwater.easybot.utils.CarpetUtils; import com.springwater.easybot.threading.EasyBotNetworkingThreadPool; import com.springwater.easybot.utils.PlayerUtils; +import com.springwater.easybot.utils.SyncCooldown; import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; public class LoginEventSyncFeature implements IEasyBotFeatures { + private final SyncCooldown joinCooldown = new SyncCooldown(); + private final SyncCooldown quitCooldown = new SyncCooldown(); @Override public void register() { ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> { @@ -27,6 +30,7 @@ public void register() { ModData.LOGGER.warn("玩家取消玩家上线同步,因为服务器未连接主程序"); return; } + if (!joinCooldown.allow(player.getUUID().toString(), ConfigLoader.get().getSync().getJoinCooldownSeconds())) return; var playerInfo = PlayerUtils.getPlayerInfo(player); // 注意,最好不要在别的线程获取,你永远不知道下一个tick数据是否可用 EasyBotNetworkingThreadPool.getInstance().addTask(() -> EasyBotModImpl.INSTANCE.getBridgeClient().syncEnterExit(playerInfo, true), "消息同步-进服"); }); @@ -45,6 +49,7 @@ public void register() { ModData.LOGGER.warn("玩家取消玩家下线同步,因为服务器未连接主程序"); return; } + if (!quitCooldown.allow(player.getUUID().toString(), ConfigLoader.get().getSync().getQuitCooldownSeconds())) return; var playerInfo = PlayerUtils.getPlayerInfo(player); // 注意,最好不要在别的线程获取,你永远不知道下一个tick数据是否可用 EasyBotNetworkingThreadPool.getInstance().addTask(() -> EasyBotModImpl.INSTANCE.getBridgeClient().syncEnterExit(playerInfo, false), "消息同步-退服"); }); diff --git a/src/main/java/com/springwater/easybot/platforms/fabric/features/PlayerDeathSyncFeature.java b/src/main/java/com/springwater/easybot/platforms/fabric/features/PlayerDeathSyncFeature.java index ad62e92..8ccf81b 100644 --- a/src/main/java/com/springwater/easybot/platforms/fabric/features/PlayerDeathSyncFeature.java +++ b/src/main/java/com/springwater/easybot/platforms/fabric/features/PlayerDeathSyncFeature.java @@ -8,6 +8,7 @@ import com.springwater.easybot.utils.CarpetUtils; import com.springwater.easybot.threading.EasyBotNetworkingThreadPool; import com.springwater.easybot.utils.PlayerUtils; +import com.springwater.easybot.utils.SyncCooldown; import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.LivingEntity; @@ -15,6 +16,7 @@ import static com.springwater.easybot.utils.DamageTypeMappings.DEATH_MESSAGES; public class PlayerDeathSyncFeature implements IEasyBotFeatures { + private final SyncCooldown deathCooldown = new SyncCooldown(); @Override public void register() { @@ -28,6 +30,9 @@ public void register() { return; } + if (EasyBotModImpl.INSTANCE.getBridgeClient() == null || !EasyBotModImpl.INSTANCE.getBridgeClient().isReady()) return; + String cause = source.typeHolder().unwrapKey().map(Object::toString).orElse(source.getMsgId()); + if (!deathCooldown.allow(player.getUUID() + ":" + cause, ConfigLoader.get().getSync().getDeathCooldownSeconds())) return; var profile = PlayerUtils.getPlayerInfo(player); var killer = new StringBuilder(); var deathReason = new StringBuilder(); diff --git a/src/main/java/com/springwater/easybot/platforms/legacyforge/features/LoginEventSyncFeature.java b/src/main/java/com/springwater/easybot/platforms/legacyforge/features/LoginEventSyncFeature.java index 77709ea..cedfd19 100644 --- a/src/main/java/com/springwater/easybot/platforms/legacyforge/features/LoginEventSyncFeature.java +++ b/src/main/java/com/springwater/easybot/platforms/legacyforge/features/LoginEventSyncFeature.java @@ -8,12 +8,15 @@ import com.springwater.easybot.threading.EasyBotNetworkingThreadPool; import com.springwater.easybot.utils.CarpetUtils; import com.springwater.easybot.utils.PlayerUtils; +import com.springwater.easybot.utils.SyncCooldown; import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.entity.player.PlayerEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; public class LoginEventSyncFeature implements IEasyBotFeatures { + private final SyncCooldown joinCooldown = new SyncCooldown(); + private final SyncCooldown quitCooldown = new SyncCooldown(); @Override public void register() { MinecraftForge.EVENT_BUS.register(this); @@ -34,6 +37,7 @@ public void onPlayerLogin(PlayerEvent.PlayerLoggedInEvent event) { return; } + if (!joinCooldown.allow(player.getUUID().toString(), ConfigLoader.get().getSync().getJoinCooldownSeconds())) return; var playerInfo = PlayerUtils.getPlayerInfo(player); EasyBotNetworkingThreadPool.getInstance().addTask(() -> LegacyForgeEntry.getBridgeClient().syncEnterExit(playerInfo, true), "消息同步-进服"); @@ -54,6 +58,7 @@ public void onPlayerLogout(PlayerEvent.PlayerLoggedOutEvent event) { return; } + if (!quitCooldown.allow(player.getUUID().toString(), ConfigLoader.get().getSync().getQuitCooldownSeconds())) return; var playerInfo = PlayerUtils.getPlayerInfo(player); EasyBotNetworkingThreadPool.getInstance().addTask(() -> LegacyForgeEntry.getBridgeClient().syncEnterExit(playerInfo, false), "消息同步-退服"); diff --git a/src/main/java/com/springwater/easybot/platforms/legacyforge/features/PlayerDeathSyncFeature.java b/src/main/java/com/springwater/easybot/platforms/legacyforge/features/PlayerDeathSyncFeature.java index 6ce3c6d..c5f32eb 100644 --- a/src/main/java/com/springwater/easybot/platforms/legacyforge/features/PlayerDeathSyncFeature.java +++ b/src/main/java/com/springwater/easybot/platforms/legacyforge/features/PlayerDeathSyncFeature.java @@ -8,6 +8,7 @@ import com.springwater.easybot.threading.EasyBotNetworkingThreadPool; import com.springwater.easybot.utils.CarpetUtils; import com.springwater.easybot.utils.PlayerUtils; +import com.springwater.easybot.utils.SyncCooldown; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.LivingEntity; import net.minecraftforge.common.MinecraftForge; @@ -17,6 +18,7 @@ import static com.springwater.easybot.utils.DamageTypeMappings.DEATH_MESSAGES; public class PlayerDeathSyncFeature implements IEasyBotFeatures { + private final SyncCooldown deathCooldown = new SyncCooldown(); @Override public void register() { @@ -40,6 +42,8 @@ public void onLivingDeath(LivingDeathEvent event) { } var source = event.getSource(); + String cause = source.typeHolder().unwrapKey().map(Object::toString).orElse(source.getMsgId()); + if (!deathCooldown.allow(player.getUUID() + ":" + cause, ConfigLoader.get().getSync().getDeathCooldownSeconds())) return; var profile = PlayerUtils.getPlayerInfo(player); var killer = new StringBuilder(); var deathReason = new StringBuilder(); diff --git a/src/main/java/com/springwater/easybot/platforms/neoforge/features/LoginEventSyncFeature.java b/src/main/java/com/springwater/easybot/platforms/neoforge/features/LoginEventSyncFeature.java index 659e1dd..3fe6cdf 100644 --- a/src/main/java/com/springwater/easybot/platforms/neoforge/features/LoginEventSyncFeature.java +++ b/src/main/java/com/springwater/easybot/platforms/neoforge/features/LoginEventSyncFeature.java @@ -8,12 +8,15 @@ import com.springwater.easybot.threading.EasyBotNetworkingThreadPool; import com.springwater.easybot.utils.CarpetUtils; import com.springwater.easybot.utils.PlayerUtils; +import com.springwater.easybot.utils.SyncCooldown; import net.minecraft.server.level.ServerPlayer; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.event.entity.player.PlayerEvent; public class LoginEventSyncFeature implements IEasyBotFeatures { + private final SyncCooldown joinCooldown = new SyncCooldown(); + private final SyncCooldown quitCooldown = new SyncCooldown(); @Override public void register() { @@ -35,6 +38,7 @@ public void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) { return; } // 注意,最好不要在别的线程获取,你永远不知道下一个tick数据是否可用 + if (!joinCooldown.allow(player.getUUID().toString(), ConfigLoader.get().getSync().getJoinCooldownSeconds())) return; var playerInfo = PlayerUtils.getPlayerInfo(player); EasyBotNetworkingThreadPool.getInstance().addTask(() -> EasyBotModImpl.INSTANCE.getBridgeClient().syncEnterExit(playerInfo, true), "消息同步-进服"); } @@ -54,6 +58,7 @@ public void onPlayerQuit(PlayerEvent.PlayerLoggedOutEvent event) { return; } // 注意,最好不要在别的线程获取,你永远不知道下一个tick数据是否可用 + if (!quitCooldown.allow(player.getUUID().toString(), ConfigLoader.get().getSync().getQuitCooldownSeconds())) return; var playerInfo = PlayerUtils.getPlayerInfo(player); EasyBotNetworkingThreadPool.getInstance().addTask(() -> EasyBotModImpl.INSTANCE.getBridgeClient().syncEnterExit(playerInfo, false), "消息同步-退服"); } diff --git a/src/main/java/com/springwater/easybot/platforms/neoforge/features/PlayerDeathSyncFeature.java b/src/main/java/com/springwater/easybot/platforms/neoforge/features/PlayerDeathSyncFeature.java index 8727d45..560e6dd 100644 --- a/src/main/java/com/springwater/easybot/platforms/neoforge/features/PlayerDeathSyncFeature.java +++ b/src/main/java/com/springwater/easybot/platforms/neoforge/features/PlayerDeathSyncFeature.java @@ -8,6 +8,7 @@ import com.springwater.easybot.threading.EasyBotNetworkingThreadPool; import com.springwater.easybot.utils.CarpetUtils; import com.springwater.easybot.utils.PlayerUtils; +import com.springwater.easybot.utils.SyncCooldown; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.LivingEntity; import net.neoforged.bus.api.SubscribeEvent; @@ -17,6 +18,7 @@ import static com.springwater.easybot.utils.DamageTypeMappings.DEATH_MESSAGES; public class PlayerDeathSyncFeature implements IEasyBotFeatures { + private final SyncCooldown deathCooldown = new SyncCooldown(); @Override public void register() { @@ -38,6 +40,9 @@ public void onLivingDeath(LivingDeathEvent event) { var source = event.getSource(); + if (EasyBotModImpl.INSTANCE.getBridgeClient() == null || !EasyBotModImpl.INSTANCE.getBridgeClient().isReady()) return; + String cause = source.typeHolder().unwrapKey().map(Object::toString).orElse(source.getMsgId()); + if (!deathCooldown.allow(player.getUUID() + ":" + cause, ConfigLoader.get().getSync().getDeathCooldownSeconds())) return; var profile = PlayerUtils.getPlayerInfo(player); var killer = new StringBuilder(); var deathReason = new StringBuilder(); diff --git a/src/main/java/com/springwater/easybot/utils/SyncCooldown.java b/src/main/java/com/springwater/easybot/utils/SyncCooldown.java new file mode 100644 index 0000000..4929fef --- /dev/null +++ b/src/main/java/com/springwater/easybot/utils/SyncCooldown.java @@ -0,0 +1,22 @@ +package com.springwater.easybot.utils; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +import java.util.concurrent.TimeUnit; + +/** Each instance tracks one event type. Suppressed events do not extend the cooldown. */ +public final class SyncCooldown { + private int seconds; + private Cache sent; + + public synchronized boolean allow(String key, int cooldownSeconds) { + if (seconds != cooldownSeconds) { + seconds = cooldownSeconds; + sent = seconds > 0 + ? CacheBuilder.newBuilder().expireAfterWrite(seconds, TimeUnit.SECONDS).build() + : null; + } + return sent == null || sent.asMap().putIfAbsent(key, Boolean.TRUE) == null; + } +} diff --git a/src/main/resources/config.json b/src/main/resources/config.json index 52b1a3a..4ab3603 100644 --- a/src/main/resources/config.json +++ b/src/main/resources/config.json @@ -32,6 +32,9 @@ "useMixinReport1201": false }, "sync": { - "chatImageSupport": true + "chatImageSupport": true, + "joinCooldownSeconds": 0, + "quitCooldownSeconds": 0, + "deathCooldownSeconds": 0 } } \ No newline at end of file From 1d20704a6d5a710d700b7178fb6ee69f300ca3c8 Mon Sep 17 00:00:00 2001 From: BaizhouziYou <111175934+BaizhouziYou@users.noreply.github.com> Date: Fri, 2 Oct 2026 21:42:48 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat(mod):=20=E6=94=AF=E6=8C=81=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E9=85=8D=E7=BD=AE=E6=92=AD=E6=8A=A5=E5=86=B7=E5=8D=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加冷却配置查询与修改命令,沿用管理员权限并提供补全 - 校验整数及配置项,保存成功后即时生效,保存失败保留原值 - 保留未知配置字段,正常修改冷却时不重连 Bridge --- README.md | 10 ++++ .../easybot/commands/EasyBotCommands.java | 1 + .../handlers/ConfigCommandHandler.java | 53 +++++++++++++++++++ .../commands/handlers/HelpCommandHandler.java | 3 ++ .../easybot/config/ConfigLoader.java | 51 ++++++++++++++++-- 5 files changed, 113 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/springwater/easybot/commands/handlers/ConfigCommandHandler.java diff --git a/README.md b/README.md index 024ba7b..081bf49 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,16 @@ EasyBot的Minecraft模组实现端,基于[Stonecutter](https://stonecutter.kikug ## 播报冷却 +管理员(权限等级 3,与 reload 相同)可用 `/easybot config` 查看冷却配置,或执行以下命令保存并即时生效: + +```text +/easybot config sync.joinCooldownSeconds 60 +/easybot config sync.quitCooldownSeconds 60 +/easybot config sync.deathCooldownSeconds 30 +``` + +省略秒数时查看该项;支持配置项和常用秒数补全。秒数须为 32 位整数,`0` 或负数关闭冷却。命令只开放上述三个冷却项,保存失败时保留原配置。 + 在 `config/easybot/config.json` 的 `sync` 中配置冷却秒数,例如: ```json diff --git a/src/main/java/com/springwater/easybot/commands/EasyBotCommands.java b/src/main/java/com/springwater/easybot/commands/EasyBotCommands.java index 0f1a149..72b4126 100644 --- a/src/main/java/com/springwater/easybot/commands/EasyBotCommands.java +++ b/src/main/java/com/springwater/easybot/commands/EasyBotCommands.java @@ -13,6 +13,7 @@ public class EasyBotCommands { new StatusCommandHandler(), new SayCommandHandler(), new ReloadCommandHandler(), + new ConfigCommandHandler(), new BindCommandHandler() ); diff --git a/src/main/java/com/springwater/easybot/commands/handlers/ConfigCommandHandler.java b/src/main/java/com/springwater/easybot/commands/handlers/ConfigCommandHandler.java new file mode 100644 index 0000000..ea02202 --- /dev/null +++ b/src/main/java/com/springwater/easybot/commands/handlers/ConfigCommandHandler.java @@ -0,0 +1,53 @@ +package com.springwater.easybot.commands.handlers; + +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.springwater.easybot.commands.ICommandHandler; +import com.springwater.easybot.config.ConfigLoader; +import com.springwater.easybot.platforms.ModData; +import com.springwater.easybot.utils.PermissionUtils; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.network.chat.Component; + +public class ConfigCommandHandler implements ICommandHandler { + @Override + public void register(LiteralArgumentBuilder stack) { + var config = Commands.literal("config") + .requires(source -> PermissionUtils.hasPermission(source, 3)) + .executes(context -> { + for (String key : ConfigLoader.COOLDOWN_KEYS) show(context.getSource(), key); + context.getSource().sendSystemMessage(Component.literal("用法: /easybot config <配置项> [秒数],0 或负数关闭冷却")); + return 1; + }); + for (String key : ConfigLoader.COOLDOWN_KEYS) { + config.then(Commands.literal("sync." + key) + .executes(context -> show(context.getSource(), key)) + .then(Commands.argument("seconds", IntegerArgumentType.integer()) + .suggests((context, builder) -> { + for (String value : new String[]{"0", "30", "60"}) { + if (value.startsWith(builder.getRemaining())) builder.suggest(value); + } + return builder.buildFuture(); + }) + .executes(context -> { + int seconds = IntegerArgumentType.getInteger(context, "seconds"); + try { + ConfigLoader.setCooldown(key, seconds); + context.getSource().sendSuccess(() -> Component.literal("已保存并生效: sync." + key + " = " + seconds + " 秒"), false); + return 1; + } catch (Exception e) { + ModData.LOGGER.error("保存播报冷却配置失败", e); + context.getSource().sendFailure(Component.literal("保存失败,冷却配置未更改,请检查服务器日志")); + return 0; + } + }))); + } + stack.then(config); + } + + private int show(CommandSourceStack source, String key) { + source.sendSystemMessage(Component.literal("sync." + key + " = " + ConfigLoader.getCooldown(key) + " 秒")); + return 1; + } +} diff --git a/src/main/java/com/springwater/easybot/commands/handlers/HelpCommandHandler.java b/src/main/java/com/springwater/easybot/commands/handlers/HelpCommandHandler.java index 76b0d74..8c8f93c 100644 --- a/src/main/java/com/springwater/easybot/commands/handlers/HelpCommandHandler.java +++ b/src/main/java/com/springwater/easybot/commands/handlers/HelpCommandHandler.java @@ -32,6 +32,9 @@ private int sendGetHelp(CommandContext context) { root.append(buildCommandLine("/easybot say <消息>", "主动消息同步")); root.append(buildCommandLine("/easybot status", "获取状态,需OP")); root.append(buildCommandLine("/easybot reload", "重载配置,需OP")); + if (com.springwater.easybot.utils.PermissionUtils.hasPermission(source, 3)) { + root.append(buildCommandLine("/easybot config <配置项> [秒数]", "查看或修改播报冷却,需OP")); + } root.append(Component.literal("\n--------------------------------------------------") .withStyle(ChatFormatting.GRAY)); source.sendSuccess(() -> root, false); diff --git a/src/main/java/com/springwater/easybot/config/ConfigLoader.java b/src/main/java/com/springwater/easybot/config/ConfigLoader.java index e85482e..214dbb8 100644 --- a/src/main/java/com/springwater/easybot/config/ConfigLoader.java +++ b/src/main/java/com/springwater/easybot/config/ConfigLoader.java @@ -3,6 +3,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonSyntaxException; +import com.google.gson.JsonObject; import com.springwater.easybot.platforms.EasyBotModImpl; import com.springwater.easybot.platforms.ModData; import org.slf4j.Logger; @@ -28,7 +29,8 @@ public class ConfigLoader { private static final AtomicBoolean isWatcherRunning = new AtomicBoolean(false); // 当前正在使用的配置实例 - private static EasyBotConfig currentConfig; + private static volatile EasyBotConfig currentConfig; + public static final List COOLDOWN_KEYS = List.of("joinCooldownSeconds", "quitCooldownSeconds", "deathCooldownSeconds"); // 配置内容哈希值,用于检测文件是否真正变化 private static long lastConfigHash = 0; @@ -121,7 +123,7 @@ private static boolean isValidToken(EasyBotConfig config) { * @throws IOException 读取配置文件时发生 IO 错误 * @throws JsonSyntaxException 配置文件格式错误 */ - public static void load() throws IOException { + public static synchronized void load() throws IOException { if (!Files.exists(CONFIG_PATH)) { LOGGER.info("未找到配置文件,正在创建默认配置..."); createDefaultConfigFromResources(); @@ -137,7 +139,7 @@ public static void load() throws IOException { /** * 热重载配置 */ - public static void reload() { + public static synchronized void reload() { LOGGER.info("正在热重载配置..."); if (!Files.exists(CONFIG_PATH)) { LOGGER.warn("配置文件不存在,尝试重新创建..."); @@ -255,7 +257,7 @@ public static void startAutoReloadWatcher() { /** * 保存当前内存中的配置到磁盘。 */ - public static void save() { + public static synchronized void save() { if (currentConfig == null) { currentConfig = new EasyBotConfig(); } @@ -271,6 +273,45 @@ public static void save() { } } + public static int getCooldown(String key) { + var sync = get().getSync(); + return switch (key) { + case "joinCooldownSeconds" -> sync.getJoinCooldownSeconds(); + case "quitCooldownSeconds" -> sync.getQuitCooldownSeconds(); + case "deathCooldownSeconds" -> sync.getDeathCooldownSeconds(); + default -> throw new IllegalArgumentException("未知冷却配置项: " + key); + }; + } + + public static synchronized void setCooldown(String key, int seconds) throws IOException { + if (!COOLDOWN_KEYS.contains(key)) throw new IllegalArgumentException("未知冷却配置项: " + key); + // Preserve fields unknown to this version, and reject malformed files before writing. + JsonObject document = GSON.fromJson(Files.readString(CONFIG_PATH), JsonObject.class); + if (document == null) throw new JsonSyntaxException("配置不能为 null"); + if (!document.has("sync")) document.add("sync", new JsonObject()); + document.getAsJsonObject("sync").addProperty(key, seconds); + String content = GSON.toJson(document); + EasyBotConfig next = GSON.fromJson(content, EasyBotConfig.class); + JsonObject expected = GSON.toJsonTree(get()).getAsJsonObject(); + expected.getAsJsonObject("sync").addProperty(key, seconds); + boolean otherSettingsChanged = !expected.equals(GSON.toJsonTree(next)); + Path temporary = Files.createTempFile(CONFIG_PATH.getParent(), "easybot-config-", ".tmp"); + try { + Files.writeString(temporary, content); + try { + Files.move(temporary, CONFIG_PATH, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING); + } catch (AtomicMoveNotSupportedException e) { + Files.move(temporary, CONFIG_PATH, StandardCopyOption.REPLACE_EXISTING); + } + } finally { + Files.deleteIfExists(temporary); + } + currentConfig = next; + lastConfigHash = content.hashCode(); + // Cooldown listeners read the current config directly; no Bridge reconnect is needed. + if (otherSettingsChanged) notifyListeners(); + } + /** * 注册当配置发生变化(重载成功)时的回调函数。 * @@ -307,4 +348,4 @@ private static void createDefaultConfigFromResources() { LOGGER.error("从 Jar 复制默认配置失败。", e); } } -} \ No newline at end of file +} From a22479a50703cf33079ff696193542471af2b04e Mon Sep 17 00:00:00 2001 From: BaizhouziYou <111175934+BaizhouziYou@users.noreply.github.com> Date: Fri, 2 Oct 2026 22:13:28 +0800 Subject: [PATCH 3/3] =?UTF-8?q?ci(mod):=20CI=20=E4=BD=BF=E7=94=A8=E5=AE=98?= =?UTF-8?q?=E6=96=B9=20Gradle=20=E4=B8=8B=E8=BD=BD=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pr-build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index 7a40ae9..1ab9b32 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -24,6 +24,9 @@ jobs: - name: Checkout repository uses: actions/checkout@v7 + - name: Use official Gradle distribution in CI + run: sed -i 's#mirrors\.aliyun\.com/macports/distfiles/gradle/#services.gradle.org/distributions/#' gradle/wrapper/gradle-wrapper.properties + - name: Set up Java 25 uses: actions/setup-java@v6 with: