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

package com.hypixel.hytale.builtin.adventure.farming;

import java.lang.invoke.CallSite;
import java.lang.reflect.UndeclaredThrowableException;
import java.lang.invoke.MethodHandle;
import java.lang.runtime.SwitchBootstraps;
import java.lang.invoke.MethodType;
import java.lang.invoke.MethodHandles;
import com.hypixel.hytale.builtin.adventure.farming.states.FarmingBlockState;
import com.hypixel.hytale.component.Holder;
import java.util.UUID;
import com.hypixel.hytale.server.core.entity.UUIDComponent;
import com.hypixel.hytale.builtin.adventure.farming.component.CoopResidentComponent;
import com.hypixel.hytale.component.ComponentType;
import java.util.Iterator;
import java.util.List;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.builtin.adventure.farming.config.FarmingCoopAsset;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.server.core.util.TargetUtil;
import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
import com.hypixel.hytale.builtin.adventure.farming.states.CoopBlock;
import com.hypixel.hytale.server.core.asset.type.blocktick.BlockTickStrategy;
import com.hypixel.hytale.server.core.universe.world.chunk.section.ChunkSection;
import com.hypixel.hytale.component.ArchetypeChunk;
import com.hypixel.hytale.component.system.tick.EntityTickingSystem;
import com.hypixel.hytale.server.core.universe.world.chunk.BlockComponentChunk;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn;
import com.hypixel.hytale.builtin.adventure.farming.config.stages.BlockStateFarmingStageData;
import com.hypixel.hytale.builtin.adventure.farming.config.stages.BlockTypeFarmingStageData;
import java.util.Objects;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.farming.FarmingStageData;
import com.hypixel.hytale.builtin.adventure.farming.states.FarmingBlock;
import javax.annotation.Nullable;
import com.hypixel.hytale.component.RemoveReason;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.server.core.modules.block.BlockModule;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.component.AddReason;
import javax.annotation.Nonnull;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.query.Query;
import com.hypixel.hytale.component.system.RefSystem;
import java.time.Instant;
import com.hypixel.hytale.protocol.Rangef;
import java.time.temporal.TemporalUnit;
import java.time.temporal.ChronoUnit;
import com.hypixel.hytale.server.core.modules.time.WorldTimeResource;
import java.util.concurrent.ThreadLocalRandom;
import com.hypixel.hytale.builtin.adventure.farming.states.TilledSoilBlock;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.farming.FarmingData;
import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk;

public class FarmingSystems
{
    private static boolean hasCropAbove(final BlockChunk blockChunk, final int x, final int y, final int z) {
        if (y + 1 >= 320) {
            return false;
        }
        final BlockSection aboveBlockSection = blockChunk.getSectionAtBlockY(y + 1);
        if (aboveBlockSection == null) {
            return false;
        }
        final BlockType aboveBlockType = BlockType.getAssetMap().getAsset(aboveBlockSection.get(x, y + 1, z));
        if (aboveBlockType == null) {
            return false;
        }
        final FarmingData farmingConfig = aboveBlockType.getFarming();
        return farmingConfig != null && farmingConfig.getStages() != null;
    }
    
    private static boolean updateSoilDecayTime(final CommandBuffer<ChunkStore> commandBuffer, final TilledSoilBlock soilBlock, final BlockType blockType) {
        if (blockType == null || blockType.getFarming() == null || blockType.getFarming().getSoilConfig() == null) {
            return false;
        }
        final FarmingData.SoilConfig soilConfig = blockType.getFarming().getSoilConfig();
        final Rangef range = soilConfig.getLifetime();
        if (range == null) {
            return false;
        }
        final double baseDuration = range.min + (range.max - range.min) * ThreadLocalRandom.current().nextDouble();
        final Instant currentTime = commandBuffer.getExternalData().getWorld().getEntityStore().getStore().getResource(WorldTimeResource.getResourceType()).getGameTime();
        final Instant endTime = currentTime.plus(Math.round(baseDuration), (TemporalUnit)ChronoUnit.SECONDS);
        soilBlock.setDecayTime(endTime);
        return true;
    }
    
