Skip to content

开发接口

LyCore 对外提供属性桥接 API,并提供跨版本物品 NBT 工具。属性桥接用于让其它插件按来源管理玩家属性文本,再统一同步到当前启用的属性插件。

添加依赖

需要强制依赖 LyCore 时,在插件的 plugin.yml 中声明:

yaml
depend:
  - LyCore

如果仅在检测到 LyCore 时启用相关功能,可以声明软依赖:

yaml
softdepend:
  - LyCore

项目证据中没有提供公开 Maven 仓库坐标。开发时需要将 LyCore 的 JAR 加入编译依赖,但不要将其打包进自己的插件。

属性桥接 API

获取属性管理器

入口类为 Ly.core.api.LyCoreAPI

java
import Ly.core.api.LyCoreAPI;
import Ly.core.attribuet.AttributeManager;

// 属性管理器,用于访问玩家属性数据
AttributeManager attributeManager = LyCoreAPI.getAttributeManager();

LyCoreAPI 当前公开的方法如下:

方法返回值说明
getAttributeManager()AttributeManager获取 LyCore 当前使用的属性管理器

获取玩家属性数据

java
import Ly.core.api.LyCoreAPI;
import Ly.core.attribuet.AttributeData;
import org.bukkit.entity.Player;

public AttributeData getPlayerAttributeData(Player player) {
    // 玩家属性数据,用于管理不同来源提供的属性文本
    AttributeData attributeData = LyCoreAPI.getAttributeManager().getAttributeData(player);
    return attributeData;
}

玩家加入服务器时,LyCore 会初始化对应的 AttributeData。插件启用时已经在线的玩家也会被初始化。

调用 getAttributeData(Player) 后仍建议检查返回值。玩家数据未初始化、已被移除或传入状态异常时,结果可能为 null

添加或覆盖属性来源

java
import Ly.core.api.LyCoreAPI;
import Ly.core.attribuet.AttributeData;
import org.bukkit.entity.Player;

import java.util.Arrays;

public void addDemoAttribute(Player player) {
    // 玩家属性数据,用于写入本插件提供的属性来源
    AttributeData attributeData = LyCoreAPI.getAttributeManager().getAttributeData(player);
    if (attributeData == null) {
        return;
    }

    attributeData.addSource(
        "demo",
        Arrays.asList("攻击力: 10", "生命值: 20")
    );
}

addSource(String, List<String>) 以来源名称为键保存属性文本:

  • 来源不存在时,创建新的属性来源。
  • 来源已经存在时,覆盖该来源的旧属性列表。
  • 属性列表为 null 时,不会修改数据,也不会触发刷新。
  • 来源名称没有自动命名空间,接入插件应使用稳定且不易冲突的名称。

建议使用插件名或插件名加业务名称作为来源,例如 MyPluginMyPlugin_equipment

判断与读取属性来源

java
import Ly.core.api.LyCoreAPI;
import Ly.core.attribuet.AttributeData;
import org.bukkit.entity.Player;

import java.util.List;

public List<String> getDemoAttribute(Player player) {
    // 玩家属性数据,用于查询指定来源是否存在
    AttributeData attributeData = LyCoreAPI.getAttributeManager().getAttributeData(player);
    if (attributeData == null || !attributeData.hasSource("demo")) {
        return null;
    }

    // 属性文本列表,用于读取 demo 来源当前保存的内容
    List<String> attributeLines = attributeData.getSource("demo");
    return attributeLines;
}

getSource(String) 直接返回该来源保存的列表。来源不存在时返回 null

移除属性来源

java
import Ly.core.api.LyCoreAPI;
import Ly.core.attribuet.AttributeData;
import org.bukkit.entity.Player;

public void removeDemoAttribute(Player player) {
    // 玩家属性数据,用于移除本插件写入的属性来源
    AttributeData attributeData = LyCoreAPI.getAttributeManager().getAttributeData(player);
    if (attributeData != null) {
        attributeData.removeSource("demo");
    }
}

只有来源存在时,removeSource(String) 才会删除数据并标记刷新。删除某个来源不会影响其它插件写入的来源。

AttributeManager 方法

方法返回值说明
getAttributePlugin()AttributePlugin获取当前选中的属性插件类型;没有成功选中时可能为 null
getAttributeData(Player player)AttributeData获取玩家的属性数据;未初始化时返回 null
initAttributeData(Player player)void为玩家创建并保存新的属性数据
removeAttributeData(Player player)void取消该玩家的刷新任务并移除属性数据

重复调用 initAttributeData(Player) 会直接替换管理器中保存的数据。业务插件通常只需要读取已有数据,不应反复自行初始化。

AttributeData 方法

