Extra base hopper code

This commit is contained in:
Shadowfacts 2020-03-28 13:52:27 -04:00
parent b8d374b982
commit 7b134f688d
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
4 changed files with 277 additions and 227 deletions

View File

@ -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<T: BaseHopperBlockEntity>(settings: Settings): BlockWithEntity<T>(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<Block, BlockState>) {
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
}
}

View File

@ -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<ItemStack> = 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<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)
}
// 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()
}

View File

@ -20,69 +20,24 @@ import net.minecraft.util.shape.VoxelShape
import net.minecraft.util.shape.VoxelShapes import net.minecraft.util.shape.VoxelShapes
import net.minecraft.world.BlockView import net.minecraft.world.BlockView
import net.minecraft.world.World import net.minecraft.world.World
import net.shadowfacts.extrahoppers.block.base.BaseHopperBlock
import net.shadowfacts.extrahoppers.block.base.BlockWithEntity import net.shadowfacts.extrahoppers.block.base.BlockWithEntity
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class WoodHopperBlock: BlockWithEntity<WoodHopperBlockEntity>(FabricBlockSettings.of(Material.WOOD).strength(2f, 3f).sounds(BlockSoundGroup.WOOD).nonOpaque().build()) { class WoodHopperBlock: BaseHopperBlock<WoodHopperBlockEntity>(
FabricBlockSettings.of(Material.WOOD)
.strength(2f, 3f)
.sounds(BlockSoundGroup.WOOD)
.nonOpaque()
.build()
) {
companion object { companion object {
val ID = Identifier("extrahoppers", "wood_hopper") 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<Block, BlockState>) { override fun createBlockEntity(world: BlockView) = WoodHopperBlockEntity()
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 onUse(state: BlockState, world: World, pos: BlockPos, player: PlayerEntity, hand: Hand, hitResult: BlockHitResult): ActionResult { override fun onUse(state: BlockState, world: World, pos: BlockPos, player: PlayerEntity, hand: Hand, hitResult: BlockHitResult): ActionResult {
if (!world.isClient) { if (!world.isClient) {
@ -94,24 +49,10 @@ class WoodHopperBlock: BlockWithEntity<WoodHopperBlockEntity>(FabricBlockSetting
return ActionResult.SUCCESS 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) { override fun onEntityCollision(state: BlockState, world: World, pos: BlockPos, entity: Entity) {
getBlockEntity(world, pos)?.also { getBlockEntity(world, pos)?.also {
it.onEntityCollision(entity) it.onEntityCollision(entity)
} }
} }
override fun canPlaceAtSide(blockState: BlockState?, blockView: BlockView?, blockPos: BlockPos?, blockPlacementEnvironment: BlockPlacementEnvironment?): Boolean {
return false
}
} }

View File

@ -1,167 +1,15 @@
package net.shadowfacts.extrahoppers.block.wood 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.Tickable
import net.minecraft.util.math.Direction import net.shadowfacts.extrahoppers.block.base.BaseHopperBlockEntity
import net.minecraft.util.shape.VoxelShapes
import net.shadowfacts.extrahoppers.init.EHBlockEntities import net.shadowfacts.extrahoppers.init.EHBlockEntities
import net.shadowfacts.extrahoppers.util.toVec3d
class WoodHopperBlockEntity: BlockEntity(EHBlockEntities.WOOD_HOPPER), Inventory, Hopper, Tickable { /**
companion object { * @author shadowfacts
val TRANSFER_COOLDOWN = 40 */
} class WoodHopperBlockEntity: BaseHopperBlockEntity(EHBlockEntities.WOOD_HOPPER), Tickable {
var inventory: DefaultedList<ItemStack> = DefaultedList.ofSize(1, ItemStack.EMPTY) override val inventorySize = 1
private set override val maxTransferCooldown = 40
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<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)
}
// 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()
} }