Engineering reference / Developer guide

LeavesAntiIllegal 开发者文档

说明从 Folia 调度模型迁移到 Leaves/Bukkit 后的模块边界、线程所有权、离线 NBT 写入协议和双版本构建方法。

Bukkit API 1.20 - 26.2 Java 17+ Maven Querz NBT 6.1
01 / Compatibility

兼容契约

代码以 Spigot Bukkit API 1.20.1 为发布基线。线上产物不调用任何核心私有实现,只依赖 Bukkit/Paper 公开类型和 Bukkit 调度器,因此可运行于 1.20 至 26.2 的兼容服务端。

维度约束实现
Java运行与编译均为 17+maven.compiler.release=17
服务端Bukkit API 1.20 - 26.2以 Spigot API 1.20.1 编译,向前兼容公共 API
线程Bukkit 世界状态只在主线程访问BukkitScheduler sync task
离线 NBT文件 I/O 不阻塞主线程异步枚举、解析、备份、写回
插件标识与旧 Folia 项目隔离dev.leavesantiillegal / LeavesAntiIllegal
不是 Folia 双平台插件移除 Folia 专用 scheduler 后,库存与区块访问采用单主线程假设。若要重新支持 Folia,应抽象调度后端并为每个实体/区块恢复所有权调度,不能把 folia-supported 加回描述文件就结束。
02 / Architecture

总体架构

触发源事件、定时任务、命令、低峰窗口
数据适配ItemStack / Inventory / CompoundTag
规则判定材料、附魔、耐久、堆叠、属性
处置与审计移除、通知、日志、备份、统计
ItemChecker在线 ItemStack 规则快照、递归库存清理与通知。
LoadedContainerScanner维护已加载区块索引,在主线程分批遍历库存。
OfflinePlayerDataScanner控制低峰调度、并发保护、文件提交和状态。

NbtItemChecker 是离线格式适配器。它与 ItemChecker 读取同一份配置,但不在异步线程调用 Bukkit 实体/世界 API;构造时在主线程把材料与附魔注册表快照成普通集合。

03 / Threading

线程所有权

操作线程原因
玩家 Inventory / EnderChest / Cursor主线程Bukkit 活对象,不能并发访问。
World / Chunk / TileEntity / Entity主线程区块和实体生命周期归服务端主线程。
注册表快照、OP 与世界目录获取主线程避免在异步任务访问 Bukkit 全局状态。
目录枚举与 .dat 解析异步文件系统和压缩 NBT 可能阻塞。
备份、临时文件、原子替换异步 文件锁域按玩家 UUID 串行,避开主线程。
在线人数计数与保护表读取可跨线程使用并发集合、volatile 快照与原子类型。
异步边界不可外扩OfflinePlayerDataScanner 的异步批次只能操作路径、普通值对象、规则快照和 NBT。新增逻辑若需要玩家名、世界对象或 Bukkit 注册表,必须在 prepareSweepOnMainThread() 中先采集不可变快照。
04 / Lifecycle

启动、重载与关闭

读取配置saveDefaultConfig + 规则快照
注册入口监听器、命令、Tab 补全
标记在线 UUID建立离线文件保护
启动扫描器玩家、容器、离线窗口任务

reloadPluginConfig() 若从非主线程进入,会先调度回主线程。真正重载按“取消旧任务 → reloadConfig → 重建规则与保护时长 → 重启扫描器”顺序执行。

  • onDisable() 取消全部 BukkitTask、注销容器监听器并清空正在等待的离线文件。
  • ItemChecker 通过 volatile 引用整体替换,事件处理始终看见完整快照。
  • 离线扫描器停止后会把 activesweepRunning 复位,正在处理的单文件依靠 finally 清理临时文件。
05 / Online path

在线检测链

PlayerListenerInventoryListener 提供低延迟阻断,周期任务提供最终一致性。两者都委托给同一个 ItemChecker,因此不会出现两套判定标准。

入口时机处置
EntityPickupItemEvent玩家拾取取消拾取、删除物品实体、发送原因。
InventoryClickEvent点击槽位或光标清空违规项并取消事件。
InventoryCreativeEvent创造栏生成物品清空 cursor 并取消事件。
Held / Interact / Swap / Drop使用和转移物品移除对应槽位或实体。
Join / Open / Close / Timer全库存检查点递归扫描背包、末影箱、cursor。

shouldSkip() 的顺序是 antiillegal.bypass、OP、UUID 白名单。容器扫描不绑定玩家,因此不会应用玩家 bypass;离线扫描显式排除 OP 和 UUID 白名单。

