外观
SealItems 开发者接入
依赖与加载顺序
公开平台中立契约来自 sealitems-api。当前工程没有声明公开 Maven 仓库,维护者应交付与服务端 JAR 同一次构建产生的 sealitems-api-1.0.0.jar;调用方把它放进项目的 libs/,只用于编译:
kotlin
dependencies {
compileOnly(files("libs/sealitems-api-1.0.0.jar"))
compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT")
}不要把 API JAR 放进服务器 plugins/,也不要 shade 进自己的插件。需要 Paper 专用的强化事件、SealItemsCraftingBukkitApi 或套装 mechanic provider 时,以完全相同构建的 SealItems 主 JAR 作为 compileOnly;这些类型不属于平台中立 API artifact。
必选联动在 plugin.yml 声明:
yaml
depend: [SealItems]可选联动改用 softdepend: [SealItems],并在 onEnable 时检查服务是否存在;服务缺失时必须关闭自己的联动功能。不要缓存跨越插件停服/重载边界的 Bukkit service。
Bukkit Services API
从 Bukkit.getServicesManager() 获取 cn.sealplugins.items.api.SealItemsApi。入口均要求主线程:
kotlin
val api = Bukkit.getServicesManager().load(SealItemsApi::class.java) ?: return
val response = api.openEnhancement(player.uniqueId)
if (!response.opened) player.sendMessage(response.reason ?: "强化界面不可用")openEnhancement(UUID) 与 /si gui enhance 走同一生命周期、在线、权限、single-session 和配置检查。 公共 API 不提供写等级、强制成功、修改概率、写保底或任意 PDC 变异接口。
同一 SealItemsApi 还提供主线程 openDecomposition(UUID):
kotlin
val response = api.openDecomposition(player.uniqueId)
if (!response.opened) player.sendMessage(response.reason ?: "分解界面不可用")该入口只负责打开服务端权威 GUI,不暴露分解规则写入、强制匹配、随机预演或跳过费用的接口。
强化事件
Paper 层提供:
SealItemsEnhancementPrepareEvent:主线程、随机前、可取消。facts是平台中立的EnhancementPreparedAttempt,包含玩家/物品 UUID、目标等级、方案、最终成功率、失败分支、保护映射、 保底、材料和货币请求。使用event.cancel("玩家可读原因")拒绝;不能修改概率、成本或装备。SealItemsEnhancementResultEvent:事务达到终态后只读发送。result是平台中立的EnhancementResult, 包含前后等级、检查点、保护、实际本地消耗、外部请求与余额观测、经济分类、里程碑和最终贡献摘要。
准备事件监听器抛异常时本次 fail-closed,零扣费、零随机;结果事件监听器抛异常只记日志,不回滚已提交事务。 ECONOMY_UNKNOWN 表示外部扣款事实无法确认,不能解释为“已准确扣款”。
属性联动边界
SealItems 是装备内容与六槽监听的所有者。每个槽位把基础、随机词条、宝石和强化贡献按 (attributeId, operation) 聚合后发布到既有 sealitems:equipment/<slot> 来源;跨槽共鸣使用独立来源并与六槽 同批 mutate。扩展插件不要重复扫描六个原版装备槽,也不要从 Lore 反向解析属性。
SealAttributes 只负责验证定义、接收来源和数值运算。使用 SealItems 六槽发布时,SealAttributes 的 built-in-equipment-reader.enabled 必须为 false。当前契约要求 SealAttributes API baseline 1.3 / 1.0.12, 不再把历史 MAX_CONTRIBUTIONS_PER_SOURCE 当作运行限制。
数据与兼容
强化真值位于 sealitems_gameplay:enhancement_state,使用独立 SEH1 codec。读取结果区分缺失、合法、旧版可迁移、 未知版本和损坏。第三方只应保存完整 Bukkit ItemStack;不要复制、解析或重写该 blob。
未知 sealitems:* 状态默认保护整件物品;已知但损坏的强化 blob 只隔离强化组件。所有提交都绑定实例 UUID、item revision、 状态 digest 和业务 fingerprint,并在最终写入前复验实际槽位。
合成 API 与外部条件
平台无关 API 使用独立 service:
kotlin
val crafting = Bukkit.getServicesManager().load(SealItemsCraftingApi::class.java) ?: return
val opened = crafting.openCrafting(player.uniqueId)
val given = crafting.giveBlueprint(player.uniqueId, "ember_blade", 1)两项调用都必须在 Bukkit 主线程执行;错误线程返回 WRONG_THREAD,不会隐式调度或阻塞。图纸发放先规划完整背包并全成全败提交。需要取得 ItemStack 而不直接交付玩家的 Paper 插件,可通过 SealItemsCraftingBukkitApi.createBlueprint(recipeId, amount) 获得不可变、逐栈 clone 的结果;该接口位于 sealitems-paper,接入方以完整 SealItems JAR 作为 compileOnly。
外部制作条件实现 CraftingConditionProvider 并通过 Bukkit Services 注册。providerId 必须是稳定 namespace:path;同页请求会按 provider 批量调用一次,最终确认再调用一次。provider 必须主线程快速、无阻塞 I/O,并为每个 request ID 返回 PASS/FAIL/UNAVAILABLE;重复 provider ID、异常或漏结果均 fail-closed。
SealItems 只在最终确认且所有前检通过后运行制作 RNG 和构建随机 SealItems 产物。GUI preview 使用静态定义,不生成 instance UUID、品质或随机词条。
套装只读 API 与 mechanic 扩展
套装使用独立 Bukkit service,不向原有 SealItemsApi 塞入玩法细节:
kotlin
val sets = Bukkit.getServicesManager().load(SealItemsSetsApi::class.java) ?: return
val opened = sets.openSets(player.uniqueId)
val state = sets.currentState(player.uniqueId)
val collection = sets.currentCollection(player.uniqueId)openSets、currentState、currentCollection、catalog 和 isMechanicActive 必须在 Bukkit 主线程调用;错误线程不会自动调度或阻塞。definitions、categories 和 acquisitionSources 是不可变 publication 读取。API 只暴露展示/查询,不允许第三方伪造穿戴件数、直接写收藏或注入属性贡献。
需要声明非数值套装效果的插件实现 SealItemsSetMechanicProvider,通过 Bukkit Services 注册一个稳定 provider。snapshot() 必须是无磁盘/数据库/网络 I/O 的廉价不可变快照;mechanic ID 必须是 provider-id:path,且 namespace 与 provider ID 一致。SealItems 只编译描述符和发布玩家当前激活 ID;具体监听伤害、技能或其他业务事件仍由扩展插件负责,并通过 isMechanicActive 查询,不要从 Lore 或 GUI 反向解析状态。
套装数值贡献由 SealItems 的六槽监听计算,并和六槽、共鸣一起提交到 SealAttributes 的独立 sealitems:equipment/derived/set 来源。扩展不得重复发布同一套装属性,也不得在装备变更热路径读取 YAML 或 MySQL。