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

package com.hypixel.hytale.server.core.universe.world.chunk;

import com.hypixel.hytale.protocol.packets.world.SetChunk;
import java.util.function.Function;
import java.util.logging.Level;
import com.hypixel.hytale.protocol.Packet;
import java.util.List;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.component.ArchetypeChunk;
import com.hypixel.hytale.component.query.Query;
import io.netty.buffer.Unpooled;
import io.netty.buffer.ByteBuf;
import com.hypixel.hytale.sneakythrow.SneakyThrow;
import com.hypixel.hytale.server.core.util.io.ByteBufUtil;
import io.netty.buffer.ByteBufAllocator;
import com.hypixel.hytale.codec.ExtraInfo;
import com.hypixel.hytale.server.core.asset.type.blocktick.BlockTickStrategy;
import com.hypixel.hytale.function.predicate.ObjectPositionBlockFunction;
import java.time.Instant;
import it.unimi.dsi.fastutil.ints.IntCollection;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import it.unimi.dsi.fastutil.ints.Int2ShortMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import com.hypixel.hytale.server.core.universe.world.chunk.environment.EnvironmentColumn;
import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.protocol.Opacity;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import javax.annotation.Nonnull;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.server.core.modules.LegacyModule;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.protocol.packets.world.SetChunkEnvironments;
import com.hypixel.hytale.protocol.packets.world.SetChunkTintmap;
import com.hypixel.hytale.protocol.packets.world.SetChunkHeightmap;
import com.hypixel.hytale.protocol.CachedPacket;
import java.util.concurrent.CompletableFuture;
import java.lang.ref.SoftReference;
import com.hypixel.hytale.server.core.universe.world.chunk.environment.EnvironmentChunk;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection;
import com.hypixel.hytale.server.core.universe.world.chunk.palette.IntBytePalette;
import com.hypixel.hytale.server.core.universe.world.chunk.palette.ShortBytePalette;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.component.Component;

public class BlockChunk implements Component<ChunkStore>
{
    public static final int VERSION = 3;
    public static final BuilderCodec<BlockChunk> CODEC;
    private static final HytaleLogger LOGGER;
    public static boolean SEND_LOCAL_LIGHTING_DATA;
    public static boolean SEND_GLOBAL_LIGHTING_DATA;
    private long index;
    private int x;
    private int z;
    private final ShortBytePalette height;
    private final IntBytePalette tint;
    @Deprecated(forRemoval = true)
    private BlockSection[] chunkSections;
    @Nullable
    @Deprecated(forRemoval = true)
    private BlockSection[] migratedChunkSections;
    private EnvironmentChunk environments;
    private boolean needsPhysics;
    private boolean needsSaving;
    @Nullable
    private transient SoftReference<CompletableFuture<CachedPacket<SetChunkHeightmap>>> cachedHeightmapPacket;
    @Nullable
    private transient SoftReference<CompletableFuture<CachedPacket<SetChunkTintmap>>> cachedTintmapPacket;
    @Nullable
    private transient SoftReference<CompletableFuture<CachedPacket<SetChunkEnvironments>>> cachedEnvironmentsPacket;
    
    public static ComponentType<ChunkStore, BlockChunk> getComponentType() {
        return LegacyModule.get().getBlockChunkComponentType();
    }
    
    private BlockChunk() {
        this(new ShortBytePalette(), new IntBytePalette(), new EnvironmentChunk(), new BlockSection[10]);
    }
    
    public void load(final int x, final int z) {
        this.x = x;
        this.z = z;
        this.index = ChunkUtil.indexChunk(x, z);
    }
    
    public BlockChunk(final int x, final int z) {
        this(x, z, new ShortBytePalette(), new IntBytePalette(), new EnvironmentChunk());
    }
    
    public BlockChunk(final int x, final int z, final ShortBytePalette height, final IntBytePalette tint, final EnvironmentChunk environments) {
        this(height, tint, environments, new BlockSection[10]);
        this.x = x;
        this.z = z;
        this.index = ChunkUtil.indexChunk(x, z);
    }
    
    public BlockChunk(final ShortBytePalette height, final IntBytePalette tint, final EnvironmentChunk environments, final BlockSection[] chunkSections) {
        this.needsPhysics = true;
        this.needsSaving = false;
        this.height = height;
        this.tint = tint;
        this.environments = environments;
        this.chunkSections = chunkSections;
    }
    