    public static class OnSoilAdded extends RefSystem<ChunkStore>
    {
        private static final Query<ChunkStore> QUERY;
        
        @Override
        public void onEntityAdded(@Nonnull final Ref<ChunkStore> ref, @Nonnull final AddReason reason, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
            final TilledSoilBlock soil = commandBuffer.getComponent(ref, TilledSoilBlock.getComponentType());
            assert soil != null;
            final BlockModule.BlockStateInfo info = commandBuffer.getComponent(ref, BlockModule.BlockStateInfo.getComponentType());
            assert info != null;
            if (!soil.isPlanted()) {
                final int x = ChunkUtil.xFromBlockInColumn(info.getIndex());
                final int y = ChunkUtil.yFromBlockInColumn(info.getIndex());
                final int z = ChunkUtil.zFromBlockInColumn(info.getIndex());
                assert info.getChunkRef() != null;
                final BlockChunk blockChunk = commandBuffer.getComponent(info.getChunkRef(), BlockChunk.getComponentType());
                assert blockChunk != null;
                final BlockSection blockSection = blockChunk.getSectionAtBlockY(y);
                final Instant decayTime = soil.getDecayTime();
                if (decayTime == null) {
                    final BlockType blockType = BlockType.getAssetMap().getAsset(blockSection.get(x, y, z));
                    FarmingSystems.updateSoilDecayTime(commandBuffer, soil, blockType);
                }
                if (decayTime == null) {
                    return;
                }
                blockSection.scheduleTick(ChunkUtil.indexBlock(x, y, z), decayTime);
            }
        }
        
        @Override
        public void onEntityRemove(@Nonnull final Ref<ChunkStore> ref, @Nonnull final RemoveReason reason, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
        }
        
        @Nullable
        @Override
        public Query<ChunkStore> getQuery() {
            return OnSoilAdded.QUERY;
        }
        
        static {
            QUERY = Query.and(BlockModule.BlockStateInfo.getComponentType(), TilledSoilBlock.getComponentType());
        }
    }
    
    public static class OnFarmBlockAdded extends RefSystem<ChunkStore>
    {
        private static final Query<ChunkStore> QUERY;
        
