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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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禁止破坏展示框");
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user