复活Bukkit的PDC(4)

前言

在前几节中我们已经实现了11400中PDC中的全部功能,但它的载体仅限于方块、生物和物品,在后期的Bukkit中世界和区块也被纳入了PDC可以管辖的范围,所以在本篇文章中我们将讨论更高版本中(以11605)新加入的现代化PDC载体:世界和区块

注:由于11605和11202的NMS相比世界和区块的加载方式发生了非常大的变化,因此我们不能完全参照11605的NMS改造世界和区块(事实上,新版本很多类和字段、方法等我们都无法在11202 NMS中找到),所以下面更多探讨的是“针对11202实现PDC在区块和世界方面持久化数据的存取”,想要完全仿照11605移植代码是不可能的,我们需要自己分析适合11202 NMS的方法

!!:此篇文章中的实现是实验性的,目前笔者还没有进行太多的测试,故无法判断它是否能够完全工作

接口改造和CraftBukkit实现

此部分我们需要使Bukkit的ChunkWorld接口继承PersistentDataHolder,并在CraftChunkCraftWorld中实现getPersistentDataContainer() 方法,它将负责从NMS区块和世界实例中获取PDC实例
注:由于此部分在前几节已经多次涉及到,故省略叙述

NMS实现

区块

PDC实例

11202的NMS的区块实例为net.minecraft.server.Chunk,在此时它还没有被进一步抽象,Chunk承载了几乎所有加载完毕区块的逻辑,所以我们的PDC实例应该放在Chunk

java 复制代码
private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry();
public final CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(DATA_TYPE_REGISTRY);

老生常谈了,不多做叙述

数据存取

寻找区块加载器

11202的区块数据加载不在Chunk中,Chunk中并没有任何与区块加载保存相关的逻辑,所以我们需要寻找区块的加载器

我们先从世界的加载入手,CraftWorld的对应的NMS类是抽象类net.minecraft.server.World,而WorldServer继承了World,它也就是实际的NMS世界实例

我们在WorldServer的构造函数中发现了区块加载的线索

java 复制代码
public WorldServer(MinecraftServer minecraftserver, IDataManager idatamanager, WorldData worlddata, int i, MethodProfiler methodprofiler, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) {
        ...
        this.chunkProvider = this.n(); //与区块加载有关
        ...
    }

其中:

java 复制代码
protected IChunkProvider chunkProvider;

IChunkProvider为一个接口

在找到区块提供者后,我们查看它被赋予了一个什么实例

java 复制代码
    protected IChunkProvider n() {
        ...
        return new ChunkProviderServer(this, ichunkloader, new co.aikar.timings.TimedChunkGenerator(this, gen)); // Paper
        // CraftBukkit end
    }

所以区块的实际实例为ChunkProviderServer类的对象,它传入了一个ichunkloader,这个对象疑似就是我们要找的区块加载器

查看ChunkProviderServerichunkloader的用法,我们发现了

java 复制代码
final ChunkRegionLoader chunkLoader = (ChunkRegionLoader) world.getChunkProviderServer().chunkLoader;

所以实际的区块加载器疑似是ChunkRegionLoader类的对象

查看ChunkRegionLoader类的方法,我们找到了大量关于NBT的操作,所以它应该就是我们要找的区块加载器

注入我们的代码

我们发现了ChunkRegionLoader中有一个非常显眼的方法

java 复制代码
public Object[] loadChunk(World world, int i, int j) throws IOException {
        // CraftBukkit end
        ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i, j);
        NBTTagCompound nbttagcompound = SupplierUtils.getIfExists(this.b.get(chunkcoordintpair)); // Spigot

        if (nbttagcompound == null) {
            // CraftBukkit start
            nbttagcompound = RegionFileCache.d(this.d, i, j);

            if (nbttagcompound == null) {
                return null;
            }

            nbttagcompound = this.e.a((DataConverterType) DataConverterTypes.CHUNK, nbttagcompound);
            // CraftBukkit end
        }

        return this.a(world, i, j, nbttagcompound);
    }

