diff --git a/README.md b/README.md index ea844c28..5d5d9c47 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ # Baritone A Minecraft bot. This project is an updated version of [Minebot](https://github.com/leijurv/MineBot/), -the original version of the bot for Minecraft 1.8, rebuilt for 1.12.2. \ No newline at end of file +the original version of the bot for Minecraft 1.8, rebuilt for 1.12.2. + +# Setup +- Open the project in IntelliJ as a Gradle project +- Run the Gradle task `setupDecompWorkspace` +- Run the Gradle task `genIntellijRuns` +- Restart IntelliJ and import Gradle changes +- Select the "Minecraft Client" launch config and run \ No newline at end of file diff --git a/build.gradle b/build.gradle index 9159f4d9..7dd75be8 100755 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ */ group 'baritone' -version '1.0' +version '1.0.0' buildscript { repositories { @@ -75,3 +75,8 @@ dependencies { } testImplementation 'junit:junit:4.12' } + +mixin { + defaultObfuscationEnv notch + add sourceSets.main, 'mixins.baritone.refmap.json' +} diff --git a/src/main/java/baritone/Baritone.java b/src/main/java/baritone/Baritone.java index 3dd2415a..d0124da2 100755 --- a/src/main/java/baritone/Baritone.java +++ b/src/main/java/baritone/Baritone.java @@ -21,6 +21,7 @@ import baritone.behavior.Behavior; import baritone.behavior.impl.LookBehavior; import baritone.behavior.impl.MemoryBehavior; import baritone.behavior.impl.PathingBehavior; +import baritone.behavior.impl.LocationTrackingBehavior; import baritone.event.GameEventHandler; import baritone.map.Map; import baritone.utils.InputOverrideHandler; @@ -72,6 +73,7 @@ public enum Baritone { registerBehavior(LookBehavior.INSTANCE); registerBehavior(MemoryBehavior.INSTANCE); registerBehavior(Map.INSTANCE); + registerBehavior(LocationTrackingBehavior.INSTANCE); } this.dir = new File(Minecraft.getMinecraft().gameDir, "baritone"); diff --git a/src/main/java/baritone/Settings.java b/src/main/java/baritone/Settings.java index 207703cb..9b918416 100644 --- a/src/main/java/baritone/Settings.java +++ b/src/main/java/baritone/Settings.java @@ -107,12 +107,25 @@ public class Settings { */ public Setting minimumImprovementRepropagation = new Setting<>(true); + /** + * Use a pythagorean metric (as opposed to the more accurate hybrid diagonal / traverse). + * You probably don't want this. It roughly triples nodes considered for no real advantage. + */ + public Setting pythagoreanMetric = new Setting<>(false); + /** * After calculating a path (potentially through cached chunks), artificially cut it off to just the part that is * entirely within currently loaded chunks. Improves path safety because cached chunks are heavily simplified. */ public Setting cutoffAtLoadBoundary = new Setting<>(true); + /** + * Stop 5 movements before anything that made the path COST_INF. + * For example, if lava has spread across the path, don't walk right up to it then recalculate, it might + * still be spreading lol + */ + public Setting costVerificationLookahead = new Setting<>(5); + /** * Static cutoff factor. 0.9 means cut off the last 10% of all paths, regardless of chunk load state */ @@ -141,6 +154,13 @@ public class Settings { */ public Setting maxFallHeightBucket = new Setting<>(20); + /** + * Is it okay to sprint through a descend followed by a diagonal? + * The player overshoots the landing, but not enough to fall off. And the diagonal ensures that there isn't + * lava or anything that's !canWalkInto in that space, so it's technically safe, just a little sketchy. + */ + public Setting allowOvershootDiagonalDescend = new Setting<>(true); + /** * If your goal is a GoalBlock in an unloaded chunk, assume it's far enough away that the Y coord * doesn't matter yet, and replace it with a GoalXZ to the same place before calculating a path. @@ -222,11 +242,24 @@ public class Settings { */ public Setting freeLook = new Setting<>(true); + /** + * Will cause some minor behavioral differences to ensure that Baritone works on anticheats. + *

