package com.hypixel.hytale.server.core.prefab.selection.buffer;
import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap;
import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockMigration;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import com.hypixel.hytale.server.core.asset.type.fluid.Fluid;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.prefab.config.SelectionPrefabSerializer;
import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.PrefabBuffer;
import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.PrefabBufferBlockEntry;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.bson.BsonArray;
import org.bson.BsonDocument;
import org.bson.BsonInt32;
import org.bson.BsonValue;
public class BsonPrefabBufferDeserializer implements PrefabBufferDeserializer<BsonDocument> {
public static final BsonPrefabBufferDeserializer INSTANCE = new BsonPrefabBufferDeserializer();
public static final BsonInt32 LEGACY_BLOCK_ID_VERSION = new BsonInt32(8);
private static final BsonInt32 DEFAULT_SUPPORT_VALUE = new BsonInt32(0);
private static final BsonInt32 DEFAULT_FILLER_VALUE = new BsonInt32(0);
private static final BsonInt32 DEFAULT_ROTATION_VALUE = new BsonInt32(0);
public BsonPrefabBufferDeserializer() {
}
@Nonnull
public PrefabBuffer deserialize(Path path, @Nonnull BsonDocument document) {
BsonValue versionValue = document.get("version");
int version = versionValue != null ? versionValue.asInt32().getValue() : -1;
if (version > 8) {
throw new IllegalArgumentException("Prefab version is too new: " + version + " by expected 8");
} else {
int worldVersion = version < 4 ? SelectionPrefabSerializer.readWorldVersion(document) : 0;
BsonValue entityVersionValue = document.get("entityVersion");
int entityVersion = entityVersionValue != null ? entityVersionValue.asInt32().getValue() : 0;
if (version < 1) {
throw new IllegalArgumentException("Prefab version " + version + " is no longer supported. Please re-save the prefab.");
} else {
Vector3i anchor = new Vector3i();
anchor.x = document.getInt32("anchorX").getValue();
anchor.y = document.getInt32("anchorY").getValue();
anchor.z = document.getInt32("anchorZ").getValue();
int blockIdVersion = document.getInt32("blockIdVersion", LEGACY_BLOCK_ID_VERSION).getValue();
Function<String, String> blockMigration = null;
Map<Integer, BlockMigration> blockMigrationMap = BlockMigration.getAssetMap().getAssetMap();
int v = blockIdVersion;
for(BlockMigration migration = (BlockMigration)blockMigrationMap.get(blockIdVersion); migration != null; migration = (BlockMigration)blockMigrationMap.get(v)) {
if (blockMigration == null) {
Objects.requireNonNull(migration);
blockMigration = migration::getMigration;
} else {
Objects.requireNonNull(migration);
blockMigration = blockMigration.andThen(migration::getMigration);
}
++v;
}
Int2ObjectOpenHashMap<Int2ObjectMap<PrefabBufferBlockEntry>> columnMap = new Int2ObjectOpenHashMap<Int2ObjectMap<PrefabBufferBlockEntry>>();
PrefabBuffer.Builder builder = PrefabBuffer.newBuilder();
builder.setAnchor(anchor);
BsonValue blocksValue = document.get("blocks");
if (blocksValue != null) {
BlockTypeAssetMap<String, BlockType> assetMap = BlockType.getAssetMap();
for(BsonValue blockValue : blocksValue.asArray()) {
BsonDocument blockDocument = blockValue.asDocument();
int realX = blockDocument.getInt32("x").getValue();
int realY = blockDocument.getInt32("y").getValue();
int realZ = blockDocument.getInt32("z").getValue();
int x = realX - anchor.x;
int y = realY - anchor.y;
int z = realZ - anchor.z;
if (-32768 > x || x > 32767) {
throw new IllegalArgumentException("Violation X: Short.MIN_VALUE < " + x + " < Short.MAX_VALUE");
}
if (-32768 > y || y > 32767) {
throw new IllegalArgumentException("Violation Y: Short.MIN_VALUE < " + y + " < Short.MAX_VALUE");
}
if (-32768 > z || z > 32767) {
throw new IllegalArgumentException("Violation Z: Short.MIN_VALUE < " + z + " < Short.MAX_VALUE");
}
PrefabBufferBlockEntry blockEntry = builder.newBlockEntry(y);
try {
deserializeBlockType(blockEntry, blockDocument, assetMap, blockMigration);
} catch (Throwable t) {
throw new IllegalStateException("Failed to load block type for " + String.valueOf(path) + " at " + realX + ", " + realY + ", " + realZ, t);
}
deserializeState(blockEntry, blockDocument, version, worldVersion);
blockEntry.supportValue = (byte)blockDocument.getInt32("support", DEFAULT_SUPPORT_VALUE).getValue();
blockEntry.filler = blockDocument.getInt32("filler", DEFAULT_FILLER_VALUE).getValue();
blockEntry.rotation = blockDocument.getInt32("rotation", DEFAULT_ROTATION_VALUE).getValue();
int columnIndex = MathUtil.packInt(x, z);
Int2ObjectMap<PrefabBufferBlockEntry> column = columnMap.get(columnIndex);
if (column == null) {
columnMap.put(columnIndex, column = new Int2ObjectOpenHashMap<PrefabBufferBlockEntry>());
}
PrefabBufferBlockEntry existing = column.putIfAbsent(y, blockEntry);
if (existing != null) {
throw new IllegalStateException("Block is already present in column. Given: " + realX + ", " + realY + ", " + realZ + ", " + blockEntry.blockTypeKey + " - Existing: " + existing.y + ", " + existing.blockTypeKey);
}
}
}
BsonValue fluidsValue = document.get("fluids");
if (fluidsValue != null) {
IndexedLookupTableAssetMap<String, Fluid> assetMap = Fluid.getAssetMap();
for(BsonValue fluidValue : fluidsValue.asArray()) {
BsonDocument fluidDocument = fluidValue.asDocument();
int realX = fluidDocument.getInt32("x").getValue();
int realY = fluidDocument.getInt32("y").getValue();
int realZ = fluidDocument.getInt32("z").getValue();
int x = realX - anchor.x;
int y = realY - anchor.y;
int z = realZ - anchor.z;
if (-32768 > x || x > 32767) {
throw new IllegalArgumentException("Violation X: Short.MIN_VALUE < " + x + " < Short.MAX_VALUE");
}
if (-32768 > y || y > 32767) {
throw new IllegalArgumentException("Violation Y: Short.MIN_VALUE < " + y + " < Short.MAX_VALUE");
}
if (-32768 > z || z > 32767) {
throw new IllegalArgumentException("Violation Z: Short.MIN_VALUE < " + z + " < Short.MAX_VALUE");
}
int columnIndex = MathUtil.packInt(x, z);
Int2ObjectMap<PrefabBufferBlockEntry> column = columnMap.get(columnIndex);
if (column == null) {
columnMap.put(columnIndex, column = new Int2ObjectOpenHashMap<PrefabBufferBlockEntry>());
}
Objects.requireNonNull(builder);
PrefabBufferBlockEntry entry = (PrefabBufferBlockEntry)column.computeIfAbsent(y, builder::newBlockEntry);
String fluidName = fluidDocument.getString("name").getValue();
entry.fluidId = Fluid.getFluidIdOrUnknown(fluidName, "Unknown fluid '%s'", fluidName);
entry.fluidLevel = (byte)fluidDocument.getInt32("level").getValue();
}
}
Int2ObjectOpenHashMap<List<Holder<EntityStore>>> entityMap = deserializeEntityHolders(document, anchor, version, entityVersion);
columnMap.int2ObjectEntrySet().fastForEach((entryx) -> {
int columnIndex = entryx.getIntKey();
int x = MathUtil.unpackLeft(columnIndex);
int z = MathUtil.unpackRight(columnIndex);
Int2ObjectMap<PrefabBufferBlockEntry> columnBlockMap = (Int2ObjectMap)entryx.getValue();
PrefabBufferBlockEntry[] entries = (PrefabBufferBlockEntry[])columnBlockMap.values().toArray((x$0) -> new PrefabBufferBlockEntry[x$0]);
Arrays.sort(entries, Comparator.comparingInt((o) -> o.y));
List<Holder<EntityStore>> entityColumn = entityMap.remove(columnIndex);
Holder[] entityArray = entityColumn != null && !entityColumn.isEmpty() ? (Holder[])entityColumn.toArray((x$0) -> new Holder[x$0]) : null;
builder.addColumn(x, z, entries, entityArray);
});
entityMap.int2ObjectEntrySet().fastForEach((entryx) -> {
int columnIndex = entryx.getIntKey();
int x = MathUtil.unpackLeft(columnIndex);
int z = MathUtil.unpackRight(columnIndex);
List<Holder<EntityStore>> entityColumn = (List)entryx.getValue();
Holder[] entityArray = !entityColumn.isEmpty() ? (Holder[])entityColumn.toArray((x$0) -> new Holder[x$0]) : null;
if (entityArray != null) {
builder.addColumn(x, z, PrefabBufferBlockEntry.EMPTY_ARRAY, entityArray);
}
});
return builder.build();
}
}
}
private static void deserializeBlockType(@Nonnull PrefabBufferBlockEntry blockEntry, @Nonnull BsonDocument blockDocument, @Nonnull BlockTypeAssetMap<String, BlockType> assetMap, @Nullable Function<String, String> blockMigration) {
String blockType = blockDocument.getString("name").getValue();
int idx = blockType.indexOf(37);
String blockTypeStr;
if (idx != -1) {
blockTypeStr = blockType.substring(idx + 1);
} else {
blockTypeStr = blockType;
}
blockEntry.blockTypeKey = blockTypeStr;
if (blockMigration != null) {
blockEntry.blockTypeKey = (String)blockMigration.apply(blockEntry.blockTypeKey);
}
blockEntry.blockId = BlockType.getBlockIdOrUnknown(assetMap, blockEntry.blockTypeKey, "Failed to find block. Given %s", blockTypeStr);
if (idx != -1) {
String chanceString = blockType.substring(0, idx);
float chancePercent = Float.parseFloat(chanceString);
if (chancePercent < 0.0F) {
throw new IllegalArgumentException("Chance is smaller than 0%. Given: " + chancePercent);
}
if (chancePercent > 100.0F) {
throw new IllegalArgumentException("Chance is larger than 100%. Given: " + chancePercent);
}
blockEntry.chance = chancePercent / 100.0F;
}
}
private static void deserializeState(@Nonnull PrefabBufferBlockEntry blockEntry, @Nonnull BsonDocument blockDocument, int version, int worldVersion) {
if (version <= 2) {
BsonValue stateValue = blockDocument.get("state");
if (stateValue != null) {
blockEntry.state = SelectionPrefabSerializer.legacyStateDecode(stateValue.asDocument());
}
} else {
BsonValue stateValue = blockDocument.get("components");
if (stateValue != null) {
if (version < 4) {
blockEntry.state = ChunkStore.REGISTRY.deserialize(stateValue.asDocument(), worldVersion);
} else {
blockEntry.state = ChunkStore.REGISTRY.deserialize(stateValue.asDocument());
}
}
}
}
@Nonnull
private static Int2ObjectOpenHashMap<List<Holder<EntityStore>>> deserializeEntityHolders(@Nonnull BsonDocument document, @Nonnull Vector3i anchor, int version, int entityVersion) {
BsonValue entitiesValue = document.get("entities");
Int2ObjectOpenHashMap<List<Holder<EntityStore>>> entityMap = new Int2ObjectOpenHashMap<List<Holder<EntityStore>>>();
if (entitiesValue == null) {
return entityMap;
} else {
BsonArray entitiesArray = entitiesValue.asArray();
int i = 0;
for(int size = entitiesArray.size(); i < size; ++i) {
BsonDocument entityDocument = entitiesArray.get(i).asDocument();
try {
Holder<EntityStore> entityHolder;
if (version <= 1) {
entityHolder = SelectionPrefabSerializer.legacyEntityDecode(entityDocument, entityVersion);
} else {
entityHolder = EntityStore.REGISTRY.deserialize(entityDocument);
}
TransformComponent transformComponent = (TransformComponent)entityHolder.getComponent(TransformComponent.getComponentType());
assert transformComponent != null;
Vector3d position = transformComponent.getPosition();
position.add((double)(-anchor.x), (double)(-anchor.y), (double)(-anchor.z));
int x = MathUtil.floor(position.getX()) & '\uffff';
int z = MathUtil.floor(position.getZ()) & '\uffff';
int columnIndex = MathUtil.packInt(x, z);
List<Holder<EntityStore>> entityColumn = entityMap.get(columnIndex);
if (entityColumn == null) {
entityMap.put(columnIndex, entityColumn = new ObjectArrayList<Holder<EntityStore>>());
}
entityColumn.add(entityHolder);
} catch (Exception e) {
throw new IllegalStateException("Failed to load entity wrapper #" + i + ": " + String.valueOf(entityDocument), e);
}
}
return entityMap;
}
}
}