复活Bukkit的PDC(3)

前言

在11400加入的PDC,它的数据序列化涉及了MojangsonParser这种“难以对付的家伙”,所以值得我们新开一章单独讲解

CraftNBTTagConfigSerializer

此类为CraftBukkit提供的PDC NBT对象序列化/反序列化类,它主要含有两个方法

java 复制代码
public static Object serialize(NBTBase base)

负责将NBT数据(NBTBase在11200为所有NBT类的基类,在11605它变为了一个接口,但我们此时只讨论11400和11202)序列化为Object

java 复制代码
public static NBTBase deserialize(Object object)

负责将Object反序列化为NBT对象

Serialize方法

java 复制代码
public static Object serialize(NBTBase base) {
    if (base instanceof NBTTagCompound) {
        Map<String, Object> innerMap = new HashMap<>();
        for (String key : ((NBTTagCompound) base).c()) {
            innerMap.put(key, serialize(((NBTTagCompound) base).get(key)));
        }

        return innerMap;
    } else if (base instanceof NBTTagList) {
        List<Object> baseList = new ArrayList<>();
        for (int i = 0; i < ((NBTTagList) base).size(); i++) {
            baseList.add(serialize((NBTBase) ((NBTTagList) base).get(i)));
        }

        return baseList;
    } else if (base instanceof NBTTagString) {
        return ((NBTTagString) base).c_();
    } else if (base instanceof NBTTagInt) { // No need to check for doubles, those are covered by the double itself
        return base.toString() + "i";
    }

    return base.toString();
}

此方法为适配11200以后的形式,如果直接使用11400的方法会出现无法找到NBT取对应数据方法的错误,由第一张的分析改过来即可

逻辑较为简单

  • 当base为NBTTagCompound时,则将其键重新映射到递归序列化后的值后返回重新映射的Map

  • 当base为NBTTagList时,则将其值全部递归序列化后返回

  • 当base为NBTTagString时,直接返回其值

  • 当base为NBTTagInt时,返回 值 + i (注:这个i非常重要)

  • 否则调用base的toString() 方法,NBT的各种Array类型均重写了此方法,所以在序列化NBTTagLongArray等Array类型时均会调用此方法(注:NBTTagCompound和NBTTagList也重写了此方法,但Bukkit并没有采用它们)将其序列化为String

Deserialize方法

从Brigadier迁移

Mojang在11300引入了全新的命令解析系统Brigadier Mojang/brigadier

Brigadier虽名为“命令解析系统”,但在NMS内被大量用于解析各种数据字符串以提供反序列化,而11400 NMS的PDC反序列化便用到了Brigadier以及适配了Brigadier的MojangsonParser,因为Brigadier在11202还并未被引入,所以笔者改造了deserialize方法,脱去了Brigadier的依赖,使用Mojangson自己的内置正则表达式解析字符串

此方法也许并不一定正确,请读者斟酌使用

java 复制代码
private static final MojangsonParser MOJANGSON_PARSER = new MojangsonParser("");

public static NBTBase deserialize(Object object) {
    if (object instanceof Map) {
        NBTTagCompound compound = new NBTTagCompound();
        for (Map.Entry<String, Object> entry : ((Map<String, Object>) object).entrySet()) {
            compound.set(entry.getKey(), deserialize(entry.getValue()));
        }

        return compound;
    } else if (object instanceof List) {
        List<Object> list = (List<Object>) object;
        if (list.isEmpty()) {
            return new NBTTagList(); // Default
        }

        NBTTagList tagList = new NBTTagList();
        for (Object tag : list) {
            tagList.add(deserialize(tag));
        }

        return tagList;
    } else if (object instanceof String) {
        String string = (String) object;

        if (ARRAY.matcher(string).matches()) {
            try {
                return new MojangsonParser(string).k();
            } catch (MojangsonParseException e) {
                throw new RuntimeException(e);
            }
        } else if (INTEGER.matcher(string).matches()) { //Read integers on our own
            return new NBTTagInt(Integer.parseInt(string.substring(0, string.length() - 1)));
        } else if (DOUBLE.matcher(string).matches()) {
            return new NBTTagDouble(Double.parseDouble(string.substring(0, string.length() - 1)));
        } else {
            NBTBase nbtBase = MOJANGSON_PARSER.c(string);

            if (nbtBase instanceof NBTTagInt) { // If this returns an integer, it did not use our method from above
                return new NBTTagString(String.valueOf(((NBTTagInt) nbtBase).e())); // It then is a string that was falsely read as an int
            } else if (nbtBase instanceof NBTTagDouble) {
                return new NBTTagString(String.valueOf(((NBTTagDouble) nbtBase).asDouble())); // Doubles add "d" at the end
            } else {
                return nbtBase;
            }
        }
    }

    throw new RuntimeException("Could not deserialize NBTBase");
}

如果你直接CV这段方法,那么会出现错误,在我们解释为什么前,先简要介绍这段代码的逻辑

  • 当object为Map时,则将其键重新映射到递归反序列化后的值后返回重新映射的NBTTagCompound

  • 当object为List时,则将其值全部递归反序列化后返回

  • 当object为String时,会使用正则表达式检查字符串

  • 当字符串符合Array toString() 方法生成的字符串形式时,使用MojangsonParser 解析字符串后调用其内部方法k() 将其反序列化为NBT对象

  • 当字符串符合NBTTagInt序列化时的 数字 + i的形式时,读取数字(除最后一个字符以前的部分)并反序列化为NBTTagInt

  • 当字符串符合NBTTagDouble时的 数字 + d的形式时,取数字(除最后一个字符以前的部分)并反序列化为NBTTagDouble

    • 为什么会这样?因为NBTTagDouble类型的数据被有被特判,所以会调用它的toString() 方法序列化为字符段,“d”是在此时被加上的
  • 当字符串不符合以上正则表达式的检测结果时,会尝试调用MojangsonParser 的内部方法NBTBase c(String s)对字符串进行解析并序列化为NBT,这个方法在11200默认不是public的,需要我们将其设为public

    • 但此时我们需要特判序列化结果,因为MojangsonParser 并不会检查序列化数据在序列化前的类型,如果一个字符串中只含有整形数字,那么他将会被反序列化为NBTTagInt,如果有小数点则为NBTTagDouble,那如果我们只是碰巧在一个NBTTagString中只存储了一个数字呢?很显然,它将会被错误的反序列化NBTTagInt或NBTTagDouble

    • 为了避免这个由Mojang史山代码引发的问题,CraftBukkit会特判数字并在其后加入“i”或"d",然后采用自己的反序列化逻辑,如上

    • 所以此时才会出现“If this returns an integer, it did not use our method from above”这样的注释

注意:上述代码使用到的方法有一些在11202中为private或protected,需要设为public

为什么要单独开一章

尽管本章的内容比较少,但是因为涉及到的MojangsonParser在以后我们还会遇到,故单开一篇分析

最后

至此,我们已经初步完成了11400 PDC的全线功能复活,在下一章我们将进一步复活11605的现代化PDC功能(List、Boolean类型,支持区块和世界持久化数据)

游客

全部评论 (0)

暂无评论,快来抢沙发吧~