        @Override
        public void onEntityAdded(@Nonnull final Ref<ChunkStore> ref, @Nonnull final AddReason reason, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
            final FarmingBlock farmingBlock = commandBuffer.getComponent(ref, FarmingBlock.getComponentType());
            assert farmingBlock != null;
            final BlockModule.BlockStateInfo info = commandBuffer.getComponent(ref, BlockModule.BlockStateInfo.getComponentType());
            assert info != null;
            final BlockChunk blockChunk = commandBuffer.getComponent(info.getChunkRef(), BlockChunk.getComponentType());
            if (farmingBlock.getLastTickGameTime() == null) {
                final int blockId = blockChunk.getBlock(ChunkUtil.xFromBlockInColumn(info.getIndex()), ChunkUtil.yFromBlockInColumn(info.getIndex()), ChunkUtil.zFromBlockInColumn(info.getIndex()));
                final BlockType blockType = BlockType.getAssetMap().getAsset(blockId);
                if (blockType.getFarming() == null) {
                    return;
                }
                farmingBlock.setCurrentStageSet(blockType.getFarming().getStartingStageSet());
                farmingBlock.setLastTickGameTime(store.getExternalData().getWorld().getEntityStore().getStore().getResource(WorldTimeResource.getResourceType()).getGameTime());
                blockChunk.markNeedsSaving();
                if (blockType.getFarming().getStages() != null) {
                    final FarmingStageData[] stages = blockType.getFarming().getStages().get(blockType.getFarming().getStartingStageSet());
                    if (stages != null && stages.length > 0) {
                        boolean found = false;
                        for (int i = 0; i < stages.length; ++i) {
                            final FarmingStageData obj;
                            final FarmingStageData stage = obj = stages[i];
                            Objects.requireNonNull(obj);
                            final BlockStateFarmingStageData blockStateFarmingStageData = (BlockStateFarmingStageData)obj;
                            switch (/* invokedynamic(!) */ProcyonInvokeDynamicHelper_24.invoke(blockStateFarmingStageData, false)) {
                                case 0: {
                                    final BlockTypeFarmingStageData data = (BlockTypeFarmingStageData)blockStateFarmingStageData;
                                    if (data.getBlock().equals(blockType.getId())) {
                                        farmingBlock.setGrowthProgress((float)i);
                                        found = true;
                                        break;
                                    }
                                    break;
                                }
                                case 1: {
                                    final BlockStateFarmingStageData data2 = blockStateFarmingStageData;
                                    final BlockType stateBlockType = blockType.getBlockForState(data2.getState());
                                    if (stateBlockType != null && stateBlockType.getId().equals(blockType.getId())) {
                                        farmingBlock.setGrowthProgress((float)i);
                                        found = true;
                                    }
                                    break;
                                }
                            }
                        }
                        if (!found) {
                            final Ref<ChunkStore> sectionRef = commandBuffer.getComponent(info.getChunkRef(), ChunkColumn.getComponentType()).getSection(ChunkUtil.chunkCoordinate(ChunkUtil.yFromBlockInColumn(info.getIndex())));
                            stages[0].apply(commandBuffer, sectionRef, ref, ChunkUtil.xFromBlockInColumn(info.getIndex()), ChunkUtil.yFromBlockInColumn(info.getIndex()), ChunkUtil.zFromBlockInColumn(info.getIndex()), null);
                        }
                    }
                }
            }
            if (farmingBlock.getLastTickGameTime() == null) {
                farmingBlock.setLastTickGameTime(store.getExternalData().getWorld().getEntityStore().getStore().getResource(WorldTimeResource.getResourceType()).getGameTime());
                blockChunk.markNeedsSaving();
            }
            final int x = ChunkUtil.xFromBlockInColumn(info.getIndex());
            final int y = ChunkUtil.yFromBlockInColumn(info.getIndex());
            final int z = ChunkUtil.zFromBlockInColumn(info.getIndex());
            final BlockComponentChunk blockComponentChunk = commandBuffer.getComponent(info.getChunkRef(), BlockComponentChunk.getComponentType());
            assert blockComponentChunk != null;
            final ChunkColumn column = commandBuffer.getComponent(info.getChunkRef(), ChunkColumn.getComponentType());
            assert column != null;
            final Ref<ChunkStore> section = column.getSection(ChunkUtil.chunkCoordinate(y));
            final BlockSection blockSection = commandBuffer.getComponent(section, BlockSection.getComponentType());
            FarmingUtil.tickFarming(commandBuffer, blockChunk, blockSection, section, ref, farmingBlock, x, y, z, true);
        }
        
        @Override
        public void onEntityRemove(@Nonnull final Ref<ChunkStore> ref, @Nonnull final RemoveReason reason, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
        }
        
        @Nullable
        @Override
        public Query<ChunkStore> getQuery() {
            return OnFarmBlockAdded.QUERY;
        }
        
        static {
            QUERY = Query.and(BlockModule.BlockStateInfo.getComponentType(), FarmingBlock.getComponentType());
        }
        
        // This helper class was generated by Procyon to approximate the behavior of an
        // 'invokedynamic' instruction that it doesn't know how to interpret.
        private static final class ProcyonInvokeDynamicHelper_24
        {
            private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
            private static MethodHandle handle;
            private static volatile int fence;
            
            private static MethodHandle handle() {
                final MethodHandle handle = ProcyonInvokeDynamicHelper_24.handle;
                if (handle != null)
                    return handle;
                return ProcyonInvokeDynamicHelper_24.ensureHandle();
            }
            
            private static MethodHandle ensureHandle() {
                ProcyonInvokeDynamicHelper_24.fence = 0;
                MethodHandle handle = ProcyonInvokeDynamicHelper_24.handle;
                if (handle == null) {
                    MethodHandles.Lookup lookup = ProcyonInvokeDynamicHelper_24.LOOKUP;
                    try {
                        handle = ((CallSite)SwitchBootstraps.typeSwitch(lookup, "typeSwitch", MethodType.methodType(int.class, Object.class, int.class), BlockTypeFarmingStageData.class, BlockStateFarmingStageData.class)).dynamicInvoker();
                    }
                    catch (Throwable t) {
                        throw new UndeclaredThrowableException(t);
                    }
                    ProcyonInvokeDynamicHelper_24.fence = 1;
                    ProcyonInvokeDynamicHelper_24.handle = handle;
                    ProcyonInvokeDynamicHelper_24.fence = 0;
                }
                return handle;
            }
            
