package com.carpentersblocks.block; import java.util.List; import net.minecraft.block.Block; import net.minecraft.block.material.Material; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.MovingObjectPosition; import net.minecraft.util.Vec3; import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; import net.minecraftforge.common.util.ForgeDirection; import com.carpentersblocks.data.Collapsible; import com.carpentersblocks.tileentity.TEBase; import com.carpentersblocks.util.collapsible.CollapsibleUtil; import com.carpentersblocks.util.handler.EventHandler; import com.carpentersblocks.util.registry.BlockRegistry; import com.carpentersblocks.util.registry.ItemRegistry; public class BlockCarpentersCollapsibleBlock extends BlockSided { public BlockCarpentersCollapsibleBlock(Material material) { super(material, new Collapsible()); } @Override /** * Increase quad depth. */ protected boolean onHammerLeftClick(TEBase TE, EntityPlayer entityPlayer) { int stepOffset = Collapsible.INSTANCE.isPositive(TE) ? -1 : 1; int quad = Collapsible.getQuad(EventHandler.hitX, EventHandler.hitZ); int depth = Collapsible.getQuadDepth(TE, quad); Collapsible.setQuadDepth(TE, quad, depth + stepOffset, false); smoothAdjacentCollapsibles(TE, quad); return true; } @Override /** * Decrease quad depth. */ protected boolean onHammerRightClick(TEBase TE, EntityPlayer entityPlayer) { int stepOffset = Collapsible.INSTANCE.isPositive(TE) ? 1 : -1; int quad = Collapsible.getQuad(EventHandler.hitX, EventHandler.hitZ); int depth = Collapsible.getQuadDepth(TE, quad); Collapsible.setQuadDepth(TE, quad, depth + stepOffset, false); smoothAdjacentCollapsibles(TE, quad); return true; } @Override /** * Damages hammer with a chance to not damage. */ protected void damageItemWithChance(World world, EntityPlayer entityPlayer) { if (world.rand.nextFloat() <= ItemRegistry.itemHammerDamageChanceFromCollapsible) { super.damageItemWithChance(world, entityPlayer); } } /** * Will attempt to smooth transitions to any adjacent collapsible blocks * given a TE and source quadrant. */ private void smoothAdjacentCollapsibles(TEBase TE, int src_quadrant) { Collapsible data = Collapsible.INSTANCE; World world = TE.getWorldObj(); TEBase TE_XN = getTileEntity(world, TE.xCoord - 1, TE.yCoord, TE.zCoord); TEBase TE_XP = getTileEntity(world, TE.xCoord + 1, TE.yCoord, TE.zCoord); TEBase TE_ZN = getTileEntity(world, TE.xCoord, TE.yCoord, TE.zCoord - 1); TEBase TE_ZP = getTileEntity(world, TE.xCoord, TE.yCoord, TE.zCoord + 1); TEBase TE_XZNN = getTileEntity(world, TE.xCoord - 1, TE.yCoord, TE.zCoord - 1); TEBase TE_XZNP = getTileEntity(world, TE.xCoord - 1, TE.yCoord, TE.zCoord + 1); TEBase TE_XZPN = getTileEntity(world, TE.xCoord + 1, TE.yCoord, TE.zCoord - 1); TEBase TE_XZPP = getTileEntity(world, TE.xCoord + 1, TE.yCoord, TE.zCoord + 1); int depth = Collapsible.getQuadDepth(TE, src_quadrant); switch (src_quadrant) { case Collapsible.QUAD_XZNN: if (TE_ZN != null && data.match(TE, TE_ZN)) { Collapsible.setQuadDepth(TE_ZN, Collapsible.QUAD_XZNP, depth, true); } if (TE_XZNN != null && data.match(TE, TE_XZNN)) { Collapsible.setQuadDepth(TE_XZNN, Collapsible.QUAD_XZPP, depth, true); } if (TE_XN != null && data.match(TE, TE_XN)) { Collapsible.setQuadDepth(TE_XN, Collapsible.QUAD_XZPN, depth, true); } break; case Collapsible.QUAD_XZNP: if (TE_XN != null && data.match(TE, TE_XN)) { Collapsible.setQuadDepth(TE_XN, Collapsible.QUAD_XZPP, depth, true); } if (TE_XZNP != null && data.match(TE, TE_XZNP)) { Collapsible.setQuadDepth(TE_XZNP, Collapsible.QUAD_XZPN, depth, true); } if (TE_ZP != null && data.match(TE, TE_ZP)) { Collapsible.setQuadDepth(TE_ZP, Collapsible.QUAD_XZNN, depth, true); } break; case Collapsible.QUAD_XZPN: if (TE_XP != null && data.match(TE, TE_XP)) { Collapsible.setQuadDepth(TE_XP, Collapsible.QUAD_XZNN, depth, true); } if (TE_XZPN != null && data.match(TE, TE_XZPN)) { Collapsible.setQuadDepth(TE_XZPN, Collapsible.QUAD_XZNP, depth, true); } if (TE_ZN != null && data.match(TE, TE_ZN)) { Collapsible.setQuadDepth(TE_ZN, Collapsible.QUAD_XZPP, depth, true); } break; case Collapsible.QUAD_XZPP: if (TE_ZP != null && data.match(TE, TE_ZP)) { Collapsible.setQuadDepth(TE_ZP, Collapsible.QUAD_XZPN, depth, true); } if (TE_XZPP != null && data.match(TE, TE_XZPP)) { Collapsible.setQuadDepth(TE_XZPP, Collapsible.QUAD_XZNN, depth, true); } if (TE_XP != null && data.match(TE, TE_XP)) { Collapsible.setQuadDepth(TE_XP, Collapsible.QUAD_XZNP, depth, true); } break; } } @Override /** * Updates the blocks bounds based on its current state. Args: world, x, y, z */ public void setBlockBoundsBasedOnState(IBlockAccess blockAccess, int x, int y, int z) { TEBase TE = getTileEntity(blockAccess, x, y, z); if (TE != null) { float maxDepth = CollapsibleUtil.getBoundsMaxDepth(TE); if (Collapsible.INSTANCE.isPositive(TE)) { setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F, maxDepth, 1.0F); } else { setBlockBounds(0.0F, 1.0F - maxDepth, 0.0F, 1.0F, 1.0F, 1.0F); } } } @Override /** * Checks if the block is a solid face on the given side, used by placement logic. */ public boolean isSideSolid(IBlockAccess blockAccess, int x, int y, int z, ForgeDirection side) { TEBase TE = getTileEntity(blockAccess, x, y, z); if (TE != null) { if (isBlockSolid(blockAccess, x, y, z)) { return Collapsible.isSideSolid(TE, side); } } return false; } /** * Returns true if a slope should end at the given coords */ private boolean isSlopeBoundary(World world, int x, int y, int z) { TEBase TE = getTileEntity(world, x, y, z); if (TE != null) { return true; } return world.getBlock(x, y, z).getMaterial().blocksMovement() || !world.getBlock(x, y - 1, z).getMaterial().blocksMovement(); } /** * Scan X axis for slopes */ private int scanX(World world, int x, int y, int z, int dir, int maxDist) { for (int nx = x + dir; nx != x + maxDist * dir; nx += dir) { if (isSlopeBoundary(world, nx, y, z)) { return nx; } } return x + dir; } /** * Scan Z axis for slopes */ private int scanZ(World world, int x, int y, int z, int dir, int maxDist) { for (int nz = z + dir; nz != z + maxDist * dir; nz += dir) { if (isSlopeBoundary(world, x, y, nz)) { return nz; } } return z + dir; } /** * Returns block height */ private static int getBlockHeight(IBlockAccess blockAccess, int x, int y, int z) { Block block = blockAccess.getBlock(x, y, z); if (!block.getMaterial().blocksMovement()) { return 0; } return (int) (block.getBlockBoundsMaxY() * 16.0); } @Override /** * Called when a block is placed using its ItemBlock. Args: World, X, Y, Z, side, hitX, hitY, hitZ, block metadata */ public int onBlockPlaced(World world, int x, int y, int z, int side, float hitX, float hitY, float hitZ, int metadata) { // If side not supported, select best side based on y hit coordinates if (!canAttachToSide(side)) { return hitY > 0.5F ? 0 : 1; } return side; } @Override /** * Checks to see if you can place this block can be placed on that side of a block: BlockLever overrides */ public boolean canPlaceBlockOnSide(World world, int x, int y, int z, int side) { return canPlaceBlockAt(world, x, y, z); } @Override /** * Called when the block is placed in the world. */ public void onBlockPlacedBy(World world, int x, int y, int z, EntityLivingBase entityLiving, ItemStack itemStack) { super.onBlockPlacedBy(world, x, y, z, entityLiving, itemStack); TEBase TE = getTileEntity(world, x, y, z); // If sneaking, skip auto-setting quadrant depths if (!entityLiving.isSneaking()) { if (TE != null) { // Create a linear slope from neighbor blocks and collapsible quadrants // Mininum and maximum height of quadrants final int MIN_HEIGHT = 0; final int MAX_HEIGHT = 16; // Find slopes in landscape int xn = scanX(world, x, y, z, -1, MAX_HEIGHT); int xp = scanX(world, x, y, z, 1, MAX_HEIGHT); int zn = scanZ(world, x, y, z, -1, MAX_HEIGHT); int zp = scanZ(world, x, y, z, 1, MAX_HEIGHT); TEBase TE_XN = getTileEntity(world, xn, y, z); TEBase TE_XP = getTileEntity(world, xp, y, z); TEBase TE_ZN = getTileEntity(world, x, y, zn); TEBase TE_ZP = getTileEntity(world, x, y, zp); int height_XZNN = MIN_HEIGHT, height_XZPN = MIN_HEIGHT, height_XZPP = MIN_HEIGHT, height_XZNP = MIN_HEIGHT; Collapsible data = Collapsible.INSTANCE; int hxn1, hxn2; if(TE_XN != null && data.match(TE, TE_XN)) { hxn1 = Collapsible.getQuadDepth(TE_XN, Collapsible.QUAD_XZPN); hxn2 = Collapsible.getQuadDepth(TE_XN, Collapsible.QUAD_XZPP); } else { hxn1 = hxn2 = getBlockHeight(world, xn, y, z); } int hxp1, hxp2; if(TE_XP != null && data.match(TE, TE_XP)) { hxp1 = Collapsible.getQuadDepth(TE_XP, Collapsible.QUAD_XZNN); hxp2 = Collapsible.getQuadDepth(TE_XP, Collapsible.QUAD_XZNP); } else { hxp1 = hxp2 = getBlockHeight(world, xp, y, z); } int hzn1, hzn2; if(TE_ZN != null && data.match(TE, TE_ZN)) { hzn1 = Collapsible.getQuadDepth(TE_ZN, Collapsible.QUAD_XZNP); hzn2 = Collapsible.getQuadDepth(TE_ZN, Collapsible.QUAD_XZPP); } else { hzn1 = hzn2 = getBlockHeight(world, x, y, zn); } int hzp1, hzp2; if(TE_ZP != null && data.match(TE, TE_ZP)) { hzp1 = Collapsible.getQuadDepth(TE_ZP, Collapsible.QUAD_XZNN); hzp2 = Collapsible.getQuadDepth(TE_ZP, Collapsible.QUAD_XZPN); } else { hzp1 = hzp2 = getBlockHeight(world, x, y, zp); } // Lerp between heights, create smooth slope int xdist = x - xn; double dx1 = (double)(hxp1 - hxn1) / (xp - xn - 1); double dx2 = (double)(hxp2 - hxn2) / (xp - xn - 1); height_XZNN = Math.max(height_XZNN, (int)(hxn1 + dx1 * (xdist - 1))); height_XZNP = Math.max(height_XZNP, (int)(hxn2 + dx2 * (xdist - 1))); height_XZPN = Math.max(height_XZPN, (int)(hxn1 + dx1 * xdist)); height_XZPP = Math.max(height_XZPP, (int)(hxn2 + dx2 * xdist)); int zdist = z - zn; double dz1 = (double)(hzp1 - hzn1) / (zp - zn - 1); double dz2 = (double)(hzp2 - hzn2) / (zp - zn - 1); height_XZNN = Math.max(height_XZNN, (int)(hzn1 + dz1 * (zdist - 1))); height_XZNP = Math.max(height_XZNP, (int)(hzn1 + dz1 * zdist)); height_XZPN = Math.max(height_XZPN, (int)(hzn2 + dz2 * (zdist - 1))); height_XZPP = Math.max(height_XZPP, (int)(hzn2 + dz2 * zdist)); Collapsible.setQuadDepth(TE, Collapsible.QUAD_XZNN, height_XZNN, false); Collapsible.setQuadDepth(TE, Collapsible.QUAD_XZNP, height_XZNP, false); Collapsible.setQuadDepth(TE, Collapsible.QUAD_XZPP, height_XZPP, false); Collapsible.setQuadDepth(TE, Collapsible.QUAD_XZPN, height_XZPN, false); for (int quad = 0; quad < 4; ++quad) { smoothAdjacentCollapsibles(TE, quad); } } } else { Collapsible.setQuadDepth(TE, Collapsible.QUAD_XZNN, 16, false); Collapsible.setQuadDepth(TE, Collapsible.QUAD_XZNP, 16, false); Collapsible.setQuadDepth(TE, Collapsible.QUAD_XZPP, 16, false); Collapsible.setQuadDepth(TE, Collapsible.QUAD_XZPN, 16, false); } } @Override /** * Adds all intersecting collision boxes to a list. (Be sure to only add boxes to the list if they intersect the * mask.) Parameters: World, X, Y, Z, mask, list, colliding entity */ public void addCollisionBoxesToList(World world, int x, int y, int z, AxisAlignedBB axisAlignedBB, List list, Entity entity) { TEBase TE = getTileEntity(world, x, y, z); if (TE != null) { AxisAlignedBB colBox = null; for (int quad = 0; quad < 4; ++quad) { float[] bounds = CollapsibleUtil.genBounds(TE, quad); colBox = AxisAlignedBB.getBoundingBox(x + bounds[0], y + bounds[1], z + bounds[2], x + bounds[3], y + bounds[4], z + bounds[5]); if (axisAlignedBB.intersectsWith(colBox)) { list.add(colBox); } } } } @Override /** * Ray traces through the blocks collision from start vector to end vector returning a ray trace hit. Args: world, * x, y, z, startVec, endVec */ public MovingObjectPosition collisionRayTrace(World world, int x, int y, int z, Vec3 startVec, Vec3 endVec) { TEBase TE = getTileEntity(world, x, y, z); MovingObjectPosition finalTrace = null; if (TE != null) { double currDist = 0.0D; double maxDist = 0.0D; // Determine if ray trace is a hit on block for (int quad = 0; quad < 4; ++quad) { float[] bounds = CollapsibleUtil.genBounds(TE, quad); setBlockBounds(bounds[0], bounds[1], bounds[2], bounds[3], bounds[4], bounds[5]); MovingObjectPosition traceResult = super.collisionRayTrace(world, x, y, z, startVec, endVec); if (traceResult != null) { currDist = traceResult.hitVec.squareDistanceTo(endVec); if (currDist > maxDist) { finalTrace = traceResult; maxDist = currDist; } } } /* Determine true face hit since it's built of quadrants. */ if (finalTrace != null) { setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F, 1.0F, 1.0F); finalTrace = super.collisionRayTrace(world, x, y, z, startVec, endVec); } } return finalTrace; } @Override /** * Returns whether sides share faces based on sloping property and face shape. */ protected boolean shareFaces(TEBase TE_adj, TEBase TE_src, ForgeDirection side_adj, ForgeDirection side_src) { Collapsible data = Collapsible.INSTANCE; if (TE_adj.getBlockType() == this && data.match(TE_src, TE_adj)) { switch (side_adj) { case NORTH: return data.getQuadDepth(TE_adj, data.QUAD_XZNN) == data.getQuadDepth(TE_src, data.QUAD_XZNP) && data.getQuadDepth(TE_adj, data.QUAD_XZPN) == data.getQuadDepth(TE_src, data.QUAD_XZPP); case SOUTH: return data.getQuadDepth(TE_adj, data.QUAD_XZNP) == data.getQuadDepth(TE_src, data.QUAD_XZNN) && data.getQuadDepth(TE_adj, data.QUAD_XZPP) == data.getQuadDepth(TE_src, data.QUAD_XZPN); case WEST: return data.getQuadDepth(TE_adj, data.QUAD_XZNP) == data.getQuadDepth(TE_src, data.QUAD_XZPP) && data.getQuadDepth(TE_adj, data.QUAD_XZNN) == data.getQuadDepth(TE_src, data.QUAD_XZPN); case EAST: return data.getQuadDepth(TE_adj, data.QUAD_XZPP) == data.getQuadDepth(TE_src, data.QUAD_XZNP) && data.getQuadDepth(TE_adj, data.QUAD_XZPN) == data.getQuadDepth(TE_src, data.QUAD_XZNN); default: {} } } return super.shareFaces(TE_adj, TE_src, side_adj, side_src); } /** * Whether block can be attached to specified side of another block. * * @param side the side * @return whether side is supported */ @Override public boolean canAttachToSide(int side) { return side < 2; } /** * Whether block requires an adjacent block with solid side for support. * * @return whether block can float freely */ @Override public boolean canFloat() { return true; } @Override /** * The type of render function that is called for this block */ public int getRenderType() { return BlockRegistry.carpentersCollapsibleBlockRenderID; } }