根据它的反混淆名,它应该就是加载区块的方法
其中在获取到NBT数据后,它调用了

java 复制代码
    @Nullable
    protected Object[] a(World world, int i, int j, NBTTagCompound nbttagcompound) { // CraftBukkit - return Chunk -> Object[]
        if (!nbttagcompound.hasKeyOfType("Level", 10)) {
            ChunkRegionLoader.a.error("Chunk file at {},{} is missing level data, skipping", Integer.valueOf(i), Integer.valueOf(j));
            return null;
        } else {
            NBTTagCompound nbttagcompound1 = nbttagcompound.getCompound("Level");

            if (!nbttagcompound1.hasKeyOfType("Sections", 9)) {
                ChunkRegionLoader.a.error("Chunk file at {},{} is missing block data, skipping", Integer.valueOf(i), Integer.valueOf(j));
                return null;
            } else {
                Chunk chunk = this.a(world, nbttagcompound1);

                if (!chunk.a(i, j)) {
                    ChunkRegionLoader.a.error("Chunk file at {},{} is in the wrong location; relocating. (Expected {}, {}, got {}, {})", Integer.valueOf(i), Integer.valueOf(j), Integer.valueOf(i), Integer.valueOf(j), Integer.valueOf(chunk.locX), Integer.valueOf(chunk.locZ));
                    nbttagcompound1.setInt("xPos", i);
                    nbttagcompound1.setInt("zPos", j);

                    // CraftBukkit start - Have to move tile entities since we don't load them at this stage
                    NBTTagList tileEntities = nbttagcompound.getCompound("Level").getList("TileEntities", 10);
                    if (tileEntities != null) {
                        for (int te = 0; te < tileEntities.size(); te++) {
                            NBTTagCompound tileEntity = (NBTTagCompound) tileEntities.get(te);
                            int x = tileEntity.getInt("x") - chunk.locX * 16;
                            int z = tileEntity.getInt("z") - chunk.locZ * 16;
                            tileEntity.setInt("x", i * 16 + x);
                            tileEntity.setInt("z", j * 16 + z);
                        }
                    }
                    // CraftBukkit end
                    chunk = this.a(world, nbttagcompound1);
                }

                // CraftBukkit start
                Object[] data = new Object[2];
                data[0] = chunk;
                data[1] = nbttagcompound;
                return data;
                // CraftBukkit end
            }
        }
    }

方法来加载这个NBT,我们应可推断:

  • nbttagcompound为存储区块数据的NBT
  • chunk为区块实例
    所以笔者在这里选择在方法返回前加载存储在nbttagcompound中"Level"键中的的PDC数据,并将其加载到区块实例的PDC中
java 复制代码
    @Nullable
    protected Object[] a(World world, int i, int j, NBTTagCompound nbttagcompound) { // CraftBukkit - return Chunk -> Object[]
        if (!nbttagcompound.hasKeyOfType("Level", 10)) {
            ChunkRegionLoader.a.error("Chunk file at {},{} is missing level data, skipping", Integer.valueOf(i), Integer.valueOf(j));
            return null;
        } else {
            NBTTagCompound nbttagcompound1 = nbttagcompound.getCompound("Level");

            if (!nbttagcompound1.hasKeyOfType("Sections", 9)) {
                ChunkRegionLoader.a.error("Chunk file at {},{} is missing block data, skipping", Integer.valueOf(i), Integer.valueOf(j));
                return null;
            } else {
                Chunk chunk = this.a(world, nbttagcompound1);

                if (!chunk.a(i, j)) {
                    ChunkRegionLoader.a.error("Chunk file at {},{} is in the wrong location; relocating. (Expected {}, {}, got {}, {})", Integer.valueOf(i), Integer.valueOf(j), Integer.valueOf(i), Integer.valueOf(j), Integer.valueOf(chunk.locX), Integer.valueOf(chunk.locZ));
                    nbttagcompound1.setInt("xPos", i);
                    nbttagcompound1.setInt("zPos", j);

                    // CraftBukkit start - Have to move tile entities since we don't load them at this stage
                    NBTTagList tileEntities = nbttagcompound.getCompound("Level").getList("TileEntities", 10);
                    if (tileEntities != null) {
                        for (int te = 0; te < tileEntities.size(); te++) {
                            NBTTagCompound tileEntity = (NBTTagCompound) tileEntities.get(te);
                            int x = tileEntity.getInt("x") - chunk.locX * 16;
                            int z = tileEntity.getInt("z") - chunk.locZ * 16;
                            tileEntity.setInt("x", i * 16 + x);
                            tileEntity.setInt("z", j * 16 + z);
                        }
                    }
                    // CraftBukkit end
                    chunk = this.a(world, nbttagcompound1);
                }
                // 我们在这里加载PDC数据
                net.minecraft.server.NBTBase persistentBase = nbttagcompound.getCompound("Level").get("ChunkBukkitValues");
                if (persistentBase instanceof NBTTagCompound) {
                    chunk.persistentDataContainer.putAll((NBTTagCompound) persistentBase);
                }
		// 结束

                // CraftBukkit start
                Object[] data = new Object[2];
                data[0] = chunk;
                data[1] = nbttagcompound;
                return data;
                // CraftBukkit end
            }
        }
    }

