Skip to content

开发接口

LyGalaxyDecomposition 提供公共接口 LyDecompositionAPI。外部插件可以读取规则、判断分解条件、预览产物、执行分解、创建物品库物品、替换 PlaceholderAPI 变量、执行命令、打开分解界面、读取外部适配器状态,以及重新加载插件配置。

接口完整类名:

java
Ly.galaxy.decomposition.client.plugin.api.LyDecompositionAPI

API 实例可通过插件入口类获取:

java
import Ly.galaxy.decomposition.client.license.Start;
import Ly.galaxy.decomposition.client.plugin.api.LyDecompositionAPI;

// decompositionApi 保存 LyGalaxyDecomposition 当前提供的公共接口。
LyDecompositionAPI decompositionApi = Start.getAPI();

调用分解、界面或命令相关方法前,应先确认 API 实例不为 null,并通过 isReady() 判断插件是否至少成功加载过一次配置。首次加载失败、插件尚未完成初始化或 API 已关闭时,isReady() 返回 false

接入要求

外部插件需要将 LyGalaxyDecomposition 加入编译依赖,并在自身的 plugin.yml 中声明依赖关系。

需要强制等待 LyGalaxyDecomposition 加载时使用 depend

yaml
depend:
  - LyGalaxyDecomposition

仅在 LyGalaxyDecomposition 存在时启用相关功能时使用 softdepend

yaml
softdepend:
  - LyGalaxyDecomposition

公共接口使用 Bukkit 的 PlayerItemStack 和事件系统。调用方不应将 LyGalaxyDecomposition 的实现类打包进自己的插件,只将插件本体作为编译依赖。

线程与状态要求

方法主线程要求API 未就绪时的行为
isReady()无强制检查返回 false
getRules()无强制检查返回当前规则快照;首次加载前通常为空
getGlobalConditions()无强制检查返回当前全局条件快照
getRule(...)无强制检查按当前规则仓库查询
match(...)无强制检查方法本身不检查就绪状态,调用方应先调用 isReady()
matchesConditions(...)无强制检查返回 false
preview(...)无强制检查返回失败结果,原因为“分解API尚未就绪”
execute(...)必须在服务端主线程返回 NOT_READY;非主线程优先返回 EXECUTION_ERROR
reload()必须在服务端主线程非主线程返回失败的重载结果
getHookStatus()无强制检查返回当前适配器状态快照
getExtraButtons()无强制检查返回当前额外按钮快照
createItem(...)接口未强制检查调用方应先确认 API 和对应物品库适配器可用
replacePlaceholders(...)接口未强制检查PlaceholderAPI 不可用时返回原文
executeCommands(...)必须在服务端主线程不执行任何命令
openGui(...)必须在服务端主线程返回 false
shutdown()应在插件停用流程的主线程调用将 API 标记为未就绪并关闭分解界面

match(...)、规则读取方法和部分适配调用没有统一执行就绪检查。外部插件不应依赖其在 API 未就绪时的结果,应在业务入口统一检查 isReady()

API 方法

状态与规则

方法返回值说明
isReady()boolean判断 API 是否至少成功完成过一次配置加载,且尚未关闭
getRules()List<DecompositionRuleData>获取当前全部只读规则,顺序与配置加载顺序一致
getGlobalConditions()List<String>获取所有规则匹配前必须满足的只读全局条件
getRule(String ruleId)DecompositionRuleData按规则 ID 获取只读规则,找不到时返回 null
getHookStatus()Map<String, DecompositionHookStatus>获取物品库、仓库、PlaceholderAPI 等适配器的运行状态
getExtraButtons()Map<Integer, ExtraButtonData>获取当前额外按钮的不可变数据,键为底部按钮槽位 08

getRules() 返回的数据不会暴露插件内部的可变规则对象。规则中的条件、产物定义和命令列表同样为只读列表。

重新加载失败时,当前已经生效的规则、额外按钮、全局条件和产物入库顺序不会被失败配置替换。因此,重载失败后读取这些方法,仍可能得到上一次成功加载的数据。

条件匹配

java
DecompositionMatchResult match(Player player, ItemStack item)