    @Override
    public Component<ChunkStore> clone() {
        throw new UnsupportedOperationException("Not implemented!");
    }
    
    @Nonnull
    @Override
    public Component<ChunkStore> cloneSerializable() {
        return this;
    }
    
    public long getIndex() {
        return this.index;
    }
    
    public int getX() {
        return this.x;
    }
    
    public int getZ() {
        return this.z;
    }
    
    public EnvironmentChunk getEnvironmentChunk() {
        return this.environments;
    }
    
    public void setEnvironmentChunk(final EnvironmentChunk environmentChunk) {
        this.environments = environmentChunk;
    }
    
    public short getHeight(final int x, final int z) {
        return this.height.get(x, z);
    }
    
    public short getHeight(final int index) {
        return this.height.get(index);
    }
    
    public void setHeight(final int x, final int z, final short height) {
        this.height.set(x, z, height);
        this.cachedHeightmapPacket = null;
        this.markNeedsSaving();
    }
    
    public void updateHeightmap() {
        for (int cx = 0; cx < 32; ++cx) {
            for (int cz = 0; cz < 32; ++cz) {
                this.updateHeight(cx, cz);
            }
        }
    }
    
    public short updateHeight(final int x, final int z) {
        return this.updateHeight(x, z, (short)320);
    }
    
    public short updateHeight(final int x, final int z, final short startY) {
        short y = startY;
        while (true) {
            --y;
            if (y <= 0) {
                break;
            }
            final BlockSection section = this.getSectionAtBlockY(y);
            if (section.isSolidAir()) {
                y = (short)(ChunkUtil.indexSection(y) * 32);
                if (y == 0) {
                    break;
                }
                continue;
            }
            else {
                final int blockId = section.get(x, y, z);
                final BlockType type = BlockType.getAssetMap().getAsset(blockId);
                if (blockId != 0 && type != null && type.getOpacity() != Opacity.Transparent) {
                    break;
                }
                continue;
            }
        }
        this.setHeight(x, z, y);
        return y;
    }
    
    @Deprecated(forRemoval = true)
    public void loadFromHolder(@Nonnull final Holder<ChunkStore> holder) {
        final ChunkColumn column = holder.getComponent(ChunkColumn.getComponentType());
        if (column == null) {
            return;
        }
        final Holder<ChunkStore>[] sections = column.getSectionHolders();
        for (int i = 0; i < sections.length; ++i) {
            final Holder<ChunkStore> section = sections[i];
            this.chunkSections[i] = ((this.migratedChunkSections != null) ? this.migratedChunkSections[i] : section.ensureAndGetComponent(BlockSection.getComponentType()));
        }
    }
    
    @Deprecated(forRemoval = false)
    public BlockSection getSectionAtIndex(final int index) {
        if (index < 0 || index >= this.chunkSections.length) {
            throw new IllegalArgumentException("Section index must >=0 and <" + this.chunkSections.length + " but was given " + index);
        }
        return this.chunkSections[index];
    }
    
    @Deprecated(forRemoval = false)
    public BlockSection getSectionAtBlockY(final int y) {
        final int index = ChunkUtil.indexSection(y);
        if (index < 0 || index >= this.chunkSections.length) {
            throw new IllegalArgumentException("Section y must >=0 and <320 but was given " + y);
        }
        return this.chunkSections[index];
    }
    
    @Deprecated(forRemoval = false)
    public BlockSection[] getChunkSections() {
        return this.chunkSections;
    }
    
    public int getSectionCount() {
        return this.chunkSections.length;
    }
    
    public int getTint(final int x, final int z) {
        return this.tint.get(x, z);
    }
    
    public void setTint(final int x, final int z, final int tint) {
        this.tint.set(x, z, tint);
        this.cachedTintmapPacket = null;
        this.markNeedsSaving();
    }
    
    public int getEnvironment(@Nonnull final Vector3d position) {
        return this.getEnvironment(MathUtil.floor(position.x), MathUtil.floor(position.y), MathUtil.floor(position.z));
    }
    
    public int getEnvironment(@Nonnull final Vector3i position) {
        return this.getEnvironment(position.x, position.y, position.z);
    }
    
    public int getEnvironment(final int x, final int y, final int z) {
        if (y < 0 || y >= 320) {
            return 0;
        }
        return this.environments.get(x, y, z);
    }
    
