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

package com.hypixel.hytale.server.core.modules.interaction;

import it.unimi.dsi.fastutil.objects.ObjectLists;
import java.util.Collection;
import com.hypixel.hytale.server.core.modules.item.ItemModule;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.server.core.universe.world.connectedblocks.ConnectedBlocksUtil;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple;
import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes;
import java.util.Iterator;
import com.hypixel.hytale.server.core.entity.ItemUtils;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.HarvestingDropType;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.PhysicsDropType;
import com.hypixel.hytale.server.core.event.events.ecs.BreakBlockEvent;
import com.hypixel.hytale.server.core.inventory.container.ItemContainer;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.server.core.modules.blockhealth.BlockHealth;
import it.unimi.dsi.fastutil.objects.ObjectList;
import com.hypixel.hytale.server.core.asset.type.gameplay.GatheringEffectsConfig;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.SoftBlockDropType;
import com.hypixel.hytale.server.core.asset.type.gameplay.BrokenPenalties;
import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection;
import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.component.AddReason;
import com.hypixel.hytale.server.core.modules.entity.item.ItemComponent;
import com.hypixel.hytale.math.vector.Vector3f;
import com.hypixel.hytale.protocol.SoundCategory;
import com.hypixel.hytale.protocol.BlockSoundEvent;
import com.hypixel.hytale.server.core.asset.type.blocksound.config.BlockSoundSet;
import com.hypixel.hytale.server.core.event.events.ecs.DamageBlockEvent;
import com.hypixel.hytale.server.core.modules.blockhealth.BlockHealthModule;
import com.hypixel.hytale.server.core.modules.blockhealth.BlockHealthChunk;
import com.hypixel.hytale.server.core.modules.time.TimeResource;
import com.hypixel.hytale.server.core.blocktype.component.BlockPhysics;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn;
import com.hypixel.hytale.server.core.universe.world.SoundUtil;
import com.hypixel.hytale.server.core.universe.world.ParticleUtil;
import java.util.List;
import com.hypixel.hytale.server.core.util.FillerBlockUtil;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.server.core.modules.entity.EntityModule;
import com.hypixel.hytale.component.spatial.SpatialResource;
import com.hypixel.hytale.server.core.asset.type.environment.config.Environment;
import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk;
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
import com.hypixel.hytale.server.core.entity.LivingEntity;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.server.core.inventory.ItemStack;
import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.server.core.modules.blockset.BlockSetModule;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockBreakingDropType;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockGathering;
import java.util.Objects;
import com.hypixel.hytale.server.core.asset.type.item.config.ItemToolSpec;
import com.hypixel.hytale.server.core.asset.type.item.config.ItemTool;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.core.asset.type.item.config.Item;

public class BlockHarvestUtils
{
    @Nullable
    public static ItemToolSpec getSpecPowerDamageBlock(@Nullable final Item item, @Nullable final BlockType blockType, @Nullable final ItemTool tool) {
        if (blockType == null) {
            return null;
        }
        final BlockGathering gathering = blockType.getGathering();
        if (gathering == null) {
            return null;
        }
        final BlockBreakingDropType breaking = gathering.getBreaking();
        if (breaking == null) {
            return null;
        }
        final String gatherType = breaking.getGatherType();
        if (gatherType == null) {
            return null;
        }
        if (item != null && (item.getWeapon() != null || item.getBuilderToolData() != null)) {
            return null;
        }
        final int requiredQuality = breaking.getQuality();
        if (tool != null) {
            if (tool.getSpecs() != null) {
                final ItemToolSpec[] specs = tool.getSpecs();
                final int length = specs.length;
                int i = 0;
                while (i < length) {
                    final ItemToolSpec spec = specs[i];
                    if (Objects.equals(spec.getGatherType(), gatherType)) {
                        if (spec.getQuality() < requiredQuality) {
                            return null;
                        }
                        return spec;
                    }
                    else {
                        ++i;
                    }
                }
            }
            return null;
        }
        final ItemToolSpec defaultSpec = ItemToolSpec.getAssetMap().getAsset(gatherType);
        if (defaultSpec != null && defaultSpec.getQuality() < requiredQuality) {
            return null;
        }
        return defaultSpec;
    }
    
