// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.server.core.prefab.selection.buffer.impl;

import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.IntCollection;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import com.hypixel.hytale.server.core.prefab.selection.buffer.PrefabBufferCall;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple;
import java.util.logging.Level;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.server.core.modules.prefabspawner.PrefabSpawnerState;
import com.hypixel.hytale.server.core.universe.world.meta.BlockStateModule;
import com.hypixel.hytale.server.core.util.io.ByteBufUtil;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import io.netty.buffer.Unpooled;
import java.util.List;
import com.hypixel.hytale.server.core.prefab.PrefabRotation;
import com.hypixel.hytale.server.core.prefab.PrefabWeights;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.component.Holder;
import javax.annotation.Nullable;
import io.netty.buffer.ByteBuf;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import javax.annotation.Nonnull;
import com.hypixel.hytale.math.vector.Vector3i;

public class PrefabBuffer
{
    public static final float DEFAULT_CHANCE = 1.0f;
    @Nonnull
    private final Vector3i anchor;
    @Nonnull
    private final Vector3i min;
    @Nonnull
    private final Vector3i max;
    @Nonnull
    private final Int2ObjectMap<PrefabBufferColumn> columns;
    @Nonnull
    private final ChildPrefab[] childPrefabs;
    @Nullable
    private ByteBuf buf;
    
    private PrefabBuffer(@Nonnull final ByteBuf buf, @Nonnull final Vector3i anchor, @Nonnull final Vector3i min, @Nonnull final Vector3i max, @Nonnull final Int2ObjectMap<PrefabBufferColumn> columns, @Nonnull final ChildPrefab[] childPrefabs) {
        this.buf = buf;
        this.anchor = anchor;
        this.min = min;
        this.max = max;
        this.columns = columns;
        this.childPrefabs = childPrefabs;
    }
    
    @Nonnull
    public static Builder newBuilder() {
        return new Builder();
    }
    
    public int getAnchorX() {
        return this.anchor.getX();
    }
    
    public int getAnchorY() {
        return this.anchor.getY();
    }
    
    public int getAnchorZ() {
        return this.anchor.getZ();
    }
    
    @Nonnull
    public PrefabBufferAccessor newAccess() {
        this.checkReleased();
        return new PrefabBufferAccessor(this);
    }
    
    public void release() {
        this.checkReleased();
        this.buf.release();
        this.buf = null;
    }
    
    private void checkReleased() {
        if (this.buf == null) {
            throw new IllegalStateException("PrefabBuffer has already been released!");
        }
    }
    
    public interface BlockMaskConstants
    {
        public static final int ID_IS_BYTE = 1;
        public static final int ID_IS_SHORT = 2;
        public static final int ID_IS_INT = 3;
        public static final int ID_MASK = 3;
        public static final int HAS_CHANCE = 4;
        public static final int OFFSET_IS_BYTE = 8;
        public static final int OFFSET_IS_SHORT = 16;
        public static final int OFFSET_IS_INT = 24;
        public static final int OFFSET_MASK = 24;
        public static final int HAS_COMPONENTS = 32;
        public static final int FLUID_IS_BYTE = 64;
        public static final int FLUID_IS_SHORT = 128;
        public static final int FLUID_IS_INT = 192;
        public static final int FLUID_MASK = 192;
        public static final int SUPPORT_MASK = 3840;
        public static final int SUPPORT_OFFSET = 8;
        public static final int HAS_FILLER = 4096;
        public static final int HAS_ROTATION = 8192;
        
        default int getBlockMask(final int blockBytes, final int fluidBytes, final boolean chance, final int offsetBytes, @Nullable final Holder<ChunkStore> holder, final byte supportValue, final int rotation, final int filler) {
            int mask = 0;
            switch (blockBytes) {
                case 0: {
                    break;
                }
                case 1: {
                    mask |= 0x1;
                    break;
                }
                case 2: {
                    mask |= 0x2;
                    break;
                }
                case 4: {
                    mask |= 0x3;
                    break;
                }
                default: {
                    throw new IllegalArgumentException("Unsupported amount of bytes for blocks (0, 1, 2, 4). Given: " + blockBytes);
                }
            }
            if (chance) {
                mask |= 0x4;
            }
            switch (offsetBytes) {
                case 0: {
                    break;
                }
                case 1: {
                    mask |= 0x8;
                    break;
                }
                case 2: {
                    mask |= 0x10;
                    break;
                }
                case 4: {
                    mask |= 0x18;
                    break;
                }
                default: {
                    throw new IllegalArgumentException("Unsupported amount of bytes for offset (0, 1, 2, 4). Given: " + offsetBytes);
                }
            }
            if (holder != null) {
                mask |= 0x20;
            }
            switch (fluidBytes) {
                case 0: {
                    break;
                }
                case 1: {
                    mask |= 0x40;
                    break;
                }
                case 2: {
                    mask |= 0x80;
                    break;
                }
                case 4: {
                    mask |= 0xC0;
                    break;
                }
                default: {
                    throw new IllegalArgumentException("Unsupported amount of bytes for fluids (0, 1, 2, 4). Given: " + fluidBytes);
                }
            }
            mask |= (supportValue << 8 & 0xF00);
            if (filler != 0) {
                mask |= 0x1000;
            }
            if (rotation != 0) {
                mask |= 0x2000;
            }
            return mask;
        }
        
