fix: 领地系统7个问题修复

1. 领地主禁止作为成员入库: addLandMember检查owner返回boolean
2. 成员独立权限编辑: permDefs补充denyMobAttack
3. 红石原件扩展: 拉杆、各种按钮、压力板、阳光传感器
4. 容器交互新增展示架: denyContainer同时拦截展示框
5. 管理员身份改为领地级: isLandAdmin/setLandAdmin基于area_land_permissions.role='admin'
6. Timer A定期同步在线玩家: 每10轮(~30-50秒)同步一次保持PHP心跳
7. 领地数据同步修复: PHP sync_lands补充deny_container/deny_mob_attack/deny_fire等新字段,hash检测改为精确字段hash
This commit is contained in:
2026-06-26 18:47:26 +08:00
parent 3a3f4f1cd2
commit f67efd0f8d
3 changed files with 133 additions and 45 deletions
+14 -35
View File
@@ -417,15 +417,14 @@ public class AreaGUIManager implements Listener {
// 获取per-player权限
Map<String, Boolean> playerPerms = areaProtect.getPlayerPermMap(landId, targetPlayer);
// ★ 管理员状态检测
org.bukkit.entity.Player targetBukkit = Bukkit.getPlayerExact(targetPlayer);
boolean isAdmin = targetBukkit != null && areaProtect.isAreaAdmin(targetBukkit);
// ★ 管理员状态检测(领地级管理员,非全局tag)
boolean isAdmin = areaProtect.isLandAdmin(landName, targetPlayer);
// ★ 管理员提示:管理员自动获得所有权限
if (isAdmin) {
inv.setItem(22, createItem(Material.GOLD_BLOCK, "§6§l管理员权限",
"§7该玩家是管理员,自动获得所有权限",
"§7无需单独配置权限"));
"§7该玩家是此领地管理员,自动获得所有权限",
"§7点击切换管理员身份"));
}
// 权限列表
@@ -991,6 +990,7 @@ public class AreaGUIManager implements Listener {
{"所有效果", "denyAllEffects"}, {"禁止展示框", "denyItemFrame"}, {"红石电路", "denyRedstoneInteraction"},
{"禁止门禁", "denyDoorInteraction"}, {"音频", "denyNoteblockJukebox"}, {"拴绳使用", "denyLead"},
{"农作物收获", "denyCropHarvest"}, {"剪切羊毛", "denyWoolShear"}, {"投喂动物", "denyAnimalFeeding"},
{"攻击生物", "denyMobAttack"},
{"玩家发光", "denyGlowing"}, {"和平模式", "peaceMode"}
};
@@ -1012,37 +1012,13 @@ public class AreaGUIManager implements Listener {
// ★ GUI模式不发送聊天消息
openMemberList(p, landName, 0);
} else if (raw == 47) {
// ★ 管理员按钮:切换管理员状态
org.bukkit.entity.Player targetBukkit = Bukkit.getPlayerExact(targetPlayer);
boolean isTargetAdmin = targetBukkit != null && areaProtect.isAreaAdmin(targetBukkit);
// ★ 管理员按钮:切换领地管理员状态(非全局tag)
boolean isTargetAdmin = areaProtect.isLandAdmin(landName, targetPlayer);
areaProtect.setLandAdmin(landName, targetPlayer, !isTargetAdmin);
if (!isTargetAdmin) {
// 设置管理员
org.bukkit.entity.Player targetOnline = Bukkit.getPlayerExact(targetPlayer);
if (targetOnline == null) {
p.sendMessage("§c玩家不在线,无法设置管理员");
} else {
ConfigManager cfg = plugin.getConfigMgr();
String tag = cfg != null ? cfg.areaProtectAdminTag : null;
if (tag != null && !tag.isEmpty()) {
targetOnline.addScoreboardTag(tag);
p.sendMessage("§a已将 §e" + targetPlayer + " §a设置为管理员");
targetOnline.sendMessage("§6[区域防护] §a你已被设置为管理员");
}
}
p.sendMessage("§a已将 §e" + targetPlayer + " §a设置为此领地管理员");
} else {
// 移除管理员
org.bukkit.entity.Player targetOnline = Bukkit.getPlayerExact(targetPlayer);
if (targetOnline == null) {
p.sendMessage("§c玩家不在线,无法移除管理员");
} else {
ConfigManager cfg = plugin.getConfigMgr();
String tag = cfg != null ? cfg.areaProtectAdminTag : null;
if (tag != null && !tag.isEmpty()) {
targetOnline.removeScoreboardTag(tag);
p.sendMessage("§a已将 §e" + targetPlayer + " §a移除管理员");
targetOnline.sendMessage("§6[区域防护] §c你已被移除管理员");
}
}
p.sendMessage("§a已将 §e" + targetPlayer + " §a移除此领地管理员");
}
openPlayerPerm(p, landName, targetPlayer);
} else if (raw == 49) {
@@ -1080,7 +1056,10 @@ public class AreaGUIManager implements Listener {
if (meta != null && meta.getDisplayName() != null) {
String playerName = meta.getDisplayName()
.replaceAll("§[0-9a-fk-orA-FK-OR]", "");
areaProtect.addLandMember(landName, playerName);
boolean added = areaProtect.addLandMember(landName, playerName);
if (!added) {
p.sendMessage("§c§l[区域防护] §f领地主不能作为成员添加");
}
// ★ GUI模式不发送聊天消息
openAddMember(p, landName);
}
+94 -8
View File
@@ -2330,6 +2330,56 @@ public class AreaProtection implements Listener {
return list;
}
/**
* 检查玩家是否是某领地的管理员(role='admin')
*/
public boolean isLandAdmin(String landName, String playerName) {
int landId = getLandIdFromDb(landName);
if (landId <= 0) return false;
if (dbConnection == null) return false;
try {
PreparedStatement stmt = dbConnection.prepareStatement(
"SELECT role FROM area_land_permissions WHERE land_id = ? AND player_name = ?");
stmt.setInt(1, landId);
stmt.setString(2, playerName);
ResultSet rs = stmt.executeQuery();
boolean isAdmin = false;
if (rs.next()) {
isAdmin = "admin".equalsIgnoreCase(rs.getString("role"));
}
rs.close();
stmt.close();
return isAdmin;
} catch (SQLException e) {
return false;
}
}
/**
* 设置/取消玩家的领地管理员身份
*/
public void setLandAdmin(String landName, String playerName, boolean admin) {
int landId = getLandIdFromDb(landName);
if (landId <= 0) return;
if (dbConnection == null) return;
try {
String role = admin ? "admin" : "member";
PreparedStatement stmt = dbConnection.prepareStatement(
"INSERT INTO area_land_permissions (land_id, player_name, role, permissions, granted_at) "
+ "VALUES (?, ?, ?, '', ?) "
+ "ON CONFLICT(land_id, player_name) DO UPDATE SET role = ?");
stmt.setInt(1, landId);
stmt.setString(2, playerName);
stmt.setString(3, role);
stmt.setLong(4, System.currentTimeMillis() / 1000);
stmt.setString(5, role);
stmt.executeUpdate();
stmt.close();
} catch (SQLException e) {
// 忽略
}
}
/**
* ★ 核心方法:获取玩家的effective deny状态
* 优先级:per-player权限 > 领地默认权限
@@ -3920,13 +3970,18 @@ public class AreaProtection implements Listener {
}
/**
* 添加领地成员
* 添加领地成员(领地主自动跳过,不允许作为成员入库)
*/
public void addLandMember(String landName, String playerName) {
public boolean addLandMember(String landName, String playerName) {
AreaConfig ac = getLand(landName);
if (ac != null && ac.owner != null && ac.owner.equalsIgnoreCase(playerName)) {
return false; // 领地主不能作为成员添加
}
Set<String> members = areaPlayerWhitelist.computeIfAbsent(
landName, k -> ConcurrentHashMap.newKeySet());
members.add(playerName.toLowerCase());
saveWhitelists();
return true;
}
/**
@@ -4545,7 +4600,7 @@ public class AreaProtection implements Listener {
}
}
// ★ 红石交互检测(中继器、比较器)
// ★ 红石交互检测(中继器、比较器、拉杆、按钮、压力板、阳光传感器)
@EventHandler
public void onRedstoneInteract(PlayerInteractEvent e) {
if (e.getClickedBlock() == null) return;
@@ -4558,8 +4613,28 @@ public class AreaProtection implements Listener {
e.getClickedBlock().getZ());
if (ac == null) return;
if (!getEffectiveDeny(p, ac, "denyRedstoneInteraction")) return;
String name = mat.name();
if (mat == Material.REPEATER || mat == Material.COMPARATOR
|| mat.name().contains("REPEATER") || mat.name().contains("COMPARATOR")) {
|| mat == Material.LEVER
|| mat == Material.STONE_BUTTON || mat == Material.OAK_BUTTON
|| mat == Material.SPRUCE_BUTTON || mat == Material.BIRCH_BUTTON
|| mat == Material.JUNGLE_BUTTON || mat == Material.ACACIA_BUTTON
|| mat == Material.DARK_OAK_BUTTON || mat == Material.MANGROVE_BUTTON
|| mat == Material.CHERRY_BUTTON || mat == Material.BAMBOO_BUTTON
|| mat == Material.CRIMSON_BUTTON || mat == Material.WARPED_BUTTON
|| mat == Material.POLISHED_BLACKSTONE_BUTTON
|| mat == Material.STONE_PRESSURE_PLATE || mat == Material.OAK_PRESSURE_PLATE
|| mat == Material.SPRUCE_PRESSURE_PLATE || mat == Material.BIRCH_PRESSURE_PLATE
|| mat == Material.JUNGLE_PRESSURE_PLATE || mat == Material.ACACIA_PRESSURE_PLATE
|| mat == Material.DARK_OAK_PRESSURE_PLATE || mat == Material.MANGROVE_PRESSURE_PLATE
|| mat == Material.CHERRY_PRESSURE_PLATE || mat == Material.BAMBOO_PRESSURE_PLATE
|| mat == Material.CRIMSON_PRESSURE_PLATE || mat == Material.WARPED_PRESSURE_PLATE
|| mat == Material.POLISHED_BLACKSTONE_PRESSURE_PLATE || mat == Material.LIGHT_WEIGHTED_PRESSURE_PLATE
|| mat == Material.HEAVY_WEIGHTED_PRESSURE_PLATE
|| mat == Material.DAYLIGHT_DETECTOR
|| name.contains("REPEATER") || name.contains("COMPARATOR")
|| name.contains("BUTTON") || name.contains("PRESSURE_PLATE")
|| name.contains("DAYLIGHT")) {
e.setCancelled(true);
p.sendMessage("§c§l[区域防护] §f禁止操作红石元件");
}
@@ -7275,6 +7350,13 @@ public class AreaProtection implements Listener {
return true;
}
// 检查是否是领地主
AreaConfig ac = getLand(landName);
if (ac != null && ac.owner != null && ac.owner.equalsIgnoreCase(playerName)) {
p.sendMessage("§c§l[添加成员] §f领地主不能作为成员添加");
return true;
}
// 添加成员
addLandMember(landName, playerName);
p.sendMessage("§a§l[添加成员] §f已成功添加 " + playerName + " 为领地成员");
@@ -7975,7 +8057,9 @@ public class AreaProtection implements Listener {
// ★ 使用统一的权限检查方法(支持per-player权限)
AreaConfig ac = findFrameArea(clicked);
if (ac == null || !getEffectiveDeny(p, ac, "denyItemFrame")) return;
if (ac == null) return;
// ★ 展示框同时受denyItemFrame和denyContainer控制
if (!getEffectiveDeny(p, ac, "denyItemFrame") && !getEffectiveDeny(p, ac, "denyContainer")) return;
// 非白名单 → 拦截
// 记录当前旋转角度
@@ -8029,8 +8113,8 @@ public class AreaProtection implements Listener {
AreaConfig ac = findFrameArea(entity);
if (ac == null) return;
// ★ 使用统一的权限检查方法(支持per-player权限)
if (!getEffectiveDeny(p, ac, "denyItemFrame")) return;
// ★ 展示框同时受denyItemFrame和denyContainer控制
if (!getEffectiveDeny(p, ac, "denyItemFrame") && !getEffectiveDeny(p, ac, "denyContainer")) return;
e.setCancelled(true);
p.sendMessage("§c§l[区域防护] §f禁止破坏展示框");
@@ -8102,7 +8186,9 @@ public class AreaProtection implements Listener {
// ★ 使用统一的权限检查方法(支持per-player权限)
AreaConfig ac = findFrameArea(hit);
if (ac == null || !getEffectiveDeny(shooter, ac, "denyItemFrame")) return;
if (ac == null) return;
// ★ 展示框同时受denyItemFrame和denyContainer控制
if (!getEffectiveDeny(shooter, ac, "denyItemFrame") && !getEffectiveDeny(shooter, ac, "denyContainer")) return;
e.setCancelled(true);
shooter.sendMessage("§c§l[区域防护] §f禁止破坏展示框");
+25 -2
View File
@@ -1473,6 +1473,10 @@ public class WebManager {
plugin.getLogger().info("[Web通信] ★ 合并定时器已启动(随机偏移A=" + (randA/20) + "s B=" + (randB/20) + "s C=" + (randC/20) + "s)");
}
// Timer A 内部计数器:每N轮同步一次在线玩家(保持PHP心跳不断)
private int timerACycleCount = 0;
private static final int SYNC_ONLINE_EVERY_N_CYCLES = 10; // ~30-50秒同步一次
/**
* 定时器A — 注册登录(3~5秒快速轮询)
* ★ v19: 3个请求串行化执行(不再并行),每个间隔2-3秒,彻底避免PHP锁库
@@ -1526,6 +1530,16 @@ public class WebManager {
});
}
// ★ 定期同步在线玩家(保持PHP心跳,全员下线时也推送空列表)
timerACycleCount++;
if (timerACycleCount >= SYNC_ONLINE_EVERY_N_CYCLES) {
timerACycleCount = 0;
submitDbTask("TimerA-syncOnline", () -> {
try { syncOnlinePlayers(); }
catch (Exception e) { /* 静默 */ }
}, true);
}
// 自调度下一轮(3~5秒,快速响应登录请求,错峰避免锁库)
scheduleTimerA(calcStaggeredDelay(TIMER_A, 3, 5));
}
@@ -2716,8 +2730,17 @@ public class WebManager {
List<Map<String, Object>> lands = areaProtect.getAllLandsForSync();
List<Map<String, Object>> shopItems = areaProtect.getPermissionShopForSync();
// ★ 无变化静默
String currentHash = lands.size() + ":" + lands.hashCode() + "|" + shopItems.size() + ":" + shopItems.hashCode();
// ★ 无变化静默:用JSON内容hash检测(比hashCode更可靠)
StringBuilder hashBuilder = new StringBuilder();
for (Map<String, Object> land : lands) {
hashBuilder.append(land.getOrDefault("id", 0)).append(":");
hashBuilder.append(land.getOrDefault("owner", "")).append(":");
hashBuilder.append(land.getOrDefault("deny_container", 0)).append(":");
hashBuilder.append(land.getOrDefault("deny_mob_attack", 0)).append(":");
hashBuilder.append(land.getOrDefault("deny_fire", 0)).append(":");
hashBuilder.append(land.getOrDefault("warp_x", 0)).append("|");
}
String currentHash = lands.size() + ":" + hashBuilder.toString();
if (currentHash.equals(lastLandDataHash)) return;
lastLandDataHash = currentHash;