match(...) 先检查全局条件,再按配置顺序查找全部匹配规则。同一物品可以同时匹配多条规则,不会在第一条规则匹配后停止。

java
boolean matchesConditions(
    Player player,
    ItemStack item,
    List<String> conditions
)

matchesConditions(...) 使用插件内置条件解析器判断调用方提供的条件列表,不会自动追加主配置中的全局条件。

条件列表遵循以下逻辑:

  • 多行条件之间为 AND,必须全部成立。
  • 单行顶层使用 || 分隔时为 OR,任意分支成立即可。
  • 函数前可以添加一个或多个 !,奇数次表示取反,偶数次恢复原结果。
  • 单引号和括号内的 || 不会被当作顶层 OR 分隔符。
  • match(...) 会自动应用 global-condition
  • matchesConditions(...) 只判断调用方传入的条件列表。
  • API 未就绪时,matchesConditions(...) 返回 false
  • 玩家为 null 时,permission(...)papi(...) 无法匹配;仅依赖物品的条件仍可参与判断。

支持的条件函数:

函数说明
hasname()判断物品是否存在自定义名称
haslore()判断物品是否存在 Lore
name(...)按指定方式匹配物品名称
lore(...)按指定方式匹配任意一行 Lore
material(...)匹配数字 ID、数字 ID 与 Data,或材质名称
permission(...)判断玩家是否拥有指定权限
papi(...)替换 PlaceholderAPI 变量后判断比较表达式
hasnbt(...)判断物品是否存在指定 NBT 键或点号路径
nbt(...)读取 NBT 值并执行文本或数值比较

依赖限制:

  • papi(...) 依赖 PlaceholderAPI。适配器不可用时条件不会匹配,包含该条件的配置也会在重载校验时报错。
  • hasnbt(...)nbt(...) 依赖当前服务端版本对应的 NBT 适配器。适配器不可用时条件不会匹配,包含该条件的配置也会在重载校验时报错。

条件的完整写法与规则配置页一致,此处仅说明 API 的调用行为。

产物预览

java
DecompositionPreviewResult preview(Player player, ItemStack item)

该方法生成不会触发概率随机、命令执行或物品发放的预览结果。

预览流程:

  1. 检查 API 是否就绪。
  2. 应用全局条件并查找全部匹配规则。
  3. 跳过 give-item: false 的规则产物。
  4. 解析每条产物定义。
  5. 通过对应物品库创建展示物品。
  6. 将展示物品数量设置为产物最大数量,但不会超过该物品的最大堆叠数量。

预览会展示所有能够成功创建的候选产物,不进行概率随机,也不会执行规则命令和产物级命令。

部分产物无法解析或创建时,其余有效产物仍可正常返回。此时 isSuccess() 可以为 true,同时 getReason() 返回首个预览问题。没有任何可展示产物且已经出现预览问题时,结果为失败。

结果方法返回值说明
isSuccess()boolean是否成功完成预览生成
getReason()String失败原因,或部分产物无法预览时的首个问题
getRules()List<DecompositionRuleData>当前物品匹配到的规则
getItems()List<DecompositionPreviewItem>成功创建的产物预览

没有匹配到规则时,预览结果为失败,原因为“没有匹配到分解规则”。

执行分解

java
DecompositionExecutionResult execute(
    Player player,
    ItemStack item,
    String sourceName
)

该方法执行一个输入物品的完整分解流程,包括规则匹配、前置事件、概率随机、产物创建、产物投递、产物级命令、规则命令和后置事件。

参数说明:

参数说明
player执行分解的玩家,也是权限、PlaceholderAPI、物品库和仓库调用的玩家上下文
item用于匹配和触发事件的输入物品;API 不会从玩家背包或调用方容器中移除该物品
sourceName结果消息中 {name} 的来源名称;传入 null 时使用“物品”

调用方必须遵守以下规则:

  • 只能在服务端主线程调用。
  • 只能在 result.isSuccess() 返回 true 后扣除输入物品。
  • API 本身不会修改或移除调用方提供的输入物品。
  • 不要在调用 execute(...) 前提前扣除物品。
  • 不要再次发放 getGeneratedItems() 返回的物品,它们已经完成投递。

