第31批(7条修正):血条色段改墙钟绝对时刻(3:00黄/4:30红)、罪人签名移到消息尾部、抢救限最后1分钟、插件设置.txt双路增量自动重载(无变化静默)、/sdf1_login reload补loadSettings、兑换码web关闭直接返回
Check Source / check (push) Successful in 4s
Auto Release / build (push) Successful in 2m3s
Simple Test / test (push) Successful in 1s
Test Env / test (push) Successful in 2s

This commit is contained in:
youpaishidifu
2026-10-21 12:25:07 +08:00
parent 55d24c2d99
commit 311138b3a6
4 changed files with 171 additions and 16 deletions
@@ -409,6 +409,11 @@ public class ConfigManager {
// ==================== 插件设置 ====================
/** ★ 第31批(#204/#205):上次加载时 插件设置.txt 的内容指纹与键值快照,
* 供 reloadSettingsIfChanged() 做「无变化静默 / 有变化重载并打日志」。 */
private String settingsContentHash = null;
private Map<String, String> lastSettingsMap = null;
public void loadSettings() {
File file = new File(dataFolder, "插件设置.txt");
// ★ 安全交换(2026-10-13):模板有新字段时自动备份→重建→老值回填
@@ -488,6 +493,94 @@ public class ConfigManager {
+ tpSendIntervalSeconds + " 秒");
// rewardChannel 已标准化为 bonds/economy,默认bonds
// ★ 第31批(#204/#205):记录本次加载的键值快照与内容指纹(增量重载比对基线)
lastSettingsMap = new LinkedHashMap<>(m);
settingsContentHash = hashSettingsFile(file);
}
/**
* ★ 第31批(#204/#205):插件设置.txt 增量自动重载。
*
* <p>由 Web 配置热重载(web感知)与垃圾箱倒计时节点两处调用:
* <ul>
* <li>文件内容没变 → 直接返回 false,不打任何日志(静默)</li>
* <li>内容变了但键值没变(注释/空行/原样重写)→ 更新指纹,静默返回 false</li>
* <li>键值真的变了 → 整份 loadSettings() 重载,并逐条打出 old → new</li>
* </ul>
*
* @return true = 本次发生了键值变化并已重载
*/
public boolean reloadSettingsIfChanged() {
try {
File file = new File(dataFolder, "插件设置.txt");
if (!file.exists()) return false;
String h = hashSettingsFile(file);
if (h == null || h.equals(settingsContentHash)) {
return false; // 没变 → 静默
}
Map<String, String> fresh = new LinkedHashMap<>();
loadMap(file, fresh);
if (lastSettingsMap != null
&& fresh.equals(lastSettingsMap)) {
settingsContentHash = h; // 只有注释/格式变动 → 静默
return false;
}
Map<String, String> old = lastSettingsMap;
loadSettings(); // 整份重载(内部会刷新 hash 与快照)
java.util.List<String> diffs =
new java.util.ArrayList<>();
if (old != null) {
for (Map.Entry<String, String> e
: fresh.entrySet()) {
String ov = old.get(e.getKey());
if (ov == null || !ov.equals(e.getValue())) {
diffs.add(e.getKey() + ": "
+ (ov == null ? "(无)" : ov)
+ " -> " + e.getValue());
}
}
for (Map.Entry<String, String> e
: old.entrySet()) {
if (!fresh.containsKey(e.getKey())) {
diffs.add(e.getKey() + ": "
+ e.getValue() + " -> (已移除)");
}
}
}
Bukkit.getLogger().info(
"[Sdf1配置] 插件设置.txt 检测到变化,已整份重载: "
+ (diffs.isEmpty()
? "(键值集合变化)"
: String.join(" | ", diffs)));
return true;
} catch (Exception e) {
Bukkit.getLogger().warning(
"[Sdf1配置] 插件设置.txt 增量重载失败(沿用旧配置): "
+ e.getClass().getSimpleName()
+ ": " + e.getMessage());
return false;
}
}
/** 读文件全文做 MD5 指纹(读失败返回 null) */
private String hashSettingsFile(File file) {
try {
byte[] bytes = java.nio.file.Files.readAllBytes(
file.toPath());
java.security.MessageDigest md =
java.security.MessageDigest.getInstance("MD5");
byte[] d = md.digest(bytes);
StringBuilder sb = new StringBuilder();
for (byte b : d) {
sb.append(Character.forDigit(
(b >> 4) & 0xF, 16));
sb.append(Character.forDigit(b & 0xF, 16));
}
return sb.toString();
} catch (Exception e) {
return null;
}
}
/**
* 奖励方式标准化:中英文全兼容
+54 -11
View File
@@ -40,6 +40,11 @@ public class GarbageManager implements Listener {
* 抢救模式需要外部重置,故提为字段。 */
private int countdown = 0;
/** ★ 第31批:本轮开始的墙钟时间戳(毫秒)。
* 血条色段按真实时间判定,与 tick 计数解耦,
* 避免“第3分30秒还是绿色”的漂移。0 = 本轮未开始。 */
private long roundStartMs = 0L;
/** ★ 第30批:顶部血条(玩家 UUID → BossBar) */
private final Map<UUID, BossBar> bossBars = new HashMap<>();
@@ -157,9 +162,14 @@ public class GarbageManager implements Listener {
if (Bukkit.getOnlinePlayers()
.isEmpty()) {
countdown = 0;
roundStartMs = 0L;
return;
}
// ★ 第31批:本轮墙钟起点(0=新轮开始,色段按真实时间判定)
if (roundStartMs == 0L)
roundStartMs = System.currentTimeMillis();
// 重新读取配置(支持热更)
cleanupInterval =
plugin.getConfig2()
@@ -170,6 +180,16 @@ public class GarbageManager implements Listener {
int remain =
cleanupInterval - countdown;
// ★ 第31批(#204/#205):垃圾箱进入下一节点 → 查一次 插件设置.txt,
// 有变化整份重载(ConfigManager 内部比对,无变化静默、有变化打日志)
if (remain == 300 || remain == 180
|| remain == 60 || remain == 30
|| remain == 10 || remain == 0
|| (remain <= 5 && remain > 0)) {
plugin.getConfig2()
.reloadSettingsIfChanged();
}
if (remain == 300)
broadcast("§e§l[垃圾站] §f距离清理还有 §e5分钟");
else if (remain == 180)
@@ -185,6 +205,7 @@ public class GarbageManager implements Listener {
else if (remain == 0) {
doCleanup();
countdown = 0;
roundStartMs = 0L;
return;
}
countdown++;
@@ -198,11 +219,12 @@ public class GarbageManager implements Listener {
/**
* ★ 第30批:顶部血条常驻倒计时 + 色段软提醒。
*
* <p>色段按「已过时间」elapsed = interval - remain 划分(默认 5 分钟一轮):
* <p>色段按「墙钟已过时间」的绝对时刻判定(第31批 #200 修正,
* 不再用百分比,不随清理间隔漂移):
* <ul>
* <li>前 60%(剩 3 分钟以上)→ 浅绿 = 安全期</li>
* <li>60% ~ 90%(剩 3 分 01 秒 ~ 1 分 30 秒)→ 黄色 = 警告「垃圾箱快动作了,别丢了」</li>
* <li>最后 10%(剩 30 秒内)→ 红色 = 危险</li>
* <li>0:00 ~ 3:00(墙钟前 3 分钟)→ 绿 = 安全期</li>
* <li>3:01 ~ 4:29 → 黄 = 警告「垃圾箱快动作了,别丢了」</li>
* <li>4:30 ~ 5:00 → 红 = 危险(另:剩余≤30秒兜底红)</li>
* </ul>
*
* <p>启发式开关(垃圾站_启发式拦截)关闭时:撤掉血条、不提示、不拦截。
@@ -216,19 +238,26 @@ public class GarbageManager implements Listener {
double progress = (double) remain / (double) interval;
if (progress < 0.0D) progress = 0.0D;
if (progress > 1.0D) progress = 1.0D;
int elapsed = interval - remain;
// ★ 第31批(#200):色段改按「墙钟已过时间」的绝对时刻判定:
// 0:00~3:00 绿 / 3:01~4:29 黄 / 4:30~5:00 红(另:剩余≤30秒兜底红)。
// 不用 elapsed = interval - remain 的百分比,否则间隔一改、色段就跟着漂移。
long wallMs = roundStartMs > 0L
? System.currentTimeMillis() - roundStartMs
: (long) (interval - remain) * 1000L;
int elapsed = (int) Math.min((long) interval,
Math.max(0L, wallMs / 1000L));
BossBar.Color color;
String label;
if (elapsed < (int) (interval * 0.6D)) {
color = BossBar.Color.GREEN;
label = "§a§l安全期 §7| §a还早,放心丢";
} else if (elapsed < (int) (interval * 0.9D)) {
if (remain <= 30 || elapsed >= 270) {
color = BossBar.Color.RED;
label = "§c§l危险 §7| §c马上清理,抓紧捡";
} else if (elapsed > 180) {
color = BossBar.Color.YELLOW;
label = "§e§l注意 §7| §e垃圾箱快动作了,别丢了";
} else {
color = BossBar.Color.RED;
label = "§c§l危险 §7| §c马上清理,抓紧捡";
color = BossBar.Color.GREEN;
label = "§a§l安全期 §7| §a还早,放心丢";
}
String title = "§6§l垃圾站 §8| " + label
+ " §8| §f剩余 " + fmtRemain(remain);
@@ -294,7 +323,21 @@ public class GarbageManager implements Listener {
p.sendMessage("§c§l[垃圾站] §f本轮清理已经结束了,来不及抢救");
return false;
}
// ★ 第31批(#203):抢救窗口 = 本轮最后 1 分钟;未到时间不可触发
int remainSec = cleanupInterval - countdown;
if (remainSec > 60) {
if (p != null)
p.sendMessage("§c§l[垃圾站] §f未到抢救窗口:本轮清理还剩 §e"
+ fmtRemain(remainSec)
+ "§f,最后 1 分钟内才能发起抢救");
else
plugin.getLogger().info(
"[垃圾站] 抢救被拒:还剩 " + remainSec
+ " 秒,未进入最后1分钟窗口");
return false;
}
countdown = 0;
roundStartMs = 0L;
pendingDrops.clear();
String who = p != null ? p.getName() : "控制台";
broadcast("§a§l[垃圾站] §f玩家 §e" + who
+18 -5
View File
@@ -6236,12 +6236,14 @@ public class Main extends JavaPlugin
chatPrefix = highestGroup.displayColor + "[" + highestGroup.name + "]§f";
}
}
// ★ 第30批:拉黑玩家消息末尾加「----我是罪人user」称号
// ★ 第31批(#201):拉黑玩家签名加在消息【尾部】——
// 「你好今天怎么样 ----我是罪人user」(空2格 + 签名),不再放最前面
String chatSuffix = "";
if (isBlacklisted(p)) {
chatPrefix = chatPrefix + "----我是罪人" + p.getName() + "§f ";
chatSuffix = " ----我是罪人" + p.getName();
}
if (!chatPrefix.isEmpty()) {
e.setFormat(chatPrefix + "%1$s: %2$s");
if (!chatPrefix.isEmpty() || !chatSuffix.isEmpty()) {
e.setFormat(chatPrefix + "%1$s: %2$s" + chatSuffix);
}
// ===== 聊天过滤 - 开关检查 =====
@@ -9700,6 +9702,9 @@ public class Main extends JavaPlugin
return true;
}
reloadConfig();
// ★ 第31批(#205):/reload 此前漏了 loadSettings,
// 插件设置.txt 改完 reload 不生效(启发式拦截/垃圾间隔没动静的根因)
if (config != null) config.loadSettings();
if (areaProtection != null) areaProtection.reload();
if (webManager != null) webManager.reloadWebConfig();
sender.sendMessage("§a[§2Sdf1_login§a] 插件全部设置已重载");
@@ -10812,7 +10817,15 @@ public class Main extends JavaPlugin
String playerName, String code) {
if (cdkManager == null)
return "fail:CDK未初始化";
return cdkManager.redeem(code, playerName);
String res = cdkManager.redeem(code, playerName);
// ★ 第31批(#202):web通信关闭时本地未匹配 → 直接返回,
// 不再让外部(sdf1)进入「正在查询远程验证」流程干等45秒
if ("fail:not_found".equals(res)
&& webManager != null
&& !webManager.isEnabled()) {
return "fail:web_disabled";
}
return res;
}
// 旧的(可能缺失或拼写错误)
public SalesStatsManager getSalesStats() {
+6
View File
@@ -159,6 +159,12 @@ public class WebManager {
settingsFileStamp = stamp;
// ★ 第31批(#204):web 感知到 插件设置.txt 变化 → 拉起整份文件重载
// (垃圾站等其他模块的配置字段;ConfigManager 内部比对,无变化静默)
if (plugin.getConfig2() != null) {
plugin.getConfig2().reloadSettingsIfChanged();
}
if (!urlChanged && !secretChanged && !enableChanged && !othersChanged) {
return; // 文件动了但 Web 段没变(如改了别的模块)→ 安静走人
}