        default int getSkipBytes(final int mask) {
            int bytes = 0;
            bytes += getBlockBytes(mask);
            bytes += getOffsetBytes(mask);
            if (hasChance(mask)) {
                bytes += 4;
            }
            bytes += getFluidBytes(mask);
            if (hasFiller(mask)) {
                bytes += 2;
            }
            if (hasRotation(mask)) {
                ++bytes;
            }
            return bytes;
        }
        
        default boolean hasChance(final int mask) {
            return (mask & 0x4) == 0x4;
        }
        
        default boolean hasFiller(final int mask) {
            return (mask & 0x1000) == 0x1000;
        }
        
        default boolean hasRotation(final int mask) {
            return (mask & 0x2000) == 0x2000;
        }
        
        default int getBlockBytes(final int mask) {
            return switch (mask & 0x3) {
                case 1 -> 1;
                case 2 -> 2;
                case 3 -> 4;
                default -> 0;
            };
        }
        
        default int getOffsetBytes(final int mask) {
            return switch (mask & 0x18) {
                case 8 -> 1;
                case 16 -> 2;
                case 24 -> 4;
                default -> 0;
            };
        }
        
        default int getFluidBytes(final int mask) {
            return switch (mask & 0xC0) {
                case 64 -> 2;
                case 128 -> 3;
                case 192 -> 5;
                default -> 0;
            };
        }
        
        default int getSupportValue(final int mask) {
            return (mask & 0xF00) >> 8;
        }
        
        default boolean hasComponents(final int mask) {
            return (mask & 0x20) == 0x20;
        }
    }
    
    public static class ChildPrefab
    {
        private final int x;
        private final int y;
        private final int z;
        @Nonnull
        private final String path;
        private final boolean fitHeightmap;
        private final boolean inheritSeed;
        private final boolean inheritHeightCondition;
        @Nonnull
        private final PrefabWeights weights;
        @Nonnull
        private final PrefabRotation rotation;
        
        private ChildPrefab(final int x, final int y, final int z, @Nonnull final String path, final boolean fitHeightmap, final boolean inheritSeed, final boolean inheritHeightCondition, @Nonnull final PrefabWeights weights, @Nonnull final PrefabRotation rotation) {
            this.x = x;
            this.y = y;
            this.z = z;
            this.path = path;
            this.fitHeightmap = fitHeightmap;
            this.inheritSeed = inheritSeed;
            this.inheritHeightCondition = inheritHeightCondition;
            this.weights = weights;
            this.rotation = rotation;
        }
        
        public int getX() {
            return this.x;
        }
        
        public int getY() {
            return this.y;
        }
        
        public int getZ() {
            return this.z;
        }
        
        @Nonnull
        public String getPath() {
            return this.path;
        }
        
        public boolean isFitHeightmap() {
            return this.fitHeightmap;
        }
        
        public boolean isInheritSeed() {
            return this.inheritSeed;
        }
        
        public boolean isInheritHeightCondition() {
            return this.inheritHeightCondition;
        }
        
        @Nonnull
        public PrefabWeights getWeights() {
            return this.weights;
        }
        
        @Nonnull
        public PrefabRotation getRotation() {
            return this.rotation;
        }
    }
    
    public static class Builder
    {
        private final ByteBuf buf;
        @Nonnull
        private final Vector3i min;
        @Nonnull
        private final Vector3i max;
        @Nonnull
        private final Int2ObjectMap<PrefabBufferColumn> columns;
        @Nonnull
        private final List<ChildPrefab> childPrefabs;
        private Vector3i anchor;
        
        private Builder() {
            this.buf = Unpooled.buffer();
            this.min = new Vector3i(Vector3i.MAX);
            this.max = new Vector3i(Vector3i.MIN);
            this.columns = new Int2ObjectOpenHashMap<PrefabBufferColumn>();
            this.childPrefabs = new ObjectArrayList<ChildPrefab>(0);
            this.anchor = Vector3i.ZERO;
        }
        
        public void setAnchor(@Nonnull final Vector3i anchor) {
            this.anchor = anchor;
        }
        