提前扣除输入物品会导致取消事件、配置错误或执行异常时无法安全恢复物品。

执行顺序如下:

  1. 检查主线程、API 状态、玩家和输入物品。
  2. 应用全局条件并查找全部匹配规则。
  3. 触发可取消的 DecompositionPreEvent
  4. 按匹配规则顺序解析产物,并分别进行概率与数量随机。
  5. 创建本次实际命中的产物。
  6. delivery-order 依次投递每个产物。
  7. 发送当前产物的结果消息。
  8. 执行当前产物定义中使用 {命令} 配置的产物级命令。
  9. 执行所有匹配规则的 commands 命令。
  10. 创建成功结果并触发 DecompositionPostEvent

同一个物品可以同时匹配多条规则。API 会叠加所有匹配规则的产物、产物级命令和规则命令。

产物按照主配置中的入库顺序依次尝试。当前目标无法完全接收时,剩余数量继续交给下一个目标;全部目标处理后仍有剩余时,物品会掉落在玩家位置,并在结果中写入警告。

输入物品管理

execute(...) 接收的 ItemStack 仅用于匹配、事件数据和执行上下文,不代表插件可以控制该物品所在的背包或容器。

推荐流程:

  1. 从调用方自己的容器中读取物品。
  2. 调用 execute(...)
  3. 检查 isSuccess()
  4. 成功后由调用方扣除对应数量的输入物品。
  5. 读取 getWarnings() 并按需要记录警告。

执行状态

DecompositionExecutionResult#getStatus() 返回 DecompositionExecutionStatus

状态当前含义
SUCCESS分解主流程已完成;仍需检查警告列表
NOT_READYAPI 尚未成功加载,或已经关闭
INVALID_ITEM玩家为空,或输入物品为空、空气、数量不大于零
NO_MATCH输入物品没有匹配到任何分解规则
CANCELLEDDecompositionPreEvent 被外部插件取消
INVENTORY_FULL枚举中保留的背包空间不足状态;当前执行实现不会返回该状态
CONFIG_ERROR产物配置错误,或物品库无法创建配置的产物
EXECUTION_ERROR非主线程调用,或执行过程中出现异常

当前产物投递会继续尝试后续仓库,最终仍有剩余时直接掉落在玩家位置。因此,背包无法完全容纳产物不会直接导致 INVENTORY_FULL

执行结果

结果方法返回值说明
isSuccess()boolean状态是否为 SUCCESS
getStatus()DecompositionExecutionStatus获取完整执行状态
getReason()String获取状态对应的简体中文原因;成功时通常为空字符串
getRequiredSlots()int获取仍需的背包槽位数量;当前执行实现返回 0
getRules()List<DecompositionRuleData>获取本次匹配到的规则
getGeneratedItems()List<ItemStack>获取本次实际生成并进入投递流程的物品副本
getWarnings()List<String>获取不改变主要执行状态的警告

getGeneratedItems() 每次都会返回独立的物品副本。修改返回的 ItemStack 不会修改结果对象内部保存的数据。

产物投递、产物级命令或规则命令出现后续异常时,插件会尽量保留成功主状态,并将问题写入 getWarnings()。调用方不能只检查 isSuccess(),还应读取警告列表。

DecompositionPostEvent 只会在执行流程进入成功结果后触发。NOT_READYINVALID_ITEMNO_MATCHCANCELLEDCONFIG_ERROREXECUTION_ERROR 不会触发后置事件。

重载接口

java
DecompositionReloadResult reload()

reload() 会重新读取并校验以下内容:

  • 主配置 config.yml
  • rule 目录及其子目录中的全部 .yml 文件。
  • 全局条件。
  • 产物入库顺序。
  • 额外按钮。
  • 物品库、仓库、PlaceholderAPI 和 NBT 适配状态。

规则文件会递归读取,并按完整路径忽略大小写排序。每个规则文件的最外层节点为规则 ID,规则 ID 必须在全部文件中全局唯一。