    public static double calculateDurabilityUse(@Nonnull final Item item, @Nullable final BlockType blockType) {
        if (blockType == null) {
            return 0.0;
        }
        if (blockType.getGathering().isSoft()) {
            return 0.0;
        }
        if (item.getTool() == null) {
            return 0.0;
        }
        final ItemTool itemTool = item.getTool();
        final ItemTool.DurabilityLossBlockTypes[] durabilityLossBlockTypes = itemTool.getDurabilityLossBlockTypes();
        if (durabilityLossBlockTypes == null) {
            return item.getDurabilityLossOnHit();
        }
        final String hitBlockTypeId = blockType.getId();
        final int hitBlockTypeIndex = BlockType.getAssetMap().getIndex(hitBlockTypeId);
        if (hitBlockTypeIndex == Integer.MIN_VALUE) {
            throw new IllegalArgumentException("Unknown key! " + hitBlockTypeId);
        }
        final BlockSetModule blockSetModule = BlockSetModule.getInstance();
        for (final ItemTool.DurabilityLossBlockTypes durabilityLossBlockType : durabilityLossBlockTypes) {
            final int[] blockTypeIndexes = durabilityLossBlockType.getBlockTypeIndexes();
            if (blockTypeIndexes != null) {
                for (final int blockTypeIndex : blockTypeIndexes) {
                    if (blockTypeIndex == hitBlockTypeIndex) {
                        return durabilityLossBlockType.getDurabilityLossOnHit();
                    }
                }
            }
            final int[] blockSetIndexes = durabilityLossBlockType.getBlockSetIndexes();
            if (blockSetIndexes != null) {
                for (final int blockSetIndex : blockSetIndexes) {
                    if (blockSetModule.blockInSet(blockSetIndex, hitBlockTypeId)) {
                        return durabilityLossBlockType.getDurabilityLossOnHit();
                    }
                }
            }
        }
        return item.getDurabilityLossOnHit();
    }
    
    public static boolean performBlockDamage(@Nonnull final Vector3i targetBlock, @Nullable final ItemStack itemStack, @Nullable final ItemTool tool, final float damageScale, final int setBlockSettings, @Nonnull final Ref<ChunkStore> chunkReference, @Nonnull final CommandBuffer<EntityStore> commandBuffer, @Nonnull final ComponentAccessor<ChunkStore> chunkStore) {
        return performBlockDamage(null, null, targetBlock, itemStack, tool, null, false, damageScale, setBlockSettings, chunkReference, commandBuffer, chunkStore);
    }
    
