开发接口
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时,不会修改数据,也不会触发刷新。 - 来源名称没有自动命名空间,接入插件应使用稳定且不易冲突的名称。
建议使用插件名或插件名加业务名称作为来源,例如 MyPlugin、MyPlugin_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,因此应优先使用 addSource 和 removeSource。
属性刷新机制
每个 AttributeData 都会创建一个每 Tick 执行一次的异步任务。调用 addSource 或成功执行 removeSource 后,数据会被标记为等待刷新;任务随后会合并全部来源的属性文本,并同步到当前属性插件。
同步时会把所有来源中的列表展开为一个属性文本列表。来源保存在 ConcurrentHashMap 中,因此不要依赖不同来源之间的遍历顺序。如果属性插件要求固定顺序,应尽量把相关属性放在同一个来源列表内。
LyCore 已包含以下属性桥接类型:
| 枚举值 | 对应插件或版本 |
|---|---|
AttributePlugin.AttributePlus | AttributePlus 2.x 或 3.x |
AttributePlugin.SXAttribute2 | SX-Attribute 2.x |
AttributePlugin.SXAttribute3 | SX-Attribute 3.x |
AttributePlugin.ItemLoreOrigin | ItemLoreOrigin |
AttributePlugin.AttributeSystem | AttributeSystem |
具体选择由 LyCore 的 attribute-plugin 配置和服务器已启用的属性插件共同决定。修改属性插件选择后需要重启服务器。
线程安全
LyCore 的属性刷新任务为异步任务,并会调用对应属性插件的 API。接入插件只需要通过 addSource 和 removeSource 修改来源,不要额外重复调用属性插件 API。
Bukkit 物品、玩家背包、世界和大多数服务器对象通常应在主线程处理。不要因为属性来源映射使用了并发容器,就默认所有 Bukkit 操作都可以异步执行。
NBT 工具
LyCore 包含 Ly.core.utils.nms.NbtUtil,用于读取和修改 ItemStack 的 NBT 数据。它通过版本实现适配多个 CraftBukkit/NMS 版本。
项目中能够确认的版本实现包括:
| Minecraft 版本 | NMS 版本 |
|---|---|
| 1.7.10 | v1_7_R4 |
| 1.8.8 | v1_8_R3 |
| 1.11.2 | v1_11_R1 |
| 1.12.2 | v1_12_R1 |
| 1.13.2 | v1_13_R2 |
| 1.14.4 | v1_14_R1 |
| 1.15.2 | v1_15_R1 |
| 1.16.5 | v1_16_R3 |
| 1.17.1 | v1_17_R1 |
| 1.18.2 | v1_18_R2 |
| 1.19.2 | v1_19_R1 |
| 1.19.4 | v1_19_R3 |
| 1.20.1 | v1_20_R1 |
| 1.20.2 | v1_20_R2 |
| 1.20.3 | v1_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");
}setKey、setIntKey 和 setStringKey 会经过 NMS 与 Bukkit 物品转换,并返回处理后的 ItemStack。调用方必须保存返回值,不能假设传入对象一定会原地修改。
嵌套路径
NBT 键支持使用英文句点分隔嵌套路径:
java
// 修改后的物品,用于保存嵌套的整数 NBT
ItemStack updatedItem = nbtUtil.setIntKey(item, "LyCore.data.level", 10);写入时,如果中间的 Compound 不存在,工具会自动创建。读取或删除时,如果路径中的任意节点不存在,则不会创建节点。
路径要求如下:
- 路径不能为
null。 - 路径去除首尾空白后不能是空字符串。
- 路径中的每一段都不能为空。
LyCore..level之类的路径会抛出IllegalArgumentException。
删除 NBT
通用 setKey 在 value 为 null 时会删除对应路径:
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 | 写入值要求 | 兼容说明 |
|---|---|---|---|
BYTE | 1 | Number | 已提供读写实现 |
SHORT | 2 | Number | 已提供读写实现 |
INT | 3 | Number | 已提供读写实现 |
LONG | 4 | Number | 已提供读写实现 |
FLOAT | 5 | Number | 已提供读写实现 |
DOUBLE | 6 | Number | 已提供读写实现 |
BYTE_ARRAY | 7 | byte[] | 已提供读写实现 |
STRING | 8 | String | 已提供读写实现 |
LIST | 9 | 未提供通用写入实现 | 枚举中存在,但版本实现没有对应读写分支 |
COMPOUND | 10 | 当前版本的 NMS NBTTagCompound | 类型与具体服务端版本绑定 |
INT_ARRAY | 11 | int[] | 已提供读写实现 |
LONG_ARRAY | 12 | long[] | 1.13.2 及以上列出的实现支持;1.7.10、1.8.8、1.11.2、1.12.2 不支持 |
BOOLEAN | 1 | Boolean | 底层使用 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 点分路径时,应选择独立的根键,避免覆盖其它插件的数据。