重载会严格检查:

  • 主配置是否包含不支持的根字段。
  • delivery-order 是否为有效且不重复的字符串列表。
  • global-condition 是否为非空字符串列表并符合条件语法。
  • 规则是否只包含 conditiongive-itemcommandsresult
  • 每条规则是否至少包含一行条件。
  • 产物数量、概率、物品库和产物级命令格式。
  • PlaceholderAPI 与 NBT 条件需要的适配器是否可用。
  • 额外按钮槽位、动作、条件、冷却和命令格式。

存在阻止加载的错误时,不会替换当前规则、按钮、全局条件和入库顺序。首次加载失败时,API 不会进入就绪状态;已经成功加载过的实例会继续保留上一次有效数据。

reload() 必须在服务端主线程调用。

结果方法返回值说明
isSuccess()boolean是否成功替换当前运行数据
getRuleCount()int本次读取并暂存的规则数量
getButtonCount()int本次读取并暂存的额外按钮数量
getWarnings()List<String>不阻止重载的问题
getErrors()List<String>阻止重载的问题

重载失败时,getRuleCount()getButtonCount() 仍可能大于 0,它们表示本次已经读取到的数据数量,不表示这些数据已经生效。是否成功应用必须以 isSuccess() 为准。

外部适配调用

创建物品

java
ItemStack createItem(Player player, String itemId)

通过插件统一物品库适配器创建物品。无法识别物品库、适配器不可用或物品不存在时返回 null

itemId 可以直接填写物品 ID,也可以使用 物品库@物品ID 明确指定来源:

text
MythicMobs@分解产物1
AzureFlow@材料
NeigeItems@装备

未填写物品库前缀时,使用主配置 item-provider 指定的默认物品库。该配置为空时回退为 MythicMobs

项目中确认支持以下物品库名称:

物品库说明
MythicMobs支持 MythicMobs 4 与 MythicMobs 5 适配
LyItemSave与 MythicMobs 名称互通,任意一方可用时两个名称均可使用
AzureFlow外部物品库适配
NeonFlash外部物品库适配
SX-Item外部物品库适配
NeigeItems外部物品库适配
OriginAttribute外部物品库适配

具体适配器是否可以安全调用,应通过 getHookStatus() 查询,不要只根据服务器插件列表判断。

查询适配器状态

java
Map<String, DecompositionHookStatus> getHookStatus()

返回当前外部适配器状态。状态可能包含物品库、仓库和 PlaceholderAPI 等外部功能。

java
// hookStatuses 保存当前全部外部适配器状态。
Map<String, DecompositionHookStatus> hookStatuses = decompositionApi.getHookStatus();

// mythicMobsStatus 保存 MythicMobs 适配器状态,键不存在时为 null。
DecompositionHookStatus mythicMobsStatus = hookStatuses.get("MythicMobs");
if (mythicMobsStatus != null && mythicMobsStatus.isAvailable()) {
    // createdItem 保存物品库创建的物品。
    ItemStack createdItem = decompositionApi.createItem(player, "MythicMobs@材料");
}

返回映射及其中的状态对象用于读取,不应尝试修改插件内部适配器状态。

替换变量

java
String replacePlaceholders(Player player, String source)

使用当前可用的 PlaceholderAPI 适配器替换玩家变量。

情况返回结果
PlaceholderAPI 可用返回替换后的文本
PlaceholderAPI 不可用返回原文
sourcenull返回空字符串

该方法只负责变量替换,不判断替换后的文本是否符合条件表达式或命令格式。

执行命令

java
void executeCommands(Player player, List<String> commands)

按列表顺序执行命令。命令执行前会替换 PlaceholderAPI 变量。

格式执行身份
[console]命令控制台
[op]命令临时给予玩家 OP 后执行,结束后恢复原状态
命令玩家

身份前缀忽略英文大小写。空命令会被跳过,单条命令异常不会阻止后续命令继续执行。

出现以下情况时,该方法不会执行任何命令:

  • API 未就绪。
  • 玩家为 null
  • 命令列表为 null
  • 当前线程不是服务端主线程。

[op] 会临时修改玩家 OP 状态。即使命令执行出现异常,插件也会尝试恢复玩家原本的 OP 状态。

打开界面

java
boolean openGui(Player player)