    public static boolean performBlockDamage(@Nullable final LivingEntity entity, @Nullable final Ref<EntityStore> ref, @Nonnull Vector3i targetBlockPos, @Nullable final ItemStack itemStack, @Nullable ItemTool tool, @Nullable final String toolId, final boolean matchTool, float damageScale, final int setBlockSettings, @Nonnull final Ref<ChunkStore> chunkReference, @Nonnull final ComponentAccessor<EntityStore> entityStore, @Nonnull final ComponentAccessor<ChunkStore> chunkStore) {
        final World world = entityStore.getExternalData().getWorld();
        final GameplayConfig gameplayConfig = world.getGameplayConfig();
        final WorldChunk worldChunkComponent = chunkStore.getComponent(chunkReference, WorldChunk.getComponentType());
        if (worldChunkComponent == null) {
            return false;
        }
        final BlockChunk blockChunkComponent = chunkStore.getComponent(chunkReference, BlockChunk.getComponentType());
        assert blockChunkComponent != null;
        BlockSection targetSection = blockChunkComponent.getSectionAtBlockY(targetBlockPos.y);
        int targetRotationIndex = targetSection.getRotationIndex(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z);
        boolean brokeBlock = false;
        final int environmentId = blockChunkComponent.getEnvironment(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z);
        final Environment environmentAsset = Environment.getAssetMap().getAsset(environmentId);
        final SpatialResource<Ref<EntityStore>, EntityStore> playerSpatialResource = entityStore.getResource(EntityModule.get().getPlayerSpatialResourceType());
        if (environmentAsset != null && !environmentAsset.isBlockModificationAllowed()) {
            targetSection.invalidateBlock(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z);
            return false;
        }
        BlockType targetBlockType = worldChunkComponent.getBlockType(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z);
        if (targetBlockType == null) {
            return false;
        }
        BlockGathering blockGathering = targetBlockType.getGathering();
        if (blockGathering == null) {
            return false;
        }
        if (matchTool && !blockGathering.getToolData().containsKey(toolId)) {
            return false;
        }
        final Vector3d targetBlockCenterPos = new Vector3d();
        targetBlockType.getBlockCenter(targetRotationIndex, targetBlockCenterPos);
        targetBlockCenterPos.add(targetBlockPos);
        Vector3i originBlock = new Vector3i(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z);
        if (!targetBlockType.isUnknown()) {
            final int filler = targetSection.getFiller(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z);
            final int fillerX = FillerBlockUtil.unpackX(filler);
            final int fillerY = FillerBlockUtil.unpackY(filler);
            final int fillerZ = FillerBlockUtil.unpackZ(filler);
            if (fillerX != 0 || fillerY != 0 || fillerZ != 0) {
                originBlock = originBlock.clone().subtract(fillerX, fillerY, fillerZ);
                final String oldBlockTypeKey = targetBlockType.getId();
                targetBlockType = world.getBlockType(originBlock.getX(), originBlock.getY(), originBlock.getZ());
                if (targetBlockType == null) {
                    return false;
                }
                if (!oldBlockTypeKey.equals(targetBlockType.getId())) {
                    worldChunkComponent.breakBlock(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z);
                    return true;
                }
                blockGathering = targetBlockType.getGathering();
                if (blockGathering == null) {
                    return false;
                }
            }
        }
        final Item heldItem = (itemStack != null) ? itemStack.getItem() : null;
        if (tool == null && heldItem != null) {
            tool = heldItem.getTool();
        }
        final ItemToolSpec itemToolSpec = getSpecPowerDamageBlock(heldItem, targetBlockType, tool);
        float specPower = (itemToolSpec != null) ? itemToolSpec.getPower() : 0.0f;
        final boolean canApplyItemStackPenalties = entity != null && entity.canApplyItemStackPenalties(ref, entityStore);
        if (specPower != 0.0f && heldItem != null && heldItem.getTool() != null && itemStack.isBroken() && canApplyItemStackPenalties) {
            final BrokenPenalties brokenPenalties = gameplayConfig.getItemDurabilityConfig().getBrokenPenalties();
            specPower *= 1.0f - (float)brokenPenalties.getTool(0.0);
        }
        int dropQuantity = 1;
        float damage;
        String itemId;
        String dropListId;
        if (specPower != 0.0f) {
            final BlockBreakingDropType breaking = blockGathering.getBreaking();
            damage = specPower;
            dropQuantity = breaking.getQuantity();
            itemId = breaking.getItemId();
            dropListId = breaking.getDropListId();
        }
        else if (blockGathering.isSoft()) {
            final SoftBlockDropType soft = blockGathering.getSoft();
            if (!soft.isWeaponBreakable() && heldItem != null && heldItem.getWeapon() != null) {
                return false;
            }
            damage = 1.0f;
            itemId = soft.getItemId();
            dropListId = soft.getDropListId();
            damageScale = 1.0f;
        }
        else {
            if (heldItem != null && heldItem.getWeapon() == null) {
                if (ref != null && entity != null) {
                    final GatheringEffectsConfig unbreakableBlockConfig = gameplayConfig.getGatheringConfig().getUnbreakableBlockConfig();
                    if ((setBlockSettings & 0x4) == 0x0) {
                        final String particleSystemId = unbreakableBlockConfig.getParticleSystemId();
                        if (particleSystemId != null) {
                            final ObjectList<Ref<EntityStore>> results = SpatialResource.getThreadLocalReferenceList();
                            playerSpatialResource.getSpatialStructure().collect(targetBlockCenterPos, 75.0, results);
                            ParticleUtil.spawnParticleEffect(particleSystemId, targetBlockCenterPos, results, entityStore);
                        }
                    }
                    if ((setBlockSettings & 0x400) == 0x0) {
                        final int soundEventIndex = unbreakableBlockConfig.getSoundEventIndex();
                        if (soundEventIndex != 0) {
                            SoundUtil.playSoundEvent3d(ref, soundEventIndex, targetBlockCenterPos, entityStore);
                        }
                        if (heldItem.getTool() != null) {
                            final int hitSoundEventLayerIndex = heldItem.getTool().getIncorrectMaterialSoundLayerIndex();
                            if (hitSoundEventLayerIndex != 0) {
                                SoundUtil.playSoundEvent3d(ref, hitSoundEventLayerIndex, targetBlockCenterPos, entityStore);
                            }
                        }
                    }
                }
                return false;
            }
            return false;
        }
        damage *= damageScale;
        final ChunkColumn chunkColumnComponent = chunkStore.getComponent(chunkReference, ChunkColumn.getComponentType());
        final Ref<ChunkStore> chunkSectionRef = (chunkColumnComponent != null) ? chunkColumnComponent.getSection(ChunkUtil.chunkCoordinate(targetBlockPos.y)) : null;
        if (targetBlockType.getGathering().shouldUseDefaultDropWhenPlaced()) {
            final BlockPhysics decoBlocks = (chunkSectionRef != null) ? chunkStore.getComponent(chunkSectionRef, BlockPhysics.getComponentType()) : null;
            final boolean isDeco = decoBlocks != null && decoBlocks.isDeco(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z);
            if (isDeco) {
                itemId = null;
                dropListId = null;
            }
        }
        final TimeResource timeResource = entityStore.getResource(TimeResource.getResourceType());
        final BlockHealthChunk blockHealthComponent = chunkStore.getComponent(chunkReference, BlockHealthModule.get().getBlockHealthChunkComponentType());
        assert blockHealthComponent != null;
        final float current = blockHealthComponent.getBlockHealth(originBlock);
        final DamageBlockEvent event = new DamageBlockEvent(itemStack, originBlock, targetBlockType, current, damage);
        if (ref != null) {
            entityStore.invoke(ref, event);
        }
        else {
            entityStore.invoke(event);
        }
        if (event.isCancelled()) {
            targetSection.invalidateBlock(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z);
            return false;
        }
        damage = event.getDamage();
        targetBlockType = event.getBlockType();
        targetBlockPos = event.getTargetBlock();
        targetSection = blockChunkComponent.getSectionAtBlockY(targetBlockPos.y);
        targetRotationIndex = targetSection.getRotationIndex(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z);
        targetBlockType.getBlockCenter(targetRotationIndex, targetBlockCenterPos);
        targetBlockCenterPos.add(targetBlockPos);
        final BlockHealth blockDamage = blockHealthComponent.damageBlock(timeResource.getNow(), world, targetBlockPos, damage);
        if (blockHealthComponent.isBlockFragile(targetBlockPos) || blockDamage.isDestroyed()) {
            final BlockGathering.BlockToolData requiredTool = blockGathering.getToolData().get(toolId);
            final boolean toolsMatch = requiredTool != null;
            if (!toolsMatch) {
                performBlockBreak(world, targetBlockPos, targetBlockType, itemStack, dropQuantity, itemId, dropListId, setBlockSettings, ref, chunkReference, entityStore, chunkStore);
                brokeBlock = true;
            }
            else {
                final String toolStateId = requiredTool.getStateId();
                final BlockType newBlockType = (toolStateId != null) ? targetBlockType.getBlockForState(toolStateId) : null;
                final boolean shouldChangeState = newBlockType != null && !targetBlockType.getId().equals(newBlockType.getId());
                if (shouldChangeState) {
                    blockDamage.setHealth(1.0f);
                    worldChunkComponent.setBlock(targetBlockPos.x, targetBlockPos.y, targetBlockPos.z, newBlockType);
                    if ((setBlockSettings & 0x400) == 0x0) {
                        final BlockSoundSet soundSet = BlockSoundSet.getAssetMap().getAsset(targetBlockType.getBlockSoundSetIndex());
                        if (soundSet != null) {
                            final int soundEventIndex2 = soundSet.getSoundEventIndices().getOrDefault(BlockSoundEvent.Break, 0);
                            if (soundEventIndex2 != 0) {
                                SoundUtil.playSoundEvent3d(soundEventIndex2, SoundCategory.SFX, targetBlockCenterPos, entityStore);
                            }
                        }
                    }
                    if ((setBlockSettings & 0x800) == 0x0) {
                        final List<ItemStack> itemStacks = getDrops(targetBlockType, 1, requiredTool.getItemId(), requiredTool.getDropListId());
                        final Vector3d dropPosition = new Vector3d(targetBlockPos.x + 0.5, targetBlockPos.y, targetBlockPos.z + 0.5);
                        final Holder<EntityStore>[] itemEntityHolders = ItemComponent.generateItemDrops(entityStore, itemStacks, dropPosition, Vector3f.ZERO);
                        entityStore.addEntities(itemEntityHolders, AddReason.SPAWN);
                    }
                }
                else {
                    performBlockBreak(world, targetBlockPos, targetBlockType, itemStack, dropQuantity, itemId, dropListId, setBlockSettings | 0x800, ref, chunkReference, entityStore, chunkStore);
                    brokeBlock = true;
                    if ((setBlockSettings & 0x800) == 0x0) {
                        final List<ItemStack> toolDrops = getDrops(targetBlockType, 1, requiredTool.getItemId(), requiredTool.getDropListId());
                        if (!toolDrops.isEmpty()) {
                            final Vector3d dropPosition = new Vector3d(targetBlockPos.x + 0.5, targetBlockPos.y, targetBlockPos.z + 0.5);
                            final Holder<EntityStore>[] itemEntityHolders = ItemComponent.generateItemDrops(entityStore, toolDrops, dropPosition, Vector3f.ZERO);
                            entityStore.addEntities(itemEntityHolders, AddReason.SPAWN);
                        }
                    }
                }
            }
        }
        else if ((setBlockSettings & 0x400) == 0x0) {
            final BlockSoundSet soundSet2 = BlockSoundSet.getAssetMap().getAsset(targetBlockType.getBlockSoundSetIndex());
            if (soundSet2 != null) {
                final int soundEventIndex3 = soundSet2.getSoundEventIndices().getOrDefault(BlockSoundEvent.Hit, 0);
                if (soundEventIndex3 != 0) {
                    SoundUtil.playSoundEvent3d(soundEventIndex3, SoundCategory.SFX, targetBlockCenterPos, entityStore);
                }
            }
        }
        if (ref != null && entity != null) {
            if ((setBlockSettings & 0x400) == 0x0 && !targetBlockCenterPos.equals(Vector3d.MAX)) {
                int hitSoundEventLayerIndex2 = 0;
                if (itemToolSpec != null) {
                    hitSoundEventLayerIndex2 = itemToolSpec.getHitSoundLayerIndex();
                }
                if (hitSoundEventLayerIndex2 == 0 && heldItem != null && heldItem.getTool() != null) {
                    hitSoundEventLayerIndex2 = heldItem.getTool().getHitSoundLayerIndex();
                }
                if (hitSoundEventLayerIndex2 != 0) {
                    SoundUtil.playSoundEvent3d(ref, hitSoundEventLayerIndex2, targetBlockCenterPos.getX(), targetBlockCenterPos.getY(), targetBlockCenterPos.getZ(), entityStore);
                }
            }
            if (itemToolSpec != null && itemToolSpec.isIncorrect()) {
                final GatheringEffectsConfig incorrectToolConfig = gameplayConfig.getGatheringConfig().getIncorrectToolConfig();
                if (incorrectToolConfig != null) {
                    if ((setBlockSettings & 0x4) == 0x0) {
                        final String particleSystemId2 = incorrectToolConfig.getParticleSystemId();
                        if (particleSystemId2 != null) {
                            final ObjectList<Ref<EntityStore>> results2 = SpatialResource.getThreadLocalReferenceList();
                            playerSpatialResource.getSpatialStructure().collect(targetBlockCenterPos, 75.0, results2);
                            ParticleUtil.spawnParticleEffect(particleSystemId2, targetBlockCenterPos, results2, entityStore);
                        }
                    }
                    if ((setBlockSettings & 0x400) == 0x0) {
                        final int soundEventIndex3 = incorrectToolConfig.getSoundEventIndex();
                        if (soundEventIndex3 != 0) {
                            SoundUtil.playSoundEvent3d(ref, soundEventIndex3, targetBlockCenterPos, entityStore);
                        }
                    }
                }
            }
        }
        if (entity != null && ref != null && entity.canDecreaseItemStackDurability(ref, entityStore) && itemStack != null && !itemStack.isUnbreakable()) {
            final byte activeHotbarSlot = entity.getInventory().getActiveHotbarSlot();
            if (activeHotbarSlot != -1) {
                final double durability = calculateDurabilityUse(heldItem, targetBlockType);
                final ItemContainer hotbar = entity.getInventory().getHotbar();
                entity.updateItemStackDurability(ref, itemStack, hotbar, activeHotbarSlot, -durability, entityStore);
            }
        }
        return brokeBlock;
    }
    
