第57批续22: 子领地权限失效修复 + 封禁播报时间被篡改修复
Check Source / check (push) Successful in 4s
Auto Release / build (push) Successful in 2m27s
Simple Test / test (push) Successful in 1s
Test Env / test (push) Successful in 1s

1. 子领地权限失效: onPlayerMove区域检测循环遇首个匹配领地即break,
   子领地完全包含在主领地内,可能命中主领地导致子领地deny_move不生效。
   修复: 收集所有匹配领地选面积最小的(子领地自然优先)。

2. 子领地创建时不继承主领地权限: confirm_create和handleApproval路径
   新增copyLandPermissions方法,子领地创建时继承主领地权限一次。

3. 封禁播报时间被篡改: WebManager对账收回封禁时,banList.addBan()
   把created设为当前时间写入banned-players.json,setCreated只改内存。
   Bukkit BanList无updateBan API,文件里保留的是收回时间。
   修复: 新增persistBanCreatedToFile方法直接改写文件持久化真实创建时间。
This commit is contained in:
youpaishidifu
2026-10-27 21:45:45 +08:00
parent 9443a5cf1b
commit cf84426bf6
2 changed files with 137 additions and 3 deletions
+87 -3
View File
@@ -3221,6 +3221,62 @@ public class AreaProtection implements Listener {
return n.endsWith("_SIGN"); return n.endsWith("_SIGN");
} }
/**
* ★ 第57批续22: 子领地创建时继承主领地权限一次。
* 后续子领地权限由子领地主/管理员/子领地授权管理员单独调配,
* 不再跟随主领地变化。
* 继承范围: deny* 权限开关、出入提示、游戏模式强制、公共建筑标记。
* 不继承: owner(创建者)、坐标、parentId、createCost。
*/
private void copyLandPermissions(AreaConfig src, AreaConfig dst) {
if (src == null || dst == null) return;
dst.denyBlockPlace = src.denyBlockPlace;
dst.denyBlockBreak = src.denyBlockBreak;
dst.denySignEdit = src.denySignEdit;
dst.denyContainer = src.denyContainer;
dst.denyPVP = src.denyPVP;
dst.denyFallDamage = src.denyFallDamage;
dst.denyHunger = src.denyHunger;
dst.denyAllDamage = src.denyAllDamage;
dst.denyMount = src.denyMount;
dst.allowDrop = src.allowDrop;
dst.denyEnderPearl = src.denyEnderPearl;
dst.denyBow = src.denyBow;
dst.denyPotion = src.denyPotion;
dst.denyExplosion = src.denyExplosion;
dst.denyMove = src.denyMove;
dst.allowPickup = src.allowPickup;
dst.denyFire = src.denyFire;
dst.denyThrownProjectiles = src.denyThrownProjectiles;
dst.denyGlowing = src.denyGlowing;
dst.denyRedstoneInteraction = src.denyRedstoneInteraction;
dst.denyDoorInteraction = src.denyDoorInteraction;
dst.denyNoteblockJukebox = src.denyNoteblockJukebox;
dst.denyLead = src.denyLead;
dst.denyCropHarvest = src.denyCropHarvest;
dst.denyFarmlandTrample = src.denyFarmlandTrample;
dst.denyEnderTeleport = src.denyEnderTeleport;
dst.denyWoolShear = src.denyWoolShear;
dst.denyAnimalFeeding = src.denyAnimalFeeding;
dst.denySpawnEgg = src.denySpawnEgg;
dst.denyWax = src.denyWax;
dst.denyMobAttack = src.denyMobAttack;
dst.denyVillagerTrade = src.denyVillagerTrade;
dst.denyRaid = src.denyRaid;
dst.denyFireSpread = src.denyFireSpread;
dst.denyAllEffects = src.denyAllEffects;
dst.denyItemFrame = src.denyItemFrame;
dst.denyEntityInteract = src.denyEntityInteract;
dst.enterMsg = src.enterMsg;
dst.leaveMsg = src.leaveMsg;
dst.enforceGameMode = src.enforceGameMode;
dst.isPublicBuilding = src.isPublicBuilding;
dst.allowVisitorTeleport = src.allowVisitorTeleport;
dst.peaceMode = src.peaceMode;
dst.peaceModeDuration = src.peaceModeDuration;
dst.clearAllBadEffects = src.clearAllBadEffects;
}
/** /**
* 获取领地默认deny状态(按字段名映射) * 获取领地默认deny状态(按字段名映射)
*/ */
@@ -3613,6 +3669,12 @@ public class AreaProtection implements Listener {
String newArea = null; String newArea = null;
String forcedMode = null; String forcedMode = null;
// ★ 第57批续22: 收集所有匹配领地,选面积最小的。
// 子领地完全包含在主领地内,ConcurrentHashMap 遍历顺序不定,
// 先 break 可能命中主领地 → 子领地 deny_move=1 不生效。
// 面积最小 = 子领地(完全包含时面积必然更小),天然优先级正确。
AreaConfig bestAc = null;
String bestName = null;
for (Map.Entry<String, AreaConfig> entry for (Map.Entry<String, AreaConfig> entry
: areas.entrySet()) { : areas.entrySet()) {
AreaConfig ac = entry.getValue(); AreaConfig ac = entry.getValue();
@@ -3628,11 +3690,25 @@ public class AreaProtection implements Listener {
if (px >= minX && px <= maxX if (px >= minX && px <= maxX
&& pz >= minZ && pz <= maxZ && pz >= minZ && pz <= maxZ
&& py >= ac.yMin && py <= ac.yMax) { && py >= ac.yMin && py <= ac.yMax) {
newArea = entry.getKey(); if (bestAc == null) {
forcedMode = ac.enforceGameMode; bestAc = ac;
break; bestName = entry.getKey();
} else {
long bestArea = (long)(Math.abs(bestAc.x1 - bestAc.x2) + 1)
* (Math.abs(bestAc.z1 - bestAc.z2) + 1);
long curArea = (long)(Math.abs(ac.x1 - ac.x2) + 1)
* (Math.abs(ac.z1 - ac.z2) + 1);
if (curArea <= bestArea) {
bestAc = ac;
bestName = entry.getKey();
}
}
} }
} }
if (bestName != null) {
newArea = bestName;
forcedMode = bestAc.enforceGameMode;
}
/* plugin.getLogger().info( /* plugin.getLogger().info(
"[防护-移动] " + p.getName() "[防护-移动] " + p.getName()
@@ -8010,6 +8086,10 @@ public class AreaProtection implements Listener {
cac.yMin = 0; cac.yMax = 255; cac.yMin = 0; cac.yMax = 255;
cac.createCost = chargedCost; // ★ 记录创建扣款(删除时按比例退费的基数) cac.createCost = chargedCost; // ★ 记录创建扣款(删除时按比例退费的基数)
cac.parentId = cnIsSub ? getLandIdFromDb(cnMain.name) : 0; // ★ 第57批续21 子领地父ID cac.parentId = cnIsSub ? getLandIdFromDb(cnMain.name) : 0; // ★ 第57批续21 子领地父ID
// ★ 第57批续22: 子领地创建时继承主领地权限一次(后续可单独调配)
if (cnIsSub) {
copyLandPermissions(cnMain, cac);
}
saveAreaToDb(cac); saveAreaToDb(cac);
areas.put(confirmName, cac); // ★ 立即更新内存 areas.put(confirmName, cac); // ★ 立即更新内存
if (cnIsSub) { if (cnIsSub) {
@@ -10705,6 +10785,10 @@ public class AreaProtection implements Listener {
nc.yMin = 0; nc.yMax = 255; nc.yMin = 0; nc.yMax = 255;
nc.createCost = charged; nc.createCost = charged;
nc.parentId = apIsSub ? getLandIdFromDb(apMain.name) : 0; // ★ 第57批续21 子领地父ID nc.parentId = apIsSub ? getLandIdFromDb(apMain.name) : 0; // ★ 第57批续21 子领地父ID
// ★ 第57批续22: 子领地创建时继承主领地权限一次(后续可单独调配)
if (apIsSub) {
copyLandPermissions(apMain, nc);
}
saveAreaToDb(nc); saveAreaToDb(nc);
areas.put(pe.landName, nc); areas.put(pe.landName, nc);
pendingExpands.remove(pe.landName.toLowerCase()); pendingExpands.remove(pe.landName.toLowerCase());
+50
View File
@@ -5096,6 +5096,11 @@ public class WebManager {
long created = longOf(row.get("created")); long created = longOf(row.get("created"));
if (pulled != null && created > 0) { if (pulled != null && created > 0) {
pulled.setCreated(new java.util.Date(created)); pulled.setCreated(new java.util.Date(created));
// ★ 第57批续22续: setCreated 只改内存对象,Bukkit BanList 无 updateBan API。
// banned-players.json 里保留的是 addBan 即当前时间,
// BanBroadcastManager 读文件 → 封禁播报全员时间混乱。
// 直接改写文件持久化真实创建时间。
persistBanCreatedToFile(target, created);
} }
plugin.getLogger().info("[对账] 已收回 Web 端封禁: " + (isIp ? "IP " : "") + target); plugin.getLogger().info("[对账] 已收回 Web 端封禁: " + (isIp ? "IP " : "") + target);
return true; return true;
@@ -5860,6 +5865,51 @@ public class WebManager {
} }
} }
/**
* ★ 第57批续22续: 封禁收回时持久化真实创建时间到 banned-players.json。
*
* setCreated 只改内存 BanEntry 对象,Bukkit BanList 没有 updateBan API,
* 文件里保留的是 addBan 时的当前时间。BanBroadcastManager 直接读文件,
* 不持久化 → 所有被收回的封禁 created 都变成收回时间 → 封禁播报全员时间陷入同一天。
*
* 方案:读 banned-players.json → Gson 解析 → 定位 target 对应条目 → 改 created 字段 → 写回。
* 文件不存在/解析失败/未找到 target → 安全返回(内存 setCreated 仍有效)。
*/
private void persistBanCreatedToFile(String target, long createdMs) {
if (target == null || target.isEmpty() || createdMs <= 0) return;
try {
java.io.File bf = new java.io.File(plugin.getServer().getWorldContainer(), "banned-players.json");
if (!bf.exists()) return;
String txt = new String(java.nio.file.Files.readAllBytes(bf.toPath()),
java.nio.charset.StandardCharsets.UTF_8);
com.google.gson.JsonElement root = com.google.gson.JsonParser.parseString(txt);
if (!root.isJsonArray()) return;
com.google.gson.JsonArray arr = root.getAsJsonArray();
boolean changed = false;
for (com.google.gson.JsonElement el : arr) {
if (el == null || !el.isJsonObject()) continue;
com.google.gson.JsonObject o = el.getAsJsonObject();
String name = o.has("name") ? o.get("name").getAsString() : "";
if (name.equals(target)) {
// Gson JsonPrimitive 无 Date 构造器,用 SimpleDateFormat 格式化为 ISO-8601
String isoTs = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", java.util.Locale.ROOT)
.format(new java.util.Date(createdMs));
o.add("created", new com.google.gson.JsonPrimitive(isoTs));
changed = true;
break;
}
}
if (changed) {
java.nio.file.Files.write(bf.toPath(),
arr.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8));
plugin.getLogger().info("[对账] 已持久化封禁创建时间: " + target
+ " created=" + createdMs);
}
} catch (Exception e) {
plugin.getLogger().warning("[对账] 持久化封禁创建时间失败: " + e.getMessage());
}
}
/** ★ 第57批根治:直接读 banned-players.json 判断是否为空 /** ★ 第57批根治:直接读 banned-players.json 判断是否为空
* MC原版可能备份{空文件后内存BanList仍残留旧数据,不能依赖 BanList * MC原版可能备份{空文件后内存BanList仍残留旧数据,不能依赖 BanList
*/ */