| package li.cil.oc.common.block |
| |
| import cpw.mods.fml.common.registry.GameRegistry |
| import java.util |
| import li.cil.oc.api.network.Environment |
| import li.cil.oc.common.tileentity.Rotatable |
| import li.cil.oc.{Config, CreativeTab} |
| import net.minecraft.block.Block |
| import net.minecraft.block.material.Material |
| import net.minecraft.client.renderer.texture.IconRegister |
| import net.minecraft.creativetab.CreativeTabs |
| import net.minecraft.entity.Entity |
| import net.minecraft.entity.EntityLivingBase |
| import net.minecraft.entity.player.EntityPlayer |
| import net.minecraft.item.ItemStack |
| import net.minecraft.tileentity.TileEntity |
| import net.minecraft.world.IBlockAccess |
| import net.minecraft.world.World |
| import net.minecraftforge.common.ForgeDirection |
| import scala.collection.mutable |
| |
| /** |
| * Block proxy for all real block implementations. |
| * |
| * All of our blocks are implemented as "sub-blocks" of this block, i.e. they |
| * are instances of this block with differing metadata. This way we only need a |
| * single block ID to represent 15 blocks. |
| * |
| * This means this block contains barely any logic, it only forwards calls to |
| * the underlying sub block, based on the metadata. The only actual logic done |
| * in here is: |
| * - block rotation, if the block has a rotatable tile entity. In that case all |
| * sides are also translated into local coordinate space for the sub block |
| * (i.e. "up" will always be up relative to the block itself. So if it's |
| * rotated up may actually be west). |
| * - Component network logic for adding / removing blocks from the component |
| * network when they are placed / removed. |
| */ |
| class Delegator[Child <: Delegate](id: Int) extends Block(id, Material.iron) { |
| setHardness(2f) |
| setCreativeTab(CreativeTab) |
| GameRegistry.registerBlock(this, classOf[Item], "oc.block." + id) |
| |
| // ----------------------------------------------------------------------- // |
| // SubBlock |
| // ----------------------------------------------------------------------- // |
| |
| val subBlocks = mutable.ArrayBuffer.empty[Child] |
| subBlocks.sizeHint(16) |
| |
| def add(subBlock: Child) = { |
| val blockId = subBlocks.length |
| subBlocks += subBlock |
| blockId |
| } |
| |
| def subBlock(world: IBlockAccess, x: Int, y: Int, z: Int): Option[Child] = |
| subBlock(world.getBlockMetadata(x, y, z)) |
| |
| def subBlock(metadata: Int) = |
| metadata match { |
| case blockId if blockId >= 0 && blockId < subBlocks.length => Some(subBlocks(blockId)) |
| case _ => None |
| } |
| |
| // ----------------------------------------------------------------------- // |
| // Rotation |
| // ----------------------------------------------------------------------- // |
| |
| def getFacing(world: IBlockAccess, x: Int, y: Int, z: Int) = |
| world.getBlockTileEntity(x, y, z) match { |
| case tileEntity: Rotatable => tileEntity.facing |
| case _ => ForgeDirection.UNKNOWN |
| } |
| |
| def setFacing(world: World, x: Int, y: Int, z: Int, value: ForgeDirection) = |
| world.getBlockTileEntity(x, y, z) match { |
| case rotatable: Rotatable => |
| rotatable.setFromFacing(value); true |
| case _ => false |
| } |
| |
| def setRotationFromEntityPitchAndYaw(world: World, x: Int, y: Int, z: Int, value: Entity) = |
| world.getBlockTileEntity(x, y, z) match { |
| case rotatable: Rotatable => |
| rotatable.setFromEntityPitchAndYaw(value); true |
| case _ => false |
| } |
| |
| private def toLocal(world: IBlockAccess, x: Int, y: Int, z: Int, value: ForgeDirection) = |
| world.getBlockTileEntity(x, y, z) match { |
| case rotatable: Rotatable => rotatable.toLocal(value) |
| case _ => value |
| } |
| |
| // ----------------------------------------------------------------------- // |
| // Block |
| // ----------------------------------------------------------------------- // |
| |
| override def breakBlock(world: World, x: Int, y: Int, z: Int, blockId: Int, metadata: Int) = { |
| subBlock(metadata) match { |
| case Some(subBlock) => { |
| world.getBlockTileEntity(x, y, z) match { |
| case environment: Environment if environment.node != null => |
| environment.node.remove() |
| case _ => // Nothing special to do. |
| } |
| subBlock.breakBlock(world, x, y, z, blockId) |
| } |
| case _ => // Invalid but avoid match error. |
| } |
| super.breakBlock(world, x, y, z, blockId, metadata) |
| } |
| |
| override def getRenderColor(metadata: Int) = |
| subBlock(metadata) match { |
| case Some(subBlock) => subBlock.getRenderColor |
| case _ => super.getRenderColor(metadata) |
| } |
| |
| override def colorMultiplier(world: IBlockAccess, x: Int, y: Int, z: Int) = |
| subBlock(world, x, y, z) match { |
| case Some(subBlock) => subBlock.colorMultiplier(world, x, y, z) |
| case _ => super.colorMultiplier(world, x, y, z) |
| } |
| |
| override def getDamageValue(world: World, x: Int, y: Int, z: Int) = world.getBlockMetadata(x, y, z) |
| |
| override def canConnectRedstone(world: IBlockAccess, x: Int, y: Int, z: Int, side: Int) = |
| subBlock(world, x, y, z) match { |
| case Some(subBlock) => subBlock.canConnectRedstone( |
| world, x, y, z, toLocal(world, x, y, z, side match { |
| case -1 => ForgeDirection.UP |
| case 0 => ForgeDirection.NORTH |
| case 1 => ForgeDirection.EAST |
| case 2 => ForgeDirection.SOUTH |
| case 3 => ForgeDirection.WEST |
| })) |
| case _ => false |
| } |
| |
| override def canProvidePower = true |
| |
| override def createTileEntity(world: World, metadata: Int): TileEntity = |
| subBlock(metadata) match { |
| case Some(subBlock) => subBlock.createTileEntity(world).orNull |
| case _ => null |
| } |
| |
| override def getCollisionBoundingBoxFromPool(world: World, x: Int, y: Int, z: Int) = |
| subBlock(world, x, y, z) match { |
| // TODO Rotate to world space if we have a Rotatable? |
| case Some(subBlock) => subBlock.getCollisionBoundingBoxFromPool(world, x, y, z) |
| case _ => super.getCollisionBoundingBoxFromPool(world, x, y, z) |
| } |
| |
| override def getBlockTexture(world: IBlockAccess, x: Int, y: Int, z: Int, side: Int) = |
| subBlock(world, x, y, z) match { |
| case Some(subBlock) => |
| val orientation = ForgeDirection.getOrientation(side) |
| subBlock.getBlockTextureFromSide(world, x, y, z, orientation, toLocal(world, x, y, z, orientation)) match { |
| case Some(icon) => icon |
| case _ => super.getBlockTexture(world, x, y, z, side) |
| } |
| case _ => super.getBlockTexture(world, x, y, z, side) |
| } |
| |
| override def getLightValue(world: IBlockAccess, x: Int, y: Int, z: Int) = |
| subBlock(world, x, y, z) match { |
| case Some(subBlock) => subBlock.getLightValue(world, x, y, z) |
| case _ => 0 |
| } |
| |
| override def getIcon(side: Int, metadata: Int) = |
| subBlock(metadata) match { |
| case Some(subBlock) => subBlock.icon(ForgeDirection.getOrientation(side)) match { |
| case Some(icon) => icon |
| case _ => super.getIcon(side, metadata) |
| } |
| case _ => super.getIcon(side, metadata) |
| } |
| |
| override def getLightOpacity(world: World, x: Int, y: Int, z: Int) = |
| subBlock(world, x, y, z) match { |
| case Some(subBlock) => subBlock.getLightOpacity(world, x, y, z) |
| case _ => 255 |
| } |
| |
| override def getSubBlocks(itemId: Int, creativeTab: CreativeTabs, list: util.List[_]) = { |
| // Workaround for MC's untyped lists... |
| def add[T](list: util.List[T], value: Any) = list.add(value.asInstanceOf[T]) |
| (0 until subBlocks.length). |
| foreach(id => add(list, new ItemStack(this, 1, id))) |
| } |
| |
| override def getRenderType = Config.blockRenderId |
| |
| def getUnlocalizedName(metadata: Int) = |
| subBlock(metadata) match { |
| case Some(subBlock) => subBlock.unlocalizedName |
| case _ => "oc.block" |
| } |
| |
| override def getValidRotations(world: World, x: Int, y: Int, z: Int) = |
| subBlock(world, x, y, z) match { |
| case Some(subBlock) => subBlock.getValidRotations(world, x, y, z) |
| case _ => super.getValidRotations(world, x, y, z) |
| } |
| |
| override def hasTileEntity(metadata: Int) = subBlock(metadata) match { |
| case Some(subBlock) => subBlock.hasTileEntity |
| case _ => false |
| } |
| |
| override def isBlockNormalCube(world: World, x: Int, y: Int, z: Int) = |
| subBlock(world.getBlockMetadata(x, y, z)) match { |
| case Some(subBlock) => subBlock.isBlockNormalCube(world, x, y, z) |
| case _ => false |
| } |
| |
| override def isProvidingStrongPower(world: IBlockAccess, x: Int, y: Int, z: Int, side: Int) = |
| subBlock(world, x, y, z) match { |
| case Some(subBlock) => subBlock.isProvidingStrongPower( |
| world, x, y, z, toLocal(world, x, y, z, ForgeDirection.getOrientation(side).getOpposite)) |
| case _ => 0 |
| } |
| |
| override def isProvidingWeakPower(world: IBlockAccess, x: Int, y: Int, z: Int, side: Int) = |
| subBlock(world, x, y, z) match { |
| case Some(subBlock) => subBlock.isProvidingWeakPower( |
| world, x, y, z, toLocal(world, x, y, z, ForgeDirection.getOrientation(side).getOpposite)) |
| case _ => 0 |
| } |
| |
| override def onBlockActivated(world: World, x: Int, y: Int, z: Int, player: EntityPlayer, side: Int, hitX: Float, hitY: Float, hitZ: Float): Boolean = { |
| // Helper method to detect items that can be used to rotate blocks, such as |
| // wrenches. This structural type is compatible with the BuildCraft wrench |
| // interface. |
| def canWrench = { |
| if (player.getCurrentEquippedItem != null) |
| try { |
| player.getCurrentEquippedItem.getItem.asInstanceOf[ { |
| def canWrench(player: EntityPlayer, x: Int, y: Int, z: Int): Boolean |
| }].canWrench(player, x, y, z) |
| } |
| catch { |
| case _: Throwable => false |
| } |
| else |
| false |
| } |
| |
| // Get valid rotations to skip rotating if there's only one. |
| val valid = getValidRotations(world, x, y, z) |
| if (valid.length > 1 && canWrench) |
| world.getBlockTileEntity(x, y, z) match { |
| case rotatable: Rotatable => { |
| if (player.isSneaking) { |
| // Rotate pitch. Get the valid pitch rotations. |
| val validPitch = valid.collect { |
| case direction if direction == ForgeDirection.DOWN || direction == ForgeDirection.UP => direction |
| case direction if direction != ForgeDirection.UNKNOWN => ForgeDirection.NORTH |
| } |
| // Check if there's more than one, and if so set to the next one. |
| if (validPitch.length > 1) { |
| // Can rotate, indicate that to the player. Note that the actual |
| // rotation logic is performed in rotateBlock. |
| return true |
| } |
| } |
| else { |
| // Rotate yaw. Get the valid yaw rotations. |
| val validYaw = valid.collect { |
| case direction if direction != ForgeDirection.DOWN && direction != ForgeDirection.UP && direction != ForgeDirection.UNKNOWN => direction |
| } |
| // Check if there's more than one, and if so set to the next one. |
| if (validYaw.length > 1) { |
| // Can rotate, indicate that to the player. Note that the actual |
| // rotation logic is performed in rotateBlock. |
| return true |
| } |
| } |
| } |
| case _ => // Cannot rotate this block. |
| } |
| |
| // No rotating tool in hand or can't rotate: activate the block. |
| subBlock(world, x, y, z) match { |
| case Some(subBlock) => subBlock.onBlockActivated( |
| world, x, y, z, player, ForgeDirection.getOrientation(side), hitX, hitY, hitZ) |
| case _ => false |
| } |
| } |
| |
| override def onBlockAdded(world: World, x: Int, y: Int, z: Int) = |
| subBlock(world, x, y, z) match { |
| case Some(subBlock) => subBlock.onBlockAdded(world, x, y, z) |
| case _ => // Invalid but avoid match error. |
| } |
| |
| override def onBlockPlacedBy(world: World, x: Int, y: Int, z: Int, player: EntityLivingBase, item: ItemStack) = |
| subBlock(world, x, y, z) match { |
| case Some(subBlock) => subBlock.onBlockPlacedBy(world, x, y, z, player, item) |
| case _ => // Invalid but avoid match error. |
| } |
| |
| override def onBlockPreDestroy(world: World, x: Int, y: Int, z: Int, metadata: Int) = |
| subBlock(world, x, y, z) match { |
| case Some(subBlock) => subBlock.onBlockPreDestroy(world, x, y, z) |
| case _ => // Invalid but avoid match error. |
| } |
| |
| override def onNeighborBlockChange(world: World, x: Int, y: Int, z: Int, blockId: Int) = |
| subBlock(world, x, y, z) match { |
| case Some(subBlock) => subBlock.onNeighborBlockChange(world, x, y, z, blockId) |
| case _ => // Invalid but avoid match error. |
| } |
| |
| override def registerIcons(iconRegister: IconRegister) = { |
| super.registerIcons(iconRegister) |
| subBlocks.foreach(_.registerIcons(iconRegister)) |
| } |
| |
| override def removeBlockByPlayer(world: World, player: EntityPlayer, x: Int, y: Int, z: Int) = |
| (subBlock(world, x, y, z) match { |
| case Some(subBlock) => subBlock.onBlockRemovedBy(world, x, y, z, player) |
| case _ => true |
| }) && super.removeBlockByPlayer(world, player, x, y, z) |
| |
| override def rotateBlock(world: World, x: Int, y: Int, z: Int, axis: ForgeDirection) = |
| world.getBlockTileEntity(x, y, z) match { |
| case rotatable: Rotatable if rotatable.rotate(axis) => |
| world.markBlockForRenderUpdate(x, y, z) |
| true |
| case _ => false |
| } |
| } |
| |
| class SimpleDelegator(id: Int) extends Delegator[SimpleDelegate](id) |
| |
| class SpecialDelegator(id: Int) extends Delegator[SpecialDelegate](id) { |
| override def isBlockSolid(world: IBlockAccess, x: Int, y: Int, z: Int, side: Int) = |
| subBlock(world.getBlockMetadata(x, y, z)) match { |
| case Some(subBlock) => subBlock.isBlockSolid(world, x, y, z, ForgeDirection.getOrientation(side)) |
| case _ => true |
| } |
| |
| override def isOpaqueCube = false |
| |
| override def renderAsNormalBlock = false |
| |
| override def shouldSideBeRendered(world: IBlockAccess, x: Int, y: Int, z: Int, side: Int) = |
| subBlock(world.getBlockMetadata(x, y, z)) match { |
| case Some(subBlock) => subBlock.shouldSideBeRendered(world, x, y, z, ForgeDirection.getOrientation(side)) |
| case _ => super.shouldSideBeRendered(world, x, y, z, side) |
| } |
| } |