第32批:垃圾站血条与启发式开关解耦;拉黑信号回响PHP(CDK双链路拦截+syncBlacklist名单同步)
This commit is contained in:
@@ -318,16 +318,26 @@ public class BlacklistManager {
|
||||
}
|
||||
|
||||
// ===== 差异应用 =====
|
||||
boolean changed = parsed.size() != entries.size();
|
||||
// 1) 新增 → 施加处罚
|
||||
for (BlackEntry e : parsed.values()) {
|
||||
if (!entries.containsKey(e.name)) {
|
||||
applyBlacklist(e);
|
||||
changed = true;
|
||||
} else {
|
||||
BlackEntry old = entries.get(e.name);
|
||||
if (old == null || old.expiresMs != e.expiresMs
|
||||
|| !old.timeText.equals(e.timeText)
|
||||
|| !old.reason.equals(e.reason)) {
|
||||
changed = true; // 条目内容改了(理由/时长)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 2) 移除 → 恢复
|
||||
for (BlackEntry e : new ArrayList<>(entries.values())) {
|
||||
if (!parsed.containsKey(e.name)) {
|
||||
restoreBlacklist(e.name);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
entries.clear();
|
||||
@@ -338,6 +348,13 @@ public class BlacklistManager {
|
||||
lastSize = mdFile.length();
|
||||
}
|
||||
plugin.getLogger().info("[黑名单] 已重载,当前生效 " + parsed.size() + " 人");
|
||||
// ★ 第32批(#208):名单变化 → 立即回响PHP(异步;syncBlacklist 内部 hash 门控兜底)
|
||||
if (changed) {
|
||||
try {
|
||||
WebManager wm = plugin.webManager;
|
||||
if (wm != null) wm.forceSyncBlacklist();
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -57,6 +57,12 @@ public class CDKManager {
|
||||
*/
|
||||
public String redeem(String code, String player) {
|
||||
try {
|
||||
// ★ 第32批:拉黑检查提到查码之前 —— 被拉黑玩家一律拒绝,
|
||||
// 不管码在本地是否存在,绝不放行进远程验证链
|
||||
if (plugin.getBlacklist() != null
|
||||
&& plugin.getBlacklist().isBlacklisted(player))
|
||||
return "fail:blacklisted";
|
||||
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"SELECT amount,type,used FROM cdk "
|
||||
+ "WHERE code=?");
|
||||
@@ -74,11 +80,6 @@ public class CDKManager {
|
||||
if (used == 1)
|
||||
return "fail:already_used";
|
||||
|
||||
// ★ 第30批:拉黑玩家禁止使用兑换码
|
||||
if (plugin.getBlacklist() != null
|
||||
&& plugin.getBlacklist().isBlacklisted(player))
|
||||
return "fail:blacklisted";
|
||||
|
||||
if (plugin.getBonds()
|
||||
.isFrozen(player))
|
||||
return "fail:frozen";
|
||||
|
||||
@@ -58,7 +58,8 @@ public class ConfigManager {
|
||||
public int garbageMaxRounds = 1;
|
||||
|
||||
|
||||
/** ★ 第30批:垃圾站启发式拦截(双击Q抢救窗口 + 倒计时段位提示)。默认开启。 */
|
||||
/** ★ 第30批:垃圾站启发式拦截 —— 仅控制「红区title警告 + 双击Q抢救窗口」。
|
||||
* ★ 第32批:与顶部血条解耦,关闭时血条照常显示。默认开启。 */
|
||||
|
||||
public boolean garbageHeuristic = true;
|
||||
|
||||
@@ -454,7 +455,7 @@ public class ConfigManager {
|
||||
garbageHeuristic = Boolean.parseBoolean(m.getOrDefault("垃圾站_启发式拦截", "true"));
|
||||
|
||||
if (!garbageHeuristic) {
|
||||
Bukkit.getLogger().warning("[Sdf1配置] 垃圾站启发式拦截已关闭:不再做倒计时段位提示与双击Q抢救拦截。");
|
||||
Bukkit.getLogger().warning("[Sdf1配置] 垃圾站启发式拦截已关闭:不再做红区title警告与双击Q抢救拦截(顶部血条照常显示)。");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -36,19 +36,27 @@ public class GarbageManager implements Listener {
|
||||
private final Map<String, Integer>
|
||||
recyclePages = new HashMap<>();
|
||||
|
||||
/** ★ 第30批:垃圾站倒计时(秒)。原为 startAutoCleanup 内部局部变量,
|
||||
* 抢救模式需要外部重置,故提为字段。 */
|
||||
/**
|
||||
* ★ 第30批:垃圾站倒计时(秒)。原为 startAutoCleanup 内部局部变量,
|
||||
* 抢救模式需要外部重置,故提为字段。
|
||||
*/
|
||||
private int countdown = 0;
|
||||
|
||||
/** ★ 第31批:本轮开始的墙钟时间戳(毫秒)。
|
||||
* 血条色段按真实时间判定,与 tick 计数解耦,
|
||||
* 避免“第3分30秒还是绿色”的漂移。0 = 本轮未开始。 */
|
||||
/**
|
||||
* ★ 第31批:本轮开始的墙钟时间戳(毫秒)。
|
||||
* 血条色段按真实时间判定,与 tick 计数解耦,
|
||||
* 避免“第3分30秒还是绿色”的漂移。0 = 本轮未开始。
|
||||
*/
|
||||
private long roundStartMs = 0L;
|
||||
|
||||
/** ★ 第30批:顶部血条(玩家 UUID → BossBar) */
|
||||
/**
|
||||
* ★ 第30批:顶部血条(玩家 UUID → BossBar)
|
||||
*/
|
||||
private final Map<UUID, BossBar> bossBars = new HashMap<>();
|
||||
|
||||
/** ★ 第30批:双击 Q 确认丢弃的首次按键时间(玩家 UUID → 毫秒) */
|
||||
/**
|
||||
* ★ 第30批:双击 Q 确认丢弃的首次按键时间(玩家 UUID → 毫秒)
|
||||
*/
|
||||
private final Map<UUID, Long> pendingDrops = new HashMap<>();
|
||||
|
||||
|
||||
@@ -174,9 +182,6 @@ public class GarbageManager implements Listener {
|
||||
cleanupInterval =
|
||||
plugin.getConfig2()
|
||||
.garbageInterval;
|
||||
boolean heuristic =
|
||||
plugin.getConfig2()
|
||||
.garbageHeuristic;
|
||||
|
||||
int remain =
|
||||
cleanupInterval - countdown;
|
||||
@@ -211,7 +216,8 @@ public class GarbageManager implements Listener {
|
||||
countdown++;
|
||||
// ★ 第30批:顶部血条常驻倒计时(软提醒)
|
||||
// 用 increment 前的 remain,与广播口径完全一致(避免差 1 秒)
|
||||
updateBossBars(remain, heuristic);
|
||||
// ★ 第32批:血条只受 垃圾站_启用 控制,与启发式开关解耦
|
||||
updateBossBars(remain);
|
||||
}
|
||||
}.runTaskTimer(plugin, 20L, 20L);
|
||||
}
|
||||
@@ -227,13 +233,11 @@ public class GarbageManager implements Listener {
|
||||
* <li>4:30 ~ 5:00 → 红 = 危险(另:剩余≤30秒兜底红)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>启发式开关(垃圾站_启发式拦截)关闭时:撤掉血条、不提示、不拦截。
|
||||
* <p>★ 第32批:血条与启发式开关解耦 —— 只要垃圾站启用,血条常驻显示;
|
||||
* 启发式开关(垃圾站_启发式拦截)关闭时只关掉「红区title警告 + 双击Q拦截」,
|
||||
* 不再撤血条(否则玩家对清理状态一无所知)。
|
||||
*/
|
||||
private void updateBossBars(int remain, boolean heuristic) {
|
||||
if (!heuristic) {
|
||||
hideAllBossBars();
|
||||
return;
|
||||
}
|
||||
private void updateBossBars(int remain) {
|
||||
int interval = Math.max(1, cleanupInterval);
|
||||
double progress = (double) remain / (double) interval;
|
||||
if (progress < 0.0D) progress = 0.0D;
|
||||
@@ -283,7 +287,7 @@ public class GarbageManager implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
/** 撤掉所有玩家的顶部血条(启发式关闭时用) */
|
||||
/** 撤掉所有玩家的顶部血条(插件关闭/停服时用;启发式开关不再触发) */
|
||||
private void hideAllBossBars() {
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
BossBar bar = bossBars.get(p.getUniqueId());
|
||||
@@ -390,8 +394,8 @@ public class GarbageManager implements Listener {
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent e) {
|
||||
if (!plugin.getConfig2().garbageHeuristic) return;
|
||||
// 下一轮 tick 会自动建血条,这里只清掉可能残留的按键状态
|
||||
// 下一轮 tick 会自动建血条(第32批:不再受启发式开关守卫),
|
||||
// 这里只清掉可能残留的按键状态
|
||||
pendingDrops.remove(e.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
|
||||
@@ -331,6 +331,7 @@ public class WebManager {
|
||||
private long lastPushedCutoff = -1; // 上次推送时的播报起点(变了就再强制推一轮)
|
||||
private String lastAdminsHash = "";
|
||||
private String lastUserRegistrationHash = "";
|
||||
private String lastBlacklistHash = ""; // ★ 第32批:拉黑名单同步快照
|
||||
|
||||
// 嵌入式HTTP服务器(接收PHP回调)
|
||||
private java.net.ServerSocket callbackServer;
|
||||
@@ -3338,7 +3339,14 @@ public class WebManager {
|
||||
// ★ 直接检查sdf1计分板CDK(bond.db只是钱包,不存CDK)
|
||||
String status = "not_found";
|
||||
int amount = 0;
|
||||
try {
|
||||
// ★ 第32批:被拉黑玩家的兑换请求 → 直接回 blacklisted,
|
||||
// 不进验证链、不加债券(PHP cdk.php 据此明确提示「你已被拉黑」)
|
||||
if (plugin.getBlacklist() != null
|
||||
&& plugin.getBlacklist().isBlacklisted(playerName)) {
|
||||
status = "blacklisted";
|
||||
plugin.getLogger().info("[CDK远程验证] " + cdkCode
|
||||
+ " 拉黑拦截 player=" + playerName);
|
||||
} else try {
|
||||
Object sdf1Plugin = Bukkit.getPluginManager().getPlugin("sdf1");
|
||||
if (sdf1Plugin != null) {
|
||||
plugin.getLogger().info("[CDK远程验证] 调用sdf1检查计分板: " + cdkCode);
|
||||
@@ -3443,7 +3451,13 @@ public class WebManager {
|
||||
String status = "not_found";
|
||||
int amount = 0;
|
||||
boolean transientFail = false; // 瞬时故障 -> 可重试
|
||||
try {
|
||||
// ★ 第32批:被拉黑玩家 → 直接回 blacklisted,不发起远程验证
|
||||
if (plugin.getBlacklist() != null
|
||||
&& plugin.getBlacklist().isBlacklisted(playerName)) {
|
||||
status = "blacklisted";
|
||||
plugin.getLogger().info("[CDK-Web验证] " + cdkCode
|
||||
+ " 拉黑拦截 player=" + playerName);
|
||||
} else try {
|
||||
String validateUrl = webBaseUrl + "/api/sync.php?action=check_cdk_exists&secret="
|
||||
+ java.net.URLEncoder.encode(secretKey, "UTF-8")
|
||||
+ "&code=" + java.net.URLEncoder.encode(cdkCode, "UTF-8");
|
||||
@@ -5292,6 +5306,8 @@ public class WebManager {
|
||||
// ★ 封禁名单和管理员列表独立于领地hash变化,必须每次都同步
|
||||
syncBans();
|
||||
syncAdmins();
|
||||
// ★ 第32批:拉黑名单同样每轮兜底(PHP重启丢数据时自愈)
|
||||
syncBlacklist();
|
||||
|
||||
// ★ 双向对账(多退少补):PHP 数据齐全就不推 —— 消除「重启必整包推」
|
||||
// (lastLandDataHash 是内存字段,重启清零,旧逻辑每次开服必整包一次)。
|
||||
@@ -5561,6 +5577,62 @@ public class WebManager {
|
||||
syncAdmins();
|
||||
}
|
||||
|
||||
// ★ 第32批(#208):拉黑名单同步到PHP(web_player_blacklist),
|
||||
// 供 PHP 后台识别被拉黑玩家并给出明确提示(登录/兑换码双链路)。
|
||||
|
||||
/**
|
||||
* 同步拉黑名单到PHP。参照 syncBans 模板:
|
||||
* hash 门控(无变化静默)→ POST → 回执 success 才提交 hash(防 404 写脏)。
|
||||
* PHP 侧 sync_blacklist 做全量覆盖,无需对账。
|
||||
*/
|
||||
private void syncBlacklist() {
|
||||
if (!enabled) return;
|
||||
try {
|
||||
BlacklistManager blm = plugin.getBlacklist();
|
||||
java.util.List<BlacklistManager.BlackEntry> list =
|
||||
blm != null ? blm.getActiveEntries() : null;
|
||||
StringBuilder sb = new StringBuilder("[");
|
||||
StringBuilder hb = new StringBuilder();
|
||||
boolean first = true;
|
||||
if (list != null) {
|
||||
for (BlacklistManager.BlackEntry e : list) {
|
||||
if (!first) sb.append(",");
|
||||
first = false;
|
||||
sb.append("{\"name\":\"").append(escapeJson(e.name))
|
||||
.append("\",\"time\":\"").append(escapeJson(e.timeText))
|
||||
.append("\",\"reason\":\"").append(escapeJson(e.reason))
|
||||
.append("\",\"expire\":").append(e.expiresMs)
|
||||
.append(",\"created\":").append(e.createdMs).append("}");
|
||||
hb.append(e.name).append(":").append(e.expiresMs)
|
||||
.append(":").append(e.reason).append("|");
|
||||
}
|
||||
}
|
||||
sb.append("]");
|
||||
String currentHash = hb.toString();
|
||||
if (currentHash.equals(lastBlacklistHash)) return; // 无变化静默
|
||||
|
||||
Map<String, Object> body = new LinkedHashMap<>();
|
||||
body.put("secret", secretKey);
|
||||
body.put("blacks", sb.toString());
|
||||
String resp = httpPost("api/sync.php?action=sync_blacklist", mapToJson(body));
|
||||
if (resp != null && resp.contains("\"success\":true")) {
|
||||
lastBlacklistHash = currentHash;
|
||||
plugin.getLogger().info("[防护-sync] 拉黑名单同步: "
|
||||
+ (list == null ? 0 : list.size()) + "人 → " + resp);
|
||||
} else {
|
||||
plugin.getLogger().warning("[防护-sync] 拉黑名单同步未确认成功,下轮重试(hash 不提交)");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning("[防护-sync] 拉黑名单同步异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** ★ 第32批:拉黑/解除时立即异步同步(不等定时轮询;内部 hash 门控兜底) */
|
||||
public void forceSyncBlacklist() {
|
||||
if (!enabled) return;
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, this::syncBlacklist);
|
||||
}
|
||||
|
||||
/**
|
||||
* ★ 同步封禁名单到PHP(供用户管理页面显示封禁状态)
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user