    public static void performBlockBreak(@Nullable final Ref<EntityStore> ref, @Nullable final ItemStack heldItemStack, @Nonnull final Vector3i targetBlock, @Nonnull final Ref<ChunkStore> chunkReference, @Nonnull final ComponentAccessor<EntityStore> entityStore, @Nonnull final ComponentAccessor<ChunkStore> chunkStore) {
        performBlockBreak(ref, heldItemStack, targetBlock, 0, chunkReference, entityStore, chunkStore);
    }
    
    public static void performBlockBreak(@Nullable final Ref<EntityStore> ref, @Nullable final ItemStack heldItemStack, @Nonnull final Vector3i targetBlock, final int setBlockSettings, @Nonnull final Ref<ChunkStore> chunkReference, @Nonnull final ComponentAccessor<EntityStore> entityStore, @Nonnull final ComponentAccessor<ChunkStore> chunkStore) {
        final World world = chunkStore.getExternalData().getWorld();
        final int targetBlockX = targetBlock.getX();
        final int targetBlockY = targetBlock.getY();
        final int targetBlockZ = targetBlock.getZ();
        final WorldChunk worldChunkComponent = chunkStore.getComponent(chunkReference, WorldChunk.getComponentType());
        assert worldChunkComponent != null;
        final int targetBlockTypeIndex = worldChunkComponent.getBlock(targetBlockX, targetBlockY, targetBlockZ);
        final BlockType targetBlockTypeAsset = BlockType.getAssetMap().getAsset(targetBlockTypeIndex);
        if (targetBlockTypeAsset == null) {
            return;
        }
        Vector3i affectedBlock = targetBlock;
        if (!targetBlockTypeAsset.isUnknown()) {
            final BlockChunk blockChunkComponent = chunkStore.getComponent(chunkReference, BlockChunk.getComponentType());
            assert blockChunkComponent != null;
            final BlockSection targetBlockSection = blockChunkComponent.getSectionAtBlockY(targetBlockY);
            final int filler = targetBlockSection.getFiller(targetBlockX, targetBlockY, targetBlockZ);
            final int fillerX = FillerBlockUtil.unpackX(filler);
            final int fillerY = FillerBlockUtil.unpackY(filler);
            final int fillerZ = FillerBlockUtil.unpackZ(filler);
            if (fillerX != 0 || fillerY != 0 || fillerZ != 0) {
                affectedBlock = affectedBlock.clone().subtract(fillerX, fillerY, fillerZ);
                final BlockType originBlock = world.getBlockType(affectedBlock);
                if (originBlock != null && !targetBlockTypeAsset.getId().equals(originBlock.getId())) {
                    world.breakBlock(targetBlockX, targetBlockY, targetBlockZ, setBlockSettings);
                    return;
                }
            }
        }
        performBlockBreak(world, affectedBlock, targetBlockTypeAsset, heldItemStack, 0, null, null, setBlockSettings, ref, chunkReference, entityStore, chunkStore);
    }
    