方法返回值说明
getPlayer()Player获取此数据关联的玩家对象
getUuid()UUID获取玩家 UUID
getRefreshTask()BukkitTask获取内部属性刷新任务
isWaitingForRefresh()boolean判断属性数据是否正在等待刷新
setWaitingForRefresh(boolean)void设置是否等待刷新
getAttributeSourceMap()Map<String, List<String>>获取全部属性来源映射
hasSource(String name)boolean判断指定来源是否存在
getSource(String name)List<String>获取指定来源的属性文本;不存在时返回 null
addSource(String name, List<String> content)void添加或覆盖属性来源,并标记刷新
removeSource(String name)void移除已有属性来源,并标记刷新

getAttributeSourceMap() 返回内部正在使用的并发映射。直接修改该映射不会自动设置 waitingForRefresh,因此应优先使用 addSourceremoveSource

属性刷新机制

每个 AttributeData 都会创建一个每 Tick 执行一次的异步任务。调用 addSource 或成功执行 removeSource 后,数据会被标记为等待刷新;任务随后会合并全部来源的属性文本,并同步到当前属性插件。

同步时会把所有来源中的列表展开为一个属性文本列表。来源保存在 ConcurrentHashMap 中,因此不要依赖不同来源之间的遍历顺序。如果属性插件要求固定顺序,应尽量把相关属性放在同一个来源列表内。

LyCore 已包含以下属性桥接类型:

枚举值对应插件或版本
AttributePlugin.AttributePlusAttributePlus 2.x 或 3.x
AttributePlugin.SXAttribute2SX-Attribute 2.x
AttributePlugin.SXAttribute3SX-Attribute 3.x
AttributePlugin.ItemLoreOriginItemLoreOrigin
AttributePlugin.AttributeSystemAttributeSystem

具体选择由 LyCore 的 attribute-plugin 配置和服务器已启用的属性插件共同决定。修改属性插件选择后需要重启服务器。

线程安全

LyCore 的属性刷新任务为异步任务,并会调用对应属性插件的 API。接入插件只需要通过 addSourceremoveSource 修改来源,不要额外重复调用属性插件 API。

Bukkit 物品、玩家背包、世界和大多数服务器对象通常应在主线程处理。不要因为属性来源映射使用了并发容器,就默认所有 Bukkit 操作都可以异步执行。

NBT 工具

LyCore 包含 Ly.core.utils.nms.NbtUtil,用于读取和修改 ItemStack 的 NBT 数据。它通过版本实现适配多个 CraftBukkit/NMS 版本。

项目中能够确认的版本实现包括:

Minecraft 版本NMS 版本
1.7.10v1_7_R4
1.8.8v1_8_R3
1.11.2v1_11_R1
1.12.2v1_12_R1
1.13.2v1_13_R2
1.14.4v1_14_R1
1.15.2v1_15_R1
1.16.5v1_16_R3
1.17.1v1_17_R1
1.18.2v1_18_R2
1.19.2v1_19_R1
1.19.4v1_19_R3
1.20.1v1_20_R1
1.20.2v1_20_R2
1.20.3v1_20_R3

没有列出的服务端版本不能根据现有项目文件确认兼容性。

获取 NBT 工具实例

java
import Ly.core.utils.nms.NbtUtil;

// NBT 工具实例,用于跨版本读写物品 NBT
NbtUtil nbtUtil = NbtUtil.getInstance();

使用前应检查实例是否为 null,避免当前服务端版本没有成功加载对应实现。

常用便捷方法

方法返回值说明
getKeys(ItemStack item)Set获取物品根 NBT 下的键集合;没有 NBT 时返回空集合
hasIntKey(ItemStack item, String key)boolean判断路径是否存在 INT 类型值
hasStringKey(ItemStack item, String key)boolean判断路径是否存在 STRING 类型值
getIntKey(ItemStack item, String key)int读取整数;不存在或类型不符时返回 0
getStringKey(ItemStack item, String key)String读取字符串;不存在或类型不符时返回 null
setIntKey(ItemStack item, String key, int value)ItemStack写入整数并返回修改后的物品
setStringKey(ItemStack item, String key, String value)ItemStack写入字符串并返回修改后的物品
getKey(ItemStack item, NbtType type, String key)Object按指定类型读取值
setKey(ItemStack item, NbtType type, String key, Object value)ItemStack按指定类型写入或删除值,并返回处理后的物品
describe(ItemStack item)List<String>递归生成物品 NBT 的文本描述

写入和读取字符串

java
import Ly.core.utils.nms.NbtUtil;
import org.bukkit.inventory.ItemStack;

public ItemStack setItemOwner(ItemStack item, String ownerName) {
    // NBT 工具实例,用于写入物品所有者标记
    NbtUtil nbtUtil = NbtUtil.getInstance();
    if (nbtUtil == null || item == null) {
        return item;
    }

    // 修改后的物品,必须接收 NBT 工具返回的新 ItemStack
    ItemStack updatedItem = nbtUtil.setStringKey(item, "LyCore.owner", ownerName);
    return updatedItem;
}

public String getItemOwner(ItemStack item) {
    // NBT 工具实例,用于读取物品所有者标记
    NbtUtil nbtUtil = NbtUtil.getInstance();
    if (nbtUtil == null || item == null) {
        return null;
    }

    return nbtUtil.getStringKey(item, "LyCore.owner");
}

