feat: 新增AreaCLIManager交互式CLI菜单系统

- CLI主菜单:创建领地/领地列表/权限商店/信息/切换模式
- 领地列表:分页显示,每条有[管理][传送][删除]可点击按钮
- 权限管理:分页显示29项权限,每项有✔/✘标记+切换按钮
- /protect无参数时CLI模式直接打开交互菜单
- AreaProtection新增getAllLands()公开方法
This commit is contained in:
youpaishidifu
2026-06-23 18:15:31 +08:00
parent 69c9e0ceb9
commit 542879177c
3 changed files with 458 additions and 1 deletions
@@ -0,0 +1,387 @@
package Sdf1_login;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.*;
import java.util.stream.Collectors;
/**
* 领地系统CLI交互式菜单
* 提供完整的文字交互界面,参照Dominion风格
*/
public class AreaCLIManager {
private final Main plugin;
private final AreaProtection areaProtect;
private static final int PAGE_SIZE = 10;
public AreaCLIManager(Main plugin, AreaProtection areaProtect) {
this.plugin = plugin;
this.areaProtect = areaProtect;
}
// ==================== 辅助方法 ====================
private Component coloredText(String text, NamedTextColor color) {
return Component.text(text).color(color);
}
private Component header(String title) {
return Component.text("\n§6§l═══════ " + title + " ═══════\n");
}
private Component clickable(String label, String description, String command) {
return Component.empty()
.append(Component.text("§a[§f" + label + "§a] §7" + description)
.hoverEvent(HoverEvent.showText(
Component.text("§e点击执行")))
.clickEvent(ClickEvent.runCommand(command)));
}
private Component clickableAction(String emoji, String label, String command) {
return Component.empty()
.append(Component.text("§a" + emoji + " [§f" + label + "§a]")
.hoverEvent(HoverEvent.showText(
Component.text("§e点击执行")))
.clickEvent(ClickEvent.runCommand(command)));
}
// ==================== 主菜单 ====================
public void showMainMenu(Player p) {
p.sendMessage(header("领地系统"));
p.sendMessage(clickableAction("⚔", "创建领地",
"/protect cli create"));
p.sendMessage(clickableAction("📋", "领地列表",
"/protect cli lands 1"));
p.sendMessage(clickableAction("🏪", "权限商店",
"/protect shop list"));
p.sendMessage(clickableAction("ℹ", "当前领地信息",
"/protect info"));
// 设置
int uiMode = 1;
try { uiMode = plugin.getDb().getUiMode(p.getName()); } catch (Exception ignored) {}
String modeLabel = uiMode == 0 ? "CLI" : "GUI";
p.sendMessage(clickableAction("⚙", "切换到" + (uiMode == 0 ? "GUI" : "CLI") + "模式",
"/protect uimode " + (uiMode == 0 ? "cli" : "gui")));
p.sendMessage(Component.text("§7§l───────────────────────────────"));
}
// ==================== 领地列表 ====================
public void showLandList(Player p, int page) {
String playerName = p.getName();
List<AreaProtection.AreaConfig> lands = areaProtect.getLandsByOwner(playerName);
// 也显示管理员领地
List<AreaProtection.AreaConfig> allLands = new ArrayList<>(lands);
for (AreaProtection.AreaConfig ac : areaProtect.getAllLands().values()) {
if (areaProtect.isAreaAdmin(p) && !allLands.contains(ac)) {
allLands.add(ac);
}
}
int totalPages = Math.max(1, (int) Math.ceil((double) allLands.size() / PAGE_SIZE));
page = Math.max(1, Math.min(page, totalPages));
p.sendMessage(header("你的领地列表 [" + page + "/" + totalPages + "]"));
if (allLands.isEmpty()) {
p.sendMessage(Component.text("§7暂无领地,点击创建:"));
p.sendMessage(clickableAction("⚔", "创建领地",
"/protect cli create"));
p.sendMessage(Component.text("§7§l───────────────────────────────"));
return;
}
int start = (page - 1) * PAGE_SIZE;
int end = Math.min(start + PAGE_SIZE, allLands.size());
for (int i = start; i < end; i++) {
AreaProtection.AreaConfig land = allLands.get(i);
boolean isOwner = playerName.equalsIgnoreCase(land.owner);
Component line = Component.empty();
line = line.append(Component.text("§a" + (i + 1) + ". §e" + land.name + " "));
line = line.append(Component.text("§7(" + land.owner + ") "));
if (isOwner || areaProtect.isAreaAdmin(p)) {
line = line.append(Component.text("§b[管理]")
.hoverEvent(HoverEvent.showText(
Component.text("§e点击管理此领地")))
.clickEvent(ClickEvent.runCommand(
"/protect cli manage " + land.name + " 1")));
line = line.append(Component.text(" "));
line = line.append(Component.text("§b[传送]")
.hoverEvent(HoverEvent.showText(
Component.text("§e点击传送到领地")))
.clickEvent(ClickEvent.runCommand(
"/protect tp " + land.name)));
line = line.append(Component.text(" "));
line = line.append(Component.text("§c[删除]")
.hoverEvent(HoverEvent.showText(
Component.text("§c点击删除此领地")))
.clickEvent(ClickEvent.runCommand(
"/protect 删除 " + land.name)));
}
p.sendMessage(line);
}
// 分页
Component pagination = Component.empty();
if (page > 1) {
pagination = pagination.append(Component.text("§a[◀ 上一页]")
.hoverEvent(HoverEvent.showText(Component.text("§e上一页")))
.clickEvent(ClickEvent.runCommand(
"/protect cli lands " + (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[下一页 ▶]")
.hoverEvent(HoverEvent.showText(Component.text("§e下一页")))
.clickEvent(ClickEvent.runCommand(
"/protect cli lands " + (page + 1))));
}
p.sendMessage(pagination);
p.sendMessage(Component.empty()
.append(Component.text("§a[◀ 返回主菜单]")
.hoverEvent(HoverEvent.showText(Component.text("§e返回")))
.clickEvent(ClickEvent.runCommand("/protect cli menu"))));
p.sendMessage(Component.text("§7§l───────────────────────────────"));
}
// ==================== 领地管理 ====================
public void showLandManage(Player p, String landName, int permPage) {
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;
}
p.sendMessage(header("管理领地: " + landName));
p.sendMessage(Component.text("§7所有者: §f" + land.owner));
p.sendMessage(Component.text("§7世界: §f" + land.world));
p.sendMessage(Component.text("§7坐标: §f(" + land.x1 + "," + land.z1 + ") → (" + land.x2 + "," + land.z2 + ")"));
p.sendMessage(Component.text("§7高度: §f" + land.yMin + " - " + land.yMax));
p.sendMessage(Component.text("§7规则数: §f" + land.ruleCount()));
p.sendMessage(Component.text("§7§l───────────────────────────────"));
// 显示权限列表(分页)
List<AreaProtection.AreaConfig> tempLandList = new ArrayList<>();
tempLandList.add(land);
showPermPage(p, land, permPage);
p.sendMessage(Component.empty()
.append(Component.text("§a[◀ 返回列表]")
.hoverEvent(HoverEvent.showText(Component.text("§e返回领地列表")))
.clickEvent(ClickEvent.runCommand("/protect cli lands 1"))));
p.sendMessage(Component.text("§7§l───────────────────────────────"));
}
private void showPermPage(Player p, AreaProtection.AreaConfig land, int page) {
List<PermItem> perms = getPermList(land);
int totalPages = Math.max(1, (int) Math.ceil((double) perms.size() / PAGE_SIZE));
page = Math.max(1, Math.min(page, totalPages));
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 = perm.enabled ? "§a✔" : "§c✘";
String state = perm.enabled ? "§a已启用" : "§c已禁用";
Component line = Component.empty();
line = line.append(Component.text(icon + " §f" + perm.name + " §7" + state));
line = line.append(Component.text(" "));
line = line.append(Component.text("§e[切换]")
.hoverEvent(HoverEvent.showText(
Component.text("§e点击切换此权限状态")))
.clickEvent(ClickEvent.runCommand(
"/protect cli toggle " + land.name + " " + perm.key)));
p.sendMessage(line);
}
// 分页
if (totalPages > 1) {
Component pagination = Component.empty();
if (page > 1) {
pagination = pagination.append(Component.text("§a[◀ 上一页]")
.hoverEvent(HoverEvent.showText(Component.text("§e上一页")))
.clickEvent(ClickEvent.runCommand(
"/protect cli manage " + 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[下一页 ▶]")
.hoverEvent(HoverEvent.showText(Component.text("§e下一页")))
.clickEvent(ClickEvent.runCommand(
"/protect cli manage " + land.name + " " + (page + 1))));
}
p.sendMessage(pagination);
}
}
// ==================== 切换权限 ====================
public void togglePerm(Player p, String landName, String permKey) {
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;
}
// 切换权限
boolean oldState = false;
switch (permKey) {
case "move": oldState = land.denyMove; land.denyMove = !land.denyMove; break;
case "block_place": oldState = land.denyBlockPlace; land.denyBlockPlace = !land.denyBlockPlace; break;
case "block_break": oldState = land.denyBlockBreak; land.denyBlockBreak = !land.denyBlockBreak; break;
case "pvp": oldState = land.denyPVP; land.denyPVP = !land.denyPVP; break;
case "mount": oldState = land.denyMount; land.denyMount = !land.denyMount; break;
case "ender_pearl": oldState = land.denyEnderPearl; land.denyEnderPearl = !land.denyEnderPearl; break;
case "thrown_projectiles": oldState = land.denyThrownProjectiles; land.denyThrownProjectiles = !land.denyThrownProjectiles; break;
case "raid": oldState = land.denyRaid; land.denyRaid = !land.denyRaid; break;
case "bow": oldState = land.denyBow; land.denyBow = !land.denyBow; break;
case "potion": oldState = land.denyPotion; land.denyPotion = !land.denyPotion; break;
case "fire": oldState = land.denyFire; land.denyFire = !land.denyFire; break;
case "fire_spread": oldState = land.denyFireSpread; land.denyFireSpread = !land.denyFireSpread; break;
case "pickup": oldState = land.denyPickup; land.denyPickup = !land.denyPickup; break;
case "drop": oldState = land.denyDrop; land.denyDrop = !land.denyDrop; break;
case "explosion": oldState = land.denyExplosion; land.denyExplosion = !land.denyExplosion; break;
case "fall_damage": oldState = land.denyFallDamage; land.denyFallDamage = !land.denyFallDamage; break;
case "hunger": oldState = land.denyHunger; land.denyHunger = !land.denyHunger; break;
case "all_damage": oldState = land.denyAllDamage; land.denyAllDamage = !land.denyAllDamage; break;
case "all_effects": oldState = land.denyAllEffects; land.denyAllEffects = !land.denyAllEffects; break;
case "item_frame": oldState = land.denyItemFrame; land.denyItemFrame = !land.denyItemFrame; break;
case "redstone": oldState = land.denyRedstoneInteraction; land.denyRedstoneInteraction = !land.denyRedstoneInteraction; break;
case "door": oldState = land.denyDoorInteraction; land.denyDoorInteraction = !land.denyDoorInteraction; break;
case "audio": oldState = land.denyNoteblockJukebox; land.denyNoteblockJukebox = !land.denyNoteblockJukebox; break;
case "lead": oldState = land.denyLead; land.denyLead = !land.denyLead; break;
case "crop_harvest": oldState = land.denyCropHarvest; land.denyCropHarvest = !land.denyCropHarvest; break;
case "wool_shear": oldState = land.denyWoolShear; land.denyWoolShear = !land.denyWoolShear; break;
case "animal_feed": oldState = land.denyAnimalFeeding; land.denyAnimalFeeding = !land.denyAnimalFeeding; break;
case "glowing": oldState = land.denyGlowing; land.denyGlowing = !land.denyGlowing; break;
case "peace_mode": oldState = land.peaceMode; land.peaceMode = !land.peaceMode; break;
}
areaProtect.saveAreaToDb(land);
String permName = getPermNameByKey(permKey);
boolean newState = !oldState;
p.sendMessage("§a已切换 §e" + permName + " §7→ " + (newState ? "§a已启用" : "§c已禁用"));
}
// ==================== 数据结构 ====================
private static class PermItem {
String key;
String name;
boolean enabled;
PermItem(String key, String name, boolean enabled) {
this.key = key;
this.name = name;
this.enabled = enabled;
}
}
private List<PermItem> getPermList(AreaProtection.AreaConfig land) {
List<PermItem> perms = new ArrayList<>();
perms.add(new PermItem("move", "移动", !land.denyMove));
perms.add(new PermItem("block_place", "放置方块", !land.denyBlockPlace));
perms.add(new PermItem("block_break", "破坏方块", !land.denyBlockBreak));
perms.add(new PermItem("pvp", "玩家对战", !land.denyPVP));
perms.add(new PermItem("mount", "骑乘坐具", !land.denyMount));
perms.add(new PermItem("ender_pearl", "投掷末影珍珠", !land.denyEnderPearl));
perms.add(new PermItem("thrown_projectiles", "投掷物(三叉戟/雪球/风蛋)", !land.denyThrownProjectiles));
perms.add(new PermItem("raid", "袭击侦测", !land.denyRaid));
perms.add(new PermItem("bow", "弓箭射击", !land.denyBow));
perms.add(new PermItem("potion", "药水效果", !land.denyPotion));
perms.add(new PermItem("fire", "点燃", !land.denyFire));
perms.add(new PermItem("fire_spread", "火焰蔓延", !land.denyFireSpread));
perms.add(new PermItem("pickup", "拾取物品", !land.denyPickup));
perms.add(new PermItem("drop", "丢弃物品", !land.denyDrop));
perms.add(new PermItem("explosion", "爆炸", !land.denyExplosion));
perms.add(new PermItem("fall_damage", "摔落伤害", !land.denyFallDamage));
perms.add(new PermItem("hunger", "饥饿", !land.denyHunger));
perms.add(new PermItem("all_damage", "所有伤害", !land.denyAllDamage));
perms.add(new PermItem("all_effects", "所有效果", !land.denyAllEffects));
perms.add(new PermItem("item_frame", "展示框交互", !land.denyItemFrame));
perms.add(new PermItem("redstone", "红石电路(中继器/比较器)", !land.denyRedstoneInteraction));
perms.add(new PermItem("door", "门禁(门/按钮/压力板)", !land.denyDoorInteraction));
perms.add(new PermItem("audio", "音频(音符盒/唱片机)", !land.denyNoteblockJukebox));
perms.add(new PermItem("lead", "拴绳使用", !land.denyLead));
perms.add(new PermItem("crop_harvest", "农作物收获", !land.denyCropHarvest));
perms.add(new PermItem("wool_shear", "剪切羊毛/生物", !land.denyWoolShear));
perms.add(new PermItem("animal_feed", "投喂动物", !land.denyAnimalFeeding));
perms.add(new PermItem("glowing", "玩家发光", !land.denyGlowing));
perms.add(new PermItem("peace_mode", "和平模式", !land.peaceMode));
return perms;
}
private String getPermNameByKey(String key) {
switch (key) {
case "move": return "移动";
case "block_place": return "放置方块";
case "block_break": return "破坏方块";
case "pvp": return "玩家对战";
case "mount": return "骑乘坐具";
case "ender_pearl": return "投掷末影珍珠";
case "thrown_projectiles": return "投掷物";
case "raid": return "袭击侦测";
case "bow": return "弓箭射击";
case "potion": return "药水效果";
case "fire": return "点燃";
case "fire_spread": return "火焰蔓延";
case "pickup": return "拾取物品";
case "drop": return "丢弃物品";
case "explosion": return "爆炸";
case "fall_damage": return "摔落伤害";
case "hunger": return "饥饿";
case "all_damage": return "所有伤害";
case "all_effects": return "所有效果";
case "item_frame": return "展示框交互";
case "redstone": return "红石电路";
case "door": return "门禁";
case "audio": return "音频";
case "lead": return "拴绳使用";
case "crop_harvest": return "农作物收获";
case "wool_shear": return "剪切羊毛";
case "animal_feed": return "投喂动物";
case "glowing": return "玩家发光";
case "peace_mode": return "和平模式";
default: return key;
}
}
}
+68 -1
View File
@@ -3588,6 +3588,13 @@ public class AreaProtection implements Listener {
return areas.get(name);
}
/**
* 获取所有领地(只读)
*/
public Map<String, AreaConfig> getAllLands() {
return Collections.unmodifiableMap(areas);
}
/**
* 获取指定玩家拥有的所有领地
*/
@@ -4416,7 +4423,7 @@ public class AreaProtection implements Listener {
"setowner", "addvisitor", "removevisitor", "listvisitors", "transfer",
"info", "setowner", "addvisitor", "removevisitor",
"listvisitors", "transfer", "shop",
"menu", "菜单", "sdf1debug", "testclear"
"menu", "菜单", "sdf1debug", "testclear", "cli"
));
String first = original[0].toLowerCase();
@@ -4472,6 +4479,14 @@ public class AreaProtection implements Listener {
return true;
}
}
// CLI模式 → 显示交互式菜单
if (sender instanceof Player) {
Player p = (Player) sender;
if (plugin.areaCLIManager != null) {
plugin.areaCLIManager.showMainMenu(p);
return true;
}
}
showHelp(sender);
return true;
}
@@ -4613,6 +4628,58 @@ public class AreaProtection implements Listener {
return true;
}
// ===== CLI交互式菜单 =====
if (sub.equals("cli")) {
if (!(sender instanceof Player)) {
sender.sendMessage("§c仅玩家可用");
return true;
}
Player p = (Player) sender;
if (plugin.areaCLIManager == null) {
p.sendMessage("§cCLI管理器未初始化");
return true;
}
String action = args.length >= 2 ? args[1] : "menu";
switch (action) {
case "menu":
plugin.areaCLIManager.showMainMenu(p);
break;
case "lands":
int page = 1;
if (args.length >= 3) {
try { page = Integer.parseInt(args[2]); } catch (Exception ignored) {}
}
plugin.areaCLIManager.showLandList(p, page);
break;
case "manage":
if (args.length < 3) {
p.sendMessage("§c用法: /protect cli manage <领地名> [页码]");
break;
}
int permPage = 1;
if (args.length >= 4) {
try { permPage = Integer.parseInt(args[3]); } catch (Exception ignored) {}
}
plugin.areaCLIManager.showLandManage(p, args[2], permPage);
break;
case "toggle":
if (args.length < 4) {
p.sendMessage("§c用法: /protect cli toggle <领地名> <权限key>");
break;
}
plugin.areaCLIManager.togglePerm(p, args[2], args[3]);
break;
case "create":
// 跳转到创建命令
// 直接转发
break;
default:
plugin.areaCLIManager.showMainMenu(p);
break;
}
return true;
}
// ===== 创建 =====
if (sub.equals("创建") || sub.equals("create")) {
if (!(sender instanceof Player)) {
+3
View File
@@ -145,6 +145,7 @@ public class Main extends JavaPlugin
public AreaProtection areaProtection;
public AreaGUIManager areaGUIManager;
public AreaCLIManager areaCLIManager;
// 钱包流水查看目标(玩家名 → 查看目标)
@@ -512,6 +513,8 @@ public class Main extends JavaPlugin
areaGUIManager = new AreaGUIManager(this, areaProtection);
getServer().getPluginManager()
.registerEvents(areaGUIManager, this);
// 区域防护CLI
areaCLIManager = new AreaCLIManager(this, areaProtection);
// 发光效果检测(每2秒扫描一次)
getServer().getScheduler().runTaskTimer(this, () -> {