Fix //snapshot in 1.18 and re-implement biome/entity restoration (#1620)

* Re-add "//snap" and "//snapshot"

* Place code in correct method

* Use CompoundBinaryTags in AnvilChunk18 and implement biome/entity restoration

* Address comments

* Fix biome reading

* Fix retrieval of entities from zipped snapshot world

Co-authored-by: Alex <mc.cache@web.de>
This commit is contained in:
Jordan 2022-02-24 10:33:25 +01:00 committed by GitHub
parent 49d5183685
commit fc2662e51e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 334 additions and 105 deletions

View File

@ -8,6 +8,7 @@ import com.fastasyncworldedit.core.entity.LazyBaseEntity;
import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory; import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory;
import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.IChunkGet;
import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket; import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket;
import com.fastasyncworldedit.core.util.NbtUtils;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
@ -367,8 +368,7 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements
readEntityIntoTag(mcEntity, minecraftTag); readEntityIntoTag(mcEntity, minecraftTag);
//add Id for AbstractChangeSet to work //add Id for AbstractChangeSet to work
final CompoundBinaryTag tag = (CompoundBinaryTag) toNativeBinary(minecraftTag); final CompoundBinaryTag tag = (CompoundBinaryTag) toNativeBinary(minecraftTag);
final Map<String, BinaryTag> tags = new HashMap<>(); final Map<String, BinaryTag> tags = NbtUtils.getCompoundBinaryTagValues(tag);
tag.keySet().forEach(key -> tags.put(key, tag.get(key)));
tags.put("Id", StringBinaryTag.of(id)); tags.put("Id", StringBinaryTag.of(id));
return CompoundBinaryTag.from(tags); return CompoundBinaryTag.from(tags);
}; };

View File

@ -8,6 +8,7 @@ import com.fastasyncworldedit.core.entity.LazyBaseEntity;
import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory; import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory;
import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.IChunkGet;
import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket; import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket;
import com.fastasyncworldedit.core.util.NbtUtils;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
@ -359,8 +360,7 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements
readEntityIntoTag(mcEntity, minecraftTag); readEntityIntoTag(mcEntity, minecraftTag);
//add Id for AbstractChangeSet to work //add Id for AbstractChangeSet to work
final CompoundBinaryTag tag = (CompoundBinaryTag) toNativeBinary(minecraftTag); final CompoundBinaryTag tag = (CompoundBinaryTag) toNativeBinary(minecraftTag);
final Map<String, BinaryTag> tags = new HashMap<>(); final Map<String, BinaryTag> tags = NbtUtils.getCompoundBinaryTagValues(tag);
tag.keySet().forEach(key -> tags.put(key, tag.get(key)));
tags.put("Id", StringBinaryTag.of(id)); tags.put("Id", StringBinaryTag.of(id));
return CompoundBinaryTag.from(tags); return CompoundBinaryTag.from(tags);
}; };

View File

@ -2,9 +2,16 @@ package com.fastasyncworldedit.core.util;
import com.sk89q.worldedit.util.nbt.BinaryTag; import com.sk89q.worldedit.util.nbt.BinaryTag;
import com.sk89q.worldedit.util.nbt.BinaryTagType; import com.sk89q.worldedit.util.nbt.BinaryTagType;
import com.sk89q.worldedit.util.nbt.BinaryTagTypes;
import com.sk89q.worldedit.util.nbt.ByteBinaryTag;
import com.sk89q.worldedit.util.nbt.CompoundBinaryTag; import com.sk89q.worldedit.util.nbt.CompoundBinaryTag;
import com.sk89q.worldedit.util.nbt.IntBinaryTag;
import com.sk89q.worldedit.util.nbt.ShortBinaryTag;
import com.sk89q.worldedit.world.storage.InvalidFormatException; import com.sk89q.worldedit.world.storage.InvalidFormatException;
import java.util.HashMap;
import java.util.Map;
public class NbtUtils { public class NbtUtils {
/** /**
@ -32,4 +39,45 @@ public class NbtUtils {
return childTagCast; return childTagCast;
} }
/**
* Get an integer from a tag.
*
* @param tag the tag to read from
* @param key the key to look for
* @return child tag
* @throws InvalidFormatException if the format of the items is invalid
* @since TODO
*/
public static int getInt(CompoundBinaryTag tag, String key) throws InvalidFormatException {
BinaryTag childTag = tag.get(key);
if (childTag == null) {
throw new InvalidFormatException("Missing a \"" + key + "\" tag");
}
BinaryTagType<?> type = childTag.type();
if (type == BinaryTagTypes.INT) {
return ((IntBinaryTag) childTag).intValue();
}
if (type == BinaryTagTypes.BYTE) {
return ((ByteBinaryTag) childTag).intValue();
}
if (type == BinaryTagTypes.SHORT) {
return ((ShortBinaryTag) childTag).intValue();
}
throw new InvalidFormatException(key + " tag is not of int, short or byte tag type.");
}
/**
* Get a mutable map of the values stored inside a {@link CompoundBinaryTag}
*
* @param tag {@link CompoundBinaryTag} to get values for
* @return Mutable map of values
* @since TODO
*/
public static Map<String, BinaryTag> getCompoundBinaryTagValues(CompoundBinaryTag tag) {
Map<String, BinaryTag> value = new HashMap<>();
tag.forEach((e) -> value.put(e.getKey(), e.getValue()));
return value;
}
} }

