feat: 成员独立权限系统 + PHP→Java同步 + SSL兼容性

This commit is contained in:
youpaishidifu
2026-06-23 23:46:44 +08:00
parent a3030c3b80
commit 8cd380a93b
6 changed files with 1084 additions and 123 deletions
+342 -1
View File
@@ -320,8 +320,10 @@ public class AreaCLIManager {
// ★ 细分管理选项
p.sendMessage(clickableAction("👤", "用户管理",
"/protect cli members " + land.name + " 1"));
p.sendMessage(clickableAction("🔑", "访客授权",
p.sendMessage(clickableAction("🔑", "访客授权(全局)",
"/protect cli visitorperm " + land.name + " 1"));
p.sendMessage(clickableAction("🎯", "成员权限(独立)",
"/protect cli memberperm " + land.name + " 1"));
// ★ 新增:设置传送点
p.sendMessage(clickableAction("📍", "设置传送点(当前位置)",
"/protect settp " + land.name));
@@ -546,11 +548,17 @@ public class AreaCLIManager {
String key;
String name;
boolean enabled;
boolean overridden; // 是否有per-player覆盖
PermItem(String key, String name, boolean enabled) {
this(key, name, enabled, false);
}
PermItem(String key, String name, boolean enabled, boolean overridden) {
this.key = key;
this.name = name;
this.enabled = enabled;
this.overridden = overridden;
}
}
@@ -622,4 +630,337 @@ public class AreaCLIManager {
default: return key;
}
}
// ==================== Per-Player 独立权限管理 ====================
/**
* 显示成员列表(点击成员进入per-player权限编辑)
*/
public void showMemberPermList(Player p, String landName, int page) {
savePageInfo(p, "memberperm", landName, page);
AreaProtection.AreaConfig land = areaProtect.getLand(landName);
if (land == null) {
p.sendMessage(Component.text("§c领地不存在: " + landName));
return;
}
// 获取成员列表
Set<String> members = areaProtect.getAreaMembers(landName);
if (members == null || members.isEmpty()) {
p.sendMessage(header("成员权限: " + landName));
p.sendMessage(Component.text("§7暂无成员,请先使用 §e/protect addvisitor §7添加"));
p.sendMessage(Component.empty()
.append(Component.text("§a[◀ 返回管理]")
.clickEvent(ClickEvent.runCommand(
"/protect cli manage " + land.name + " 1"))));
return;
}
List<String> memberList = new ArrayList<>(members);
int totalPages = Math.max(1, (int) Math.ceil((double) memberList.size() / PAGE_SIZE));
page = Math.max(1, Math.min(page, totalPages));
p.sendMessage(header("成员权限: " + landName + " §7第" + page + "/" + totalPages + "页"));
int start = (page - 1) * PAGE_SIZE;
int end = Math.min(start + PAGE_SIZE, memberList.size());
for (int i = start; i < end; i++) {
String member = memberList.get(i);
boolean isOwner = member.equalsIgnoreCase(land.owner);
String roleTag = isOwner ? " §c[所有者]" : "";
Component line = Component.empty();
line = line.append(Component.text("§e● " + member + roleTag));
line = line.append(Component.text(" "));
line = line.append(Component.text("§a[编辑权限]")
.hoverEvent(HoverEvent.showText(
Component.text("§e设置该成员的独立权限")))
.clickEvent(ClickEvent.runCommand(
"/protect cli playerperm " + land.name + " " + member + " 1")));
p.sendMessage(line);
}
// 分页
if (totalPages > 1) {
Component pagination = Component.empty();
if (page > 1) {
pagination = pagination.append(Component.text("§a[◀ 上一页]")
.clickEvent(ClickEvent.runCommand(
"/protect cli memberperm " + land.name + " " + (page - 1))));
pagination = pagination.append(Component.text(" "));
}
pagination = pagination.append(Component.text("§7" + page + "/" + totalPages));
if (page < totalPages) {
pagination = pagination.append(Component.text(" "));
pagination = pagination.append(Component.text("§a[下一页 ▶]")
.clickEvent(ClickEvent.runCommand(
"/protect cli memberperm " + land.name + " " + (page + 1))));
}
p.sendMessage(pagination);
}
p.sendMessage(Component.empty()
.append(Component.text("§a[◀ 返回管理]")
.clickEvent(ClickEvent.runCommand(
"/protect cli manage " + land.name + " " + page))));
p.sendMessage(Component.text("§7§l───────────────────────────────"));
}
/**
* 显示某个成员的独立权限编辑页
*/
public void showPlayerPerm(Player p, String landName, String targetPlayer, int page) {
savePageInfo(p, "playerperm", landName + ":" + targetPlayer, page);
AreaProtection.AreaConfig land = areaProtect.getLand(landName);
if (land == null) {
p.sendMessage(Component.text("§c领地不存在: " + landName));
return;
}
int landId = areaProtect.getLandIdFromDb(landName);
if (landId <= 0) {
p.sendMessage(Component.text("§c领地数据库ID获取失败"));
return;
}
// 获取该成员的per-player权限
Map<String, Boolean> playerPerms = areaProtect.getPlayerPermMap(landId, targetPlayer);
List<PermItem> perms = getPermListWithOverrides(land, playerPerms);
int totalPages = Math.max(1, (int) Math.ceil((double) perms.size() / PAGE_SIZE));
page = Math.max(1, Math.min(page, totalPages));
p.sendMessage(header(targetPlayer + " 的独立权限"));
// 显示说明
p.sendMessage(Component.text("§7§l说明:"));
p.sendMessage(Component.text(" §a✔ 已启用 §7= 允许该操作"));
p.sendMessage(Component.text(" §c✘ 已禁用 §7= 禁止该操作"));
p.sendMessage(Component.text(" §e★ 自定义 §7= 与领地默认不同"));
p.sendMessage(Component.text(""));
int start = (page - 1) * PAGE_SIZE;
int end = Math.min(start + PAGE_SIZE, perms.size());
for (int i = start; i < end; i++) {
PermItem perm = perms.get(i);
String icon;
if (perm.overridden) {
icon = perm.enabled ? "§a★" : "§c★"; // 星号表示per-player覆盖
} else {
icon = perm.enabled ? "§a✔" : "§c✘";
}
String state = perm.enabled ? "§a允许" : "§c禁止";
String overrideTag = perm.overridden ? " §e[自定义]" : " §7[领地默认]";
Component line = Component.empty();
line = line.append(Component.text(icon + " §f" + perm.name + " §7" + state + overrideTag));
line = line.append(Component.text(" "));
line = line.append(Component.text("§e[切换]")
.hoverEvent(HoverEvent.showText(
Component.text("§e切换该成员的此权限")))
.clickEvent(ClickEvent.runCommand(
"/protect cli toggleplayerperm " + land.name + " " + targetPlayer + " " + perm.key + " " + page)));
p.sendMessage(line);
}
// 分页
if (totalPages > 1) {
Component pagination = Component.empty();
if (page > 1) {
pagination = pagination.append(Component.text("§a[◀ 上一页]")
.clickEvent(ClickEvent.runCommand(
"/protect cli playerperm " + land.name + " " + targetPlayer + " " + (page - 1))));
pagination = pagination.append(Component.text(" "));
}
pagination = pagination.append(Component.text("§7" + page + "/" + totalPages));
if (page < totalPages) {
pagination = pagination.append(Component.text(" "));
pagination = pagination.append(Component.text("§a[下一页 ▶]")
.clickEvent(ClickEvent.runCommand(
"/protect cli playerperm " + land.name + " " + targetPlayer + " " + (page + 1))));
}
p.sendMessage(pagination);
}
// 清除按钮
p.sendMessage(Component.empty()
.append(Component.text("§c[清除所有自定义权限]")
.hoverEvent(HoverEvent.showText(
Component.text("§c将该成员的权限恢复为领地默认")))
.clickEvent(ClickEvent.runCommand(
"/protect cli clearplayerperm " + land.name + " " + targetPlayer))));
p.sendMessage(Component.empty()
.append(Component.text("§a[◀ 返回成员列表]")
.clickEvent(ClickEvent.runCommand(
"/protect cli memberperm " + land.name + " 1"))));
p.sendMessage(Component.text("§7§l───────────────────────────────"));
}
/**
* 切换某个成员的独立权限
*/
public void togglePlayerPerm(Player p, String landName, String targetPlayer, String permKey, int page) {
AreaProtection.AreaConfig land = areaProtect.getLand(landName);
if (land == null) {
p.sendMessage(Component.text("§c领地不存在: " + landName));
return;
}
// 权限检查
boolean isOwner = p.getName().equalsIgnoreCase(land.owner);
boolean isAdmin = areaProtect.isAreaAdmin(p);
if (!isOwner && !isAdmin) {
p.sendMessage(Component.text("§c需要领地所有者或管理员权限"));
return;
}
int landId = areaProtect.getLandIdFromDb(landName);
if (landId <= 0) {
p.sendMessage(Component.text("§c领地数据库ID获取失败"));
return;
}
// 获取当前值,判断是否已有覆盖
Map<String, Boolean> currentPerms = areaProtect.getPlayerPermMap(landId, targetPlayer);
boolean currentVal = currentPerms.getOrDefault(permKey, getLandDefaultVal(land, permKey));
// 切换
boolean newVal = !currentVal;
areaProtect.setPlayerPerm(landId, targetPlayer, permKey, newVal);
String permName = getPermNameByKey(permKey);
p.sendMessage("§a已设置 §e" + targetPlayer + " §a的 §e" + permName + " §7→ " + (newVal ? "§a允许" : "§c禁止"));
// 刷新页面
showPlayerPerm(p, landName, targetPlayer, page);
}
/**
* 清除某成员所有per-player权限覆盖
*/
public void clearPlayerPerm(Player p, String landName, String targetPlayer) {
AreaProtection.AreaConfig land = areaProtect.getLand(landName);
if (land == null) {
p.sendMessage(Component.text("§c领地不存在: " + landName));
return;
}
boolean isOwner = p.getName().equalsIgnoreCase(land.owner);
boolean isAdmin = areaProtect.isAreaAdmin(p);
if (!isOwner && !isAdmin) {
p.sendMessage(Component.text("§c需要领地所有者或管理员权限"));
return;
}
int landId = areaProtect.getLandIdFromDb(landName);
if (landId <= 0) {
p.sendMessage(Component.text("§c领地数据库ID获取失败"));
return;
}
areaProtect.setPlayerPermJson(landId, targetPlayer, "");
p.sendMessage("§a已清除 §e" + targetPlayer + " §a的所有自定义权限,恢复为领地默认");
// 返回成员列表
showMemberPermList(p, landName, 1);
}
/**
* 获取带per-player覆盖的权限列表
*/
private List<PermItem> getPermListWithOverrides(AreaProtection.AreaConfig land, Map<String, Boolean> playerPerms) {
List<PermItem> perms = new ArrayList<>();
String[][] permDefs = {
{"move", "移动", "denyMove"},
{"block_place", "放置方块", "denyBlockPlace"},
{"block_break", "破坏方块", "denyBlockBreak"},
{"pvp", "玩家对战", "denyPVP"},
{"mount", "骑乘坐具", "denyMount"},
{"ender_pearl", "投掷末影珍珠", "denyEnderPearl"},
{"thrown_projectiles", "投掷物(三叉戟/雪球/风蛋)", "denyThrownProjectiles"},
{"raid", "袭击侦测", "denyRaid"},
{"bow", "弓箭射击", "denyBow"},
{"potion", "药水效果", "denyPotion"},
{"fire", "点燃", "denyFire"},
{"fire_spread", "火焰蔓延", "denyFireSpread"},
{"pickup", "拾取物品", "denyPickup"},
{"drop", "丢弃物品", "denyDrop"},
{"explosion", "爆炸", "denyExplosion"},
{"fall_damage", "摔落伤害", "denyFallDamage"},
{"hunger", "饥饿", "denyHunger"},
{"all_damage", "所有伤害", "denyAllDamage"},
{"all_effects", "所有效果", "denyAllEffects"},
{"item_frame", "展示框交互", "denyItemFrame"},
{"redstone", "红石电路(中继器/比较器)", "denyRedstoneInteraction"},
{"door", "门禁(门/按钮/压力板)", "denyDoorInteraction"},
{"audio", "音频(音符盒/唱片机)", "denyNoteblockJukebox"},
{"lead", "拴绳使用", "denyLead"},
{"crop_harvest", "农作物收获", "denyCropHarvest"},
{"wool_shear", "剪切羊毛/生物", "denyWoolShear"},
{"animal_feed", "投喂动物", "denyAnimalFeeding"},
{"glowing", "玩家发光", "denyGlowing"},
{"peace_mode", "和平模式", "peaceMode"}
};
for (String[] def : permDefs) {
String key = def[0];
String name = def[1];
String field = def[2];
boolean landDefault = getLandDefaultVal(land, field);
// 如果有per-player覆盖,使用覆盖值;否则使用领地默认
if (playerPerms.containsKey(field)) {
boolean playerVal = playerPerms.get(field);
// 注意:per-player存储的是deny值
// perm.enabled = true 表示"允许操作"(即deny=false)
perms.add(new PermItem(key, name, !playerVal, true));
} else {
perms.add(new PermItem(key, name, !landDefault, false));
}
}
return perms;
}
/**
* 获取领地默认deny值
*/
private boolean getLandDefaultVal(AreaProtection.AreaConfig land, String field) {
switch (field) {
case "denyMove": return land.denyMove;
case "denyBlockPlace": return land.denyBlockPlace;
case "denyBlockBreak": return land.denyBlockBreak;
case "denyPVP": return land.denyPVP;
case "denyMount": return land.denyMount;
case "denyEnderPearl": return land.denyEnderPearl;
case "denyThrownProjectiles": return land.denyThrownProjectiles;
case "denyRaid": return land.denyRaid;
case "denyBow": return land.denyBow;
case "denyPotion": return land.denyPotion;
case "denyFire": return land.denyFire;
case "denyFireSpread": return land.denyFireSpread;
case "denyPickup": return land.denyPickup;
case "denyDrop": return land.denyDrop;
case "denyExplosion": return land.denyExplosion;
case "denyFallDamage": return land.denyFallDamage;
case "denyHunger": return land.denyHunger;
case "denyAllDamage": return land.denyAllDamage;
case "denyAllEffects": return land.denyAllEffects;
case "denyItemFrame": return land.denyItemFrame;
case "denyRedstoneInteraction": return land.denyRedstoneInteraction;
case "denyDoorInteraction": return land.denyDoorInteraction;
case "denyNoteblockJukebox": return land.denyNoteblockJukebox;
case "denyLead": return land.denyLead;
case "denyCropHarvest": return land.denyCropHarvest;
case "denyWoolShear": return land.denyWoolShear;
case "denyAnimalFeeding": return land.denyAnimalFeeding;
case "denyGlowing": return land.denyGlowing;
case "peaceMode": return land.peaceMode;
default: return false;
}
}
}
@@ -32,6 +32,8 @@ public class AreaGUIManager implements Listener {
private static final String T_VISITOR_PERM = "§6§l访客权限";
private static final String T_ADD_MEMBER = "§6§l添加成员";
private static final String T_LAND_SETTINGS = "§6§l领地设置";
private static final String T_MEMBER_PERM_LIST = "§6§l成员权限列表";
private static final String T_PLAYER_PERM = "§6§l成员权限";
// 每页显示数量
private static final int PAGE_SIZE = 45;
@@ -44,6 +46,9 @@ public class AreaGUIManager implements Listener {
// 玩家当前管理的领地
private final Map<UUID, String> managingLand = new HashMap<>();
// ★ 玩家当前编辑的per-player目标玩家
private final Map<UUID, String> playerPermTarget = new HashMap<>();
public AreaGUIManager(Main plugin, AreaProtection areaProtect) {
this.plugin = plugin;
this.areaProtect = areaProtect;
@@ -309,6 +314,173 @@ public class AreaGUIManager implements Listener {
p.openInventory(inv);
}
/**
* 打开成员权限列表(per-player权限编辑入口)
*/
public void openMemberPermList(Player p, String landName) {
managingLand.put(p.getUniqueId(), landName);
Inventory inv = Bukkit.createInventory(null, 54, T_MEMBER_PERM_LIST + " - " + landName);
AreaProtection.AreaConfig land = areaProtect.getLand(landName);
if (land == null) {
p.sendMessage("§c领地不存在");
return;
}
// 获取成员列表
Set<String> members = areaProtect.getLandMembers(landName);
List<String> memberList = new ArrayList<>(members);
int slot = 0;
for (String member : memberList) {
if (slot >= 45) break;
boolean isOwner = member.equalsIgnoreCase(land.owner);
String roleTag = isOwner ? " §c[所有者]" : "";
// 检查是否有自定义权限
int landId = areaProtect.getLandIdFromDb(landName);
String permJson = "";
if (landId > 0) {
permJson = areaProtect.getPlayerPermJson(landId, member);
}
boolean hasCustom = permJson != null && !permJson.isEmpty();
String customTag = hasCustom ? " §e[自定义]" : "";
ItemStack playerItem = createItem(Material.PLAYER_HEAD,
"§a§l" + member + roleTag + customTag,
"§7左键编辑该成员的独立权限",
"",
"§e点击编辑");
SkullMeta skull = (SkullMeta) playerItem.getItemMeta();
if (skull != null) {
try {
Player target = Bukkit.getPlayer(member);
if (target != null) skull.setOwningPlayer(target);
} catch (Exception ignored) {}
playerItem.setItemMeta(skull);
}
inv.setItem(slot, playerItem);
slot++;
}
// 返回按钮(位置48)
inv.setItem(48, createItem(Material.ARROW, "§c§l返回管理领地", ""));
p.openInventory(inv);
}
/**
* 打开某个成员的独立权限编辑页面
*/
public void openPlayerPerm(Player p, String landName, String targetPlayer) {
managingLand.put(p.getUniqueId(), landName);
// 用 playerPermTarget 保存目标玩家
playerPermTarget.put(p.getUniqueId(), targetPlayer);
Inventory inv = Bukkit.createInventory(null, 54, T_PLAYER_PERM + " - " + targetPlayer);
AreaProtection.AreaConfig land = areaProtect.getLand(landName);
if (land == null) {
p.sendMessage("§c领地不存在");
return;
}
int landId = areaProtect.getLandIdFromDb(landName);
if (landId <= 0) {
p.sendMessage("§c领地数据库ID获取失败");
return;
}
// 获取per-player权限
Map<String, Boolean> playerPerms = areaProtect.getPlayerPermMap(landId, targetPlayer);
// 权限列表
String[][] permDefs = {
{"移动", "denyMove"}, {"放置方块", "denyBlockPlace"}, {"破坏方块", "denyBlockBreak"},
{"玩家对战", "denyPVP"}, {"骑乘坐具", "denyMount"}, {"投掷末影珍珠", "denyEnderPearl"},
{"投掷物", "denyThrownProjectiles"}, {"袭击侦测", "denyRaid"}, {"弓箭射击", "denyBow"},
{"药水效果", "denyPotion"}, {"点燃", "denyFire"}, {"火焰蔓延", "denyFireSpread"},
{"拾取物品", "denyPickup"}, {"丢弃物品", "denyDrop"}, {"爆炸", "denyExplosion"},
{"摔落伤害", "denyFallDamage"}, {"饥饿", "denyHunger"}, {"所有伤害", "denyAllDamage"},
{"所有效果", "denyAllEffects"}, {"展示框交互", "denyItemFrame"}, {"红石电路", "denyRedstoneInteraction"},
{"门禁", "denyDoorInteraction"}, {"音频", "denyNoteblockJukebox"}, {"拴绳使用", "denyLead"},
{"农作物收获", "denyCropHarvest"}, {"剪切羊毛", "denyWoolShear"}, {"投喂动物", "denyAnimalFeeding"},
{"玩家发光", "denyGlowing"}, {"和平模式", "peaceMode"}
};
int slot = 0;
for (String[] def : permDefs) {
if (slot >= 45) break;
String name = def[0];
String field = def[1];
boolean landDefault = getLandDefaultVal(land, field);
boolean isOverridden = playerPerms.containsKey(field);
boolean effective = isOverridden ? playerPerms.get(field) : landDefault;
// enabled = true 表示"允许操作"(即deny=false)
boolean allowed = !effective;
Material mat = allowed ? Material.LIME_DYE : Material.GRAY_DYE;
String status = allowed ? "§a✔ 允许" : "§c✘ 禁止";
String overrideTag = isOverridden ? " §e★自定义" : " §7[默认]";
inv.setItem(slot, createItem(mat,
"§e§l" + name + overrideTag,
status,
"",
"§e点击切换"));
slot++;
}
// 清除所有自定义权限按钮(位置48)
inv.setItem(48, createItem(Material.BARRIER, "§c§l清除所有自定义",
"§7恢复为领地默认权限"));
// 返回按钮(位置49)
inv.setItem(49, createItem(Material.ARROW, "§a§l返回成员列表", ""));
p.openInventory(inv);
}
/**
* 获取领地默认deny值(按字段名)
*/
private boolean getLandDefaultVal(AreaProtection.AreaConfig land, String field) {
switch (field) {
case "denyMove": return land.denyMove;
case "denyBlockPlace": return land.denyBlockPlace;
case "denyBlockBreak": return land.denyBlockBreak;
case "denyPVP": return land.denyPVP;
case "denyMount": return land.denyMount;
case "denyEnderPearl": return land.denyEnderPearl;
case "denyThrownProjectiles": return land.denyThrownProjectiles;
case "denyRaid": return land.denyRaid;
case "denyBow": return land.denyBow;
case "denyPotion": return land.denyPotion;
case "denyFire": return land.denyFire;
case "denyFireSpread": return land.denyFireSpread;
case "denyPickup": return land.denyPickup;
case "denyDrop": return land.denyDrop;
case "denyExplosion": return land.denyExplosion;
case "denyFallDamage": return land.denyFallDamage;
case "denyHunger": return land.denyHunger;
case "denyAllDamage": return land.denyAllDamage;
case "denyAllEffects": return land.denyAllEffects;
case "denyItemFrame": return land.denyItemFrame;
case "denyRedstoneInteraction": return land.denyRedstoneInteraction;
case "denyDoorInteraction": return land.denyDoorInteraction;
case "denyNoteblockJukebox": return land.denyNoteblockJukebox;
case "denyLead": return land.denyLead;
case "denyCropHarvest": return land.denyCropHarvest;
case "denyWoolShear": return land.denyWoolShear;
case "denyAnimalFeeding": return land.denyAnimalFeeding;
case "denyGlowing": return land.denyGlowing;
case "peaceMode": return land.peaceMode;
default: return false;
}
}
/**
* 打开添加成员菜单
*/
@@ -548,6 +720,9 @@ public class AreaGUIManager implements Listener {
openMemberList(p, landName, 0);
} else if (raw == 13) {
openVisitorPerm(p, landName, 0);
} else if (raw == 14) {
// ★ 成员权限(独立)
openMemberPermList(p, landName);
} else if (raw == 15) {
openLandSettings(p, landName);
} else if (raw == 31) {
@@ -634,6 +809,83 @@ public class AreaGUIManager implements Listener {
return;
}
// ★ 成员权限列表
if (title.startsWith(T_MEMBER_PERM_LIST)) {
event.setCancelled(true);
String landName = managingLand.get(p.getUniqueId());
if (landName == null) return;
if (raw >= 0 && raw < 45) {
ItemStack item = event.getView().getTopInventory().getItem(raw);
if (item != null && item.getType() == Material.PLAYER_HEAD) {
ItemMeta meta = item.getItemMeta();
if (meta != null && meta.getDisplayName() != null) {
String playerName = meta.getDisplayName()
.replaceAll("§[0-9a-fk-orA-FK-OR]", "")
.replaceAll("\\[所有者\\]", "")
.replaceAll("\\[自定义\\]", "")
.trim();
openPlayerPerm(p, landName, playerName);
}
}
} else if (raw == 48) {
openLandManage(p, landName);
}
return;
}
// ★ 成员权限编辑
if (title.startsWith(T_PLAYER_PERM)) {
event.setCancelled(true);
String landName = managingLand.get(p.getUniqueId());
String targetPlayer = playerPermTarget.get(p.getUniqueId());
if (landName == null || targetPlayer == null) return;
AreaProtection.AreaConfig land = areaProtect.getLand(landName);
if (land == null) return;
int landId = areaProtect.getLandIdFromDb(landName);
if (landId <= 0) return;
if (raw >= 0 && raw < 45) {
// 切换权限
String[][] permDefs = {
{"移动", "denyMove"}, {"放置方块", "denyBlockPlace"}, {"破坏方块", "denyBlockBreak"},
{"玩家对战", "denyPVP"}, {"骑乘坐具", "denyMount"}, {"投掷末影珍珠", "denyEnderPearl"},
{"投掷物", "denyThrownProjectiles"}, {"袭击侦测", "denyRaid"}, {"弓箭射击", "denyBow"},
{"药水效果", "denyPotion"}, {"点燃", "denyFire"}, {"火焰蔓延", "denyFireSpread"},
{"拾取物品", "denyPickup"}, {"丢弃物品", "denyDrop"}, {"爆炸", "denyExplosion"},
{"摔落伤害", "denyFallDamage"}, {"饥饿", "denyHunger"}, {"所有伤害", "denyAllDamage"},
{"所有效果", "denyAllEffects"}, {"展示框交互", "denyItemFrame"}, {"红石电路", "denyRedstoneInteraction"},
{"门禁", "denyDoorInteraction"}, {"音频", "denyNoteblockJukebox"}, {"拴绳使用", "denyLead"},
{"农作物收获", "denyCropHarvest"}, {"剪切羊毛", "denyWoolShear"}, {"投喂动物", "denyAnimalFeeding"},
{"玩家发光", "denyGlowing"}, {"和平模式", "peaceMode"}
};
if (raw < permDefs.length) {
String field = permDefs[raw][1];
// 获取当前effective值
Map<String, Boolean> currentPerms = areaProtect.getPlayerPermMap(landId, targetPlayer);
boolean currentVal = currentPerms.containsKey(field)
? currentPerms.get(field)
: getLandDefaultVal(land, field);
boolean newVal = !currentVal;
areaProtect.setPlayerPerm(landId, targetPlayer, field, newVal);
p.sendMessage("§a已设置 §e" + targetPlayer + " §a的权限");
openPlayerPerm(p, landName, targetPlayer);
}
} else if (raw == 48) {
// 清除所有自定义权限
areaProtect.setPlayerPermJson(landId, targetPlayer, "");
p.sendMessage("§a已清除 §e" + targetPlayer + " §a的所有自定义权限");
openMemberPermList(p, landName);
} else if (raw == 49) {
// 返回成员列表
openMemberPermList(p, landName);
}
return;
}
// 添加成员
if (title.startsWith(T_ADD_MEMBER)) {
event.setCancelled(true);
+327 -79
View File
@@ -46,9 +46,6 @@ import org.bukkit.event.entity.EntitySpawnEvent;
import org.bukkit.entity.Item;
import org.bukkit.event.inventory.InventoryOpenEvent;
public class AreaProtection implements Listener {
// 三级权限枚举
@@ -2232,6 +2229,204 @@ public class AreaProtection implements Listener {
return requiredLevel == PermissionLevel.VISITOR;
}
// ===== Per-Player 独立权限系统 =====
/**
* 获取玩家在指定领地的独立权限JSON
* 格式: {"denyMove":true,"denyPVP":false,...}
* 空字符串或null表示没有自定义权限(使用领地默认)
*/
public String getPlayerPermJson(int landId, String playerName) {
if (dbConnection == null) return "";
try {
PreparedStatement stmt = dbConnection.prepareStatement(
"SELECT permissions FROM area_land_permissions "
+ "WHERE land_id = ? AND player_name = ?");
stmt.setInt(1, landId);
stmt.setString(2, playerName);
ResultSet rs = stmt.executeQuery();
String result = "";
if (rs.next()) {
result = rs.getString("permissions");
if (result == null) result = "";
}
rs.close();
stmt.close();
return result;
} catch (SQLException e) {
return "";
}
}
/**
* 设置玩家在指定领地的独立权限JSON
*/
public void setPlayerPermJson(int landId, String playerName, String permJson) {
if (dbConnection == null) return;
try {
PreparedStatement stmt = dbConnection.prepareStatement(
"UPDATE area_land_permissions SET permissions = ? "
+ "WHERE land_id = ? AND player_name = ?");
stmt.setString(1, permJson != null ? permJson : "");
stmt.setInt(2, landId);
stmt.setString(3, playerName);
stmt.executeUpdate();
stmt.close();
} catch (SQLException e) {
plugin.getLogger().warning("[防护] 设置玩家权限失败: " + e.getMessage());
}
}
/**
* 获取玩家独立权限(解析JSON为Map)
*/
public Map<String, Boolean> getPlayerPermMap(int landId, String playerName) {
Map<String, Boolean> map = new HashMap<>();
String json = getPlayerPermJson(landId, playerName);
if (json == null || json.isEmpty()) return map;
// 简单解析 {"key":true,"key2":false}
String clean = json.trim();
if (clean.startsWith("{")) clean = clean.substring(1);
if (clean.endsWith("}")) clean = clean.substring(0, clean.length() - 1);
if (clean.isEmpty()) return map;
for (String pair : clean.split(",")) {
String[] kv = pair.split(":");
if (kv.length == 2) {
String key = kv[0].trim().replace("\"", "");
boolean val = Boolean.parseBoolean(kv[1].trim().replace("\"", ""));
map.put(key, val);
}
}
return map;
}
/**
* 设置玩家独立权限中的单个权限项
* @param permName 权限名(如 "denyMove")
* @param enabled true=启用限制(deny),false=取消限制(允许)
*/
public void setPlayerPerm(int landId, String playerName, String permName, boolean enabled) {
Map<String, Boolean> map = getPlayerPermMap(landId, playerName);
map.put(permName, enabled);
String json = mapToJson(map);
setPlayerPermJson(landId, playerName, json);
}
/**
* 获取所有有独立权限的成员列表
*/
public List<Map<String, Object>> getLandMembersWithPerms(int landId) {
List<Map<String, Object>> list = new ArrayList<>();
if (dbConnection == null) return list;
try {
PreparedStatement stmt = dbConnection.prepareStatement(
"SELECT player_name, role, permissions FROM area_land_permissions "
+ "WHERE land_id = ?");
stmt.setInt(1, landId);
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
Map<String, Object> entry = new HashMap<>();
entry.put("player", rs.getString("player_name"));
entry.put("role", rs.getString("role"));
entry.put("permissions", rs.getString("permissions"));
list.add(entry);
}
rs.close();
stmt.close();
} catch (SQLException e) {
// 忽略
}
return list;
}
/**
* ★ 核心方法:获取玩家的effective deny状态
* 优先级:per-player权限 > 领地默认权限
* @param permName 权限字段名(如 "denyMove")
* @return true=限制生效,false=允许
*/
public boolean getEffectiveDeny(Player player, AreaConfig ac, String permName) {
// ADMIN/OWNER不检查per-player deny
PermissionLevel level = getPermissionLevel(player, ac);
if (level == PermissionLevel.ADMIN || level == PermissionLevel.OWNER) {
return false;
}
// 尝试读取per-player权限
int landId = getLandIdFromDb(ac.name);
if (landId > 0) {
Map<String, Boolean> playerPerms = getPlayerPermMap(landId, player.getName());
if (playerPerms.containsKey(permName)) {
return playerPerms.get(permName);
}
}
// 回退到领地默认权限
return getLandDefaultDeny(ac, permName);
}
/**
* 获取领地默认deny状态(按字段名映射)
*/
private boolean getLandDefaultDeny(AreaConfig ac, String permName) {
switch (permName) {
case "denyMove": return ac.denyMove;
case "denyBlockPlace": return ac.denyBlockPlace;
case "denyBlockBreak": return ac.denyBlockBreak;
case "denyPVP": return ac.denyPVP;
case "denyFallDamage": return ac.denyFallDamage;
case "denyHunger": return ac.denyHunger;
case "denyAllDamage": return ac.denyAllDamage;
case "denyDrop": return ac.denyDrop;
case "denyMount": return ac.denyMount;
case "denyEnderPearl": return ac.denyEnderPearl;
case "denyBow": return ac.denyBow;
case "denyPotion": return ac.denyPotion;
case "denyExplosion": return ac.denyExplosion;
case "denyRaid": return ac.denyRaid;
case "denyFireSpread": return ac.denyFireSpread;
case "denyAllEffects": return ac.denyAllEffects;
case "denyItemFrame": return ac.denyItemFrame;
case "denyPickup": return ac.denyPickup;
case "denyFire": return ac.denyFire;
case "denyThrownProjectiles": return ac.denyThrownProjectiles;
case "denyGlowing": return ac.denyGlowing;
case "denyRedstoneInteraction": return ac.denyRedstoneInteraction;
case "denyDoorInteraction": return ac.denyDoorInteraction;
case "denyNoteblockJukebox": return ac.denyNoteblockJukebox;
case "denyLead": return ac.denyLead;
case "denyCropHarvest": return ac.denyCropHarvest;
case "denyWoolShear": return ac.denyWoolShear;
case "denyAnimalFeeding": return ac.denyAnimalFeeding;
case "peaceMode": return ac.peaceMode;
default: return false;
}
}
/**
* Map转简单JSON
*/
private String mapToJson(Map<String, Boolean> map) {
if (map.isEmpty()) return "";
StringBuilder sb = new StringBuilder("{");
boolean first = true;
for (Map.Entry<String, Boolean> e : map.entrySet()) {
if (!first) sb.append(",");
sb.append("\"").append(e.getKey()).append("\":").append(e.getValue());
first = false;
}
sb.append("}");
return sb.toString();
}
/**
* 获取指定领地的成员列表
*/
public Set<String> getAreaMembers(String areaName) {
Set<String> members = areaPlayerWhitelist.get(areaName);
return members != null ? new HashSet<>(members) : new HashSet<>();
}
// ===== 统一白名单检查(供所有事件处理器使用)=====
/**
@@ -2529,8 +2724,8 @@ public class AreaProtection implements Listener {
if (newArea != null) {
AreaConfig ac = areas.get(newArea);
if (ac != null) {
// ★ denyMove检查:无OWNER/ADMIN权限的玩家不能移动
if (ac.denyMove && !hasPermission(p, ac, PermissionLevel.OWNER)) {
// ★ denyMove检查:支持per-player独立权限
if (getEffectiveDeny(p, ac, "denyMove")) {
Location safeLoc = findSafeExitLocation(p.getLocation(), ac);
if (safeLoc != null) {
p.teleport(safeLoc);
@@ -2669,8 +2864,8 @@ public class AreaProtection implements Listener {
AreaConfig newAc = areas.get(newArea);
if (newAc != null) {
// ★ denyMove检查:无OWNER/ADMIN权限的玩家不能进入
if (newAc.denyMove && !hasPermission(p, newAc, PermissionLevel.OWNER)) {
// ★ denyMove检查:支持per-player独立权限
if (getEffectiveDeny(p, newAc, "denyMove")) {
Location safeLoc = findSafeExitLocation(event.getFrom(), newAc);
if (safeLoc != null) {
p.teleport(safeLoc);
@@ -3877,14 +4072,11 @@ public class AreaProtection implements Listener {
e.getBlock().getX(),
e.getBlock().getY(),
e.getBlock().getZ());
if (ac != null && ac.denyBlockPlace) {
// 访客禁止放置方块,所有者和管理员允许
if (!hasPermission(p, ac, PermissionLevel.OWNER)) {
if (ac != null && getEffectiveDeny(p, ac, "denyBlockPlace")) {
e.setCancelled(true);
p.sendMessage("§c§l[区域防护] §f禁止放置方块");
}
}
}
@EventHandler
public void onBlockBreak(BlockBreakEvent e) {
@@ -3894,14 +4086,11 @@ public class AreaProtection implements Listener {
e.getBlock().getX(),
e.getBlock().getY(),
e.getBlock().getZ());
if (ac != null && ac.denyBlockBreak) {
// 访客禁止破坏方块,所有者和管理员允许
if (!hasPermission(p, ac, PermissionLevel.OWNER)) {
if (ac != null && getEffectiveDeny(p, ac, "denyBlockBreak")) {
e.setCancelled(true);
p.sendMessage("§c§l[区域防护] §f禁止破坏方块");
}
}
}
@EventHandler
@@ -3914,9 +4103,9 @@ public class AreaProtection implements Listener {
p.getLocation().getBlockY(),
p.getLocation().getBlockZ());
if (ac == null) return;
// 保护性规则:对所有人无条件生效
if (ac.denyAllDamage) { e.setCancelled(true); return; }
if (ac.denyFallDamage
// 保护性规则:支持per-player独立权限
if (getEffectiveDeny(p, ac, "denyAllDamage")) { e.setCancelled(true); return; }
if (getEffectiveDeny(p, ac, "denyFallDamage")
&& e.getCause() == EntityDamageEvent.DamageCause.FALL)
e.setCancelled(true);
}
@@ -3932,14 +4121,11 @@ public class AreaProtection implements Listener {
p.getLocation().getBlockX(),
p.getLocation().getBlockY(),
p.getLocation().getBlockZ());
if (ac != null && ac.denyPVP) {
// 访客禁止PVP,所有者和管理员允许
if (!hasPermission(p, ac, PermissionLevel.OWNER)) {
if (ac != null && getEffectiveDeny(p, ac, "denyPVP")) {
e.setCancelled(true);
p.sendMessage("§c§l[区域防护] §f禁止PVP");
}
}
}
@EventHandler
@@ -3951,7 +4137,7 @@ public class AreaProtection implements Listener {
p.getLocation().getBlockX(),
p.getLocation().getBlockY(),
p.getLocation().getBlockZ());
if (ac != null && ac.denyHunger)
if (ac != null && getEffectiveDeny(p, ac, "denyHunger"))
e.setCancelled(true);
}
@@ -3965,13 +4151,11 @@ public class AreaProtection implements Listener {
p.getLocation().getBlockX(),
p.getLocation().getBlockY(),
p.getLocation().getBlockZ());
if (ac != null && ac.denyDrop) {
if (!hasPermission(p, ac, PermissionLevel.OWNER)) {
if (ac != null && getEffectiveDeny(p, ac, "denyDrop")) {
e.setCancelled(true);
p.sendMessage("§c§l[区域防护] §f禁止丢弃物品");
}
}
}
// 替换整个 onInteract 方法
@EventHandler
@@ -4047,26 +4231,22 @@ public class AreaProtection implements Listener {
p.getLocation().getBlockZ());
if (ac == null) return;
// 末影珍珠禁止 (only for visitors)
if (ac.denyEnderPearl
// 末影珍珠禁止(支持per-player权限)
if (getEffectiveDeny(p, ac, "denyEnderPearl")
&& hand.getType() == Material.ENDER_PEARL) {
if (!hasPermission(p, ac, PermissionLevel.OWNER)) {
e.setCancelled(true);
p.sendMessage("§c§l[区域防护] §f禁止使用末影珍珠");
return;
}
}
// 弓箭禁止 (only for visitors)
if (ac.denyBow
// 弓箭禁止(支持per-player权限)
if (getEffectiveDeny(p, ac, "denyBow")
&& (hand.getType() == Material.BOW
|| hand.getType() == Material.CROSSBOW)) {
if (!hasPermission(p, ac, PermissionLevel.OWNER)) {
e.setCancelled(true);
p.sendMessage("§c§l[区域防护] §f禁止使用弓箭");
return;
}
}
// 所有者+管理员跳过禁止使用物品检查
if (hasPermission(p, ac, PermissionLevel.OWNER)) {
@@ -4217,13 +4397,11 @@ public class AreaProtection implements Listener {
shooter.getLocation().getBlockX(),
shooter.getLocation().getBlockY(),
shooter.getLocation().getBlockZ());
if (ac != null && ac.denyPotion) {
if (!hasPermission(shooter, ac, PermissionLevel.OWNER)) {
if (ac != null && getEffectiveDeny(shooter, ac, "denyPotion")) {
e.setCancelled(true);
shooter.sendMessage("§c§l[区域防护] §f禁止使用药水");
}
}
}
// ===== ★ 投掷物检测(三叉戟、雪球、风蛋、箭)=====
@EventHandler
@@ -4237,8 +4415,7 @@ public class AreaProtection implements Listener {
shooter.getLocation().getBlockY(),
shooter.getLocation().getBlockZ());
if (ac == null) return;
if (!ac.denyThrownProjectiles) return;
if (hasPermission(shooter, ac, PermissionLevel.OWNER)) return;
if (!getEffectiveDeny(shooter, ac, "denyThrownProjectiles")) return;
// 检查投掷物类型:箭、三叉戟、雪球、风蛋、龙息弹等
String typeName = proj.getType().name();
if (typeName.contains("ARROW") || typeName.contains("TRIDENT")
@@ -4262,8 +4439,7 @@ public class AreaProtection implements Listener {
e.getClickedBlock().getY(),
e.getClickedBlock().getZ());
if (ac == null) return;
if (!ac.denyDoorInteraction) return;
if (hasPermission(p, ac, PermissionLevel.OWNER)) return;
if (!getEffectiveDeny(p, ac, "denyDoorInteraction")) return;
// 按钮
if (mat.name().contains("BUTTON")) {
e.setCancelled(true);
@@ -4291,8 +4467,7 @@ public class AreaProtection implements Listener {
e.getClickedBlock().getY(),
e.getClickedBlock().getZ());
if (ac == null) return;
if (!ac.denyDoorInteraction) return;
if (hasPermission(p, ac, PermissionLevel.OWNER)) return;
if (!getEffectiveDeny(p, ac, "denyDoorInteraction")) return;
String name = mat.name();
if (name.contains("DOOR") || name.contains("GATE") || name.contains("FENCE_GATE")) {
e.setCancelled(true);
@@ -4312,8 +4487,7 @@ public class AreaProtection implements Listener {
e.getClickedBlock().getY(),
e.getClickedBlock().getZ());
if (ac == null) return;
if (!ac.denyRedstoneInteraction) return;
if (hasPermission(p, ac, PermissionLevel.OWNER)) return;
if (!getEffectiveDeny(p, ac, "denyRedstoneInteraction")) return;
if (mat == Material.REPEATER || mat == Material.COMPARATOR
|| mat.name().contains("REPEATER") || mat.name().contains("COMPARATOR")) {
e.setCancelled(true);
@@ -4333,8 +4507,7 @@ public class AreaProtection implements Listener {
e.getClickedBlock().getY(),
e.getClickedBlock().getZ());
if (ac == null) return;
if (!ac.denyNoteblockJukebox) return;
if (hasPermission(p, ac, PermissionLevel.OWNER)) return;
if (!getEffectiveDeny(p, ac, "denyNoteblockJukebox")) return;
if (mat == Material.NOTE_BLOCK || mat.name().contains("JUKEBOX")
|| mat.name().contains("CHERRY_JUKEBOX")) {
e.setCancelled(true);
@@ -4368,8 +4541,7 @@ public class AreaProtection implements Listener {
loc.getBlockY(),
loc.getBlockZ());
if (ac == null) return;
if (!ac.denyWoolShear) return;
if (hasPermission(p, ac, PermissionLevel.OWNER)) return;
if (!getEffectiveDeny(p, ac, "denyWoolShear")) return;
String entityType = e.getEntity().getType().name();
if (entityType.contains("SHEEP") || entityType.contains("MOoshroom")) {
e.setCancelled(true);
@@ -4397,8 +4569,7 @@ public class AreaProtection implements Listener {
loc.getBlockY(),
loc.getBlockZ());
if (ac == null) return;
if (!ac.denyAnimalFeeding) return;
if (hasPermission(p, ac, PermissionLevel.OWNER)) return;
if (!getEffectiveDeny(p, ac, "denyAnimalFeeding")) return;
// 检查玩家手持的物品是否是食物
ItemStack hand = p.getInventory().getItemInMainHand();
if (hand != null && hand.getType().isEdible()) {
@@ -4420,8 +4591,7 @@ public class AreaProtection implements Listener {
loc.getBlockY(),
loc.getBlockZ());
if (ac == null) return;
if (!ac.denyLead) return;
if (hasPermission(p, ac, PermissionLevel.OWNER)) return;
if (!getEffectiveDeny(p, ac, "denyLead")) return;
e.setCancelled(true);
p.sendMessage("§c§l[区域防护] §f禁止在此区域使用拴绳");
}
@@ -4832,6 +5002,50 @@ public class AreaProtection implements Listener {
// ★ CLI全局配置页面
plugin.areaCLIManager.showConfigPage(p);
break;
case "memberperm":
// ★ 成员独立权限列表
if (args.length < 3) {
p.sendMessage("§c用法: /protect cli memberperm <领地名> [页码]");
break;
}
int mpPage = 1;
if (args.length >= 4) {
try { mpPage = Integer.parseInt(args[3]); } catch (Exception ignored) {}
}
plugin.areaCLIManager.showMemberPermList(p, args[2], mpPage);
break;
case "playerperm":
// ★ 某成员的独立权限编辑
if (args.length < 4) {
p.sendMessage("§c用法: /protect cli playerperm <领地名> <玩家> [页码]");
break;
}
int ppPage = 1;
if (args.length >= 5) {
try { ppPage = Integer.parseInt(args[4]); } catch (Exception ignored) {}
}
plugin.areaCLIManager.showPlayerPerm(p, args[2], args[3], ppPage);
break;
case "toggleplayerperm":
// ★ 切换某成员的独立权限
if (args.length < 5) {
p.sendMessage("§c用法: /protect cli toggleplayerperm <领地名> <玩家> <权限key> [页码]");
break;
}
int tpPage = 1;
if (args.length >= 6) {
try { tpPage = Integer.parseInt(args[5]); } catch (Exception ignored) {}
}
plugin.areaCLIManager.togglePlayerPerm(p, args[2], args[3], args[4], tpPage);
break;
case "clearplayerperm":
// ★ 清除某成员所有自定义权限
if (args.length < 4) {
p.sendMessage("§c用法: /protect cli clearplayerperm <领地名> <玩家>");
break;
}
plugin.areaCLIManager.clearPlayerPerm(p, args[2], args[3]);
break;
case "create":
// 跳转到创建命令
// 直接转发
@@ -6529,7 +6743,7 @@ public class AreaProtection implements Listener {
/**
* 从数据库获取领地ID
*/
private int getLandIdFromDb(String name) {
public int getLandIdFromDb(String name) {
if (dbConnection == null) return -1;
try {
PreparedStatement stmt = dbConnection.prepareStatement("SELECT id FROM area_lands WHERE name = ?");
@@ -6544,6 +6758,54 @@ public class AreaProtection implements Listener {
}
}
/**
* PHP端修改领地所有者时调用
*/
public void setLandOwnerFromWeb(String landName, String newOwner) {
if (dbConnection == null) return;
try {
// 更新数据库
PreparedStatement ps = dbConnection.prepareStatement("UPDATE area_lands SET owner = ? WHERE name = ?");
ps.setString(1, newOwner);
ps.setString(2, landName);
ps.executeUpdate();
ps.close();
// 更新内存中的AreaConfig
AreaConfig ac = getLand(landName);
if (ac != null) {
ac.owner = newOwner;
}
plugin.getLogger().info("[防护] PHP端更新领地所有者: " + landName + " → " + newOwner);
} catch (SQLException e) {
plugin.getLogger().warning("[防护] 更新领地所有者失败: " + e.getMessage());
}
}
/**
* PHP端清除成员权限时调用
*/
public void clearPlayerPermFromWeb(String landName, String playerName) {
if (dbConnection == null) return;
try {
int landId = getLandIdFromDb(landName);
if (landId < 0) return;
// 清除数据库中的自定义权限
PreparedStatement ps = dbConnection.prepareStatement(
"UPDATE area_land_permissions SET permissions = '' WHERE land_id = ? AND player_name = ?");
ps.setInt(1, landId);
ps.setString(2, playerName);
ps.executeUpdate();
ps.close();
plugin.getLogger().info("[防护] PHP端清除成员权限: " + playerName + " @ " + landName);
} catch (SQLException e) {
plugin.getLogger().warning("[防护] 清除成员权限失败: " + e.getMessage());
}
}
/**
* 格式化时长(秒 → 可读字符串)
*/
@@ -7076,11 +7338,9 @@ public class AreaProtection implements Listener {
if (!(e.getDamager() instanceof Player)) return;
Player p = (Player) e.getDamager();
// ★ 统一白名单检查(globalPlayerWhitelist + areaPlayerWhitelist)
// ★ 统一白名单检查(支持per-player权限)
AreaConfig ac = findFrameArea(entity);
if (ac == null || !ac.denyItemFrame) return;
if (hasPermission(p, ac, PermissionLevel.OWNER)) return;
if (ac == null || !getEffectiveDeny(p, ac, "denyItemFrame")) return;
e.setCancelled(true);
p.sendMessage("§c§l[区域防护] §f禁止破坏展示框");
@@ -7096,13 +7356,11 @@ public class AreaProtection implements Listener {
p2.getLocation().getBlockX(),
p2.getLocation().getBlockY(),
p2.getLocation().getBlockZ());
if (ac2 != null && ac2.denyPVP) {
if (!hasPermission(p2, ac2, PermissionLevel.OWNER)) {
if (ac2 != null && getEffectiveDeny(p2, ac2, "denyPVP")) {
e.setCancelled(true);
p2.sendMessage("§c§l[区域防护] §f禁止PVP");
}
}
}
// 展示框被点击(旋转/放入/取出)
@EventHandler
@@ -7117,12 +7375,9 @@ public class AreaProtection implements Listener {
Player p = e.getPlayer();
// ★ 使用统一的权限检查方法
// ★ 使用统一的权限检查方法(支持per-player权限)
AreaConfig ac = findFrameArea(clicked);
if (ac == null || !ac.denyItemFrame) return;
// 所有者+管理员跳过
if (hasPermission(p, ac, PermissionLevel.OWNER)) return;
if (ac == null || !getEffectiveDeny(p, ac, "denyItemFrame")) return;
// 非白名单 → 拦截
// 记录当前旋转角度
@@ -7176,10 +7431,8 @@ public class AreaProtection implements Listener {
AreaConfig ac = findFrameArea(entity);
if (ac == null) return;
// ★ 使用统一的权限检查方法
if (hasPermission(p, ac, PermissionLevel.OWNER)) return;
if (!ac.denyItemFrame) return;
// ★ 使用统一的权限检查方法(支持per-player权限)
if (!getEffectiveDeny(p, ac, "denyItemFrame")) return;
e.setCancelled(true);
p.sendMessage("§c§l[区域防护] §f禁止破坏展示框");
@@ -7195,13 +7448,11 @@ public class AreaProtection implements Listener {
p2.getLocation().getBlockX(),
p2.getLocation().getBlockY(),
p2.getLocation().getBlockZ());
if (ac2 != null && ac2.denyPVP) {
if (!hasPermission(p2, ac2, PermissionLevel.OWNER)) {
if (ac2 != null && getEffectiveDeny(p2, ac2, "denyPVP")) {
e.setCancelled(true);
p2.sendMessage("§c§l[区域防护] §f禁止PVP");
}
}
}
private AreaConfig findFrameArea(Entity frame) {
Location loc = frame.getLocation();
int bx = loc.getBlockX();
@@ -7239,11 +7490,9 @@ public class AreaProtection implements Listener {
return;
}
// ★ 使用统一的权限检查方法
// ★ 使用统一的权限检查方法(支持per-player权限)
AreaConfig ac = findFrameArea(hit);
if (ac == null || !ac.denyItemFrame) return;
if (hasPermission(shooter, ac, PermissionLevel.OWNER)) return;
if (ac == null || !getEffectiveDeny(shooter, ac, "denyItemFrame")) return;
e.setCancelled(true);
shooter.sendMessage("§c§l[区域防护] §f禁止破坏展示框");
@@ -7365,8 +7614,7 @@ public class AreaProtection implements Listener {
p.getLocation().getBlockX(),
p.getLocation().getBlockY(),
p.getLocation().getBlockZ());
if (ac != null && ac.denyGlowing) {
if (hasPermission(p, ac, PermissionLevel.OWNER)) continue;
if (ac != null && getEffectiveDeny(p, ac, "denyGlowing")) {
if (p.hasPotionEffect(PotionEffectType.GLOWING)) {
p.removePotionEffect(PotionEffectType.GLOWING);
p.sendMessage("§c§l[区域防护] §f此区域禁止使用发光效果");
+41 -5
View File
@@ -6147,7 +6147,8 @@ public class Main extends JavaPlugin
// ★ cli: 子命令
if (sub.equals("cli")) {
return filterTab(
Arrays.asList("menu", "lands", "manage", "members", "visitorperm", "toggle", "create", "config"),
Arrays.asList("menu", "lands", "manage", "members", "visitorperm", "toggle", "create", "config",
"memberperm", "playerperm", "toggleplayerperm", "clearplayerperm"),
args[1]);
}
}
@@ -6210,11 +6211,13 @@ public class Main extends JavaPlugin
Arrays.asList("create_price", "max_lands", "default_height", "peace_duration"),
args[2]);
}
// ★ cli manage/members/visitorperm/toggle: 第三层是领地名
// ★ cli manage/members/visitorperm/toggle/memberperm/playerperm/toggleplayerperm/clearplayerperm: 第三层是领地名
if (sub.equals("cli")) {
String action = args[1].toLowerCase();
if (action.equals("manage") || action.equals("members")
|| action.equals("visitorperm") || action.equals("toggle")) {
|| action.equals("visitorperm") || action.equals("toggle")
|| action.equals("memberperm") || action.equals("playerperm")
|| action.equals("toggleplayerperm") || action.equals("clearplayerperm")) {
return filterTab(
new ArrayList<>(areaProtection.getAreaNames()),
args[2]);
@@ -6222,7 +6225,8 @@ public class Main extends JavaPlugin
}
}
// ★ 第四层:cli toggle需要权限key,cli manage/members/visitorperm需要页码
// ★ 第四层:cli toggle需要权限key,cli manage/members/visitorperm/memberperm需要页码
// playerperm/toggleplayerperm需要玩家名,clearplayerperm需要玩家名
if (args.length == 4) {
if (sub.equals("cli")) {
String action = args[1].toLowerCase();
@@ -6238,10 +6242,42 @@ public class Main extends JavaPlugin
args[3]);
}
if (action.equals("manage") || action.equals("members")
|| action.equals("visitorperm")) {
|| action.equals("visitorperm") || action.equals("memberperm")) {
// 页码
return Arrays.asList("1", "2", "3", "4", "5");
}
if (action.equals("playerperm") || action.equals("clearplayerperm")) {
// 玩家名
List<String> players = new ArrayList<>();
for (Player pl : Bukkit.getOnlinePlayers())
players.add(pl.getName());
return filterTab(players, args[3]);
}
if (action.equals("toggleplayerperm")) {
// 玩家名
List<String> players = new ArrayList<>();
for (Player pl : Bukkit.getOnlinePlayers())
players.add(pl.getName());
return filterTab(players, args[3]);
}
}
}
// ★ 第五层:cli toggleplayerperm需要权限key
if (args.length == 5) {
if (sub.equals("cli")) {
String action = args[1].toLowerCase();
if (action.equals("toggleplayerperm")) {
// 权限key
return filterTab(Arrays.asList(
"denyMove", "denyBlockPlace", "denyBlockBreak", "denyPVP", "denyMount",
"denyEnderPearl", "denyThrownProjectiles", "denyRaid", "denyBow", "denyPotion",
"denyFire", "denyFireSpread", "denyPickup", "denyDrop", "denyExplosion",
"denyFallDamage", "denyHunger", "denyAllDamage", "denyAllEffects",
"denyItemFrame", "denyRedstoneInteraction", "denyDoorInteraction", "denyNoteblockJukebox",
"denyLead", "denyCropHarvest", "denyWoolShear", "denyAnimalFeeding", "denyGlowing", "peaceMode"),
args[4]);
}
}
}
+86 -18
View File
@@ -23,6 +23,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
/**
* Web通信管理器 - 插件与PHP后端的桥梁
@@ -315,18 +316,9 @@ public class WebManager {
SSLContext sc = SSLContext.getInstance("TLSv1.2");
sc.init(null, trustAllCerts, new SecureRandom());
// ★ 配置SSL参数:指定兼容CF的密码套件
// ★ 不要硬编码密码套件!JVM可能不支持某些套件导致IllegalArgumentException
// 只固定协议版本为TLSv1.2,密码套件让JVM自动选择
javax.net.ssl.SSLParameters sslParams = sc.getDefaultSSLParameters();
sslParams.setCipherSuites(new String[]{
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_RSA_WITH_AES_128_CBC_SHA256",
"TLS_RSA_WITH_AES_256_CBC_SHA384"
});
sslParams.setProtocols(new String[]{"TLSv1.2"});
// ★ java.net.http.HttpClient + HTTP/1.1
@@ -339,7 +331,7 @@ public class WebManager {
.version(HttpClient.Version.HTTP_1_1)
.build();
plugin.getLogger().info("[Web通信] SSL已初始化(TLSv1.2, 信任所有证书, CF兼容密码套件, HTTP/1.1)");
plugin.getLogger().info("[Web通信] SSL已初始化(TLSv1.2, 信任所有证书, 自动密码套件, HTTP/1.1)");
} catch (Exception e) {
plugin.getLogger().severe("[Web通信] SSL完全初始化失败: " + e.getMessage());
}
@@ -361,12 +353,7 @@ public class WebManager {
sc.init(null, trustAllCerts, new SecureRandom());
javax.net.ssl.SSLParameters sslParams = sc.getDefaultSSLParameters();
sslParams.setCipherSuites(new String[]{
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384"
});
// ★ 不硬编码密码套件,让JVM自动选择支持的套件
sslParams.setProtocols(new String[]{"TLSv1.2"});
cfHttpClient = HttpClient.newBuilder()
@@ -654,6 +641,7 @@ public class WebManager {
submitNormalDbTask("首次-syncAllPlayerIps", () -> syncAllPlayerIps());
submitNormalDbTask("首次-syncServiceProviders", () -> syncServiceProviders());
submitNormalDbTask("首次-syncLandData", () -> syncLandData());
submitNormalDbTask("首次-pollAdminChanges", () -> pollAdminChanges());
}
}.runTaskLaterAsynchronously(plugin, 20L * 10);
@@ -1118,6 +1106,7 @@ public class WebManager {
submitNormalDbTask("周期-syncAllPlayerIps", () -> syncAllPlayerIps());
submitNormalDbTask("周期-syncServiceProviders", () -> syncServiceProviders());
submitNormalDbTask("周期-syncLandData", () -> syncLandData());
submitNormalDbTask("周期-pollAdminChanges", () -> pollAdminChanges());
} else {
// ★ 不在线:跳过非必要同步任务
plugin.getLogger().info("[Web通信] 全员下线,跳过非登录类同步任务");
@@ -2215,6 +2204,84 @@ public class WebManager {
}
}
/**
* 轮询PHP管理员变更(所有者变更、权限清除等)
*/
private long lastPollAdminChangesId = 0;
public void pollAdminChanges() {
if (!enabled) return;
try {
AreaProtection areaProtect = plugin.getAreaProtection();
if (areaProtect == null) return;
// 查询PHP端管理员变更
String url = webBaseUrl + "/api/land_api.php?action=poll_admin_changes&secret=" + secretKey + "&last_id=" + lastPollAdminChangesId + "&limit=50";
String response = doGet(url);
if (response == null) return;
Map<String, Object> result = parseJson(response);
if (!Boolean.TRUE.equals(result.get("success"))) return;
List<Map<String, Object>> changes = (List<Map<String, Object>>) result.get("changes");
if (changes == null || changes.isEmpty()) return;
List<Integer> ackedIds = new ArrayList<>();
for (Map<String, Object> change : changes) {
int id = ((Number) change.getOrDefault("id", 0)).intValue();
String changeType = String.valueOf(change.getOrDefault("change_type", ""));
String targetName = String.valueOf(change.getOrDefault("target_name", ""));
String changeDataStr = String.valueOf(change.getOrDefault("change_data", "{}"));
Map<String, Object> changeData = parseJson(changeDataStr);
if (changeData == null) changeData = new HashMap<>();
try {
switch (changeType) {
case "owner_change": {
// PHP端修改了领地所有者
String newOwner = String.valueOf(changeData.getOrDefault("new_owner", ""));
String landName = targetName;
if (!newOwner.isEmpty() && !landName.isEmpty()) {
// 更新本地领地所有者
areaProtect.setLandOwnerFromWeb(landName, newOwner);
plugin.getLogger().info("[Web通信] PHP端领地所有者变更: " + landName + " → " + newOwner);
}
break;
}
case "perm_clear": {
// PHP端清除了成员权限
String playerName = targetName;
String landNameJson = String.valueOf(changeData.getOrDefault("land_name", ""));
if (!playerName.isEmpty() && !landNameJson.isEmpty()) {
// 清除本地成员权限
areaProtect.clearPlayerPermFromWeb(landNameJson, playerName);
plugin.getLogger().info("[Web通信] PHP端清除成员权限: " + playerName + " @ " + landNameJson);
}
break;
}
default:
plugin.getLogger().fine("[Web通信] 未知变更类型: " + changeType);
}
ackedIds.add(id);
} catch (Exception e) {
plugin.getLogger().warning("[Web通信] 处理变更失败(id=" + id + "): " + e.getMessage());
}
}
// 确认已处理的变更
if (!ackedIds.isEmpty()) {
String ackUrl = webBaseUrl + "/api/land_api.php?action=ack_admin_changes&secret=" + secretKey;
String ackBody = "ids=" + String.join(",", ackedIds.stream().map(String::valueOf).collect(Collectors.toList()));
httpPost(ackUrl, ackBody);
}
} catch (Exception e) {
plugin.getLogger().warning("[Web通信] 轮询PHP管理员变更异常: " + e.getMessage());
}
}
/**
* 5. 推送债券余额快照到Web端
*/
@@ -3072,6 +3139,7 @@ public class WebManager {
submitDbTask("管理-syncUserRegistrations", () -> syncUserRegistrations());
submitDbTask("管理-syncServiceProviders", () -> syncServiceProviders());
submitDbTask("管理-syncLandData", () -> syncLandData());
submitDbTask("管理-pollAdminChanges", () -> pollAdminChanges());
}
}.runTaskAsynchronously(plugin);
sender.sendMessage("§a[Web] 同步任务已启动(通过DB队列串行化)...");
+16
View File
@@ -0,0 +1,16 @@
import paramiko
import sys
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
ssh.connect('sfe4-connect.simpfun.cn', port=2046, username='sfe3192491.df7d2f7f', password='5350427807', timeout=30)
sftp = ssh.open_sftp()
sftp.put('D:/桌面/源码/mc_plugn/Sdf1_login/target/Sdf1_login.jar', '/plugins/Sdf1_login.jar')
sftp.close()
print('SFTP upload success')
except Exception as e:
print(f'Error: {e}', file=sys.stderr)
sys.exit(1)
finally:
ssh.close()