package fi.dy.masa.enderutilities.util; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import javax.annotation.Nullable; import com.google.common.base.Predicate; import com.google.common.collect.Lists; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.Entity; import net.minecraft.init.Blocks; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.EnumFacing; import net.minecraft.util.Mirror; import net.minecraft.util.Rotation; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos.MutableBlockPos; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; import net.minecraft.world.border.WorldBorder; import net.minecraft.world.chunk.Chunk; import net.minecraftforge.common.DimensionManager; import net.minecraftforge.fml.common.FMLCommonHandler; import fi.dy.masa.enderutilities.util.nbt.TargetData; public class PositionUtils { public static final AxisAlignedBB ZERO_BB = new AxisAlignedBB(0, 0, 0, 0, 0, 0); public static final EnumFacing[] ADJACENT_SIDES_ZY = new EnumFacing[] { EnumFacing.DOWN, EnumFacing.UP, EnumFacing.NORTH, EnumFacing.SOUTH }; public static final EnumFacing[] ADJACENT_SIDES_XY = new EnumFacing[] { EnumFacing.DOWN, EnumFacing.UP, EnumFacing.EAST, EnumFacing.WEST }; public static final EnumFacing[] ADJACENT_SIDES_XZ = new EnumFacing[] { EnumFacing.NORTH, EnumFacing.SOUTH, EnumFacing.EAST, EnumFacing.WEST }; // These are additional offsets from the corresponding sides in ADJACENT_SIDES_XXX public static final EnumFacing[] CORNERS_ZY = new EnumFacing[] { EnumFacing.SOUTH, EnumFacing.NORTH, EnumFacing.DOWN, EnumFacing.UP }; public static final EnumFacing[] CORNERS_XY = new EnumFacing[] { EnumFacing.WEST, EnumFacing.EAST, EnumFacing.DOWN, EnumFacing.UP }; public static final EnumFacing[] CORNERS_XZ = new EnumFacing[] { EnumFacing.WEST, EnumFacing.EAST, EnumFacing.NORTH, EnumFacing.SOUTH }; public static final EnumFacing[][] FROM_TO_CW_ROTATION_AXES = new EnumFacing[][] { { null, null, EnumFacing.WEST, EnumFacing.EAST, EnumFacing.SOUTH, EnumFacing.NORTH }, // from down { null, null, EnumFacing.EAST, EnumFacing.WEST, EnumFacing.NORTH, EnumFacing.SOUTH }, // from up { EnumFacing.EAST, EnumFacing.WEST, null, null, EnumFacing.DOWN, EnumFacing.UP }, // from north { EnumFacing.WEST, EnumFacing.EAST, null, null, EnumFacing.UP, EnumFacing.DOWN }, // from south { EnumFacing.NORTH, EnumFacing.SOUTH, EnumFacing.UP, EnumFacing.DOWN, null, null }, // from west { EnumFacing.SOUTH, EnumFacing.NORTH, EnumFacing.DOWN, EnumFacing.UP, null, null } // from east }; public static EnumFacing[] getSidesForAxis(EnumFacing.Axis axis) { if (axis == EnumFacing.Axis.X) { return ADJACENT_SIDES_ZY; } return axis == EnumFacing.Axis.Z ? ADJACENT_SIDES_XY : ADJACENT_SIDES_XZ; } private static EnumFacing[] getCornersForAxis(EnumFacing.Axis axis) { if (axis == EnumFacing.Axis.X) { return CORNERS_ZY; } return axis == EnumFacing.Axis.Z ? CORNERS_XY : CORNERS_XZ; } public static BlockPosEU[] getAdjacentPositions(BlockPosEU center, EnumFacing front, boolean diagonals) { if (diagonals) { BlockPosEU[] positions = new BlockPosEU[8]; EnumFacing[] corners = getCornersForAxis(front.getAxis()); int i = 0; for (EnumFacing side : getSidesForAxis(front.getAxis())) { positions[i ] = center.offset(side); positions[i + 1] = positions[i].offset(corners[i / 2]); i += 2; } return positions; } else { BlockPosEU[] positions = new BlockPosEU[4]; int i = 0; for (EnumFacing side : getSidesForAxis(front.getAxis())) { positions[i++] = center.offset(side); } return positions; } } public static BlockPos[] getAdjacentPositions(BlockPos center, EnumFacing front, boolean diagonals) { if (diagonals) { BlockPos[] positions = new BlockPos[8]; EnumFacing[] corners = getCornersForAxis(front.getAxis()); int i = 0; for (EnumFacing side : getSidesForAxis(front.getAxis())) { positions[i ] = center.offset(side); positions[i + 1] = positions[i].offset(corners[i / 2]); i += 2; } return positions; } else { BlockPos[] positions = new BlockPos[4]; int i = 0; for (EnumFacing side : getSidesForAxis(front.getAxis())) { positions[i++] = center.offset(side); } return positions; } } public static BlockPos getAreaSizeFromRelativeEndPosition(BlockPos posEndRelative) { int x = posEndRelative.getX(); int y = posEndRelative.getY(); int z = posEndRelative.getZ(); x = x >= 0 ? x + 1 : x - 1; y = y >= 0 ? y + 1 : y - 1; z = z >= 0 ? z + 1 : z - 1; return new BlockPos(x, y, z); } public static BlockPos getRelativeEndPositionFromAreaSize(BlockPos size) { int x = size.getX(); int y = size.getY(); int z = size.getZ(); x = x >= 0 ? x - 1 : x + 1; y = y >= 0 ? y - 1 : y + 1; z = z >= 0 ? z - 1 : z + 1; return new BlockPos(x, y, z); } @Nullable public static EnumFacing getCWRotationAxis(EnumFacing from, EnumFacing to) { return FROM_TO_CW_ROTATION_AXES[from.getIndex()][to.getIndex()]; } public static EnumFacing rotateAround(EnumFacing facing, EnumFacing rotationAxis) { EnumFacing newFacing = facing.rotateAround(rotationAxis.getAxis()); if (rotationAxis.getAxisDirection() == EnumFacing.AxisDirection.POSITIVE) { return newFacing; } // Negative axis direction, if the facing was actually rotated then get the opposite return newFacing != facing ? newFacing.getOpposite() : facing; } /** * This returns the given <b>facing</b> as what it would be if the <b>mainFacing</b> * side was NORTH, which is the default rotation for the model. * That way the <b>facing</b> side's texture will be placed on the correct face * of the non-rotated model, before the <b>mainFacing</b> rotation is applied to the entire model. */ public static EnumFacing getRelativeFacing(EnumFacing mainFacing, EnumFacing facing) { switch (mainFacing) { // North is the default model rotation, don't modify the given facing for this mainFacing case NORTH: return facing; case SOUTH: if (facing.getAxis().isHorizontal()) { return facing.getOpposite(); } return facing; default: EnumFacing axis = PositionUtils.getCWRotationAxis(EnumFacing.NORTH, mainFacing).getOpposite(); if (facing.getAxis() != axis.getAxis()) { EnumFacing result = PositionUtils.rotateAround(facing, axis); //System.out.printf("facing: %s axis: %s filter: %s result: %s\n", facing, axis, facingFilteredOut, result); return result; } return facing; } } public static BlockPos getMinCorner(BlockPos pos1, BlockPos pos2) { return new BlockPos(Math.min(pos1.getX(), pos2.getX()), Math.min(pos1.getY(), pos2.getY()), Math.min(pos1.getZ(), pos2.getZ())); } public static BlockPos getMaxCorner(BlockPos pos1, BlockPos pos2) { return new BlockPos(Math.max(pos1.getX(), pos2.getX()), Math.max(pos1.getY(), pos2.getY()), Math.max(pos1.getZ(), pos2.getZ())); } public static boolean isPositionInsideArea(BlockPos pos, BlockPos posMin, BlockPos posMax) { return pos.getX() >= posMin.getX() && pos.getX() <= posMax.getX() && pos.getY() >= posMin.getY() && pos.getY() <= posMax.getY() && pos.getZ() >= posMin.getZ() && pos.getZ() <= posMax.getZ(); } public static boolean isPositionInsideArea(BlockPosEU pos, BlockPos posMin, BlockPos posMax) { return pos.getX() >= posMin.getX() && pos.getX() <= posMax.getX() && pos.getY() >= posMin.getY() && pos.getY() <= posMax.getY() && pos.getZ() >= posMin.getZ() && pos.getZ() <= posMax.getZ(); } public static boolean isPositionValid(BlockPosEU pos) { World world = DimensionManager.getWorld(pos.getDimension()); return world != null && pos.getY() >= 0 && pos.getY() < world.getHeight() && world.getWorldBorder().contains(pos.toBlockPos()); } public static BlockPos getPositionInfrontOfEntity(Entity entity) { BlockPos pos = new BlockPos(entity.posX, entity.posY, entity.posZ); if (entity.rotationPitch >= 80) { return pos.down(); } else if (entity.rotationPitch <= -80) { return new BlockPos(entity.posX, Math.ceil(entity.getEntityBoundingBox().maxY), entity.posZ); } double y = Math.floor(entity.posY + entity.getEyeHeight()); switch (entity.getHorizontalFacing()) { case EAST: return new BlockPos((int) Math.ceil( entity.posX + entity.width / 2), (int) y, (int) Math.floor(entity.posZ)); case WEST: return new BlockPos((int) Math.floor(entity.posX - entity.width / 2) - 1, (int) y, (int) Math.floor(entity.posZ)); case SOUTH: return new BlockPos((int) Math.floor(entity.posX), (int) y, (int) Math.ceil( entity.posZ + entity.width / 2) ); case NORTH: return new BlockPos((int) Math.floor(entity.posX), (int) y, (int) Math.floor(entity.posZ - entity.width / 2) - 1); default: } return pos; } public static Rotation getReverseRotation(Rotation rotation) { switch (rotation) { case CLOCKWISE_90: return Rotation.COUNTERCLOCKWISE_90; case COUNTERCLOCKWISE_90: return Rotation.CLOCKWISE_90; case CLOCKWISE_180: return Rotation.CLOCKWISE_180; default: return Rotation.NONE; } } /** * Turns a mirror value into a rotation that will result in the same transform * in the <b>facingIn</b> facing. If the axis of <b>facingIn</b> is vertical, then NONE is returned. */ /*public static Rotation getRotationFromMirror(EnumFacing facingIn, Mirror mirror, EnumFacing.Axis mirrorAxis) { EnumFacing.Axis facingAxis = facingIn.getAxis(); if (facingAxis.isHorizontal() == false) { return Rotation.NONE; } if (facingAxis == mirrorAxis) { return mirror == Mirror.FRONT_BACK ? Rotation.CLOCKWISE_180 : Rotation.NONE; } return mirror == Mirror.LEFT_RIGHT ? Rotation.CLOCKWISE_180 : Rotation.NONE; }*/ /** * Returns the facing that is mirrored by the value <b>mirror</b> * in relation to the mirror axis <b>mirrorAxis</b>. * So if the original facing is along the mirror axis, then * FRONT_BACK mirror will return the opposite facing. * If the original facing is NOT on the mirror axis, then LEFT_RIGHT * mirror will return the opposite facing. */ /*public static EnumFacing getMirroredFacing(EnumFacing facingIn, Mirror mirror, EnumFacing.Axis mirrorAxis) { EnumFacing.Axis facingAxis = facingIn.getAxis(); if (facingAxis.isHorizontal() == false) { return facingIn; } if (facingAxis == mirrorAxis) { return mirror == Mirror.FRONT_BACK ? facingIn.getOpposite() : facingIn; } return mirror == Mirror.LEFT_RIGHT ? facingIn.getOpposite() : facingIn; }*/ /** * Mirrors and then rotates the given position around the origin */ public static BlockPos getTransformedBlockPos(BlockPos pos, Mirror mirror, Rotation rotation) { int x = pos.getX(); int y = pos.getY(); int z = pos.getZ(); boolean isMirrored = true; switch (mirror) { // LEFT_RIGHT is essentially NORTH_SOUTH case LEFT_RIGHT: z = -z; break; // FRONT_BACK is essentially EAST_WEST case FRONT_BACK: x = -x; break; default: isMirrored = false; } switch (rotation) { case CLOCKWISE_90: return new BlockPos(-z, y, x); case COUNTERCLOCKWISE_90: return new BlockPos( z, y, -x); case CLOCKWISE_180: return new BlockPos(-x, y, -z); default: return isMirrored ? new BlockPos(x, y, z) : pos; } } /** * Mirrors and then rotates the given position around the origin */ public static BlockPosEU getTransformedBlockPos(BlockPosEU pos, Mirror mirror, Rotation rotation) { int x = pos.getX(); int y = pos.getY(); int z = pos.getZ(); boolean isMirrored = true; switch (mirror) { // LEFT_RIGHT is essentially NORTH_SOUTH case LEFT_RIGHT: z = -z; break; // FRONT_BACK is essentially EAST_WEST case FRONT_BACK: x = -x; break; default: isMirrored = false; } switch (rotation) { case CLOCKWISE_90: return new BlockPosEU(-z, y, x, pos.getDimension(), pos.getFacing()); case COUNTERCLOCKWISE_90: return new BlockPosEU( z, y, -x, pos.getDimension(), pos.getFacing()); case CLOCKWISE_180: return new BlockPosEU(-x, y, -z, pos.getDimension(), pos.getFacing()); default: return isMirrored ? new BlockPosEU(x, y, z, pos.getDimension(), pos.getFacing()) : pos; } } /** * Does the opposite transform from getTransformedBlockPos(), to return the original, * non-transformed position from the transformed position. */ public static BlockPosEU getOriginalPositionFromTransformed(BlockPosEU pos, Mirror mirror, Rotation rotation) { int x = pos.getX(); int y = pos.getY(); int z = pos.getZ(); int tmp; boolean noRotation = false; switch (rotation) { case CLOCKWISE_90: tmp = x; x = -z; z = tmp; case COUNTERCLOCKWISE_90: tmp = x; x = z; z = -tmp; case CLOCKWISE_180: x = -x; z = -z; default: noRotation = true; } switch (mirror) { case LEFT_RIGHT: z = -z; break; case FRONT_BACK: x = -x; break; default: if (noRotation) { return pos; } } return new BlockPosEU(x, y, z, pos.getDimension(), pos.getFacing()); } public static Vec3d transformedVec3d(Vec3d vec, Mirror mirrorIn, Rotation rotationIn) { double x = vec.x; double y = vec.y; double z = vec.z; boolean isMirrored = true; switch (mirrorIn) { case LEFT_RIGHT: z = 1.0D - z; break; case FRONT_BACK: x = 1.0D - x; break; default: isMirrored = false; } switch (rotationIn) { case COUNTERCLOCKWISE_90: return new Vec3d(z, y, 1.0D - x); case CLOCKWISE_90: return new Vec3d(1.0D - z, y, x); case CLOCKWISE_180: return new Vec3d(1.0D - x, y, 1.0D - z); default: return isMirrored ? new Vec3d(x, y, z) : vec; } } /** * Gets the "front" facing from the given positions, * so that pos1 is in the "front left" corner and pos2 is in the "back right" corner * of the area, when looking at the "front" face of the area. */ public static EnumFacing getFacingFromPositions(BlockPosEU pos1, BlockPosEU pos2) { if (pos1 == null || pos2 == null) { return null; } return getFacingFromPositions(pos1.getX(), pos1.getZ(), pos2.getX(), pos2.getZ()); } /** * Gets the "front" facing from the given positions, * so that pos1 is in the "front left" corner and pos2 is in the "back right" corner * of the area, when looking at the "front" face of the area. */ public static EnumFacing getFacingFromPositions(BlockPos pos1, BlockPos pos2) { if (pos1 == null || pos2 == null) { return null; } return getFacingFromPositions(pos1.getX(), pos1.getZ(), pos2.getX(), pos2.getZ()); } private static EnumFacing getFacingFromPositions(int x1,int z1, int x2, int z2) { if (x2 == x1) { return z2 > z1 ? EnumFacing.SOUTH : EnumFacing.NORTH; } if (z2 == z1) { return x2 > x1 ? EnumFacing.EAST : EnumFacing.WEST; } if (x2 > x1) { return z2 > z1 ? EnumFacing.EAST : EnumFacing.NORTH; } return z2 > z1 ? EnumFacing.SOUTH : EnumFacing.WEST; } public static Vec3d rotatePointAroundAxis(Vec3d point, Vec3d reference, EnumFacing from, EnumFacing to) { if (from == to) { return point; } return rotatePointAroundAxis(point.x, point.y, point.z, reference, from, to); } public static Vec3d rotatePointAroundAxis(double x, double y, double z, Vec3d reference, EnumFacing from, EnumFacing to) { if (to == from.getOpposite()) { double rx = reference.x; if (from.getAxis().isHorizontal()) { //System.out.printf("rotatePointAroundAxis - opposite, horizontal, from: %s to: %s\n", from, to); double rz = reference.z; x = rx + (rx - x); z = rz + (rz - z); } // Rotate around the z-axis when the to/from axes are vertical else { //System.out.printf("rotatePointAroundAxis - opposite, vertical, from: %s to: %s\n", from, to); double ry = reference.y; x = rx + (rx - x); y = ry + (ry - y); } return new Vec3d(x, y, z); } return rotatePointCWAroundAxis(x, y, z, reference, FROM_TO_CW_ROTATION_AXES[from.getIndex()][to.getIndex()]); } public static Vec3d rotatePointCWAroundAxis(Vec3d point, Vec3d reference, EnumFacing facing) { return rotatePointCWAroundAxis(point.x, point.y, point.z, reference, facing); } public static Vec3d rotatePointCWAroundAxis(double x, double y, double z, Vec3d reference, EnumFacing facing) { //System.out.printf("rotatePointCWAroundAxis - axis: %s, ref: %s, x: %.4f, y: %.4f, z: %.4f -> ", facing, reference, x, y, z); //System.out.printf("rotatePointCWAroundAxis - axis: %s, ref: %s, vec: %s -> ", facing, reference, new Vec3d(x, y, z)); //System.out.printf("rotatePointCWAroundAxis - axis: %s\n", facing); double rx = reference.x; double ry = reference.y; double rz = reference.z; double newX = x; double newY = y; double newZ = z; if (facing.getAxis() == EnumFacing.Axis.Y) { if (facing.getAxisDirection() == EnumFacing.AxisDirection.POSITIVE) { newX = rx - (z - rz); newZ = rz + (x - rx); } else { newX = rx + (z - rz); newZ = rz - (x - rx); } } else if (facing.getAxis() == EnumFacing.Axis.Z) { if (facing.getAxisDirection() == EnumFacing.AxisDirection.POSITIVE) { newX = rx + (y - ry); newY = ry - (x - rx); } else { newX = rx - (y - ry); newY = ry + (x - rx); } } else if (facing.getAxis() == EnumFacing.Axis.X) { if (facing.getAxisDirection() == EnumFacing.AxisDirection.POSITIVE) { newZ = rz - (y - ry); newY = ry + (z - rz); } else { newZ = rz + (y - ry); newY = ry - (z - rz); } } //System.out.printf("x: %.4f, y: %.4f, z: %.4f\n", newX, newY, newZ); //System.out.printf("vec: %s\n", new Vec3d(newX, newY, newZ)); return new Vec3d(newX, newY, newZ); } public static AxisAlignedBB rotateBoxAroundPoint(AxisAlignedBB bb, Vec3d reference, EnumFacing from, EnumFacing to) { if (from == to) { return bb; } Vec3d min = rotatePointAroundAxis(bb.minX, bb.minY, bb.minZ, reference, from, to); Vec3d max = rotatePointAroundAxis(bb.maxX, bb.maxY, bb.maxZ, reference, from, to); //System.out.printf("rotateBoxAroundPoint - from: %s to: %s ref: %s bb: %s, min: %s, max: %s\n", from, to, reference, bb, min, max); return new AxisAlignedBB(min.x, min.y, min.z, max.x, max.y, max.z); } /** * Returns the MutableBlockPos <b>pos</b> with a position set to <b>posReference</b> offset by <b>amount</b> in the direction <b>side</b>. */ public static MutableBlockPos getOffsetPosition(MutableBlockPos pos, BlockPos posReference, EnumFacing side, int amount) { switch (side) { case NORTH: pos.setPos(posReference.getX(), posReference.getY(), posReference.getZ() - amount); case SOUTH: pos.setPos(posReference.getX(), posReference.getY(), posReference.getZ() + amount); case EAST: pos.setPos(posReference.getX() + amount, posReference.getY(), posReference.getZ()); case WEST: pos.setPos(posReference.getX() - amount, posReference.getY(), posReference.getZ()); case UP: pos.setPos(posReference.getX(), posReference.getY() + amount, posReference.getZ()); case DOWN: pos.setPos(posReference.getX(), posReference.getY() - amount, posReference.getZ()); } return pos; } /** * Returns the rotation that would transform <b>facingOriginal</b> into <b>facingRotated</b>. * @param facingOriginal * @param facingRotated * @return */ public static Rotation getRotation(EnumFacing facingOriginal, EnumFacing facingRotated) { if (facingOriginal.getAxis() == EnumFacing.Axis.Y || facingRotated.getAxis() == EnumFacing.Axis.Y || facingOriginal == facingRotated) { return Rotation.NONE; } if (facingRotated == facingOriginal.getOpposite()) { return Rotation.CLOCKWISE_180; } return facingRotated == facingOriginal.rotateY() ? Rotation.CLOCKWISE_90 : Rotation.COUNTERCLOCKWISE_90; } public static boolean isWithinRange(BlockPos pos, Entity entity, int rangeH, int rangeV) { return isWithinRange(pos, entity.posX, entity.posY, entity.posZ, rangeH, rangeV); } public static boolean isWithinRange(BlockPos pos, double x, double y, double z, int rangeH, int rangeV) { return Math.abs(pos.getX() - x + 0.5) <= rangeH && Math.abs(pos.getZ() - z + 0.5) <= rangeH && Math.abs(pos.getY() - y + 0.5) <= rangeV; } public static boolean isWithinRange(BlockPos pos1, BlockPos pos2, int rangeH, int rangeVertPos, int rangeVertNeg) { return Math.abs(pos2.getX() - pos1.getX()) <= rangeH && Math.abs(pos2.getZ() - pos1.getZ()) <= rangeH && (pos2.getY() - pos1.getY()) <= rangeVertPos && (pos1.getY() - pos2.getY()) <= rangeVertNeg; } /** * Gets a list of all the TileEntities within the given range of the center position. * The list is sorted by distance to the center position (closest one first). * @param world * @param centerPos * @param rangeH * @param rangeVertPos the range upwards from the center position * @param rangeVertNeg the range downwards from the center position * @return */ public static List<BlockPosDistance> getTileEntityPositions(World world, BlockPos centerPos, int rangeH, int rangeVertPos, int rangeVertNeg) { return getTileEntityPositions(world, centerPos, rangeH, rangeVertPos, rangeVertNeg, null); } /** * Gets a list of all the TileEntities within the given range of the center position. * The list is sorted by distance to the center position (closest one first) * @param world * @param centerPos * @param rangeH * @param rangeVertPos the range upwards from the center position * @param rangeVertNeg the range downwards from the center position * @param filter a predicate to check if the found TileEntity is suitable * @return */ public static List<BlockPosDistance> getTileEntityPositions(World world, BlockPos centerPos, int rangeH, int rangeVertPos, int rangeVertNeg, Predicate <? super TileEntity> filter) { List<BlockPosDistance> posDist = new ArrayList<BlockPosDistance>(); for (int cx = (centerPos.getX() - rangeH) >> 4; cx <= ((centerPos.getX() + rangeH) >> 4); cx++) { for (int cz = (centerPos.getZ() - rangeH) >> 4; cz <= ((centerPos.getZ() + rangeH) >> 4); cz++) { if (world.isBlockLoaded(new BlockPos(cx << 4, centerPos.getY(), cz << 4), world.isRemote) == false) { continue; } Chunk chunk = world.getChunk(cx, cz); if (chunk != null) { Map<BlockPos, TileEntity> map = chunk.getTileEntityMap(); for (BlockPos pos : map.keySet()) { if (PositionUtils.isWithinRange(centerPos, pos, rangeH, rangeVertPos, rangeVertNeg) && (filter == null || filter.apply(map.get(pos)))) { posDist.add(new BlockPosDistance(pos, centerPos)); } } } } } Collections.sort(posDist); return posDist; } public static void getPositionsInBoxSpiralingOutwards(List<BlockPos> positions, int vertR, int horizR, int yLevel, int centerX, int centerZ) { getPositionsOnPlaneSpiralingOutwards(positions, horizR, yLevel, centerX, centerZ); for (int y = 1; y <= vertR; y++) { getPositionsOnPlaneSpiralingOutwards(positions, horizR, yLevel + y, centerX, centerZ); getPositionsOnPlaneSpiralingOutwards(positions, horizR, yLevel - y, centerX, centerZ); } } public static void getPositionsOnPlaneSpiralingOutwards(List<BlockPos> positions, int radius, int yLevel, int centerX, int centerZ) { positions.add(new BlockPos(centerX, yLevel, centerZ)); for (int r = 1; r <= radius; r++) { getPositionsOnRing(positions, r, yLevel, centerX, centerZ); } } public static void getPositionsOnRing(List<BlockPos> positions, int radius, int yLevel, int centerX, int centerZ) { int minX = centerX - radius; int minZ = centerZ - radius; int maxX = centerX + radius; int maxZ = centerZ + radius; for (int x = minX; x <= maxX; x++) { positions.add(new BlockPos(x, yLevel, minZ)); } for (int z = minZ + 1; z <= maxZ; z++) { positions.add(new BlockPos(maxX, yLevel, z)); } for (int x = maxX - 1; x >= minX; x--) { positions.add(new BlockPos(x, yLevel, maxZ)); } for (int z = maxZ - 1; z > minZ; z--) { positions.add(new BlockPos(minX, yLevel, z)); } } public static TargetData adjustTargetPosition(TargetData target, Entity entity) { if (target.blockFace < 0) { return target; } target = target.copy(); float widthAdj = entity.width / 2; target.dPosX += target.facing.getXOffset() * widthAdj; target.dPosZ += target.facing.getZOffset() * widthAdj; // Targeting the bottom face of a block, adjust the position lower if (target.facing == EnumFacing.DOWN) { target.dPosY -= entity.height; } return target; } /** * Returns the given position scaled by the given scale factors, and clamped to within the world border * of the destination world, with the given margin to the border. */ public static Vec3d getScaledClampedPosition(Vec3d pos, @Nullable World world, double scaleX, double scaleY, double scaleZ, int margin) { // The world border in other dimensions than the Overworld gets always // reset to the default 60M blocks when the dimensions loads. // The client side WorldBorder is synced on world load, so the // border _appears_ to be where the Overworld border is, // but on the server side the border is actually at 60M blocks, // unless the border has been changed using the commands, // because the border change listener will then update the border // size from the Overworld border to the other dimensions (see WorldServerMulti), // however that change only persists as long as the dimension stays loaded. // So we are just getting the border size from the Overworld for now... world = FMLCommonHandler.instance().getMinecraftServerInstance().getWorld(0); int worldLimit = 30000000 - margin; // Note: getActualHeight() could be better (at least for the Nether), but it would return 128 for the End too... int worldHeight = world != null ? world.getHeight() : 256; double posX = MathHelper.clamp(pos.x * scaleX, -worldLimit, worldLimit); double posY = MathHelper.clamp(pos.y * scaleY, 0, worldHeight); double posZ = MathHelper.clamp(pos.z * scaleZ, -worldLimit, worldLimit); if (world != null) { WorldBorder border = world.getWorldBorder(); margin = Math.min(margin, (int)(border.getDiameter() / 2)); posX = MathHelper.clamp(pos.x * scaleX, border.minX() + margin, border.maxX() - margin); posZ = MathHelper.clamp(pos.z * scaleZ, border.minZ() + margin, border.maxZ() - margin); //System.out.printf("border size: %.2f posX: %.4f posY: %.4f posZ: %.4f\n", border.getDiameter(), posX, posY, posZ); //System.out.printf("border: %s (%s)\n", border.getClass().getName(), border); } return new Vec3d(posX, posY, posZ); } /** * Gets the surrounding collision boxes for an EntityItem, when it is being pushed * out of blocks. This is identical to the vanilla method other than the vanilla * method not passing the entity to the addCollisionBoxToList() method, which * prevents having blocks that don't have any collision boxes for EntityItems, * when it comes to the pushing-out-of-blocks code. * @param world * @param bb * @param entity * @return */ public static List<AxisAlignedBB> getSurroundingCollisionBoxesForEntityItem(World world, AxisAlignedBB bb, @Nullable Entity entity) { List<AxisAlignedBB> list = Lists.<AxisAlignedBB>newArrayList(); int xMin = MathHelper.floor(bb.minX) - 1; int xMax = MathHelper.ceil( bb.maxX) + 1; int yMin = MathHelper.floor(bb.minY) - 1; int yMax = MathHelper.ceil( bb.maxY) + 1; int zMin = MathHelper.floor(bb.minZ) - 1; int zMax = MathHelper.ceil( bb.maxZ) + 1; BlockPos.PooledMutableBlockPos pos = BlockPos.PooledMutableBlockPos.retain(); for (int x = xMin; x < xMax; x++) { for (int z = zMin; z < zMax; z++) { int edges = (x != xMin && x != xMax - 1 ? 0 : 1) + (z != zMin && z != zMax - 1 ? 0 : 1); if (edges != 2 && world.isBlockLoaded(pos.setPos(x, 64, z))) { for (int y = yMin; y < yMax; y++) { if (edges <= 0 || y != yMin && y != yMax - 1) { pos.setPos(x, y, z); IBlockState state; if (x >= -30000000 && x < 30000000 && z >= -30000000 && z < 30000000) { state = world.getBlockState(pos); } else { state = Blocks.BEDROCK.getDefaultState(); } state.addCollisionBoxToList(world, pos, bb, list, entity, false); } } } } } pos.release(); return list; } }