        public void addColumn(final int x, final int z, @Nonnull final PrefabBufferBlockEntry[] entries, @Nullable final Holder<EntityStore>[] entityHolders) {
            if (x < -32768) {
                throw new IllegalArgumentException("x is smaller than -32768. Given: " + x);
            }
            if (x > 32767) {
                throw new IllegalArgumentException("x is larger than 32767. Given: " + x);
            }
            if (z < -32768) {
                throw new IllegalArgumentException("z is smaller than -32768. Given: " + z);
            }
            if (z > 32767) {
                throw new IllegalArgumentException("z is larger than 32767. Given: " + z);
            }
            final int columnIndex = MathUtil.packInt((short)x, (short)z);
            if (this.columns.containsKey(columnIndex)) {
                throw new IllegalStateException("Column is already set! Given: " + x + ", " + z);
            }
            final int blockCount = entries.length;
            Int2ObjectOpenHashMap<Holder<ChunkStore>> holderMap = new Int2ObjectOpenHashMap<Holder<ChunkStore>>();
            if (blockCount == 0 && (entityHolders == null || entityHolders.length == 0)) {
                return;
            }
            final int readerIndex = this.buf.writerIndex();
            this.buf.writeInt(blockCount);
            if (blockCount > 0) {
                final int offset = entries[0].y;
                if (offset < this.min.y) {
                    this.min.y = offset;
                }
                this.buf.writeInt(offset - 1);
                int lastY = Integer.MIN_VALUE;
                for (int i = 0; i < blockCount; ++i) {
                    final PrefabBufferBlockEntry entry = entries[i];
                    final int y = entry.y;
                    final int blockId = entry.blockId;
                    final float chance = entry.chance;
                    final Holder<ChunkStore> holder = entry.state;
                    final int fluidId = entry.fluidId;
                    final byte fluidLevel = entry.fluidLevel;
                    if (y <= lastY) {
                        throw new IllegalArgumentException("Y Values are not sequential. " + lastY + " -> " + y);
                    }
                    final int offset2 = (i == 0) ? 0 : (y - lastY);
                    if (offset2 > 65535) {
                        throw new IllegalArgumentException("Offset is larger than 65535. Given: " + offset2);
                    }
                    final boolean hasChance = chance < 1.0f;
                    final int blockBytes = MathUtil.byteCount(blockId);
                    final int offsetBytes = (offset2 == 1) ? 0 : MathUtil.byteCount(offset2);
                    final int fluidBytes = MathUtil.byteCount(fluidId);
                    final int mask = BlockMaskConstants.getBlockMask(blockBytes, fluidBytes, hasChance, offsetBytes, holder, entry.supportValue, entry.rotation, entry.filler);
                    this.buf.writeShort(mask);
                    ByteBufUtil.writeNumber(this.buf, blockBytes, blockId);
                    ByteBufUtil.writeNumber(this.buf, offsetBytes, offset2);
                    if (hasChance) {
                        this.buf.writeFloat(chance);
                    }
                    if (entry.rotation != 0) {
                        this.buf.writeByte(entry.rotation);
                    }
                    if (entry.filler != 0) {
                        this.buf.writeShort(entry.filler);
                    }
                    if (fluidId != 0) {
                        ByteBufUtil.writeNumber(this.buf, fluidBytes, fluidId);
                        this.buf.writeByte(fluidLevel);
                    }
                    if (holder != null) {
                        holderMap.put(y, holder);
                        this.handleBlockComponents(entry.rotation, x, y, z, holder);
                    }
                    lastY = y;
                }
                if (lastY > this.max.y) {
                    this.max.y = lastY;
                }
            }
            if (x < this.min.x) {
                this.min.x = x;
            }
            if (x > this.max.x) {
                this.max.x = x;
            }
            if (z < this.min.z) {
                this.min.z = z;
            }
            if (z > this.max.z) {
                this.max.z = z;
            }
            if (holderMap.isEmpty()) {
                holderMap = null;
            }
            final PrefabBufferColumn column = new PrefabBufferColumn(readerIndex, entityHolders, holderMap);
            this.columns.put(columnIndex, column);
        }
        
        private void handleBlockComponents(final int blockRotation, final int x, final int y, final int z, @Nonnull final Holder<ChunkStore> holder) {
            final ComponentType<ChunkStore, PrefabSpawnerState> componentType = BlockStateModule.get().getComponentType(PrefabSpawnerState.class);
            final PrefabSpawnerState spawnerState = holder.getComponent(componentType);
            if (spawnerState == null) {
                return;
            }
            final String path = spawnerState.getPrefabPath();
            if (path == null) {
                HytaleLogger.getLogger().at(Level.WARNING).log("Prefab spawner at %d, %d, %d is missing prefab path!", x, y, z);
                return;
            }
            final PrefabWeights weights = spawnerState.getPrefabWeights();
            final PrefabRotation rotation = PrefabRotation.fromRotation(RotationTuple.get(blockRotation).yaw());
            this.addChildPrefab(x, y, z, path, spawnerState.isFitHeightmap(), spawnerState.isInheritSeed(), spawnerState.isInheritHeightCondition(), weights, rotation);
        }
        
        public void addChildPrefab(final int x, final int y, final int z, @Nonnull final String path, final boolean fitHeightmap, final boolean inheritSeed, final boolean inheritHeightCondition, @Nullable final PrefabWeights weights, @Nonnull final PrefabRotation rotation) {
            this.childPrefabs.add(new ChildPrefab(x, y, z, path, fitHeightmap, inheritSeed, inheritHeightCondition, weights, rotation));
        }
        
        @Nonnull
        public PrefabBufferBlockEntry newBlockEntry(final int y) {
            return new PrefabBufferBlockEntry(y);
        }
        
        @Nonnull
        public PrefabBuffer build() {
            final ByteBuf buffer = Unpooled.copiedBuffer(this.buf);
            this.buf.release();
            final ChildPrefab[] childPrefabArray = this.childPrefabs.toArray(ChildPrefab[]::new);
            if (this.columns.isEmpty()) {
                this.min.assign(0);
                this.max.assign(0);
            }
            return new PrefabBuffer(buffer, this.anchor, this.min, this.max, this.columns, childPrefabArray);
        }
    }
    
    public static class PrefabBufferAccessor implements IPrefabBuffer
    {
        @Nonnull
        private final PrefabBuffer prefabBuffer;
        @Nullable
        private ByteBuf buffer;
        
        private PrefabBufferAccessor(@Nonnull final PrefabBuffer prefabBuffer) {
            this.buffer = prefabBuffer.buf.retainedDuplicate();
            this.prefabBuffer = prefabBuffer;
        }
        
        @Override
        public int getAnchorX() {
            return this.prefabBuffer.getAnchorX();
        }
        
        @Override
        public int getAnchorY() {
            return this.prefabBuffer.getAnchorY();
        }
        
        @Override
        public int getAnchorZ() {
            return this.prefabBuffer.getAnchorZ();
        }
        
        @Override
        public int getMinX(@Nonnull final PrefabRotation rotation) {
            this.prefabBuffer.checkReleased();
            return Math.min(rotation.getX(this.prefabBuffer.min.getX(), this.prefabBuffer.min.getZ()), rotation.getX(this.prefabBuffer.max.getX(), this.prefabBuffer.max.getZ()));
        }
        
