diff --git a/src/main/kotlin/net/shadowfacts/extrahoppers/block/base/BaseHopperBlock.kt b/src/main/kotlin/net/shadowfacts/extrahoppers/block/base/BaseHopperBlock.kt new file mode 100644 index 0000000..5778807 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/extrahoppers/block/base/BaseHopperBlock.kt @@ -0,0 +1,92 @@ +package net.shadowfacts.extrahoppers.block.base + +import net.minecraft.block.Block +import net.minecraft.block.BlockPlacementEnvironment +import net.minecraft.block.BlockState +import net.minecraft.block.entity.Hopper +import net.minecraft.entity.EntityContext +import net.minecraft.item.ItemPlacementContext +import net.minecraft.state.StateManager +import net.minecraft.state.property.Properties +import net.minecraft.util.BooleanBiFunction +import net.minecraft.util.ItemScatterer +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction +import net.minecraft.util.shape.VoxelShape +import net.minecraft.util.shape.VoxelShapes +import net.minecraft.world.BlockView +import net.minecraft.world.World + +/** + * @author shadowfacts + */ +abstract class BaseHopperBlock(settings: Settings): BlockWithEntity(settings) { + + companion object { + val FACING = Properties.HOPPER_FACING + // todo: redstone support +// val ENABLED = Properties.ENABLED + val TOP_SHAPE = createCuboidShape(0.0, 10.0, 0.0, 16.0, 16.0, 16.0) + val MIDDLE_SHAPE = createCuboidShape(4.0, 4.0, 4.0, 12.0, 10.0, 12.0) + val OUTSIDE_SHAPE = VoxelShapes.union(MIDDLE_SHAPE, TOP_SHAPE) + val DEFAULT_SHAPE = VoxelShapes.combineAndSimplify(OUTSIDE_SHAPE, Hopper.INSIDE_SHAPE, BooleanBiFunction.ONLY_FIRST) + val DOWN_SHAPE = VoxelShapes.union(DEFAULT_SHAPE, createCuboidShape(6.0, 0.0, 6.0, 10.0, 4.0, 10.0)) + val EAST_SHAPE = VoxelShapes.union(DEFAULT_SHAPE, createCuboidShape(12.0, 4.0, 6.0, 16.0, 8.0, 10.0)) + val NORTH_SHAPE = VoxelShapes.union(DEFAULT_SHAPE, createCuboidShape(6.0, 4.0, 0.0, 10.0, 8.0, 4.0)) + val SOUTH_SHAPE = VoxelShapes.union(DEFAULT_SHAPE, createCuboidShape(6.0, 4.0, 12.0, 10.0, 8.0, 16.0)) + val WEST_SHAPE = VoxelShapes.union(DEFAULT_SHAPE, createCuboidShape(0.0, 4.0, 6.0, 4.0, 8.0, 10.0)) + val DOWN_RAY_TRACE_SHAPE = Hopper.INSIDE_SHAPE + val EAST_RAY_TRACE_SHAPE = VoxelShapes.union(Hopper.INSIDE_SHAPE, createCuboidShape(12.0, 8.0, 6.0, 16.0, 10.0, 10.0)) + val NORTH_RAY_TRACE_SHAPE = VoxelShapes.union(Hopper.INSIDE_SHAPE, createCuboidShape(6.0, 8.0, 0.0, 10.0, 10.0, 4.0)) + val SOUTH_RAY_TRACE_SHAPE = VoxelShapes.union(Hopper.INSIDE_SHAPE, createCuboidShape(6.0, 8.0, 12.0, 10.0, 10.0, 16.0)) + val WEST_RAY_TRACE_SHAPE = VoxelShapes.union(Hopper.INSIDE_SHAPE, createCuboidShape(0.0, 8.0, 6.0, 4.0, 10.0, 10.0)) + } + + override fun appendProperties(builder: StateManager.Builder) { + builder.add(FACING) + } + + + override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, entityContext: EntityContext): VoxelShape { + return when (state.get(FACING)) { + Direction.DOWN -> DOWN_SHAPE + Direction.NORTH -> NORTH_SHAPE + Direction.SOUTH -> SOUTH_SHAPE + Direction.WEST -> WEST_SHAPE + Direction.EAST -> EAST_SHAPE + else -> DEFAULT_SHAPE + } + } + + override fun getRayTraceShape(state: BlockState, world: BlockView, pos: BlockPos): VoxelShape { + return when (state.get(FACING)) { + Direction.DOWN -> DOWN_RAY_TRACE_SHAPE + Direction.NORTH -> NORTH_RAY_TRACE_SHAPE + Direction.SOUTH -> SOUTH_RAY_TRACE_SHAPE + Direction.WEST -> WEST_RAY_TRACE_SHAPE + Direction.EAST -> EAST_RAY_TRACE_SHAPE + else -> Hopper.INSIDE_SHAPE + } + } + + override fun getPlacementState(context: ItemPlacementContext): BlockState { + val hitFacing = context.side.opposite + val facing = if (hitFacing.axis == Direction.Axis.Y) Direction.DOWN else hitFacing + return defaultState.with(FACING, facing) + } + + override fun onBlockRemoved(oldState: BlockState, world: World, pos: BlockPos, newState: BlockState, bl: Boolean) { + if (oldState.block != newState.block) { + getBlockEntity(world, pos)?.also { + ItemScatterer.spawn(world, pos, it) + world.updateHorizontalAdjacent(pos, this) + } + super.onBlockRemoved(oldState, world, pos, newState, bl) + } + } + + override fun canPlaceAtSide(blockState: BlockState?, blockView: BlockView?, blockPos: BlockPos?, blockPlacementEnvironment: BlockPlacementEnvironment?): Boolean { + return false + } + +} diff --git a/src/main/kotlin/net/shadowfacts/extrahoppers/block/base/BaseHopperBlockEntity.kt b/src/main/kotlin/net/shadowfacts/extrahoppers/block/base/BaseHopperBlockEntity.kt new file mode 100644 index 0000000..c3647d8 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/extrahoppers/block/base/BaseHopperBlockEntity.kt @@ -0,0 +1,169 @@ +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.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 { + + protected open val inventorySize = 5 + protected open val maxTransferCooldown = 8 + var inventory: DefaultedList = 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()) || HopperBlockEntity.extract(this)) { + didWork = true + } + + if (didWork) { + transferCooldown = maxTransferCooldown + markDirty() + } + } + + fun insert(): Boolean { + val outputInv = getOutputInventory() ?: return false + + val insertionSide = cachedState.get(BaseHopperBlock.FACING).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 isFull(): Boolean { + return inventory.none(ItemStack::isEmpty) + } + + fun getOutputInventory(): Inventory? { + val facing = cachedState.get(BaseHopperBlock.FACING) + return HopperBlockEntity.getInventoryAt(world!!, pos.offset(facing)) + } + + 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) + } + + // 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() + +} diff --git a/src/main/kotlin/net/shadowfacts/extrahoppers/block/wood/WoodHopperBlock.kt b/src/main/kotlin/net/shadowfacts/extrahoppers/block/wood/WoodHopperBlock.kt index bfcba06..f058bdc 100644 --- a/src/main/kotlin/net/shadowfacts/extrahoppers/block/wood/WoodHopperBlock.kt +++ b/src/main/kotlin/net/shadowfacts/extrahoppers/block/wood/WoodHopperBlock.kt @@ -20,69 +20,24 @@ import net.minecraft.util.shape.VoxelShape import net.minecraft.util.shape.VoxelShapes import net.minecraft.world.BlockView import net.minecraft.world.World +import net.shadowfacts.extrahoppers.block.base.BaseHopperBlock import net.shadowfacts.extrahoppers.block.base.BlockWithEntity /** * @author shadowfacts */ -class WoodHopperBlock: BlockWithEntity(FabricBlockSettings.of(Material.WOOD).strength(2f, 3f).sounds(BlockSoundGroup.WOOD).nonOpaque().build()) { +class WoodHopperBlock: BaseHopperBlock( + FabricBlockSettings.of(Material.WOOD) + .strength(2f, 3f) + .sounds(BlockSoundGroup.WOOD) + .nonOpaque() + .build() +) { companion object { val ID = Identifier("extrahoppers", "wood_hopper") - - val FACING = Properties.HOPPER_FACING - // todo: redstone support -// val ENABLED = Properties.ENABLED - val TOP_SHAPE = createCuboidShape(0.0, 10.0, 0.0, 16.0, 16.0, 16.0) - val MIDDLE_SHAPE = createCuboidShape(4.0, 4.0, 4.0, 12.0, 10.0, 12.0) - val OUTSIDE_SHAPE = VoxelShapes.union(MIDDLE_SHAPE, TOP_SHAPE) - val DEFAULT_SHAPE = VoxelShapes.combineAndSimplify(OUTSIDE_SHAPE, Hopper.INSIDE_SHAPE, BooleanBiFunction.ONLY_FIRST) - val DOWN_SHAPE = VoxelShapes.union(DEFAULT_SHAPE, createCuboidShape(6.0, 0.0, 6.0, 10.0, 4.0, 10.0)) - val EAST_SHAPE = VoxelShapes.union(DEFAULT_SHAPE, createCuboidShape(12.0, 4.0, 6.0, 16.0, 8.0, 10.0)) - val NORTH_SHAPE = VoxelShapes.union(DEFAULT_SHAPE, createCuboidShape(6.0, 4.0, 0.0, 10.0, 8.0, 4.0)) - val SOUTH_SHAPE = VoxelShapes.union(DEFAULT_SHAPE, createCuboidShape(6.0, 4.0, 12.0, 10.0, 8.0, 16.0)) - val WEST_SHAPE = VoxelShapes.union(DEFAULT_SHAPE, createCuboidShape(0.0, 4.0, 6.0, 4.0, 8.0, 10.0)) - val DOWN_RAY_TRACE_SHAPE = Hopper.INSIDE_SHAPE - val EAST_RAY_TRACE_SHAPE = VoxelShapes.union(Hopper.INSIDE_SHAPE, createCuboidShape(12.0, 8.0, 6.0, 16.0, 10.0, 10.0)) - val NORTH_RAY_TRACE_SHAPE = VoxelShapes.union(Hopper.INSIDE_SHAPE, createCuboidShape(6.0, 8.0, 0.0, 10.0, 10.0, 4.0)) - val SOUTH_RAY_TRACE_SHAPE = VoxelShapes.union(Hopper.INSIDE_SHAPE, createCuboidShape(6.0, 8.0, 12.0, 10.0, 10.0, 16.0)) - val WEST_RAY_TRACE_SHAPE = VoxelShapes.union(Hopper.INSIDE_SHAPE, createCuboidShape(0.0, 8.0, 6.0, 4.0, 10.0, 10.0)) } - override fun appendProperties(builder: StateManager.Builder) { - builder.add(FACING) - } - - override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, entityContext: EntityContext): VoxelShape { - return when (state.get(FACING)) { - Direction.DOWN -> DOWN_SHAPE - Direction.NORTH -> NORTH_SHAPE - Direction.SOUTH -> SOUTH_SHAPE - Direction.WEST -> WEST_SHAPE - Direction.EAST -> EAST_SHAPE - else -> DEFAULT_SHAPE - } - } - - override fun getRayTraceShape(state: BlockState, world: BlockView, pos: BlockPos): VoxelShape { - return when (state.get(FACING)) { - Direction.DOWN -> DOWN_RAY_TRACE_SHAPE - Direction.NORTH -> NORTH_RAY_TRACE_SHAPE - Direction.SOUTH -> SOUTH_RAY_TRACE_SHAPE - Direction.WEST -> WEST_RAY_TRACE_SHAPE - Direction.EAST -> EAST_RAY_TRACE_SHAPE - else -> Hopper.INSIDE_SHAPE - } - } - - override fun getPlacementState(context: ItemPlacementContext): BlockState { - val hitFacing = context.side.opposite - val facing = if (hitFacing.axis == Direction.Axis.Y) Direction.DOWN else hitFacing - return defaultState.with(FACING, facing) - } - - override fun createBlockEntity(world: BlockView): WoodHopperBlockEntity { - return WoodHopperBlockEntity() - } + override fun createBlockEntity(world: BlockView) = WoodHopperBlockEntity() override fun onUse(state: BlockState, world: World, pos: BlockPos, player: PlayerEntity, hand: Hand, hitResult: BlockHitResult): ActionResult { if (!world.isClient) { @@ -94,24 +49,10 @@ class WoodHopperBlock: BlockWithEntity(FabricBlockSetting return ActionResult.SUCCESS } - override fun onBlockRemoved(oldState: BlockState, world: World, pos: BlockPos, newState: BlockState, bl: Boolean) { - if (oldState.block != newState.block) { - getBlockEntity(world, pos)?.also { - ItemScatterer.spawn(world, pos, it) - world.updateHorizontalAdjacent(pos, this) - } - super.onBlockRemoved(oldState, world, pos, newState, bl) - } - } - override fun onEntityCollision(state: BlockState, world: World, pos: BlockPos, entity: Entity) { getBlockEntity(world, pos)?.also { it.onEntityCollision(entity) } } - override fun canPlaceAtSide(blockState: BlockState?, blockView: BlockView?, blockPos: BlockPos?, blockPlacementEnvironment: BlockPlacementEnvironment?): Boolean { - return false - } - } diff --git a/src/main/kotlin/net/shadowfacts/extrahoppers/block/wood/WoodHopperBlockEntity.kt b/src/main/kotlin/net/shadowfacts/extrahoppers/block/wood/WoodHopperBlockEntity.kt index 0e53179..1f72fbb 100644 --- a/src/main/kotlin/net/shadowfacts/extrahoppers/block/wood/WoodHopperBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/extrahoppers/block/wood/WoodHopperBlockEntity.kt @@ -1,167 +1,15 @@ package net.shadowfacts.extrahoppers.block.wood -import net.minecraft.block.entity.BlockEntity -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.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.block.base.BaseHopperBlockEntity import net.shadowfacts.extrahoppers.init.EHBlockEntities -import net.shadowfacts.extrahoppers.util.toVec3d -class WoodHopperBlockEntity: BlockEntity(EHBlockEntities.WOOD_HOPPER), Inventory, Hopper, Tickable { - companion object { - val TRANSFER_COOLDOWN = 40 - } +/** + * @author shadowfacts + */ +class WoodHopperBlockEntity: BaseHopperBlockEntity(EHBlockEntities.WOOD_HOPPER), Tickable { - var inventory: DefaultedList = DefaultedList.ofSize(1, ItemStack.EMPTY) - private set - var transferCooldown = -1 - var lastTickTime = 0L - - 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 - lastTickTime = world.time - 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() - } - } - } - - 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()) || HopperBlockEntity.extract(this))) { - didWork = true - } - - if (didWork) { - transferCooldown = TRANSFER_COOLDOWN - markDirty() - } - } - - fun insert(): Boolean { - val outputInv = getOutputInventory() ?: return false - - val insertionSide = cachedState.get(WoodHopperBlock.FACING).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 isFull(): Boolean { - return inventory.none(ItemStack::isEmpty) - } - - fun getOutputInventory(): Inventory? { - val facing = cachedState.get(WoodHopperBlock.FACING) - return HopperBlockEntity.getInventoryAt(world!!, pos.offset(facing)) - } - - 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) - } - - // 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() + override val inventorySize = 1 + override val maxTransferCooldown = 40 }