第31批(7条修正):血条色段改墙钟绝对时刻(3:00黄/4:30红)、罪人签名移到消息尾部、抢救限最后1分钟、插件设置.txt双路增量自动重载(无变化静默)、/sdf1_login reload补loadSettings、兑换码web关闭直接返回
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 奖励方式标准化:中英文全兼容
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 段没变(如改了别的模块)→ 安静走人
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user