第57批续22: 子领地权限失效修复 + 封禁播报时间被篡改修复
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:
@@ -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());
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user