    public EnvironmentColumn getEnvironmentColumn(final int x, final int z) {
        return this.environments.get(x, z);
    }
    
    public void setEnvironment(final int x, final int y, final int z, final int environment) {
        if (y < 0 || y >= 320) {
            return;
        }
        this.environments.set(x, y, z, environment);
        this.cachedEnvironmentsPacket = null;
        this.markNeedsSaving();
    }
    
    public byte getRedBlockLight(final int x, final int y, final int z) {
        if (y < 0 || y >= 320) {
            return 0;
        }
        return this.getSectionAtBlockY(y).getGlobalLight().getRedBlockLight(x, y, z);
    }
    
    public byte getGreenBlockLight(final int x, final int y, final int z) {
        if (y < 0 || y >= 320) {
            return 0;
        }
        return this.getSectionAtBlockY(y).getGlobalLight().getGreenBlockLight(x, y, z);
    }
    
    public byte getBlueBlockLight(final int x, final int y, final int z) {
        if (y < 0 || y >= 320) {
            return 0;
        }
        return this.getSectionAtBlockY(y).getGlobalLight().getBlueBlockLight(x, y, z);
    }
    
    public short getBlockLight(final int x, final int y, final int z) {
        if (y < 0 || y >= 320) {
            return 0;
        }
        return this.getSectionAtBlockY(y).getGlobalLight().getBlockLight(x, y, z);
    }
    
    public byte getSkyLight(final int x, final int y, final int z) {
        if (y < 0 || y >= 320) {
            return 0;
        }
        return this.getSectionAtBlockY(y).getGlobalLight().getSkyLight(x, y, z);
    }
    
    public byte getBlockLightIntensity(final int x, final int y, final int z) {
        if (y < 0 || y >= 320) {
            return 0;
        }
        return this.getSectionAtBlockY(y).getGlobalLight().getBlockLightIntensity(x, y, z);
    }
    
    public int getBlock(final int x, final int y, final int z) {
        if (y < 0 || y >= 320) {
            return 0;
        }
        return this.getSectionAtBlockY(y).get(x, y, z);
    }
    
    public boolean setBlock(final int x, final int y, final int z, final int blockId, final int rotation, final int filler) {
        if (y < 0 || y >= 320) {
            throw new IllegalArgumentException(String.format("Failed to set block at %d, %d, %d to %d because it is outside the world bounds", x, y, z, blockId));
        }
        final int sectionIndex = ChunkUtil.indexSection(y);
        final BlockSection section = this.chunkSections[sectionIndex];
        final boolean changed = section.set(x, y, z, blockId, rotation, filler);
        if (changed) {
            this.invalidateChunkSection(sectionIndex);
            this.markNeedsSaving();
        }
        return changed;
    }
    
    public boolean contains(final int blockId) {
        return this.count(blockId) != 0;
    }
    
    public int count(final int blockId) {
        int count = 0;
        for (final BlockSection section : this.chunkSections) {
            count += section.count(blockId);
        }
        return count;
    }
    
    @Nonnull
    public Int2IntMap blockCounts() {
        final Int2IntMap map = new Int2IntOpenHashMap();
        for (final BlockSection section : this.chunkSections) {
            for (final Int2ShortMap.Entry entry : section.valueCounts().int2ShortEntrySet()) {
                final int blockId = entry.getIntKey();
                final short count = entry.getShortValue();
                map.mergeInt(blockId, count, Integer::sum);
            }
        }
        return map;
    }
    
    @Nonnull
    public IntSet blocks() {
        final IntSet set = new IntOpenHashSet();
        for (final BlockSection section : this.chunkSections) {
            set.addAll(section.values());
        }
        return set;
    }
    
    public int blockCount() {
        return this.blocks().size();
    }
    
    public void preTick(final Instant gameTime) {
        for (int sectionIndex = 0; sectionIndex < this.chunkSections.length; ++sectionIndex) {
            this.chunkSections[sectionIndex].preTick(gameTime);
        }
    }
    
    public <T, V> int forEachTicking(final T t, final V v, final ObjectPositionBlockFunction<T, V, BlockTickStrategy> acceptor) {
        int ticked = 0;
        for (int sectionIndex = 0; sectionIndex < this.chunkSections.length; ++sectionIndex) {
            final BlockSection section = this.chunkSections[sectionIndex];
            ticked += section.forEachTicking(t, v, sectionIndex, acceptor);
        }
        if (ticked > 0) {
            this.markNeedsSaving();
        }
        return ticked;
    }
    