            private static int invoke(Object p0, int p1) {
                try {
                    return ProcyonInvokeDynamicHelper_24.handle().invokeExact(p0, p1);
                }
                catch (Throwable t) {
                    throw new UndeclaredThrowableException(t);
                }
            }
        }
    }
    
    public static class Ticking extends EntityTickingSystem<ChunkStore>
    {
        private static final Query<ChunkStore> QUERY;
        static final /* synthetic */ boolean $assertionsDisabled;
        
        @Override
        public void tick(final float dt, final int index, @Nonnull final ArchetypeChunk<ChunkStore> archetypeChunk, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
            final BlockSection blocks = archetypeChunk.getComponent(index, BlockSection.getComponentType());
            assert blocks != null;
            if (blocks.getTickingBlocksCountCopy() == 0) {
                return;
            }
            final ChunkSection section = archetypeChunk.getComponent(index, ChunkSection.getComponentType());
            assert section != null;
            if (section.getChunkColumnReference() == null || !section.getChunkColumnReference().isValid()) {
                return;
            }
            final BlockComponentChunk blockComponentChunk = commandBuffer.getComponent(section.getChunkColumnReference(), BlockComponentChunk.getComponentType());
            assert blockComponentChunk != null;
            final Ref<ChunkStore> ref = archetypeChunk.getReferenceTo(index);
            final BlockChunk blockChunk = commandBuffer.getComponent(section.getChunkColumnReference(), BlockChunk.getComponentType());
            assert blockChunk != null;
            blocks.forEachTicking(blockComponentChunk, commandBuffer, section.getY(), (blockComponentChunk1, commandBuffer1, localX, localY, localZ, blockId) -> {
                final Ref<ChunkStore> blockRef = blockComponentChunk1.getEntityReference(ChunkUtil.indexBlockInColumn(localX, localY, localZ));
                if (blockRef == null) {
                    return BlockTickStrategy.IGNORED;
                }
                else {
                    final FarmingBlock farming = commandBuffer1.getComponent(blockRef, FarmingBlock.getComponentType());
                    if (farming != null) {
                        FarmingUtil.tickFarming(commandBuffer1, blockChunk, blocks, ref, blockRef, farming, localX, localY, localZ, false);
                        return BlockTickStrategy.SLEEP;
                    }
                    else {
                        final TilledSoilBlock soil = commandBuffer1.getComponent(blockRef, TilledSoilBlock.getComponentType());
                        if (soil != null) {
                            tickSoil(commandBuffer1, blockComponentChunk1, blockRef, soil);
                            return BlockTickStrategy.SLEEP;
                        }
                        else {
                            final CoopBlock coop = commandBuffer1.getComponent(blockRef, CoopBlock.getComponentType());
                            if (coop != null) {
                                tickCoop(commandBuffer1, blockComponentChunk1, blockRef, coop);
                                return BlockTickStrategy.SLEEP;
                            }
                            else {
                                return BlockTickStrategy.IGNORED;
                            }
                        }
                    }
                }
            });
        }
        