同样的,我们发现了

java 复制代码
    public void saveChunk(World world, Chunk chunk, boolean unloaded) throws IOException, ExceptionWorldConflict { // Spigot
        world.checkSession();

        try {
            NBTTagCompound nbttagcompound = new NBTTagCompound(2); // Beast - Use initial capacity
            NBTTagCompound nbttagcompound1 = new NBTTagCompound(12); // Beast - Use initial capacity 

            nbttagcompound.set("Level", nbttagcompound1);
            nbttagcompound.setInt("DataVersion", 1343);

            // Spigot start
            final long worldTime = world.getTime();
            final boolean worldHasSkyLight = world.worldProvider.m();
            saveEntities(nbttagcompound1, chunk, world);
            Supplier<NBTTagCompound> completion = new Supplier<NBTTagCompound>() {
                public NBTTagCompound get() {
                    saveBody(nbttagcompound1, chunk, worldTime, worldHasSkyLight);
                    return nbttagcompound;
                }
            };

            this.a(chunk.k(), SupplierUtils.createUnivaluedSupplier(completion, unloaded && this.b.size() < SAVE_QUEUE_TARGET_SIZE));
            // Spigot end
        } catch (Exception exception) {
            ChunkRegionLoader.a.error("Failed to save chunk", exception);
        }

    }

这个保存区块的方法(这里笔者使用了Beast fork,所以NBT的构造函数被使用fastutil进行了初始化优化,其他版本可能有出入)
我们发现有两个NBT nbttagcompoundnbttagcompound1,我们的"Level"键对应nbttagcompound1,它之前有12个键值对,在我们加入PDC的键值对后,应该将它的初始容量变为13

java 复制代码
    public void saveChunk(World world, Chunk chunk, boolean unloaded) throws IOException, ExceptionWorldConflict { // Spigot
        world.checkSession();

        try {
            NBTTagCompound nbttagcompound = new NBTTagCompound(2); // Beast - Use initial capacity
            NBTTagCompound nbttagcompound1 = new NBTTagCompound(13); // Beast - Use initial capacity // 变为13

            nbttagcompound.set("Level", nbttagcompound1);
            nbttagcompound.setInt("DataVersion", 1343);

            // Spigot start
            final long worldTime = world.getTime();
            final boolean worldHasSkyLight = world.worldProvider.m();
            saveEntities(nbttagcompound1, chunk, world);
            Supplier<NBTTagCompound> completion = new Supplier<NBTTagCompound>() {
                public NBTTagCompound get() {
                    saveBody(nbttagcompound1, chunk, worldTime, worldHasSkyLight);
                    return nbttagcompound;
                }
            };

            this.a(chunk.k(), SupplierUtils.createUnivaluedSupplier(completion, unloaded && this.b.size() < SAVE_QUEUE_TARGET_SIZE));
            // Spigot end
        } catch (Exception exception) {
            ChunkRegionLoader.a.error("Failed to save chunk", exception);
        }

    }