    public static void performBlockBreak(@Nonnull final World world, @Nonnull final Vector3i blockPosition, @Nonnull final BlockType targetBlockTypeKey, @Nullable final ItemStack heldItemStack, final int dropQuantity, @Nullable final String dropItemId, @Nullable final String dropListId, int setBlockSettings, @Nullable final Ref<EntityStore> ref, @Nonnull final Ref<ChunkStore> chunkReference, @Nonnull final ComponentAccessor<EntityStore> entityStore, @Nonnull final ComponentAccessor<ChunkStore> chunkStore) {
        final World targetWorld = world;
        Vector3i targetBlockPosition = blockPosition;
        Ref<ChunkStore> targetChunkReference = chunkReference;
        ComponentAccessor<ChunkStore> targetChunkStore = chunkStore;
        if (ref != null) {
            final BreakBlockEvent event = new BreakBlockEvent(heldItemStack, targetBlockPosition, targetBlockTypeKey);
            entityStore.invoke(ref, event);
            if (event.isCancelled()) {
                final BlockChunk blockChunkComponent = chunkStore.getComponent(targetChunkReference, BlockChunk.getComponentType());
                assert blockChunkComponent != null;
                final BlockSection blockSection = blockChunkComponent.getSectionAtBlockY(targetBlockPosition.getY());
                blockSection.invalidateBlock(targetBlockPosition.getX(), targetBlockPosition.getY(), targetBlockPosition.getZ());
                return;
            }
            else {
                targetBlockPosition = event.getTargetBlock();
                targetChunkStore = targetWorld.getChunkStore().getStore();
                final long chunkIndex = ChunkUtil.indexChunkFromBlock(targetBlockPosition.x, targetBlockPosition.z);
                targetChunkReference = targetChunkStore.getExternalData().getChunkReference(chunkIndex);
                if (targetChunkReference == null || !targetChunkReference.isValid()) {
                    return;
                }
            }
        }
        if (!targetBlockPosition.equals(blockPosition) || !targetWorld.equals(world)) {
            final BlockChunk blockChunkComponent2 = chunkStore.getComponent(chunkReference, BlockChunk.getComponentType());
            assert blockChunkComponent2 != null;
            final BlockSection blockSection2 = blockChunkComponent2.getSectionAtBlockY(blockPosition.getY());
            blockSection2.invalidateBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
        }
        final int x = blockPosition.getX();
        final int y = blockPosition.getY();
        final int z = blockPosition.getZ();
        final BlockChunk blockChunkComponent3 = chunkStore.getComponent(targetChunkReference, BlockChunk.getComponentType());
        assert blockChunkComponent3 != null;
        final BlockSection blockSection3 = blockChunkComponent3.getSectionAtBlockY(y);
        final int filler = blockSection3.getFiller(x, y, z);
        final int blockTypeIndex = blockSection3.get(x, y, z);
        final BlockType blockTypeAsset = BlockType.getAssetMap().getAsset(blockTypeIndex);
        final boolean isNaturalBlockBreak = BlockInteractionUtils.isNaturalAction(ref, entityStore);
        setBlockSettings |= 0x100;
        if (!isNaturalBlockBreak) {
            setBlockSettings |= 0x800;
        }
        naturallyRemoveBlock(targetBlockPosition, blockTypeAsset, filler, dropQuantity, dropItemId, dropListId, setBlockSettings, targetChunkReference, entityStore, targetChunkStore);
    }
    