满足以下全部条件时,为玩家打开分解界面并返回 true

  • API 已就绪。
  • 玩家不为 null
  • 当前位于服务端主线程。

任一条件不满足时返回 false

关闭 API

java
void shutdown()

该方法会执行以下操作:

  • 将 API 标记为未就绪。
  • 关闭所有仍在使用分解界面的在线玩家界面。
  • 重置核心初始化状态。

该方法用于插件停用或核心关闭流程,不应作为普通配置重载方法使用。关闭后需要由插件自身重新完成初始化和配置加载,外部插件不能通过普通业务调用恢复 API。

数据对象

公共数据对象位于以下包中:

java
Ly.galaxy.decomposition.client.plugin.api.data

DecompositionRuleData

只读规则数据。

方法返回值说明
getId()String规则唯一 ID
isGiveItem()boolean是否发放 result 中的产物
getConditions()List<String>规则条件列表
getResults()List<String>原始产物定义列表
getCommands()List<String>规则执行命令列表

DecompositionMatchResult

方法返回值说明
isMatched()boolean是否至少匹配到一条规则
getRules()List<DecompositionRuleData>按配置顺序排列的匹配规则

DecompositionPreviewResult

方法返回值说明
isSuccess()boolean是否成功完成产物预览
getReason()String失败原因或首个部分预览问题
getRules()List<DecompositionRuleData>当前输入物品匹配到的规则
getItems()List<DecompositionPreviewItem>成功创建的预览物品

DecompositionPreviewItem

方法返回值说明
getProduct()DecompositionProductData获取解析后的产物定义
getItem()ItemStack获取独立的预览物品副本

DecompositionProductData

方法返回值说明
getRuleId()String所属规则 ID
getItemId()String物品库物品 ID,可能包含物品库前缀
getMinimumAmount()int最小产物数量
getMaximumAmount()int最大产物数量
getChance()double出现概率,范围为 01
getCommands()List<String>当前产物命中并投递后执行的命令

产物定义的命令不会在 preview(...) 中执行,只会在 execute(...) 实际随机命中该产物后执行。

DecompositionExecutionResult

方法返回值说明
isSuccess()boolean状态是否为 SUCCESS
getStatus()DecompositionExecutionStatus获取执行状态
getReason()String获取状态原因
getRequiredSlots()int获取仍需的背包槽位数量
getRules()List<DecompositionRuleData>获取匹配规则
getGeneratedItems()List<ItemStack>获取实际生成物品的独立副本
getWarnings()List<String>获取非致命警告

DecompositionReloadResult

方法返回值说明
isSuccess()boolean是否成功替换运行数据
getRuleCount()int本次读取的规则数量
getButtonCount()int本次读取的额外按钮数量
getWarnings()List<String>获取重载警告
getErrors()List<String>获取阻止重载的问题

DecompositionHookStatus

方法返回值说明
getName()String外部插件或适配器名称
isAvailable()boolean当前是否可以安全调用
getMessage()String当前状态说明

Bukkit 事件

事件位于以下包中:

java
Ly.galaxy.decomposition.client.plugin.api.event

DecompositionPreEvent

完整类名:

java
Ly.galaxy.decomposition.client.plugin.api.event.DecompositionPreEvent

该事件在概率随机、产物创建、物品投递和命令执行之前触发,可以取消。

方法返回值说明
getPlayer()Player获取执行分解的玩家
getInputItem()ItemStack获取本次输入物品的独立副本
getRules()List<DecompositionRuleData>获取本次匹配到的只读规则
isCancelled()boolean判断事件是否已取消
setCancelled(boolean cancelled)void设置取消状态

事件被取消后,execute(...) 返回 CANCELLED,不会随机产物、创建或发放物品、执行命令,也不会触发 DecompositionPostEvent。输入物品仍由调用方管理。

java
import Ly.galaxy.decomposition.client.plugin.api.event.DecompositionPreEvent;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;

public final class DecompositionListener implements Listener {

    /** 在分解执行前检查并按业务需要取消操作。 */
    @EventHandler
    public void onDecompositionPre(DecompositionPreEvent event) {
        if (event.getRules().isEmpty()) {
            event.setCancelled(true);
        }
    }
}