        private static void tickSoil(final CommandBuffer<ChunkStore> commandBuffer, final BlockComponentChunk blockComponentChunk, final Ref<ChunkStore> blockRef, final TilledSoilBlock soilBlock) {
            final BlockModule.BlockStateInfo info = commandBuffer.getComponent(blockRef, BlockModule.BlockStateInfo.getComponentType());
            assert info != null;
            final int x = ChunkUtil.xFromBlockInColumn(info.getIndex());
            final int y = ChunkUtil.yFromBlockInColumn(info.getIndex());
            final int z = ChunkUtil.zFromBlockInColumn(info.getIndex());
            if (y >= 320) {
                return;
            }
            assert info.getChunkRef() != null;
            final BlockChunk blockChunk = commandBuffer.getComponent(info.getChunkRef(), BlockChunk.getComponentType());
            assert blockChunk != null;
            final BlockSection blockSection = blockChunk.getSectionAtBlockY(y);
            final boolean hasCrop = FarmingSystems.hasCropAbove(blockChunk, x, y, z);
            final BlockType blockType = BlockType.getAssetMap().getAsset(blockSection.get(x, y, z));
            final Instant currentTime = commandBuffer.getExternalData().getWorld().getEntityStore().getStore().getResource(WorldTimeResource.getResourceType()).getGameTime();
            final Instant decayTime = soilBlock.getDecayTime();
            int targetBlockId = 0;
            BlockType targetBlockType = null;
            int rotation = 0;
            WorldChunk worldChunk = null;
            if (soilBlock.isPlanted() && !hasCrop) {
                if (!FarmingSystems.updateSoilDecayTime(commandBuffer, soilBlock, blockType)) {
                    return;
                }
                if (decayTime != null) {
                    blockSection.scheduleTick(ChunkUtil.indexBlock(x, y, z), decayTime);
                }
            }
            else if (!soilBlock.isPlanted() && !hasCrop) {
                if (decayTime == null || !decayTime.isAfter(currentTime)) {
                    assert info.getChunkRef() != null;
                    if (blockType == null || blockType.getFarming() == null || blockType.getFarming().getSoilConfig() == null) {
                        return;
                    }
                    final FarmingData.SoilConfig soilConfig = blockType.getFarming().getSoilConfig();
                    final String targetBlock = soilConfig.getTargetBlock();
                    if (targetBlock == null) {
                        return;
                    }
                    targetBlockId = BlockType.getAssetMap().getIndex(targetBlock);
                    if (targetBlockId == Integer.MIN_VALUE) {
                        return;
                    }
                    targetBlockType = BlockType.getAssetMap().getAsset(targetBlockId);
                    rotation = blockSection.getRotationIndex(x, y, z);
                    worldChunk = commandBuffer.getComponent(info.getChunkRef(), WorldChunk.getComponentType());
                    commandBuffer.run(_store -> worldChunk.setBlock(x, y, z, targetBlockId, targetBlockType, rotation, 0, 0));
                    return;
                }
            }
            else if (hasCrop) {
                soilBlock.setDecayTime(null);
            }
            final String targetBlock2 = soilBlock.computeBlockType(currentTime, blockType);
            if (targetBlock2 != null && !targetBlock2.equals(blockType.getId())) {
                final WorldChunk worldChunk2 = commandBuffer.getComponent(info.getChunkRef(), WorldChunk.getComponentType());
                final int rotation2 = blockSection.getRotationIndex(x, y, z);
                final int targetBlockId2 = BlockType.getAssetMap().getIndex(targetBlock2);
                final BlockType targetBlockType2 = BlockType.getAssetMap().getAsset(targetBlockId2);
                commandBuffer.run(_store -> worldChunk.setBlock(x, y, z, targetBlockId, targetBlockType, rotation, 0, 2));
            }
            soilBlock.setPlanted(hasCrop);
        }
        