    public void mergeTickingBlocks() {
        for (final BlockSection section : this.chunkSections) {
            section.mergeTickingBlocks();
        }
    }
    
    public boolean setTicking(final int x, final int y, final int z, final boolean ticking) {
        if (y < 0 || y >= 320) {
            return false;
        }
        final boolean changed = this.getSectionAtBlockY(y).setTicking(x, y, z, ticking);
        if (changed) {
            this.markNeedsSaving();
        }
        return changed;
    }
    
    public boolean isTicking(final int x, final int y, final int z) {
        return y >= 0 && y < 320 && this.getSectionAtBlockY(y).isTicking(x, y, z);
    }
    
    public int getTickingBlocksCount() {
        int ticking = 0;
        for (final BlockSection chunkSection : this.chunkSections) {
            ticking += chunkSection.getTickingBlocksCount();
        }
        return ticking;
    }
    
    public boolean setNeighbourBlocksTicking(final int x, final int y, final int z) {
        boolean success = true;
        for (int ix = -1; ix < 2; ++ix) {
            final int wx = x + ix;
            for (int iz = -1; iz < 2; ++iz) {
                final int wz = z + iz;
                if (!ChunkUtil.isInsideChunkRelative(wx, wz)) {
                    success = false;
                }
                else {
                    for (int iy = -1; iy < 2; ++iy) {
                        final int wy = y + iy;
                        this.setTicking(wx, wy, wz, true);
                    }
                }
            }
        }
        return success;
    }
    
    public void markNeedsSaving() {
        this.needsSaving = true;
    }
    
    public boolean getNeedsSaving() {
        return this.needsSaving;
    }
    
    public boolean consumeNeedsSaving() {
        final boolean out = this.needsSaving;
        this.needsSaving = false;
        return out;
    }
    
    public void markNeedsPhysics() {
        this.needsPhysics = true;
    }
    
    public boolean consumeNeedsPhysics() {
        final boolean old = this.needsPhysics;
        this.needsPhysics = false;
        return old;
    }
    
    public void invalidateChunkSection(final int sectionIndex) {
        this.chunkSections[sectionIndex].invalidate();
    }
    
    @Nullable
    @Deprecated(forRemoval = true)
    public BlockSection[] takeMigratedSections() {
        final BlockSection[] temp = this.migratedChunkSections;
        this.migratedChunkSections = null;
        return temp;
    }
    
    @Nullable
    @Deprecated(forRemoval = true)
    public BlockSection[] getMigratedSections() {
        return this.migratedChunkSections;
    }
    
    private byte[] serialize(final ExtraInfo extraInfo) {
        final ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();
        try {
            buf.writeBoolean(this.needsPhysics);
            this.height.serialize(buf);
            this.tint.serialize(buf);
            return ByteBufUtil.getBytesRelease(buf);
        }
        catch (final Throwable t) {
            buf.release();
            throw SneakyThrow.sneakyThrow(t);
        }
    }
    
    private void deserialize(@Nonnull final byte[] bytes, @Nonnull final ExtraInfo extraInfo) {
        final ByteBuf buf = Unpooled.wrappedBuffer(bytes);
        this.needsPhysics = buf.readBoolean();
        this.height.deserialize(buf);
        this.tint.deserialize(buf);
        if (extraInfo.getVersion() <= 2) {
            final int sections = buf.readInt();
            this.migratedChunkSections = new BlockSection[sections];
            for (int y = 0; y < sections; ++y) {
                final BlockSection section = new BlockSection();
                section.deserialize(BlockType.KEY_DESERIALIZER, buf, extraInfo.getVersion());
                this.migratedChunkSections[y] = section;
            }
        }
    }
    
    @Nonnull
    private CompletableFuture<CachedPacket<SetChunkHeightmap>> getCachedHeightmapPacket() {
        final SoftReference<CompletableFuture<CachedPacket<SetChunkHeightmap>>> ref = this.cachedHeightmapPacket;
        CompletableFuture<CachedPacket<SetChunkHeightmap>> future = (ref != null) ? ref.get() : null;
        if (future != null) {
            return future;
        }
        future = CompletableFuture.supplyAsync(() -> {
            final SetChunkHeightmap packet = new SetChunkHeightmap(this.x, this.z, this.height.serialize());
            return CachedPacket.cache(packet);
        });
        this.cachedHeightmapPacket = new SoftReference<CompletableFuture<CachedPacket<SetChunkHeightmap>>>(future);
        return future;
    }
    