06 / Chunks

已加载容器扫描

LoadedContainerScanner 在启动时快照所有已加载区块,此后通过 ChunkLoadEvent / ChunkUnloadEvent 维护 ChunkRef 集合。每个周期从游标位置抽取最多 chunks-per-run 个区块。

  1. 检查区块仍加载已卸载引用立即从集合移除。
  2. 读取 tile entities只处理实现 InventoryHolder 的方块状态。
  3. 读取已加载实体要求 chunk.isEntitiesLoaded(),跳过玩家。
  4. 按对象身份去重使用 IdentityHashMap 支持双箱等共享库存对象。
  5. 委托 ItemChecker同步移除、通知并累加统计。
不触发区块加载扫描器只从已维护集合中取引用,并在访问前调用 isChunkLoaded(x, z)。不要把它改成遍历全世界坐标或直接调用可能强制加载的 API。
07 / Offline pipeline

离线数据流水线

窗口检查异步,每 60 秒,核对日期与人数
主线程快照OP UUID、世界名、playerdata 路径
异步批处理枚举、锁定 UUID、解析和判定
事务式写回备份、临时文件、原子替换、状态

并发保护协议

阶段保护结果
AsyncPreLogin LOWESTUUID 写入登录保护截止时间扫描器不会碰正在登录的文件。
JoinUUID 加入 onlinePlayerIds在线期间持续保护。
Quit / 登录失败移出在线集,保留退出缓冲时间等待服务端完成保存。
单文件处理ReentrantLock 按 UUID 加锁,再次检查保护同一 UUID 不会并发写入。
写回同目录临时文件 + ATOMIC_MOVE文件系统支持时原子提交,否则替换移动。

只有完整扫完队列才调用 saveLastSuccessfulDate()。窗口结束、禁用插件或枚举异常都以失败收尾,不推进运行日期。

08 / NBT format

NBT 兼容层

NbtItemChecker 同时接受现代 data components 与旧版 tag 结构,主入口只修改 InventoryEnderItems 两个 Compound 列表。

语义现代字段旧字段
数量countCount
耐久components.minecraft:damagetag.Damage
附魔minecraft:enchantments.levelsEnchantments / StoredEnchantments
属性minecraft:attribute_modifiersAttributeModifiers
不可破坏minecraft:unbreakable 存在Unbreakable=true
嵌套容器minecraft:container / minecraft:bundle_contentsBlockEntityTag.Items / tag.Items
未知附魔的保守上限离线线程不能动态查询注册表,启动时已注册附魔会快照真实上限;未知 ID 使用 unknown-enchant-max-level,默认 10。新增自定义附魔集成时应把允许值写入 custom-enchant-limits
09 / Rules

规则快照与判定顺序

在线与离线适配器遵循相同的早返回顺序,第一条违规原因决定处置日志。规则不是修复器:一旦判违,整个物品栈从所在列表移除。

  1. 材料黑名单标准化为不含命名空间的小写 ID。
  2. 附魔等级自定义上限优先,其次注册表最大等级乘倍率。
  3. 耐久拒绝负 damage 与超过材料最大耐久 + 10 的值。
  4. 堆叠拒绝负数和超过材料原版最大堆叠的数量。
  5. 属性七类属性按 modifier amount 绝对值比较。
  6. 不可破坏配置启用时,存在对应标记即判违。

属性检查不依赖核心私有实现,通过 Bukkit 属性键映射阈值,同时接受 generic.attack_damage 等旧键形式。

10 / Source map

源码索引

文件职责主要协作者
LeavesAntiIllegalPlugin.java生命周期、任务编排、规则快照、玩家数据保护全部模块
Metrics.javabStats状态上报功能类主类
ItemChecker.java在线 ItemStack/Inventory 判定、递归与通知监听器、两个扫描器
listener/PlayerListener.java玩家事件和登录/退出保护主类、ItemChecker
listener/InventoryListener.java库存交互拦截与开关库存检查ItemChecker
scanner/LoadedContainerScanner.java已加载区块索引和主线程批次ItemChecker
scanner/OfflinePlayerDataScanner.java窗口、队列、锁、文件事务和统计NbtItemChecker、主类
scanner/NbtItemChecker.java现代/旧版 NBT 格式判定与列表清理OfflinePlayerDataScanner
command/AntiIllegalCommand.java管理命令、状态与补全主类、扫描器
Java 包结构
dev.leavesantiillegal
├── LeavesAntiIllegalPlugin
├── ItemChecker
├── Metrics
├── command.AntiIllegalCommand
├── listener.InventoryListener
├── listener.PlayerListener
└── scanner
    ├── LoadedContainerScanner
    ├── NbtItemChecker
    └── OfflinePlayerDataScanner
