ExtraHoppers/src/main/kotlin/net/shadowfacts/extrahoppers/block/base/BaseHopperBlockEntity.kt

224 lines
6.9 KiB
Kotlin

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<ItemStack> 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<Int> {
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()
}