package com.hypixel.hytale.server.core.prefab.config;
import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap;
import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap;
import com.hypixel.hytale.codec.DirectDecodeCodec;
import com.hypixel.hytale.codec.ExtraInfo;
import com.hypixel.hytale.codec.lookup.ACodecMapCodec;
import com.hypixel.hytale.component.Archetype;
import com.hypixel.hytale.component.Component;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.component.data.unknown.TempUnknownComponent;
import com.hypixel.hytale.component.data.unknown.UnknownComponents;
import com.hypixel.hytale.logger.HytaleLogger;
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.blocktype.config.Rotation;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple;
import com.hypixel.hytale.server.core.asset.type.fluid.Fluid;
import com.hypixel.hytale.server.core.entity.Entity;
import com.hypixel.hytale.server.core.modules.entity.EntityModule;
import com.hypixel.hytale.server.core.prefab.selection.buffer.BsonPrefabBufferDeserializer;
import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.universe.world.meta.BlockState;
import com.hypixel.hytale.server.core.universe.world.meta.BlockStateModule;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.server.core.util.FillerBlockUtil;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.logging.Level;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.bson.BsonArray;
import org.bson.BsonDocument;
import org.bson.BsonInt32;
import org.bson.BsonString;
import org.bson.BsonValue;
public class SelectionPrefabSerializer {
public static final int VERSION = 8;
private static final Comparator<BsonDocument> COMPARE_BLOCK_POSITION = Comparator.comparingInt((doc) -> doc.getInt32("x").getValue()).thenComparingInt((doc) -> doc.getInt32("z").getValue()).thenComparingInt((doc) -> doc.getInt32("y").getValue());
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);
private SelectionPrefabSerializer() {
}
@Nonnull
public static BlockSelection deserialize(@Nonnull BsonDocument doc) {
BsonValue versionValue = doc.get("version");
int version = versionValue != null ? versionValue.asInt32().getValue() : -1;
if (version <= 0) {
throw new IllegalArgumentException("Prefab version is too old: " + version);
} else if (version > 8) {
throw new IllegalArgumentException("Prefab version is too new: " + version + " by expected 8");
} else {
int worldVersion = version < 4 ? readWorldVersion(doc) : 0;
BsonValue entityVersionValue = doc.get("entityVersion");
int entityVersion = entityVersionValue != null ? entityVersionValue.asInt32().getValue() : 0;
int anchorX = doc.getInt32("anchorX").getValue();
int anchorY = doc.getInt32("anchorY").getValue();
int anchorZ = doc.getInt32("anchorZ").getValue();
BlockSelection selection = new BlockSelection();
selection.setAnchor(anchorX, anchorY, anchorZ);
int blockIdVersion = doc.getInt32("blockIdVersion", BsonPrefabBufferDeserializer.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;
}
BsonValue blocksValue = doc.get("blocks");
if (blocksValue != null) {
BlockTypeAssetMap<String, BlockType> assetMap = BlockType.getAssetMap();
BsonArray bsonArray = blocksValue.asArray();
for(int i = 0; i < bsonArray.size(); ++i) {
BsonDocument innerObj = bsonArray.get(i).asDocument();
int x = innerObj.getInt32("x").getValue();
int y = innerObj.getInt32("y").getValue();
int z = innerObj.getInt32("z").getValue();
String blockTypeStr = innerObj.getString("name").getValue();
boolean legacyStripName = false;
if (version <= 4) {
Fluid.ConversionResult result = Fluid.convertBlockToFluid(blockTypeStr);
if (result != null) {
legacyStripName = true;
selection.addFluidAtLocalPos(x, y, z, result.fluidId, result.fluidLevel);
if (result.blockTypeStr == null) {
continue;
}
}
}
int support = 0;
if (version >= 6) {
support = innerObj.getInt32("support", DEFAULT_SUPPORT_VALUE).getValue();
} else if (blockTypeStr.contains("|Deco")) {
legacyStripName = true;
support = 15;
} else if (blockTypeStr.contains("|Support=")) {
legacyStripName = true;
int start = blockTypeStr.indexOf("|Support=") + "|Support=".length();
int end = blockTypeStr.indexOf(124, start);
if (end == -1) {
end = blockTypeStr.length();
}
support = Integer.parseInt(blockTypeStr, start, end, 10);
} else {
support = 0;
}
int filler = 0;
if (version >= 7) {
filler = innerObj.getInt32("filler", DEFAULT_FILLER_VALUE).getValue();
} else if (blockTypeStr.contains("|Filler=")) {
legacyStripName = true;
int start = blockTypeStr.indexOf("|Filler=") + "|Filler=".length();
int firstComma = blockTypeStr.indexOf(44, start);
if (firstComma == -1) {
throw new IllegalArgumentException("Invalid filler metadata! Missing comma");
}
int secondComma = blockTypeStr.indexOf(44, firstComma + 1);
if (secondComma == -1) {
throw new IllegalArgumentException("Invalid filler metadata! Missing second comma");
}
int end = blockTypeStr.indexOf(124, start);
if (end == -1) {
end = blockTypeStr.length();
}
int fillerX = Integer.parseInt(blockTypeStr, start, firstComma, 10);
int fillerY = Integer.parseInt(blockTypeStr, firstComma + 1, secondComma, 10);
int fillerZ = Integer.parseInt(blockTypeStr, secondComma + 1, end, 10);
filler = FillerBlockUtil.pack(fillerX, fillerY, fillerZ);
} else {
filler = 0;
}
int rotation = 0;
if (version >= 8) {
rotation = innerObj.getInt32("rotation", DEFAULT_ROTATION_VALUE).getValue();
} else {
Rotation yaw = Rotation.None;
Rotation pitch = Rotation.None;
Rotation roll = Rotation.None;
if (blockTypeStr.contains("|Yaw=")) {
legacyStripName = true;
int start = blockTypeStr.indexOf("|Yaw=") + "|Yaw=".length();
int end = blockTypeStr.indexOf(124, start);
if (end == -1) {
end = blockTypeStr.length();
}
yaw = Rotation.ofDegrees(Integer.parseInt(blockTypeStr, start, end, 10));
}
if (blockTypeStr.contains("|Pitch=")) {
legacyStripName = true;
int start = blockTypeStr.indexOf("|Pitch=") + "|Pitch=".length();
int end = blockTypeStr.indexOf(124, start);
if (end == -1) {
end = blockTypeStr.length();
}
pitch = Rotation.ofDegrees(Integer.parseInt(blockTypeStr, start, end, 10));
}
if (blockTypeStr.contains("|Roll=")) {
legacyStripName = true;
int start = blockTypeStr.indexOf("|Roll=") + "|Roll=".length();
int end = blockTypeStr.indexOf(124, start);
if (end == -1) {
end = blockTypeStr.length();
}
pitch = Rotation.ofDegrees(Integer.parseInt(blockTypeStr, start, end, 10));
}
rotation = RotationTuple.index(yaw, pitch, roll);
}
if (legacyStripName) {
int endOfName = blockTypeStr.indexOf(124);
if (endOfName != -1) {
blockTypeStr = blockTypeStr.substring(0, endOfName);
}
}
String blockTypeKey = blockTypeStr;
if (blockMigration != null) {
blockTypeKey = (String)blockMigration.apply(blockTypeStr);
}
int blockId = BlockType.getBlockIdOrUnknown(assetMap, blockTypeKey, "Failed to find block '%s' in unknown legacy prefab!", blockTypeStr);
Holder<ChunkStore> wrapper = null;
if (version <= 2) {
BsonValue stateValue = innerObj.get("state");
if (stateValue != null) {
wrapper = legacyStateDecode(stateValue.asDocument());
}
} else {
BsonValue stateValue = innerObj.get("components");
if (stateValue != null) {
if (version < 4) {
wrapper = ChunkStore.REGISTRY.deserialize(stateValue.asDocument(), worldVersion);
} else {
wrapper = ChunkStore.REGISTRY.deserialize(stateValue.asDocument());
}
}
}
selection.addBlockAtLocalPos(x, y, z, blockId, rotation, filler, support, wrapper);
}
}
BsonValue fluidsValue = doc.get("fluids");
if (fluidsValue != null) {
IndexedLookupTableAssetMap<String, Fluid> assetMap = Fluid.getAssetMap();
BsonArray bsonArray = fluidsValue.asArray();
for(int i = 0; i < bsonArray.size(); ++i) {
BsonDocument innerObj = bsonArray.get(i).asDocument();
int x = innerObj.getInt32("x").getValue();
int y = innerObj.getInt32("y").getValue();
int z = innerObj.getInt32("z").getValue();
String fluidName = innerObj.getString("name").getValue();
int fluidId = Fluid.getFluidIdOrUnknown(assetMap, fluidName, "Failed to find fluid '%s' in unknown legacy prefab!", fluidName);
byte fluidLevel = (byte)innerObj.getInt32("level").getValue();
selection.addFluidAtLocalPos(x, y, z, fluidId, fluidLevel);
}
}
BsonValue entitiesValues = doc.get("entities");
if (entitiesValues != null) {
BsonArray entities = entitiesValues.asArray();
for(int i = 0; i < entities.size(); ++i) {
BsonDocument bsonDocument = entities.get(i).asDocument();
if (version <= 1) {
try {
selection.addEntityHolderRaw(legacyEntityDecode(bsonDocument, entityVersion));
} catch (Throwable t) {
((HytaleLogger.Api)HytaleLogger.getLogger().at(Level.WARNING).withCause(t)).log("Exception when loading entity state %s", bsonDocument);
}
} else {
selection.addEntityHolderRaw(EntityStore.REGISTRY.deserialize(bsonDocument));
}
}
}
return selection;
}
}
@Nonnull
public static BsonDocument serialize(@Nonnull BlockSelection prefab) {
Objects.requireNonNull(prefab, "null prefab");
BlockTypeAssetMap<String, BlockType> assetMap = BlockType.getAssetMap();
IndexedLookupTableAssetMap<String, Fluid> fluidMap = Fluid.getAssetMap();
BsonDocument out = new BsonDocument();
out.put((String)"version", (BsonValue)(new BsonInt32(8)));
out.put((String)"blockIdVersion", (BsonValue)(new BsonInt32(BlockMigration.getAssetMap().getAssetCount())));
out.put((String)"anchorX", (BsonValue)(new BsonInt32(prefab.getAnchorX())));
out.put((String)"anchorY", (BsonValue)(new BsonInt32(prefab.getAnchorY())));
out.put((String)"anchorZ", (BsonValue)(new BsonInt32(prefab.getAnchorZ())));
BsonArray contentOut = new BsonArray();
prefab.forEachBlock((x, y, z, block) -> {
BsonDocument innerObj = new BsonDocument();
innerObj.put((String)"x", (BsonValue)(new BsonInt32(x)));
innerObj.put((String)"y", (BsonValue)(new BsonInt32(y)));
innerObj.put((String)"z", (BsonValue)(new BsonInt32(z)));
innerObj.put((String)"name", (BsonValue)(new BsonString(((BlockType)assetMap.getAsset(block.blockId())).getId().toString())));
if (block.holder() != null) {
innerObj.put((String)"components", (BsonValue)ChunkStore.REGISTRY.serialize(block.holder()));
}
if (block.supportValue() != 0) {
innerObj.put((String)"support", (BsonValue)(new BsonInt32(block.supportValue())));
}
if (block.filler() != 0) {
innerObj.put((String)"filler", (BsonValue)(new BsonInt32(block.filler())));
}
if (block.rotation() != 0) {
innerObj.put((String)"rotation", (BsonValue)(new BsonInt32(block.rotation())));
}
contentOut.add((BsonValue)innerObj);
});
contentOut.sort((a, b) -> {
BsonDocument aDoc = a.asDocument();
BsonDocument bDoc = b.asDocument();
return COMPARE_BLOCK_POSITION.compare(aDoc, bDoc);
});
out.put((String)"blocks", (BsonValue)contentOut);
BsonArray fluidContentOut = new BsonArray();
prefab.forEachFluid((x, y, z, fluid, level) -> {
BsonDocument innerObj = new BsonDocument();
innerObj.put((String)"x", (BsonValue)(new BsonInt32(x)));
innerObj.put((String)"y", (BsonValue)(new BsonInt32(y)));
innerObj.put((String)"z", (BsonValue)(new BsonInt32(z)));
innerObj.put((String)"name", (BsonValue)(new BsonString(((Fluid)fluidMap.getAsset(fluid)).getId())));
innerObj.put((String)"level", (BsonValue)(new BsonInt32(level)));
fluidContentOut.add((BsonValue)innerObj);
});
fluidContentOut.sort((a, b) -> {
BsonDocument aDoc = a.asDocument();
BsonDocument bDoc = b.asDocument();
return COMPARE_BLOCK_POSITION.compare(aDoc, bDoc);
});
if (!fluidContentOut.isEmpty()) {
out.put((String)"fluids", (BsonValue)fluidContentOut);
}
List<BsonDocument> entityList = new ArrayList();
prefab.forEachEntity((holder) -> entityList.add(EntityStore.REGISTRY.serialize(holder)));
if (!entityList.isEmpty()) {
BsonArray entities = new BsonArray();
Objects.requireNonNull(entities);
entityList.forEach(entities::add);
out.put((String)"entities", (BsonValue)entities);
}
return out;
}
public static int readWorldVersion(@Nonnull BsonDocument document) {
int worldVersion;
if (document.containsKey("worldVersion")) {
worldVersion = document.getInt32("worldVersion").getValue();
} else if (document.containsKey("worldver")) {
worldVersion = document.getInt32("worldver").getValue();
} else {
worldVersion = 5;
}
if (worldVersion == 18553) {
throw new IllegalArgumentException("WorldChunk version old format! Update!");
} else if (worldVersion > 23) {
throw new IllegalArgumentException("WorldChunk version is newer than we understand! Version: " + worldVersion + ", Latest Version: 23");
} else {
return worldVersion;
}
}
@Nullable
public static Holder<EntityStore> legacyEntityDecode(@Nonnull BsonDocument document, int version) {
String entityTypeStr = document.getString("EntityType").getValue();
Class<? extends Entity> entityType = EntityModule.get().getClass(entityTypeStr);
if (entityType == null) {
UnknownComponents unknownComponents = new UnknownComponents();
unknownComponents.addComponent(entityTypeStr, new TempUnknownComponent(document));
return EntityStore.REGISTRY.newHolder(Archetype.of(EntityStore.REGISTRY.getUnknownComponentType()), new Component[]{unknownComponents});
} else {
Function<World, ? extends Entity> constructor = EntityModule.get().getConstructor(entityType);
if (constructor == null) {
return null;
} else {
DirectDecodeCodec<? extends Entity> codec = EntityModule.get().<Entity>getCodec(entityType);
Objects.requireNonNull(codec, "Unable to create entity because there is no associated codec");
Entity entity = (Entity)constructor.apply((Object)null);
codec.decode(document, entity, new ExtraInfo(version));
return entity.toHolder();
}
}
}
@Nonnull
public static Holder<ChunkStore> legacyStateDecode(@Nonnull BsonDocument document) {
ExtraInfo extraInto = (ExtraInfo)ExtraInfo.THREAD_LOCAL.get();
String type = BlockState.TYPE_STRUCTURE.getNow(document, extraInto);
Class<? extends BlockState> blockStateClass = BlockState.CODEC.getClassFor(type);
if (blockStateClass != null) {
try {
BlockState t = (BlockState)BlockState.CODEC.decode(document, extraInto);
Holder<ChunkStore> holder = ChunkStore.REGISTRY.newHolder();
ComponentType<ChunkStore, ? extends BlockState> componentType = BlockStateModule.get().getComponentType(blockStateClass);
if (componentType == null) {
throw new IllegalArgumentException("Unable to find component type for: " + String.valueOf(blockStateClass));
}
holder.addComponent(componentType, t);
return holder;
} catch (ACodecMapCodec.UnknownIdException var7) {
}
}
Holder<ChunkStore> holder = ChunkStore.REGISTRY.newHolder();
UnknownComponents<ChunkStore> unknownComponents = new UnknownComponents<ChunkStore>();
unknownComponents.addComponent(type, new TempUnknownComponent(document));
holder.addComponent(ChunkStore.REGISTRY.getUnknownComponentType(), unknownComponents);
return holder;
}
}