        @Override
        public int getMinY() {
            this.prefabBuffer.checkReleased();
            return this.prefabBuffer.min.getY();
        }
        
        @Override
        public int getMinZ(@Nonnull final PrefabRotation rotation) {
            this.prefabBuffer.checkReleased();
            return Math.min(rotation.getZ(this.prefabBuffer.min.getX(), this.prefabBuffer.min.getZ()), rotation.getZ(this.prefabBuffer.max.getX(), this.prefabBuffer.max.getZ()));
        }
        
        @Override
        public int getMaxX(@Nonnull final PrefabRotation rotation) {
            this.prefabBuffer.checkReleased();
            return Math.max(rotation.getX(this.prefabBuffer.min.getX(), this.prefabBuffer.min.getZ()), rotation.getX(this.prefabBuffer.max.getX(), this.prefabBuffer.max.getZ()));
        }
        
        @Override
        public int getMaxY() {
            this.prefabBuffer.checkReleased();
            return this.prefabBuffer.max.getY();
        }
        
        @Override
        public int getMaxZ(@Nonnull final PrefabRotation rotation) {
            this.prefabBuffer.checkReleased();
            return Math.max(rotation.getZ(this.prefabBuffer.min.getX(), this.prefabBuffer.min.getZ()), rotation.getZ(this.prefabBuffer.max.getX(), this.prefabBuffer.max.getZ()));
        }
        
        @Override
        public int getColumnCount() {
            return this.prefabBuffer.columns.size();
        }
        
        @Nonnull
        @Override
        public ChildPrefab[] getChildPrefabs() {
            return this.prefabBuffer.childPrefabs;
        }
        
        @Override
        public int getMinYAt(@Nonnull final PrefabRotation rotation, final int x, final int z) {
            this.prefabBuffer.checkReleased();
            final int rotatedX = rotation.getX(x, z);
            final int rotatedZ = rotation.getZ(x, z);
            final int columnIndex = MathUtil.packInt(rotatedX, rotatedZ);
            final PrefabBufferColumn columnData = this.prefabBuffer.columns.get(columnIndex);
            if (columnData != null) {
                this.buffer.readerIndex(columnData.getReaderIndex());
                final int blockCount = this.buffer.readInt();
                if (blockCount > 0) {
                    return this.buffer.readInt() + 1;
                }
            }
            return -1;
        }
        
        @Override
        public int getMaxYAt(@Nonnull final PrefabRotation rotation, final int x, final int z) {
            this.prefabBuffer.checkReleased();
            final int rotatedX = rotation.getX(x, z);
            final int rotatedZ = rotation.getZ(x, z);
            final int columnIndex = MathUtil.packInt(rotatedX, rotatedZ);
            final PrefabBufferColumn column = this.prefabBuffer.columns.get(columnIndex);
            if (column == null) {
                return -1;
            }
            this.buffer.readerIndex(column.getReaderIndex());
            final int blockCount = this.buffer.readInt();
            if (blockCount > 0) {
                int y = this.buffer.readInt();
                for (int i = 0; i < blockCount; ++i) {
                    final int mask = this.buffer.readUnsignedShort();
                    if (BlockMaskConstants.getOffsetBytes(mask) > 0) {
                        this.buffer.skipBytes(BlockMaskConstants.getBlockBytes(mask));
                        y += ByteBufUtil.readNumber(this.buffer, BlockMaskConstants.getOffsetBytes(mask));
                        if (BlockMaskConstants.hasChance(mask)) {
                            this.buffer.skipBytes(4);
                        }
                        if (BlockMaskConstants.hasRotation(mask)) {
                            this.buffer.skipBytes(1);
                        }
                        if (BlockMaskConstants.hasFiller(mask)) {
                            this.buffer.skipBytes(2);
                        }
                        this.buffer.skipBytes(BlockMaskConstants.getFluidBytes(mask));
                    }
                    else {
                        this.buffer.skipBytes(BlockMaskConstants.getSkipBytes(mask));
                        ++y;
                    }
                }
                return y;
            }
            return -1;
        }
        
