//On contact with entity, if the entity is on the top and the entity is an item entity, pull the entity into the hopper's inventory.

        public override void OnEntityCollide(IWorldAccessor world, Entity entity, BlockPos pos, BlockFacing facing, Vec3d collideSpeed, bool isImpact)
        {
            base.OnEntityCollide(world, entity, pos, facing, collideSpeed, isImpact);

            // Don't suck up everything instantly
            if (world.Rand.NextDouble() < 0.9)
            {
                return;
            }

            if (facing == BlockFacing.UP && entity is EntityItem)
            {
                EntityItem  inWorldItem = (EntityItem)entity;
                BlockEntity blockEntity = world.BlockAccessor.GetBlockEntity(pos);
                if (blockEntity is BlockEntityItemFlow)
                {
                    BlockEntityItemFlow beItemFlow = (BlockEntityItemFlow)blockEntity;

                    WeightedSlot ws = beItemFlow.inventory.GetBestSuitedSlot(inWorldItem.Slot);

                    if (ws.slot != null) //we have determined there is room for this itemStack in this inventory.
                    {
                        inWorldItem.Slot.TryPutInto(api.World, ws.slot, 1);
                        if (inWorldItem.Slot.StackSize <= 0)
                        {
                            inWorldItem.Itemstack = null;
                        }
                    }
                }
            }
        }
        private void TryPullFrom(BlockFacing inputFace)
        {
            BlockPos InputPosition = Pos.AddCopy(inputFace);

            if (Api.World.BlockAccessor.GetBlockEntity(InputPosition) is BlockEntityContainer beContainer)
            {
                //do not both push and pull across the same chute-chute connection
                if (beContainer.Block is BlockChute chute)
                {
                    string[] pushFaces = chute.Attributes["pushFaces"].AsArray <string>(null);
                    if (pushFaces?.Contains(inputFace.Opposite.Code) == true)
                    {
                        return;
                    }
                }

                ItemSlot            sourceSlot = beContainer.Inventory.GetAutoPullFromSlot(inputFace.Opposite);
                ItemSlot            targetSlot = sourceSlot == null ? null : inventory.GetBestSuitedSlot(sourceSlot).slot;
                BlockEntityItemFlow beFlow     = beContainer as BlockEntityItemFlow;

                if (sourceSlot != null && targetSlot != null && (beFlow == null || targetSlot.Empty))
                {
                    ItemStackMoveOperation op = new ItemStackMoveOperation(Api.World, EnumMouseButton.Left, 0, EnumMergePriority.DirectMerge, (int)itemFlowAccum);

                    int horTravelled = sourceSlot.Itemstack.Attributes.GetInt("chuteQHTravelled");
                    if (horTravelled < 2)
                    {
                        int qmoved = sourceSlot.TryPutInto(targetSlot, ref op);
                        if (qmoved > 0)
                        {
                            if (beFlow != null)
                            {
                                targetSlot.Itemstack.Attributes.SetInt("chuteQHTravelled", inputFace.IsHorizontal ? (horTravelled + 1): 0);
                                targetSlot.Itemstack.Attributes.SetInt("chuteDir", inputFace.Opposite.Index);
                            }
                            else
                            {
                                targetSlot.Itemstack.Attributes.RemoveAttribute("chuteQHTravelled");
                                targetSlot.Itemstack.Attributes.RemoveAttribute("chuteDir");
                            }

                            sourceSlot.MarkDirty();
                            targetSlot.MarkDirty();
                            MarkDirty(false);
                            beFlow?.MarkDirty();
                        }

                        if (qmoved > 0 && Api.World.Rand.NextDouble() < 0.2)
                        {
                            Api.World.PlaySoundAt(hopperTumble, Pos.X + 0.5, Pos.Y + 0.5, Pos.Z + 0.5, null, true, 8, 0.5f);

                            itemFlowAccum -= qmoved;
                        }
                    }
                }
            }
        }
        private bool TryPushInto(BlockFacing outputFace)
        {
            BlockPos OutputPosition = Pos.AddCopy(outputFace);

            if (Api.World.BlockAccessor.GetBlockEntity(OutputPosition) is BlockEntityContainer beContainer)
            {
                ItemSlot sourceSlot = inventory.FirstOrDefault(slot => !slot.Empty);
                if ((sourceSlot?.Itemstack?.StackSize ?? 0) == 0)
                {
                    return(false);                                               //seems FirstOrDefault() method can sometimes give a slot with stacksize == 0, weird
                }
                int horTravelled = sourceSlot.Itemstack.Attributes.GetInt("chuteQHTravelled");
                int chuteDir     = sourceSlot.Itemstack.Attributes.GetInt("chuteDir");
                sourceSlot.Itemstack.Attributes.RemoveAttribute("chuteQHTravelled");
                sourceSlot.Itemstack.Attributes.RemoveAttribute("chuteDir");

                if (horTravelled >= 2)
                {
                    return(false);                    //chutes can't move items more than 1 block horizontally without a drop
                }
                ItemSlot            targetSlot = beContainer.Inventory.GetAutoPushIntoSlot(outputFace.Opposite, sourceSlot);
                BlockEntityItemFlow beFlow     = beContainer as BlockEntityItemFlow;

                if (targetSlot != null && (beFlow == null || targetSlot.Empty))
                {
                    int quantity = (int)itemFlowAccum;
                    ItemStackMoveOperation op = new ItemStackMoveOperation(Api.World, EnumMouseButton.Left, 0, EnumMergePriority.DirectMerge, quantity);

                    int qmoved = sourceSlot.TryPutInto(targetSlot, ref op);

                    if (qmoved > 0)
                    {
                        if (Api.World.Rand.NextDouble() < 0.2)
                        {
                            Api.World.PlaySoundAt(hopperTumble, Pos.X + 0.5, Pos.Y + 0.5, Pos.Z + 0.5, null, true, 8, 0.5f);
                        }

                        if (beFlow != null)
                        {
                            targetSlot.Itemstack.Attributes.SetInt("chuteQHTravelled", outputFace.IsHorizontal ? (horTravelled + 1) : 0);
                            targetSlot.Itemstack.Attributes.SetInt("chuteDir", outputFace.Index);
                        }
                        else
                        {
                            targetSlot.Itemstack.Attributes.RemoveAttribute("chuteQHTravelled");
                            targetSlot.Itemstack.Attributes.RemoveAttribute("chuteDir");
                        }

                        sourceSlot.MarkDirty();
                        targetSlot.MarkDirty();
                        MarkDirty(false);
                        beFlow?.MarkDirty(false);

                        itemFlowAccum -= qmoved;

                        return(true);
                    }
                    else
                    {
                        //If the push failed, re-apply original chuteDir so that the itemStack still has it for next push attempt
                        sourceSlot.Itemstack.Attributes.SetInt("chuteDir", chuteDir);
                    }
                }
            }

            return(false);
        }