    @Deprecated
    public static void naturallyRemoveBlockByPhysics(@Nonnull final Vector3i blockPosition, @Nonnull final BlockType blockType, final int filler, int setBlockSettings, @Nonnull final Ref<ChunkStore> chunkReference, @Nonnull final ComponentAccessor<EntityStore> entityStore, @Nonnull final ComponentAccessor<ChunkStore> chunkStore) {
        int quantity = 1;
        String itemId = null;
        String dropListId = null;
        final BlockGathering blockGathering = blockType.getGathering();
        if (blockGathering != null) {
            final PhysicsDropType physics = blockGathering.getPhysics();
            final BlockBreakingDropType breaking = blockGathering.getBreaking();
            final SoftBlockDropType soft = blockGathering.getSoft();
            final HarvestingDropType harvest = blockGathering.getHarvest();
            if (physics != null) {
                itemId = physics.getItemId();
                dropListId = physics.getDropListId();
            }
            else if (breaking != null) {
                quantity = breaking.getQuantity();
                itemId = breaking.getItemId();
                dropListId = breaking.getDropListId();
            }
            else if (soft != null) {
                itemId = soft.getItemId();
                dropListId = soft.getDropListId();
            }
            else if (harvest != null) {
                itemId = harvest.getItemId();
                dropListId = harvest.getDropListId();
            }
        }
        setBlockSettings |= 0x20;
        naturallyRemoveBlock(blockPosition, blockType, filler, quantity, itemId, dropListId, setBlockSettings, chunkReference, entityStore, chunkStore);
    }
    