View File

@ -20,6 +20,7 @@
package com.sk89q.worldedit.extension.factory.parser; package com.sk89q.worldedit.extension.factory.parser;
import com.fastasyncworldedit.core.configuration.Caption; import com.fastasyncworldedit.core.configuration.Caption;
import com.fastasyncworldedit.core.util.NbtUtils;
import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.blocks.BaseItem; import com.sk89q.worldedit.blocks.BaseItem;
import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.blocks.BaseItemStack;
@ -132,9 +133,7 @@ public class DefaultItemParser extends InputParser<BaseItem> {
if (itemNbtData == null) { if (itemNbtData == null) {
itemNbtData = otherTag; itemNbtData = otherTag;
} else { } else {
for (String key : otherTag.keySet()) { itemNbtData.put(NbtUtils.getCompoundBinaryTagValues(otherTag));
itemNbtData.put(key, otherTag.get(key));
}
} }
} catch (IOException e) { } catch (IOException e) {
throw new NoMatchException(TranslatableComponent.of( throw new NoMatchException(TranslatableComponent.of(

View File

@ -467,7 +467,9 @@ public final class PlatformCommandManager {
); );
registerSubCommands( registerSubCommands(
"snapshot", "snapshot",
ImmutableList.of("snap"), //FAWE start - add "/" aliases as well
ImmutableList.of("snap", "/snapshot", "/snap"),
//FAWE end
"Snapshot commands for restoring backups", "Snapshot commands for restoring backups",
SnapshotCommandsRegistration.builder(), SnapshotCommandsRegistration.builder(),
new SnapshotCommands(worldEdit) new SnapshotCommands(worldEdit)

View File

@ -31,9 +31,7 @@ import com.sk89q.worldedit.world.biome.BiomeTypes;
/** /**
* The chunk format for Minecraft 1.15 and newer * The chunk format for Minecraft 1.15 and newer
*/ */
//FAWE start - biome and entity restore
public class AnvilChunk15 extends AnvilChunk13 { public class AnvilChunk15 extends AnvilChunk13 {
//FAWE end
/** /**
* Construct the chunk with a compound tag. * Construct the chunk with a compound tag.

View File

@ -95,7 +95,7 @@ public class AnvilChunk17 implements Chunk {
blocks = new BlockState[16][]; // initialise with default length blocks = new BlockState[16][]; // initialise with default length
ListBinaryTag sections = NbtUtils.getChildTag(rootTag, "Sections", BinaryTagTypes.LIST); ListBinaryTag sections = rootTag.getList("Sections");
for (BinaryTag rawSectionTag : sections) { for (BinaryTag rawSectionTag : sections) {
if (!(rawSectionTag instanceof CompoundBinaryTag)) { if (!(rawSectionTag instanceof CompoundBinaryTag)) {
@ -107,7 +107,7 @@ public class AnvilChunk17 implements Chunk {
continue; // Empty section. continue; // Empty section.
} }
int y = NbtUtils.getChildTag(sectionTag, "Y", BinaryTagTypes.BYTE).value(); int y = NbtUtils.getInt(tag, "Y");
updateSectionIndexRange(y); updateSectionIndexRange(y);
// parse palette // parse palette
@ -143,7 +143,7 @@ public class AnvilChunk17 implements Chunk {
} }
// parse block states // parse block states
long[] blockStatesSerialized = NbtUtils.getChildTag(sectionTag, "BlockStates", BinaryTagTypes.LONG_ARRAY).value(); long[] blockStatesSerialized = sectionTag.getLongArray("BlockStates");
BlockState[] chunkSectionBlocks = new BlockState[4096]; BlockState[] chunkSectionBlocks = new BlockState[4096];
blocks[y - minSectionPosition] = chunkSectionBlocks; blocks[y - minSectionPosition] = chunkSectionBlocks;
@ -197,7 +197,7 @@ public class AnvilChunk17 implements Chunk {
if (rootTag.get("TileEntities") == null) { if (rootTag.get("TileEntities") == null) {
return; return;
} }
ListBinaryTag tags = NbtUtils.getChildTag(rootTag, "TileEntities", BinaryTagTypes.LIST); ListBinaryTag tags = rootTag.getList("TileEntities");
for (BinaryTag tag : tags) { for (BinaryTag tag : tags) {
if (!(tag instanceof CompoundBinaryTag)) { if (!(tag instanceof CompoundBinaryTag)) {
@ -274,7 +274,7 @@ public class AnvilChunk17 implements Chunk {
if (rootTag.get("Biomes") == null) { if (rootTag.get("Biomes") == null) {
return; return;
} }
int[] stored = NbtUtils.getChildTag(rootTag, "Biomes", BinaryTagTypes.INT_ARRAY).value(); int[] stored = rootTag.getIntArray("Biomes");
for (int i = 0; i < 1024; i++) { for (int i = 0; i < 1024; i++) {
biomes[i] = BiomeTypes.getLegacy(stored[i]); biomes[i] = BiomeTypes.getLegacy(stored[i]);
} }
@ -297,7 +297,7 @@ public class AnvilChunk17 implements Chunk {
if (entityTagSupplier == null || (entityTag = entityTagSupplier.get()) == null) { if (entityTagSupplier == null || (entityTag = entityTagSupplier.get()) == null) {
return; return;
} }
ListBinaryTag tags = NbtUtils.getChildTag(entityTag, "Entities", BinaryTagTypes.LIST); ListBinaryTag tags = entityTag.getList("Entities");
for (BinaryTag tag : tags) { for (BinaryTag tag : tags) {
if (!(tag instanceof CompoundBinaryTag)) { if (!(tag instanceof CompoundBinaryTag)) {

View File

@ -19,91 +19,134 @@
package com.sk89q.worldedit.world.chunk; package com.sk89q.worldedit.world.chunk;
import com.fastasyncworldedit.core.util.NbtUtils;
import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.IntTag; import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.jnbt.ListTag;
import com.sk89q.jnbt.LongArrayTag;
import com.sk89q.jnbt.NBTUtils;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.registry.state.Property; import com.sk89q.worldedit.registry.state.Property;
import com.sk89q.worldedit.util.concurrency.LazyReference;
import com.sk89q.worldedit.util.nbt.BinaryTag;
import com.sk89q.worldedit.util.nbt.BinaryTagTypes;
import com.sk89q.worldedit.util.nbt.CompoundBinaryTag;
import com.sk89q.worldedit.util.nbt.ListBinaryTag;
import com.sk89q.worldedit.world.DataException; import com.sk89q.worldedit.world.DataException;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockType;
import com.sk89q.worldedit.world.block.BlockTypes; import com.sk89q.worldedit.world.block.BlockTypes;
import com.sk89q.worldedit.world.entity.EntityTypes;
import com.sk89q.worldedit.world.storage.InvalidFormatException; import com.sk89q.worldedit.world.storage.InvalidFormatException;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Supplier;
/** /**
* The chunk format for Minecraft 1.18 and newer * The chunk format for Minecraft 1.18 and newer
*/ */
public class AnvilChunk18 implements Chunk { public class AnvilChunk18 implements Chunk {
private final CompoundTag rootTag; //FAWE start - CBT
private final CompoundBinaryTag rootTag;
//FAWE end
private final Int2ObjectOpenHashMap<BlockState[]> blocks; private final Int2ObjectOpenHashMap<BlockState[]> blocks;
private final int rootX; //FAWE start - entity and biome restore
private final int rootZ; private final int sectionCount;
private final Supplier<CompoundBinaryTag> entityTagSupplier;
private Int2ObjectOpenHashMap<BiomeType[]> biomes = null;
private List<BaseEntity> entities;
private Map<BlockVector3, CompoundBinaryTag> tileEntities;
//FAWE end
private Map<BlockVector3, Map<String, Tag>> tileEntities;
/** /**
* Construct the chunk with a compound tag. * Construct the chunk with a compound tag.
* *
* @param tag the tag to read * @param tag the tag to read
* @throws DataException on a data error * @throws DataException on a data error
* @deprecated Use {@link AnvilChunk18#AnvilChunk18(CompoundBinaryTag, Supplier)}
*/ */
@Deprecated
public AnvilChunk18(CompoundTag tag) throws DataException { public AnvilChunk18(CompoundTag tag) throws DataException {
//FAWE start - CBT
this(tag.asBinaryTag(), () -> null);
}
/**
* Construct the chunk with a compound tag.
*
* @param tag the tag to read
* @throws DataException on a data error
* @deprecated Use {@link AnvilChunk18#AnvilChunk18(CompoundBinaryTag, Supplier)}
* @since TODO
*/
@Deprecated
public AnvilChunk18(CompoundTag tag, Supplier<CompoundTag> entitiesTag) throws DataException {
//FAWE start - CBT
this(tag.asBinaryTag(), () -> {
CompoundTag compoundTag = entitiesTag.get();
return compoundTag == null ? null : compoundTag.asBinaryTag();
});
}
/**
* Construct the chunk with a compound tag.
*
* @param tag the tag to read
* @throws DataException on a data error
* @since TODO
*/
public AnvilChunk18(CompoundBinaryTag tag, Supplier<CompoundBinaryTag> entityTag) throws DataException {
//FAWE end
rootTag = tag; rootTag = tag;
entityTagSupplier = entityTag;
rootX = NBTUtils.getChildTag(rootTag.getValue(), "xPos", IntTag.class).getValue(); //FAWE start - CBT
rootZ = NBTUtils.getChildTag(rootTag.getValue(), "zPos", IntTag.class).getValue(); ListBinaryTag sections = rootTag.getList("sections");
this.sectionCount = sections.size();
List<Tag> sections = NBTUtils.getChildTag(rootTag.getValue(), "sections", ListTag.class).getValue();
blocks = new Int2ObjectOpenHashMap<>(sections.size()); blocks = new Int2ObjectOpenHashMap<>(sections.size());
for (Tag rawSectionTag : sections) { for (BinaryTag rawSectionTag : sections) {
if (!(rawSectionTag instanceof CompoundTag sectionTag)) { if (!(rawSectionTag instanceof CompoundBinaryTag sectionTag)) {
continue; continue;
} }
Object yValue = sectionTag.getValue().get("Y").getValue(); // sometimes a byte, sometimes an int int y = NbtUtils.getInt(sectionTag, "Y"); // sometimes a byte, sometimes an int
if (!(yValue instanceof Number)) {
throw new InvalidFormatException("Y is not numeric: " + yValue);
}
int y = ((Number) yValue).intValue();
Tag rawBlockStatesTag = sectionTag.getValue().get("block_states"); // null for sections outside of the world limits BinaryTag rawBlockStatesTag = sectionTag.get("block_states"); // null for sections outside of the world limits
if (rawBlockStatesTag instanceof CompoundTag blockStatesTag) { if (rawBlockStatesTag instanceof CompoundBinaryTag blockStatesTag) {
// parse palette // parse palette
List<CompoundTag> paletteEntries = blockStatesTag.getList("palette", CompoundTag.class); ListBinaryTag paletteEntries = blockStatesTag.getList("palette");
int paletteSize = paletteEntries.size(); int paletteSize = paletteEntries.size();
if (paletteSize == 0) { if (paletteSize == 0) {
continue; continue;
} }
BlockState[] palette = new BlockState[paletteSize]; BlockState[] palette = new BlockState[paletteSize];
for (int paletteEntryId = 0; paletteEntryId < paletteSize; paletteEntryId++) { for (int paletteEntryId = 0; paletteEntryId < paletteSize; paletteEntryId++) {
CompoundTag paletteEntry = paletteEntries.get(paletteEntryId); CompoundBinaryTag paletteEntry = (CompoundBinaryTag) paletteEntries.get(paletteEntryId);
BlockType type = BlockTypes.get(paletteEntry.getString("Name")); BlockType type = BlockTypes.get(paletteEntry.getString("Name"));
if (type == null) { if (type == null) {
throw new InvalidFormatException("Invalid block type: " + paletteEntry.getString("Name")); throw new InvalidFormatException("Invalid block type: " + paletteEntry.getString("Name"));
} }
BlockState blockState = type.getDefaultState(); BlockState blockState = type.getDefaultState();
if (paletteEntry.containsKey("Properties")) { BinaryTag propertiesTag = paletteEntry.get("Properties");
CompoundTag properties = NBTUtils.getChildTag(paletteEntry.getValue(), "Properties", CompoundTag.class); if (propertiesTag instanceof CompoundBinaryTag properties) {
for (Property<?> property : blockState.getStates().keySet()) { for (Property<?> property : blockState.getStates().keySet()) {
if (properties.containsKey(property.getName())) { String value;
String value = properties.getString(property.getName()); if (!(value = properties.getString(property.getName())).isEmpty()) {
try { try {
blockState = getBlockStateWith(blockState, property, value); blockState = getBlockStateWith(blockState, property, value);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
throw new InvalidFormatException("Invalid block state for " + blockState.getBlockType().getId() + ", " + property.getName() + ": " + value); throw new InvalidFormatException("Invalid block state for " + blockState
.getBlockType()
.getId() + ", " + property.getName() + ": " + value);
} }
} }
} }
@ -117,7 +160,7 @@ public class AnvilChunk18 implements Chunk {
} }
// parse block states // parse block states
long[] blockStatesSerialized = NBTUtils.getChildTag(blockStatesTag.getValue(), "data", LongArrayTag.class).getValue(); long[] blockStatesSerialized = blockStatesTag.getLongArray("data");
BlockState[] chunkSectionBlocks = new BlockState[16 * 16 * 16]; BlockState[] chunkSectionBlocks = new BlockState[16 * 16 * 16];
blocks.put(y, chunkSectionBlocks); blocks.put(y, chunkSectionBlocks);
@ -125,6 +168,7 @@ public class AnvilChunk18 implements Chunk {
readBlockStates(palette, blockStatesSerialized, chunkSectionBlocks); readBlockStates(palette, blockStatesSerialized, chunkSectionBlocks);
} }
} }
//FAWE end
} }
protected void readBlockStates(BlockState[] palette, long[] blockStatesSerialized, BlockState[] chunkSectionBlocks) throws InvalidFormatException { protected void readBlockStates(BlockState[] palette, long[] blockStatesSerialized, BlockState[] chunkSectionBlocks) throws InvalidFormatException {
@ -146,26 +190,24 @@ public class AnvilChunk18 implements Chunk {
* Used to load the tile entities. * Used to load the tile entities.
*/ */
private void populateTileEntities() throws DataException { private void populateTileEntities() throws DataException {
//FAWE start - CBT
tileEntities = new HashMap<>(); tileEntities = new HashMap<>();
if (!rootTag.getValue().containsKey("block_entities")) { if (!(rootTag.get("block_entities") instanceof ListBinaryTag tags)) {
return; return;
} }
List<Tag> tags = NBTUtils.getChildTag(rootTag.getValue(), for (BinaryTag tag : tags) {
"block_entities", ListTag.class).getValue(); if (!(tag instanceof CompoundBinaryTag t)) {
for (Tag tag : tags) {
if (!(tag instanceof CompoundTag t)) {
throw new InvalidFormatException("CompoundTag expected in block_entities"); throw new InvalidFormatException("CompoundTag expected in block_entities");
} }
Map<String, Tag> values = new HashMap<>(t.getValue()); int x = t.getInt("x");
int x = ((IntTag) values.get("x")).getValue(); int y = t.getInt("y");
int y = ((IntTag) values.get("y")).getValue(); int z = t.getInt("z");
int z = ((IntTag) values.get("z")).getValue();
BlockVector3 vec = BlockVector3.at(x, y, z); BlockVector3 vec = BlockVector3.at(x, y, z);
tileEntities.put(vec, values); tileEntities.put(vec, t);
} }
//FAWE end
} }
/** /**
@ -178,24 +220,21 @@ public class AnvilChunk18 implements Chunk {
* @throws DataException thrown if there is a data error * @throws DataException thrown if there is a data error
*/ */
@Nullable @Nullable
private CompoundTag getBlockTileEntity(BlockVector3 position) throws DataException { private CompoundBinaryTag getBlockTileEntity(BlockVector3 position) throws DataException {
//FAWE start - CBT
if (tileEntities == null) { if (tileEntities == null) {
populateTileEntities(); populateTileEntities();
} }
Map<String, Tag> values = tileEntities.get(position); return tileEntities.get(position);
if (values == null) { //FAWE end
return null;
}
return new CompoundTag(values);
} }
@Override @Override
public BaseBlock getBlock(BlockVector3 position) throws DataException { public BaseBlock getBlock(BlockVector3 position) throws DataException {
int x = position.getX() - rootX * 16; int x = position.getX() & 15;
int y = position.getY(); int y = position.getY();
int z = position.getZ() - rootZ * 16; int z = position.getZ() & 15;
int section = y >> 4; int section = y >> 4;
int yIndex = y & 0x0F; int yIndex = y & 0x0F;
@ -206,7 +245,7 @@ public class AnvilChunk18 implements Chunk {
} }
BlockState state = sectionBlocks[sectionBlocks.length == 1 ? 0 : ((yIndex << 8) | (z << 4) | x)]; BlockState state = sectionBlocks[sectionBlocks.length == 1 ? 0 : ((yIndex << 8) | (z << 4) | x)];
CompoundTag tileEntity = getBlockTileEntity(position); CompoundBinaryTag tileEntity = getBlockTileEntity(position);
if (tileEntity != null) { if (tileEntity != null) {
return state.toBaseBlock(tileEntity); return state.toBaseBlock(tileEntity);
@ -215,4 +254,110 @@ public class AnvilChunk18 implements Chunk {
return state.toBaseBlock(); return state.toBaseBlock();
} }
@Override
public BiomeType getBiome(final BlockVector3 position) throws DataException {
if (biomes == null) {
populateBiomes();
}
int x = (position.getX() & 15) >> 2;
int y = (position.getY() & 15) >> 2;
int z = (position.getZ() & 15) >> 2;
int section = position.getY() >> 4;
BiomeType[] sectionBiomes = biomes.get(section);
if (sectionBiomes.length == 1) {
return sectionBiomes[0];
}
return biomes.get(section)[y << 4 | z << 2 | x];
}
private void populateBiomes() throws DataException {
biomes = new Int2ObjectOpenHashMap<>(sectionCount);
ListBinaryTag sections = rootTag.getList("sections");
for (BinaryTag rawSectionTag : sections) {
if (!(rawSectionTag instanceof CompoundBinaryTag sectionTag)) {
continue;
}
int y = NbtUtils.getInt(sectionTag, "Y"); // sometimes a byte, sometimes an int
BinaryTag rawBlockStatesTag = sectionTag.get("biomes"); // null for sections outside of the world limits
if (rawBlockStatesTag instanceof CompoundBinaryTag biomeTypesTag) {
// parse palette
ListBinaryTag paletteEntries = biomeTypesTag.getList("palette");
int paletteSize = paletteEntries.size();
if (paletteSize == 0) {
continue;
}
BiomeType[] palette = new BiomeType[paletteSize];
for (int paletteEntryId = 0; paletteEntryId < paletteSize; paletteEntryId++) {
String paletteEntry = paletteEntries.getString(paletteEntryId);
BiomeType type = BiomeType.REGISTRY.get(paletteEntry);
if (type == null) {
throw new InvalidFormatException("Invalid biome type: " + paletteEntry);
}
palette[paletteEntryId] = type;
}
if (paletteSize == 1) {
// the same block everywhere
biomes.put(y, palette);
continue;
}
// parse block states
long[] biomesSerialized = biomeTypesTag.getLongArray("data");
if (biomesSerialized.length == 0) {
throw new InvalidFormatException("Biome data not present.");
}
BiomeType[] chunkSectionBiomes = new BiomeType[64];
biomes.put(y, chunkSectionBiomes);
readBiomes(palette, biomesSerialized, chunkSectionBiomes);
}
}
}
protected void readBiomes(BiomeType[] palette, long[] biomesSerialized, BiomeType[] chunkSectionBiomes) throws
InvalidFormatException {
PackedIntArrayReader reader = new PackedIntArrayReader(biomesSerialized, 64);
for (int biomePos = 0; biomePos < chunkSectionBiomes.length; biomePos++) {
int index = reader.get(biomePos);
if (index >= palette.length) {
throw new InvalidFormatException("Invalid biome table entry: " + index);
}
chunkSectionBiomes[biomePos] = palette[index];
}
}
@Override
public List<BaseEntity> getEntities() throws DataException {
if (entities == null) {
populateEntities();
}
return entities;
}
/**
* Used to load the biomes.
*/
private void populateEntities() throws DataException {
entities = new ArrayList<>();
CompoundBinaryTag entityTag;
if (entityTagSupplier == null || (entityTag = entityTagSupplier.get()) == null) {
return;
}
ListBinaryTag tags = NbtUtils.getChildTag(entityTag, "Entities", BinaryTagTypes.LIST);
for (BinaryTag tag : tags) {
if (!(tag instanceof CompoundBinaryTag t)) {
throw new InvalidFormatException("CompoundTag expected in Entities");
}
entities.add(new BaseEntity(EntityTypes.get(t.getString("id")), LazyReference.computed(t)));
}
}
} }

View File

@ -32,28 +32,56 @@ public class PackedIntArrayReader {
} }
} }
private static final int SIZE = 4096;
private final long[] data; private final long[] data;
private final int elementBits; private final int elementBits;
private final long maxValue; private final long maxValue;
private final int elementsPerLong; private final int elementsPerLong;
private final int factor; private final int factor;
//FAWE start - allow other sizes of data to be parsed
private final int storedSize;
//FAWE end
/**
* Create a new PackedIntArrayReader instance based on an array of longs containing 4096 integers.
*
* @param data long array containing data
*/
public PackedIntArrayReader(long[] data) { public PackedIntArrayReader(long[] data) {
this.data = data; this.data = data;
this.storedSize = 4096;
this.elementBits = data.length * 64 / 4096; this.elementBits = data.length * 64 / 4096;
this.maxValue = (1L << elementBits) - 1L; this.maxValue = (1L << elementBits) - 1L;
this.elementsPerLong = 64 / elementBits; this.elementsPerLong = 64 / elementBits;
this.factor = FACTORS[elementsPerLong - 1]; this.factor = FACTORS[elementsPerLong - 1];
int j = (SIZE + this.elementsPerLong - 1) / this.elementsPerLong; int j = (storedSize + this.elementsPerLong - 1) / this.elementsPerLong;
if (j != data.length) { if (j != data.length) {
throw new IllegalStateException("Invalid packed-int array provided, should be of length " + j); throw new IllegalStateException("Invalid packed-int array provided, should be of length " + j);
} }
} }
//FAWE start - allow other sizes of data to be parsed
/**
* Create a new PackedIntArrayReader instance based on an array of longs containing a certain number of integers.
*
* @param data long array containing data
* @param storedSize the amount of integers stored in the long array
*/
public PackedIntArrayReader(long[] data, int storedSize) {
this.data = data;
this.storedSize = storedSize;
this.elementBits = data.length * 64 / storedSize;
this.maxValue = (1L << elementBits) - 1L;
this.elementsPerLong = 64 / elementBits;
this.factor = FACTORS[elementsPerLong - 1];
int j = (storedSize + this.elementsPerLong - 1) / this.elementsPerLong;
if (j != data.length) {
throw new IllegalStateException("Invalid packed-int array provided, should be of length " + j);
}
}
//FAWE end
public int get(int index) { public int get(int index) {
checkElementIndex(index, SIZE); checkElementIndex(index, storedSize);
int i = this.adjustIndex(index); int i = this.adjustIndex(index);
long l = this.data[i]; long l = this.data[i];
int j = (index - i * this.elementsPerLong) * this.elementBits; int j = (index - i * this.elementsPerLong) * this.elementBits;

View File

@ -73,28 +73,8 @@ public class ChunkStoreHelper {
* @throws DataException if the rootTag is not valid chunk data * @throws DataException if the rootTag is not valid chunk data
*/ */
public static Chunk getChunk(CompoundTag rootTag) throws DataException { public static Chunk getChunk(CompoundTag rootTag) throws DataException {
int dataVersion = rootTag.getInt("DataVersion");
if (dataVersion == 0) {
dataVersion = -1;
}
final Platform platform = WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING);
final int currentDataVersion = platform.getDataVersion();
if ((dataVersion > 0 || hasLevelSections(rootTag)) && dataVersion < currentDataVersion) { // only fix up MCA format, DFU doesn't support MCR chunks
final DataFixer dataFixer = platform.getDataFixer();
if (dataFixer != null) {
rootTag = (CompoundTag) AdventureNBTConverter.fromAdventure(dataFixer.fixUp(DataFixer.FixTypes.CHUNK,
rootTag.asBinaryTag(), dataVersion));
dataVersion = currentDataVersion;
}
}
if (dataVersion >= Constants.DATA_VERSION_MC_1_18) {
return new AnvilChunk18(rootTag);
}
//FAWE start - biome and entity restore //FAWE start - biome and entity restore
return getChunk(rootTag, () -> null); return getChunk(rootTag, () -> null);
//FAWE end
} }
/** /**
@ -105,9 +85,32 @@ public class ChunkStoreHelper {
* location * location
* @return a Chunk implementation * @return a Chunk implementation
* @throws DataException if the rootTag is not valid chunk data * @throws DataException if the rootTag is not valid chunk data
* @since TODO
*/ */
public static Chunk getChunk(CompoundTag rootTag, Supplier<CompoundTag> entitiesTag) throws DataException { public static Chunk getChunk(CompoundTag rootTag, Supplier<CompoundTag> entitiesTag) throws DataException {
//FAWE end //FAWE end
int dataVersion = rootTag.getInt("DataVersion");
if (dataVersion == 0) {
dataVersion = -1;
}
final Platform platform = WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING);
final int currentDataVersion = platform.getDataVersion();
if ((dataVersion > 0 || hasLevelSections(rootTag)) && dataVersion < currentDataVersion) { // only fix up MCA format, DFU doesn't support MCR chunks
final DataFixer dataFixer = platform.getDataFixer();
if (dataFixer != null) {
//FAWE start - CBT
rootTag = (CompoundTag) AdventureNBTConverter.fromAdventure(dataFixer.fixUp(DataFixer.FixTypes.CHUNK,
rootTag.asBinaryTag(), dataVersion));
//FAWE end
dataVersion = currentDataVersion;
}
}
if (dataVersion >= Constants.DATA_VERSION_MC_1_18) {
return new AnvilChunk18(rootTag, entitiesTag);
}
Map<String, Tag> children = rootTag.getValue(); Map<String, Tag> children = rootTag.getValue();
CompoundTag tag = null; CompoundTag tag = null;
@ -130,10 +133,6 @@ public class ChunkStoreHelper {
throw new ChunkStoreException("Missing root 'Level' tag"); throw new ChunkStoreException("Missing root 'Level' tag");
} }
int dataVersion = rootTag.getInt("DataVersion");
if (dataVersion == 0) {
dataVersion = -1;
}
//FAWE start - biome and entity restore //FAWE start - biome and entity restore
if (dataVersion >= Constants.DATA_VERSION_MC_1_17) { if (dataVersion >= Constants.DATA_VERSION_MC_1_17) {
return new AnvilChunk17(tag, entitiesTag); return new AnvilChunk17(tag, entitiesTag);

View File

@ -80,14 +80,13 @@ public class TrueZipMcRegionChunkStore extends McRegionChunkStore {
protected InputStream getInputStream(String name, String worldName, @Nullable String folderOverride) throws IOException, protected InputStream getInputStream(String name, String worldName, @Nullable String folderOverride) throws IOException,
DataException { DataException {
// Detect subfolder for the world's files // Detect subfolder for the world's files
if (folderOverride != null) { if (folder != null) {
if (!folderOverride.isEmpty()) {
name = folderOverride + "/" + name;
}
} else if (folder != null) {
//FAWE end
if (!folder.isEmpty()) { if (!folder.isEmpty()) {
//FAWE end
name = folder + "/" + name; name = folder + "/" + name;
if (folderOverride != null) {
name = name.replace("region", folderOverride);
}
} }
} else { } else {
Pattern pattern = Pattern.compile(".*\\.mc[ra]$"); Pattern pattern = Pattern.compile(".*\\.mc[ra]$");
@ -105,6 +104,12 @@ public class TrueZipMcRegionChunkStore extends McRegionChunkStore {
endIndex = entryName.lastIndexOf('\\'); endIndex = entryName.lastIndexOf('\\');
} }
folder = entryName.substring(0, endIndex); folder = entryName.substring(0, endIndex);
//FAWE start - biome and entity restore
if (folderOverride != null && folder.endsWith(folderOverride)) {
name = folder + "/" + name;
break;
}
//FAWE end
if (folder.endsWith("poi") || folder.endsWith("entities")) { if (folder.endsWith("poi") || folder.endsWith("entities")) {
continue; continue;
} }

View File

@ -78,14 +78,13 @@ public class ZippedMcRegionChunkStore extends McRegionChunkStore {
protected InputStream getInputStream(String name, String worldName, @Nullable String folderOverride) throws IOException, protected InputStream getInputStream(String name, String worldName, @Nullable String folderOverride) throws IOException,
DataException { DataException {
// Detect subfolder for the world's files // Detect subfolder for the world's files
if (folderOverride != null) { if (folder != null) {
if (!folderOverride.isEmpty()) {
name = folderOverride + "/" + name;
}
} else if (folder != null) {
if (!folder.isEmpty()) { if (!folder.isEmpty()) {
//FAWE end //FAWE end
name = folder + "/" + name; name = folder + "/" + name;
if (folderOverride != null) {
name = name.replace("region", folderOverride);
}
} }
} else { } else {
Pattern pattern = Pattern.compile(".*\\.mc[ra]$"); Pattern pattern = Pattern.compile(".*\\.mc[ra]$");
@ -101,6 +100,12 @@ public class ZippedMcRegionChunkStore extends McRegionChunkStore {
endIndex = entryName.lastIndexOf('\\'); endIndex = entryName.lastIndexOf('\\');
} }
folder = entryName.substring(0, endIndex); folder = entryName.substring(0, endIndex);
//FAWE start - biome and entity restore
if (folderOverride != null && folder.endsWith(folderOverride)) {
name = folder + "/" + name;
break;
}
//FAWE end
if (folder.endsWith("poi") || folder.endsWith("entities")) { if (folder.endsWith("poi") || folder.endsWith("entities")) {
continue; continue;
} }