+ * At the moment this will silently set the player's rotations when using freeLook so you're not sprinting in + * directions other than forward, which is picken up by more "advanced" anticheats like AAC, but not NCP. + */ + public Setting antiCheatCompatibility = new Setting<>(true); + /** * Exclusively use cached chunks for pathing */ public Setting pathThroughCachedOnly = new Setting<>(false); + /** + * Whether or not to use the "#" command prefix + */ + public Setting prefix = new Setting<>(false); + public final Map> byLowerName; public final List> allSettings; @@ -291,7 +324,7 @@ public class Settings { @SuppressWarnings("unchecked") public List> getByValueType(Class klass) { - ArrayList> result = new ArrayList<>(); + List> result = new ArrayList<>(); for (Setting setting : allSettings) { if (setting.klass.equals(klass)) { result.add((Setting) setting); diff --git a/src/main/java/baritone/behavior/impl/LocationTrackingBehavior.java b/src/main/java/baritone/behavior/impl/LocationTrackingBehavior.java new file mode 100644 index 00000000..b4caa766 --- /dev/null +++ b/src/main/java/baritone/behavior/impl/LocationTrackingBehavior.java @@ -0,0 +1,53 @@ +/* + * This file is part of Baritone. + * + * Baritone 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. + * + * Baritone 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 Baritone. If not, see . + */ + +package baritone.behavior.impl; + +import baritone.behavior.Behavior; +import baritone.chunk.Waypoint; +import baritone.chunk.WorldProvider; +import baritone.event.events.BlockInteractEvent; +import baritone.utils.BlockStateInterface; +import net.minecraft.block.BlockBed; + +/** + * A collection of event methods that are used to interact with Baritone's + * waypoint system. This class probably needs a better name. + * + * @see Waypoint + * + * @author Brady + * @since 8/22/2018 + */ +public final class LocationTrackingBehavior extends Behavior { + + public static final LocationTrackingBehavior INSTANCE = new LocationTrackingBehavior(); + + private LocationTrackingBehavior() {} + + @Override + public void onBlockInteract(BlockInteractEvent event) { + if (event.getType() == BlockInteractEvent.Type.USE && BlockStateInterface.getBlock(event.getPos()) instanceof BlockBed) { + WorldProvider.INSTANCE.getCurrentWorld().waypoints.addWaypoint(new Waypoint("bed", Waypoint.Tag.BED, event.getPos())); + } + } + + @Override + public void onPlayerDeath() { + WorldProvider.INSTANCE.getCurrentWorld().waypoints.addWaypoint(new Waypoint("death", Waypoint.Tag.DEATH, playerFeet())); + } +} diff --git a/src/main/java/baritone/behavior/impl/LookBehavior.java b/src/main/java/baritone/behavior/impl/LookBehavior.java index a531cd88..00ffce54 100644 --- a/src/main/java/baritone/behavior/impl/LookBehavior.java +++ b/src/main/java/baritone/behavior/impl/LookBehavior.java @@ -58,21 +58,42 @@ public class LookBehavior extends Behavior { @Override public void onPlayerUpdate(PlayerUpdateEvent event) { - if (event.getState() == EventState.PRE && this.target != null && this.force) { - player().rotationYaw = this.target.getFirst(); - float oldPitch = player().rotationPitch; - float desiredPitch = this.target.getSecond(); - player().rotationPitch = desiredPitch; - if (desiredPitch == oldPitch) { - nudgeToLevel(); + if (this.target == null) + return; + + // Whether or not we're going to silently set our angles + boolean silent = Baritone.settings().antiCheatCompatibility.get(); + + switch (event.getState()) { + case PRE: { + if (this.force) { + player().rotationYaw = this.target.getFirst(); + float oldPitch = player().rotationPitch; + float desiredPitch = this.target.getSecond(); + player().rotationPitch = desiredPitch; + if (desiredPitch == oldPitch) { + nudgeToLevel(); + } + this.target = null; + } else if (silent) { + this.lastYaw = player().rotationYaw; + player().rotationYaw = this.target.getFirst(); + } + break; + } + case POST: { + if (!this.force && silent) { + player().rotationYaw = this.lastYaw; + this.target = null; + } + break; } - this.target = null; } } @Override public void onPlayerRelativeMove(RelativeMoveEvent event) { - if (this.target != null && !force) { + if (this.target != null && !this.force) { switch (event.getState()) { case PRE: this.lastYaw = player().rotationYaw; @@ -80,7 +101,10 @@ public class LookBehavior extends Behavior { break; case POST: player().rotationYaw = this.lastYaw; - this.target = null; + + // If we have antiCheatCompatibility on, we're going to use the target value later in onPlayerUpdate() + if (!Baritone.settings().antiCheatCompatibility.get()) + this.target = null; break; } } diff --git a/src/main/java/baritone/behavior/impl/PathingBehavior.java b/src/main/java/baritone/behavior/impl/PathingBehavior.java index b35baca1..83a479a4 100644 --- a/src/main/java/baritone/behavior/impl/PathingBehavior.java +++ b/src/main/java/baritone/behavior/impl/PathingBehavior.java @@ -19,6 +19,7 @@ package baritone.behavior.impl; import baritone.Baritone; import baritone.behavior.Behavior; +import baritone.event.events.PathEvent; import baritone.event.events.PlayerUpdateEvent; import baritone.event.events.RenderEvent; import baritone.event.events.TickEvent; @@ -59,6 +60,10 @@ public class PathingBehavior extends Behavior { private boolean lastAutoJump; + private void dispatchPathEvent(PathEvent event) { + new Thread(() -> Baritone.INSTANCE.getGameEventHandler().onPathEvent(event)).start(); + } + @Override public void onTick(TickEvent event) { if (event.getType() == TickEvent.Type.OUT) { @@ -74,6 +79,7 @@ public class PathingBehavior extends Behavior { current = null; if (goal == null || goal.isInGoal(playerFeet())) { displayChatMessageRaw("All done. At " + goal); + dispatchPathEvent(PathEvent.AT_GOAL); next = null; return; } @@ -85,10 +91,12 @@ public class PathingBehavior extends Behavior { // but if we fail in the middle of current // we're nowhere close to our planned ahead path // so need to discard it sadly. + dispatchPathEvent(PathEvent.DISCARD_NEXT); next = null; } if (next != null) { displayChatMessageRaw("Continuing on to planned next path"); + dispatchPathEvent(PathEvent.CONTINUING_ONTO_PLANNED_NEXT); current = next; next = null; return; @@ -96,9 +104,11 @@ public class PathingBehavior extends Behavior { // at this point, current just ended, but we aren't in the goal and have no plan for the future synchronized (pathCalcLock) { if (isPathCalcInProgress) { + dispatchPathEvent(PathEvent.PATH_FINISHED_NEXT_STILL_CALCULATING); // if we aren't calculating right now return; } + dispatchPathEvent(PathEvent.CALC_STARTED); findPathInNewThread(pathStart(), true, Optional.empty()); } return; @@ -110,6 +120,7 @@ public class PathingBehavior extends Behavior { if (next.getPath().positions().contains(playerFeet())) { // jump directly onto the next path displayChatMessageRaw("Splicing into planned next path early..."); + dispatchPathEvent(PathEvent.SPLICING_ONTO_NEXT_EARLY); current = next; next = null; return; @@ -132,6 +143,7 @@ public class PathingBehavior extends Behavior { if (ticksRemainingInSegment().get() < Baritone.settings().planningTickLookAhead.get()) { // and this path has 5 seconds or less left displayChatMessageRaw("Path almost over. Planning ahead..."); + dispatchPathEvent(PathEvent.NEXT_SEGMENT_CALC_STARTED); findPathInNewThread(current.getPath().getDest(), false, Optional.of(current.getPath())); } } @@ -164,6 +176,10 @@ public class PathingBehavior extends Behavior { this.goal = goal; } + public Goal getGoal() { + return goal; + } + public PathExecutor getCurrent() { return current; } @@ -192,6 +208,7 @@ public class PathingBehavior extends Behavior { if (isPathCalcInProgress) { return; } + dispatchPathEvent(PathEvent.CALC_STARTED); findPathInNewThread(pathStart(), true, Optional.empty()); } } @@ -227,19 +244,29 @@ public class PathingBehavior extends Behavior { if (Baritone.settings().cutoffAtLoadBoundary.get()) { path = path.map(IPath::cutoffAtLoadedChunks); } - path.map(p -> p.staticCutoff(goal)).map(PathExecutor::new).ifPresent(p -> { - synchronized (pathPlanLock) { - if (current == null) { - current = p; + Optional executor = path.map(p -> p.staticCutoff(goal)).map(PathExecutor::new); + synchronized (pathPlanLock) { + if (current == null) { + if (executor.isPresent()) { + dispatchPathEvent(PathEvent.CALC_FINISHED_NOW_EXECUTING); + current = executor.get(); } else { - if (next == null) { - next = p; + dispatchPathEvent(PathEvent.CALC_FAILED); + } + } else { + if (next == null) { + if (executor.isPresent()) { + dispatchPathEvent(PathEvent.NEXT_SEGMENT_CALC_FINISHED); + next = executor.get(); } else { - throw new IllegalStateException("I have no idea what to do with this path"); + dispatchPathEvent(PathEvent.NEXT_CALC_FAILED); } + } else { + throw new IllegalStateException("I have no idea what to do with this path"); } } - }); + } + if (talkAboutIt && current != null && current.getPath() != null) { if (goal == null || goal.isInGoal(current.getPath().getDest())) { displayChatMessageRaw("Finished finding a path from " + start + " to " + goal + ". " + current.getPath().getNumNodesConsidered() + " nodes considered"); diff --git a/src/main/java/baritone/chunk/CachedChunk.java b/src/main/java/baritone/chunk/CachedChunk.java index f4d56fd2..520bcbea 100644 --- a/src/main/java/baritone/chunk/CachedChunk.java +++ b/src/main/java/baritone/chunk/CachedChunk.java @@ -21,8 +21,10 @@ import baritone.utils.pathing.IBlockTypeAccess; import baritone.utils.pathing.PathingBlockType; import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; +import net.minecraft.init.Blocks; +import net.minecraft.util.math.BlockPos; -import java.util.BitSet; +import java.util.*; /** * @author Brady @@ -30,6 +32,39 @@ import java.util.BitSet; */ public final class CachedChunk implements IBlockTypeAccess { + public static final Set BLOCKS_TO_KEEP_TRACK_OF = Collections.unmodifiableSet(new HashSet() {{ + add(Blocks.DIAMOND_ORE); + add(Blocks.DIAMOND_BLOCK); + //add(Blocks.COAL_ORE); + add(Blocks.COAL_BLOCK); + //add(Blocks.IRON_ORE); + add(Blocks.IRON_BLOCK); + //add(Blocks.GOLD_ORE); + add(Blocks.GOLD_BLOCK); + add(Blocks.EMERALD_ORE); + add(Blocks.EMERALD_BLOCK); + + add(Blocks.ENDER_CHEST); + add(Blocks.FURNACE); + add(Blocks.CHEST); + add(Blocks.END_PORTAL); + add(Blocks.END_PORTAL_FRAME); + add(Blocks.MOB_SPAWNER); + // TODO add all shulker colors + add(Blocks.PORTAL); + add(Blocks.HOPPER); + add(Blocks.BEACON); + add(Blocks.BREWING_STAND); + add(Blocks.SKULL); + add(Blocks.ENCHANTING_TABLE); + add(Blocks.ANVIL); + add(Blocks.LIT_FURNACE); + add(Blocks.BED); + add(Blocks.DRAGON_EGG); + add(Blocks.JUKEBOX); + add(Blocks.END_GATEWAY); + }}); + /** * The size of the chunk data in bits. Equal to 16 KiB. *

@@ -66,7 +101,9 @@ public final class CachedChunk implements IBlockTypeAccess { private final int[] heightMap; - CachedChunk(int x, int z, BitSet data, String[] overview) { + private final Map> specialBlockLocations; + + CachedChunk(int x, int z, BitSet data, String[] overview, Map> specialBlockLocations) { validateSize(data); this.x = x; @@ -74,6 +111,7 @@ public final class CachedChunk implements IBlockTypeAccess { this.data = data; this.overview = overview; this.heightMap = new int[256]; + this.specialBlockLocations = specialBlockLocations; calculateHeightMap(); } @@ -82,11 +120,7 @@ public final class CachedChunk implements IBlockTypeAccess { int internalPos = z << 4 | x; if (heightMap[internalPos] == y) { // we have this exact block, it's a surface block - String name = overview[internalPos]; - if (!name.contains(":")) { - name = "minecraft:" + name; - } - IBlockState state = Block.getBlockFromName(name).getDefaultState(); + IBlockState state = ChunkPacker.stringToBlock(overview[internalPos]).getDefaultState(); //System.out.println("Saying that " + x + "," + y + "," + z + " is " + state); return state; } @@ -118,6 +152,21 @@ public final class CachedChunk implements IBlockTypeAccess { return overview; } + public final Map> getRelativeBlocks() { + return specialBlockLocations; + } + + public final LinkedList getAbsoluteBlocks(String blockType) { + if (specialBlockLocations.get(blockType) == null) { + return null; + } + LinkedList res = new LinkedList<>(); + for (BlockPos pos : specialBlockLocations.get(blockType)) { + res.add(new BlockPos(pos.getX() + x * 16, pos.getY(), pos.getZ() + z * 16)); + } + return res; + } + /** * @return Returns the raw packed chunk data as a byte array */ diff --git a/src/main/java/baritone/chunk/CachedRegion.java b/src/main/java/baritone/chunk/CachedRegion.java index d8996fa3..7d2ed3dc 100644 --- a/src/main/java/baritone/chunk/CachedRegion.java +++ b/src/main/java/baritone/chunk/CachedRegion.java @@ -19,13 +19,13 @@ package baritone.chunk; import baritone.utils.pathing.IBlockTypeAccess; import net.minecraft.block.state.IBlockState; +import net.minecraft.util.math.BlockPos; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.BitSet; -import java.util.function.Consumer; +import java.util.*; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; @@ -70,34 +70,39 @@ public final class CachedRegion implements IBlockTypeAccess { @Override public final IBlockState getBlock(int x, int y, int z) { - CachedChunk chunk = this.getChunk(x >> 4, z >> 4); + CachedChunk chunk = chunks[x >> 4][z >> 4]; if (chunk != null) { return chunk.getBlock(x & 15, y, z & 15); } return null; } - final void updateCachedChunk(int chunkX, int chunkZ, CachedChunk chunk) { + public final LinkedList getLocationsOf(String block) { + LinkedList res = new LinkedList<>(); + for (int chunkX = 0; chunkX < 32; chunkX++) { + for (int chunkZ = 0; chunkZ < 32; chunkZ++) { + if (chunks[chunkX][chunkZ] == null) { + continue; + } + List locs = chunks[chunkX][chunkZ].getAbsoluteBlocks(block); + if (locs == null) { + continue; + } + for (BlockPos pos : locs) { + res.add(pos); + } + } + } + return res; + } + + final synchronized void updateCachedChunk(int chunkX, int chunkZ, CachedChunk chunk) { this.chunks[chunkX][chunkZ] = chunk; hasUnsavedChanges = true; } - private CachedChunk getChunk(int chunkX, int chunkZ) { - return this.chunks[chunkX][chunkZ]; - } - public void forEachChunk(Consumer consumer) { - for (int x = 0; x < 32; x++) { - for (int z = 0; z < 32; z++) { - CachedChunk chunk = getChunk(x, z); - if (chunk != null) { - consumer.accept(chunk); - } - } - } - } - - public final void save(String directory) { + public synchronized final void save(String directory) { if (!hasUnsavedChanges) { return; } @@ -139,7 +144,22 @@ public final class CachedRegion implements IBlockTypeAccess { } } } - out.writeInt(~CACHED_REGION_MAGIC); + for (int z = 0; z < 32; z++) { + for (int x = 0; x < 32; x++) { + if (chunks[x][z] != null) { + Map> locs = chunks[x][z].getRelativeBlocks(); + out.writeShort(locs.entrySet().size()); + for (Map.Entry> entry : locs.entrySet()) { + out.writeUTF(entry.getKey()); + out.writeShort(entry.getValue().size()); + for (BlockPos pos : entry.getValue()) { + out.writeByte((byte) (pos.getZ() << 4 | pos.getX())); + out.writeByte((byte) (pos.getY())); + } + } + } + } + } } hasUnsavedChanges = false; System.out.println("Saved region successfully"); @@ -148,7 +168,7 @@ public final class CachedRegion implements IBlockTypeAccess { } } - public void load(String directory) { + public synchronized void load(String directory) { try { Path path = Paths.get(directory); if (!Files.exists(path)) @@ -174,6 +194,7 @@ public final class CachedRegion implements IBlockTypeAccess { throw new IOException("Bad magic value " + magic); } CachedChunk[][] tmpCached = new CachedChunk[32][32]; + Map>[][] location = new Map[32][32]; for (int z = 0; z < 32; z++) { for (int x = 0; x < 32; x++) { int isChunkPresent = in.read(); @@ -181,7 +202,12 @@ public final class CachedRegion implements IBlockTypeAccess { case CHUNK_PRESENT: byte[] bytes = new byte[CachedChunk.SIZE_IN_BYTES]; in.readFully(bytes); - tmpCached[x][z] = new CachedChunk(x, z, BitSet.valueOf(bytes), new String[256]); + location[x][z] = new HashMap<>(); + int regionX = this.x; + int regionZ = this.z; + int chunkX = x + 32 * regionX; + int chunkZ = z + 32 * regionZ; + tmpCached[x][z] = new CachedChunk(chunkX, chunkZ, BitSet.valueOf(bytes), new String[256], location[x][z]); break; case CHUNK_NOT_PRESENT: tmpCached[x][z] = null; @@ -200,9 +226,33 @@ public final class CachedRegion implements IBlockTypeAccess { } } } - int fileEndMagic = in.readInt(); - if (fileEndMagic != ~magic) { - throw new IOException("Bad end of file magic"); + for (int z = 0; z < 32; z++) { + for (int x = 0; x < 32; x++) { + if (tmpCached[x][z] != null) { + // 16 * 16 * 256 = 65536 so a short is enough + // ^ haha jokes on leijurv, java doesn't have unsigned types so that isn't correct + // also why would you have more than 32767 special blocks in a chunk + // haha double jokes on you now it works for 65535 not just 32767 + int numSpecialBlockTypes = in.readShort() & 0xffff; + for (int i = 0; i < numSpecialBlockTypes; i++) { + String blockName = in.readUTF(); + List locs = new ArrayList<>(); + location[x][z].put(blockName, locs); + int numLocations = in.readShort() & 0xffff; + if (numLocations == 0) { + // an entire chunk full of air can happen in the end + numLocations = 65536; + } + for (int j = 0; j < numLocations; j++) { + byte xz = in.readByte(); + int X = xz & 0x0f; + int Z = (xz >>> 4) & 0x0f; + int Y = in.readByte() & 0xff; + locs.add(new BlockPos(X, Y, Z)); + } + } + } + } } // only if the entire file was uncorrupted do we actually set the chunks for (int x = 0; x < 32; x++) { @@ -219,15 +269,6 @@ public final class CachedRegion implements IBlockTypeAccess { } } - private static boolean isAllZeros(final byte[] array) { - for (byte b : array) { - if (b != 0) { - return false; - } - } - return true; - } - /** * @return The region x coordinate */ diff --git a/src/main/java/baritone/chunk/CachedWorld.java b/src/main/java/baritone/chunk/CachedWorld.java index 5374e837..b24857de 100644 --- a/src/main/java/baritone/chunk/CachedWorld.java +++ b/src/main/java/baritone/chunk/CachedWorld.java @@ -17,16 +17,18 @@ package baritone.chunk; +import baritone.Baritone; import baritone.utils.pathing.IBlockTypeAccess; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import net.minecraft.block.state.IBlockState; +import net.minecraft.util.math.BlockPos; import net.minecraft.world.chunk.Chunk; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.BitSet; +import java.util.LinkedList; import java.util.concurrent.LinkedBlockingQueue; import java.util.function.Consumer; @@ -64,6 +66,21 @@ public final class CachedWorld implements IBlockTypeAccess { // Insert an invalid region element cachedRegions.put(0, null); new PackerThread().start(); + new Thread() { + public void run() { + try { + while (true) { + // since a region only saves if it's been modified since its last save + // saving every 10 minutes means that once it's time to exit + // we'll only have a couple regions to save + save(); + Thread.sleep(600000); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }.start(); } public final void queueForPacking(Chunk chunk) { @@ -84,12 +101,45 @@ public final class CachedWorld implements IBlockTypeAccess { return region.getBlock(x & 511, y, z & 511); } + public final LinkedList getLocationsOf(String block, int minimum, int maxRegionDistanceSq) { + LinkedList res = new LinkedList<>(); + int playerRegionX = playerFeet().getX() >> 9; + int playerRegionZ = playerFeet().getZ() >> 9; + + int searchRadius = 0; + while (searchRadius <= maxRegionDistanceSq) { + for (int xoff = -searchRadius; xoff <= searchRadius; xoff++) { + for (int zoff = -searchRadius; zoff <= searchRadius; zoff++) { + int distance = xoff * xoff + zoff * zoff; + if (distance != searchRadius) { + continue; + } + int regionX = xoff + playerRegionX; + int regionZ = zoff + playerRegionZ; + CachedRegion region = getOrCreateRegion(regionX, regionZ); + if (region != null) + for (BlockPos pos : region.getLocationsOf(block)) + res.add(pos); + } + } + if (res.size() >= minimum) { + return res; + } + searchRadius++; + } + return res; + } + private void updateCachedChunk(CachedChunk chunk) { CachedRegion region = getOrCreateRegion(chunk.x >> 5, chunk.z >> 5); region.updateCachedChunk(chunk.x & 31, chunk.z & 31, chunk); } public final void save() { + if (!Baritone.settings().chunkCaching.get()) { + System.out.println("Not saving to disk; chunk caching is disabled."); + return; + } long start = System.currentTimeMillis(); this.cachedRegions.values().forEach(region -> { if (region != null) @@ -99,6 +149,16 @@ public final class CachedWorld implements IBlockTypeAccess { System.out.println("World save took " + (now - start) + "ms"); } + public final void reloadAllFromDisk() { + long start = System.currentTimeMillis(); + this.cachedRegions.values().forEach(region -> { + if (region != null) + region.load(this.directory); + }); + long now = System.currentTimeMillis(); + System.out.println("World load took " + (now - start) + "ms"); + } + /** * Returns the region at the specified region coordinates * @@ -118,7 +178,7 @@ public final class CachedWorld implements IBlockTypeAccess { * @param regionZ The region Z coordinate * @return The region located at the specified coordinates */ - private CachedRegion getOrCreateRegion(int regionX, int regionZ) { + private synchronized CachedRegion getOrCreateRegion(int regionX, int regionZ) { return cachedRegions.computeIfAbsent(getRegionID(regionX, regionZ), id -> { CachedRegion newRegion = new CachedRegion(regionX, regionZ); newRegion.load(this.directory); @@ -168,9 +228,7 @@ public final class CachedWorld implements IBlockTypeAccess { } try { Chunk chunk = queue.take(); - BitSet packedChunk = ChunkPacker.createPackedChunk(chunk); - String[] packedOverview = ChunkPacker.createPackedOverview(chunk); - CachedChunk cached = new CachedChunk(chunk.x, chunk.z, packedChunk, packedOverview); + CachedChunk cached = ChunkPacker.pack(chunk); CachedWorld.this.updateCachedChunk(cached); //System.out.println("Processed chunk at " + chunk.x + "," + chunk.z); } catch (InterruptedException e) { diff --git a/src/main/java/baritone/chunk/ChunkPacker.java b/src/main/java/baritone/chunk/ChunkPacker.java index c0688786..e205d7f8 100644 --- a/src/main/java/baritone/chunk/ChunkPacker.java +++ b/src/main/java/baritone/chunk/ChunkPacker.java @@ -28,9 +28,10 @@ import net.minecraft.block.BlockTallGrass; import net.minecraft.block.state.IBlockState; import net.minecraft.init.Blocks; import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; import net.minecraft.world.chunk.Chunk; -import java.util.BitSet; +import java.util.*; /** * @author Brady @@ -40,30 +41,33 @@ public final class ChunkPacker implements Helper { private ChunkPacker() {} - public static BitSet createPackedChunk(Chunk chunk) { + public static CachedChunk pack(Chunk chunk) { long start = System.currentTimeMillis(); + + Map> specialBlocks = new HashMap<>(); BitSet bitSet = new BitSet(CachedChunk.SIZE); try { for (int y = 0; y < 256; y++) { for (int z = 0; z < 16; z++) { for (int x = 0; x < 16; x++) { int index = CachedChunk.getPositionIndex(x, y, z); - boolean[] bits = getPathingBlockType(chunk.getBlockState(x, y, z).getBlock()).getBits(); + Block block = chunk.getBlockState(x, y, z).getBlock(); + boolean[] bits = getPathingBlockType(block).getBits(); bitSet.set(index, bits[0]); bitSet.set(index + 1, bits[1]); + if (CachedChunk.BLOCKS_TO_KEEP_TRACK_OF.contains(block)) { + String name = blockToString(block); + specialBlocks.computeIfAbsent(name, b -> new ArrayList<>()).add(new BlockPos(x, y, z)); + } } } } } catch (Exception e) { e.printStackTrace(); } + //System.out.println("Packed special blocks: " + specialBlocks); long end = System.currentTimeMillis(); //System.out.println("Chunk packing took " + (end - start) + "ms for " + chunk.x + "," + chunk.z); - return bitSet; - } - - public static String[] createPackedOverview(Chunk chunk) { - long start = System.currentTimeMillis(); String[] blockNames = new String[256]; for (int z = 0; z < 16; z++) { for (int x = 0; x < 16; x++) { @@ -75,17 +79,29 @@ public final class ChunkPacker implements Helper { break; } } - ResourceLocation loc = Block.REGISTRY.getNameForObject(blockState.getBlock()); - String name = loc.getPath(); // normally, only write the part after the minecraft: - if (!loc.getNamespace().equals("minecraft")) { - // Baritone is running on top of forge with mods installed, perhaps? - name = loc.toString(); // include the namespace with the colon - } + String name = blockToString(blockState.getBlock()); blockNames[z << 4 | x] = name; } } - long end = System.currentTimeMillis(); - return blockNames; + CachedChunk cached = new CachedChunk(chunk.x, chunk.z, bitSet, blockNames, specialBlocks); + return cached; + } + + public static String blockToString(Block block) { + ResourceLocation loc = Block.REGISTRY.getNameForObject(block); + String name = loc.getPath(); // normally, only write the part after the minecraft: + if (!loc.getNamespace().equals("minecraft")) { + // Baritone is running on top of forge with mods installed, perhaps? + name = loc.toString(); // include the namespace with the colon + } + return name; + } + + public static Block stringToBlock(String name) { + if (!name.contains(":")) { + name = "minecraft:" + name; + } + return Block.getBlockFromName(name); } private static PathingBlockType getPathingBlockType(Block block) { @@ -96,7 +112,6 @@ public final class ChunkPacker implements Helper { if (MovementHelper.avoidWalkingInto(block)) { return PathingBlockType.AVOID; } - // We used to do an AABB check here // however, this failed in the nether when you were near a nether fortress // because fences check their adjacent blocks in the world for their fence connection status to determine AABB shape @@ -118,7 +133,18 @@ public final class ChunkPacker implements Helper { case AVOID: return Blocks.LAVA.getDefaultState(); case SOLID: - return Blocks.OBSIDIAN.getDefaultState(); + // Dimension solid types + switch (mc.player.dimension) { + case -1: + return Blocks.NETHERRACK.getDefaultState(); + case 0: + return Blocks.STONE.getDefaultState(); + case 1: + return Blocks.END_STONE.getDefaultState(); + } + + // The fallback solid type + return Blocks.STONE.getDefaultState(); } } return null; diff --git a/src/main/java/baritone/chunk/Waypoint.java b/src/main/java/baritone/chunk/Waypoint.java new file mode 100644 index 00000000..091c958c --- /dev/null +++ b/src/main/java/baritone/chunk/Waypoint.java @@ -0,0 +1,90 @@ +/* + * This file is part of Baritone. + * + * Baritone 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. + * + * Baritone 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 Baritone. If not, see . + */ + +package baritone.chunk; + +import net.minecraft.util.math.BlockPos; + +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * A single waypoint + * + * @author leijurv + */ +public class Waypoint { + public final String name; + public final Tag tag; + private final long creationTimestamp; + public final BlockPos location; + + public Waypoint(String name, Tag tag, BlockPos location) { + this(name, tag, location, System.currentTimeMillis()); + } + + Waypoint(String name, Tag tag, BlockPos location, long creationTimestamp) { // read from disk + this.name = name; + this.tag = tag; + this.location = location; + this.creationTimestamp = creationTimestamp; + } + + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + if (!(o instanceof Waypoint)) { + return false; + } + Waypoint w = (Waypoint) o; + return name.equals(w.name) && tag == w.tag && location.equals(w.location); + } + + @Override + public int hashCode() { + return name.hashCode() + tag.hashCode() + location.hashCode(); //lol + } + + public long creationTimestamp() { + return creationTimestamp; + } + + public String toString() { + return name + " " + location.toString() + " " + new Date(creationTimestamp).toString(); + } + + public enum Tag { + HOME, DEATH, BED, USER; + + } + + public static final Map TAG_MAP; + + static { + HashMap map = new HashMap<>(); + map.put("home", Tag.HOME); + map.put("base", Tag.HOME); + map.put("bed", Tag.BED); + map.put("spawn", Tag.BED); + map.put("death", Tag.DEATH); + TAG_MAP = Collections.unmodifiableMap(map); + } +} diff --git a/src/main/java/baritone/chunk/Waypoints.java b/src/main/java/baritone/chunk/Waypoints.java index 4617ad63..d75ff733 100644 --- a/src/main/java/baritone/chunk/Waypoints.java +++ b/src/main/java/baritone/chunk/Waypoints.java @@ -17,12 +17,27 @@ package baritone.chunk; -import java.io.IOException; +import net.minecraft.util.math.BlockPos; + +import java.io.*; import java.nio.file.Files; import java.nio.file.Path; +import java.util.*; +/** + * Waypoints for a world + * + * @author leijurv + */ public class Waypoints { + + /** + * Magic value to detect invalid waypoint files + */ + private static final long WAYPOINT_MAGIC_VALUE = 121977993584L; // good value + private final Path directory; + private final Map> waypoints; Waypoints(Path directory) { this.directory = directory; @@ -32,5 +47,78 @@ public class Waypoints { } catch (IOException ignored) {} } System.out.println("Would save waypoints to " + directory); + this.waypoints = new HashMap<>(); + load(); + } + + private void load() { + for (Waypoint.Tag tag : Waypoint.Tag.values()) { + load(tag); + } + } + + private synchronized void load(Waypoint.Tag tag) { + waypoints.put(tag, new HashSet<>()); + + Path fileName = directory.resolve(tag.name().toLowerCase() + ".mp4"); + if (!Files.exists(fileName)) + return; + + try ( + FileInputStream fileIn = new FileInputStream(fileName.toFile()); + BufferedInputStream bufIn = new BufferedInputStream(fileIn); + DataInputStream in = new DataInputStream(bufIn) + ) { + long magic = in.readLong(); + if (magic != WAYPOINT_MAGIC_VALUE) { + throw new IOException("Bad magic value " + magic); + } + + long length = in.readLong(); // Yes I want 9,223,372,036,854,775,807 waypoints, do you not? + while (length-- > 0) { + String name = in.readUTF(); + long creationTimestamp = in.readLong(); + int x = in.readInt(); + int y = in.readInt(); + int z = in.readInt(); + waypoints.get(tag).add(new Waypoint(name, tag, new BlockPos(x, y, z), creationTimestamp)); + } + } catch (IOException ignored) {} + } + + private synchronized void save(Waypoint.Tag tag) { + Path fileName = directory.resolve(tag.name().toLowerCase() + ".mp4"); + try ( + FileOutputStream fileOut = new FileOutputStream(fileName.toFile()); + BufferedOutputStream bufOut = new BufferedOutputStream(fileOut); + DataOutputStream out = new DataOutputStream(bufOut) + ) { + out.writeLong(WAYPOINT_MAGIC_VALUE); + out.writeLong(waypoints.get(tag).size()); + for (Waypoint waypoint : waypoints.get(tag)) { + out.writeUTF(waypoint.name); + out.writeLong(waypoint.creationTimestamp()); + out.writeInt(waypoint.location.getX()); + out.writeInt(waypoint.location.getY()); + out.writeInt(waypoint.location.getZ()); + } + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + public Set getByTag(Waypoint.Tag tag) { + return Collections.unmodifiableSet(waypoints.get(tag)); + } + + public Waypoint getMostRecentByTag(Waypoint.Tag tag) { + // Find a waypoint of the given tag which has the greatest timestamp value, indicating the most recent + return this.waypoints.get(tag).stream().min(Comparator.comparingLong(w -> -w.creationTimestamp())).orElse(null); + } + + public void addWaypoint(Waypoint waypoint) { + // no need to check for duplicate, because it's a Set not a List + waypoints.get(waypoint.tag).add(waypoint); + save(waypoint.tag); } } diff --git a/src/main/java/baritone/chunk/WorldData.java b/src/main/java/baritone/chunk/WorldData.java index 10043e6d..43bbe9cb 100644 --- a/src/main/java/baritone/chunk/WorldData.java +++ b/src/main/java/baritone/chunk/WorldData.java @@ -19,6 +19,11 @@ package baritone.chunk; import java.nio.file.Path; +/** + * Data about a world, from baritone's point of view. Includes cached chunks, waypoints, and map data. + * + * @author leijurv + */ public class WorldData { public final CachedWorld cache; public final Waypoints waypoints; diff --git a/src/main/java/baritone/chunk/WorldProvider.java b/src/main/java/baritone/chunk/WorldProvider.java index af18e922..709ddff8 100644 --- a/src/main/java/baritone/chunk/WorldProvider.java +++ b/src/main/java/baritone/chunk/WorldProvider.java @@ -26,6 +26,7 @@ import net.minecraft.server.integrated.IntegratedServer; import net.minecraft.world.WorldServer; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -52,6 +53,7 @@ public enum WorldProvider implements Helper { public final void initWorld(WorldClient world) { int dimensionID = world.provider.getDimensionType().getId(); File directory; + File readme; IntegratedServer integratedServer = mc.getIntegratedServer(); if (integratedServer != null) { WorldServer localServerWorld = integratedServer.getWorld(dimensionID); @@ -59,17 +61,26 @@ public enum WorldProvider implements Helper { IAnvilChunkLoader loader = (IAnvilChunkLoader) provider.getChunkLoader(); directory = loader.getChunkSaveLocation(); - if (!directory.getParentFile().getName().equals("saves")) { + // Gets the "depth" of this directory relative the the game's run directory, 2 is the location of the world + if (directory.toPath().relativize(mc.gameDir.toPath()).getNameCount() != 2) { // subdirectory of the main save directory for this world directory = directory.getParentFile(); } directory = new File(directory, "baritone"); - + readme = directory; + } else { //remote directory = new File(Baritone.INSTANCE.getDir(), mc.getCurrentServerData().serverIP); + readme = Baritone.INSTANCE.getDir(); } + // lol wtf is this baritone folder in my minecraft save? + try (FileOutputStream out = new FileOutputStream(new File(readme, "readme.txt"))) { + // good thing we have a readme + out.write("https://github.com/cabaletta/baritone\n".getBytes()); + } catch (IOException ignored) {} + directory = new File(directory, "DIM" + dimensionID); Path dir = directory.toPath(); if (!Files.exists(dir)) { diff --git a/src/main/java/baritone/event/GameEventHandler.java b/src/main/java/baritone/event/GameEventHandler.java index c6ab20fd..02c7f393 100644 --- a/src/main/java/baritone/event/GameEventHandler.java +++ b/src/main/java/baritone/event/GameEventHandler.java @@ -42,10 +42,6 @@ import baritone.event.listener.IGameEventListener; import baritone.utils.Helper; import baritone.utils.InputOverrideHandler; import baritone.utils.interfaces.Toggleable; -import net.minecraft.client.renderer.BufferBuilder; -import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.client.renderer.Tessellator; -import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.client.settings.KeyBinding; import net.minecraft.world.chunk.Chunk; import org.lwjgl.input.Keyboard; @@ -111,15 +107,14 @@ public final class GameEventHandler implements IGameEventListener, Helper { && type == ChunkEvent.Type.UNLOAD && mc.world.getChunkProvider().isChunkGeneratedAt(event.getX(), event.getZ()); - if (Baritone.settings().chunkCaching.get()) { - if (isPostPopulate || isPreUnload) { - WorldProvider.INSTANCE.ifWorldLoaded(world -> { - Chunk chunk = mc.world.getChunk(event.getX(), event.getZ()); - world.cache.queueForPacking(chunk); - }); - } + if (isPostPopulate || isPreUnload) { + WorldProvider.INSTANCE.ifWorldLoaded(world -> { + Chunk chunk = mc.world.getChunk(event.getX(), event.getZ()); + world.cache.queueForPacking(chunk); + }); } + dispatch(listener -> listener.onChunkEvent(event)); } @@ -136,19 +131,17 @@ public final class GameEventHandler implements IGameEventListener, Helper { @Override public final void onWorldEvent(WorldEvent event) { - if (Baritone.settings().chunkCaching.get()) { - WorldProvider cache = WorldProvider.INSTANCE; + WorldProvider cache = WorldProvider.INSTANCE; - switch (event.getState()) { - case PRE: - cache.closeWorld(); - break; - case POST: - cache.closeWorld(); - if (event.getWorld() != null) - cache.initWorld(event.getWorld()); - break; - } + switch (event.getState()) { + case PRE: + cache.closeWorld(); + break; + case POST: + cache.closeWorld(); + if (event.getWorld() != null) + cache.initWorld(event.getWorld()); + break; } dispatch(listener -> listener.onWorldEvent(event)); @@ -174,6 +167,21 @@ public final class GameEventHandler implements IGameEventListener, Helper { dispatch(listener -> listener.onPlayerRelativeMove(event)); } + @Override + public void onBlockInteract(BlockInteractEvent event) { + dispatch(listener -> listener.onBlockInteract(event)); + } + + @Override + public void onPlayerDeath() { + dispatch(IGameEventListener::onPlayerDeath); + } + + @Override + public void onPathEvent(PathEvent event) { + dispatch(listener -> listener.onPathEvent(event)); + } + public final void registerEventListener(IGameEventListener listener) { this.listeners.add(listener); } @@ -185,27 +193,4 @@ public final class GameEventHandler implements IGameEventListener, Helper { private boolean canDispatch(IGameEventListener listener) { return !(listener instanceof Toggleable) || ((Toggleable) listener).isEnabled(); } - - private void drawChunkLine(int posX, int posZ, float partialTicks) { - GlStateManager.enableBlend(); - GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0); - GlStateManager.color(1.0F, 1.0F, 0.0F, 0.4F); - GlStateManager.glLineWidth(2.0F); - GlStateManager.disableTexture2D(); - - Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder buffer = tessellator.getBuffer(); - double d0 = mc.getRenderManager().viewerPosX; - double d1 = mc.getRenderManager().viewerPosY; - double d2 = mc.getRenderManager().viewerPosZ; - buffer.begin(3, DefaultVertexFormats.POSITION); - buffer.pos(posX - d0, 0 - d1, posZ - d2).endVertex(); - buffer.pos(posX - d0, 256 - d1, posZ - d2).endVertex(); - tessellator.draw(); - - GlStateManager.enableDepth(); - GlStateManager.depthMask(true); - GlStateManager.enableTexture2D(); - GlStateManager.disableBlend(); - } } diff --git a/src/main/java/baritone/event/events/BlockInteractEvent.java b/src/main/java/baritone/event/events/BlockInteractEvent.java new file mode 100644 index 00000000..c458f6b6 --- /dev/null +++ b/src/main/java/baritone/event/events/BlockInteractEvent.java @@ -0,0 +1,69 @@ +/* + * This file is part of Baritone. + * + * Baritone 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. + * + * Baritone 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 Baritone. If not, see . + */ + +package baritone.event.events; + +import net.minecraft.util.math.BlockPos; + +/** + * @author Brady + * @since 8/22/2018 + */ +public final class BlockInteractEvent { + + /** + * The position of the block interacted with + */ + private final BlockPos pos; + + /** + * The type of interaction that occurred + */ + private final Type type; + + public BlockInteractEvent(BlockPos pos, Type type) { + this.pos = pos; + this.type = type; + } + + /** + * @return The position of the block interacted with + */ + public final BlockPos getPos() { + return this.pos; + } + + /** + * @return The type of interaction with the target block + */ + public final Type getType() { + return this.type; + } + + public enum Type { + + /** + * We're breaking the target block. + */ + BREAK, + + /** + * We're right clicking on the target block. Either placing or interacting with. + */ + USE + } +} diff --git a/src/main/java/baritone/event/events/PathEvent.java b/src/main/java/baritone/event/events/PathEvent.java new file mode 100644 index 00000000..53892058 --- /dev/null +++ b/src/main/java/baritone/event/events/PathEvent.java @@ -0,0 +1,32 @@ +/* + * This file is part of Baritone. + * + * Baritone 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. + * + * Baritone 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 Baritone. If not, see . + */ + +package baritone.event.events; + +public enum PathEvent { + CALC_STARTED, + CALC_FINISHED_NOW_EXECUTING, + CALC_FAILED, + NEXT_SEGMENT_CALC_STARTED, + NEXT_SEGMENT_CALC_FINISHED, + CONTINUING_ONTO_PLANNED_NEXT, + SPLICING_ONTO_NEXT_EARLY, + AT_GOAL, + PATH_FINISHED_NEXT_STILL_CALCULATING, + NEXT_CALC_FAILED, + DISCARD_NEXT +} diff --git a/src/main/java/baritone/event/listener/AbstractGameEventListener.java b/src/main/java/baritone/event/listener/AbstractGameEventListener.java index 46cbeb65..4b8de23a 100644 --- a/src/main/java/baritone/event/listener/AbstractGameEventListener.java +++ b/src/main/java/baritone/event/listener/AbstractGameEventListener.java @@ -41,9 +41,8 @@ import baritone.event.events.*; * overridden with empty bodies, allowing inheritors of this class to choose * which events they would like to listen in on. * - * @see IGameEventListener - * * @author Brady + * @see IGameEventListener * @since 8/1/2018 6:29 PM */ public interface AbstractGameEventListener extends IGameEventListener { @@ -80,4 +79,13 @@ public interface AbstractGameEventListener extends IGameEventListener { @Override default void onPlayerRelativeMove(RelativeMoveEvent event) {} + + @Override + default void onBlockInteract(BlockInteractEvent event) {} + + @Override + default void onPlayerDeath() {} + + @Override + default void onPathEvent(PathEvent event) {} } diff --git a/src/main/java/baritone/event/listener/IGameEventListener.java b/src/main/java/baritone/event/listener/IGameEventListener.java index 7d239133..4900f1eb 100644 --- a/src/main/java/baritone/event/listener/IGameEventListener.java +++ b/src/main/java/baritone/event/listener/IGameEventListener.java @@ -39,6 +39,7 @@ import io.netty.util.concurrent.GenericFutureListener; import net.minecraft.block.state.IBlockState; import net.minecraft.client.Minecraft; import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.client.gui.GuiGameOver; import net.minecraft.client.multiplayer.WorldClient; import net.minecraft.client.renderer.EntityRenderer; import net.minecraft.client.settings.GameSettings; @@ -46,6 +47,7 @@ import net.minecraft.entity.Entity; import net.minecraft.entity.player.InventoryPlayer; import net.minecraft.network.NetworkManager; import net.minecraft.network.Packet; +import net.minecraft.util.text.ITextComponent; /** * @author Brady @@ -132,4 +134,26 @@ public interface IGameEventListener { * @see Entity#moveRelative(float, float, float, float) */ void onPlayerRelativeMove(RelativeMoveEvent event); + + /** + * Called when the local player interacts with a block, whether it is breaking or opening/placing. + * + * @see Minecraft#clickMouse() + * @see Minecraft#rightClickMouse() + */ + void onBlockInteract(BlockInteractEvent event); + + /** + * Called when the local player dies, as indicated by the creation of the {@link GuiGameOver} screen. + * + * @see GuiGameOver(ITextComponent) + */ + void onPlayerDeath(); + + /** + * When the pathfinder's state changes + * + * @param event + */ + void onPathEvent(PathEvent event); } diff --git a/src/main/java/baritone/launch/mixins/MixinMain.java b/src/main/java/baritone/launch/mixins/MixinBlockPos.java old mode 100755 new mode 100644 similarity index 59% rename from src/main/java/baritone/launch/mixins/MixinMain.java rename to src/main/java/baritone/launch/mixins/MixinBlockPos.java index 1e4323e9..1cf52132 --- a/src/main/java/baritone/launch/mixins/MixinMain.java +++ b/src/main/java/baritone/launch/mixins/MixinBlockPos.java @@ -1,40 +1,43 @@ -/* - * This file is part of Baritone. - * - * Baritone 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. - * - * Baritone 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 Baritone. If not, see . - */ - -package baritone.launch.mixins; - -import net.minecraft.client.main.Main; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -/** - * @author Brady - * @since 7/31/2018 10:18 PM - */ -@Mixin(Main.class) -public class MixinMain { - - @Inject( - method = "main", - at = @At("HEAD") - ) - private static void main(String[] args, CallbackInfo ci) { - System.setProperty("java.net.preferIPv4Stack", "true"); - } -} +/* + * This file is part of Baritone. + * + * Baritone 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. + * + * Baritone 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 Baritone. If not, see . + */ + +package baritone.launch.mixins; + +import com.google.common.base.MoreObjects; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3i; +import org.spongepowered.asm.mixin.Mixin; + +import javax.annotation.Nonnull; + +/** + * @author Brady + * @since 8/25/2018 + */ +@Mixin(BlockPos.class) +public abstract class MixinBlockPos extends Vec3i { + + public MixinBlockPos(int xIn, int yIn, int zIn) { + super(xIn, yIn, zIn); + } + + @Override + @Nonnull + public String toString() { + return MoreObjects.toStringHelper("BlockPos").add("x", this.getX()).add("y", this.getY()).add("z", this.getZ()).toString(); + } +} diff --git a/src/main/java/baritone/launch/mixins/MixinGameSettings.java b/src/main/java/baritone/launch/mixins/MixinGameSettings.java index 2179affb..b68cce51 100755 --- a/src/main/java/baritone/launch/mixins/MixinGameSettings.java +++ b/src/main/java/baritone/launch/mixins/MixinGameSettings.java @@ -34,7 +34,8 @@ public class MixinGameSettings { method = "isKeyDown", at = @At( value = "INVOKE", - target = "org/lwjgl/input/Keyboard.isKeyDown(I)Z" + target = "org/lwjgl/input/Keyboard.isKeyDown(I)Z", + remap = false ) ) private static boolean isKeyDown(int keyCode) { diff --git a/src/main/java/baritone/launch/mixins/MixinGuiContainer.java b/src/main/java/baritone/launch/mixins/MixinGuiContainer.java index 6d37ab97..dd9bf715 100755 --- a/src/main/java/baritone/launch/mixins/MixinGuiContainer.java +++ b/src/main/java/baritone/launch/mixins/MixinGuiContainer.java @@ -37,7 +37,8 @@ public class MixinGuiContainer { }, at = @At( value = "INVOKE", - target = "org/lwjgl/input/Keyboard.isKeyDown(I)Z" + target = "org/lwjgl/input/Keyboard.isKeyDown(I)Z", + remap = false ) ) private boolean isKeyDown(int keyCode) { diff --git a/src/main/java/baritone/launch/mixins/MixinGuiScreen.java b/src/main/java/baritone/launch/mixins/MixinGuiScreen.java index 81bf1bf5..47877058 100755 --- a/src/main/java/baritone/launch/mixins/MixinGuiScreen.java +++ b/src/main/java/baritone/launch/mixins/MixinGuiScreen.java @@ -38,7 +38,8 @@ public class MixinGuiScreen { }, at = @At( value = "INVOKE", - target = "org/lwjgl/input/Keyboard.isKeyDown(I)Z" + target = "org/lwjgl/input/Keyboard.isKeyDown(I)Z", + remap = false ) ) private static boolean isKeyDown(int keyCode) { diff --git a/src/main/java/baritone/launch/mixins/MixinMinecraft.java b/src/main/java/baritone/launch/mixins/MixinMinecraft.java index 1c14d179..3d0b6619 100755 --- a/src/main/java/baritone/launch/mixins/MixinMinecraft.java +++ b/src/main/java/baritone/launch/mixins/MixinMinecraft.java @@ -19,13 +19,19 @@ package baritone.launch.mixins; import baritone.Baritone; import baritone.behavior.impl.PathingBehavior; +import baritone.event.events.BlockInteractEvent; import baritone.event.events.TickEvent; import baritone.event.events.WorldEvent; import baritone.event.events.type.EventState; import baritone.utils.ExampleBaritoneControl; import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.multiplayer.WorldClient; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; import org.spongepowered.asm.lib.Opcodes; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -33,6 +39,7 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; /** * @author Brady @@ -42,6 +49,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; public class MixinMinecraft { @Shadow private int leftClickCounter; + @Shadow public EntityPlayerSP player; @Shadow public WorldClient world; @Inject( @@ -78,7 +86,8 @@ public class MixinMinecraft { method = "runTickKeyboard", at = @At( value = "INVOKE", - target = "org/lwjgl/input/Keyboard.isKeyDown(I)Z" + target = "org/lwjgl/input/Keyboard.isKeyDown(I)Z", + remap = false ) ) private boolean Keyboard$isKeyDown(int keyCode) { @@ -93,23 +102,6 @@ public class MixinMinecraft { Baritone.INSTANCE.getGameEventHandler().onProcessKeyBinds(); } - @Redirect( - method = { - "setIngameFocus", - "runTick" - }, - at = @At( - value = "FIELD", - opcode = Opcodes.PUTFIELD, - target = "net/minecraft/client/Minecraft.leftClickCounter:I", - ordinal = 0 - ) - ) - private void setLeftClickCounter(Minecraft mc, int value) { - if (!Baritone.INSTANCE.isInitialized() || !Baritone.INSTANCE.getInputOverrideHandler().isInputForcedDown(mc.gameSettings.keyBindAttack)) - this.leftClickCounter = value; - } - @Inject( method = "loadWorld(Lnet/minecraft/client/multiplayer/WorldClient;Ljava/lang/String;)V", at = @At("HEAD") @@ -153,6 +145,30 @@ public class MixinMinecraft { ) ) private boolean isAllowUserInput(GuiScreen screen) { - return PathingBehavior.INSTANCE.getCurrent() != null || screen.allowUserInput; + return (PathingBehavior.INSTANCE.getCurrent() != null && player != null) || screen.allowUserInput; + } + + @Inject( + method = "clickMouse", + at = @At( + value = "INVOKE", + target = "net/minecraft/client/multiplayer/PlayerControllerMP.clickBlock(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/EnumFacing;)Z" + ), + locals = LocalCapture.CAPTURE_FAILHARD + ) + private void onBlockBreak(CallbackInfo ci, BlockPos pos) { + Baritone.INSTANCE.getGameEventHandler().onBlockInteract(new BlockInteractEvent(pos, BlockInteractEvent.Type.BREAK)); + } + + @Inject( + method = "rightClickMouse", + at = @At( + value = "INVOKE", + target = "net/minecraft/client/entity/EntityPlayerSP.swingArm(Lnet/minecraft/util/EnumHand;)V" + ), + locals = LocalCapture.CAPTURE_FAILHARD + ) + private void onBlockUse(CallbackInfo ci, EnumHand var1[], int var2, int var3, EnumHand enumhand, ItemStack itemstack, BlockPos blockpos, int i, EnumActionResult enumactionresult) { + Baritone.INSTANCE.getGameEventHandler().onBlockInteract(new BlockInteractEvent(blockpos, BlockInteractEvent.Type.USE)); } } diff --git a/src/main/java/baritone/launch/mixins/MixinNetHandlerPlayClient.java b/src/main/java/baritone/launch/mixins/MixinNetHandlerPlayClient.java index 17d10240..1af4ca6c 100644 --- a/src/main/java/baritone/launch/mixins/MixinNetHandlerPlayClient.java +++ b/src/main/java/baritone/launch/mixins/MixinNetHandlerPlayClient.java @@ -22,6 +22,7 @@ import baritone.event.events.ChunkEvent; import baritone.event.events.type.EventState; import net.minecraft.client.network.NetHandlerPlayClient; import net.minecraft.network.play.server.SPacketChunkData; +import net.minecraft.network.play.server.SPacketCombatEvent; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -66,4 +67,15 @@ public class MixinNetHandlerPlayClient { ) ); } + + @Inject( + method = "handleCombatEvent", + at = @At( + value = "INVOKE", + target = "net/minecraft/client/Minecraft.displayGuiScreen(Lnet/minecraft/client/gui/GuiScreen;)V" + ) + ) + private void onPlayerDeath(SPacketCombatEvent packetIn, CallbackInfo ci) { + Baritone.INSTANCE.getGameEventHandler().onPlayerDeath(); + } } diff --git a/src/main/java/baritone/launch/mixins/MixinNetworkManager.java b/src/main/java/baritone/launch/mixins/MixinNetworkManager.java index 63e302d7..48da859f 100644 --- a/src/main/java/baritone/launch/mixins/MixinNetworkManager.java +++ b/src/main/java/baritone/launch/mixins/MixinNetworkManager.java @@ -64,8 +64,7 @@ public abstract class MixinNetworkManager { at = @At( value = "INVOKE", target = "net/minecraft/network/Packet.processPacket(Lnet/minecraft/network/INetHandler;)V" - ), - remap = false + ) ) private void preProcessPacket(ChannelHandlerContext context, Packet packet, CallbackInfo ci) { Baritone.INSTANCE.getGameEventHandler().onReceivePacket(new PacketEvent(EventState.PRE, packet)); @@ -73,8 +72,7 @@ public abstract class MixinNetworkManager { @Inject( method = "channelRead0", - at = @At("RETURN"), - remap = false + at = @At("RETURN") ) private void postProcessPacket(ChannelHandlerContext context, Packet packet, CallbackInfo ci) { if (this.channel.isOpen()) diff --git a/src/main/java/baritone/pathing/calc/AStarPathFinder.java b/src/main/java/baritone/pathing/calc/AStarPathFinder.java index 6d8b3ac7..2c1df800 100644 --- a/src/main/java/baritone/pathing/calc/AStarPathFinder.java +++ b/src/main/java/baritone/pathing/calc/AStarPathFinder.java @@ -77,8 +77,7 @@ public class AStarPathFinder extends AbstractNodeCostSearch implements Helper { int numNodes = 0; int numEmptyChunk = 0; boolean favoring = favoredPositions.isPresent(); - boolean cache = Baritone.settings().chunkCaching.get(); // grab all settings beforehand so that changing settings during pathing doesn't cause a crash or unpredictable behavior - int pathingMaxChunkBorderFetch = Baritone.settings().pathingMaxChunkBorderFetch.get(); + int pathingMaxChunkBorderFetch = Baritone.settings().pathingMaxChunkBorderFetch.get(); // grab all settings beforehand so that changing settings during pathing doesn't cause a crash or unpredictable behavior double favorCoeff = Baritone.settings().backtrackCostFavoringCoefficient.get(); boolean minimumImprovementRepropagation = Baritone.settings().minimumImprovementRepropagation.get(); while (!openSet.isEmpty() && numEmptyChunk < pathingMaxChunkBorderFetch && System.currentTimeMillis() < timeoutTime) { @@ -112,11 +111,9 @@ public class AStarPathFinder extends AbstractNodeCostSearch implements Helper { } BetterBlockPos dest = (BetterBlockPos) movementToGetToNeighbor.getDest(); boolean isPositionCached = false; - if (cache) { - if (WorldProvider.INSTANCE.getCurrentWorld() != null) { - if (WorldProvider.INSTANCE.getCurrentWorld().cache.getBlock(dest) != null) { - isPositionCached = true; - } + if (WorldProvider.INSTANCE.getCurrentWorld() != null) { + if (WorldProvider.INSTANCE.getCurrentWorld().cache.getBlock(dest) != null) { + isPositionCached = true; } } if (!isPositionCached && Minecraft.getMinecraft().world.getChunk(dest) instanceof EmptyChunk) { diff --git a/src/main/java/baritone/pathing/goals/GoalXZ.java b/src/main/java/baritone/pathing/goals/GoalXZ.java index d8153b00..de48da83 100644 --- a/src/main/java/baritone/pathing/goals/GoalXZ.java +++ b/src/main/java/baritone/pathing/goals/GoalXZ.java @@ -64,6 +64,9 @@ public class GoalXZ implements Goal { } public static double calculate(double xDiff, double zDiff) { + if (Baritone.settings().pythagoreanMetric.get()) { + return Math.sqrt(xDiff * xDiff + zDiff * zDiff) * Baritone.settings().costHeuristic.get(); + } //This is a combination of pythagorean and manhattan distance //It takes into account the fact that pathing can either walk diagonally or forwards diff --git a/src/main/java/baritone/pathing/goals/GoalYLevel.java b/src/main/java/baritone/pathing/goals/GoalYLevel.java index 7a437f9d..89cef250 100644 --- a/src/main/java/baritone/pathing/goals/GoalYLevel.java +++ b/src/main/java/baritone/pathing/goals/GoalYLevel.java @@ -48,7 +48,7 @@ public class GoalYLevel implements Goal { } if (pos.getY() < level) { // need to ascend - return (level - pos.getY()) * JUMP_ONE_BLOCK_COST * 0.9; + return (level - pos.getY()) * JUMP_ONE_BLOCK_COST; } return 0; } diff --git a/src/main/java/baritone/pathing/movement/Movement.java b/src/main/java/baritone/pathing/movement/Movement.java index e461d701..11a88ad1 100644 --- a/src/main/java/baritone/pathing/movement/Movement.java +++ b/src/main/java/baritone/pathing/movement/Movement.java @@ -29,9 +29,12 @@ import net.minecraft.block.Block; import net.minecraft.block.BlockLadder; import net.minecraft.block.BlockVine; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.RayTraceResult; import net.minecraft.util.math.Vec3d; import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.Optional; import static baritone.utils.InputOverrideHandler.Input; @@ -54,6 +57,8 @@ public abstract class Movement implements Helper, MovementHelper { */ protected final BlockPos[] positionsToPlace; + private boolean didBreakLastTick; + private Double cost; protected Movement(BlockPos src, BlockPos dest, BlockPos[] toBreak, BlockPos[] toPlace) { @@ -100,7 +105,6 @@ public abstract class Movement implements Helper, MovementHelper { * @return Status */ public MovementStatus update() { - player().setSprinting(false); MovementState latestState = updateState(currentState); if (BlockStateInterface.isLiquid(playerFeet())) { latestState.setInput(Input.JUMP, true); @@ -114,10 +118,29 @@ public abstract class Movement implements Helper, MovementHelper { // TODO: calculate movement inputs from latestState.getGoal().position // latestState.getTarget().position.ifPresent(null); NULL CONSUMER REALLY SHOULDN'T BE THE FINAL THING YOU SHOULD REALLY REPLACE THIS WITH ALMOST ACTUALLY ANYTHING ELSE JUST PLEASE DON'T LEAVE IT AS IT IS THANK YOU KANYE + + this.didBreakLastTick = false; + latestState.getInputStates().forEach((input, forced) -> { + RayTraceResult trace = mc.objectMouseOver; + boolean isBlockTrace = trace != null && trace.typeOfHit == RayTraceResult.Type.BLOCK; + boolean isLeftClick = forced && input == Input.CLICK_LEFT; + + // If we're forcing left click, we're in a gui screen, and we're looking + // at a block, break the block without a direct game input manipulation. + if (mc.currentScreen != null && isLeftClick && isBlockTrace) { + BlockBreakHelper.tryBreakBlock(trace.getBlockPos(), trace.sideHit); + this.didBreakLastTick = true; + return; + } + Baritone.INSTANCE.getInputOverrideHandler().setInputForceState(input, forced); }); latestState.getInputStates().replaceAll((input, forced) -> false); + + if (!this.didBreakLastTick) + BlockBreakHelper.stopBreakingBlock(); + currentState = latestState; if (isFinished()) @@ -187,41 +210,58 @@ public abstract class Movement implements Helper, MovementHelper { currentState.setStatus(MovementStatus.CANCELED); } + public void reset() { + currentState = new MovementState().setStatus(MovementStatus.PREPPING); + } public double getTotalHardnessOfBlocksToBreak(CalculationContext ctx) { - /* - double sum = 0; - HashSet toBreak = new HashSet(); - for (BlockPos positionsToBreak1 : positionsToBreak) { - toBreak.add(positionsToBreak1); - if (this instanceof ActionFall) {//if we are digging straight down, assume we have already broken the sand above us - continue; - } - BlockPos tmp = positionsToBreak1.up(); - while (canFall(tmp)) { - toBreak.add(tmp); - tmp = tmp.up(); - } + if (positionsToBreak.length == 0) { + return 0; } - for (BlockPos pos : toBreak) { - sum += getHardness(ts, Baritone.get(pos), pos); - if (sum >= COST_INF) { - return COST_INF; - } + if (positionsToBreak.length == 1) { + return MovementHelper.getMiningDurationTicks(ctx, positionsToBreak[0], true); } - if (!Baritone.allowBreakOrPlace || !Baritone.hasThrowaway) { - for (int i = 0; i < blocksToPlace.length; i++) { - if (!canWalkOn(positionsToPlace[i])) { - return COST_INF; + int firstColumnX = positionsToBreak[0].getX(); + int firstColumnZ = positionsToBreak[0].getZ(); + int firstColumnMaxY = positionsToBreak[0].getY(); + int firstColumnMaximalIndex = 0; + boolean hasSecondColumn = false; + int secondColumnX = -1; + int secondColumnZ = -1; + int secondColumnMaxY = -1; + int secondColumnMaximalIndex = -1; + for (int i = 0; i < positionsToBreak.length; i++) { + BlockPos pos = positionsToBreak[i]; + if (pos.getX() == firstColumnX && pos.getZ() == firstColumnZ) { + if (pos.getY() > firstColumnMaxY) { + firstColumnMaxY = pos.getY(); + firstColumnMaximalIndex = i; + } + } else { + if (!hasSecondColumn || (pos.getX() == secondColumnX && pos.getZ() == secondColumnZ)) { + if (hasSecondColumn) { + if (pos.getY() > secondColumnMaxY) { + secondColumnMaxY = pos.getY(); + secondColumnMaximalIndex = i; + } + } else { + hasSecondColumn = true; + secondColumnX = pos.getX(); + secondColumnZ = pos.getZ(); + secondColumnMaxY = pos.getY(); + secondColumnMaximalIndex = i; + } + } else { + throw new IllegalStateException("I literally have no idea " + Arrays.asList(positionsToBreak)); } } - }*/ - //^ the above implementation properly deals with falling blocks, TODO integrate + } + double sum = 0; - for (BlockPos pos : positionsToBreak) { - sum += MovementHelper.getMiningDurationTicks(ctx, pos); + for (int i = 0; i < positionsToBreak.length; i++) { + sum += MovementHelper.getMiningDurationTicks(ctx, positionsToBreak[i], firstColumnMaximalIndex == i || secondColumnMaximalIndex == i); if (sum >= COST_INF) { - return COST_INF; + break; } } return sum; @@ -243,15 +283,19 @@ public abstract class Movement implements Helper, MovementHelper { return state; } - public ArrayList toBreakCached = null; - public ArrayList toPlaceCached = null; - public ArrayList toWalkIntoCached = null; + public BlockPos getDirection() { + return getDest().subtract(getSrc()); + } - public ArrayList toBreak() { + public List toBreakCached = null; + public List toPlaceCached = null; + public List toWalkIntoCached = null; + + public List toBreak() { if (toBreakCached != null) { return toBreakCached; } - ArrayList result = new ArrayList<>(); + List result = new ArrayList<>(); for (BlockPos positionToBreak : positionsToBreak) { if (!MovementHelper.canWalkThrough(positionToBreak)) { result.add(positionToBreak); @@ -261,11 +305,11 @@ public abstract class Movement implements Helper, MovementHelper { return result; } - public ArrayList toPlace() { + public List toPlace() { if (toPlaceCached != null) { return toPlaceCached; } - ArrayList result = new ArrayList<>(); + List result = new ArrayList<>(); for (BlockPos positionToBreak : positionsToPlace) { if (!MovementHelper.canWalkOn(positionToBreak)) { result.add(positionToBreak); @@ -275,7 +319,7 @@ public abstract class Movement implements Helper, MovementHelper { return result; } - public ArrayList toWalkInto() { // overridden by movementdiagonal + public List toWalkInto() { // overridden by movementdiagonal if (toWalkIntoCached == null) { toWalkIntoCached = new ArrayList<>(); } diff --git a/src/main/java/baritone/pathing/movement/MovementHelper.java b/src/main/java/baritone/pathing/movement/MovementHelper.java index 26a026d0..549ee621 100644 --- a/src/main/java/baritone/pathing/movement/MovementHelper.java +++ b/src/main/java/baritone/pathing/movement/MovementHelper.java @@ -24,15 +24,18 @@ import baritone.pathing.movement.movements.MovementDescend; import baritone.pathing.movement.movements.MovementFall; import baritone.utils.*; import net.minecraft.block.*; +import net.minecraft.block.properties.PropertyBool; import net.minecraft.block.state.IBlockState; import net.minecraft.client.Minecraft; import net.minecraft.client.entity.EntityPlayerSP; import net.minecraft.entity.Entity; import net.minecraft.init.Blocks; import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumFacing; import net.minecraft.util.NonNullList; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.RayTraceResult; +import net.minecraft.world.chunk.EmptyChunk; import java.util.Optional; @@ -75,52 +78,87 @@ public interface MovementHelper extends ActionCosts, Helper { || block instanceof BlockEndPortal) {//you can't actually walk through a lilypad from the side, and you shouldn't walk through fire return false; } + if (block instanceof BlockDoor || block instanceof BlockFenceGate) { + if (block == Blocks.IRON_DOOR) { + return false; + } + return true; // we can just open the door + } + if (block instanceof BlockSnow || block instanceof BlockTrapDoor) { + // we've already checked doors + // so the only remaining dynamic isPassables are snow, fence gate, and trapdoor + // if they're cached as a top block, we don't know their metadata + // default to true (mostly because it would otherwise make long distance pathing through snowy biomes impossible) + if (mc.world.getChunk(pos) instanceof EmptyChunk) { + return true; + } + } IBlockState up = BlockStateInterface.get(pos.up()); if (BlockStateInterface.isFlowing(state) || up.getBlock() instanceof BlockLiquid || up.getBlock() instanceof BlockLilyPad) { return false; // Don't walk through flowing liquids } - if (block instanceof BlockDoor && !Blocks.IRON_DOOR.equals(block)) { - return true; // we can just open the door - } return block.isPassable(mc.world, pos); } - static boolean isDoorPassable(BlockPos doorPos, BlockPos playerPos) { - IBlockState door = BlockStateInterface.get(doorPos); - if (!(door.getBlock() instanceof BlockDoor)) { - return true; - } - String facing = door.getValue(BlockDoor.FACING).getName(); - boolean open = door.getValue(BlockDoor.OPEN).booleanValue(); - /** - * yes this is dumb - * change it if you want + static boolean isReplacable(BlockPos pos, IBlockState state) { + // for MovementTraverse and MovementAscend + // block double plant defaults to true when the block doesn't match, so don't need to check that case + // all other overrides just return true or false + // the only case to deal with is snow + /* + * public boolean isReplaceable(IBlockAccess worldIn, BlockPos pos) + * { + * return ((Integer)worldIn.getBlockState(pos).getValue(LAYERS)).intValue() == 1; + * } */ - String playerFacing = ""; - if (playerPos.equals(doorPos)) { - return false; + if (state.getBlock() instanceof BlockSnow) { + // as before, default to true (mostly because it would otherwise make long distance pathing through snowy biomes impossible) + if (mc.world.getChunk(pos) instanceof EmptyChunk) { + return true; + } } - if (playerPos.north().equals(doorPos) || playerPos.south().equals(doorPos)) { - playerFacing = "northsouth"; - } else if (playerPos.east().equals(doorPos) || playerPos.west().equals(doorPos)) { - playerFacing = "eastwest"; + return state.getBlock().isReplaceable(mc.world, pos); + } + + static boolean isDoorPassable(BlockPos doorPos, BlockPos playerPos) { + if (playerPos.equals(doorPos)) + return false; + + IBlockState state = BlockStateInterface.get(doorPos); + if (!(state.getBlock() instanceof BlockDoor)) + return true; + + return isHorizontalBlockPassable(doorPos, state, playerPos, BlockDoor.OPEN); + } + + static boolean isGatePassable(BlockPos gatePos, BlockPos playerPos) { + if (playerPos.equals(gatePos)) + return false; + + IBlockState state = BlockStateInterface.get(gatePos); + if (!(state.getBlock() instanceof BlockFenceGate)) + return true; + + return isHorizontalBlockPassable(gatePos, state, playerPos, BlockFenceGate.OPEN); + } + + static boolean isHorizontalBlockPassable(BlockPos blockPos, IBlockState blockState, BlockPos playerPos, PropertyBool propertyOpen) { + if (playerPos.equals(blockPos)) + return false; + + EnumFacing.Axis facing = blockState.getValue(BlockHorizontal.FACING).getAxis(); + boolean open = blockState.getValue(propertyOpen); + + EnumFacing.Axis playerFacing; + if (playerPos.north().equals(blockPos) || playerPos.south().equals(blockPos)) { + playerFacing = EnumFacing.Axis.Z; + } else if (playerPos.east().equals(blockPos) || playerPos.west().equals(blockPos)) { + playerFacing = EnumFacing.Axis.X; } else { return true; } - if (facing == "north" || facing == "south") { - if (open) { - return playerFacing == "northsouth"; - } else { - return playerFacing == "eastwest"; - } - } else { - if (open) { - return playerFacing == "eastwest"; - } else { - return playerFacing == "northsouth"; - } - } + return facing == playerFacing == open; } static boolean avoidWalkingInto(Block block) { @@ -170,12 +208,12 @@ public interface MovementHelper extends ActionCosts, Helper { return BlockStateInterface.get(pos).getBlock() instanceof BlockFalling; } - static double getMiningDurationTicks(CalculationContext context, BlockPos position) { + static double getMiningDurationTicks(CalculationContext context, BlockPos position, boolean includeFalling) { IBlockState state = BlockStateInterface.get(position); - return getMiningDurationTicks(context, position, state); + return getMiningDurationTicks(context, position, state, includeFalling); } - static double getMiningDurationTicks(CalculationContext context, BlockPos position, IBlockState state) { + static double getMiningDurationTicks(CalculationContext context, BlockPos position, IBlockState state, boolean includeFalling) { Block block = state.getBlock(); if (!block.equals(Blocks.AIR) && !canWalkThrough(position, state)) { // TODO is the air check really necessary? Isn't air canWalkThrough? if (!context.allowBreak()) { @@ -185,9 +223,17 @@ public interface MovementHelper extends ActionCosts, Helper { return COST_INF; } double m = Blocks.CRAFTING_TABLE.equals(block) ? 10 : 1; // TODO see if this is still necessary. it's from MineBot when we wanted to penalize breaking its crafting table - return m / context.getToolSet().getStrVsBlock(state, position); + double result = m / context.getToolSet().getStrVsBlock(state, position); + if (includeFalling) { + BlockPos up = position.up(); + IBlockState above = BlockStateInterface.get(up); + if (above.getBlock() instanceof BlockFalling) { + result += getMiningDurationTicks(context, up, above, true); + } + } + return result; } - return 0; + return 0; // we won't actually mine it, so don't check fallings above } /** @@ -266,22 +312,22 @@ public interface MovementHelper extends ActionCosts, Helper { static Movement generateMovementFallOrDescend(BlockPos pos, BlockPos dest, CalculationContext calcContext) { // A //SA - // B + // A // B // C // D - //if S is where you start, both of B need to be air for a movementfall + //if S is where you start, B needs to be air for a movementfall //A is plausibly breakable by either descend or fall //C, D, etc determine the length of the fall - for (int i = 1; i < 3; i++) { - if (!canWalkThrough(dest.down(i))) { - //if any of these two (B in the diagram) aren't air - //have to do a descend, because fall is impossible - //this doesn't guarantee descend is possible, it just guarantees fall is impossible - return new MovementDescend(pos, dest.down()); // standard move out by 1 and descend by 1 - } + if (!canWalkThrough(dest.down(2))) { + //if B in the diagram aren't air + //have to do a descend, because fall is impossible + + //this doesn't guarantee descend is possible, it just guarantees fall is impossible + return new MovementDescend(pos, dest.down()); // standard move out by 1 and descend by 1 } + // we're clear for a fall 2 // let's see how far we can fall for (int fallHeight = 3; true; fallHeight++) { diff --git a/src/main/java/baritone/pathing/movement/movements/MovementAscend.java b/src/main/java/baritone/pathing/movement/movements/MovementAscend.java index e7ecce25..f3225337 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementAscend.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementAscend.java @@ -64,15 +64,20 @@ public class MovementAscend extends Movement { // Counterpoint to the above TODO ^ you should move then pillar instead of ascend } + @Override + public void reset() { + super.reset(); + ticksWithoutPlacement = 0; + } + @Override protected double calculateCost(CalculationContext context) { IBlockState toPlace = BlockStateInterface.get(positionsToPlace[0]); if (!MovementHelper.canWalkOn(positionsToPlace[0], toPlace)) { - if (!BlockStateInterface.isAir(toPlace) && !BlockStateInterface.isWater(toPlace.getBlock())) { - // TODO replace this check with isReplacable or similar + if (!context.hasThrowaway()) { return COST_INF; } - if (!context.hasThrowaway()) { + if (!BlockStateInterface.isAir(toPlace) && !BlockStateInterface.isWater(toPlace.getBlock()) && !MovementHelper.isReplacable(positionsToPlace[0], toPlace)) { return COST_INF; } for (BlockPos against1 : against) { @@ -125,9 +130,9 @@ public class MovementAscend extends Movement { default: return state; } + if (playerFeet().equals(dest)) { - state.setStatus(MovementStatus.SUCCESS); - return state; + return state.setStatus(MovementStatus.SUCCESS); } if (!MovementHelper.canWalkOn(positionsToPlace[0])) { @@ -141,34 +146,56 @@ public class MovementAscend extends Movement { double faceZ = (dest.getZ() + anAgainst.getZ() + 1.0D) * 0.5D; state.setTarget(new MovementState.MovementTarget(Utils.calcRotationFromVec3d(playerHead(), new Vec3d(faceX, faceY, faceZ), playerRotations()), true)); EnumFacing side = Minecraft.getMinecraft().objectMouseOver.sideHit; - if (Objects.equals(LookBehaviorUtils.getSelectedBlock().orElse(null), anAgainst) && LookBehaviorUtils.getSelectedBlock().get().offset(side).equals(positionsToPlace[0])) { - ticksWithoutPlacement++; - state.setInput(InputOverrideHandler.Input.SNEAK, true); - if (player().isSneaking()) { - state.setInput(InputOverrideHandler.Input.CLICK_RIGHT, true); + + LookBehaviorUtils.getSelectedBlock().ifPresent(selectedBlock -> { + if (Objects.equals(selectedBlock, anAgainst) && selectedBlock.offset(side).equals(positionsToPlace[0])) { + ticksWithoutPlacement++; + state.setInput(InputOverrideHandler.Input.SNEAK, true); + if (player().isSneaking()) { + state.setInput(InputOverrideHandler.Input.CLICK_RIGHT, true); + } + if (ticksWithoutPlacement > 20) { + // After 20 ticks without placement, we might be standing in the way, move back + state.setInput(InputOverrideHandler.Input.MOVE_BACK, true); + } } - if (ticksWithoutPlacement > 20) { - state.setInput(InputOverrideHandler.Input.MOVE_BACK, true);//we might be standing in the way, move back - } - } - System.out.println("Trying to look at " + anAgainst + ", actually looking at" + LookBehaviorUtils.getSelectedBlock()); + System.out.println("Trying to look at " + anAgainst + ", actually looking at" + selectedBlock); + }); return state; } } return state.setStatus(MovementStatus.UNREACHABLE); } MovementHelper.moveTowards(state, dest); - state.setInput(InputOverrideHandler.Input.JUMP, true); - // TODO check if the below actually helps or hurts, it's weird - //double flatDistToNext = Math.abs(to.getX() - from.getX()) * Math.abs((to.getX() + 0.5D) - thePlayer.posX) + Math.abs(to.getZ() - from.getZ()) * Math.abs((to.getZ() + 0.5D) - thePlayer.posZ); - //boolean pointingInCorrectDirection = MovementManager.moveTowardsBlock(to); - //MovementManager.jumping = flatDistToNext < 1.2 && pointingInCorrectDirection; - //once we are pointing the right way and moving, start jumping - //this is slightly more efficient because otherwise we might start jumping before moving, and fall down without moving onto the block we want to jump onto - //also wait until we are close enough, because we might jump and hit our head on an adjacent block - //return Baritone.playerFeet.equals(to); + if (headBonkClear()) { + return state.setInput(InputOverrideHandler.Input.JUMP, true); + } - return state; + int xAxis = Math.abs(src.getX() - dest.getX()); // either 0 or 1 + int zAxis = Math.abs(src.getZ() - dest.getZ()); // either 0 or 1 + double flatDistToNext = xAxis * Math.abs((dest.getX() + 0.5D) - player().posX) + zAxis * Math.abs((dest.getZ() + 0.5D) - player().posZ); + double sideDist = zAxis * Math.abs((dest.getX() + 0.5D) - player().posX) + xAxis * Math.abs((dest.getZ() + 0.5D) - player().posZ); + // System.out.println(flatDistToNext + " " + sideDist); + if (flatDistToNext > 1.2 || sideDist > 0.2) { + return state; + } + + // Once we are pointing the right way and moving, start jumping + // This is slightly more efficient because otherwise we might start jumping before moving, and fall down without moving onto the block we want to jump onto + // Also wait until we are close enough, because we might jump and hit our head on an adjacent block + return state.setInput(InputOverrideHandler.Input.JUMP, true); + } + + private boolean headBonkClear() { + BlockPos startUp = src.up(2); + for (int i = 0; i < 4; i++) { + BlockPos check = startUp.offset(EnumFacing.byHorizontalIndex(i)); + if (!MovementHelper.canWalkThrough(check)) { + // We might bonk our head + return false; + } + } + return true; } } diff --git a/src/main/java/baritone/pathing/movement/movements/MovementDescend.java b/src/main/java/baritone/pathing/movement/movements/MovementDescend.java index 97c55f63..6e299bd1 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementDescend.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementDescend.java @@ -36,6 +36,12 @@ public class MovementDescend extends Movement { super(start, end, new BlockPos[]{end.up(2), end.up(), end}, new BlockPos[]{end.down()}); } + @Override + public void reset() { + super.reset(); + numTicks = 0; + } + @Override protected double calculateCost(CalculationContext context) { if (!MovementHelper.canWalkOn(positionsToPlace[0])) { @@ -67,6 +73,7 @@ public class MovementDescend extends Movement { default: return state; } + BlockPos playerFeet = playerFeet(); if (playerFeet.equals(dest)) { if (BlockStateInterface.isLiquid(dest) || player().posY - playerFeet.getY() < 0.094) { // lilypads @@ -85,9 +92,6 @@ public class MovementDescend extends Movement { double fromStart = Math.sqrt(x * x + z * z); if (!playerFeet.equals(dest) || ab > 0.25) { BlockPos fakeDest = new BlockPos(dest.getX() * 2 - src.getX(), dest.getY(), dest.getZ() * 2 - src.getZ()); - double diffX2 = player().posX - (fakeDest.getX() + 0.5); - double diffZ2 = player().posZ - (fakeDest.getZ() + 0.5); - double d = Math.sqrt(diffX2 * diffX2 + diffZ2 * diffZ2); if (numTicks++ < 20) { MovementHelper.moveTowards(state, fakeDest); if (fromStart > 1.25) { diff --git a/src/main/java/baritone/pathing/movement/movements/MovementDiagonal.java b/src/main/java/baritone/pathing/movement/movements/MovementDiagonal.java index 274f23c6..20defe13 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementDiagonal.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementDiagonal.java @@ -22,6 +22,7 @@ import baritone.pathing.movement.Movement; import baritone.pathing.movement.MovementHelper; import baritone.pathing.movement.MovementState; import baritone.utils.BlockStateInterface; +import baritone.utils.InputOverrideHandler; import net.minecraft.block.BlockMagma; import net.minecraft.block.state.IBlockState; import net.minecraft.init.Blocks; @@ -29,6 +30,7 @@ import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; import java.util.ArrayList; +import java.util.List; public class MovementDiagonal extends Movement { @@ -58,12 +60,13 @@ public class MovementDiagonal extends Movement { default: return state; } + if (playerFeet().equals(dest)) { state.setStatus(MovementState.MovementStatus.SUCCESS); return state; } if (!BlockStateInterface.isLiquid(playerFeet())) { - player().setSprinting(true); + state.setInput(InputOverrideHandler.Input.SPRINT, true); } MovementHelper.moveTowards(state, dest); return state; @@ -80,7 +83,7 @@ public class MovementDiagonal extends Movement { } double multiplier = WALK_ONE_BLOCK_COST; - // for either possible soul sand, that affects half of our walking + // For either possible soul sand, that affects half of our walking if (destWalkOn.getBlock().equals(Blocks.SOUL_SAND)) { multiplier += (WALK_ONE_OVER_SOUL_SAND_COST - WALK_ONE_BLOCK_COST) / 2; } @@ -94,8 +97,8 @@ public class MovementDiagonal extends Movement { if (BlockStateInterface.get(positionsToBreak[4].down()).getBlock() instanceof BlockMagma) { return COST_INF; } - double optionA = MovementHelper.getMiningDurationTicks(context, positionsToBreak[0]) + MovementHelper.getMiningDurationTicks(context, positionsToBreak[1]); - double optionB = MovementHelper.getMiningDurationTicks(context, positionsToBreak[2]) + MovementHelper.getMiningDurationTicks(context, positionsToBreak[3]); + double optionA = MovementHelper.getMiningDurationTicks(context, positionsToBreak[0], false) + MovementHelper.getMiningDurationTicks(context, positionsToBreak[1], true); + double optionB = MovementHelper.getMiningDurationTicks(context, positionsToBreak[2], false) + MovementHelper.getMiningDurationTicks(context, positionsToBreak[3], true); if (optionA != 0 && optionB != 0) { return COST_INF; } @@ -116,17 +119,17 @@ public class MovementDiagonal extends Movement { } } if (BlockStateInterface.isWater(src) || BlockStateInterface.isWater(dest)) { - // ignore previous multiplier - // whatever we were walking on (possibly soul sand) doesn't matter as we're actually floating on water - // not even touching the blocks below + // Ignore previous multiplier + // Whatever we were walking on (possibly soul sand) doesn't matter as we're actually floating on water + // Not even touching the blocks below multiplier = WALK_ONE_IN_WATER_COST; } if (optionA != 0 || optionB != 0) { multiplier *= SQRT_2 - 0.001; // TODO tune } if (multiplier == WALK_ONE_BLOCK_COST && context.canSprint()) { - // if we aren't edging around anything, and we aren't in water or soul sand - // we can sprint =D + // If we aren't edging around anything, and we aren't in water or soul sand + // We can sprint =D multiplier = SPRINT_ONE_BLOCK_COST; } return multiplier * SQRT_2; @@ -138,11 +141,11 @@ public class MovementDiagonal extends Movement { } @Override - public ArrayList toBreak() { + public List toBreak() { if (toBreakCached != null) { return toBreakCached; } - ArrayList result = new ArrayList<>(); + List result = new ArrayList<>(); for (int i = 4; i < 6; i++) { if (!MovementHelper.canWalkThrough(positionsToBreak[i])) { result.add(positionsToBreak[i]); @@ -153,11 +156,11 @@ public class MovementDiagonal extends Movement { } @Override - public ArrayList toWalkInto() { + public List toWalkInto() { if (toWalkIntoCached == null) { toWalkIntoCached = new ArrayList<>(); } - ArrayList result = new ArrayList<>(); + List result = new ArrayList<>(); for (int i = 0; i < 4; i++) { if (!MovementHelper.canWalkThrough(positionsToBreak[i])) { result.add(positionsToBreak[i]); diff --git a/src/main/java/baritone/pathing/movement/movements/MovementDownward.java b/src/main/java/baritone/pathing/movement/movements/MovementDownward.java index 2490fdd1..38232396 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementDownward.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementDownward.java @@ -36,6 +36,12 @@ public class MovementDownward extends Movement { super(start, end, new BlockPos[]{end}, new BlockPos[0]); } + @Override + public void reset() { + super.reset(); + numTicks = 0; + } + @Override protected double calculateCost(CalculationContext context) { if (!MovementHelper.canWalkOn(dest.down())) { @@ -47,7 +53,8 @@ public class MovementDownward extends Movement { if (ladder) { return LADDER_DOWN_ONE_COST; } else { - return FALL_N_BLOCKS_COST[1] + MovementHelper.getMiningDurationTicks(context, dest, d); + // we're standing on it, while it might be block falling, it'll be air by the time we get here in the movement + return FALL_N_BLOCKS_COST[1] + MovementHelper.getMiningDurationTicks(context, dest, d, false); } } @@ -62,6 +69,7 @@ public class MovementDownward extends Movement { default: return state; } + if (playerFeet().equals(dest)) { state.setStatus(MovementState.MovementStatus.SUCCESS); return state; diff --git a/src/main/java/baritone/pathing/movement/movements/MovementFall.java b/src/main/java/baritone/pathing/movement/movements/MovementFall.java index 6d96b365..c42a8661 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementFall.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementFall.java @@ -18,25 +18,20 @@ package baritone.pathing.movement.movements; import baritone.Baritone; -import baritone.behavior.impl.LookBehaviorUtils; import baritone.pathing.movement.CalculationContext; import baritone.pathing.movement.Movement; import baritone.pathing.movement.MovementHelper; import baritone.pathing.movement.MovementState; import baritone.pathing.movement.MovementState.MovementStatus; import baritone.pathing.movement.MovementState.MovementTarget; -import baritone.utils.BlockStateInterface; -import baritone.utils.InputOverrideHandler; -import baritone.utils.Rotation; -import baritone.utils.Utils; -import net.minecraft.block.Block; +import baritone.utils.*; +import net.minecraft.block.BlockFalling; import net.minecraft.init.Items; import net.minecraft.item.ItemStack; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.RayTraceResult; import net.minecraft.util.math.Vec3d; -import java.util.Optional; - public class MovementFall extends Movement { private static final ItemStack STACK_BUCKET_WATER = new ItemStack(Items.WATER_BUCKET); @@ -61,22 +56,29 @@ public class MovementFall extends Movement { } placeBucketCost = context.placeBlockCost(); } - double frontTwo = MovementHelper.getMiningDurationTicks(context, positionsToBreak[0]) + MovementHelper.getMiningDurationTicks(context, positionsToBreak[1]); - if (frontTwo >= COST_INF) { + double frontThree = 0; + for (int i = 0; i < 3; i++) { + frontThree += MovementHelper.getMiningDurationTicks(context, positionsToBreak[i], false); + // don't include falling because we will check falling right after this, and if it's there it's COST_INF + if (frontThree >= COST_INF) { + return COST_INF; + } + } + if (BlockStateInterface.get(positionsToBreak[0].up()).getBlock() instanceof BlockFalling) { return COST_INF; } - for (int i = 2; i < positionsToBreak.length; i++) { + for (int i = 3; i < positionsToBreak.length; i++) { // TODO is this the right check here? - // miningDurationTicks is all right, but shouldn't it be canWalkThrough instead? - // lilypads (i think?) are 0 ticks to mine, but they definitely cause fall damage - // same thing for falling through water... we can't actually do that - // and falling through signs is possible, but they do have a mining duration, right? - if (MovementHelper.getMiningDurationTicks(context, positionsToBreak[i]) > 0) { + // MiningDurationTicks is all right, but shouldn't it be canWalkThrough instead? + // Lilypads (i think?) are 0 ticks to mine, but they definitely cause fall damage + // Same thing for falling through water... we can't actually do that + // And falling through signs is possible, but they do have a mining duration, right? + if (MovementHelper.getMiningDurationTicks(context, positionsToBreak[i], false) > 0) { //can't break while falling return COST_INF; } } - return WALK_OFF_BLOCK_COST + FALL_N_BLOCKS_COST[positionsToBreak.length - 1] + placeBucketCost + frontTwo; + return WALK_OFF_BLOCK_COST + FALL_N_BLOCKS_COST[positionsToBreak.length - 1] + placeBucketCost + frontThree; } @Override @@ -90,21 +92,28 @@ public class MovementFall extends Movement { default: return state; } + BlockPos playerFeet = playerFeet(); - Optional targetRotation = Optional.empty(); + Rotation targetRotation = null; if (!BlockStateInterface.isWater(dest) && src.getY() - dest.getY() > Baritone.settings().maxFallHeightNoWater.get() && !playerFeet.equals(dest)) { - if (!player().inventory.hasItemStack(STACK_BUCKET_WATER) || world().provider.isNether()) { // TODO check if water bucket is on hotbar or main inventory + if (!player().inventory.hasItemStack(STACK_BUCKET_WATER) || world().provider.isNether()) { state.setStatus(MovementStatus.UNREACHABLE); return state; } + if (player().posY - dest.getY() < mc.playerController.getBlockReachDistance()) { player().inventory.currentItem = player().inventory.getSlotFor(STACK_BUCKET_WATER); - targetRotation = LookBehaviorUtils.reachable((BlockStateInterface.get(dest).getCollisionBoundingBox(mc.world, dest) == Block.NULL_AABB) ? dest : dest.down()); + + targetRotation = new Rotation(player().rotationYaw, 90.0F); + + RayTraceResult trace = RayTraceUtils.simulateRayTrace(player().rotationYaw, 90.0F); + if (trace != null && trace.typeOfHit == RayTraceResult.Type.BLOCK) { + state.setInput(InputOverrideHandler.Input.CLICK_RIGHT, true); + } } } - if (targetRotation.isPresent()) { - state.setInput(InputOverrideHandler.Input.CLICK_RIGHT, true) - .setTarget(new MovementTarget(targetRotation.get(), true)); + if (targetRotation != null) { + state.setTarget(new MovementTarget(targetRotation, true)); } else { state.setTarget(new MovementTarget(Utils.calcRotationFromVec3d(playerHead(), Utils.getBlockPosCenter(dest)), false)); } diff --git a/src/main/java/baritone/pathing/movement/movements/MovementPillar.java b/src/main/java/baritone/pathing/movement/movements/MovementPillar.java index 890637b0..dba55466 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementPillar.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementPillar.java @@ -38,6 +38,12 @@ public class MovementPillar extends Movement { super(start, end, new BlockPos[]{start.up(2)}, new BlockPos[]{start}); } + @Override + public void reset() { + super.reset(); + numTicks = 0; + } + @Override protected double calculateCost(CalculationContext context) { Block fromDown = BlockStateInterface.get(src).getBlock(); @@ -109,6 +115,7 @@ public class MovementPillar extends Movement { default: return state; } + IBlockState fromDown = BlockStateInterface.get(src); boolean ladder = fromDown.getBlock() instanceof BlockLadder || fromDown.getBlock() instanceof BlockVine; boolean vine = fromDown.getBlock() instanceof BlockVine; @@ -117,57 +124,68 @@ public class MovementPillar extends Movement { Utils.getBlockPosCenter(positionsToPlace[0]), new Rotation(mc.player.rotationYaw, mc.player.rotationPitch)), true)); } - EntityPlayerSP thePlayer = Minecraft.getMinecraft().player; + boolean blockIsThere = MovementHelper.canWalkOn(src) || ladder; if (ladder) { BlockPos against = vine ? getAgainst(src) : src.offset(fromDown.getValue(BlockLadder.FACING).getOpposite()); if (against == null) { displayChatMessageRaw("Unable to climb vines"); - state.setStatus(MovementState.MovementStatus.UNREACHABLE); - return state; + return state.setStatus(MovementState.MovementStatus.UNREACHABLE); } - if (playerFeet().equals(against.up()) || playerFeet().equals(dest)) { - state.setStatus(MovementState.MovementStatus.SUCCESS); - return state; + + if (playerFeet().equals(against.up()) || playerFeet().equals(dest)) + return state.setStatus(MovementState.MovementStatus.SUCCESS); + + /* + if (thePlayer.getPosition0().getX() != from.getX() || thePlayer.getPosition0().getZ() != from.getZ()) { + Baritone.moveTowardsBlock(from); } - /*if (thePlayer.getPosition0().getX() != from.getX() || thePlayer.getPosition0().getZ() != from.getZ()) { - Baritone.moveTowardsBlock(from); - }*/ + */ + MovementHelper.moveTowards(state, against); return state; } else { - if (!MovementHelper.throwaway(true)) {//get ready to place a throwaway block + // Get ready to place a throwaway block + if (!MovementHelper.throwaway(true)) { state.setStatus(MovementState.MovementStatus.UNREACHABLE); return state; } + numTicks++; - state.setInput(InputOverrideHandler.Input.JUMP, thePlayer.posY < dest.getY()); //if our Y coordinate is above our goal, stop jumping + // If our Y coordinate is above our goal, stop jumping + state.setInput(InputOverrideHandler.Input.JUMP, player().posY < dest.getY()); state.setInput(InputOverrideHandler.Input.SNEAK, true); - //otherwise jump + + // Otherwise jump if (numTicks > 40) { - double diffX = thePlayer.posX - (dest.getX() + 0.5); - double diffZ = thePlayer.posZ - (dest.getZ() + 0.5); + double diffX = player().posX - (dest.getX() + 0.5); + double diffZ = player().posZ - (dest.getZ() + 0.5); double dist = Math.sqrt(diffX * diffX + diffZ * diffZ); if (dist > 0.17) {//why 0.17? because it seemed like a good number, that's why //[explanation added after baritone port lol] also because it needs to be less than 0.2 because of the 0.3 sneak limit //and 0.17 is reasonably less than 0.2 - state.setInput(InputOverrideHandler.Input.MOVE_FORWARD, true);//if it's been more than forty ticks of trying to jump and we aren't done yet, go forward, maybe we are stuck + + // If it's been more than forty ticks of trying to jump and we aren't done yet, go forward, maybe we are stuck + state.setInput(InputOverrideHandler.Input.MOVE_FORWARD, true); } } + if (!blockIsThere) { Block fr = BlockStateInterface.get(src).getBlock(); if (!(fr instanceof BlockAir || fr.isReplaceable(Minecraft.getMinecraft().world, src))) { state.setInput(InputOverrideHandler.Input.CLICK_LEFT, true); blockIsThere = false; } else if (Minecraft.getMinecraft().player.isSneaking()) { - state.setInput(InputOverrideHandler.Input.CLICK_RIGHT, true);//constantly right click + state.setInput(InputOverrideHandler.Input.CLICK_RIGHT, true); } } } - if (playerFeet().equals(dest) && blockIsThere) {//if we are at our goal and the block below us is placed - state.setStatus(MovementState.MovementStatus.SUCCESS); - return state;//we are done + + // If we are at our goal and the block below us is placed + if (playerFeet().equals(dest) && blockIsThere) { + return state.setStatus(MovementState.MovementStatus.SUCCESS); } + return state; } } \ No newline at end of file diff --git a/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java b/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java index 105c1997..20b284e6 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java @@ -25,10 +25,7 @@ import baritone.pathing.movement.MovementState; import baritone.utils.BlockStateInterface; import baritone.utils.InputOverrideHandler; import baritone.utils.Utils; -import net.minecraft.block.Block; -import net.minecraft.block.BlockDoor; -import net.minecraft.block.BlockLadder; -import net.minecraft.block.BlockVine; +import net.minecraft.block.*; import net.minecraft.block.state.IBlockState; import net.minecraft.client.Minecraft; import net.minecraft.init.Blocks; @@ -66,6 +63,12 @@ public class MovementTraverse extends Movement { //note: do NOT add ability to place against .down().down() } + @Override + public void reset() { + super.reset(); + wasTheBridgeBlockAlwaysThere = true; + } + @Override protected double calculateCost(CalculationContext context) { IBlockState pb0 = BlockStateInterface.get(positionsToBreak[0]); @@ -85,15 +88,15 @@ public class MovementTraverse extends Movement { } if (MovementHelper.canWalkThrough(positionsToBreak[0], pb0) && MovementHelper.canWalkThrough(positionsToBreak[1], pb1)) { if (WC == WALK_ONE_BLOCK_COST && context.canSprint()) { - // if there's nothing in the way, and this isn't water or soul sand, and we aren't sneak placing - // we can sprint =D + // If there's nothing in the way, and this isn't water or soul sand, and we aren't sneak placing + // We can sprint =D WC = SPRINT_ONE_BLOCK_COST; } return WC; } - //double hardness1 = blocksToBreak[0].getBlockHardness(Minecraft.getMinecraft().world, positionsToBreak[0]); - //double hardness2 = blocksToBreak[1].getBlockHardness(Minecraft.getMinecraft().world, positionsToBreak[1]); - //Out.log("Can't walk through " + blocksToBreak[0] + " (hardness" + hardness1 + ") or " + blocksToBreak[1] + " (hardness " + hardness2 + ")"); + // double hardness1 = blocksToBreak[0].getBlockHardness(Minecraft.getMinecraft().world, positionsToBreak[0]); + // double hardness2 = blocksToBreak[1].getBlockHardness(Minecraft.getMinecraft().world, positionsToBreak[1]); + // Out.log("Can't walk through " + blocksToBreak[0] + " (hardness" + hardness1 + ") or " + blocksToBreak[1] + " (hardness " + hardness2 + ")"); return WC + getTotalHardnessOfBlocksToBreak(context); } else {//this is a bridge, so we need to place a block Block srcDown = BlockStateInterface.get(src.down()).getBlock(); @@ -101,7 +104,7 @@ public class MovementTraverse extends Movement { return COST_INF; } IBlockState pp0 = BlockStateInterface.get(positionsToPlace[0]); - if (pp0.getBlock().equals(Blocks.AIR) || (!BlockStateInterface.isWater(pp0.getBlock()) && pp0.getBlock().isReplaceable(Minecraft.getMinecraft().world, positionsToPlace[0]))) { + if (pp0.getBlock().equals(Blocks.AIR) || (!BlockStateInterface.isWater(pp0.getBlock()) && MovementHelper.isReplacable(positionsToPlace[0], pp0))) { if (!context.hasThrowaway()) { return COST_INF; } @@ -118,7 +121,7 @@ public class MovementTraverse extends Movement { return WC + context.placeBlockCost() + getTotalHardnessOfBlocksToBreak(context); } return COST_INF; - //Out.log("Can't walk on " + Baritone.get(positionsToPlace[0]).getBlock()); + // Out.log("Can't walk on " + Baritone.get(positionsToPlace[0]).getBlock()); } } @@ -133,27 +136,44 @@ public class MovementTraverse extends Movement { default: return state; } + Block fd = BlockStateInterface.get(src.down()).getBlock(); boolean ladder = fd instanceof BlockLadder || fd instanceof BlockVine; IBlockState pb0 = BlockStateInterface.get(positionsToBreak[0]); IBlockState pb1 = BlockStateInterface.get(positionsToBreak[1]); - boolean door = BlockStateInterface.get(src).getBlock() instanceof BlockDoor || pb0.getBlock() instanceof BlockDoor || pb1.getBlock() instanceof BlockDoor; + + boolean door = pb0.getBlock() instanceof BlockDoor || pb1.getBlock() instanceof BlockDoor; if (door) { boolean isDoorActuallyBlockingUs = false; - Block srcBlock = BlockStateInterface.get(src).getBlock(); - if (srcBlock instanceof BlockDoor && !MovementHelper.isDoorPassable(src, dest)) { + if (pb0.getBlock() instanceof BlockDoor && !MovementHelper.isDoorPassable(src, dest)) { isDoorActuallyBlockingUs = true; } else if (pb1.getBlock() instanceof BlockDoor && !MovementHelper.isDoorPassable(dest, src)) { isDoorActuallyBlockingUs = true; } if (isDoorActuallyBlockingUs) { - if (!(Blocks.IRON_DOOR.equals(srcBlock) || Blocks.IRON_DOOR.equals(pb0.getBlock()) || Blocks.IRON_DOOR.equals(pb1.getBlock()))) { + if (!(Blocks.IRON_DOOR.equals(pb0.getBlock()) || Blocks.IRON_DOOR.equals(pb1.getBlock()))) { state.setTarget(new MovementState.MovementTarget(Utils.calcRotationFromVec3d(playerHead(), Utils.calcCenterFromCoords(positionsToBreak[0], world())), true)); state.setInput(InputOverrideHandler.Input.CLICK_RIGHT, true); return state; } } } + + if (pb0.getBlock() instanceof BlockFenceGate || pb1.getBlock() instanceof BlockFenceGate) { + BlockPos blocked = null; + if (!MovementHelper.isGatePassable(positionsToBreak[0], src.up())) { + blocked = positionsToBreak[0]; + } else if (!MovementHelper.isGatePassable(positionsToBreak[1], src)) { + blocked = positionsToBreak[1]; + } + + if (blocked != null) { + state.setTarget(new MovementState.MovementTarget(Utils.calcRotationFromVec3d(playerHead(), Utils.calcCenterFromCoords(blocked, world())), true)); + state.setInput(InputOverrideHandler.Input.CLICK_RIGHT, true); + return state; + } + } + boolean isTheBridgeBlockThere = MovementHelper.canWalkOn(positionsToPlace[0]) || ladder; BlockPos whereAmI = playerFeet(); if (whereAmI.getY() != dest.getY() && !ladder) { @@ -163,13 +183,14 @@ public class MovementTraverse extends Movement { } return state; } + if (isTheBridgeBlockThere) { if (playerFeet().equals(dest)) { state.setStatus(MovementState.MovementStatus.SUCCESS); return state; } if (wasTheBridgeBlockAlwaysThere && !BlockStateInterface.isLiquid(playerFeet())) { - player().setSprinting(true); + state.setInput(InputOverrideHandler.Input.SPRINT, true); } Block destDown = BlockStateInterface.get(dest.down()).getBlock(); if (ladder && (destDown instanceof BlockVine || destDown instanceof BlockLadder)) { @@ -195,18 +216,18 @@ public class MovementTraverse extends Movement { EnumFacing side = Minecraft.getMinecraft().objectMouseOver.sideHit; if (Objects.equals(LookBehaviorUtils.getSelectedBlock().orElse(null), against1) && Minecraft.getMinecraft().player.isSneaking()) { if (LookBehaviorUtils.getSelectedBlock().get().offset(side).equals(positionsToPlace[0])) { - state.setInput(InputOverrideHandler.Input.CLICK_RIGHT, true); + return state.setInput(InputOverrideHandler.Input.CLICK_RIGHT, true); } else { // Out.gui("Wrong. " + side + " " + LookBehaviorUtils.getSelectedBlock().get().offset(side) + " " + positionsToPlace[0], Out.Mode.Debug); } } System.out.println("Trying to look at " + against1 + ", actually looking at" + LookBehaviorUtils.getSelectedBlock()); - return state; + return state.setInput(InputOverrideHandler.Input.CLICK_LEFT, true); } } state.setInput(InputOverrideHandler.Input.SNEAK, true); if (whereAmI.equals(dest)) { - // if we are in the block that we are trying to get to, we are sneaking over air and we need to place a block beneath us against the one we just walked off of + // If we are in the block that we are trying to get to, we are sneaking over air and we need to place a block beneath us against the one we just walked off of // Out.log(from + " " + to + " " + faceX + "," + faceY + "," + faceZ + " " + whereAmI); if (!MovementHelper.throwaway(true)) {// get ready to place a throwaway block displayChatMessageRaw("bb pls get me some blocks. dirt or cobble"); diff --git a/src/main/java/baritone/pathing/path/IPath.java b/src/main/java/baritone/pathing/path/IPath.java index c01c7f53..39c17993 100644 --- a/src/main/java/baritone/pathing/path/IPath.java +++ b/src/main/java/baritone/pathing/path/IPath.java @@ -146,7 +146,7 @@ public interface IPath extends Helper { } double factor = Baritone.settings().pathCutoffFactor.get(); int newLength = (int) (length() * factor); - displayChatMessageRaw("Static cutoff " + length() + " to " + newLength); + //displayChatMessageRaw("Static cutoff " + length() + " to " + newLength); return new CutoffPath(this, newLength); } } diff --git a/src/main/java/baritone/pathing/path/PathExecutor.java b/src/main/java/baritone/pathing/path/PathExecutor.java index dee3458a..0e7ebec6 100644 --- a/src/main/java/baritone/pathing/path/PathExecutor.java +++ b/src/main/java/baritone/pathing/path/PathExecutor.java @@ -22,6 +22,9 @@ import baritone.event.events.TickEvent; import baritone.pathing.movement.ActionCosts; import baritone.pathing.movement.Movement; import baritone.pathing.movement.MovementState; +import baritone.pathing.movement.movements.MovementDescend; +import baritone.pathing.movement.movements.MovementDiagonal; +import baritone.pathing.movement.movements.MovementTraverse; import baritone.utils.BlockStateInterface; import baritone.utils.Helper; import net.minecraft.client.entity.EntityPlayerSP; @@ -90,7 +93,11 @@ public class PathExecutor implements Helper { for (int i = 0; i < pathPosition - 2 && i < path.length(); i++) {//this happens for example when you lag out and get teleported back a couple blocks if (whereAmI.equals(path.positions().get(i))) { displayChatMessageRaw("Skipping back " + (pathPosition - i) + " steps, to " + i); + int previousPos = pathPosition; pathPosition = Math.max(i - 1, 0); // previous step might not actually be done + for (int j = pathPosition; j <= previousPos; j++) { + path.movements().get(j).reset(); + } Baritone.INSTANCE.getInputOverrideHandler().clearAllKeys(); return false; } @@ -207,6 +214,15 @@ public class PathExecutor implements Helper { if (costEstimateIndex == null || costEstimateIndex != pathPosition) { costEstimateIndex = pathPosition; currentMovementInitialCostEstimate = currentCost; // do this only once, when the movement starts + for (int i = 1; i < Baritone.settings().costVerificationLookahead.get() && pathPosition + i < path.length() - 1; i++) { + if (path.movements().get(pathPosition + i).recalculateCost() >= ActionCosts.COST_INF) { + displayChatMessageRaw("Something has changed in the world and a future movement has become impossible. Cancelling."); + pathPosition = path.length() + 3; + failed = true; + Baritone.INSTANCE.getInputOverrideHandler().clearAllKeys(); + return true; + } + } } MovementState.MovementStatus movementStatus = movement.update(); if (movementStatus == UNREACHABLE || movementStatus == FAILED) { @@ -224,6 +240,7 @@ public class PathExecutor implements Helper { onTick(event); return true; } else { + sprintIfRequested(); ticksOnCurrent++; if (ticksOnCurrent > currentMovementInitialCostEstimate + Baritone.settings().movementTimeoutTicks.get()) { // only fail if the total time has exceeded the initial estimate @@ -241,6 +258,59 @@ public class PathExecutor implements Helper { return false; // movement is in progress } + private void sprintIfRequested() { + if (!Baritone.settings().allowSprint.get()) { + player().setSprinting(false); + return; + } + if (Baritone.INSTANCE.getInputOverrideHandler().isInputForcedDown(mc.gameSettings.keyBindSprint)) { + if (!player().isSprinting()) { + player().setSprinting(true); + } + return; + } + Movement movement = path.movements().get(pathPosition); + if (movement instanceof MovementDescend && pathPosition < path.length() - 2) { + Movement next = path.movements().get(pathPosition + 1); + if (next instanceof MovementDescend) { + if (next.getDirection().equals(movement.getDirection())) { + if (playerFeet().equals(movement.getDest())) { + pathPosition++; + Baritone.INSTANCE.getInputOverrideHandler().clearAllKeys(); + } + if (!player().isSprinting()) { + player().setSprinting(true); + } + return; + } + } + if (next instanceof MovementTraverse) { + if (next.getDirection().down().equals(movement.getDirection())) { + if (playerFeet().equals(movement.getDest())) { + pathPosition++; + Baritone.INSTANCE.getInputOverrideHandler().clearAllKeys(); + } + if (!player().isSprinting()) { + player().setSprinting(true); + } + return; + } + } + if (next instanceof MovementDiagonal && Baritone.settings().allowOvershootDiagonalDescend.get()) { + if (playerFeet().equals(movement.getDest())) { + pathPosition++; + Baritone.INSTANCE.getInputOverrideHandler().clearAllKeys(); + } + if (!player().isSprinting()) { + player().setSprinting(true); + } + return; + } + //displayChatMessageRaw("Turning off sprinting " + movement + " " + next + " " + movement.getDirection() + " " + next.getDirection().down() + " " + next.getDirection().down().equals(movement.getDirection())); + } + player().setSprinting(false); + } + public int getPosition() { return pathPosition; } diff --git a/src/main/java/baritone/utils/BlockBreakHelper.java b/src/main/java/baritone/utils/BlockBreakHelper.java new file mode 100644 index 00000000..8c224a2b --- /dev/null +++ b/src/main/java/baritone/utils/BlockBreakHelper.java @@ -0,0 +1,52 @@ +/* + * This file is part of Baritone. + * + * Baritone 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. + * + * Baritone 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 Baritone. If not, see . + */ + +package baritone.utils; + +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; + +/** + * @author Brady + * @since 8/25/2018 + */ +public final class BlockBreakHelper implements Helper { + + private BlockBreakHelper() {} + + /** + * The last block that we tried to break, if this value changes + * between attempts, then we re-initialize the breaking process. + */ + private static BlockPos lastBlock; + + public static void tryBreakBlock(BlockPos pos, EnumFacing side) { + if (!pos.equals(lastBlock)) { + mc.playerController.clickBlock(pos, side); + } + if (mc.playerController.onPlayerDamageBlock(pos, side)) { + mc.player.swingArm(EnumHand.MAIN_HAND); + } + lastBlock = pos; + } + + public static void stopBreakingBlock() { + mc.playerController.resetBlockRemoving(); + lastBlock = null; + } +} diff --git a/src/main/java/baritone/utils/BlockStateInterface.java b/src/main/java/baritone/utils/BlockStateInterface.java index 1c23e74a..a1013d7f 100644 --- a/src/main/java/baritone/utils/BlockStateInterface.java +++ b/src/main/java/baritone/utils/BlockStateInterface.java @@ -18,7 +18,7 @@ package baritone.utils; import baritone.Baritone; -import baritone.chunk.CachedWorld; +import baritone.chunk.WorldData; import baritone.chunk.WorldProvider; import net.minecraft.block.Block; import net.minecraft.block.BlockFalling; @@ -42,16 +42,15 @@ public class BlockStateInterface implements Helper { return chunk.getBlockState(pos); } } - if (Baritone.settings().chunkCaching.get()) { - CachedWorld world = WorldProvider.INSTANCE.getCurrentWorld().cache; - if (world != null) { - IBlockState type = world.getBlock(pos); - if (type != null) { - return type; - } + WorldData world = WorldProvider.INSTANCE.getCurrentWorld(); + if (world != null) { + IBlockState type = world.cache.getBlock(pos); + if (type != null) { + return type; } } + return Blocks.AIR.getDefaultState(); } diff --git a/src/main/java/baritone/utils/ExampleBaritoneControl.java b/src/main/java/baritone/utils/ExampleBaritoneControl.java index a681816d..14addcf6 100644 --- a/src/main/java/baritone/utils/ExampleBaritoneControl.java +++ b/src/main/java/baritone/utils/ExampleBaritoneControl.java @@ -21,23 +21,23 @@ import baritone.Baritone; import baritone.Settings; import baritone.behavior.Behavior; import baritone.behavior.impl.PathingBehavior; +import baritone.chunk.ChunkPacker; +import baritone.chunk.Waypoint; +import baritone.chunk.WorldProvider; import baritone.event.events.ChatEvent; import baritone.map.Map; import baritone.pathing.calc.AStarPathFinder; -import baritone.pathing.goals.Goal; -import baritone.pathing.goals.GoalBlock; -import baritone.pathing.goals.GoalXZ; -import baritone.pathing.goals.GoalYLevel; +import baritone.pathing.goals.*; import baritone.pathing.movement.ActionCosts; import baritone.pathing.movement.CalculationContext; import baritone.pathing.movement.Movement; import baritone.utils.pathing.BetterBlockPos; +import net.minecraft.block.Block; import net.minecraft.util.math.BlockPos; +import net.minecraft.world.chunk.EmptyChunk; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; public class ExampleBaritoneControl extends Behavior { public static ExampleBaritoneControl INSTANCE = new ExampleBaritoneControl(); @@ -56,7 +56,10 @@ public class ExampleBaritoneControl extends Behavior { return; } String msg = event.getMessage(); - if (msg.startsWith("/")) { + if (Baritone.settings().prefix.get()) { + if (!msg.startsWith("#")) { + return; + } msg = msg.substring(1); } if (msg.toLowerCase().startsWith("goal")) { @@ -107,6 +110,79 @@ public class ExampleBaritoneControl extends Behavior { displayChatMessageRaw("ok canceled"); return; } + if (msg.toLowerCase().equals("invert")) { + Goal goal = PathingBehavior.INSTANCE.getGoal(); + BlockPos runAwayFrom; + if (goal instanceof GoalXZ) { + runAwayFrom = new BlockPos(((GoalXZ) goal).getX(), 0, ((GoalXZ) goal).getZ()); + } else if (goal instanceof GoalBlock) { + runAwayFrom = ((GoalBlock) goal).getGoalPos(); + } else { + displayChatMessageRaw("Goal must be GoalXZ or GoalBlock to invert"); + displayChatMessageRaw("Inverting goal of player feet"); + runAwayFrom = playerFeet(); + } + PathingBehavior.INSTANCE.setGoal(new GoalRunAway(1, runAwayFrom) { + @Override + public boolean isInGoal(BlockPos pos) { + return false; + } + }); + PathingBehavior.INSTANCE.path(); + event.cancel(); + return; + } + if (msg.toLowerCase().equals("reloadall")) { + WorldProvider.INSTANCE.getCurrentWorld().cache.reloadAllFromDisk(); + displayChatMessageRaw("ok"); + event.cancel(); + return; + } + if (msg.toLowerCase().equals("saveall")) { + WorldProvider.INSTANCE.getCurrentWorld().cache.save(); + displayChatMessageRaw("ok"); + event.cancel(); + return; + } + if (msg.toLowerCase().startsWith("find")) { + String blockType = msg.toLowerCase().substring(4).trim(); + LinkedList locs = WorldProvider.INSTANCE.getCurrentWorld().cache.getLocationsOf(blockType, 1, 4); + displayChatMessageRaw("Have " + locs.size() + " locations"); + for (BlockPos pos : locs) { + Block actually = BlockStateInterface.get(pos).getBlock(); + if (!ChunkPacker.blockToString(actually).equalsIgnoreCase(blockType)) { + System.out.println("Was looking for " + blockType + " but actually found " + actually + " " + ChunkPacker.blockToString(actually)); + } + } + event.cancel(); + return; + } + if (msg.toLowerCase().startsWith("mine")) { + String blockType = msg.toLowerCase().substring(4).trim(); + List locs = new ArrayList<>(WorldProvider.INSTANCE.getCurrentWorld().cache.getLocationsOf(blockType, 1, 1)); + if (locs.isEmpty()) { + displayChatMessageRaw("No locations known"); + event.cancel(); + return; + } + BlockPos playerFeet = playerFeet(); + locs.sort(Comparator.comparingDouble(playerFeet::distanceSq)); + + // remove any that are within loaded chunks that aren't actually what we want + locs.removeAll(locs.stream() + .filter(pos -> !(world().getChunk(pos) instanceof EmptyChunk)) + .filter(pos -> !ChunkPacker.blockToString(BlockStateInterface.get(pos).getBlock()).equalsIgnoreCase(blockType)) + .collect(Collectors.toList())); + + if (locs.size() > 30) { + displayChatMessageRaw("Pathing to any of closest 30"); + locs = locs.subList(0, 30); + } + PathingBehavior.INSTANCE.setGoal(new GoalComposite(locs.stream().map(GoalTwoBlocks::new).toArray(Goal[]::new))); + PathingBehavior.INSTANCE.path(); + event.cancel(); + return; + } if (msg.toLowerCase().startsWith("thisway")) { Goal goal = GoalXZ.fromDirection(playerFeetAsVec(), player().rotationYaw, Double.parseDouble(msg.substring(7).trim())); PathingBehavior.INSTANCE.setGoal(goal); @@ -114,18 +190,90 @@ public class ExampleBaritoneControl extends Behavior { event.cancel(); return; } - if (msg.toLowerCase().equals("spawn")) { - BlockPos spawnPoint = player().getBedLocation(); - // for some reason the default spawnpoint is underground sometimes - Goal goal = new GoalXZ(spawnPoint.getX(), spawnPoint.getY()); + if (msg.toLowerCase().startsWith("list") || msg.toLowerCase().startsWith("get ") || msg.toLowerCase().startsWith("show")) { + String waypointType = msg.toLowerCase().substring(4).trim(); + if (waypointType.endsWith("s")) { + // for example, "show deaths" + waypointType = waypointType.substring(0, waypointType.length() - 1); + } + Waypoint.Tag tag = Waypoint.TAG_MAP.get(waypointType); + if (tag == null) { + displayChatMessageRaw("Not a valid tag. Tags are: " + Arrays.asList(Waypoint.Tag.values()).toString().toLowerCase()); + event.cancel(); + return; + } + Set waypoints = WorldProvider.INSTANCE.getCurrentWorld().waypoints.getByTag(tag); + // might as well show them from oldest to newest + List sorted = new ArrayList<>(waypoints); + sorted.sort(Comparator.comparingLong(Waypoint::creationTimestamp)); + displayChatMessageRaw("Waypoints under tag " + tag + ":"); + for (Waypoint waypoint : sorted) { + displayChatMessageRaw(waypoint.toString()); + } + event.cancel(); + return; + } + if (msg.toLowerCase().startsWith("goto")) { + String waypointType = msg.toLowerCase().substring(4).trim(); + if (waypointType.endsWith("s")) { + // for example, "show deaths" + waypointType = waypointType.substring(0, waypointType.length() - 1); + } + Waypoint.Tag tag = Waypoint.TAG_MAP.get(waypointType); + if (tag == null) { + displayChatMessageRaw("Not a valid tag. Tags are: " + Arrays.asList(Waypoint.Tag.values()).toString().toLowerCase()); + event.cancel(); + return; + } + Waypoint waypoint = WorldProvider.INSTANCE.getCurrentWorld().waypoints.getMostRecentByTag(tag); + if (waypoint == null) { + displayChatMessageRaw("None saved for tag " + tag); + event.cancel(); + return; + } + Goal goal = new GoalBlock(waypoint.location); PathingBehavior.INSTANCE.setGoal(goal); - displayChatMessageRaw("Goal: " + goal); + PathingBehavior.INSTANCE.path(); + event.cancel(); + return; + } + if (msg.toLowerCase().equals("spawn") || msg.toLowerCase().equals("bed")) { + Waypoint waypoint = WorldProvider.INSTANCE.getCurrentWorld().waypoints.getMostRecentByTag(Waypoint.Tag.BED); + if (waypoint == null) { + BlockPos spawnPoint = player().getBedLocation(); + // for some reason the default spawnpoint is underground sometimes + Goal goal = new GoalXZ(spawnPoint.getX(), spawnPoint.getZ()); + displayChatMessageRaw("spawn not saved, defaulting to world spawn. set goal to " + goal); + PathingBehavior.INSTANCE.setGoal(goal); + } else { + Goal goal = new GoalBlock(waypoint.location); + PathingBehavior.INSTANCE.setGoal(goal); + displayChatMessageRaw("Set goal to most recent bed " + goal); + } + event.cancel(); + return; + } + if (msg.toLowerCase().equals("sethome")) { + WorldProvider.INSTANCE.getCurrentWorld().waypoints.addWaypoint(new Waypoint("", Waypoint.Tag.HOME, playerFeet())); + displayChatMessageRaw("Saved. Say home to set goal."); + event.cancel(); + return; + } + if (msg.toLowerCase().equals("home")) { + Waypoint waypoint = WorldProvider.INSTANCE.getCurrentWorld().waypoints.getMostRecentByTag(Waypoint.Tag.HOME); + if (waypoint == null) { + displayChatMessageRaw("home not saved"); + } else { + Goal goal = new GoalBlock(waypoint.location); + PathingBehavior.INSTANCE.setGoal(goal); + displayChatMessageRaw("Set goal to saved home " + goal); + } event.cancel(); return; } if (msg.toLowerCase().equals("costs")) { Movement[] movements = AStarPathFinder.getConnectedPositions(new BetterBlockPos(playerFeet()), new CalculationContext()); - ArrayList moves = new ArrayList<>(Arrays.asList(movements)); + List moves = new ArrayList<>(Arrays.asList(movements)); moves.sort(Comparator.comparingDouble(movement -> movement.getCost(new CalculationContext()))); for (Movement move : moves) { String[] parts = move.getClass().toString().split("\\."); diff --git a/src/main/java/baritone/utils/Helper.java b/src/main/java/baritone/utils/Helper.java index c637e48f..c973b1ca 100755 --- a/src/main/java/baritone/utils/Helper.java +++ b/src/main/java/baritone/utils/Helper.java @@ -20,16 +20,12 @@ package baritone.utils; import baritone.Baritone; import net.minecraft.client.Minecraft; import net.minecraft.client.entity.EntityPlayerSP; -import net.minecraft.client.gui.GuiNewChat; -import net.minecraft.client.gui.GuiUtilRenderComponents; import net.minecraft.client.multiplayer.WorldClient; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.TextComponentString; - -import java.util.List; +import net.minecraft.util.text.TextFormatting; /** * @author Brady @@ -37,6 +33,8 @@ import java.util.List; */ public interface Helper { + ITextComponent MESSAGE_PREFIX = new TextComponentString("§5[§dBaritone§5]§7"); + Minecraft mc = Minecraft.getMinecraft(); default EntityPlayerSP player() { @@ -73,14 +71,10 @@ public interface Helper { System.out.println(message); return; } - GuiNewChat gui = mc.ingameGUI.getChatGUI(); - int normalMaxWidth = MathHelper.floor((float) gui.getChatWidth() / gui.getChatScale()); - int widthWithStyleFormat = normalMaxWidth - 2; - List list = GuiUtilRenderComponents.splitText(new TextComponentString("§5[§dBaritone§5]§7 " + message), widthWithStyleFormat, - this.mc.fontRenderer, false, true); - for (ITextComponent component : list) { - gui.printChatMessage(new TextComponentString("§7" + component.getUnformattedText())); - } + ITextComponent component = MESSAGE_PREFIX.createCopy(); + component.getStyle().setColor(TextFormatting.GRAY); + component.appendSibling(new TextComponentString(" " + message)); + mc.ingameGUI.getChatGUI().printChatMessage(component); } } diff --git a/src/main/java/baritone/utils/InputOverrideHandler.java b/src/main/java/baritone/utils/InputOverrideHandler.java index 18426548..9860e2e1 100755 --- a/src/main/java/baritone/utils/InputOverrideHandler.java +++ b/src/main/java/baritone/utils/InputOverrideHandler.java @@ -84,8 +84,6 @@ public final class InputOverrideHandler implements Helper { * @param forced Whether or not the state is being forced */ public final void setInputForceState(Input input, boolean forced) { - if (!forced) - System.out.println(input); inputForceStateMap.put(input.getKeyBinding(), forced); } diff --git a/src/main/java/baritone/utils/RayTraceUtils.java b/src/main/java/baritone/utils/RayTraceUtils.java new file mode 100644 index 00000000..f6547454 --- /dev/null +++ b/src/main/java/baritone/utils/RayTraceUtils.java @@ -0,0 +1,47 @@ +/* + * This file is part of Baritone. + * + * Baritone 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. + * + * Baritone 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 Baritone. If not, see . + */ + +package baritone.utils; + +import net.minecraft.util.math.RayTraceResult; + +/** + * @author Brady + * @since 8/25/2018 + */ +public final class RayTraceUtils implements Helper { + + private RayTraceUtils() {} + + public static RayTraceResult simulateRayTrace(float yaw, float pitch) { + RayTraceResult oldTrace = mc.objectMouseOver; + float oldYaw = mc.player.rotationYaw; + float oldPitch = mc.player.rotationPitch; + + mc.player.rotationYaw = yaw; + mc.player.rotationPitch = pitch; + + mc.entityRenderer.getMouseOver(1.0F); + RayTraceResult result = mc.objectMouseOver; + mc.objectMouseOver = oldTrace; + + mc.player.rotationYaw = oldYaw; + mc.player.rotationPitch = oldPitch; + + return result; + } +} diff --git a/src/main/java/baritone/utils/ToolSet.java b/src/main/java/baritone/utils/ToolSet.java index f5f14c8f..bb445226 100644 --- a/src/main/java/baritone/utils/ToolSet.java +++ b/src/main/java/baritone/utils/ToolSet.java @@ -69,7 +69,13 @@ public class ToolSet implements Helper { * The values in this map are *not* hotbar slots indexes, they need to be looked up in slots * in order to be converted into hotbar slots. */ - private Map cache = new HashMap<>(); + private Map slotCache = new HashMap<>(); + + /** + * A cache mapping a {@link IBlockState} to how long it will take to break + * with this toolset, given the optimum tool is used. + */ + private Map breakStrengthCache = new HashMap<>(); /** * Create a toolset from the current player's inventory (but don't calculate any hardness values just yet) @@ -96,7 +102,7 @@ public class ToolSet implements Helper { * @return get which tool on the hotbar is best for mining it */ public Item getBestTool(IBlockState state) { - return tools.get(cache.computeIfAbsent(state.getBlock(), block -> getBestToolIndex(state))); + return tools.get(slotCache.computeIfAbsent(state.getBlock(), block -> getBestToolIndex(state))); } /** @@ -129,7 +135,7 @@ public class ToolSet implements Helper { * @return a byte indicating which hotbar slot worked best */ public byte getBestSlot(IBlockState state) { - return slots.get(cache.computeIfAbsent(state.getBlock(), block -> getBestToolIndex(state))); + return slots.get(slotCache.computeIfAbsent(state.getBlock(), block -> getBestToolIndex(state))); } /** @@ -140,6 +146,18 @@ public class ToolSet implements Helper { * @return how long it would take in ticks */ public double getStrVsBlock(IBlockState state, BlockPos pos) { + return this.breakStrengthCache.computeIfAbsent(state, s -> calculateStrVsBlock(s, pos)); + } + + /** + * Calculates how long would it take to mine the specified block given the best tool + * in this toolset is used. + * + * @param state the blockstate to be mined + * @param pos the blockpos to be mined + * @return how long it would take in ticks + */ + private double calculateStrVsBlock(IBlockState state, BlockPos pos) { // Calculate the slot with the best item byte slot = this.getBestSlot(state); diff --git a/src/main/resources/mixins.baritone.json b/src/main/resources/mixins.baritone.json index 025d42f8..c0b5d3bb 100755 --- a/src/main/resources/mixins.baritone.json +++ b/src/main/resources/mixins.baritone.json @@ -1,13 +1,14 @@ { "required": true, "package": "baritone.launch.mixins", - "refmap": "mixins.client.refmap.json", + "refmap": "mixins.baritone.refmap.json", "compatibilityLevel": "JAVA_8", "verbose": false, "injectors": { "maxShiftBy": 2 }, "client": [ + "MixinBlockPos", "MixinEntity", "MixinEntityPlayerSP", "MixinEntityRenderer", @@ -16,7 +17,6 @@ "MixinGuiScreen", "MixinInventoryPlayer", "MixinKeyBinding", - "MixinMain", "MixinMinecraft", "MixinNetHandlerPlayClient", "MixinNetworkManager", diff --git a/src/test/java/baritone/chunk/CachedRegionTest.java b/src/test/java/baritone/chunk/CachedRegionTest.java new file mode 100644 index 00000000..8350390c --- /dev/null +++ b/src/test/java/baritone/chunk/CachedRegionTest.java @@ -0,0 +1,47 @@ +/* + * This file is part of Baritone. + * + * Baritone 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. + * + * Baritone 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 Baritone. If not, see . + */ + +package baritone.chunk; + +import org.junit.Test; + +import static junit.framework.TestCase.assertEquals; + +public class CachedRegionTest { + + @Test + public void blockPosSaving() { + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + for (int y = 0; y < 256; y++) { + byte part1 = (byte) (z << 4 | x); + byte part2 = (byte) (y); + byte xz = part1; + int X = xz & 0x0f; + int Z = (xz >>> 4) & 0x0f; + int Y = part2 & 0xff; + if (x != X || y != Y || z != Z) { + System.out.println(x + " " + X + " " + y + " " + Y + " " + z + " " + Z); + } + assertEquals(x, X); + assertEquals(y, Y); + assertEquals(z, Z); + } + } + } + } +} \ No newline at end of file