    @Nonnull
    private CompletableFuture<CachedPacket<SetChunkTintmap>> getCachedTintsPacket() {
        final SoftReference<CompletableFuture<CachedPacket<SetChunkTintmap>>> ref = this.cachedTintmapPacket;
        CompletableFuture<CachedPacket<SetChunkTintmap>> future = (ref != null) ? ref.get() : null;
        if (future != null) {
            return future;
        }
        future = CompletableFuture.supplyAsync(() -> {
            final SetChunkTintmap packet = new SetChunkTintmap(this.x, this.z, this.tint.serialize());
            return CachedPacket.cache(packet);
        });
        this.cachedTintmapPacket = new SoftReference<CompletableFuture<CachedPacket<SetChunkTintmap>>>(future);
        return future;
    }
    
    @Nonnull
    private CompletableFuture<CachedPacket<SetChunkEnvironments>> getCachedEnvironmentsPacket() {
        final SoftReference<CompletableFuture<CachedPacket<SetChunkEnvironments>>> ref = this.cachedEnvironmentsPacket;
        CompletableFuture<CachedPacket<SetChunkEnvironments>> future = (ref != null) ? ref.get() : null;
        if (future != null) {
            return future;
        }
        future = CompletableFuture.supplyAsync(() -> {
            final SetChunkEnvironments packet = new SetChunkEnvironments(this.x, this.z, this.environments.serializeProtocol());
            return CachedPacket.cache(packet);
        });
        this.cachedEnvironmentsPacket = new SoftReference<CompletableFuture<CachedPacket<SetChunkEnvironments>>>(future);
        return future;
    }
    
