feat: 新增SN防伪系统,接入四类道具签发、拾取/合成/熔炼探查、销毁解绑与PHP目录同步
Check Source / check (push) Successful in 3s
Auto Release / build (push) Successful in 2m2s
Simple Test / test (push) Successful in 1s
Test Env / test (push) Successful in 1s

This commit is contained in:
youpaishidifu
2026-09-29 11:36:53 +08:00
parent 62ebf5c771
commit 7aad66f219
7 changed files with 2214 additions and 4 deletions
@@ -1765,6 +1765,25 @@ public class AreaGUIManager implements Listener {
return;
}
}
// ★ SN 管控:1人同种类仅限1个
if (plugin.getSnManager() != null
&& plugin.getSnManager().isBlocked(
p, SnManager.TYPE_WAND)) {
p.sendMessage("§e你已有区域选择工具的有效登记,"
+ "旧SN未注销前不能重新申领");
areaProtect.wandCooldownMap.put(p.getUniqueId(), now);
return;
}
if (plugin.getSnManager() != null) {
String sn = plugin.getSnManager().applySn(
p, SnManager.TYPE_WAND, "区域选择工具");
if (sn == null) {
areaProtect.wandCooldownMap.put(p.getUniqueId(), now);
return;
}
plugin.getSnManager().writeSn(
wand, sn, SnManager.TYPE_WAND);
}
p.getInventory().addItem(wand);
// ★ GUI模式不发送聊天消息
areaProtect.wandCooldownMap.put(p.getUniqueId(), now);
@@ -6628,12 +6628,31 @@ public class AreaProtection implements Listener {
return true;
}
}
// ★ SN 管控:1人同种类仅限1个
if (plugin.getSnManager() != null
&& plugin.getSnManager().isBlocked(
p, SnManager.TYPE_WAND)) {
p.sendMessage("§c§l[防护] §f你已有区域选择工具的有效登记,"
+ "旧SN未注销前不能重新申领");
wandCooldownMap.put(p.getUniqueId(), now);
return true;
}
ItemStack tool = new ItemStack(WAND);
ItemMeta meta = tool.getItemMeta();
meta.setDisplayName("§a§l区域选择工具");
meta.setLore(Arrays.asList(
"§7左键: 位置1", "§7右键: 位置2"));
tool.setItemMeta(meta);
if (plugin.getSnManager() != null) {
String sn = plugin.getSnManager().applySn(
p, SnManager.TYPE_WAND, "区域选择工具");
if (sn == null) {
wandCooldownMap.put(p.getUniqueId(), now);
return true;
}
plugin.getSnManager().writeSn(
tool, sn, SnManager.TYPE_WAND);
}
p.getInventory().addItem(tool);
p.sendMessage("§a§l[防护] §f已获取选择工具");
wandCooldownMap.put(p.getUniqueId(), now);
@@ -110,7 +110,25 @@ public class LandRecordManager implements Listener {
/** 直接发放一枚回声碎片给玩家(GUI 菜单调用) */
public void giveEchoShard(Player p) {
// ★ SN 管控:1人同种类仅限1个
if (plugin.getSnManager() != null
&& plugin.getSnManager().isBlocked(
p, SnManager.TYPE_ECHO)) {
p.sendMessage("§c§l[回声碎片] §f你已有回声碎片的有效登记,"
+ "旧SN未注销前不能重新申领");
return;
}
ItemStack shard = createEchoShard();
if (plugin.getSnManager() != null) {
String sn = plugin.getSnManager().applySn(
p, SnManager.TYPE_ECHO, "回声碎片");
if (sn == null) {
p.sendMessage("§c§l[回声碎片] §f申领被SN系统拒绝");
return;
}
plugin.getSnManager().writeSn(
shard, sn, SnManager.TYPE_ECHO);
}
HashMap<Integer, ItemStack> left =
p.getInventory().addItem(shard);
if (!left.isEmpty()) {
+108 -1
View File
@@ -155,6 +155,7 @@ public class Main extends JavaPlugin
// 在 Main.java 字段声明区域(bondManager 附近)添加:
// 在 bondManager 字段附近添加:
private BondPrinter bondPrinter;
private SnManager snManager;
private OrderManager orderManager;
public OrderManager getOrderManager() {
@@ -591,6 +592,17 @@ public class Main extends JavaPlugin
getCommand("cypay").setTabCompleter(cypayCommand);
}
bondPrinter = new BondPrinter(this);
// ===== SN 防刷系统:建表 + 注册事件 =====
try {
snManager = new SnManager(this);
snManager.init();
getServer().getPluginManager()
.registerEvents(snManager, this);
} catch (Throwable t) {
snManager = null;
getLogger().severe("[SN] 初始化失败: " + t.getMessage());
t.printStackTrace();
}
// cdkManager.loadCDKsFromDir();
//14 ====商店====
// ★ 构造器内已加载完毕,此处重复
@@ -757,6 +769,10 @@ public class Main extends JavaPlugin
return bondPrinter;
}
public SnManager getSnManager() {
return snManager;
}
public ShopManager getShopManager() {
return shopManager;
}
@@ -2716,6 +2732,34 @@ public class Main extends JavaPlugin
}
}
/**
* SN 打标:为即将发放的物品登记 SN 并写入物品。
*
* @return true = 可以发放;false = 被 SN 管控拒绝(调用方放弃发放)
*/
private boolean snTag(Player p, ItemStack it,
String type, String what) {
if (snManager == null) return true;
String sn = snManager.applySnQuiet(p, type, what);
if (sn == null) {
// 冷静期 / 同种类已有有效登记 → 拒绝发放
if (snManager.isBlocked(p, type)) return false;
// 数据库异常等:放行但不打标,避免卡死正常发放
getLogger().warning("[SN] 打标失败: " + what
+ " -> " + p.getName());
return true;
}
snManager.writeSn(it, sn, type);
return true;
}
/** 轻点:清理某玩家背包内无 SN 的插件自定义物品并补发带 SN 版本 */
private void snSweep(CommandSender sender, Player target) {
int[] r = snManager.auditAndResend(target, false);
sender.sendMessage("§a[SN] " + target.getName()
+ ":清理 §f" + r[0] + " §a件,补发 §f" + r[1] + " §a件");
}
private static final String MENU_SNOWBALL_TAG =
"sdf1_menu";
@@ -2745,6 +2789,12 @@ public class Main extends JavaPlugin
return;
}
// ★ SN 管控:1人同种类仅限1个,旧SN未注销/冷静期未过不再发放
if (snManager != null
&& snManager.isBlocked(p, SnManager.TYPE_MENU)) {
return;
}
ItemStack customIcon =
menuIconMgr != null ? menuIconMgr.getIcon(p) : null;
// getLogger().info("[菜单] " + name + " 自定义图标: " + (customIcon != null ? customIcon.getType().name() : "null"));
@@ -2779,6 +2829,10 @@ public class Main extends JavaPlugin
cm.setLore(cmLore);
customIcon.setItemMeta(cm);
}
if (!snTag(p, customIcon,
SnManager.TYPE_MENU, "自定义菜单图标")) {
return;
}
int slot = p.getInventory()
.firstEmpty();
if (slot >= 0) {
@@ -2804,6 +2858,9 @@ public class Main extends JavaPlugin
im.setLore(lore);
snow.setItemMeta(im);
}
if (!snTag(p, snow, SnManager.TYPE_MENU, "雪球菜单")) {
return;
}
int slot = p.getInventory()
.firstEmpty();
if (slot >= 0) {
@@ -2831,6 +2888,12 @@ public class Main extends JavaPlugin
return;
}
}
// ★ SN 管控:1人同种类仅限1个,旧SN未注销/冷静期未过不再发放
if (snManager != null
&& snManager.isBlocked(p, SnManager.TYPE_MENU)) {
return;
}
ItemStack snow = new ItemStack(
Material.SNOWBALL);
ItemMeta im = snow.getItemMeta();
@@ -2844,6 +2907,9 @@ public class Main extends JavaPlugin
im.setLore(lore);
snow.setItemMeta(im);
}
if (!snTag(p, snow, SnManager.TYPE_MENU, "雪球菜单")) {
return;
}
int slot = p.getInventory()
.firstEmpty();
if (slot >= 0) {
@@ -5982,6 +6048,46 @@ public class Main extends JavaPlugin
bondPrinter.printAll(sender);
return true;
}
// ===== /printer item [玩家名] → 导出 SN 记录 =====
if (args.length >= 1
&& "item".equalsIgnoreCase(args[0])) {
if (snManager == null) {
sender.sendMessage("§cSN系统未初始化");
return true;
}
String snTarget = args.length >= 2
? args[1] : null;
sender.sendMessage("§e正在导出SN记录...");
snManager.printSnRecords(sender, snTarget);
return true;
}
// ===== /printer sweep [玩家名] → 一次性轻点补发 =====
if (args.length >= 1
&& "sweep".equalsIgnoreCase(args[0])) {
if (snManager == null) {
sender.sendMessage("§cSN系统未初始化");
return true;
}
if (args.length >= 2) {
Player tp = Bukkit.getPlayer(args[1]);
if (tp == null) {
sender.sendMessage("§c玩家不在线: " + args[1]);
return true;
}
snSweep(sender, tp);
return true;
}
sender.sendMessage("§e正在轻点全体在线玩家...");
int removed = 0, resent = 0;
for (Player tp : Bukkit.getOnlinePlayers()) {
int[] r = snManager.auditAndResend(tp, false);
removed += r[0];
resent += r[1];
}
sender.sendMessage("§a[SN] 轻点完成:清理 §f" + removed
+ " §a件,补发 §f" + resent + " §a件");
return true;
}
if (args.length == 1) {
// 打印指定玩家
String target = args[0];
@@ -5992,7 +6098,8 @@ public class Main extends JavaPlugin
return true;
}
sender.sendMessage(
"§7用法: /printer [玩家名]");
"§7用法: /printer [玩家名] | /printer item [玩家名]"
+ " | /printer sweep [玩家名]");
return true;
}
// ===== /chat 独立命令 =====
+20 -3
View File
@@ -485,10 +485,27 @@ public class PVPManager implements Listener {
break;
}
}
if (!has) {
p.getInventory()
.addItem(createTool());
if (has) return;
// ★ SN 管控:1人同种类仅限1个
if (plugin.getSnManager() != null
&& plugin.getSnManager().isBlocked(
p, SnManager.TYPE_PVP)) {
p.sendMessage("§c[PVP] 你已有PVP圈地棒的有效登记,"
+ "旧SN未注销前不能重新申领");
return;
}
ItemStack tool = createTool();
if (plugin.getSnManager() != null) {
String sn = plugin.getSnManager().applySn(
p, SnManager.TYPE_PVP, "PVP圈地棒");
if (sn == null) {
p.sendMessage("§c[PVP] 申领被SN系统拒绝");
return;
}
plugin.getSnManager().writeSn(
tool, sn, SnManager.TYPE_PVP);
}
p.getInventory().addItem(tool);
}
// ===== 边框粒子 =====
File diff suppressed because it is too large Load Diff
+9
View File
@@ -2012,6 +2012,15 @@ public class WebManager {
catch (Exception e) { landSyncFailCount++; }
}
// ★ SN 防刷同步(任务6):手持上报 + 事件队列 + 命令拉取
try {
SnManager snm = plugin.getSnManager();
if (snm != null) snm.tickSync();
} catch (Throwable t) {
plugin.getLogger().warning(
"[SN] 同步异常: " + t.getMessage());
}
// 全量批处理同步(仅在nextSyncTime到达时执行)
if (now >= nextSyncTime) {
try { doActiveSyncBatch(); }