        @Override
        public <T extends PrefabBufferCall> void forEach(@Nonnull final ColumnPredicate<T> columnPredicate, @Nonnull final BlockConsumer<T> blockConsumer, @Nullable final EntityConsumer<T> entityConsumer, @Nullable final ChildConsumer<T> childConsumer, @Nonnull final T t) {
            this.prefabBuffer.checkReleased();
            this.prefabBuffer.columns.int2ObjectEntrySet().forEach(entry -> {
                final int columnIndex = entry.getIntKey();
                final int cx = MathUtil.unpackLeft(columnIndex);
                final int cz = MathUtil.unpackRight(columnIndex);
                final int x2 = t.rotation.getX(cx, cz);
                final int z2 = t.rotation.getZ(cx, cz);
                final PrefabBufferColumn column = (PrefabBufferColumn)entry.getValue();
                this.buffer.readerIndex(column.getReaderIndex());
                final int blockCount = this.buffer.readInt();
                if (!columnPredicate.test(x2, z2, blockCount, t)) {
                    return;
                }
                else {
                    final BlockTypeAssetMap<String, BlockType> assetMap = BlockType.getAssetMap();
                    if (blockCount > 0) {
                        int y = this.buffer.readInt();
                        for (int j = 0; j < blockCount; ++j) {
                            final int mask = this.buffer.readUnsignedShort();
                            final int blockBytes = BlockMaskConstants.getBlockBytes(mask);
                            final int blockId = ByteBufUtil.readNumber(this.buffer, blockBytes);
                            final int offsetBytes = BlockMaskConstants.getOffsetBytes(mask);
                            y += ((offsetBytes == 0) ? 1 : ByteBufUtil.readNumber(this.buffer, offsetBytes));
                            if (BlockMaskConstants.hasChance(mask)) {
                                final float chance = this.buffer.readFloat();
                                if (chance < t.random.nextFloat()) {
                                    this.buffer.skipBytes(2);
                                    this.buffer.skipBytes(BlockMaskConstants.getFluidBytes(mask));
                                    continue;
                                }
                            }
                            final Holder<ChunkStore> holder = BlockMaskConstants.hasComponents(mask) ? column.getBlockComponents().get(y) : null;
                            final int supportValue = BlockMaskConstants.getSupportValue(mask);
                            int rotation = 0;
                            if (BlockMaskConstants.hasRotation(mask)) {
                                rotation = this.buffer.readUnsignedByte();
                            }
                            final int rotation2 = t.rotation.getRotation(rotation);
                            int filler = 0;
                            if (BlockMaskConstants.hasFiller(mask)) {
                                filler = t.rotation.getFiller(this.buffer.readUnsignedShort());
                            }
                            final int fluidBytes = BlockMaskConstants.getFluidBytes(mask);
                            int fluidId = 0;
                            int fluidLevel = 0;
                            if (fluidBytes != 0) {
                                fluidId = ByteBufUtil.readNumber(this.buffer, fluidBytes - 1);
                                fluidLevel = this.buffer.readByte();
                            }
                            blockConsumer.accept(x2, y, z2, blockId, holder, supportValue, rotation2, filler, t, fluidId, fluidLevel);
                        }
                    }
                    final Holder<EntityStore>[] entityHolders = column.getEntityHolders();
                    if (entityHolders != null && entityConsumer != null) {
                        entityConsumer.accept(x2, z2, entityHolders, t);
                    }
                    return;
                }
            });
            if (this.prefabBuffer.childPrefabs != null && childConsumer != null) {
                for (final ChildPrefab childPrefab : this.prefabBuffer.childPrefabs) {
                    final int x = t.rotation.getX(childPrefab.x, childPrefab.z);
                    final int z = t.rotation.getZ(childPrefab.x, childPrefab.z);
                    childConsumer.accept(x, childPrefab.y, z, childPrefab.path, childPrefab.fitHeightmap, childPrefab.inheritSeed, childPrefab.inheritHeightCondition, childPrefab.weights, childPrefab.rotation, t);
                }
            }
        }
        
        @Override
        public <T> void forEachRaw(@Nonnull final ColumnPredicate<T> columnPredicate, @Nonnull final RawBlockConsumer<T> blockConsumer, @Nonnull final FluidConsumer<T> fluidConsumer, @Nullable final EntityConsumer<T> entityConsumer, @Nullable final T t) {
            this.prefabBuffer.checkReleased();
            this.prefabBuffer.columns.int2ObjectEntrySet().forEach(entry -> {
                final int columnIndex = entry.getIntKey();
                final int x = MathUtil.unpackLeft(columnIndex);
                final int z = MathUtil.unpackRight(columnIndex);
                final PrefabBufferColumn column = (PrefabBufferColumn)entry.getValue();
                this.buffer.readerIndex(column.getReaderIndex());
                final int blockCount = this.buffer.readInt();
                if (!(!columnPredicate.test(x, z, blockCount, t))) {
                    if (blockCount > 0) {
                        int y = this.buffer.readInt();
                        for (int i = 0; i < blockCount; ++i) {
                            final int mask = this.buffer.readUnsignedShort();
                            final int blockBytes = BlockMaskConstants.getBlockBytes(mask);
                            final int blockId = ByteBufUtil.readNumber(this.buffer, blockBytes);
                            final int offsetBytes = BlockMaskConstants.getOffsetBytes(mask);
                            y += ((offsetBytes == 0) ? 1 : ByteBufUtil.readNumber(this.buffer, offsetBytes));
                            final float chance = BlockMaskConstants.hasChance(mask) ? this.buffer.readFloat() : 1.0f;
                            final Holder<ChunkStore> holder = BlockMaskConstants.hasComponents(mask) ? column.getBlockComponents().get(y) : null;
                            final int supportValue = BlockMaskConstants.getSupportValue(mask);
                            int rotation = 0;
                            if (BlockMaskConstants.hasRotation(mask)) {
                                rotation = this.buffer.readUnsignedByte();
                            }
                            int filler = 0;
                            if (BlockMaskConstants.hasFiller(mask)) {
                                filler = this.buffer.readUnsignedShort();
                            }
                            final int position = this.buffer.readerIndex();
                            blockConsumer.accept(x, y, z, mask, blockId, chance, holder, supportValue, rotation, filler, t);
                            this.buffer.readerIndex(position);
                            final int fluidBytes = BlockMaskConstants.getFluidBytes(mask);
                            if (fluidBytes != 0) {
                                final int fluidId = ByteBufUtil.readNumber(this.buffer, fluidBytes - 1);
                                final byte fluidLevel = this.buffer.readByte();
                                final int position2 = this.buffer.readerIndex();
                                fluidConsumer.accept(x, y, z, fluidId, fluidLevel, t);
                                this.buffer.readerIndex(position2);
                            }
                        }
                    }
                    final Holder<EntityStore>[] entityHolders = column.getEntityHolders();
                    if (entityConsumer != null) {
                        entityConsumer.accept(x, z, entityHolders, t);
                    }
                }
            });
        }
        