DecompositionPostEvent

完整类名:

java
Ly.galaxy.decomposition.client.plugin.api.event.DecompositionPostEvent

该事件在产物投递、产物级命令和规则命令处理结束后触发,为只读事件。

方法返回值说明
getPlayer()Player获取执行分解的玩家
getInputItem()ItemStack获取本次输入物品的独立副本
getResult()DecompositionExecutionResult获取本次完整执行结果
java
import Ly.galaxy.decomposition.client.plugin.api.data.DecompositionExecutionResult;
import Ly.galaxy.decomposition.client.plugin.api.event.DecompositionPostEvent;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;

public final class DecompositionResultListener implements Listener {

    /** 在分解完成后读取实际产物和非致命警告。 */
    @EventHandler
    public void onDecompositionPost(DecompositionPostEvent event) {
        // executionResult 保存本次分解的完整执行结果。
        DecompositionExecutionResult executionResult = event.getResult();
        // generatedItemCount 保存本次实际生成的产物种类数量。
        int generatedItemCount = executionResult.getGeneratedItems().size();
        // warningCount 保存本次执行产生的非致命警告数量。
        int warningCount = executionResult.getWarnings().size();
    }
}

事件中的输入物品均为副本。修改事件返回的物品不会直接修改调用方原物品。

完整调用流程

java
import Ly.galaxy.decomposition.client.license.Start;
import Ly.galaxy.decomposition.client.plugin.api.LyDecompositionAPI;
import Ly.galaxy.decomposition.client.plugin.api.data.DecompositionExecutionResult;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;

/** 尝试执行一次分解,并仅在成功后处理输入物品。 */
public boolean decomposeItem(Player player, ItemStack inputItem) {
    // decompositionApi 保存插件当前提供的公共分解接口。
    LyDecompositionAPI decompositionApi = Start.getAPI();
    if (decompositionApi == null || !decompositionApi.isReady()) {
        return false;
    }

    // sourceName 保存结果消息中显示的输入物品名称。
    String sourceName = inputItem.hasItemMeta()
            && inputItem.getItemMeta().hasDisplayName()
            ? inputItem.getItemMeta().getDisplayName()
            : inputItem.getType().name();

    // executionResult 保存产物投递、命令和事件处理后的完整结果。
    DecompositionExecutionResult executionResult = decompositionApi.execute(
            player,
            inputItem,
            sourceName
    );

    if (!executionResult.isSuccess()) {
        return false;
    }

    // 此处由调用方根据自身容器逻辑扣除对应输入物品。
    return true;
}

该方法必须由服务端主线程调用。如果调用入口可能位于异步任务中,应先切换回 Bukkit 主线程,再调用 execute(...)

注意事项

  • execute(...) 已包含产物创建、产物投递、产物级命令和规则命令,不要重复处理这些动作。
  • getGeneratedItems() 用于读取执行结果,不代表尚未发放的待处理物品。
  • preview(...) 不进行概率随机,不能用预览列表代替实际执行结果。
  • 预览物品数量使用产物最大值,不代表实际分解一定得到该数量。
  • 同一物品可能匹配多条规则,调用方不能假定只返回第一条规则。
  • DecompositionPreEvent 中的输入物品和规则数据用于读取,不会直接修改调用方原物品或插件内部规则。
  • DecompositionPostEvent 只在成功主流程中触发,失败和取消结果需要直接读取 execute(...) 返回值。
  • 配置重载失败时,应读取 DecompositionReloadResult#getErrors(),不要仅依赖控制台文本。
  • 重载失败不会替换上一份有效规则,但本次读取数量仍可能大于 0
  • 调用物品库、仓库或 PlaceholderAPI 前,可以通过 getHookStatus() 判断对应适配器是否可用。
  • API 不负责扣除输入物品。提前扣除会破坏取消事件和失败状态下的数据安全。
  • SUCCESS 不代表完全没有后续问题,调用方还应检查 getWarnings()
  • 当前背包无法容纳的剩余产物会继续尝试后续仓库,最终无法接收的部分会掉落在玩家位置。
  • shutdown() 用于插件生命周期结束,不应代替 reload()