package net.shadowfacts.extrahoppers.block.base import net.minecraft.block.entity.BlockEntity import net.minecraft.block.entity.BlockEntityType import net.minecraft.block.entity.Hopper import net.minecraft.block.entity.HopperBlockEntity import net.minecraft.entity.Entity import net.minecraft.entity.ItemEntity import net.minecraft.entity.player.PlayerEntity import net.minecraft.inventory.Inventories import net.minecraft.inventory.Inventory import net.minecraft.inventory.SidedInventory import net.minecraft.item.ItemStack import net.minecraft.nbt.CompoundTag import net.minecraft.state.property.DirectionProperty import net.minecraft.util.BooleanBiFunction import net.minecraft.util.DefaultedList import net.minecraft.util.Tickable import net.minecraft.util.math.Direction import net.minecraft.util.shape.VoxelShapes import net.shadowfacts.extrahoppers.util.toVec3d /** * @author shadowfacts */ abstract class BaseHopperBlockEntity(type: BlockEntityType<*>): BlockEntity(type), Inventory, Hopper, Tickable { abstract val inventorySize: Int abstract val maxTransferCooldown: Int abstract val facingProp: DirectionProperty abstract val inputSide: Direction val inventory: DefaultedList by lazy { DefaultedList.ofSize(inventorySize, ItemStack.EMPTY) } protected var transferCooldown = -1 override fun toTag(tag: CompoundTag): CompoundTag { Inventories.toTag(tag, inventory) tag.putInt("transferCooldown", transferCooldown) return super.toTag(tag) } override fun fromTag(tag: CompoundTag) { super.fromTag(tag) inventory.clear() Inventories.fromTag(tag, inventory) transferCooldown = tag.getInt("transferCooldown") } override fun tick() { val world = world if (world == null || world.isClient) return transferCooldown -= 1 if (transferCooldown <= 0) { transferCooldown = 0 insertAndExtract() } } fun onEntityCollision(entity: Entity) { if (entity is ItemEntity) { if (VoxelShapes.matchesAnywhere(VoxelShapes.cuboid(entity.boundingBox.offset(-pos.x.toDouble(), -pos.y.toDouble(), -pos.z.toDouble())), inputAreaShape, BooleanBiFunction.AND)) { insertAndExtract { HopperBlockEntity.extract(this, entity) } } } } fun insertAndExtract(extractor: (() -> Boolean)? = null) { val world = world if (world == null || world.isClient) return var didWork = false if (!isInvEmpty && insert()) { didWork = true } if (!isFull() && (extractor != null && extractor()) || extract()) { didWork = true } if (didWork) { transferCooldown = maxTransferCooldown markDirty() } } fun insert(): Boolean { val outputInv = getOutputInventory() ?: return false val insertionSide = cachedState.get(facingProp).opposite if (isInventoryFull(outputInv, insertionSide)) return false for (slot in 0 until invSize) { if (getInvStack(slot).isEmpty) continue val stackCopy = getInvStack(slot).copy() val remaining = HopperBlockEntity.transfer(this, outputInv, takeInvStack(slot, 1), insertionSide) if (remaining.isEmpty) { outputInv.markDirty() return true } setInvStack(slot, stackCopy) } return false } fun extract(): Boolean { val inputInv = getInputInventory() return if (inputInv != null) { val extractionSide = inputSide.opposite if (!isInventoryEmpty(inputInv, extractionSide)) { inventorySlots(inputInv, extractionSide).any { slot -> extractFromInv(inputInv, slot, extractionSide) } } else { false } } else { HopperBlockEntity.getInputItemEntities(this).any { HopperBlockEntity.extract(this, it) } } } fun extractFromInv(inventory: Inventory, slot: Int, extractonSide: Direction): Boolean { val stack = inventory.getInvStack(slot) if (!stack.isEmpty && canExtract(inventory, stack, slot, extractonSide)) { val stackCopy = stack.copy() val remaining = HopperBlockEntity.transfer(inventory, this, inventory.takeInvStack(slot, 1), null) if (remaining.isEmpty) { inventory.markDirty() return true } inventory.setInvStack(slot, stackCopy) } return false } fun isFull(): Boolean { return inventory.none(ItemStack::isEmpty) } fun getOutputInventory(): Inventory? { val facing = cachedState.get(facingProp) return HopperBlockEntity.getInventoryAt(world!!, pos.offset(facing)) } fun getInputInventory(): Inventory? { return HopperBlockEntity.getInventoryAt(world!!, pos.offset(inputSide)) } fun inventorySlots(inv: Inventory, side: Direction): Sequence { return if (inv is SidedInventory) { inv.getInvAvailableSlots(side).asSequence() } else { (0 until inv.invSize).asSequence() } } fun isInventoryFull(inv: Inventory, side: Direction): Boolean { val slots = inventorySlots(inv, side) return slots.map(inv::getInvStack).none(ItemStack::isEmpty) } fun isInventoryEmpty(inv: Inventory, side: Direction): Boolean { val slots = inventorySlots(inv, side) return slots.map(inv::getInvStack).all(ItemStack::isEmpty) } fun canExtract(inv: Inventory, stack: ItemStack, slot: Int, extractionSide: Direction): Boolean { return if (inv is SidedInventory) { inv.canExtractInvStack(slot, stack, extractionSide) } else { true } } // Inventory override fun getInvSize() = inventory.size override fun takeInvStack(slot: Int, amount: Int): ItemStack { return Inventories.splitStack(inventory, slot, amount) } override fun isInvEmpty() = inventory.isEmpty() override fun getInvStack(slot: Int) = inventory[slot] override fun clear() { inventory.clear() } override fun setInvStack(slot: Int, stack: ItemStack) { inventory[slot] = stack } override fun removeInvStack(slot: Int): ItemStack { return Inventories.removeStack(inventory, slot) } override fun canPlayerUseInv(player: PlayerEntity): Boolean { return if (world?.getBlockEntity(pos) != this) { false } else { val distance = player.squaredDistanceTo(this.pos.toVec3d()) distance < 64 } } // Hopper override fun getHopperX() = pos.x.toDouble() override fun getHopperY() = pos.y.toDouble() override fun getHopperZ() = pos.z.toDouble() }