From 702b9169485010daec4b8581750595aa06f6c0b6 Mon Sep 17 00:00:00 2001 From: Leijurv Date: Tue, 8 Jan 2019 20:45:02 -0800 Subject: [PATCH] preliminary schematic stuff --- .../api/pathing/goals/GoalGetToBlock.java | 6 +- .../baritone/behavior/PathingBehavior.java | 9 +- .../pathing/movement/CalculationContext.java | 9 +- .../pathing/movement/MovementHelper.java | 4 +- .../movement/movements/MovementAscend.java | 6 + .../movement/movements/MovementPillar.java | 4 +- .../java/baritone/process/BuilderProcess.java | 200 +++++++++++++++--- src/main/java/baritone/utils/ISchematic.java | 6 + .../java/baritone/utils/MapArtSchematic.java | 52 +++++ src/main/java/baritone/utils/Schematic.java | 93 ++++++++ 10 files changed, 346 insertions(+), 43 deletions(-) create mode 100644 src/main/java/baritone/utils/MapArtSchematic.java create mode 100644 src/main/java/baritone/utils/Schematic.java diff --git a/src/api/java/baritone/api/pathing/goals/GoalGetToBlock.java b/src/api/java/baritone/api/pathing/goals/GoalGetToBlock.java index c4856cdd..fb2a07aa 100644 --- a/src/api/java/baritone/api/pathing/goals/GoalGetToBlock.java +++ b/src/api/java/baritone/api/pathing/goals/GoalGetToBlock.java @@ -28,9 +28,9 @@ import net.minecraft.util.math.BlockPos; */ public class GoalGetToBlock implements Goal, IGoalRenderPos { - private final int x; - private final int y; - private final int z; + public final int x; + public final int y; + public final int z; public GoalGetToBlock(BlockPos pos) { this.x = pos.getX(); diff --git a/src/main/java/baritone/behavior/PathingBehavior.java b/src/main/java/baritone/behavior/PathingBehavior.java index a7509173..06020598 100644 --- a/src/main/java/baritone/behavior/PathingBehavior.java +++ b/src/main/java/baritone/behavior/PathingBehavior.java @@ -57,6 +57,7 @@ public final class PathingBehavior extends Behavior implements IPathingBehavior, private boolean safeToCancel; private boolean pauseRequestedLastTick; + private boolean unpausedLastTick; private boolean cancelRequested; private boolean calcFailedLastTick; @@ -102,10 +103,14 @@ public final class PathingBehavior extends Behavior implements IPathingBehavior, private void tickPath() { if (pauseRequestedLastTick && safeToCancel) { pauseRequestedLastTick = false; - baritone.getInputOverrideHandler().clearAllKeys(); - baritone.getInputOverrideHandler().getBlockBreakHelper().stopBreakingBlock(); + if (unpausedLastTick) { + baritone.getInputOverrideHandler().clearAllKeys(); + baritone.getInputOverrideHandler().getBlockBreakHelper().stopBreakingBlock(); + } + unpausedLastTick = false; return; } + unpausedLastTick = true; if (cancelRequested) { cancelRequested = false; baritone.getInputOverrideHandler().clearAllKeys(); diff --git a/src/main/java/baritone/pathing/movement/CalculationContext.java b/src/main/java/baritone/pathing/movement/CalculationContext.java index 66ed3d93..4611d975 100644 --- a/src/main/java/baritone/pathing/movement/CalculationContext.java +++ b/src/main/java/baritone/pathing/movement/CalculationContext.java @@ -142,11 +142,14 @@ public class CalculationContext { return placeBlockCost; } - public boolean canBreakAt(int x, int y, int z) { + public double breakCostMultiplierAt(int x, int y, int z) { if (!allowBreak) { - return false; + return COST_INF; } - return !isPossiblyProtected(x, y, z); + if (isPossiblyProtected(x, y, z)) { + return COST_INF; + } + return 1; } public double placeBucketCost() { diff --git a/src/main/java/baritone/pathing/movement/MovementHelper.java b/src/main/java/baritone/pathing/movement/MovementHelper.java index d07f05e7..96c4a769 100644 --- a/src/main/java/baritone/pathing/movement/MovementHelper.java +++ b/src/main/java/baritone/pathing/movement/MovementHelper.java @@ -341,7 +341,8 @@ public interface MovementHelper extends ActionCosts, Helper { static double getMiningDurationTicks(CalculationContext context, int x, int y, int z, IBlockState state, boolean includeFalling) { Block block = state.getBlock(); if (!canWalkThrough(context.bsi, x, y, z, state)) { - if (!context.canBreakAt(x, y, z)) { + double mult = context.breakCostMultiplierAt(x, y, z); + if (mult >= COST_INF) { return COST_INF; } if (avoidBreaking(context.bsi, x, y, z, state)) { @@ -358,6 +359,7 @@ public interface MovementHelper extends ActionCosts, Helper { double result = m / strVsBlock; result += context.breakBlockAdditionalCost; + result *= mult; if (includeFalling) { IBlockState above = context.get(x, y + 1, z); if (above.getBlock() instanceof BlockFalling) { diff --git a/src/main/java/baritone/pathing/movement/movements/MovementAscend.java b/src/main/java/baritone/pathing/movement/movements/MovementAscend.java index 7da7ad15..5fafdeed 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementAscend.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementAscend.java @@ -240,4 +240,10 @@ public class MovementAscend extends Movement { } return true; } + + @Override + public boolean safeToCancel(MovementState state) { + // if we had to place, don't allow pause + return state.getStatus() != MovementStatus.RUNNING || ticksWithoutPlacement == 0; + } } diff --git a/src/main/java/baritone/pathing/movement/movements/MovementPillar.java b/src/main/java/baritone/pathing/movement/movements/MovementPillar.java index dd93eeed..18a248c1 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementPillar.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementPillar.java @@ -35,6 +35,8 @@ import net.minecraft.init.Blocks; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; +import java.util.Objects; + public class MovementPillar extends Movement { public MovementPillar(IBaritone baritone, BetterBlockPos start, BetterBlockPos end) { @@ -229,7 +231,7 @@ public class MovementPillar extends Movement { if (!(fr instanceof BlockAir || fr.isReplaceable(ctx.world(), src))) { state.setInput(Input.CLICK_LEFT, true); blockIsThere = false; - } else if (ctx.player().isSneaking()) { // 1 tick after we're able to place + } else if (ctx.player().isSneaking() && (Objects.equals(src.down(), ctx.objectMouseOver().getBlockPos()) || Objects.equals(src, ctx.objectMouseOver().getBlockPos()))) { // 1 tick after we're able to place state.setInput(Input.CLICK_RIGHT, true); } } diff --git a/src/main/java/baritone/process/BuilderProcess.java b/src/main/java/baritone/process/BuilderProcess.java index 8bb966ea..27142120 100644 --- a/src/main/java/baritone/process/BuilderProcess.java +++ b/src/main/java/baritone/process/BuilderProcess.java @@ -25,11 +25,15 @@ import baritone.api.pathing.goals.GoalGetToBlock; import baritone.api.process.PathingCommand; import baritone.api.process.PathingCommandType; import baritone.api.utils.BetterBlockPos; +import baritone.api.utils.Rotation; +import baritone.api.utils.RotationUtils; +import baritone.api.utils.input.Input; import baritone.pathing.movement.CalculationContext; +import baritone.pathing.movement.MovementHelper; import baritone.utils.BaritoneProcessHelper; -import baritone.utils.BlockStateInterface; import baritone.utils.ISchematic; import baritone.utils.PathingCommandContext; +import baritone.utils.Schematic; import net.minecraft.block.state.IBlockState; import net.minecraft.client.Minecraft; import net.minecraft.init.Blocks; @@ -38,14 +42,15 @@ import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompressedStreamTools; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.EnumFacing; +import net.minecraft.util.Tuple; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3i; import java.io.File; import java.io.FileInputStream; import java.io.IOException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; import static baritone.api.pathing.movement.ActionCosts.COST_INF; @@ -80,7 +85,7 @@ public class BuilderProcess extends BaritoneProcessHelper { } private static ISchematic parse(NBTTagCompound schematic) { - throw new UnsupportedOperationException("would rather die than parse " + schematic); + return new Schematic(schematic); } @Override @@ -88,6 +93,32 @@ public class BuilderProcess extends BaritoneProcessHelper { return schematic != null; } + public Optional> toBreakNearPlayer(BuilderCalculationContext bcc) { + BetterBlockPos center = ctx.playerFeet(); + for (int dx = -5; dx <= 5; dx++) { + for (int dy = 0; dy <= 5; dy++) { + for (int dz = -5; dz <= 5; dz++) { + int x = center.x + dx; + int y = center.y + dy; + int z = center.z + dz; + IBlockState desired = bcc.getSchematic(x, y, z); + if (desired == null) { + continue; // irrelevant + } + IBlockState curr = bcc.bsi.get0(x, y, z); + if (curr.getBlock() != Blocks.AIR && !valid(curr, desired)) { + BetterBlockPos pos = new BetterBlockPos(x, y, z); + Optional rot = RotationUtils.reachable(ctx.player(), pos, ctx.playerController().getBlockReachDistance()); + if (rot.isPresent()) { + return Optional.of(new Tuple<>(pos, rot.get())); + } + } + } + } + } + return Optional.empty(); + } + @Override public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { // TODO somehow tell inventorybehavior what we'd like to have on the hotbar @@ -97,22 +128,113 @@ public class BuilderProcess extends BaritoneProcessHelper { // this will work as is, but it'll be trashy // need to iterate over incorrectPositions and see which ones we can "correct" from our current standing position - // considerations: - // shouldn't break blocks that are supporting our current path segment, maybe? - // - return new PathingCommandContext(new GoalComposite(assemble()), PathingCommandType.FORCE_REVALIDATE_GOAL_AND_PATH, new BuilderCalculationContext(schematic, origin)); + + BuilderCalculationContext bcc = new BuilderCalculationContext(schematic, origin); + if (!recalc(bcc)) { + logDirect("Done building"); + onLostControl(); + return null; + } + Optional> toBreak = toBreakNearPlayer(bcc); + baritone.getInputOverrideHandler().clearAllKeys(); + if (toBreak.isPresent() && isSafeToCancel && ctx.player().onGround) { + // we'd like to pause to break this block + // only change look direction if it's safe (don't want to fuck up an in progress parkour for example + Rotation rot = toBreak.get().getSecond(); + BetterBlockPos pos = toBreak.get().getFirst(); + baritone.getLookBehavior().updateTarget(rot, true); + MovementHelper.switchToBestToolFor(ctx, bcc.get(pos)); + if (Objects.equals(ctx.objectMouseOver().getBlockPos(), rot) || ctx.playerRotations().isReallyCloseTo(rot)) { + baritone.getInputOverrideHandler().setInputForceState(Input.CLICK_LEFT, true); + } + return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE); + } + + Goal[] goals = assemble(bcc); + if (goals.length == 0) { + logDirect("Unable to do it =("); + onLostControl(); + return null; + } + return new PathingCommandContext(new GoalComposite(goals), PathingCommandType.FORCE_REVALIDATE_GOAL_AND_PATH, bcc); } - private Goal[] assemble() { - BlockStateInterface bsi = new CalculationContext(baritone).bsi; - return incorrectPositions.stream().map(pos -> - bsi.get0(pos).getBlock() == Blocks.AIR ? - // it's air and it shouldn't be - new GoalBlock(pos.up()) - // it's a block and it shouldn't be - // todo disallow right above - : new GoalGetToBlock(pos) // replace with GoalTwoBlocks to mine using pathfinding system only - ).toArray(Goal[]::new); + public boolean recalc(BuilderCalculationContext bcc) { + if (incorrectPositions == null) { + incorrectPositions = new HashSet<>(); + fullRecalc(bcc); + if (incorrectPositions.isEmpty()) { + return false; + } + } + recalcNearby(bcc); + if (incorrectPositions.isEmpty()) { + fullRecalc(bcc); + } + return !incorrectPositions.isEmpty(); + } + + public void recalcNearby(BuilderCalculationContext bcc) { + BetterBlockPos center = ctx.playerFeet(); + for (int dx = -5; dx <= 5; dx++) { + for (int dy = -5; dy <= 5; dy++) { + for (int dz = -5; dz <= 5; dz++) { + int x = center.x + dx; + int y = center.y + dy; + int z = center.z + dz; + IBlockState desired = bcc.getSchematic(x, y, z); + if (desired != null) { + // we care about this position + if (valid(bcc.bsi.get0(x, y, z), desired)) { + incorrectPositions.remove(new BetterBlockPos(x, y, z)); + } else { + incorrectPositions.add(new BetterBlockPos(x, y, z)); + } + } + } + } + } + } + + public void fullRecalc(BuilderCalculationContext bcc) { + incorrectPositions = new HashSet<>(); + for (int y = 0; y < schematic.heightY(); y++) { + for (int z = 0; z < schematic.lengthZ(); z++) { + for (int x = 0; x < schematic.widthX(); x++) { + if (schematic.inSchematic(x, y, z)) { + if (!valid(bcc.bsi.get0(x + origin.getX(), y + origin.getY(), z + origin.getZ()), schematic.desiredState(x, y, z))) { + incorrectPositions.add(new BetterBlockPos(x + origin.getX(), y + origin.getY(), z + origin.getZ())); + } + } + } + } + } + } + + private Goal[] assemble(BuilderCalculationContext bcc) { + List approxPlacable = placable(); + List placable = incorrectPositions.stream().filter(pos -> bcc.bsi.get0(pos).getBlock() == Blocks.AIR && approxPlacable.contains(bcc.getSchematic(pos.x, pos.y, pos.z))).collect(Collectors.toList()); + if (!placable.isEmpty()) { + return placable.stream().filter(pos -> !placable.contains(pos.down()) && !placable.contains(pos.down(2))).map(BetterBlockPos::up).map(GoalBlock::new).toArray(Goal[]::new); + } + return incorrectPositions.stream().filter(pos -> bcc.bsi.get0(pos).getBlock() != Blocks.AIR).map(GoalBreak::new).toArray(Goal[]::new); + } + + public static class GoalBreak extends GoalGetToBlock { + + public GoalBreak(BlockPos pos) { + super(pos); + } + + @Override + public boolean isInGoal(int x, int y, int z) { + // can't stand right on top of a block, that might not work (what if it's unsupported, can't break then) + if (x == this.x && y == this.y + 1 && z == this.z) { + return false; + } + // but any other adjacent works for breaking, including inside or below + return super.isInGoal(x, y, z); + } } @Override @@ -149,6 +271,11 @@ public class BuilderProcess extends BaritoneProcessHelper { return result; } + public boolean valid(IBlockState current, IBlockState desired) { + // TODO more complicated comparison logic I guess + return desired == null || current.equals(desired); + } + public class BuilderCalculationContext extends CalculationContext { private final List placable; private final ISchematic schematic; @@ -181,6 +308,11 @@ public class BuilderProcess extends BaritoneProcessHelper { IBlockState sch = getSchematic(x, y, z); if (sch != null) { // TODO this can return true even when allowPlace is off.... is that an issue? + if (sch.getBlock() == Blocks.AIR) { + // we want this to be air, but they're asking if they can place here + // this won't be a schematic block, this will be a throwaway + return placeBlockCost * 2; // we're going to have to break it eventually + } if (placable.contains(sch)) { return 0; // thats right we gonna make it FREE to place a block where it should go in a structure // no place block penalty at all 😎 @@ -190,15 +322,9 @@ public class BuilderProcess extends BaritoneProcessHelper { if (!hasThrowaway) { return COST_INF; } - if (sch.getBlock() == Blocks.AIR) { - // we want this to be air, but they're asking if they can place here - // this won't be a schematic block, this will be a throwaway - return placeBlockCost * 2; // we're going to have to break it eventually - } else { - // we want it to be something that we don't have - // even more of a pain to place something wrong - return placeBlockCost * 3; - } + // we want it to be something that we don't have + // even more of a pain to place something wrong + return placeBlockCost * 3; } else { if (hasThrowaway) { return placeBlockCost; @@ -209,24 +335,32 @@ public class BuilderProcess extends BaritoneProcessHelper { } @Override - public boolean canBreakAt(int x, int y, int z) { + public double breakCostMultiplierAt(int x, int y, int z) { if (!allowBreak || isPossiblyProtected(x, y, z)) { - return false; + return COST_INF; } IBlockState sch = getSchematic(x, y, z); if (sch != null) { if (sch.getBlock() == Blocks.AIR) { // it should be air // regardless of current contents, we can break it - return true; + return 1; } // it should be a real block // is it already that block? - return !bsi.get0(x, y, z).equals(sch); // can break if it's wrong + if (valid(bsi.get0(x, y, z), sch)) { + return 3; + } else { + // can break if it's wrong + // would be great to return less than 1 here, but that would actually make the cost calculation messed up + // since we're breaking a block, if we underestimate the cost, then it'll fail when it really takes the correct amount of time + return 1; + + } // TODO do blocks in render distace only? // TODO allow breaking blocks that we have a tool to harvest and immediately place back? } else { - return true; // why not lol + return 1; // why not lol } } } diff --git a/src/main/java/baritone/utils/ISchematic.java b/src/main/java/baritone/utils/ISchematic.java index db494c29..b68f1a1c 100644 --- a/src/main/java/baritone/utils/ISchematic.java +++ b/src/main/java/baritone/utils/ISchematic.java @@ -36,4 +36,10 @@ public interface ISchematic { boolean inSchematic(int x, int y, int z); IBlockState desiredState(int x, int y, int z); + + int widthX(); + + int heightY(); + + int lengthZ(); } \ No newline at end of file diff --git a/src/main/java/baritone/utils/MapArtSchematic.java b/src/main/java/baritone/utils/MapArtSchematic.java new file mode 100644 index 00000000..9813b637 --- /dev/null +++ b/src/main/java/baritone/utils/MapArtSchematic.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 Lesser 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Baritone. If not, see . + */ + +package baritone.utils; + +import net.minecraft.block.state.IBlockState; +import net.minecraft.init.Blocks; +import net.minecraft.nbt.NBTTagCompound; + +public class MapArtSchematic extends Schematic { + private final int[][] heightMap; + + public MapArtSchematic(NBTTagCompound schematic) { + super(schematic); + heightMap = new int[widthX][lengthZ]; + for (int x = 0; x < widthX; x++) { + https: + for (int z = 0; z < lengthZ; z++) { + IBlockState[] column = states[x][z]; + for (int y = heightY - 1; y >= 0; y--) { + if (column[y].getBlock() != Blocks.AIR) { + heightMap[x][z] = y; + continue https; + } + } + System.out.println("Column " + x + "," + z + " has no blocks, but it's apparently map art? wtf"); + System.out.println("Letting it be whatever"); + heightMap[x][z] = 256; + } + } + } + + @Override + public boolean inSchematic(int x, int y, int z) { + // in map art, we only care about coordinates in or above the art + return super.inSchematic(x, y, z) && y >= heightMap[x][z]; + } +} diff --git a/src/main/java/baritone/utils/Schematic.java b/src/main/java/baritone/utils/Schematic.java new file mode 100644 index 00000000..dbc80a73 --- /dev/null +++ b/src/main/java/baritone/utils/Schematic.java @@ -0,0 +1,93 @@ +/* + * This file is part of Baritone. + * + * Baritone is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Baritone. If not, see . + */ + +package baritone.utils; + +import net.minecraft.block.Block; +import net.minecraft.block.state.IBlockState; +import net.minecraft.nbt.NBTTagCompound; + +public class Schematic implements ISchematic { + public final int widthX; + public final int heightY; + public final int lengthZ; + protected final IBlockState[][][] states; + + public Schematic(NBTTagCompound schematic) { + String type = schematic.getString("Materials"); + if (!type.equals("Alpha")) { + throw new IllegalStateException("bad schematic " + type); + } + widthX = schematic.getInteger("Width"); + heightY = schematic.getInteger("Height"); + lengthZ = schematic.getInteger("Length"); + byte[] blocks = schematic.getByteArray("Blocks"); + byte[] metadata = schematic.getByteArray("Data"); + + byte[] additional = null; + if (schematic.hasKey("AddBlocks")) { + byte[] addBlocks = schematic.getByteArray("AddBlocks"); + additional = new byte[addBlocks.length * 2]; + for (int i = 0; i < addBlocks.length; i++) { + additional[i * 2 + 0] = (byte) ((addBlocks[i] >> 4) & 0xF); // lower nibble + additional[i * 2 + 1] = (byte) ((addBlocks[i] >> 0) & 0xF); // upper nibble + } + } + states = new IBlockState[widthX][lengthZ][heightY]; + for (int y = 0; y < heightY; y++) { + for (int z = 0; z < lengthZ; z++) { + for (int x = 0; x < widthX; x++) { + int blockInd = (y * lengthZ + z) * widthX + x; + + int blockID = blocks[blockInd] & 0xFF; + if (additional != null) { + // additional is 0 through 15 inclusive since it's & 0xF above + blockID |= additional[blockInd] << 8; + } + Block block = Block.REGISTRY.getObjectById(blockID); + int meta = metadata[blockInd] & 0xFF; + states[x][z][y] = block.getStateFromMeta(meta); + } + } + } + } + + @Override + public boolean inSchematic(int x, int y, int z) { + return x >= 0 && x < widthX && y >= 0 && y < heightY && z >= 0 && z < lengthZ; + } + + @Override + public IBlockState desiredState(int x, int y, int z) { + return states[x][z][y]; + } + + @Override + public int widthX() { + return widthX; + } + + @Override + public int heightY() { + return heightY; + } + + @Override + public int lengthZ() { + return lengthZ; + } +}