兼容契约
代码以 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-supported 加回描述文件就结束。总体架构
NbtItemChecker 是离线格式适配器。它与 ItemChecker 读取同一份配置,但不在异步线程调用 Bukkit 实体/世界 API;构造时在主线程把材料与附魔注册表快照成普通集合。
线程所有权
| 操作 | 线程 | 原因 |
|---|---|---|
| 玩家 Inventory / EnderChest / Cursor | 主线程 | Bukkit 活对象,不能并发访问。 |
| World / Chunk / TileEntity / Entity | 主线程 | 区块和实体生命周期归服务端主线程。 |
| 注册表快照、OP 与世界目录获取 | 主线程 | 避免在异步任务访问 Bukkit 全局状态。 |
目录枚举与 .dat 解析 | 异步 | 文件系统和压缩 NBT 可能阻塞。 |
| 备份、临时文件、原子替换 | 异步 文件锁域 | 按玩家 UUID 串行,避开主线程。 |
| 在线人数计数与保护表读取 | 可跨线程 | 使用并发集合、volatile 快照与原子类型。 |
OfflinePlayerDataScanner 的异步批次只能操作路径、普通值对象、规则快照和 NBT。新增逻辑若需要玩家名、世界对象或 Bukkit 注册表,必须在 prepareSweepOnMainThread() 中先采集不可变快照。启动、重载与关闭
reloadPluginConfig() 若从非主线程进入,会先调度回主线程。真正重载按“取消旧任务 → reloadConfig → 重建规则与保护时长 → 重启扫描器”顺序执行。
onDisable()取消全部 BukkitTask、注销容器监听器并清空正在等待的离线文件。ItemChecker通过 volatile 引用整体替换,事件处理始终看见完整快照。- 离线扫描器停止后会把
active和sweepRunning复位,正在处理的单文件依靠 finally 清理临时文件。
在线检测链
PlayerListener 与 InventoryListener 提供低延迟阻断,周期任务提供最终一致性。两者都委托给同一个 ItemChecker,因此不会出现两套判定标准。
| 入口 | 时机 | 处置 |
|---|---|---|
EntityPickupItemEvent | 玩家拾取 | 取消拾取、删除物品实体、发送原因。 |
InventoryClickEvent | 点击槽位或光标 | 清空违规项并取消事件。 |
InventoryCreativeEvent | 创造栏生成物品 | 清空 cursor 并取消事件。 |
| Held / Interact / Swap / Drop | 使用和转移物品 | 移除对应槽位或实体。 |
| Join / Open / Close / Timer | 全库存检查点 | 递归扫描背包、末影箱、cursor。 |
shouldSkip() 的顺序是 antiillegal.bypass、OP、UUID 白名单。容器扫描不绑定玩家,因此不会应用玩家 bypass;离线扫描显式排除 OP 和 UUID 白名单。
已加载容器扫描
LoadedContainerScanner 在启动时快照所有已加载区块,此后通过 ChunkLoadEvent / ChunkUnloadEvent 维护 ChunkRef 集合。每个周期从游标位置抽取最多 chunks-per-run 个区块。
- 检查区块仍加载已卸载引用立即从集合移除。
- 读取 tile entities只处理实现
InventoryHolder的方块状态。 - 读取已加载实体要求
chunk.isEntitiesLoaded(),跳过玩家。 - 按对象身份去重使用
IdentityHashMap支持双箱等共享库存对象。 - 委托 ItemChecker同步移除、通知并累加统计。
isChunkLoaded(x, z)。不要把它改成遍历全世界坐标或直接调用可能强制加载的 API。离线数据流水线
并发保护协议
| 阶段 | 保护 | 结果 |
|---|---|---|
| AsyncPreLogin LOWEST | UUID 写入登录保护截止时间 | 扫描器不会碰正在登录的文件。 |
| Join | UUID 加入 onlinePlayerIds | 在线期间持续保护。 |
| Quit / 登录失败 | 移出在线集,保留退出缓冲时间 | 等待服务端完成保存。 |
| 单文件处理 | ReentrantLock 按 UUID 加锁,再次检查保护 | 同一 UUID 不会并发写入。 |
| 写回 | 同目录临时文件 + ATOMIC_MOVE | 文件系统支持时原子提交,否则替换移动。 |
只有完整扫完队列才调用 saveLastSuccessfulDate()。窗口结束、禁用插件或枚举异常都以失败收尾,不推进运行日期。
NBT 兼容层
NbtItemChecker 同时接受现代 data components 与旧版 tag 结构,主入口只修改 Inventory 和 EnderItems 两个 Compound 列表。
| 语义 | 现代字段 | 旧字段 |
|---|---|---|
| 数量 | count | Count |
| 耐久 | components.minecraft:damage | tag.Damage |
| 附魔 | minecraft:enchantments.levels | Enchantments / StoredEnchantments |
| 属性 | minecraft:attribute_modifiers | AttributeModifiers |
| 不可破坏 | minecraft:unbreakable 存在 | Unbreakable=true |
| 嵌套容器 | minecraft:container / minecraft:bundle_contents | BlockEntityTag.Items / tag.Items |
unknown-enchant-max-level,默认 10。新增自定义附魔集成时应把允许值写入 custom-enchant-limits。规则快照与判定顺序
在线与离线适配器遵循相同的早返回顺序,第一条违规原因决定处置日志。规则不是修复器:一旦判违,整个物品栈从所在列表移除。
- 材料黑名单标准化为不含命名空间的小写 ID。
- 附魔等级自定义上限优先,其次注册表最大等级乘倍率。
- 耐久拒绝负 damage 与超过材料最大耐久 + 10 的值。
- 堆叠拒绝负数和超过材料原版最大堆叠的数量。
- 属性七类属性按 modifier amount 绝对值比较。
- 不可破坏配置启用时,存在对应标记即判违。
属性检查不依赖核心私有实现,通过 Bukkit 属性键映射阈值,同时接受 generic.attack_damage 等旧键形式。
源码索引
| 文件 | 职责 | 主要协作者 |
|---|---|---|
LeavesAntiIllegalPlugin.java | 生命周期、任务编排、规则快照、玩家数据保护 | 全部模块 |
Metrics.java | bStats状态上报功能类 | 主类 |
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 | 管理命令、状态与补全 | 主类、扫描器 |
dev.leavesantiillegal
├── LeavesAntiIllegalPlugin
├── ItemChecker
├── Metrics
├── command.AntiIllegalCommand
├── listener.InventoryListener
├── listener.PlayerListener
└── scanner
├── LoadedContainerScanner
├── NbtItemChecker
└── OfflinePlayerDataScanner
构建与依赖
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.1 | provided | 由服务器提供,不打入 JAR。 |
com.github.Querz:NBT:6.1 | compile | shade 到 JAR,并重定位为 dev.leavesantiillegal.lib.querz。 |
构建使用 Spigot 官方快照仓库。版本实现位于 versions/v1_20_1、versions/v1_21 和 versions/v26_2,CI 应分别编译这些模块并重新组装统一 JAR。
扩展时必须保留的约束
增加一种在线判定
- 把阈值读入
ItemChecker的构造快照,不要在每件物品上反复查 YAML。 - 在
checkItem()返回可审计的中文原因,不要直接在规则函数中删除物品。 - 若离线也应生效,在
NbtItemChecker增加等价格式解析和合成 NBT 测试。
增加一种扫描来源
- Bukkit 活对象必须在主线程获取和修改。
- 批次必须有上限与配置开关,不能一次遍历全服所有对象。
- 复用
ItemChecker.scanContainer()或checkItem(),保持通知和统计一致。
修改离线写回
- 保留“按 UUID 加锁 → 再检查保护 → 备份 → 临时文件 → 原子替换”的顺序。
- 任何异常都必须清理临时文件,且不能推进成功日期。
- 不得异步调用玩家、世界、注册表或插件管理器的活状态。
bStats上报
按照bStats要求添加相关内容
- 根据bStats提供的指示,添加了类
Metrics.java。来源:https://github.com/Bastian/bstats-metrics/blob/single-file/bukkit/Metrics.java - 在
LeavesAntiIllegalPlugin(主类)的onEnable()添加上报相关代码
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")
);
}
}
验证与发布
- 默认构建通过 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 测试服完成启动、重载、玩家扫描、容器扫描和离线备份恢复。
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