setKeysetIntKeysetStringKey 会经过 NMS 与 Bukkit 物品转换,并返回处理后的 ItemStack。调用方必须保存返回值,不能假设传入对象一定会原地修改。

嵌套路径

NBT 键支持使用英文句点分隔嵌套路径:

java
// 修改后的物品,用于保存嵌套的整数 NBT
ItemStack updatedItem = nbtUtil.setIntKey(item, "LyCore.data.level", 10);

写入时,如果中间的 Compound 不存在,工具会自动创建。读取或删除时,如果路径中的任意节点不存在,则不会创建节点。

路径要求如下:

  • 路径不能为 null
  • 路径去除首尾空白后不能是空字符串。
  • 路径中的每一段都不能为空。
  • LyCore..level 之类的路径会抛出 IllegalArgumentException

删除 NBT

通用 setKeyvaluenull 时会删除对应路径:

java
import Ly.core.utils.nms.NbtUtil;
import Ly.core.utils.nms.NbtUtil.NbtType;
import org.bukkit.inventory.ItemStack;

public ItemStack removeItemOwner(ItemStack item) {
    // NBT 工具实例,用于删除指定物品标记
    NbtUtil nbtUtil = NbtUtil.getInstance();
    if (nbtUtil == null || item == null) {
        return item;
    }

    // 修改后的物品,用于替换原物品对象
    ItemStack updatedItem = nbtUtil.setKey(item, NbtType.STRING, "LyCore.owner", null);
    return updatedItem;
}

删除不存在的路径时,方法会返回原物品,不会创建新的 Compound。

NBT 类型

NbtUtil.NbtType 声明了以下类型:

类型类型 ID写入值要求兼容说明
BYTE1Number已提供读写实现
SHORT2Number已提供读写实现
INT3Number已提供读写实现
LONG4Number已提供读写实现
FLOAT5Number已提供读写实现
DOUBLE6Number已提供读写实现
BYTE_ARRAY7byte[]已提供读写实现
STRING8String已提供读写实现
LIST9未提供通用写入实现枚举中存在,但版本实现没有对应读写分支
COMPOUND10当前版本的 NMS NBTTagCompound类型与具体服务端版本绑定
INT_ARRAY11int[]已提供读写实现
LONG_ARRAY12long[]1.13.2 及以上列出的实现支持;1.7.10、1.8.8、1.11.2、1.12.2 不支持
BOOLEAN1Boolean底层使用 Byte 类型 ID

数值类型传入非 Number、字符串类型传入非 String、数组类型传入错误数组类型或布尔类型传入非 Boolean 时,会抛出 IllegalArgumentException。版本不支持的类型会抛出 UnsupportedOperationException

COMPOUND 需要传入当前 Minecraft 版本对应的 NMS 类型,无法直接编写一份完全跨版本的强类型调用代码。普通接入插件应优先使用字符串、整数等基础类型和点分路径。

输出 NBT 描述

java
import Ly.core.utils.nms.NbtUtil;
import org.bukkit.inventory.ItemStack;

import java.util.Collections;
import java.util.List;

public List<String> describeItemNbt(ItemStack item) {
    // NBT 工具实例,用于生成可显示的 NBT 文本
    NbtUtil nbtUtil = NbtUtil.getInstance();
    if (nbtUtil == null) {
        return Collections.emptyList();
    }

    // NBT 描述行,用于日志或界面显示
    List<String> descriptionLines = nbtUtil.describe(item);
    return descriptionLines;
}

describe(ItemStack) 会递归读取 Compound 和 List,最大递归深度为 64,并避免重复访问同一个对象。返回文本包含 §7 颜色代码。

为了避免重复显示 Bukkit 物品的常规名称与 Lore,遍历 display Compound 时会过滤:

  • String 类型的 Name
  • List 类型的 Lore

物品为 null、没有 NBT 或无法通过反射读取 NBT 时,返回空列表。

警告

NBT 工具依赖 CraftBukkit/NMS 实现,必须在服务端运行。不要在异步线程修改玩家背包中的物品;应切回 Bukkit 主线程,并把方法返回的 ItemStack 放回原槽位。

接入注意事项

  • LyCore 的包名中属性目录实际拼写为 Ly.core.attribuet,导入时必须保持该拼写。
  • 属性文本的具体格式由当前属性插件解析,LyCore 只负责合并来源并转交。
  • 不要直接依赖属性来源映射的遍历顺序。
  • 不要直接修改 getAttributeSourceMap() 后期待自动刷新。
  • 不要取消 AttributeData.getRefreshTask(),除非同时自行接管该玩家的数据生命周期。
  • 操作 NBT 时必须接收 setKey 或便捷写入方法返回的 ItemStack
  • 使用 NBT 点分路径时,应选择独立的根键,避免覆盖其它插件的数据。