基于NBT的ItemStack序列化 [1.8.x-1.18.x]
环境
Kotlin
TabooLib
参考原型:
[1.8.x-1.18.x] [v7.18.2] [Maven] Single Class NBT Editor for items, skulls, mobs, and tile entities!
前言
为什么要使用NMS从NBT序列化ItemStack,而不直接调用 ConfigurationSerializable 接口中的序列化方法?
1. ConfigurationSerializable 得到的Map中未知NBT会被处理,可读性不佳
2. 序列化储存时大多数时候需要String,Map需要被二次序列化
3. ConfigurationSerializable 出的结果无法直接被没有 bukkitAPI 的外部程序反序列化(需要额外处理),而NBT本身易于解析与读取
4. NBT与原版密切相关,利于兼容
实现思路
序列化:
Bukkit-ItemStack -> NMS-ItemStack
NMS-ItemStack -> NBT
NBT -> String
反序列化:
String -> NBT
NBT -> NMS-ItemStack
NMS-ItemStack -> Bukkit-ItemStack
实现辅助
TabooLib:
taboolib.module.nms.MinecraftVersion -> 用于获取当前服务器版本
taboolib.module.nms.nmsClass -> 获取NMS(net.minecraft.server)类
taboolib.module.nms.obcClass -> 获取OBC(org.bukkit.craftbukkit)类
其他:
Reflexes -> 反射工具类 用于优化代码可读性
对于不基于 TabooLib 的开发(真的有人Kotlin开发不用TabooLib吗!| Doge ),这里有一份简单的方法含义:
具体实现-Reflexes
Reflexes 类的意义就是通过Kotlin的运算符重载,让反射调用看起来更整洁
- import java.lang.reflect.Constructor
- import java.lang.reflect.Method
- @SuppressWarnings("unchecked","shadowed")
- internal operator fun <T> Method.invoke(obj: Any? = null, vararg arg: Any?) : T?{
- return this.invoke(obj,*arg) as T?
- }
- @SuppressWarnings("unchecked")
- internal operator fun <T> Constructor<*>.invoke(vararg arg: Any?) : T{
- return this.newInstance(*arg) as T
- }
具体实现-无反射
//就不用代码块了,一个颜色费眼睛
fun serialize(itemStack: ItemStack):String{
val nmsItemStack = CraftItemStack.asNMSCopy(itemStack) // Bukkit-ItemStack -> NMS-ItemStack
val nbt = nmsItemStack.save(NBTTagCompound()) // NMS-ItemStack -> NBT
return nbt.toString() // NBT -> String
}
fun deserialize(nbtRaw: String):ItemStack{
val nbt = MojangsonParser.parse(nbtRaw) // String -> NBT
// 此处ItemStack为NMS-Itemstack
// 部分版本为 ItemStack.createStack(nbt)
val nmsItemStack = ItemStack(nbt) // NBT -> NMS-ItemStack
return CraftItemStack.asBukkitCopy(nmsItemStack) // NMS-ItemStack -> Bukkit-ItemStack
}
此时的函数没有跨版本的能力,我们需要再做一下修正
具体实现-反射预备
反射来获取其中用到的方法
- private val CraftItemStack_asNMSCopy: Method = obcClass("inventory.CraftItemStack").getMethod("asNMSCopy", ItemStack::class.java)
- private val CraftItemStack_asBukkitCopy: Method = obcClass("inventory.CraftItemStack").getMethod("asBukkitCopy", nmsClass("ItemStack"))
- private val NBTTagCompound_: Constructor<*> = nmsClass("NBTTagCompound").getConstructor()
- private val MojangsonParser_parse = if (MinecraftVersion.major >= 0){
- nmsClass("MojangsonParser").getMethod("a", String::class.java)
- }else{
- nmsClass("MojangsonParser").getMethod("parse", String::class.java)
- }
- private val ItemStack_createStack: Method? = if (MinecraftVersion.major<=2){
- nmsClass("ItemStack").getMethod("createStack", nmsClass("NBTTagCompound"))
- }else if(MinecraftVersion.major>=5){
- nmsClass("ItemStack").getMethod("a", nmsClass("NBTTagCompound"))
- }else{
- null
- }
- private val ItemStack_: Constructor<*>? = try {
- nmsClass("ItemStack").getConstructor()
- }catch (e : NoSuchMethodException){
- null
- // 不必须的
- }
- private val ItemStack_save: Method = if(MinecraftVersion.major >= 10){
- nmsClass("ItemStack").getMethod("b", nmsClass("NBTTagCompound"))
- }else{
- nmsClass("ItemStack").getMethod("save", nmsClass("NBTTagCompound"))
- }
具体实现-反射调用
- fun serialize(itemStack: ItemStack) : String{
- return ItemStack_save(
- CraftItemStack_asNMSCopy(null,itemStack),
- NBTTagCompound_()
- ).toString()
- }
- fun deserialize(nbtRaw: String):ItemStack{
- val nbt = MojangsonParser_parse(null,nbtRaw)
- return CraftItemStack_asBukkitCopy<ItemStack>(
- null,
- if(MinecraftVersion.major in listOf(3,4)){
- ItemStack_!!(nbt)
- }else{
- ItemStack_createStack!!(null,nbt)
- })!!
- }
这里就能看出来 Reflexes 类的作用了 可以让反射的调用更加整齐
到这里,反射NMS序列化ItemStack就完成了
* 这里再贴出来完整的源码
**个人技术与测试环境有限,没法保证100%的正确和200%的效率
如果此帖有问题或有其他的方法 欢迎提出
其实可以直接 `ItemStack#serialize() -> Map<String,Object>` 和 `ItemStack.deserialize(Map<S,O>) -> ItemStack` 然后走 GSON, etc.
包括 PersistentDataContainer 内的数据也会被序列化出来,大体可读。
包括 PersistentDataContainer 内的数据也会被序列化出来,大体可读。
_zZ白熊Zz_ 发表于 2022-5-21 14:14
其实可以直接 `ItemStack#serialize() -> Map` 和 `ItemStack.deserialize(Map) -> ItemStack` 然后走 GSON ...
不使用ItemStack#serialize() 理由我已经在前言里写了
就最简单的一个情景
NBT序列化可以从单人客户端获取NBT Tag
但是 Map<String,Object> 只有在服务端才能获得
本帖最后由 _zZ白熊Zz_ 于 2022-5-21 17:40 编辑
感觉其实可读性什么的都还挺好..
复制代码
而且他序列化成 Map 其实很快. 至于我这里item.state是 nbt array 那是我自己选的 PDC<?,byte[]> ,如果是用 String 的会更美观一些
美味的曲奇 发表于 2022-5-21 14:18
不使用ItemStack#serialize() 理由我已经在前言里写了
就最简单的一个情景
NBT序列化可以从单人 ...
感觉其实可读性什么的都还挺好..
- {
- "v": 1,
- "type": "BOW",
- "meta": {
- "displayName": "§aaaaa",
- "damage": 0,
- "repairCost": 0,
- "enchants": {},
- "hideFlags": [],
- "persistentDataContainer": {
- "context": {},
- "map": {
- "astralflow:internal.item.state": [0, 0, 0, 0, 0, 0, 0, 118, 123, 34, 116, 121, 112, 101, 34, 58, 34, 105, 111, 46, 105, 98, 54, 55, 46, 97, 115, 116, 114, 97, 108, 102, 108, 111, 119, 46, 105, 110, 116, 101, 114, 110, 97, 108, 46, 105, 116, 101, 109, 46, 115, 116, 97, 116, 101, 46, 73, 110, 116, 101, 114, 110, 97, 108, 73, 116, 101, 109, 83, 116, 97, 116, 101, 34, 44, 34, 100, 97, 116, 97, 34, 58, 123, 34, 112, 114, 111, 116, 111, 116, 121, 112, 101, 75, 101, 121, 34, 58, 123, 34, 105, 100, 34, 58, 34, 99, 34, 44, 34, 110, 97, 109, 101, 115, 112, 97, 99, 101, 34, 58, 34, 97, 34, 125, 125, 125]
- }
- },
- "unbreakable": false,
- "customModelData": 0
- }
- }
而且他序列化成 Map 其实很快. 至于我这里item.state是 nbt array 那是我自己选的 PDC<?,byte[]> ,如果是用 String 的会更美观一些
可以直接用yml序列化,可以保存nbt和nbt顺序复制代码
- /**
- * 序列化itemStack为String
- *
- * @param itemStack 物品
- * [url=home.php?mod=space&uid=491268]@Return[/url] String
- */
- public static String itemStackSerialize(ItemStack itemStack) {
- YamlConfiguration yml = new YamlConfiguration();
- yml.set("item", itemStack);
- return yml.saveToString();
- }
- /**
- * 反序列化String为itemStack
- *
- * @param str 物品str
- * @return ItemStack
- */
- public static ItemStack itemStackDeserialize(String str) {
- YamlConfiguration yml = new YamlConfiguration();
- ItemStack item;
- try {
- yml.loadFromString(str);
- item = yml.getItemStack("item");
- } catch (InvalidConfigurationException ex) {
- item = new ItemStack(Material.AIR, 1);
- }
- return item;
- }
做的不错支持