        @Override
        public <T> boolean forEachRaw(@Nonnull final ColumnPredicate<T> columnPredicate, @Nonnull final RawBlockPredicate<T> blockPredicate, @Nonnull final FluidPredicate<T> fluidPredicate, @Nullable final EntityPredicate<T> entityPredicate, @Nullable final T t) {
            this.prefabBuffer.checkReleased();
            for (final Int2ObjectMap.Entry<PrefabBufferColumn> entry : this.prefabBuffer.columns.int2ObjectEntrySet()) {
                final int columnIndex = entry.getIntKey();
                final int x = MathUtil.unpackLeft(columnIndex);
                final int z = MathUtil.unpackRight(columnIndex);
                final PrefabBufferColumn column = entry.getValue();
                this.buffer.readerIndex(column.getReaderIndex());
                final int blockCount = this.buffer.readInt();
                if (!columnPredicate.test(x, z, blockCount, t)) {
                    return false;
                }
                if (blockCount > 0) {
                    int y = this.buffer.readInt();
                    for (int i = 0; i < blockCount; ++i) {
                        final int mask = this.buffer.readUnsignedShort();
                        final int blockBytes = BlockMaskConstants.getBlockBytes(mask);
                        final int blockId = ByteBufUtil.readNumber(this.buffer, blockBytes);
                        final int offsetBytes = BlockMaskConstants.getOffsetBytes(mask);
                        y += ((offsetBytes == 0) ? 1 : ByteBufUtil.readNumber(this.buffer, offsetBytes));
                        final float chance = BlockMaskConstants.hasChance(mask) ? this.buffer.readFloat() : 1.0f;
                        final Holder<ChunkStore> holder = BlockMaskConstants.hasComponents(mask) ? column.getBlockComponents().get(y) : null;
                        final short rotation = (short)(BlockMaskConstants.hasRotation(mask) ? this.buffer.readUnsignedByte() : 0);
                        final int filler = BlockMaskConstants.hasFiller(mask) ? this.buffer.readUnsignedShort() : 0;
                        final int supportValue = BlockMaskConstants.getSupportValue(mask);
                        int position = this.buffer.readerIndex();
                        if (!blockPredicate.test(x, y, z, blockId, chance, holder, supportValue, rotation, filler, t)) {
                            return false;
                        }
                        this.buffer.readerIndex(position);
                        final int fluidBytes = BlockMaskConstants.getFluidBytes(mask);
                        if (fluidBytes != 0) {
                            final int fluidId = ByteBufUtil.readNumber(this.buffer, fluidBytes - 1);
                            final byte fluidLevel = this.buffer.readByte();
                            position = this.buffer.readerIndex();
                            if (!fluidPredicate.test(x, y, z, fluidId, fluidLevel, t)) {
                                return false;
                            }
                            this.buffer.readerIndex(position);
                        }
                    }
                }
                final Holder<EntityStore>[] entityHolders = column.getEntityHolders();
                if (entityPredicate != null && !entityPredicate.test(x, z, entityHolders, t)) {
                    return false;
                }
            }
            return true;
        }
        
        @Override
        public void release() {
            this.buffer.release();
            this.buffer = null;
        }
        