之后我们查找nbttagcompound1在哪里被保存,容易发现,它的主体应在

java 复制代码
    private static void saveBody(NBTTagCompound nbttagcompound, Chunk chunk, long worldTime, boolean worldHasSkyLight) { // Spigot
        nbttagcompound.setInt("xPos", chunk.locX);
        nbttagcompound.setInt("zPos", chunk.locZ);
        nbttagcompound.setLong("LastUpdate", worldTime); // Spigot
        nbttagcompound.setIntArray("HeightMap", chunk.r());
        nbttagcompound.setBoolean("TerrainPopulated", chunk.isDone());
        nbttagcompound.setBoolean("LightPopulated", chunk.v());
        nbttagcompound.setLong("InhabitedTime", chunk.x());
        ChunkSection[] achunksection = chunk.getSections();
        NBTTagList nbttaglist = new NBTTagList(achunksection.length); // Beast - Use initial capacity
        boolean flag = worldHasSkyLight; // Spigot
        ChunkSection[] achunksection1 = achunksection;
        int i = achunksection.length;

        NBTTagCompound nbttagcompound1;

        for (int j = 0; j < i; ++j) {
            ChunkSection chunksection = achunksection[j]; // Beast - Inline

            if (chunksection != Chunk.a) {
                nbttagcompound1 = new NBTTagCompound(7); // Beast - Use initial capacity
                nbttagcompound1.setByte("Y", (byte) (chunksection.getYPosition() >> 4 & 255));
                byte[] abyte = new byte[4096];
                NibbleArray nibblearray = new NibbleArray();
                NibbleArray nibblearray1 = chunksection.getBlocks().exportData(abyte, nibblearray);

                nbttagcompound1.setByteArray("Blocks", abyte);
                nbttagcompound1.setByteArray("Data", nibblearray.asBytes());
                if (nibblearray1 != null) {
                    nbttagcompound1.setByteArray("Add", nibblearray1.asBytes());
                }

                nbttagcompound1.setByteArray("BlockLight", chunksection.getEmittedLightArray().asBytes());
                if (worldHasSkyLight) { // Beast - Inline
                    nbttagcompound1.setByteArray("SkyLight", chunksection.getSkyLightArray().asBytes());
                } else {
                    nbttagcompound1.setByteArray("SkyLight", new byte[chunksection.getEmittedLightArray().asBytes().length]);
                }

                nbttaglist.add(nbttagcompound1);
            }
        }

        nbttagcompound.set("Sections", nbttaglist);
        nbttagcompound.setByteArray("Biomes", chunk.getBiomeIndex());

        // Spigot start - End this method here and split off entity saving to another method
    }

中被保存,所以我们只需在最后加入PDC的保存逻辑即可

java 复制代码
        if (!chunk.persistentDataContainer.isEmpty()) {
            nbttagcompound.set("ChunkBukkitValues", chunk.persistentDataContainer.toTagCompound());
        }

至此,对于区块的PDC改造完成

世界

此部分参考11605的源码比较容易移植,易判断PDC的实例以及存取方法应放在WorldData类中

java 复制代码
    public void storeBukkitValues(NBTTagCompound c) {
        if (!this.persistentDataContainer.isEmpty()) {
            c.set("BukkitValues", this.persistentDataContainer.toTagCompound());
        }
    }

    public void readBukkitValues(NBTBase c) {
        if (c instanceof NBTTagCompound) {
            this.persistentDataContainer.putAll((NBTTagCompound) c);
        }
    }

分别将读和存方法加入WorldData的构造函数和private void a(NBTTagCompound nbttagcompound, NBTTagCompound nbttagcompound1)
中即可
#最后
关于PDC的移植在此已经完成了99%,下一篇文章我们将提供现代化PDC的类型移植以及部分测试用例,而后PDC的复活便告一段落

游客

全部评论 (0)

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