11 / Build

构建与依赖

Maven 默认解析 Spigot Bukkit API 1.20.1,使用 Java 17 编译。运行时由 1.20 至 26.2 的兼容服务端提供 Bukkit API,不将服务端 API 打包进插件。

多版本构建
# 发布基线
mvn clean package
# => plugin/target/LeavesAntiIllegal-3.1.1.jar

# 兼容 1.20 - 26.2 的统一产物
mvn clean package
# => plugin/target/LeavesAntiIllegal-3.1.1.jar
依赖范围发布行为
org.spigotmc:spigot-api:1.20.1provided由服务器提供,不打入 JAR。
com.github.Querz:NBT:6.1compileshade 到 JAR,并重定位为 dev.leavesantiillegal.lib.querz

构建使用 Spigot 官方快照仓库。版本实现位于 versions/v1_20_1versions/v1_21versions/v26_2,CI 应分别编译这些模块并重新组装统一 JAR。

12 / Extension

扩展时必须保留的约束

增加一种在线判定

  • 把阈值读入 ItemChecker 的构造快照,不要在每件物品上反复查 YAML。
  • checkItem() 返回可审计的中文原因,不要直接在规则函数中删除物品。
  • 若离线也应生效,在 NbtItemChecker 增加等价格式解析和合成 NBT 测试。

增加一种扫描来源

  • Bukkit 活对象必须在主线程获取和修改。
  • 批次必须有上限与配置开关,不能一次遍历全服所有对象。
  • 复用 ItemChecker.scanContainer()checkItem(),保持通知和统计一致。

修改离线写回

  • 保留“按 UUID 加锁 → 再检查保护 → 备份 → 临时文件 → 原子替换”的顺序。
  • 任何异常都必须清理临时文件,且不能推进成功日期。
  • 不得异步调用玩家、世界、注册表或插件管理器的活状态。
13 / bStats

bStats上报

按照bStats要求添加相关内容

  • 根据bStats提供的指示,添加了类Metrics.java。来源:https://github.com/Bastian/bstats-metrics/blob/single-file/bukkit/Metrics.java
  • LeavesAntiIllegalPlugin(主类)的onEnable()添加上报相关代码
  • LeavesAntiIllegalPlugin.java
    import org.bukkit.plugin.java.JavaPlugin;
    
    public class ExamplePlugin extends JavaPlugin {
    
        @Override
        public void onEnable() {
            // You can find the plugin id of your plugins on
            // the page https://bstats.org/what-is-my-plugin-id
            int pluginId = 33374;
            Metrics metrics = new Metrics(this, pluginId);
    
            // Optional: Add custom charts
            metrics.addCustomChart(
                new Metrics.SimplePie("chart_id", () -> "My value")
            );
        }
    
    }
13 / Verification

验证与发布

  • 默认构建通过 Spigot Bukkit API 1.20.1 编译。
  • 同一份 Java 17 字节码面向 1.20 至 26.2 的 Bukkit API 兼容服务端运行。
  • JAR 内 plugin.yml 的 name、version、main 与 Maven 坐标一致。
  • JAR 不包含 Bukkit API,但包含已重定位的 Querz NBT 类和版本实现。
  • 默认 config.yml 能被 SnakeYAML 解析,且关键扫描器默认开启。
  • 合成 NBT 覆盖材料黑名单、32K 附魔、堆叠、属性、现代/旧版嵌套容器。
  • 静态文档在桌面与移动视口无横向正文溢出,搜索、目录和复制按钮可用。
  • 在隔离的 1.20、1.21 和 26.2 测试服完成启动、重载、玩家扫描、容器扫描和离线备份恢复。
发布产物选择发布以 Bukkit API 1.20.1 编译的统一产物。不要直接调用某个核心的私有类;若未来 Bukkit API 删除或改变公共方法,应通过反射适配或拆分版本适配层。
发布前快速检查
jar tf plugin/target/LeavesAntiIllegal-3.1.1.jar
unzip -p plugin/target/LeavesAntiIllegal-3.1.1.jar plugin.yml
unzip -p plugin/target/LeavesAntiIllegal-3.1.1.jar config.yml
sha256sum plugin/target/LeavesAntiIllegal-3.1.1.jar