        private static void tickCoop(final CommandBuffer<ChunkStore> commandBuffer, final BlockComponentChunk blockComponentChunk, final Ref<ChunkStore> blockRef, final CoopBlock coopBlock) {
            final BlockModule.BlockStateInfo info = commandBuffer.getComponent(blockRef, BlockModule.BlockStateInfo.getComponentType());
            assert info != null;
            final Store<EntityStore> store = commandBuffer.getExternalData().getWorld().getEntityStore().getStore();
            final WorldTimeResource worldTimeResource = store.getResource(WorldTimeResource.getResourceType());
            final FarmingCoopAsset coopAsset = coopBlock.getCoopAsset();
            if (coopAsset == null) {
                return;
            }
            final int x = ChunkUtil.xFromBlockInColumn(info.getIndex());
            final int y = ChunkUtil.yFromBlockInColumn(info.getIndex());
            final int z = ChunkUtil.zFromBlockInColumn(info.getIndex());
            final BlockChunk blockChunk = commandBuffer.getComponent(info.getChunkRef(), BlockChunk.getComponentType());
            assert blockChunk != null;
            final ChunkColumn column = commandBuffer.getComponent(info.getChunkRef(), ChunkColumn.getComponentType());
            assert column != null;
            final Ref<ChunkStore> sectionRef = column.getSection(ChunkUtil.chunkCoordinate(y));
            assert sectionRef != null;
            final BlockSection blockSection = commandBuffer.getComponent(sectionRef, BlockSection.getComponentType());
            assert blockSection != null;
            final ChunkSection chunkSection = commandBuffer.getComponent(sectionRef, ChunkSection.getComponentType());
            assert chunkSection != null;
            final int worldX = ChunkUtil.worldCoordFromLocalCoord(chunkSection.getX(), x);
            final int worldY = ChunkUtil.worldCoordFromLocalCoord(chunkSection.getY(), y);
            final int worldZ = ChunkUtil.worldCoordFromLocalCoord(chunkSection.getZ(), z);
            final World world = commandBuffer.getExternalData().getWorld();
            final WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(worldX, worldZ));
            final double blockRotation = chunk.getRotation(worldX, worldY, worldZ).yaw().getRadians();
            final Vector3d spawnOffset = new Vector3d().assign(coopAsset.getResidentSpawnOffset()).rotateY((float)blockRotation);
            final Vector3i coopLocation = new Vector3i(worldX, worldY, worldZ);
            final boolean tryCapture = coopAsset.getCaptureWildNPCsInRange();
            final float captureRange = coopAsset.getWildCaptureRadius();
            if (tryCapture && captureRange >= 0.0f) {
                world.execute(() -> {
                    final List<Ref<EntityStore>> entities = TargetUtil.getAllEntitiesInSphere(coopLocation.toVector3d(), captureRange, store);
                    for (final Ref<EntityStore> entity : entities) {
                        coopBlock.tryPutWildResidentFromWild(store, entity, worldTimeResource, coopLocation);
                    }
                    return;
                });
            }
            if (coopBlock.shouldResidentsBeInCoop(worldTimeResource)) {
                world.execute(() -> coopBlock.ensureNoResidentsInWorld(store));
            }
            else {
                world.execute(() -> {
                    coopBlock.ensureSpawnResidentsInWorld(world, store, coopLocation.toVector3d(), spawnOffset);
                    coopBlock.generateProduceToInventory(worldTimeResource);
                    final Vector3i blockPos = new Vector3i(worldX, worldY, worldZ);
                    final BlockType currentBlockType = world.getBlockType(blockPos);
                    if (!Ticking.$assertionsDisabled && currentBlockType == null) {
                        throw new AssertionError();
                    }
                    else {
                        chunk.setBlockInteractionState(blockPos, currentBlockType, coopBlock.hasProduce() ? "Produce_Ready" : "default");
                        return;
                    }
                });
            }
            final Instant nextTickInstant = coopBlock.getNextScheduledTick(worldTimeResource);
            if (nextTickInstant != null) {
                blockSection.scheduleTick(ChunkUtil.indexBlock(x, y, z), nextTickInstant);
            }
        }
        
        @Nullable
        @Override
        public Query<ChunkStore> getQuery() {
            return Ticking.QUERY;
        }
        
