mirror of
https://github.com/plexusorg/Plex-FAWE.git
synced 2024-12-22 17:27:38 +00:00
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:
parent
49d5183685
commit
fc2662e51e
@ -8,6 +8,7 @@ import com.fastasyncworldedit.core.entity.LazyBaseEntity;
|
||||
import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory;
|
||||
import com.fastasyncworldedit.core.queue.IChunkGet;
|
||||
import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket;
|
||||
import com.fastasyncworldedit.core.util.NbtUtils;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
@ -367,8 +368,7 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements
|
||||
readEntityIntoTag(mcEntity, minecraftTag);
|
||||
//add Id for AbstractChangeSet to work
|
||||
final CompoundBinaryTag tag = (CompoundBinaryTag) toNativeBinary(minecraftTag);
|
||||
final Map<String, BinaryTag> tags = new HashMap<>();
|
||||
tag.keySet().forEach(key -> tags.put(key, tag.get(key)));
|
||||
final Map<String, BinaryTag> tags = NbtUtils.getCompoundBinaryTagValues(tag);
|
||||
tags.put("Id", StringBinaryTag.of(id));
|
||||
return CompoundBinaryTag.from(tags);
|
||||
};
|
||||
|
@ -8,6 +8,7 @@ import com.fastasyncworldedit.core.entity.LazyBaseEntity;
|
||||
import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory;
|
||||
import com.fastasyncworldedit.core.queue.IChunkGet;
|
||||
import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket;
|
||||
import com.fastasyncworldedit.core.util.NbtUtils;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
@ -359,8 +360,7 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements
|
||||
readEntityIntoTag(mcEntity, minecraftTag);
|
||||
//add Id for AbstractChangeSet to work
|
||||
final CompoundBinaryTag tag = (CompoundBinaryTag) toNativeBinary(minecraftTag);
|
||||
final Map<String, BinaryTag> tags = new HashMap<>();
|
||||
tag.keySet().forEach(key -> tags.put(key, tag.get(key)));
|
||||
final Map<String, BinaryTag> tags = NbtUtils.getCompoundBinaryTagValues(tag);
|
||||
tags.put("Id", StringBinaryTag.of(id));
|
||||
return CompoundBinaryTag.from(tags);
|
||||
};
|
||||
|
@ -2,9 +2,16 @@ package com.fastasyncworldedit.core.util;
|
||||
|
||||
import com.sk89q.worldedit.util.nbt.BinaryTag;
|
||||
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.IntBinaryTag;
|
||||
import com.sk89q.worldedit.util.nbt.ShortBinaryTag;
|
||||
import com.sk89q.worldedit.world.storage.InvalidFormatException;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class NbtUtils {
|
||||
|
||||
/**
|
||||
@ -32,4 +39,45 @@ public class NbtUtils {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
||||
package com.sk89q.worldedit.extension.factory.parser;
|
||||
|
||||
import com.fastasyncworldedit.core.configuration.Caption;
|
||||
import com.fastasyncworldedit.core.util.NbtUtils;
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
import com.sk89q.worldedit.blocks.BaseItem;
|
||||
import com.sk89q.worldedit.blocks.BaseItemStack;
|
||||
@ -132,9 +133,7 @@ public class DefaultItemParser extends InputParser<BaseItem> {
|
||||
if (itemNbtData == null) {
|
||||
itemNbtData = otherTag;
|
||||
} else {
|
||||
for (String key : otherTag.keySet()) {
|
||||
itemNbtData.put(key, otherTag.get(key));
|
||||
}
|
||||
itemNbtData.put(NbtUtils.getCompoundBinaryTagValues(otherTag));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new NoMatchException(TranslatableComponent.of(
|
||||
|
@ -467,7 +467,9 @@ public final class PlatformCommandManager {
|
||||
);
|
||||
registerSubCommands(
|
||||
"snapshot",
|
||||
ImmutableList.of("snap"),
|
||||
//FAWE start - add "/" aliases as well
|
||||
ImmutableList.of("snap", "/snapshot", "/snap"),
|
||||
//FAWE end
|
||||
"Snapshot commands for restoring backups",
|
||||
SnapshotCommandsRegistration.builder(),
|
||||
new SnapshotCommands(worldEdit)
|
||||
|
@ -31,9 +31,7 @@ import com.sk89q.worldedit.world.biome.BiomeTypes;
|
||||
/**
|
||||
* The chunk format for Minecraft 1.15 and newer
|
||||
*/
|
||||
//FAWE start - biome and entity restore
|
||||
public class AnvilChunk15 extends AnvilChunk13 {
|
||||
//FAWE end
|
||||
|
||||
/**
|
||||
* Construct the chunk with a compound tag.
|
||||
|
@ -95,7 +95,7 @@ public class AnvilChunk17 implements Chunk {
|
||||
|
||||
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) {
|
||||
if (!(rawSectionTag instanceof CompoundBinaryTag)) {
|
||||
@ -107,7 +107,7 @@ public class AnvilChunk17 implements Chunk {
|
||||
continue; // Empty section.
|
||||
}
|
||||
|
||||
int y = NbtUtils.getChildTag(sectionTag, "Y", BinaryTagTypes.BYTE).value();
|
||||
int y = NbtUtils.getInt(tag, "Y");
|
||||
updateSectionIndexRange(y);
|
||||
|
||||
// parse palette
|
||||
@ -143,7 +143,7 @@ public class AnvilChunk17 implements Chunk {
|
||||
}
|
||||
|
||||
// parse block states
|
||||
long[] blockStatesSerialized = NbtUtils.getChildTag(sectionTag, "BlockStates", BinaryTagTypes.LONG_ARRAY).value();
|
||||
long[] blockStatesSerialized = sectionTag.getLongArray("BlockStates");
|
||||
|
||||
BlockState[] chunkSectionBlocks = new BlockState[4096];
|
||||
blocks[y - minSectionPosition] = chunkSectionBlocks;
|
||||
@ -197,7 +197,7 @@ public class AnvilChunk17 implements Chunk {
|
||||
if (rootTag.get("TileEntities") == null) {
|
||||
return;
|
||||
}
|
||||
ListBinaryTag tags = NbtUtils.getChildTag(rootTag, "TileEntities", BinaryTagTypes.LIST);
|
||||
ListBinaryTag tags = rootTag.getList("TileEntities");
|
||||
|
||||
for (BinaryTag tag : tags) {
|
||||
if (!(tag instanceof CompoundBinaryTag)) {
|
||||
@ -274,7 +274,7 @@ public class AnvilChunk17 implements Chunk {
|
||||
if (rootTag.get("Biomes") == null) {
|
||||
return;
|
||||
}
|
||||
int[] stored = NbtUtils.getChildTag(rootTag, "Biomes", BinaryTagTypes.INT_ARRAY).value();
|
||||
int[] stored = rootTag.getIntArray("Biomes");
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
biomes[i] = BiomeTypes.getLegacy(stored[i]);
|
||||
}
|
||||
@ -297,7 +297,7 @@ public class AnvilChunk17 implements Chunk {
|
||||
if (entityTagSupplier == null || (entityTag = entityTagSupplier.get()) == null) {
|
||||
return;
|
||||
}
|
||||
ListBinaryTag tags = NbtUtils.getChildTag(entityTag, "Entities", BinaryTagTypes.LIST);
|
||||
ListBinaryTag tags = entityTag.getList("Entities");
|
||||
|
||||
for (BinaryTag tag : tags) {
|
||||
if (!(tag instanceof CompoundBinaryTag)) {
|
||||
|
@ -19,91 +19,134 @@
|
||||
|
||||
package com.sk89q.worldedit.world.chunk;
|
||||
|
||||
import com.fastasyncworldedit.core.util.NbtUtils;
|
||||
import com.sk89q.jnbt.CompoundTag;
|
||||
import com.sk89q.jnbt.IntTag;
|
||||
import com.sk89q.jnbt.ListTag;
|
||||
import com.sk89q.jnbt.LongArrayTag;
|
||||
import com.sk89q.jnbt.NBTUtils;
|
||||
import com.sk89q.jnbt.Tag;
|
||||
import com.sk89q.worldedit.entity.BaseEntity;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
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.biome.BiomeType;
|
||||
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||
import com.sk89q.worldedit.world.block.BlockState;
|
||||
import com.sk89q.worldedit.world.block.BlockType;
|
||||
import com.sk89q.worldedit.world.block.BlockTypes;
|
||||
import com.sk89q.worldedit.world.entity.EntityTypes;
|
||||
import com.sk89q.worldedit.world.storage.InvalidFormatException;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* The chunk format for Minecraft 1.18 and newer
|
||||
*/
|
||||
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 int rootX;
|
||||
private final int rootZ;
|
||||
//FAWE start - entity and biome restore
|
||||
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.
|
||||
*
|
||||
* @param tag the tag to read
|
||||
* @throws DataException on a data error
|
||||
* @deprecated Use {@link AnvilChunk18#AnvilChunk18(CompoundBinaryTag, Supplier)}
|
||||
*/
|
||||
@Deprecated
|
||||
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;
|
||||
entityTagSupplier = entityTag;
|
||||
|
||||
rootX = NBTUtils.getChildTag(rootTag.getValue(), "xPos", IntTag.class).getValue();
|
||||
rootZ = NBTUtils.getChildTag(rootTag.getValue(), "zPos", IntTag.class).getValue();
|
||||
//FAWE start - CBT
|
||||
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());
|
||||
|
||||
for (Tag rawSectionTag : sections) {
|
||||
if (!(rawSectionTag instanceof CompoundTag sectionTag)) {
|
||||
for (BinaryTag rawSectionTag : sections) {
|
||||
if (!(rawSectionTag instanceof CompoundBinaryTag sectionTag)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object yValue = sectionTag.getValue().get("Y").getValue(); // sometimes a byte, sometimes an int
|
||||
if (!(yValue instanceof Number)) {
|
||||
throw new InvalidFormatException("Y is not numeric: " + yValue);
|
||||
}
|
||||
int y = ((Number) yValue).intValue();
|
||||
int y = NbtUtils.getInt(sectionTag, "Y"); // sometimes a byte, sometimes an int
|
||||
|
||||
Tag rawBlockStatesTag = sectionTag.getValue().get("block_states"); // null for sections outside of the world limits
|
||||
if (rawBlockStatesTag instanceof CompoundTag blockStatesTag) {
|
||||
BinaryTag rawBlockStatesTag = sectionTag.get("block_states"); // null for sections outside of the world limits
|
||||
if (rawBlockStatesTag instanceof CompoundBinaryTag blockStatesTag) {
|
||||
|
||||
// parse palette
|
||||
List<CompoundTag> paletteEntries = blockStatesTag.getList("palette", CompoundTag.class);
|
||||
ListBinaryTag paletteEntries = blockStatesTag.getList("palette");
|
||||
int paletteSize = paletteEntries.size();
|
||||
if (paletteSize == 0) {
|
||||
continue;
|
||||
}
|
||||
BlockState[] palette = new BlockState[paletteSize];
|
||||
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"));
|
||||
if (type == null) {
|
||||
throw new InvalidFormatException("Invalid block type: " + paletteEntry.getString("Name"));
|
||||
}
|
||||
BlockState blockState = type.getDefaultState();
|
||||
if (paletteEntry.containsKey("Properties")) {
|
||||
CompoundTag properties = NBTUtils.getChildTag(paletteEntry.getValue(), "Properties", CompoundTag.class);
|
||||
BinaryTag propertiesTag = paletteEntry.get("Properties");
|
||||
if (propertiesTag instanceof CompoundBinaryTag properties) {
|
||||
for (Property<?> property : blockState.getStates().keySet()) {
|
||||
if (properties.containsKey(property.getName())) {
|
||||
String value = properties.getString(property.getName());
|
||||
String value;
|
||||
if (!(value = properties.getString(property.getName())).isEmpty()) {
|
||||
try {
|
||||
blockState = getBlockStateWith(blockState, property, value);
|
||||
} 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
|
||||
long[] blockStatesSerialized = NBTUtils.getChildTag(blockStatesTag.getValue(), "data", LongArrayTag.class).getValue();
|
||||
long[] blockStatesSerialized = blockStatesTag.getLongArray("data");
|
||||
|
||||
BlockState[] chunkSectionBlocks = new BlockState[16 * 16 * 16];
|
||||
blocks.put(y, chunkSectionBlocks);
|
||||
@ -125,6 +168,7 @@ public class AnvilChunk18 implements Chunk {
|
||||
readBlockStates(palette, blockStatesSerialized, chunkSectionBlocks);
|
||||
}
|
||||
}
|
||||
//FAWE end
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
private void populateTileEntities() throws DataException {
|
||||
//FAWE start - CBT
|
||||
tileEntities = new HashMap<>();
|
||||
if (!rootTag.getValue().containsKey("block_entities")) {
|
||||
if (!(rootTag.get("block_entities") instanceof ListBinaryTag tags)) {
|
||||
return;
|
||||
}
|
||||
List<Tag> tags = NBTUtils.getChildTag(rootTag.getValue(),
|
||||
"block_entities", ListTag.class).getValue();
|
||||
|
||||
for (Tag tag : tags) {
|
||||
if (!(tag instanceof CompoundTag t)) {
|
||||
for (BinaryTag tag : tags) {
|
||||
if (!(tag instanceof CompoundBinaryTag t)) {
|
||||
throw new InvalidFormatException("CompoundTag expected in block_entities");
|
||||
}
|
||||
|
||||
Map<String, Tag> values = new HashMap<>(t.getValue());
|
||||
int x = ((IntTag) values.get("x")).getValue();
|
||||
int y = ((IntTag) values.get("y")).getValue();
|
||||
int z = ((IntTag) values.get("z")).getValue();
|
||||
int x = t.getInt("x");
|
||||
int y = t.getInt("y");
|
||||
int z = t.getInt("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
|
||||
*/
|
||||
@Nullable
|
||||
private CompoundTag getBlockTileEntity(BlockVector3 position) throws DataException {
|
||||
private CompoundBinaryTag getBlockTileEntity(BlockVector3 position) throws DataException {
|
||||
//FAWE start - CBT
|
||||
if (tileEntities == null) {
|
||||
populateTileEntities();
|
||||
}
|
||||
|
||||
Map<String, Tag> values = tileEntities.get(position);
|
||||
if (values == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new CompoundTag(values);
|
||||
return tileEntities.get(position);
|
||||
//FAWE end
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBlock getBlock(BlockVector3 position) throws DataException {
|
||||
int x = position.getX() - rootX * 16;
|
||||
int x = position.getX() & 15;
|
||||
int y = position.getY();
|
||||
int z = position.getZ() - rootZ * 16;
|
||||
int z = position.getZ() & 15;
|
||||
|
||||
int section = y >> 4;
|
||||
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)];
|
||||
|
||||
CompoundTag tileEntity = getBlockTileEntity(position);
|
||||
CompoundBinaryTag tileEntity = getBlockTileEntity(position);
|
||||
|
||||
if (tileEntity != null) {
|
||||
return state.toBaseBlock(tileEntity);
|
||||
@ -215,4 +254,110 @@ public class AnvilChunk18 implements Chunk {
|
||||
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)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -32,28 +32,56 @@ public class PackedIntArrayReader {
|
||||
}
|
||||
}
|
||||
|
||||
private static final int SIZE = 4096;
|
||||
|
||||
private final long[] data;
|
||||
private final int elementBits;
|
||||
private final long maxValue;
|
||||
private final int elementsPerLong;
|
||||
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) {
|
||||
this.data = data;
|
||||
this.storedSize = 4096;
|
||||
this.elementBits = data.length * 64 / 4096;
|
||||
this.maxValue = (1L << elementBits) - 1L;
|
||||
this.elementsPerLong = 64 / elementBits;
|
||||
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) {
|
||||
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) {
|
||||
checkElementIndex(index, SIZE);
|
||||
checkElementIndex(index, storedSize);
|
||||
int i = this.adjustIndex(index);
|
||||
long l = this.data[i];
|
||||
int j = (index - i * this.elementsPerLong) * this.elementBits;
|
||||
|
@ -73,28 +73,8 @@ public class ChunkStoreHelper {
|
||||
* @throws DataException if the rootTag is not valid chunk data
|
||||
*/
|
||||
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
|
||||
return getChunk(rootTag, () -> null);
|
||||
//FAWE end
|
||||
}
|
||||
|
||||
/**
|
||||
@ -105,9 +85,32 @@ public class ChunkStoreHelper {
|
||||
* location
|
||||
* @return a Chunk implementation
|
||||
* @throws DataException if the rootTag is not valid chunk data
|
||||
* @since TODO
|
||||
*/
|
||||
public static Chunk getChunk(CompoundTag rootTag, Supplier<CompoundTag> entitiesTag) throws DataException {
|
||||
//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();
|
||||
CompoundTag tag = null;
|
||||
|
||||
@ -130,10 +133,6 @@ public class ChunkStoreHelper {
|
||||
throw new ChunkStoreException("Missing root 'Level' tag");
|
||||
}
|
||||
|
||||
int dataVersion = rootTag.getInt("DataVersion");
|
||||
if (dataVersion == 0) {
|
||||
dataVersion = -1;
|
||||
}
|
||||
//FAWE start - biome and entity restore
|
||||
if (dataVersion >= Constants.DATA_VERSION_MC_1_17) {
|
||||
return new AnvilChunk17(tag, entitiesTag);
|
||||
|
@ -80,14 +80,13 @@ public class TrueZipMcRegionChunkStore extends McRegionChunkStore {
|
||||
protected InputStream getInputStream(String name, String worldName, @Nullable String folderOverride) throws IOException,
|
||||
DataException {
|
||||
// Detect subfolder for the world's files
|
||||
if (folderOverride != null) {
|
||||
if (!folderOverride.isEmpty()) {
|
||||
name = folderOverride + "/" + name;
|
||||
}
|
||||
} else if (folder != null) {
|
||||
//FAWE end
|
||||
if (folder != null) {
|
||||
if (!folder.isEmpty()) {
|
||||
//FAWE end
|
||||
name = folder + "/" + name;
|
||||
if (folderOverride != null) {
|
||||
name = name.replace("region", folderOverride);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Pattern pattern = Pattern.compile(".*\\.mc[ra]$");
|
||||
@ -105,6 +104,12 @@ public class TrueZipMcRegionChunkStore extends McRegionChunkStore {
|
||||
endIndex = entryName.lastIndexOf('\\');
|
||||
}
|
||||
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")) {
|
||||
continue;
|
||||
}
|
||||
|
@ -78,14 +78,13 @@ public class ZippedMcRegionChunkStore extends McRegionChunkStore {
|
||||
protected InputStream getInputStream(String name, String worldName, @Nullable String folderOverride) throws IOException,
|
||||
DataException {
|
||||
// Detect subfolder for the world's files
|
||||
if (folderOverride != null) {
|
||||
if (!folderOverride.isEmpty()) {
|
||||
name = folderOverride + "/" + name;
|
||||
}
|
||||
} else if (folder != null) {
|
||||
if (folder != null) {
|
||||
if (!folder.isEmpty()) {
|
||||
//FAWE end
|
||||
name = folder + "/" + name;
|
||||
if (folderOverride != null) {
|
||||
name = name.replace("region", folderOverride);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Pattern pattern = Pattern.compile(".*\\.mc[ra]$");
|
||||
@ -101,6 +100,12 @@ public class ZippedMcRegionChunkStore extends McRegionChunkStore {
|
||||
endIndex = entryName.lastIndexOf('\\');
|
||||
}
|
||||
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")) {
|
||||
continue;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user