    static {
        // 
        // This method could not be decompiled.
        // 
        // Original Bytecode:
        // 
        //     2: invokedynamic   BootstrapMethod #6, get:()Ljava/util/function/Supplier;
        //     7: invokestatic    com/hypixel/hytale/codec/builder/BuilderCodec.builder:(Ljava/lang/Class;Ljava/util/function/Supplier;)Lcom/hypixel/hytale/codec/builder/BuilderCodec$Builder;
        //    10: invokevirtual   com/hypixel/hytale/codec/builder/BuilderCodec$Builder.versioned:()Lcom/hypixel/hytale/codec/builder/BuilderCodec$BuilderBase;
        //    13: checkcast       Lcom/hypixel/hytale/codec/builder/BuilderCodec$Builder;
        //    16: iconst_3       
        //    17: invokevirtual   com/hypixel/hytale/codec/builder/BuilderCodec$Builder.codecVersion:(I)Lcom/hypixel/hytale/codec/builder/BuilderCodec$BuilderBase;
        //    20: checkcast       Lcom/hypixel/hytale/codec/builder/BuilderCodec$Builder;
        //    23: new             Lcom/hypixel/hytale/codec/KeyedCodec;
        //    26: dup            
        //    27: ldc_w           "Data"
        //    30: getstatic       com/hypixel/hytale/codec/Codec.BYTE_ARRAY:Lcom/hypixel/hytale/codec/Codec;
        //    33: invokespecial   com/hypixel/hytale/codec/KeyedCodec.<init>:(Ljava/lang/String;Lcom/hypixel/hytale/codec/Codec;)V
        //    36: invokedynamic   BootstrapMethod #7, accept:()Lcom/hypixel/hytale/function/consumer/TriConsumer;
        //    41: invokedynamic   BootstrapMethod #8, apply:()Ljava/util/function/BiFunction;
        //    46: invokevirtual   com/hypixel/hytale/codec/builder/BuilderCodec$Builder.append:(Lcom/hypixel/hytale/codec/KeyedCodec;Lcom/hypixel/hytale/function/consumer/TriConsumer;Ljava/util/function/BiFunction;)Lcom/hypixel/hytale/codec/builder/BuilderField$FieldBuilder;
        //    49: invokevirtual   com/hypixel/hytale/codec/builder/BuilderField$FieldBuilder.add:()Lcom/hypixel/hytale/codec/builder/BuilderCodec$BuilderBase;
        //    52: checkcast       Lcom/hypixel/hytale/codec/builder/BuilderCodec$Builder;
        //    55: invokevirtual   com/hypixel/hytale/codec/builder/BuilderCodec$Builder.build:()Lcom/hypixel/hytale/codec/builder/BuilderCodec;
        //    58: putstatic       com/hypixel/hytale/server/core/universe/world/chunk/BlockChunk.CODEC:Lcom/hypixel/hytale/codec/builder/BuilderCodec;
        //    61: invokestatic    com/hypixel/hytale/logger/HytaleLogger.forEnclosingClass:()Lcom/hypixel/hytale/logger/HytaleLogger;
        //    64: putstatic       com/hypixel/hytale/server/core/universe/world/chunk/BlockChunk.LOGGER:Lcom/hypixel/hytale/logger/HytaleLogger;
        //    67: iconst_1       
        //    68: putstatic       com/hypixel/hytale/server/core/universe/world/chunk/BlockChunk.SEND_LOCAL_LIGHTING_DATA:Z
        //    71: iconst_0       
        //    72: putstatic       com/hypixel/hytale/server/core/universe/world/chunk/BlockChunk.SEND_GLOBAL_LIGHTING_DATA:Z
        //    75: return         
        // 
        // The error that occurred was:
        // 
        // java.lang.UnsupportedOperationException: The requested operation is not supported.
        //     at com.strobel.util.ContractUtils.unsupported(ContractUtils.java:27)
        //     at com.strobel.assembler.metadata.TypeReference.getRawType(TypeReference.java:284)
        //     at com.strobel.assembler.metadata.TypeReference.getRawType(TypeReference.java:279)
        //     at com.strobel.assembler.metadata.TypeReference.makeGenericType(TypeReference.java:154)
        //     at com.strobel.assembler.metadata.TypeSubstitutionVisitor.visitParameterizedType(TypeSubstitutionVisitor.java:225)
        //     at com.strobel.assembler.metadata.TypeSubstitutionVisitor.visitParameterizedType(TypeSubstitutionVisitor.java:25)
        //     at com.strobel.assembler.metadata.ParameterizedType.accept(ParameterizedType.java:103)
        //     at com.strobel.assembler.metadata.TypeSubstitutionVisitor.visit(TypeSubstitutionVisitor.java:40)
        //     at com.strobel.assembler.metadata.TypeSubstitutionVisitor.visitMethod(TypeSubstitutionVisitor.java:314)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferCall(TypeAnalysis.java:2611)
        //     at com.strobel.decompiler.ast.TypeAnalysis.doInferTypeForExpression(TypeAnalysis.java:1040)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:815)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:782)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:778)
        //     at com.strobel.decompiler.ast.TypeAnalysis.doInferTypeForExpression(TypeAnalysis.java:1510)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:815)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:790)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferCall(TypeAnalysis.java:2689)
        //     at com.strobel.decompiler.ast.TypeAnalysis.doInferTypeForExpression(TypeAnalysis.java:1040)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:815)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:790)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferCall(TypeAnalysis.java:2689)
        //     at com.strobel.decompiler.ast.TypeAnalysis.doInferTypeForExpression(TypeAnalysis.java:1040)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:815)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:782)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:778)
        //     at com.strobel.decompiler.ast.TypeAnalysis.doInferTypeForExpression(TypeAnalysis.java:1510)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:815)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:790)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferCall(TypeAnalysis.java:2689)
        //     at com.strobel.decompiler.ast.TypeAnalysis.doInferTypeForExpression(TypeAnalysis.java:1040)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:815)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:782)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:778)
        //     at com.strobel.decompiler.ast.TypeAnalysis.doInferTypeForExpression(TypeAnalysis.java:1083)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:815)
        //     at com.strobel.decompiler.ast.TypeAnalysis.runInference(TypeAnalysis.java:684)
        //     at com.strobel.decompiler.ast.TypeAnalysis.runInference(TypeAnalysis.java:667)
        //     at com.strobel.decompiler.ast.TypeAnalysis.runInference(TypeAnalysis.java:373)
        //     at com.strobel.decompiler.ast.TypeAnalysis.run(TypeAnalysis.java:95)
        //     at com.strobel.decompiler.ast.AstOptimizer.optimize(AstOptimizer.java:344)
        //     at com.strobel.decompiler.ast.AstOptimizer.optimize(AstOptimizer.java:42)
        //     at com.strobel.decompiler.languages.java.ast.AstMethodBodyBuilder.createMethodBody(AstMethodBodyBuilder.java:206)
        //     at com.strobel.decompiler.languages.java.ast.AstMethodBodyBuilder.createMethodBody(AstMethodBodyBuilder.java:93)
        //     at com.strobel.decompiler.languages.java.ast.AstBuilder.createMethodBody(AstBuilder.java:868)
        //     at com.strobel.decompiler.languages.java.ast.AstBuilder.createMethod(AstBuilder.java:761)
        //     at com.strobel.decompiler.languages.java.ast.AstBuilder.addTypeMembers(AstBuilder.java:638)
        //     at com.strobel.decompiler.languages.java.ast.AstBuilder.createTypeCore(AstBuilder.java:605)
        //     at com.strobel.decompiler.languages.java.ast.AstBuilder.createTypeNoCache(AstBuilder.java:195)
        //     at com.strobel.decompiler.languages.java.ast.AstBuilder.createType(AstBuilder.java:162)
        //     at com.strobel.decompiler.languages.java.ast.AstBuilder.addType(AstBuilder.java:137)
        //     at com.strobel.decompiler.languages.java.JavaLanguage.buildAst(JavaLanguage.java:71)
        //     at com.strobel.decompiler.languages.java.JavaLanguage.decompileType(JavaLanguage.java:59)
        //     at com.strobel.decompiler.DecompilerDriver.decompileType(DecompilerDriver.java:333)
        //     at com.strobel.decompiler.DecompilerDriver.decompileJar(DecompilerDriver.java:254)
        //     at com.strobel.decompiler.DecompilerDriver.main(DecompilerDriver.java:129)
        // 
        throw new IllegalStateException("An error occurred while decompiling this method.");
    }
    