        static {
            QUERY = Query.and(BlockSection.getComponentType(), ChunkSection.getComponentType());
        }
    }
    
    public static class OnCoopAdded extends RefSystem<ChunkStore>
    {
        private static final Query<ChunkStore> QUERY;
        
        @Override
        public void onEntityAdded(@Nonnull final Ref<ChunkStore> ref, @Nonnull final AddReason reason, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
            final CoopBlock coopBlock = commandBuffer.getComponent(ref, CoopBlock.getComponentType());
            if (coopBlock == null) {
                return;
            }
            final WorldTimeResource worldTimeResource = commandBuffer.getExternalData().getWorld().getEntityStore().getStore().getResource(WorldTimeResource.getResourceType());
            final BlockModule.BlockStateInfo info = commandBuffer.getComponent(ref, BlockModule.BlockStateInfo.getComponentType());
            assert info != null;
            final int x = ChunkUtil.xFromBlockInColumn(info.getIndex());
            final int y = ChunkUtil.yFromBlockInColumn(info.getIndex());
            final int z = ChunkUtil.zFromBlockInColumn(info.getIndex());
            final BlockChunk blockChunk = commandBuffer.getComponent(info.getChunkRef(), BlockChunk.getComponentType());
            assert blockChunk != null;
            final BlockSection blockSection = blockChunk.getSectionAtBlockY(y);
            blockSection.scheduleTick(ChunkUtil.indexBlock(x, y, z), coopBlock.getNextScheduledTick(worldTimeResource));
        }
        
        @Override
        public void onEntityRemove(@Nonnull final Ref<ChunkStore> ref, @Nonnull final RemoveReason reason, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
            if (reason == RemoveReason.UNLOAD) {
                return;
            }
            final CoopBlock coop = commandBuffer.getComponent(ref, CoopBlock.getComponentType());
            if (coop == null) {
                return;
            }
            final BlockModule.BlockStateInfo info = commandBuffer.getComponent(ref, BlockModule.BlockStateInfo.getComponentType());
            assert info != null;
            final Store<EntityStore> entityStore = commandBuffer.getExternalData().getWorld().getEntityStore().getStore();
            final int x = ChunkUtil.xFromBlockInColumn(info.getIndex());
            final int y = ChunkUtil.yFromBlockInColumn(info.getIndex());
            final int z = ChunkUtil.zFromBlockInColumn(info.getIndex());
            final BlockChunk blockChunk = commandBuffer.getComponent(info.getChunkRef(), BlockChunk.getComponentType());
            assert blockChunk != null;
            final ChunkColumn column = commandBuffer.getComponent(info.getChunkRef(), ChunkColumn.getComponentType());
            assert column != null;
            final Ref<ChunkStore> sectionRef = column.getSection(ChunkUtil.chunkCoordinate(y));
            assert sectionRef != null;
            final BlockSection blockSection = commandBuffer.getComponent(sectionRef, BlockSection.getComponentType());
            assert blockSection != null;
            final ChunkSection chunkSection = commandBuffer.getComponent(sectionRef, ChunkSection.getComponentType());
            assert chunkSection != null;
            final int worldX = ChunkUtil.worldCoordFromLocalCoord(chunkSection.getX(), x);
            final int worldY = ChunkUtil.worldCoordFromLocalCoord(chunkSection.getY(), y);
            final int worldZ = ChunkUtil.worldCoordFromLocalCoord(chunkSection.getZ(), z);
            final World world = commandBuffer.getExternalData().getWorld();
            final WorldTimeResource worldTimeResource = world.getEntityStore().getStore().getResource(WorldTimeResource.getResourceType());
            coop.handleBlockBroken(world, worldTimeResource, entityStore, worldX, worldY, worldZ);
        }
        
        @Nullable
        @Override
        public Query<ChunkStore> getQuery() {
            return OnCoopAdded.QUERY;
        }
        
        static {
            QUERY = Query.and(BlockModule.BlockStateInfo.getComponentType(), CoopBlock.getComponentType());
        }
    }
    
    public static class CoopResidentEntitySystem extends RefSystem<EntityStore>
    {
        private static final ComponentType<EntityStore, CoopResidentComponent> componentType;
        
        @Override
        public Query<EntityStore> getQuery() {
            return CoopResidentEntitySystem.componentType;
        }
        
        @Override
        public void onEntityAdded(@Nonnull final Ref<EntityStore> ref, @Nonnull final AddReason reason, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
        }
        
        @Override
        public void onEntityRemove(@Nonnull final Ref<EntityStore> ref, @Nonnull final RemoveReason reason, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            if (reason == RemoveReason.UNLOAD) {
                return;
            }
            final UUIDComponent uuidComponent = commandBuffer.getComponent(ref, UUIDComponent.getComponentType());
            if (uuidComponent == null) {
                return;
            }
            final UUID uuid = uuidComponent.getUuid();
            final CoopResidentComponent coopResidentComponent = commandBuffer.getComponent(ref, CoopResidentEntitySystem.componentType);
            if (coopResidentComponent == null) {
                return;
            }
            final Vector3i coopPosition = coopResidentComponent.getCoopLocation();
            final World world = commandBuffer.getExternalData().getWorld();
            final long chunkIndex = ChunkUtil.indexChunkFromBlock(coopPosition.x, coopPosition.z);
            final WorldChunk chunk = world.getChunkIfLoaded(chunkIndex);
            if (chunk == null) {
                return;
            }
            final Ref<ChunkStore> chunkReference = world.getChunkStore().getChunkReference(chunkIndex);
            if (chunkReference == null || !chunkReference.isValid()) {
                return;
            }
            final Store<ChunkStore> chunkStore = world.getChunkStore().getStore();
            final ChunkColumn chunkColumnComponent = chunkStore.getComponent(chunkReference, ChunkColumn.getComponentType());
            if (chunkColumnComponent == null) {
                return;
            }
            final BlockChunk blockChunkComponent = chunkStore.getComponent(chunkReference, BlockChunk.getComponentType());
            if (blockChunkComponent == null) {
                return;
            }
            final Ref<ChunkStore> sectionRef = chunkColumnComponent.getSection(ChunkUtil.chunkCoordinate(coopPosition.y));
            if (sectionRef == null || !sectionRef.isValid()) {
                return;
            }
            final BlockComponentChunk blockComponentChunk = chunkStore.getComponent(chunkReference, BlockComponentChunk.getComponentType());
            if (blockComponentChunk == null) {
                return;
            }
            final int blockIndexColumn = ChunkUtil.indexBlockInColumn(coopPosition.x, coopPosition.y, coopPosition.z);
            final Ref<ChunkStore> coopEntityReference = blockComponentChunk.getEntityReference(blockIndexColumn);
            if (coopEntityReference == null) {
                return;
            }
            final CoopBlock coop = chunkStore.getComponent(coopEntityReference, CoopBlock.getComponentType());
            if (coop == null) {
                return;
            }
            coop.handleResidentDespawn(uuid);
        }
        
        static {
            componentType = CoopResidentComponent.getComponentType();
        }
    }
    
    public static class CoopResidentTicking extends EntityTickingSystem<EntityStore>
    {
        private static final ComponentType<EntityStore, CoopResidentComponent> componentType;
        
        @Override
        public Query<EntityStore> getQuery() {
            return CoopResidentTicking.componentType;
        }
        
        @Override
        public void tick(final float dt, final int index, @Nonnull final ArchetypeChunk<EntityStore> archetypeChunk, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            final CoopResidentComponent coopResidentComponent = archetypeChunk.getComponent(index, CoopResidentComponent.getComponentType());
            if (coopResidentComponent == null) {
                return;
            }
            if (coopResidentComponent.getMarkedForDespawn()) {
                commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE);
            }
        }
        
        static {
            componentType = CoopResidentComponent.getComponentType();
        }
    }
    
    @Deprecated(forRemoval = true)
    public static class MigrateFarming extends BlockModule.MigrationSystem
    {
        @Override
        public void onEntityAdd(@Nonnull final Holder<ChunkStore> holder, @Nonnull final AddReason reason, @Nonnull final Store<ChunkStore> store) {
            final FarmingBlockState oldState = holder.getComponent(FarmingPlugin.get().getFarmingBlockStateComponentType());
            final FarmingBlock farming = new FarmingBlock();
            farming.setGrowthProgress((float)oldState.getCurrentFarmingStageIndex());
            farming.setCurrentStageSet(oldState.getCurrentFarmingStageSetName());
            farming.setSpreadRate(oldState.getSpreadRate());
            holder.putComponent(FarmingBlock.getComponentType(), farming);
            holder.removeComponent(FarmingPlugin.get().getFarmingBlockStateComponentType());
        }
        
        @Override
        public void onEntityRemoved(@Nonnull final Holder<ChunkStore> holder, @Nonnull final RemoveReason reason, @Nonnull final Store<ChunkStore> store) {
        }
        
        @Nullable
        @Override
        public Query<ChunkStore> getQuery() {
            return FarmingPlugin.get().getFarmingBlockStateComponentType();
        }
    }
}