    public static void naturallyRemoveBlock(@Nonnull final Vector3i blockPosition, @Nullable BlockType blockType, final int filler, final int quantity, final String itemId, final String dropListId, final int setBlockSettings, @Nonnull final Ref<ChunkStore> chunkReference, @Nonnull final ComponentAccessor<EntityStore> entityStore, @Nonnull final ComponentAccessor<ChunkStore> chunkStore) {
        if (blockType == null) {
            return;
        }
        final WorldChunk worldChunkComponent = chunkStore.getComponent(chunkReference, WorldChunk.getComponentType());
        assert worldChunkComponent != null;
        final BlockChunk blockChunkComponent = chunkStore.getComponent(chunkReference, BlockChunk.getComponentType());
        assert blockChunkComponent != null;
        Vector3i affectedBlock = blockPosition;
        if (!blockType.isUnknown()) {
            final int fillerX = FillerBlockUtil.unpackX(filler);
            final int fillerY = FillerBlockUtil.unpackY(filler);
            final int fillerZ = FillerBlockUtil.unpackZ(filler);
            if (fillerX != 0 || fillerY != 0 || fillerZ != 0) {
                affectedBlock = affectedBlock.clone().subtract(fillerX, fillerY, fillerZ);
                final String oldBlockTypeKey = blockType.getId();
                blockType = worldChunkComponent.getBlockType(affectedBlock.getX(), affectedBlock.getY(), affectedBlock.getZ());
                if (blockType == null) {
                    throw new IllegalStateException("Null block type fetched for " + String.valueOf(affectedBlock) + " during block break");
                }
                if (!oldBlockTypeKey.equals(blockType.getId())) {
                    worldChunkComponent.breakBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), setBlockSettings);
                    return;
                }
            }
        }
        if ((setBlockSettings & 0x400) == 0x0) {
            final BlockSoundSet soundSet = BlockSoundSet.getAssetMap().getAsset(blockType.getBlockSoundSetIndex());
            if (soundSet != null) {
                final int soundEventIndex = soundSet.getSoundEventIndices().getOrDefault(BlockSoundEvent.Break, 0);
                if (soundEventIndex != 0) {
                    final BlockSection section = blockChunkComponent.getSectionAtBlockY(blockPosition.getY());
                    final int rotationIndex = section.getRotationIndex(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
                    final Vector3d centerPosition = new Vector3d();
                    blockType.getBlockCenter(rotationIndex, centerPosition);
                    centerPosition.add(blockPosition);
                    SoundUtil.playSoundEvent3d(soundEventIndex, SoundCategory.SFX, centerPosition, entityStore);
                }
            }
        }
        removeBlock(affectedBlock, blockType, setBlockSettings, chunkReference, chunkStore);
        if ((setBlockSettings & 0x800) == 0x0 && quantity > 0) {
            final Vector3d dropPosition = blockPosition.toVector3d().add(0.5, 0.0, 0.5);
            final List<ItemStack> itemStacks = getDrops(blockType, quantity, itemId, dropListId);
            final Holder<EntityStore>[] itemEntityHolders = ItemComponent.generateItemDrops(entityStore, itemStacks, dropPosition, Vector3f.ZERO);
            entityStore.addEntities(itemEntityHolders, AddReason.SPAWN);
        }
    }
    
    public static boolean shouldPickupByInteraction(@Nullable final BlockType blockType) {
        return blockType != null && blockType.getGathering() != null && blockType.getGathering().isHarvestable();
    }
    
    public static void performPickupByInteraction(@Nonnull final Ref<EntityStore> ref, @Nonnull final Vector3i targetBlock, @Nonnull BlockType blockType, final int filler, @Nonnull final Ref<ChunkStore> chunkReference, @Nonnull final ComponentAccessor<EntityStore> entityStore, @Nonnull final ComponentAccessor<ChunkStore> chunkStore) {
        final WorldChunk worldChunkComponent = chunkStore.getComponent(chunkReference, WorldChunk.getComponentType());
        assert worldChunkComponent != null;
        final BlockChunk blockChunkComponent = chunkStore.getComponent(chunkReference, BlockChunk.getComponentType());
        assert blockChunkComponent != null;
        Vector3i affectedBlock = targetBlock;
        if (!blockType.isUnknown()) {
            final int fillerX = FillerBlockUtil.unpackX(filler);
            final int fillerY = FillerBlockUtil.unpackY(filler);
            final int fillerZ = FillerBlockUtil.unpackZ(filler);
            if (fillerX != 0 || fillerY != 0 || fillerZ != 0) {
                affectedBlock = affectedBlock.clone().subtract(fillerX, fillerY, fillerZ);
                final String oldBlockTypeKey = blockType.getId();
                blockType = worldChunkComponent.getBlockType(affectedBlock.getX(), affectedBlock.getY(), affectedBlock.getZ());
                if (blockType == null) {
                    return;
                }
                if (!oldBlockTypeKey.equals(blockType.getId())) {
                    worldChunkComponent.breakBlock(targetBlock.getX(), targetBlock.getY(), targetBlock.getZ());
                    return;
                }
            }
        }
        final BlockSection section = blockChunkComponent.getSectionAtBlockY(targetBlock.getY());
        final Vector3d centerPosition = new Vector3d();
        final int rotationIndex = section.getRotationIndex(targetBlock.getX(), targetBlock.getY(), targetBlock.getZ());
        blockType.getBlockCenter(rotationIndex, centerPosition);
        centerPosition.add(targetBlock);
        int setBlockSettings = 0;
        setBlockSettings |= 0x100;
        if (!BlockInteractionUtils.isNaturalAction(ref, entityStore)) {
            setBlockSettings |= 0x800;
        }
        removeBlock(affectedBlock, blockType, setBlockSettings, chunkReference, chunkStore);
        final HarvestingDropType harvest = blockType.getGathering().getHarvest();
        final String itemId = harvest.getItemId();
        final String dropListId = harvest.getDropListId();
        for (final ItemStack itemStack : getDrops(blockType, 1, itemId, dropListId)) {
            ItemUtils.interactivelyPickupItem(ref, itemStack, centerPosition, entityStore);
        }
        if ((setBlockSettings & 0x400) == 0x0) {
            final BlockSoundSet soundSet = BlockSoundSet.getAssetMap().getAsset(blockType.getBlockSoundSetIndex());
            if (soundSet != null) {
                final int soundEventIndex = soundSet.getSoundEventIndices().getOrDefault(BlockSoundEvent.Harvest, 0);
                if (soundEventIndex != 0) {
                    SoundUtil.playSoundEvent3d(soundEventIndex, SoundCategory.SFX, centerPosition, entityStore);
                }
            }
        }
    }
    
    protected static void removeBlock(@Nonnull final Vector3i blockPosition, @Nonnull final BlockType blockType, final int setBlockSettings, @Nonnull final Ref<ChunkStore> chunkReference, @Nonnull final ComponentAccessor<ChunkStore> chunkStore) {
        final World world = chunkStore.getExternalData().getWorld();
        final ComponentType<ChunkStore, BlockHealthChunk> blockHealthComponentType = BlockHealthModule.get().getBlockHealthChunkComponentType();
        final BlockHealthChunk blockHealthComponent = chunkStore.getComponent(chunkReference, blockHealthComponentType);
        assert blockHealthComponent != null;
        blockHealthComponent.removeBlock(world, blockPosition);
        final WorldChunk worldChunkComponent = chunkStore.getComponent(chunkReference, WorldChunk.getComponentType());
        assert worldChunkComponent != null;
        final BlockChunk blockChunkComponent = chunkStore.getComponent(chunkReference, BlockChunk.getComponentType());
        assert blockChunkComponent != null;
        worldChunkComponent.breakBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), setBlockSettings);
        if ((setBlockSettings & 0x100) != 0x0) {
            final BlockSection section = blockChunkComponent.getSectionAtBlockY(blockPosition.y);
            final int rotationIndex = section.getRotationIndex(blockPosition.x, blockPosition.y, blockPosition.z);
            final BlockBoundingBoxes hitBoxType = BlockBoundingBoxes.getAssetMap().getAsset(blockType.getHitboxTypeIndex());
            if (hitBoxType != null) {
                FillerBlockUtil.forEachFillerBlock(hitBoxType.get(rotationIndex), (x, y, z) -> world.performBlockUpdate(blockPosition.getX() + x, blockPosition.getY() + y, blockPosition.getZ() + z, false));
            }
        }
        ConnectedBlocksUtil.setConnectedBlockAndNotifyNeighbors(BlockType.getAssetMap().getIndex("Empty"), RotationTuple.NONE, Vector3i.ZERO, blockPosition, worldChunkComponent, blockChunkComponent);
    }
    
    @Nonnull
    public static List<ItemStack> getDrops(@Nonnull final BlockType blockType, final int quantity, @Nullable final String itemId, @Nullable final String dropListId) {
        if (dropListId != null || itemId != null) {
            final List<ItemStack> randomItemDrops = new ObjectArrayList<ItemStack>();
            if (dropListId != null) {
                final ItemModule itemModule = ItemModule.get();
                if (itemModule.isEnabled()) {
                    for (int i = 0; i < quantity; ++i) {
                        final List<ItemStack> randomItemsToDrop = itemModule.getRandomItemDrops(dropListId);
                        randomItemDrops.addAll(randomItemsToDrop);
                    }
                }
            }
            if (itemId != null) {
                randomItemDrops.add(new ItemStack(itemId, quantity));
            }
            return randomItemDrops;
        }
        final Item item = blockType.getItem();
        if (item == null) {
            return (List<ItemStack>)ObjectLists.emptyList();
        }
        return ObjectLists.singleton(new ItemStack(item.getId(), quantity));
    }
}
