前言
在前几节中我们已经实现了11400中PDC中的全部功能,但它的载体仅限于方块、生物和物品,在后期的Bukkit中世界和区块也被纳入了PDC可以管辖的范围,所以在本篇文章中我们将讨论更高版本中(以11605)新加入的现代化PDC载体:世界和区块
注:由于11605和11202的NMS相比世界和区块的加载方式发生了非常大的变化,因此我们不能完全参照11605的NMS改造世界和区块(事实上,新版本很多类和字段、方法等我们都无法在11202 NMS中找到),所以下面更多探讨的是“针对11202实现PDC在区块和世界方面持久化数据的存取”,想要完全仿照11605移植代码是不可能的,我们需要自己分析适合11202 NMS的方法
!!:此篇文章中的实现是实验性的,目前笔者还没有进行太多的测试,故无法判断它是否能够完全工作
接口改造和CraftBukkit实现
此部分我们需要使Bukkit的Chunk和World接口继承PersistentDataHolder,并在CraftChunk和 CraftWorld中实现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,这个对象疑似就是我们要找的区块加载器
查看ChunkProviderServer中ichunkloader的用法,我们发现了
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 nbttagcompound和nbttagcompound1,我们的"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)
暂无评论,快来抢沙发吧~