        @Override
        public <T extends PrefabBufferCall> boolean compare(@Nonnull final BlockComparingPrefabPredicate<T> blockComparingIterator, @Nonnull final T t, @Nonnull final IPrefabBuffer otherPrefab) {
            if (otherPrefab instanceof final PrefabBufferAccessor secondPrefab) {
                final Int2ObjectMap<PrefabBufferColumn> secondPrefabColumns = secondPrefab.prefabBuffer.columns;
                final IntOpenHashSet columnIndexes = new IntOpenHashSet(this.prefabBuffer.columns.size() + secondPrefabColumns.size());
                columnIndexes.addAll(this.prefabBuffer.columns.keySet());
                columnIndexes.addAll(secondPrefabColumns.keySet());
                this.prefabBuffer.checkReleased();
                final BlockTypeAssetMap<String, BlockType> assetMap = BlockType.getAssetMap();
                final IntIterator columnIterator = columnIndexes.iterator();
                while (columnIterator.hasNext()) {
                    final int columnIndex = columnIterator.nextInt();
                    final int cx = MathUtil.unpackLeft(columnIndex);
                    final int cz = MathUtil.unpackRight(columnIndex);
                    final int x = t.rotation.getX(cx, cz);
                    final int z = t.rotation.getZ(cx, cz);
                    final PrefabBufferColumn firstColumn = this.prefabBuffer.columns.get(columnIndex);
                    final PrefabBufferColumn secondColumn = secondPrefabColumns.get(columnIndex);
                    if (firstColumn != null) {
                        this.buffer.readerIndex(firstColumn.getReaderIndex());
                    }
                    if (secondColumn != null) {
                        secondPrefab.buffer.readerIndex(secondColumn.getReaderIndex());
                    }
                    final int firstColumnBlockCount = (firstColumn != null) ? this.buffer.readInt() : 0;
                    final int secondColumnBlockCount = (secondColumn != null) ? secondPrefab.buffer.readInt() : 0;
                    if (firstColumnBlockCount == 0 && secondColumnBlockCount == 0) {
                        continue;
                    }
                    int firstColumnY = (firstColumnBlockCount > 0) ? this.buffer.readInt() : Integer.MAX_VALUE;
                    int secondColumnY = (secondColumnBlockCount > 0) ? secondPrefab.buffer.readInt() : Integer.MAX_VALUE;
                    int firstColumnBlockId = Integer.MIN_VALUE;
                    float firstColumnChance = 1.0f;
                    int firstColumnRotation = 0;
                    int firstColumnFiller = 0;
                    Holder<ChunkStore> firstColumnComponents = null;
                    int secondColumnBlockId = Integer.MIN_VALUE;
                    float secondColumnChance = 1.0f;
                    int secondColumnRotation = 0;
                    int secondColumnFiller = 0;
                    Holder<ChunkStore> secondColumnComponents = null;
                    int firstColumnBlocksRead = 0;
                    int secondColumnBlocksRead = 0;
                    while (firstColumnBlocksRead < firstColumnBlockCount || secondColumnBlocksRead < secondColumnBlockCount) {
                        final int oldFirstColumnY = firstColumnY;
                        final int oldSecondColumnY = secondColumnY;
                        final int oldFirstColumnReaderIndex = (firstColumnBlocksRead < firstColumnBlockCount) ? this.buffer.readerIndex() : -1;
                        final int oldSecondColumnReaderIndex = (secondColumnBlocksRead < secondColumnBlockCount) ? secondPrefab.buffer.readerIndex() : -1;
                        if (firstColumnBlocksRead < firstColumnBlockCount) {
                            final int mask = this.buffer.readUnsignedShort();
                            final int blockBytes = BlockMaskConstants.getBlockBytes(mask);
                            firstColumnBlockId = ByteBufUtil.readNumber(this.buffer, blockBytes);
                            final int offsetBytes = BlockMaskConstants.getOffsetBytes(mask);
                            firstColumnY += ((offsetBytes == 0) ? 1 : ByteBufUtil.readNumber(this.buffer, offsetBytes));
                            firstColumnChance = (BlockMaskConstants.hasChance(mask) ? this.buffer.readFloat() : 1.0f);
                            firstColumnRotation = t.rotation.getRotation(BlockMaskConstants.hasRotation(mask) ? this.buffer.readUnsignedByte() : 0);
                            firstColumnFiller = (BlockMaskConstants.hasFiller(mask) ? t.rotation.getFiller(this.buffer.readUnsignedShort()) : 0);
                            firstColumnComponents = (BlockMaskConstants.hasComponents(mask) ? firstColumn.getBlockComponents().get(firstColumnY) : null);
                            this.buffer.skipBytes(BlockMaskConstants.getFluidBytes(mask));
                        }
                        if (secondColumnBlocksRead < secondColumnBlockCount) {
                            final int mask = secondPrefab.buffer.readUnsignedShort();
                            final int blockBytes = BlockMaskConstants.getBlockBytes(mask);
                            secondColumnBlockId = ByteBufUtil.readNumber(secondPrefab.buffer, blockBytes);
                            final int offsetBytes = BlockMaskConstants.getOffsetBytes(mask);
                            secondColumnY += ((offsetBytes == 0) ? 1 : ByteBufUtil.readNumber(secondPrefab.buffer, offsetBytes));
                            secondColumnChance = (BlockMaskConstants.hasChance(mask) ? secondPrefab.buffer.readFloat() : 1.0f);
                            secondColumnRotation = t.rotation.getRotation(BlockMaskConstants.hasRotation(mask) ? secondPrefab.buffer.readUnsignedByte() : 0);
                            secondColumnFiller = (BlockMaskConstants.hasFiller(mask) ? t.rotation.getFiller(secondPrefab.buffer.readUnsignedShort()) : 0);
                            secondColumnComponents = (BlockMaskConstants.hasComponents(mask) ? secondColumn.getBlockComponents().get(secondColumnY) : null);
                            secondPrefab.buffer.skipBytes(BlockMaskConstants.getFluidBytes(mask));
                        }
                        if (firstColumnY == secondColumnY) {
                            ++firstColumnBlocksRead;
                            ++secondColumnBlocksRead;
                            final boolean test = blockComparingIterator.test(x, firstColumnY, z, firstColumnBlockId, firstColumnComponents, firstColumnChance, firstColumnRotation, firstColumnFiller, secondColumnBlockId, secondColumnComponents, secondColumnChance, secondColumnRotation, secondColumnFiller, t);
                            if (!test) {
                                return false;
                            }
                            continue;
                        }
                        else if ((firstColumnY < secondColumnY && firstColumnBlocksRead < firstColumnBlockCount) || secondColumnBlocksRead >= secondColumnBlockCount) {
                            ++firstColumnBlocksRead;
                            secondColumnY = oldSecondColumnY;
                            if (oldSecondColumnReaderIndex != -1) {
                                secondPrefab.buffer.readerIndex(oldSecondColumnReaderIndex);
                            }
                            final boolean test = blockComparingIterator.test(x, firstColumnY, z, firstColumnBlockId, firstColumnComponents, firstColumnChance, firstColumnRotation, firstColumnFiller, Integer.MIN_VALUE, null, 1.0f, 0, 0, t);
                            if (!test) {
                                return false;
                            }
                            continue;
                        }
                        else {
                            ++secondColumnBlocksRead;
                            firstColumnY = oldFirstColumnY;
                            if (oldFirstColumnReaderIndex != -1) {
                                this.buffer.readerIndex(oldFirstColumnReaderIndex);
                            }
                            final boolean test = blockComparingIterator.test(x, secondColumnY, z, Integer.MIN_VALUE, null, 1.0f, 0, 0, secondColumnBlockId, secondColumnComponents, secondColumnChance, secondColumnRotation, secondColumnFiller, t);
                            if (!test) {
                                return false;
                            }
                            continue;
                        }
                    }
                }
                return true;
            }
            return super.compare(blockComparingIterator, t, otherPrefab);
        }
        
