feat: 新增区域防护,修复无参数指令报错问题

This commit is contained in:
ypsdf1
2026-05-29 00:17:56 +08:00
parent aa719dc2da
commit c66ec5eaaa
10 changed files with 3379 additions and 96 deletions
+72 -8
View File
@@ -361,12 +361,8 @@ http-port=443
1. 自助退款,买多买错,不用慌,一件退单很安心<br>
2. 购物车,买的多怕买错,先加购物车,后续统一结算<br>
3. 害怕订单找不到?一键查看历史订单<br>
![](img/shop4.png)
4. 玩家要耍赖皮,拿着小票来找麻烦怎么办?退款订单自动贴标,拒绝白嫖
![](img/shop7.png)<br>
![](img/shop6.png)
5. 想查看玩家消费记录?小手一点,明明白白<br>
![](img/shop5.png)
@@ -391,26 +387,94 @@ http-port=443
- printer 打印流水数据,支持不带参数&带玩家名为参数<br>
- sdf1_login printer 作用同printer<br>
- sdf1_login shop 商城
- protect 区域防护
## 4. 自定义菜单物品
- 打开主界面,选择"我的"进入<br>
![](img/menu1.png)
- 将你的自定义物品与雪球交换<br>
![](img/menu2.png)
- 随后系统会给你重新发放菜单,并且此时图标就是你的自定义物品了
## 5. pvp战神榜
- 通过此功能,你可以在服务器里制作大服务器同款的击杀排行榜,而且能精准定位指定区域<br>
![](img/pvp1.png)
- 并且有着网游同款的击杀通报<br>
![](img/pvp3.png)
- 播报逻辑是:<br>
- 3杀<br>
- 5杀<br>
- 5杀以上每杀一次都是一条播报<br>
- 终结者通报,触发连杀的玩家被终结也会有全服通报
## 6. 区域防护
- 通过此功能,您可在服务器里需要防护的地方(比如展示框不想被玩家随便破坏、主城不准玩家袭击等)加以防护。<br>
- 还能够保护指定区域的玩家不受任何伤害。<br>
| 参数 | 效果 |
| :---: | :---: |
| 和平模式 | 所有敌对生物全部进虚空 |
| 禁止袭击 | 立即清理玩家身上的袭击效果,传灾厄村民进虚空 |
| 清除XX效果 | 立即清除玩家身上的效果
| 给与xx效果 | 根据配置文件,给予玩家指定等级、时长的效果
## 生物属性对照表
| 中文名称 | 类型 | 附加属性 |
| :---: | :----: | :----: |
| 溺尸 | 近战/远程 | 亡灵、水生 |
| 骷髅 | 远程 | 亡灵 |
| 流浪者 | 远程 | 亡灵、寒冷群系变种 |
| 沼骸 | 远程 | 亡灵、沼泽变种 |
| 焦骸 | 远程 | 亡灵、沙漠变种、免疫阳光 |
| 苦力怕 | 近战(爆炸) | 无 |
| 幻翼 | 远程(俯冲) | 亡灵、夜间生成 |
| 掠夺者 | 远程 | 灾厄村民、袭击成员 |
| 卫道士 | 近战 | 灾厄村民、袭击成员 |
| 幻术师 | 远程 | 灾厄村民、Java专属、隐身分身 |
| 旋风人 | 远程 | 试炼密室生成、免疫弹射物、强击退 |
| 监守者 | 近战/声波远程 | 深暗之域、附带盲视效果 |
| 远古守卫者 | 近战/激光远程 | 海底神殿、水下、附加挖掘疲劳 |
| 守卫者 | 近战/激光远程 | 海底神殿、水下 |
配置文件解析:<br>
- 支持注释格式:`Java的//单行注释`、`yml的#单行注释`、`HTML的<!--- --->注释`和`Java/* */块注释`
```txt
# 区域: 主城区
起点: world,-130,-713
终点: world,-427,-560
高度范围: 0-255
没收物品: 蘑菇,毒蘑菇 #没收哪些物品(末地推荐这两个)
// 禁止放置方块 #保护区内,非区白/全白玩家禁止放置方块
// 禁止破坏方块 # 同上
禁止PVP # 玩家之间不能PVP
禁止摔伤 # 摔落无法造成任何伤害
禁止饥饿 # 不会消耗饱食度
禁止一切伤害 # 任何怪物都无法造成伤害
# 禁止丢弃物品
# 禁止末影珍珠
# 禁止使用弓箭
# 禁止骑乘
// 和平模式 // 所有的敌对生物全部传送进虚空
禁止袭击
禁止交互展示框 # 核心,任何玩家不能触碰展示框。区白/全白玩家除外
// 清除所有效果 # 清理所有负面效果
效果 村庄英雄 255 999999 # 给予玩家效果。格式为: 效果 效果名字 等级(最高255) 时间
效果 生命提升 255 999999
效果 抗性提升 255 999999
效果 抗火 255 999999
# 禁止药水
禁止爆炸
禁止使用物品: TNT
# 效果: 夜视 1 999
进入提示: 欢迎来到保护区 # 是否开启进入提示,即自定义提示
离开提示: 已离开保护区
没收提示: 你的违禁品已被没收
通报批评: §c§l{player} §f在{area}违规携带违禁品,被没收 §e{count}§f件! # 通报违禁品。{player} 玩家名 {count}数量 {area}区域
# 惩罚命令: ban {player} 60s 违规
```
## 运行截图
主界面
-48
View File
@@ -1,48 +0,0 @@
#-------------------------------------------------------------------------------#
# Qodana analysis is configured by qodana.yaml file #
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
#-------------------------------------------------------------------------------#
#################################################################################
# WARNING: Do not store sensitive information in this file, #
# as its contents will be included in the Qodana report. #
#################################################################################
version: "1.0"
#Specify inspection profile for code analysis
profile:
name: qodana.starter
#Enable inspections
#include:
# - name: <SomeEnabledInspectionId>
#Disable inspections
#exclude:
# - name: <SomeDisabledInspectionId>
# paths:
# - <path/where/not/run/inspection>
projectJDK: "21" #(Applied in CI/CD pipeline)
#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
#bootstrap: sh ./prepare-qodana.sh
#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
#plugins:
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)
# Quality gate. Will fail the CI/CD pipeline if any condition is not met
# severityThresholds - configures maximum thresholds for different problem severities
# testCoverageThresholds - configures minimum code coverage on a whole project and newly added code
# Code Coverage is available in Ultimate and Ultimate Plus plans
#failureConditions:
# severityThresholds:
# any: 15
# critical: 5
# testCoverageThresholds:
# fresh: 70
# total: 50
#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
linter: jetbrains/qodana-jvm:2026.1
File diff suppressed because it is too large Load Diff
@@ -22,6 +22,7 @@ public class BondManager {
public static final String TX_FREEZE = "freeze";
public static final String TX_UNFREEZE = "unfreeze";
public static final int MAX_WATER_DAYS = 14;
public static final int ADMIN_MAX_WATER_DAYS = 30;
@@ -38,7 +38,9 @@ public class ChatInputManager {
TICKET_ADMIN_DESC,
TICKET_ADMIN_PROVIDER,
BACK_CHECK_ECONOMY_CONFIRM,
}
USER_MGMT_SEARCH,
}
public static class PlayerState {
public InputType type = InputType.NONE;
@@ -49,6 +51,7 @@ public class ChatInputManager {
public int ticketId = 0;
public int smtpStep = 0;
public int scoreValue = 0;
}
private final Map<String, PlayerState> states =
+57 -12
View File
@@ -46,6 +46,9 @@ public class GUIManager implements Listener {
"§6§l用户管理";
public static final String T_TASK_CENTER =
"§6§l任务中心";
private final Map<UUID, Integer> userMgmtPages = new HashMap<>();
private static final int UMGMT_PAGE_SIZE = 45;
public GUIManager(Main plugin) {
@@ -1853,6 +1856,7 @@ public class GUIManager implements Listener {
g.setItem(24, mkItem(Material.ARROW, "§7返回"));
// openAdmin 方法中加:
p.openInventory(g);
}
@@ -1907,30 +1911,71 @@ public class GUIManager implements Listener {
}
public void openUserManagement(Player p) {
Inventory g = Bukkit.createInventory(
null, 54, T_USER_MGMT);
openUserManagement(p, 1);
}
public void openUserManagement(Player p, int page) {
userMgmtPages.put(p.getUniqueId(), page);
Inventory g = Bukkit.createInventory(null, 54, T_USER_MGMT);
fillBg(g);
List<Map<String, Object>> users = plugin.getDb()
.getAllUsers();
List<Map<String, Object>> users = plugin.getDb().getAllUsers();
int totalPages = Math.max(1, (users.size() + UMGMT_PAGE_SIZE - 1) / UMGMT_PAGE_SIZE);
if (page > totalPages) page = totalPages;
userMgmtPages.put(p.getUniqueId(), page);
int start = (page - 1) * UMGMT_PAGE_SIZE;
int end = Math.min(start + UMGMT_PAGE_SIZE, users.size());
int slot = 0;
for (Map<String, Object> user : users) {
if (slot >= 45) break;
for (int i = start; i < end; i++) {
if (slot >= UMGMT_PAGE_SIZE) break;
Map<String, Object> user = users.get(i);
String name = (String) user.get("player_name");
int pts = ((Number) user.getOrDefault(
"points", 0)).intValue();
int stage = ((Number) user.getOrDefault(
"gift_stage", 0)).intValue();
int pts = ((Number) user.getOrDefault("points", 0)).intValue();
int stage = ((Number) user.getOrDefault("gift_stage", 0)).intValue();
g.setItem(slot, mkItem(Material.PLAYER_HEAD,
"§e" + name,
"§7积分: " + pts
+ " 礼包阶段: " + stage,
"§7积分: " + pts + " 礼包阶段: " + stage,
"§7双击管理"));
slot++;
}
// ===== 底部导航栏 =====
// 上一页
g.setItem(45, page > 1
? mkItem(Material.ARROW, "§a上一页")
: mkItem(Material.GRAY_STAINED_GLASS_PANE, " "));
// 页码信息
g.setItem(49, mkItem(Material.PAPER,
"§e第" + page + "/" + totalPages + "页",
"§7共 " + users.size() + " 名玩家"));
// 下一页
g.setItem(51, page < totalPages
? mkItem(Material.ARROW, "§a下一页")
: mkItem(Material.GRAY_STAINED_GLASS_PANE, " "));
// 搜索按钮
g.setItem(46, mkItem(Material.ENDER_PEARL,
"§b§l搜索玩家",
"§7点击后输入玩家名",
"§7快速定位指定玩家"));
// 返回
g.setItem(53, mkItem(Material.ARROW, "§7返回"));
p.openInventory(g);
}
public int getUserMgmtPage(Player p) {
return userMgmtPages.getOrDefault(p.getUniqueId(), 1);
}
public int getUserMgmtTotalUsers() {
return plugin.getDb().getAllUsers().size();
}
public void openTaskCenter(Player p) {
Inventory g = Bukkit.createInventory(
null, 27, T_TASK_CENTER);
+203 -22
View File
@@ -143,6 +143,8 @@ public class Main extends JavaPlugin
public OrderManager getOrderManager() {
return orderManager;
}
public AreaProtection areaProtection;
// 钱包流水查看目标(玩家名 → 查看目标)
private final Map<String, String> walletViewTarget =
@@ -282,6 +284,16 @@ private PVPManager pvpManager;
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent e) {
if (areaProtection != null) {
Player p = e.getPlayer();
areaProtection.onPlayerOffline(
p.getUniqueId(), p.getName(), "");
}
}
// public RadioManager radio;
@Override
public void onEnable() {
@@ -380,8 +392,8 @@ private PVPManager pvpManager;
if (getCommand("垃圾清理") != null)
getCommand("垃圾清理")
.setExecutor(this);
if (getCommand("oa") != null)
getCommand("oa").setExecutor(this);
/* if (getCommand("oa") != null)
getCommand("oa").setExecutor(this);*/
if (getCommand("menu") != null)
getCommand("menu").setExecutor(this);
if (getCommand("printer") != null) {
@@ -389,6 +401,13 @@ private PVPManager pvpManager;
getCommand("shop").setExecutor(this);
getCommand("商店").setExecutor(this);
// getCommand("testorder").setExecutor(this);
if (getCommand("protect") != null) {
getCommand("protect").setExecutor(this);
getCommand("protect").setTabCompleter(this);
getLogger().info("[防护] protect命令注册成功");
} else {
getLogger().severe("[防护] protect命令注册失败!plugin.yml可能有问题");
}
}
@@ -459,12 +478,66 @@ private PVPManager pvpManager;
configMgr.loadSettings();
orderManager = new OrderManager(this);
//15 ======区域保护=========
areaProtection = new AreaProtection(this);
getServer().getPluginManager()
.registerEvents(areaProtection, this);
// 效果清除定时器
getServer().getScheduler().runTaskTimer(this, () -> {
if (areaProtection != null) {
areaProtection.checkPendingClears();
}
}, 100L, 100L);
getServer().getPluginManager()
.registerEvents(areaProtection, this);
// 和平模式独立检测(每2秒扫描)
getServer().getScheduler().runTaskTimer(this, () -> {
if (areaProtection == null) return;
areaProtection.cleanupExpiredProtections();
for (Player p : Bukkit.getOnlinePlayers()) {
AreaProtection.AreaConfig ac = areaProtection.getArea(
p.getWorld().getName(),
p.getLocation().getBlockX(),
p.getLocation().getBlockY(),
p.getLocation().getBlockZ());
if (ac != null && ac.peaceMode) {
areaProtection.banHostilesWithWhitelist(p, ac);
}
}
}, 40L, 40L);
// 效果清理定时器
getServer().getScheduler().runTaskTimer(this, () -> {
if (areaProtection != null) {
areaProtection.checkPendingClears();
}
}, 20L, 20L);
/* // ===== 命令诊断 =====
getLogger().info("========== 命令诊断 ==========");
String[] expectedCmds = {
"sdf1_login", "reg", "login", "l", "签到",
"recycle", "垃圾清理", "oa", "menu",
"printer", "shop", "商店", "protect",
"区域保护", "cypay"
};
for (String cmdName : expectedCmds) {
if (getCommand(cmdName) != null) {
getLogger().info("[诊断] " + cmdName
+ " → plugin.yml已注册, executor="
+ (getCommand(cmdName).getExecutor() != null
? "已设置" : "未设置"));
} else {
getLogger().severe("[诊断] " + cmdName
+ " → plugin.yml中不存在!");
}
}
getLogger().info("========== 诊断结束 ==========");*/
getLogger().info("Sdf1_login v1.0 | 就绪");
getLogger().info("[SDF1] orderManager=" + (orderManager != null ? "OK" : "NULL"));
// getLogger().info("[SDF1] orderManager=" + (orderManager != null ? "OK" : "NULL"));
}
public BondPrinter getBondPrinter() {
@@ -1840,6 +1913,16 @@ private PVPManager pvpManager;
}
}
private List<String> filterTab(List<String> opts, String prefix) {
List<String> r = new ArrayList<>();
for (String o : opts) {
if (o.toLowerCase().startsWith(prefix.toLowerCase())) {
r.add(o);
}
}
return r;
}
/*
雪球菜单:
@@ -3658,25 +3741,46 @@ private PVPManager pvpManager;
String[] args) {
String cmdName = cmd.getName().toLowerCase();
if (cmd.getName().equalsIgnoreCase("testorder")
&& sender instanceof Player) {
Player p = (Player) sender;
p.sendMessage("§e=== 测试订单中心 ===");
p.sendMessage("§7当前界面: §f"
+ p.getOpenInventory().getTitle());
this.getOrderManager().openOrderCenter(p);
// 延迟1秒检查实际打开了什么
Bukkit.getScheduler().runTaskLater(this, () -> {
if (p.isOnline()) {
String opened = p.getOpenInventory()
.getTitle();
p.sendMessage("§7打开后界面: §f" + opened);
this.getLogger().info("[TEST] 打开后: "
+ opened);
}
}, 20L);
return true;
// 区域防护(必须在最前面)
if (cmd.getName().equalsIgnoreCase("protect")
|| cmd.getName().equals("区域保护")) {
if (areaProtection == null) {
sender.sendMessage("§c区域防护未初始化");
return true;
}
if (args.length == 0) {
sender.sendMessage("§a§l===== 区域防护 =====");
sender.sendMessage("§e/protect 工具 §7获取选区工具");
sender.sendMessage("§e/protect 创建 <区域名> §7创建区域");
sender.sendMessage("§e/protect 列表 §7查看所有区域");
sender.sendMessage("§e/protect 重载 §7重载配置");
sender.sendMessage("§e/protect add <玩家> §7全局加白");
sender.sendMessage("§e/protect add <区域> <玩家> §7区域加白");
sender.sendMessage("§e/protect remove <玩家> §7全局删白");
sender.sendMessage("§e/protect remove <区域> <玩家> §7区域删白");
sender.sendMessage("§e/protect additem <物品ID> §7全局加黑");
sender.sendMessage("§e/protect additem <区域> <物品ID> §7区域加黑");
sender.sendMessage("§e/protect removeitem <物品ID> §7全局删黑");
sender.sendMessage("§e/protect removeitem <区域> <物品ID> §7区域删黑");
sender.sendMessage("§e/protect list §7全局白名单");
sender.sendMessage("§e/protect list <区域> §7区域白名单");
sender.sendMessage("§e/protect listitem §7全局物品黑名单");
sender.sendMessage("§e/protect listitem <区域> §7区域物品黑名单");
sender.sendMessage("§e/protect expand [格数] §7扩建选区");
sender.sendMessage("§e/protect contraction [格数] §7收缩选区");
return true;
}
// 其他子命令交给 areaProtection
try {
return areaProtection.handleCommand(sender, args);
} catch (Exception e) {
sender.sendMessage("§c执行出错: " + e.getMessage());
e.printStackTrace();
return true;
}
}
if (cmd.getName().equalsIgnoreCase("shop")
|| cmd.getName().equals("商店")) {
if (!(sender instanceof Player)) {
@@ -5262,6 +5366,83 @@ private PVPManager pvpManager;
"ticket", "oa", "stop"));
}
}
if (cmd.getName().equalsIgnoreCase("protect")
|| cmd.getName().equals("区域保护")) {
String sub = args.length > 0 ? args[0].toLowerCase() : "";
// ===== 第一层:子命令 =====
if (args.length == 1) {
List<String> opts = Arrays.asList(
"工具", "创建", "列表", "重载", "删除",
"add", "remove",
"additem", "removeitem",
"on", "off", "tempon",
"expand", "contraction",
"list", "listitem");
return filterTab(opts, args[0]);
}
// ===== 第二层 =====
if (args.length == 2) {
// add/remove: global + 在线玩家 + 区域名
if (sub.equals("add") || sub.equals("remove")) {
List<String> all = new ArrayList<>();
all.add("global");
for (Player pl : Bukkit.getOnlinePlayers())
all.add(pl.getName());
all.addAll(areaProtection.getAreaNames());
return filterTab(all, args[1]);
}
// additem/removeitem: 区域名 + 常用物品ID
if (sub.equals("additem") || sub.equals("removeitem")) {
List<String> all = new ArrayList<>();
all.add("global");
all.addAll(areaProtection.getAreaNames());
return filterTab(all, args[1]);
}
// list/listitem: 区域名
if (sub.equals("list") || sub.equals("listitem")) {
List<String> all = new ArrayList<>(areaProtection.getAreaNames());
return filterTab(all, args[1]);
}
// expand/contraction: 数字
if (sub.equals("expand") || sub.equals("contraction")) {
return Arrays.asList("1", "3", "5", "10", "20", "50");
}
// 创建: 自由输入区域名
if (sub.equals("创建") || sub.equals("delete") || sub.equals("删除")) {
List<String> all = new ArrayList<>(areaProtection.getAreaNames());
return filterTab(all, args[1]);
}
}
// ===== 第三层 =====
if (args.length == 3) {
// add global <玩家> 或 add <区域> <玩家>
if (sub.equals("add") || sub.equals("remove")) {
List<String> players = new ArrayList<>();
for (Player pl : Bukkit.getOnlinePlayers())
players.add(pl.getName());
return filterTab(players, args[2]);
}
// additem global <物品> 或 additem <区域> <物品>
if (sub.equals("additem") || sub.equals("removeitem")) {
List<String> items = Arrays.asList(
"TNT", "BROWN_MUSHROOM", "RED_MUSHROOM",
"MUSHROOM_STEW", "POISONOUS_POTATO",
"SPIDER_EYE", "POTION", "SPLASH_POTION",
"OMINOUS_BOTTLE", "ENDER_PEARL",
"BOW", "CROSSBOW", "ARROW",
"FLINT_AND_STEEL");
return filterTab(items, args[2]);
}
}
return null;
}
if (cmd.getName().equalsIgnoreCase("chat")
&& args.length == 1
&& isAdmin(sender)) {
@@ -5276,4 +5457,4 @@ private PVPManager pvpManager;
return list;
}
}
}
@@ -125,7 +125,8 @@ public class RadioDownloadListener
// 不踢出,只记录熔断,玩家正常游玩
p.sendMessage("§e§l[Radio] 资源包加载失败"
+ ",跳过资源包,部分画面、音频可能出现问题"
+ "§d§l若想继续下载,请退出重进");
+ "\n§d§l若想继续下载,请退出重进" +
"\n§b§l若持续失败,请重启客户端");
return;
}
@@ -110,7 +110,7 @@ public class SalesStatsManager {
public void report() {
if (sessionItems == 0 && sessionRefunds == 0) {
plugin.getLogger().info("[Sales] 无数据,跳过");
// plugin.getLogger().info("[Sales] 无数据,跳过");
return;
}
+6 -3
View File
@@ -42,9 +42,9 @@ commands:
签到:
description: 每日签到(中文)
usage: /签到
oa:
description: 申请删除账号
usage: /oa [玩家名]
#oa:
# description: 申请删除账号
# usage: /oa [玩家名]
删除账号:
description: 申请删除账号(中文)
usage: /删除账号 [玩家名]
@@ -99,3 +99,6 @@ commands:
#testorder:
# description: 测试订单中心
# usage: /testorder
protect:
description: 区域保护
usage: /protect