    public static class LoadBlockChunkPacketSystem extends ChunkStore.LoadFuturePacketDataQuerySystem
    {
        private final ComponentType<ChunkStore, BlockChunk> componentType;
        
        public LoadBlockChunkPacketSystem(final ComponentType<ChunkStore, BlockChunk> blockChunkComponentType) {
            this.componentType = blockChunkComponentType;
        }
        
        @Override
        public Query<ChunkStore> getQuery() {
            return this.componentType;
        }
        
        @Override
        public void fetch(final int index, @Nonnull final ArchetypeChunk<ChunkStore> archetypeChunk, final Store<ChunkStore> store, final CommandBuffer<ChunkStore> commandBuffer, final PlayerRef player, @Nonnull final List<CompletableFuture<Packet>> results) {
            final BlockChunk component = archetypeChunk.getComponent(index, this.componentType);
            results.add(component.getCachedHeightmapPacket().exceptionally(throwable -> {
                if (throwable != null) {
                    BlockChunk.LOGGER.at(Level.SEVERE).withCause(throwable).log("Exception when compressing chunk heightmap:");
                }
                return null;
            }).thenApply((Function<? super CachedPacket<SetChunkHeightmap>, ? extends Packet>)Function.identity()));
            results.add(component.getCachedTintsPacket().exceptionally(throwable -> {
                if (throwable != null) {
                    BlockChunk.LOGGER.at(Level.SEVERE).withCause(throwable).log("Exception when compressing chunk tints:");
                }
                return null;
            }).thenApply((Function<? super CachedPacket<SetChunkTintmap>, ? extends Packet>)Function.identity()));
            results.add(component.getCachedEnvironmentsPacket().exceptionally(throwable -> {
                if (throwable != null) {
                    BlockChunk.LOGGER.at(Level.SEVERE).withCause(throwable).log("Exception when compressing chunk environments:");
                }
                return null;
            }).thenApply((Function<? super CachedPacket<SetChunkEnvironments>, ? extends Packet>)Function.identity()));
            for (int y = 0; y < component.chunkSections.length; ++y) {
                final BlockSection section = component.chunkSections[y];
                results.add(section.getCachedChunkPacket(component.getX(), y, component.getZ()).exceptionally(throwable -> {
                    if (throwable != null) {
                        BlockChunk.LOGGER.at(Level.SEVERE).withCause(throwable).log("Exception while compressing set chunk (%d, %d):", component.x, component.z);
                    }
                    return null;
                }).thenApply((Function<? super CachedPacket<SetChunk>, ? extends Packet>)Function.identity()));
            }
        }
    }
}