        @Override
        public int getBlockId(final int x, final int y, final int z) {
            this.prefabBuffer.checkReleased();
            final PrefabBufferColumn column = this.prefabBuffer.columns.get(MathUtil.packInt(x, z));
            if (column == null) {
                return 0;
            }
            this.buffer.readerIndex(column.getReaderIndex());
            final int blockCount = this.buffer.readInt();
            if (blockCount <= 0) {
                return 0;
            }
            int blockY = this.buffer.readInt();
            int i = 0;
            while (i < blockCount) {
                final int mask = this.buffer.readUnsignedShort();
                final int blockBytes = BlockMaskConstants.getBlockBytes(mask);
                final int blockId = ByteBufUtil.readNumber(this.buffer, blockBytes);
                final int offsetBytes = BlockMaskConstants.getOffsetBytes(mask);
                blockY += ((offsetBytes == 0) ? 1 : ByteBufUtil.readNumber(this.buffer, offsetBytes));
                if (blockY > y) {
                    return 0;
                }
                if (BlockMaskConstants.hasChance(mask)) {
                    this.buffer.readFloat();
                }
                if (BlockMaskConstants.hasRotation(mask)) {
                    this.buffer.readUnsignedByte();
                }
                if (BlockMaskConstants.hasFiller(mask)) {
                    this.buffer.readUnsignedShort();
                }
                final int fluidBytes = BlockMaskConstants.getFluidBytes(mask);
                this.buffer.skipBytes(fluidBytes);
                if (blockY == y) {
                    if (BlockMaskConstants.hasChance(mask)) {
                        throw new UnsupportedOperationException("Unable to access block with chance!");
                    }
                    return blockId;
                }
                else {
                    ++i;
                }
            }
            return 0;
        }
        
        @Override
        public int getFiller(final int x, final int y, final int z) {
            this.prefabBuffer.checkReleased();
            final PrefabBufferColumn column = this.prefabBuffer.columns.get(MathUtil.packInt(x, z));
            if (column == null) {
                return 0;
            }
            this.buffer.readerIndex(column.getReaderIndex());
            final int blockCount = this.buffer.readInt();
            if (blockCount <= 0) {
                return 0;
            }
            int blockY = this.buffer.readInt();
            int i = 0;
            while (i < blockCount) {
                final int mask = this.buffer.readUnsignedShort();
                final int blockBytes = BlockMaskConstants.getBlockBytes(mask);
                final int blockId = ByteBufUtil.readNumber(this.buffer, blockBytes);
                final int offsetBytes = BlockMaskConstants.getOffsetBytes(mask);
                blockY += ((offsetBytes == 0) ? 1 : ByteBufUtil.readNumber(this.buffer, offsetBytes));
                if (blockY > y) {
                    return 0;
                }
                if (BlockMaskConstants.hasChance(mask)) {
                    this.buffer.readFloat();
                }
                if (BlockMaskConstants.hasRotation(mask)) {
                    this.buffer.readUnsignedByte();
                }
                int filler = 0;
                if (BlockMaskConstants.hasFiller(mask)) {
                    filler = this.buffer.readUnsignedShort();
                }
                final int fluidBytes = BlockMaskConstants.getFluidBytes(mask);
                this.buffer.skipBytes(fluidBytes);
                if (blockY == y) {
                    if (BlockMaskConstants.hasChance(mask)) {
                        throw new UnsupportedOperationException("Unable to access block with chance!");
                    }
                    return filler;
                }
                else {
                    ++i;
                }
            }
            return 0;
        }
        
        @Override
        public int getRotationIndex(final int x, final int y, final int z) {
            this.prefabBuffer.checkReleased();
            final PrefabBufferColumn column = this.prefabBuffer.columns.get(MathUtil.packInt(x, z));
            if (column == null) {
                return 0;
            }
            this.buffer.readerIndex(column.getReaderIndex());
            final int blockCount = this.buffer.readInt();
            if (blockCount <= 0) {
                return 0;
            }
            int blockY = this.buffer.readInt();
            int i = 0;
            while (i < blockCount) {
                final int mask = this.buffer.readUnsignedShort();
                final int blockBytes = BlockMaskConstants.getBlockBytes(mask);
                final int blockId = ByteBufUtil.readNumber(this.buffer, blockBytes);
                final int offsetBytes = BlockMaskConstants.getOffsetBytes(mask);
                blockY += ((offsetBytes == 0) ? 1 : ByteBufUtil.readNumber(this.buffer, offsetBytes));
                if (blockY > y) {
                    return 0;
                }
                if (BlockMaskConstants.hasChance(mask)) {
                    this.buffer.readFloat();
                }
                int rotation = 0;
                if (BlockMaskConstants.hasRotation(mask)) {
                    rotation = this.buffer.readUnsignedByte();
                }
                if (BlockMaskConstants.hasFiller(mask)) {
                    this.buffer.readUnsignedShort();
                }
                final int fluidBytes = BlockMaskConstants.getFluidBytes(mask);
                this.buffer.skipBytes(fluidBytes);
                if (blockY == y) {
                    if (BlockMaskConstants.hasChance(mask)) {
                        throw new UnsupportedOperationException("Unable to access block with chance!");
                    }
                    return rotation;
                }
                else {
                    ++i;
                }
            }
            return 0;
        }
    }
}
