Java tutorial
/******************************************************************************* * This file is part of Minebot. * * Minebot is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Minebot is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Minebot. If not, see <http://www.gnu.org/licenses/>. *******************************************************************************/ package net.famzangl.minecraft.minebot.ai; import java.io.File; import java.util.List; import java.util.Random; import org.apache.commons.lang3.RandomUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; import net.famzangl.minecraft.minebot.Pos; import net.famzangl.minecraft.minebot.ai.command.AIChatController; import net.famzangl.minecraft.minebot.ai.net.NetworkHelper; import net.famzangl.minecraft.minebot.ai.path.world.BlockBounds; import net.famzangl.minecraft.minebot.ai.path.world.WorldData; import net.famzangl.minecraft.minebot.ai.strategy.AIStrategy; import net.famzangl.minecraft.minebot.ai.task.BlockHalf; import net.famzangl.minecraft.minebot.ai.tools.ToolRater; import net.famzangl.minecraft.minebot.ai.utils.RandUtils; import net.famzangl.minecraft.minebot.build.BuildManager; import net.famzangl.minecraft.minebot.map.MapReader; import net.famzangl.minecraft.minebot.settings.MinebotSettings; import net.minecraft.block.Block; import net.minecraft.block.BlockFence; import net.minecraft.block.BlockFenceGate; import net.minecraft.block.BlockSlab; import net.minecraft.block.BlockStairs; import net.minecraft.block.BlockWall; import net.minecraft.block.state.IBlockState; import net.minecraft.client.Minecraft; import net.minecraft.client.settings.KeyBinding; import net.minecraft.entity.Entity; import net.minecraft.init.Blocks; import net.minecraft.item.ItemStack; import net.minecraft.util.BlockPos; import net.minecraft.util.EnumFacing; import net.minecraft.util.MathHelper; import net.minecraft.util.MovementInput; import net.minecraft.util.MovingObjectPosition; import net.minecraft.util.Vec3; import net.minecraft.util.MovingObjectPosition.MovingObjectType; import net.minecraft.util.ResourceLocation; import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.storage.ExtendedBlockStorage; import com.google.common.base.Predicate; /** * Lots and lots of helpful methods to control the current player. This contains * the current marked positions ({@link #pos1}, {@link #pos2}), fast methods to * access/check minecraft blocks ({@link #getBlockId(BlockPos)}), many lists of * different block types/presets useful for filtering, methods to control the * player ({@link #face(double, double, double)}, {@link #overrideUseItem()}), * some methods to get the player state ({@link #isAlive()}, * {@link #getPlayerPosition()}) and some to simplify block handling ( * {@link #getSignDirection(IBlockState)}) * * @author michael * */ public abstract class AIHelper { private static final Marker MARKER_FACING = MarkerManager.getMarker("facing"); private static final Logger LOGGER = LogManager.getLogger(AIHelper.class); private static final double SNEAK_OFFSET = .2; private static final double WALK_PER_STEP = 4.3 / 20; private static final double MIN_DISTANCE_ERROR = 0.05; private static final float MAX_PITCH_CHANGE = 20.0f; private static final float MAX_YAW_CHANGE = 22.5f; private static Minecraft mc = Minecraft.getMinecraft(); /** * A world that never gets a delta applied to it. */ private static WorldData minecraftWorld; private final Random rand = new Random(); public final BuildManager buildManager = new BuildManager(); private boolean objectMouseOverInvalidated; protected BlockPos pos1 = null; protected BlockPos pos2 = null; private MovementInput resetMovementInput; private KeyBinding resetAttackKey; private KeyBinding resetUseItemKey; private boolean useItemKeyJustPressed; private boolean attackKeyJustPressed; protected boolean doUngrab; private KeyBinding resetSneakKey; private boolean sneakKeyJustPressed; private KeyBinding resetSprintKey; private boolean sprintKeyJustPressed; protected MapReader activeMapReader; /** * A random number in a range, that does not exactly hit the sides. * * @param minY * @param maxY * @return */ private static double randBetweenNice(double minY, double maxY) { return maxY - minY < 0.1 ? (maxY + minY) / 2 : randBetween(minY + 0.03, maxY - 0.03); } private static double randBetween(double a, double b) { return RandUtils.getBetween(a, b); } protected void invalidateChunkCache() { if (minecraftWorld == null || mc.theWorld != minecraftWorld.getBackingWorld()) { minecraftWorld = mc.theWorld == null ? null : new WorldData(mc.theWorld, mc.thePlayer); } if (minecraftWorld != null) { minecraftWorld.invalidateChunkCache(); } } public Minecraft getMinecraft() { return mc; } public static File getMinebotDir() { File dir = new File(mc.mcDataDir, "minebot"); if (!dir.exists()) { dir.mkdir(); } return dir; } /** * Gets the pos1 marker. * * @return The marker or <code>null</code> if it is unset. */ public BlockPos getPos1() { return pos1; } /** * Gets the pos2 marker. * * @return The marker or <code>null</code> if it is unset. */ public BlockPos getPos2() { return pos2; } /** * Sets the pos1 or pos2 marker. * * @param pos * The new position. * @param isPos2 */ public void setPosition(BlockPos pos, boolean isPos2) { int posIndex; if (isPos2) { pos2 = pos; posIndex = 2; } else { pos1 = pos; posIndex = 1; } AIChatController.addChatLine("Set position" + posIndex + " to " + pos); } /** * Needs to be called whenever the player moved and the block faced might * have changed. */ public void invalidateObjectMouseOver() { objectMouseOverInvalidated = true; } /** * Gets the object the player is currently facing. This should always be * used instead of the Minecraft version, since it handles player movement * updates in the same game tick. * * @return */ public MovingObjectPosition getObjectMouseOver() { if (objectMouseOverInvalidated) { objectMouseOverInvalidated = false; mc.entityRenderer.getMouseOver(1.0F); } return mc.objectMouseOver; } /** * Gets the block at that position. * * @param pos * @return */ public Block getBlock(BlockPos pos) { // TODO: Warn that no delta is used. return getBlock(pos.getX(), pos.getY(), pos.getZ()); } /** * Gets the block at that position. * * @param x * @param y * @param z * @return The block. */ public Block getBlock(int x, int y, int z) { // TODO: Warn that no delta is used. return mc.theWorld.getBlockState(new BlockPos(x, y, z)).getBlock(); } public WorldData getWorld() { return minecraftWorld; } public boolean isFacing(Vec3 vec) { return isFacing(vec.xCoord, vec.yCoord, vec.zCoord); } public boolean isFacing(double x, double y, double z) { return face(x, y, z, 0, 0); } public boolean face(Vec3 vec) { return face(vec.xCoord, vec.yCoord, vec.zCoord); } /** * Faces an exact position in space. * * @param x * @param y * @param z */ public boolean face(double x, double y, double z) { return face(x, y, z, 1, 1); } private boolean face(double x, double y, double z, float yawInfluence, float pitchInfluence) { LOGGER.trace(MARKER_FACING, "facing " + x + "," + y + "," + z + " using influence: " + yawInfluence + ";" + pitchInfluence); final double d0 = x - mc.thePlayer.posX; final double d1 = z - mc.thePlayer.posZ; final double d2 = y - mc.thePlayer.posY - mc.thePlayer.getEyeHeight(); final double d3 = d0 * d0 + d2 * d2 + d1 * d1; if (d3 >= 2.500000277905201E-7D) { final float rotationYaw = mc.thePlayer.rotationYaw; final float rotationPitch = mc.thePlayer.rotationPitch; final float yaw = (float) (Math.atan2(d1, d0) * 180.0D / Math.PI) - 90.0F; final float pitch = (float) -(Math.atan2(d2, Math.sqrt(d0 * d0 + d1 * d1)) * 180.0D / Math.PI); float yawChange = closestRotation(yaw - rotationYaw); float pitchChange = pitch - rotationPitch; assert -Math.PI <= yawChange && yawChange <= Math.PI; float yawClamp = Math.min(Math.abs(MAX_YAW_CHANGE / yawChange), 1); float pitchClamp = Math.min(Math.abs(MAX_PITCH_CHANGE / pitchChange), 1); float clamp = Math.min(yawClamp, pitchClamp); if (yawInfluence <= 0 && pitchInfluence <= 0) { // only test, do not set return Math.abs(yawChange) < .01 && Math.abs(pitchChange) < .01; } yawInfluence = Math.min(yawInfluence, clamp); pitchInfluence = Math.min(pitchInfluence, clamp); // TODO: Make this linear? mc.thePlayer.setAngles(yawChange / 0.15f * yawInfluence, -pitchChange / 0.15f * pitchInfluence); invalidateObjectMouseOver(); LOGGER.trace(MARKER_FACING, "facing clamped at " + clamp + ", new influence: " + yawInfluence + ";" + pitchInfluence + ", done: " + (clamp > .999)); return clamp > .999; } return true; } private float closestRotation(float f) { float halfRot = 180; float fullRot = halfRot * 2; return (((f + halfRot) % fullRot + fullRot) % fullRot) - halfRot; } private float fullRotations(float yaw) { return (float) (((int) (yaw / (Math.PI * 2))) * Math.PI * 2); } /* * public void moveTo(int x, int y, int z) { face(x + .5, mc.thePlayer.posY, * z + .5); MovementInput i = new MovementInput(); i.moveForward = 0.8f; * overrideMovement(i); } */ public boolean isFacingBlock(BlockPos pos) { return isFacingBlock(pos.getX(), pos.getY(), pos.getZ()); } /** * Checks if the player is currently facing the block so that it can be * interacted with. * * @param x * @param y * @param z * @return <code>true</code> if it is facing the Block. */ public boolean isFacingBlock(int x, int y, int z) { final MovingObjectPosition o = getObjectMouseOver(); return o != null && o.typeOfHit == MovingObjectType.BLOCK && new BlockPos(x, y, z).equals(o.getBlockPos()) && (y < 255 || allowTopOfWorldHit() || o.sideHit != EnumFacing.UP); } private boolean allowTopOfWorldHit() { // TODO Use a preference for this. return false; } public boolean isFacingBlock(BlockPos pos, EnumFacing blockSide, BlockHalf half) { return isFacingBlock(pos.getX(), pos.getY(), pos.getZ(), blockSide, half); } /** * Checks if the player is facing a specific side of the block. * * @param x * @param y * @param z * @param blockSide * The side of the block. * @param half * Can restrict the check to the upper or lower half of the side * (not useful for top/bottom) * @return <code>true</code> if the player faces the block. */ public boolean isFacingBlock(int x, int y, int z, EnumFacing blockSide, BlockHalf half) { if (!isFacingBlock(x, y, z, blockSide)) { return false; } else { final double fy = getObjectMouseOver().hitVec.yCoord - y; return half != BlockHalf.LOWER_HALF && fy > .5 || half != BlockHalf.UPPER_HALF && fy <= .5; } } public boolean isFacingBlock(BlockPos pos, EnumFacing side) { return isFacingBlock(pos.getX(), pos.getY(), pos.getZ(), side); } /** * Checks if the player is facing a specific side of the block. * * @param x * @param y * @param z * @param blockSide * The side of the block as game integer. * @return <code>true</code> if the player faces the block. */ public boolean isFacingBlock(int x, int y, int z, EnumFacing side) { final MovingObjectPosition o = getObjectMouseOver(); return isFacingBlock(x, y, z) && o.sideHit == side; } public boolean isStandingOn(BlockPos pos) { return isStandingOn(pos.getX(), pos.getY(), pos.getZ()); } /** * Are the feet standing on that block? * * @param x * @param y * @param z * @return */ public boolean isStandingOn(int x, int y, int z) { // boolean isFence = blockIsOneOf(getBlock(x, y - 1, z), // FenceBuildTask.BLOCKS); return Math.abs(x + 0.5 - mc.thePlayer.posX) < 0.2 && Math.abs(z + 0.5 - mc.thePlayer.posZ) < 0.2 && Math.abs(mc.thePlayer.getEntityBoundingBox().minY - y) < 0.52; } public double realBlockTopY(BlockPos pos) { return realBlockTopY(pos.getX(), pos.getY(), pos.getZ()); } /** * The real top y cord of the block we stand on. Watch out: Minecraft blocks * do not always provide the right bounds with it's block objects. * * @param x * @param y * @param z * @return */ public double realBlockTopY(int x, int y, int z) { final Block block = getBlock(x, y - 1, z); // Fence bounds are not exposed... double maxY; if (block instanceof BlockFence || block instanceof BlockFenceGate || block instanceof BlockWall) { maxY = 1.5; } else if (block instanceof BlockSlab) { final int blockMetadata = getWorld().getBlockIdWithMeta(x, y, z) & 0xf; maxY = (blockMetadata & 0x8) == 0 ? 0.5 : 1; } else { maxY = block.getBlockBoundsMaxY(); } return y - 1 + maxY; } /** * Get the current position the player is at. * * @return The position. */ public BlockPos getPlayerPosition() { return getWorld().getPlayerPosition(); } /* * public void moveTo(int x, int y, int z) { face(x + .5, mc.thePlayer.posY, * z + .5); MovementInput i = new MovementInput(); i.moveForward = 0.8f; * overrideMovement(i); } */ /** * Checks if an item is on the hotbar. * * @param f * The item to search * @return <code>true</code> if it is selectable. */ public boolean canSelectItem(ItemFilter f) { for (int i = 0; i < 9; ++i) { if (f.matches(mc.thePlayer.inventory.getStackInSlot(i))) { return true; } } return false; } /** * Selects an item. * * @param f * The item to search for. * @return <code>true</code> if the player is now holding that item. */ public boolean selectCurrentItem(ItemFilter f) { if (f.matches(mc.thePlayer.inventory.getCurrentItem())) { return true; } for (int i = 0; i < 9; ++i) { if (f.matches(mc.thePlayer.inventory.getStackInSlot(i))) { mc.thePlayer.inventory.currentItem = i; return true; } } return false; } /** * Selects a good tool for mining the given Block. * * @param pos */ public void selectToolFor(final BlockPos pos) { ToolRater toolRater = new ToolRater(); toolRater.addRater(ToolRater.getToolMatchesRater()); selectToolFor(pos, toolRater); } /** * Selects a good tool for mining the given Block. * * @param pos * @param ToolRater * the tool rater that rates the tool. */ public float selectToolFor(final BlockPos pos, ToolRater rater) { int bestRatingSlot = mc.thePlayer.inventory.currentItem; if (bestRatingSlot < 0 || bestRatingSlot >= 9) { bestRatingSlot = 0; } int block = pos == null ? -1 : getWorld().getBlockIdWithMeta(pos); float bestRating = rater.rateTool(mc.thePlayer.inventory.getStackInSlot(bestRatingSlot), block); for (int i = 0; i < 9; ++i) { float rating = rater.rateTool(mc.thePlayer.inventory.getStackInSlot(i), block); if (rating > bestRating) { bestRating = rating; bestRatingSlot = i; } } mc.thePlayer.inventory.currentItem = bestRatingSlot; return bestRating; } /** * Faces a block and destroys it if possible. * * @param pos * the Position of that block. */ public void faceAndDestroy(final BlockPos pos) { if (!isFacingBlock(pos)) { faceBlock(pos); } if (isFacingBlock(pos)) { ToolRater settings = MinebotSettings.getSettings().getToolRater(); selectToolFor(pos, settings); overrideAttack(); } } public void faceAndDestroyWithHangingBlock(final BlockPos pos) { faceAndDestroy(pos); if (!isFacingBlock(pos)) { // Check if there is a block hanging here. for (EnumFacing d : EnumFacing.values()) { BlockPos offseted = pos.offset(d); if (isFacingBlock(offseted)) { BlockPos hanging = getWorld().getHangingOnBlock(offseted); if (hanging != null && hanging.equals(pos)) { overrideAttack(); } } } } } /** * Faces a block. * * @param pos * @return */ public boolean faceBlock(BlockPos pos) { return face(getWorld().getBlockBounds(pos).random(pos, .95)); } /** * Face the side of a block. * * @param pos * @param sideToFace * @return */ public boolean faceSideOf(BlockPos pos, EnumFacing sideToFace) { BlockBounds bounds = getWorld().getBlockBounds(pos); return face(bounds.onlySide(sideToFace).random(pos, 0.8)); } /** * Advanced version to face a specific side of a block. * * @param x * @param y * @param z * @param sideToFace * To restrict facing on a given side. Can be <code>null</code> * to disable. * @param minY * Limits the Y coordinate that is faced. * @param maxY * Limits the Y coordinate that is faced. * @param centerX * @param centerZ * @param xzdir * The direction the xz restriction affects. */ public void faceSideOf(BlockPos pos, EnumFacing sideToFace, double minY, double maxY, double centerX, double centerZ, EnumFacing xzdir) { // System.out.println("x = " + x + " y=" + y + " z=" + z + " dir=" // + sideToFace); BlockBounds bounds = getWorld().getBlockBounds(pos); BlockBounds faceArea = bounds.clampY(minY, maxY).onlySide(sideToFace); LOGGER.trace(MARKER_FACING, "Facing: " + faceArea); face(faceArea.random(pos, .9)); // minY = Math.max(minY, block.getBlockBoundsMinY()); // maxY = Math.min(maxY, block.getBlockBoundsMaxY()); // double faceY = randBetweenNice(minY, maxY); // double faceX, faceZ; // // if (xzdir == EnumFacing.EAST) { // faceX = randBetween(Math.max(block.getBlockBoundsMinX(), centerX), // block.getBlockBoundsMaxX()); // faceZ = centerZ; // } else if (xzdir == EnumFacing.WEST) { // faceX = randBetween(block.getBlockBoundsMinX(), // Math.min(block.getBlockBoundsMaxX(), centerX)); // faceZ = centerZ; // } else if (xzdir == EnumFacing.SOUTH) { // faceZ = randBetween(Math.max(block.getBlockBoundsMinZ(), centerZ), // block.getBlockBoundsMaxZ()); // faceX = centerX; // } else if (xzdir == EnumFacing.NORTH) { // faceZ = randBetween(block.getBlockBoundsMinZ(), // Math.min(block.getBlockBoundsMaxZ(), centerZ)); // faceX = centerX; // } else { // faceX = randBetweenNice(block.getBlockBoundsMinX(), // block.getBlockBoundsMaxX()); // faceZ = randBetweenNice(block.getBlockBoundsMinZ(), // block.getBlockBoundsMaxZ()); // } // switch (sideToFace) { // case UP: // faceY = block.getBlockBoundsMaxY(); // break; // case DOWN: // faceY = block.getBlockBoundsMinY(); // break; // case EAST: // faceX = block.getBlockBoundsMaxX(); // break; // case WEST: // faceX = block.getBlockBoundsMinX(); // break; // case SOUTH: // faceZ = block.getBlockBoundsMaxZ(); // break; // case NORTH: // faceZ = block.getBlockBoundsMinZ(); // break; // default: // break; // } // face(faceX + pos.getX(), faceY + pos.getY(), faceZ + pos.getZ()); } /** * Checks if an item is in the main inventory. * * @param itemFiler * @return */ public boolean hasItemInInvetory(ItemFilter itemFiler) { for (final ItemStack s : mc.thePlayer.inventory.mainInventory) { if (itemFiler.matches(s)) { return true; } } return false; } /** * overrides the keyboard input in the next game tick. * * @param i */ public void overrideMovement(MovementInput i) { if (resetMovementInput == null) { resetMovementInput = mc.thePlayer.movementInput; } mc.thePlayer.movementInput = i; } /** * Presses the use item key in the next game tick. */ public void overrideUseItem() { if (resetUseItemKey == null) { resetUseItemKey = mc.gameSettings.keyBindUseItem; // useItemKeyJustPressed |= resetUseItemKey.getIsKeyPressed(); } mc.gameSettings.keyBindUseItem = new InteractAlways(mc.gameSettings.keyBindAttack.getKeyDescription(), 501, mc.gameSettings.keyBindAttack.getKeyCategory(), !useItemKeyJustPressed); } /** * Presses the attack key in the next game tick. */ public void overrideAttack() { if (mc.thePlayer.isUsingItem()) { System.err.println("WARNING: Player is currently using an item, but attack was requested."); } if (resetAttackKey == null) { resetAttackKey = mc.gameSettings.keyBindAttack; // if (resetAttackKey.getIsKeyPressed()) { // System.out.println("Attack was pressed."); // } // This just made problems... // attackKeyJustPressed |= resetAttackKey.getIsKeyPressed(); } mc.gameSettings.keyBindAttack = new InteractAlways(mc.gameSettings.keyBindAttack.getKeyDescription(), 502, mc.gameSettings.keyBindAttack.getKeyCategory(), !attackKeyJustPressed); } /** * Presses the sneak key in the next game step. */ public void overrideSneak() { if (resetSneakKey == null) { resetSneakKey = mc.gameSettings.keyBindSneak; // sneakKeyJustPressed |= resetSneakKey.getIsKeyPressed(); } mc.gameSettings.keyBindSneak = new InteractAlways(mc.gameSettings.keyBindSneak.getKeyDescription(), 503, mc.gameSettings.keyBindSneak.getKeyCategory(), !sneakKeyJustPressed); } /** * Presses the sneak key in the next game step. */ public void overrideSprint() { if (resetSprintKey == null) { resetSprintKey = mc.gameSettings.keyBindSprint; // sneakKeyJustPressed |= resetSneakKey.getIsKeyPressed(); } mc.gameSettings.keyBindSprint = new InteractAlways(mc.gameSettings.keyBindSprint.getKeyDescription(), 504, mc.gameSettings.keyBindSprint.getKeyCategory(), !sprintKeyJustPressed); } /** * Restore all inputs to the default minecraft inputs. */ protected void resetAllInputs() { if (resetMovementInput != null) { mc.thePlayer.movementInput = resetMovementInput; resetMovementInput = null; } attackKeyJustPressed = resetAttackKey != null; if (resetAttackKey != null) { mc.gameSettings.keyBindAttack = resetAttackKey; resetAttackKey = null; } useItemKeyJustPressed = resetUseItemKey != null; if (resetUseItemKey != null) { mc.gameSettings.keyBindUseItem = resetUseItemKey; resetUseItemKey = null; } sneakKeyJustPressed = resetSneakKey != null; if (resetSneakKey != null) { mc.gameSettings.keyBindSneak = resetSneakKey; resetSneakKey = null; } sprintKeyJustPressed = resetSprintKey != null; if (resetSprintKey != null) { mc.gameSettings.keyBindSprint = resetSprintKey; resetSprintKey = null; } } protected boolean userTookOver() { final MovementInput mi = resetMovementInput == null ? mc.thePlayer.movementInput : resetMovementInput; final KeyBinding attack = resetAttackKey == null ? mc.gameSettings.keyBindAttack : resetAttackKey; final KeyBinding use = resetUseItemKey == null ? mc.gameSettings.keyBindUseItem : resetUseItemKey; final KeyBinding sneak = resetSneakKey == null ? mc.gameSettings.keyBindSneak : resetSneakKey; return mi.moveForward != 0 || mi.moveStrafe != 0 || mi.jump || attack.isKeyDown() || use.isKeyDown() || sneak.isKeyDown(); } /** * Ungrabs the mouse. */ public void ungrab() { doUngrab = true; } /** * Gets a list of entities in a given distance filtered with a filter. * * @param dist * @param selector * @return */ @SuppressWarnings("unchecked") public List<Entity> getEntities(int dist, Predicate<Entity> selector) { return mc.theWorld.func_175674_a(mc.getRenderViewEntity(), mc.getRenderViewEntity().getEntityBoundingBox() .addCoord(-dist, -dist, -dist).addCoord(dist, dist, dist).expand(1, 1, 1), selector); } /** * Gets the closest entity limited by a given distance. * * @param dist * @param selector * @return * @see #getEntities(int, Predicate<Entity>) */ public Entity getClosestEntity(int dist, Predicate<Entity> selector) { final List<Entity> entities = getEntities(dist, selector); double mindist = Double.MAX_VALUE; Entity found = null; for (final Entity e : entities) { final double mydist = e.getDistanceSqToEntity(mc.thePlayer); if (mydist < mindist) { found = e; mindist = mydist; } } return found; } /** * Sneak while standing on that block facing that direction. * * @param pos * The position of which we should sneak to the side. * @param inDirection * The side to sneak at. * @return <code>true</code> on arrival. */ public boolean sneakFrom(BlockPos pos, EnumFacing inDirection) { return sneakFrom(pos, inDirection, true); } /** * Sneak while standing on that block. * * @param pos * The position of which we should sneak to the side. * @param inDirection * The side to sneak at. * @param face * Should we face that position? * @return <code>true</code> on arrival. */ public boolean sneakFrom(BlockPos pos, EnumFacing inDirection, boolean face) { BlockBounds bounds = getWorld().getBlockBounds(pos); double destX = pos.getX() + .5; double destZ = pos.getZ() + .5; switch (inDirection) { case EAST: destX = pos.getX() + bounds.getMaxX() + SNEAK_OFFSET; break; case WEST: destX = pos.getX() + bounds.getMinX() - SNEAK_OFFSET; break; case SOUTH: destZ = pos.getZ() + bounds.getMaxZ() + SNEAK_OFFSET; break; case NORTH: destZ = pos.getZ() + bounds.getMinZ() - SNEAK_OFFSET; break; default: throw new IllegalArgumentException("Cannot handle " + inDirection); } return walkTowards(destX, destZ, false, face); } /** * Walks towards a given point. Automatically slows down at the point * * @param x * @param z * @param jump * If we should jump. * @return <code>true</code> after arrival. */ public boolean walkTowards(double x, double z, boolean jump) { return walkTowards(x, z, jump, true); } public boolean walkTowards(double x, double z, boolean jump, boolean face) { final double dx = x - mc.thePlayer.posX; final double dz = z - mc.thePlayer.posZ; final double distTo = Math.sqrt(dx * dx + dz * dz); boolean arrived = distTo > MIN_DISTANCE_ERROR; if (arrived) { if (face) { face(x, mc.thePlayer.getEyeHeight() + mc.thePlayer.posY, z, 1, .1f); } double speed = 1; if (distTo < 4 * WALK_PER_STEP) { speed = Math.max(distTo / WALK_PER_STEP / 4, 0.1); } final double yaw = mc.thePlayer.rotationYaw / 180 * Math.PI; final double lookX = -Math.sin(yaw); final double lookZ = Math.cos(yaw); final double dlength = Math.sqrt(dx * dx + dz * dz); final double same = (lookX * dx + lookZ * dz) / dlength; final double strafe = (lookZ * dx - lookX * dz) / dlength; LOGGER.trace(MARKER_FACING, "look: " + lookX + "," + lookZ + "; d = " + dx + "," + dz + "; walk: " + same + "," + strafe); final MovementInput movement = new MovementInput(); movement.moveForward = (float) (speed * same); movement.moveStrafe = (float) (speed * strafe); movement.jump = jump; overrideMovement(movement); if (distTo < 0.5 || mc.thePlayer.isSprinting() && distTo < 0.8) { overrideSneak(); } else if (distTo > 6) { overrideSprint(); } return false; } else { return true; } } public boolean arrivedAt(double x, double z) { final double dx = x - mc.thePlayer.posX; final double dz = z - mc.thePlayer.posZ; final double distTo = Math.sqrt(dx * dx + dz * dz); return distTo <= MIN_DISTANCE_ERROR; } public boolean isJumping() { return !mc.thePlayer.onGround; } /** * Gets the horizontal direction we look at. * * @return */ public EnumFacing getLookDirection() { switch (MathHelper.floor_double(getMinecraft().thePlayer.rotationYaw / 360 * 4 + .5) & 3) { case 1: return EnumFacing.WEST; case 2: return EnumFacing.NORTH; case 3: return EnumFacing.EAST; default: return EnumFacing.SOUTH; } } /** * See if the player has not died (=> the game over screen would be * displayed). * * @return <code>true</code> if it is alive. */ public boolean isAlive() { return mc.thePlayer != null && mc.thePlayer.getHealth() > 0.0F; } public void respawn() { if (!isAlive()) { mc.thePlayer.respawnPlayer(); mc.displayGuiScreen(null); } } /** * Converts an valid x/z pair to a direction if possible. * * @param x * @param z * @return */ public static EnumFacing getDirectionForXZ(int x, int z) { if (x != 0 || z != 0) { for (final EnumFacing d : EnumFacing.values()) { if (d.getFrontOffsetX() == x && d.getFrontOffsetZ() == z) { return d; } } } throw new IllegalArgumentException("Cannot convert to direction: " + x + " " + z); } public static EnumFacing getDirectionFor(BlockPos delta) { for (final EnumFacing d : EnumFacing.values()) { if (Pos.fromDir(d).equals(delta)) { return d; } } throw new IllegalArgumentException("Cannot convert to direction: " + delta); } // TODO: Move this to WorldData public int getLightAt(BlockPos pos) { final Chunk chunk = mc.theWorld.getChunkFromChunkCoords(pos.getX() >> 4, pos.getZ() >> 4); final ExtendedBlockStorage storage = chunk.getBlockStorageArray()[pos.getY() >> 4]; if (storage == null) { return 0; } else { return storage.getExtBlocklightValue(pos.getX() & 15, pos.getY() & 15, pos.getZ() & 15); } } public void setActiveMapReader(MapReader activeMapReader) { if (this.activeMapReader != null) { this.activeMapReader.onStop(); } this.activeMapReader = activeMapReader; } public abstract AIStrategy getResumeStrategy(); public abstract NetworkHelper getNetworkHelper(); public MapReader getActiveMapReader() { return activeMapReader; } }