From b2c26b751a22719ec3dd6acf11eeadfc3e5aa6d5 Mon Sep 17 00:00:00 2001 From: Thojo0 <53666000+thojo0@users.noreply.github.com> Date: Sun, 5 Nov 2023 00:25:16 +0000 Subject: [PATCH 1/3] support for building selected litematic --- .../baritone/api/process/IBuilderProcess.java | 2 ++ .../command/defaults/LitematicaCommand.java | 12 ++++++----- .../java/baritone/process/BuilderProcess.java | 21 ++++++++++++++----- .../litematica/LitematicaHelper.java | 11 ++++++++++ .../placement/SchematicPlacementManager.java | 8 +++++++ 5 files changed, 44 insertions(+), 10 deletions(-) diff --git a/src/api/java/baritone/api/process/IBuilderProcess.java b/src/api/java/baritone/api/process/IBuilderProcess.java index 77250ce73..59b690a89 100644 --- a/src/api/java/baritone/api/process/IBuilderProcess.java +++ b/src/api/java/baritone/api/process/IBuilderProcess.java @@ -58,6 +58,8 @@ default boolean build(String schematicFile, BlockPos origin) { void buildOpenSchematic(); + void buildOpenLitematic(); + void buildOpenLitematic(int i); void pause(); diff --git a/src/main/java/baritone/command/defaults/LitematicaCommand.java b/src/main/java/baritone/command/defaults/LitematicaCommand.java index bfe0079b3..9975de592 100644 --- a/src/main/java/baritone/command/defaults/LitematicaCommand.java +++ b/src/main/java/baritone/command/defaults/LitematicaCommand.java @@ -40,11 +40,13 @@ public void execute(String label, IArgConsumer args) throws CommandException { if (args.is(Integer.class)) { schematic = args.getAs(Integer.class) - 1; } - } - try { - baritone.getBuilderProcess().buildOpenLitematic(schematic); - } catch (IndexOutOfBoundsException e) { - logDirect("Pleas provide a valid index."); + try { + baritone.getBuilderProcess().buildOpenLitematic(schematic); + } catch (IndexOutOfBoundsException e) { + logDirect("Pleas provide a valid index."); + } + } else { + baritone.getBuilderProcess().buildOpenLitematic(); } } diff --git a/src/main/java/baritone/process/BuilderProcess.java b/src/main/java/baritone/process/BuilderProcess.java index 0ae796e6e..aeb396443 100644 --- a/src/main/java/baritone/process/BuilderProcess.java +++ b/src/main/java/baritone/process/BuilderProcess.java @@ -215,6 +215,20 @@ public void buildOpenSchematic() { } } + @Override + public void buildOpenLitematic() { + if (LitematicaHelper.isLitematicaPresent()) { + Integer selectedIndex = LitematicaHelper.getSelectedIndex(); + if (selectedIndex != -1) { + buildOpenLitematic(selectedIndex); + } else { + logDirect("No schematic currently selected"); + } + } else { + logDirect("Litematica is not present"); + } + } + @Override public void buildOpenLitematic(int i) { if (LitematicaHelper.isLitematicaPresent()) { @@ -532,7 +546,7 @@ public int lengthZ() { } Optional> toBreak = toBreakNearPlayer(bcc); - if (toBreak.isPresent() && isSafeToCancel && ctx.player().isOnGround()) { + 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().getB(); @@ -552,7 +566,7 @@ public int lengthZ() { } List desirableOnHotbar = new ArrayList<>(); Optional toPlace = searchForPlacables(bcc, desirableOnHotbar); - if (toPlace.isPresent() && isSafeToCancel && ctx.player().isOnGround() && ticks <= 0) { + if (toPlace.isPresent() && isSafeToCancel && ctx.player().onGround() && ticks <= 0) { Rotation rot = toPlace.get().rot; baritone.getLookBehavior().updateTarget(rot, true); ctx.player().getInventory().selected = toPlace.get().hotbarSelection; @@ -1063,9 +1077,6 @@ private boolean valid(BlockState current, BlockState desired, boolean itemVerify if (desired.getBlock() instanceof AirBlock && Baritone.settings().buildIgnoreBlocks.value.contains(current.getBlock())) { return true; } - if (!(current.getBlock() instanceof AirBlock) && Baritone.settings().buildIgnoreExisting.value && !itemVerify) { - return true; - } if (Baritone.settings().buildSkipBlocks.value.contains(desired.getBlock()) && !itemVerify) { return true; } diff --git a/src/main/java/baritone/utils/schematic/litematica/LitematicaHelper.java b/src/main/java/baritone/utils/schematic/litematica/LitematicaHelper.java index 3b05c23ef..3997a1316 100644 --- a/src/main/java/baritone/utils/schematic/litematica/LitematicaHelper.java +++ b/src/main/java/baritone/utils/schematic/litematica/LitematicaHelper.java @@ -54,6 +54,17 @@ public static boolean hasLoadedSchematic() { return DataManager.getSchematicPlacementManager().getAllSchematicsPlacements().size() > 0; } + /** + * @return the index of the currently selected schematic. -1 if none selected. + */ + public static Integer getSelectedIndex() { + try { + return DataManager.getSchematicPlacementManager().getAllSchematicsPlacements().indexOf(DataManager.getSchematicPlacementManager().getSelectedSchematicPlacement()); + } catch (NullPointerException e) { + return -1; + } + } + /** * @param i index of the Schematic in the schematic placement list. * @return the name of the requested schematic. diff --git a/src/schematica_api/java/fi/dy/masa/litematica/schematic/placement/SchematicPlacementManager.java b/src/schematica_api/java/fi/dy/masa/litematica/schematic/placement/SchematicPlacementManager.java index ab60e27fa..291224c65 100644 --- a/src/schematica_api/java/fi/dy/masa/litematica/schematic/placement/SchematicPlacementManager.java +++ b/src/schematica_api/java/fi/dy/masa/litematica/schematic/placement/SchematicPlacementManager.java @@ -19,13 +19,21 @@ import java.util.ArrayList; import java.util.List; +import javax.annotation.Nullable; public class SchematicPlacementManager { private final List schematicPlacements = new ArrayList<>(); + @Nullable + private SchematicPlacement selectedPlacement; + //in case of a java.lang.NoSuchMethodError try change the name of this method to getAllSchematicPlacements() //there are inconsistencies in the litematica mod about the naming of this method public List getAllSchematicsPlacements() { return schematicPlacements; } + + public SchematicPlacement getSelectedSchematicPlacement() { + return selectedPlacement; + } } \ No newline at end of file From 4ff617522738aca06c8cd90f6c974694af275b0c Mon Sep 17 00:00:00 2001 From: Thojo0 <53666000+thojo0@users.noreply.github.com> Date: Sun, 5 Nov 2023 14:45:53 +0000 Subject: [PATCH 2/3] fix builderprocess accidentally removing and changing things --- .../java/baritone/process/BuilderProcess.java | 2338 +++++++++-------- 1 file changed, 1171 insertions(+), 1167 deletions(-) diff --git a/src/main/java/baritone/process/BuilderProcess.java b/src/main/java/baritone/process/BuilderProcess.java index aeb396443..0f5f72008 100644 --- a/src/main/java/baritone/process/BuilderProcess.java +++ b/src/main/java/baritone/process/BuilderProcess.java @@ -15,1170 +15,1174 @@ * along with Baritone. If not, see . */ -package baritone.process; - -import baritone.Baritone; -import baritone.api.pathing.goals.Goal; -import baritone.api.pathing.goals.GoalBlock; -import baritone.api.pathing.goals.GoalComposite; -import baritone.api.pathing.goals.GoalGetToBlock; -import baritone.api.process.IBuilderProcess; -import baritone.api.process.PathingCommand; -import baritone.api.process.PathingCommandType; -import baritone.api.schematic.FillSchematic; -import baritone.api.schematic.ISchematic; -import baritone.api.schematic.IStaticSchematic; -import baritone.api.schematic.SubstituteSchematic; -import baritone.api.schematic.format.ISchematicFormat; -import baritone.api.utils.*; -import baritone.api.utils.input.Input; -import baritone.pathing.movement.CalculationContext; -import baritone.pathing.movement.Movement; -import baritone.pathing.movement.MovementHelper; -import baritone.utils.BaritoneProcessHelper; -import baritone.utils.BlockStateInterface; -import baritone.utils.PathingCommandContext; -import baritone.utils.schematic.MapArtSchematic; -import baritone.utils.schematic.SelectionSchematic; -import baritone.utils.schematic.SchematicSystem; -import baritone.utils.schematic.format.defaults.LitematicaSchematic; -import baritone.utils.schematic.litematica.LitematicaHelper; -import baritone.utils.schematic.schematica.SchematicaHelper; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.core.Vec3i; -import net.minecraft.nbt.NbtIo; -import net.minecraft.util.Tuple; -import net.minecraft.world.InteractionHand; -import net.minecraft.world.item.BlockItem; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.context.BlockPlaceContext; -import net.minecraft.world.item.context.UseOnContext; -import net.minecraft.world.level.block.AirBlock; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.HorizontalDirectionalBlock; -import net.minecraft.world.level.block.LiquidBlock; -import net.minecraft.world.level.block.PipeBlock; -import net.minecraft.world.level.block.RotatedPillarBlock; -import net.minecraft.world.level.block.StairBlock; -import net.minecraft.world.level.block.TrapDoorBlock; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.properties.Property; -import net.minecraft.world.phys.AABB; -import net.minecraft.world.phys.BlockHitResult; -import net.minecraft.world.phys.HitResult; -import net.minecraft.world.phys.Vec3; -import net.minecraft.world.phys.shapes.VoxelShape; - -import java.io.File; -import java.io.FileInputStream; -import java.nio.file.Files; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static baritone.api.pathing.movement.ActionCosts.COST_INF; - -public final class BuilderProcess extends BaritoneProcessHelper implements IBuilderProcess { - - private HashSet incorrectPositions; - private LongOpenHashSet observedCompleted; // positions that are completed even if they're out of render distance and we can't make sure right now - private String name; - private ISchematic realSchematic; - private ISchematic schematic; - private Vec3i origin; - private int ticks; - private boolean paused; - private int layer; - private int numRepeats; - private List approxPlaceable; - public int stopAtHeight = 0; - - public BuilderProcess(Baritone baritone) { - super(baritone); - } - - @Override - public void build(String name, ISchematic schematic, Vec3i origin) { - this.name = name; - this.schematic = schematic; - this.realSchematic = null; - boolean buildingSelectionSchematic = schematic instanceof SelectionSchematic; - if (!Baritone.settings().buildSubstitutes.value.isEmpty()) { - this.schematic = new SubstituteSchematic(this.schematic, Baritone.settings().buildSubstitutes.value); - } - int x = origin.getX(); - int y = origin.getY(); - int z = origin.getZ(); - if (Baritone.settings().schematicOrientationX.value) { - x += schematic.widthX(); - } - if (Baritone.settings().schematicOrientationY.value) { - y += schematic.heightY(); - } - if (Baritone.settings().schematicOrientationZ.value) { - z += schematic.lengthZ(); - } - this.origin = new Vec3i(x, y, z); - this.paused = false; - this.layer = Baritone.settings().startAtLayer.value; - this.stopAtHeight = schematic.heightY(); - if (Baritone.settings().buildOnlySelection.value && buildingSelectionSchematic) { // currently redundant but safer maybe - if (baritone.getSelectionManager().getSelections().length == 0) { - logDirect("Poor little kitten forgot to set a selection while BuildOnlySelection is true"); - this.stopAtHeight = 0; - } else if (Baritone.settings().buildInLayers.value) { - OptionalInt minim = Stream.of(baritone.getSelectionManager().getSelections()).mapToInt(sel -> sel.min().y).min(); - OptionalInt maxim = Stream.of(baritone.getSelectionManager().getSelections()).mapToInt(sel -> sel.max().y).max(); - if (minim.isPresent() && maxim.isPresent()) { - int startAtHeight = Baritone.settings().layerOrder.value ? y + schematic.heightY() - maxim.getAsInt() : minim.getAsInt() - y; - this.stopAtHeight = (Baritone.settings().layerOrder.value ? y + schematic.heightY() - minim.getAsInt() : maxim.getAsInt() - y) + 1; - this.layer = Math.max(this.layer, startAtHeight / Baritone.settings().layerHeight.value); // startAtLayer or startAtHeight, whichever is highest - logDebug(String.format("Schematic starts at y=%s with height %s", y, schematic.heightY())); - logDebug(String.format("Selection starts at y=%s and ends at y=%s", minim.getAsInt(), maxim.getAsInt())); - logDebug(String.format("Considering relevant height %s - %s", startAtHeight, this.stopAtHeight)); - } - } - } - - this.numRepeats = 0; - this.observedCompleted = new LongOpenHashSet(); - this.incorrectPositions = null; - } - - public void resume() { - paused = false; - } - - public void pause() { - paused = true; - } - - @Override - public boolean isPaused() { - return paused; - } - - @Override - public boolean build(String name, File schematic, Vec3i origin) { - Optional format = SchematicSystem.INSTANCE.getByFile(schematic); - if (!format.isPresent()) { - return false; - } - ISchematic parsed; - try { - parsed = format.get().parse(new FileInputStream(schematic)); - } catch (Exception e) { - e.printStackTrace(); - return false; - } - parsed = applyMapArtAndSelection(origin, (IStaticSchematic) parsed); - build(name, parsed, origin); - return true; - } - - private ISchematic applyMapArtAndSelection(Vec3i origin, IStaticSchematic parsed) { - ISchematic schematic = parsed; - if (Baritone.settings().mapArtMode.value) { - schematic = new MapArtSchematic(parsed); - } - if (Baritone.settings().buildOnlySelection.value) { - schematic = new SelectionSchematic(schematic, origin, baritone.getSelectionManager().getSelections()); - } - return schematic; - } - - @Override - public void buildOpenSchematic() { - if (SchematicaHelper.isSchematicaPresent()) { - Optional> schematic = SchematicaHelper.getOpenSchematic(); - if (schematic.isPresent()) { - IStaticSchematic s = schematic.get().getA(); - BlockPos origin = schematic.get().getB(); - ISchematic schem = Baritone.settings().mapArtMode.value ? new MapArtSchematic(s) : s; - if (Baritone.settings().buildOnlySelection.value) { - schem = new SelectionSchematic(schem, origin, baritone.getSelectionManager().getSelections()); - } - this.build( - schematic.get().getA().toString(), - schem, - origin - ); - } else { - logDirect("No schematic currently open"); - } - } else { - logDirect("Schematica is not present"); - } - } - - @Override - public void buildOpenLitematic() { - if (LitematicaHelper.isLitematicaPresent()) { - Integer selectedIndex = LitematicaHelper.getSelectedIndex(); - if (selectedIndex != -1) { - buildOpenLitematic(selectedIndex); - } else { - logDirect("No schematic currently selected"); - } - } else { - logDirect("Litematica is not present"); - } - } - - @Override - public void buildOpenLitematic(int i) { - if (LitematicaHelper.isLitematicaPresent()) { - //if java.lang.NoSuchMethodError is thrown see comment in SchematicPlacementManager - if (LitematicaHelper.hasLoadedSchematic()) { - String name = LitematicaHelper.getName(i); - try { - LitematicaSchematic schematic1 = new LitematicaSchematic(NbtIo.readCompressed(Files.newInputStream(LitematicaHelper.getSchematicFile(i).toPath())), false); - Vec3i correctedOrigin = LitematicaHelper.getCorrectedOrigin(schematic1, i); - ISchematic schematic2 = LitematicaHelper.blackMagicFuckery(schematic1, i); - schematic2 = applyMapArtAndSelection(origin, (IStaticSchematic) schematic2); - build(name, schematic2, correctedOrigin); - } catch (Exception e) { - logDirect("Schematic File could not be loaded."); - } - } else { - logDirect("No schematic currently loaded"); - } - } else { - logDirect("Litematica is not present"); - } - } - - public void clearArea(BlockPos corner1, BlockPos corner2) { - BlockPos origin = new BlockPos(Math.min(corner1.getX(), corner2.getX()), Math.min(corner1.getY(), corner2.getY()), Math.min(corner1.getZ(), corner2.getZ())); - int widthX = Math.abs(corner1.getX() - corner2.getX()) + 1; - int heightY = Math.abs(corner1.getY() - corner2.getY()) + 1; - int lengthZ = Math.abs(corner1.getZ() - corner2.getZ()) + 1; - build("clear area", new FillSchematic(widthX, heightY, lengthZ, Blocks.AIR.defaultBlockState()), origin); - } - - @Override - public List getApproxPlaceable() { - return new ArrayList<>(approxPlaceable); - } - - @Override - public boolean isActive() { - return schematic != null; - } - - public BlockState placeAt(int x, int y, int z, BlockState current) { - if (!isActive()) { - return null; - } - if (!schematic.inSchematic(x - origin.getX(), y - origin.getY(), z - origin.getZ(), current)) { - return null; - } - BlockState state = schematic.desiredState(x - origin.getX(), y - origin.getY(), z - origin.getZ(), current, this.approxPlaceable); - if (state.getBlock() instanceof AirBlock) { - return null; - } - return state; - } - - private Optional> toBreakNearPlayer(BuilderCalculationContext bcc) { - BetterBlockPos center = ctx.playerFeet(); - BetterBlockPos pathStart = baritone.getPathingBehavior().pathStart(); - for (int dx = -5; dx <= 5; dx++) { - for (int dy = Baritone.settings().breakFromAbove.value ? -1 : 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; - if (dy == -1 && x == pathStart.x && z == pathStart.z) { - continue; // dont mine what we're supported by, but not directly standing on - } - BlockState desired = bcc.getSchematic(x, y, z, bcc.bsi.get0(x, y, z)); - if (desired == null) { - continue; // irrelevant - } - BlockState curr = bcc.bsi.get0(x, y, z); - if (!(curr.getBlock() instanceof AirBlock) && !(curr.getBlock() == Blocks.WATER || curr.getBlock() == Blocks.LAVA) && !valid(curr, desired, false)) { - BetterBlockPos pos = new BetterBlockPos(x, y, z); - Optional rot = RotationUtils.reachable(ctx, pos, ctx.playerController().getBlockReachDistance()); - if (rot.isPresent()) { - return Optional.of(new Tuple<>(pos, rot.get())); - } - } - } - } - } - return Optional.empty(); - } - - public static class Placement { - - private final int hotbarSelection; - private final BlockPos placeAgainst; - private final Direction side; - private final Rotation rot; - - public Placement(int hotbarSelection, BlockPos placeAgainst, Direction side, Rotation rot) { - this.hotbarSelection = hotbarSelection; - this.placeAgainst = placeAgainst; - this.side = side; - this.rot = rot; - } - } - - private Optional searchForPlacables(BuilderCalculationContext bcc, List desirableOnHotbar) { - BetterBlockPos center = ctx.playerFeet(); - for (int dx = -5; dx <= 5; dx++) { - for (int dy = -5; dy <= 1; dy++) { - for (int dz = -5; dz <= 5; dz++) { - int x = center.x + dx; - int y = center.y + dy; - int z = center.z + dz; - BlockState desired = bcc.getSchematic(x, y, z, bcc.bsi.get0(x, y, z)); - if (desired == null) { - continue; // irrelevant - } - BlockState curr = bcc.bsi.get0(x, y, z); - if (MovementHelper.isReplaceable(x, y, z, curr, bcc.bsi) && !valid(curr, desired, false)) { - if (dy == 1 && bcc.bsi.get0(x, y + 1, z).getBlock() instanceof AirBlock) { - continue; - } - desirableOnHotbar.add(desired); - Optional opt = possibleToPlace(desired, x, y, z, bcc.bsi); - if (opt.isPresent()) { - return opt; - } - } - } - } - } - return Optional.empty(); - } - - public boolean placementPlausible(BlockPos pos, BlockState state) { - VoxelShape voxelshape = state.getCollisionShape(ctx.world(), pos); - return voxelshape.isEmpty() || ctx.world().isUnobstructed(null, voxelshape.move(pos.getX(), pos.getY(), pos.getZ())); - } - - private Optional possibleToPlace(BlockState toPlace, int x, int y, int z, BlockStateInterface bsi) { - for (Direction against : Direction.values()) { - BetterBlockPos placeAgainstPos = new BetterBlockPos(x, y, z).relative(against); - BlockState placeAgainstState = bsi.get0(placeAgainstPos); - if (MovementHelper.isReplaceable(placeAgainstPos.x, placeAgainstPos.y, placeAgainstPos.z, placeAgainstState, bsi)) { - continue; - } - if (!toPlace.canSurvive(ctx.world(), new BetterBlockPos(x, y, z))) { - continue; - } - if (!placementPlausible(new BetterBlockPos(x, y, z), toPlace)) { - continue; - } - AABB aabb = placeAgainstState.getShape(ctx.world(), placeAgainstPos).bounds(); - for (Vec3 placementMultiplier : aabbSideMultipliers(against)) { - double placeX = placeAgainstPos.x + aabb.minX * placementMultiplier.x + aabb.maxX * (1 - placementMultiplier.x); - double placeY = placeAgainstPos.y + aabb.minY * placementMultiplier.y + aabb.maxY * (1 - placementMultiplier.y); - double placeZ = placeAgainstPos.z + aabb.minZ * placementMultiplier.z + aabb.maxZ * (1 - placementMultiplier.z); - Rotation rot = RotationUtils.calcRotationFromVec3d(RayTraceUtils.inferSneakingEyePosition(ctx.player()), new Vec3(placeX, placeY, placeZ), ctx.playerRotations()); - Rotation actualRot = baritone.getLookBehavior().getAimProcessor().peekRotation(rot); - HitResult result = RayTraceUtils.rayTraceTowards(ctx.player(), actualRot, ctx.playerController().getBlockReachDistance(), true); - if (result != null && result.getType() == HitResult.Type.BLOCK && ((BlockHitResult) result).getBlockPos().equals(placeAgainstPos) && ((BlockHitResult) result).getDirection() == against.getOpposite()) { - OptionalInt hotbar = hasAnyItemThatWouldPlace(toPlace, result, actualRot); - if (hotbar.isPresent()) { - return Optional.of(new Placement(hotbar.getAsInt(), placeAgainstPos, against.getOpposite(), rot)); - } - } - } - } - return Optional.empty(); - } - - private OptionalInt hasAnyItemThatWouldPlace(BlockState desired, HitResult result, Rotation rot) { - for (int i = 0; i < 9; i++) { - ItemStack stack = ctx.player().getInventory().items.get(i); - if (stack.isEmpty() || !(stack.getItem() instanceof BlockItem)) { - continue; - } - float originalYaw = ctx.player().getYRot(); - float originalPitch = ctx.player().getXRot(); - // the state depends on the facing of the player sometimes - ctx.player().setYRot(rot.getYaw()); - ctx.player().setXRot(rot.getPitch()); - BlockPlaceContext meme = new BlockPlaceContext(new UseOnContext( - ctx.world(), - ctx.player(), - InteractionHand.MAIN_HAND, - stack, - (BlockHitResult) result - ) {}); // that {} gives us access to a protected constructor lmfao - BlockState wouldBePlaced = ((BlockItem) stack.getItem()).getBlock().getStateForPlacement(meme); - ctx.player().setYRot(originalYaw); - ctx.player().setXRot(originalPitch); - if (wouldBePlaced == null) { - continue; - } - if (!meme.canPlace()) { - continue; - } - if (valid(wouldBePlaced, desired, true)) { - return OptionalInt.of(i); - } - } - return OptionalInt.empty(); - } - - private static Vec3[] aabbSideMultipliers(Direction side) { - switch (side) { - case UP: - return new Vec3[]{new Vec3(0.5, 1, 0.5), new Vec3(0.1, 1, 0.5), new Vec3(0.9, 1, 0.5), new Vec3(0.5, 1, 0.1), new Vec3(0.5, 1, 0.9)}; - case DOWN: - return new Vec3[]{new Vec3(0.5, 0, 0.5), new Vec3(0.1, 0, 0.5), new Vec3(0.9, 0, 0.5), new Vec3(0.5, 0, 0.1), new Vec3(0.5, 0, 0.9)}; - case NORTH: - case SOUTH: - case EAST: - case WEST: - double x = side.getStepX() == 0 ? 0.5 : (1 + side.getStepX()) / 2D; - double z = side.getStepZ() == 0 ? 0.5 : (1 + side.getStepZ()) / 2D; - return new Vec3[]{new Vec3(x, 0.25, z), new Vec3(x, 0.75, z)}; - default: // null - throw new IllegalStateException(); - } - } - - @Override - public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { - return onTick(calcFailed, isSafeToCancel, 0); - } - - public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel, int recursions) { - if (recursions > 1000) { // onTick calls itself, don't crash - return new PathingCommand(null, PathingCommandType.SET_GOAL_AND_PATH); - } - approxPlaceable = approxPlaceable(36); - if (baritone.getInputOverrideHandler().isInputForcedDown(Input.CLICK_LEFT)) { - ticks = 5; - } else { - ticks--; - } - baritone.getInputOverrideHandler().clearAllKeys(); - if (paused) { - return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); - } - if (Baritone.settings().buildInLayers.value) { - if (realSchematic == null) { - realSchematic = schematic; - } - ISchematic realSchematic = this.realSchematic; // wrap this properly, dont just have the inner class refer to the builderprocess.this - int minYInclusive; - int maxYInclusive; - // layer = 0 should be nothing - // layer = realSchematic.heightY() should be everything - if (Baritone.settings().layerOrder.value) { // top to bottom - maxYInclusive = realSchematic.heightY() - 1; - minYInclusive = realSchematic.heightY() - layer * Baritone.settings().layerHeight.value; - } else { - maxYInclusive = layer * Baritone.settings().layerHeight.value - 1; - minYInclusive = 0; - } - schematic = new ISchematic() { - @Override - public BlockState desiredState(int x, int y, int z, BlockState current, List approxPlaceable) { - return realSchematic.desiredState(x, y, z, current, BuilderProcess.this.approxPlaceable); - } - - @Override - public boolean inSchematic(int x, int y, int z, BlockState currentState) { - return ISchematic.super.inSchematic(x, y, z, currentState) && y >= minYInclusive && y <= maxYInclusive && realSchematic.inSchematic(x, y, z, currentState); - } - - @Override - public void reset() { - realSchematic.reset(); - } - - @Override - public int widthX() { - return realSchematic.widthX(); - } - - @Override - public int heightY() { - return realSchematic.heightY(); - } - - @Override - public int lengthZ() { - return realSchematic.lengthZ(); - } - }; - } - BuilderCalculationContext bcc = new BuilderCalculationContext(); - if (!recalc(bcc)) { - if (Baritone.settings().buildInLayers.value && layer * Baritone.settings().layerHeight.value < stopAtHeight) { - logDirect("Starting layer " + layer); - layer++; - return onTick(calcFailed, isSafeToCancel, recursions + 1); - } - Vec3i repeat = Baritone.settings().buildRepeat.value; - int max = Baritone.settings().buildRepeatCount.value; - numRepeats++; - if (repeat.equals(new Vec3i(0, 0, 0)) || (max != -1 && numRepeats >= max)) { - logDirect("Done building"); - if (Baritone.settings().notificationOnBuildFinished.value) { - logNotification("Done building", false); - } - onLostControl(); - return null; - } - // build repeat time - layer = 0; - origin = new BlockPos(origin).offset(repeat); - if (!Baritone.settings().buildRepeatSneaky.value) { - schematic.reset(); - } - logDirect("Repeating build in vector " + repeat + ", new origin is " + origin); - return onTick(calcFailed, isSafeToCancel, recursions + 1); - } - if (Baritone.settings().distanceTrim.value) { - trim(); - } - - Optional> toBreak = toBreakNearPlayer(bcc); - 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().getB(); - BetterBlockPos pos = toBreak.get().getA(); - baritone.getLookBehavior().updateTarget(rot, true); - MovementHelper.switchToBestToolFor(ctx, bcc.get(pos)); - if (ctx.player().isCrouching()) { - // really horrible bug where a block is visible for breaking while sneaking but not otherwise - // so you can't see it, it goes to place something else, sneaks, then the next tick it tries to break - // and is unable since it's unsneaked in the intermediary tick - baritone.getInputOverrideHandler().setInputForceState(Input.SNEAK, true); - } - if (ctx.isLookingAt(pos) || ctx.playerRotations().isReallyCloseTo(rot)) { - baritone.getInputOverrideHandler().setInputForceState(Input.CLICK_LEFT, true); - } - return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); - } - List desirableOnHotbar = new ArrayList<>(); - Optional toPlace = searchForPlacables(bcc, desirableOnHotbar); - if (toPlace.isPresent() && isSafeToCancel && ctx.player().onGround() && ticks <= 0) { - Rotation rot = toPlace.get().rot; - baritone.getLookBehavior().updateTarget(rot, true); - ctx.player().getInventory().selected = toPlace.get().hotbarSelection; - baritone.getInputOverrideHandler().setInputForceState(Input.SNEAK, true); - if ((ctx.isLookingAt(toPlace.get().placeAgainst) && ((BlockHitResult) ctx.objectMouseOver()).getDirection().equals(toPlace.get().side)) || ctx.playerRotations().isReallyCloseTo(rot)) { - baritone.getInputOverrideHandler().setInputForceState(Input.CLICK_RIGHT, true); - } - return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); - } - - if (Baritone.settings().allowInventory.value) { - ArrayList usefulSlots = new ArrayList<>(); - List noValidHotbarOption = new ArrayList<>(); - outer: - for (BlockState desired : desirableOnHotbar) { - for (int i = 0; i < 9; i++) { - if (valid(approxPlaceable.get(i), desired, true)) { - usefulSlots.add(i); - continue outer; - } - } - noValidHotbarOption.add(desired); - } - - outer: - for (int i = 9; i < 36; i++) { - for (BlockState desired : noValidHotbarOption) { - if (valid(approxPlaceable.get(i), desired, true)) { - if (!baritone.getInventoryBehavior().attemptToPutOnHotbar(i, usefulSlots::contains)) { - // awaiting inventory move, so pause - return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE); - } - break outer; - } - } - } - } - - Goal goal = assemble(bcc, approxPlaceable.subList(0, 9)); - if (goal == null) { - goal = assemble(bcc, approxPlaceable, true); // we're far away, so assume that we have our whole inventory to recalculate placeable properly - if (goal == null) { - if (Baritone.settings().skipFailedLayers.value && Baritone.settings().buildInLayers.value && layer * Baritone.settings().layerHeight.value < realSchematic.heightY()) { - logDirect("Skipping layer that I cannot construct! Layer #" + layer); - layer++; - return onTick(calcFailed, isSafeToCancel, recursions + 1); - } - logDirect("Unable to do it. Pausing. resume to resume, cancel to cancel"); - paused = true; - return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE); - } - } - return new PathingCommandContext(goal, PathingCommandType.FORCE_REVALIDATE_GOAL_AND_PATH, bcc); - } - - private 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(); - } - - private void trim() { - HashSet copy = new HashSet<>(incorrectPositions); - copy.removeIf(pos -> pos.distSqr(ctx.player().blockPosition()) > 200); - if (!copy.isEmpty()) { - incorrectPositions = copy; - } - } - - private void recalcNearby(BuilderCalculationContext bcc) { - BetterBlockPos center = ctx.playerFeet(); - int radius = Baritone.settings().builderTickScanRadius.value; - for (int dx = -radius; dx <= radius; dx++) { - for (int dy = -radius; dy <= radius; dy++) { - for (int dz = -radius; dz <= radius; dz++) { - int x = center.x + dx; - int y = center.y + dy; - int z = center.z + dz; - BlockState desired = bcc.getSchematic(x, y, z, bcc.bsi.get0(x, y, z)); - if (desired != null) { - // we care about this position - BetterBlockPos pos = new BetterBlockPos(x, y, z); - if (valid(bcc.bsi.get0(x, y, z), desired, false)) { - incorrectPositions.remove(pos); - observedCompleted.add(BetterBlockPos.longHash(pos)); - } else { - incorrectPositions.add(pos); - observedCompleted.remove(BetterBlockPos.longHash(pos)); - } - } - } - } - } - } - - private 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++) { - int blockX = x + origin.getX(); - int blockY = y + origin.getY(); - int blockZ = z + origin.getZ(); - BlockState current = bcc.bsi.get0(blockX, blockY, blockZ); - if (!schematic.inSchematic(x, y, z, current)) { - continue; - } - if (bcc.bsi.worldContainsLoadedChunk(blockX, blockZ)) { // check if its in render distance, not if its in cache - // we can directly observe this block, it is in render distance - if (valid(bcc.bsi.get0(blockX, blockY, blockZ), schematic.desiredState(x, y, z, current, this.approxPlaceable), false)) { - observedCompleted.add(BetterBlockPos.longHash(blockX, blockY, blockZ)); - } else { - incorrectPositions.add(new BetterBlockPos(blockX, blockY, blockZ)); - observedCompleted.remove(BetterBlockPos.longHash(blockX, blockY, blockZ)); - if (incorrectPositions.size() > Baritone.settings().incorrectSize.value) { - return; - } - } - continue; - } - // this is not in render distance - if (!observedCompleted.contains(BetterBlockPos.longHash(blockX, blockY, blockZ)) - && !Baritone.settings().buildSkipBlocks.value.contains(schematic.desiredState(x, y, z, current, this.approxPlaceable).getBlock())) { - // and we've never seen this position be correct - // therefore mark as incorrect - incorrectPositions.add(new BetterBlockPos(blockX, blockY, blockZ)); - if (incorrectPositions.size() > Baritone.settings().incorrectSize.value) { - return; - } - } - } - } - } - } - - private Goal assemble(BuilderCalculationContext bcc, List approxPlaceable) { - return assemble(bcc, approxPlaceable, false); - } - - private Goal assemble(BuilderCalculationContext bcc, List approxPlaceable, boolean logMissing) { - List placeable = new ArrayList<>(); - List breakable = new ArrayList<>(); - List sourceLiquids = new ArrayList<>(); - List flowingLiquids = new ArrayList<>(); - Map missing = new HashMap<>(); - incorrectPositions.forEach(pos -> { - BlockState state = bcc.bsi.get0(pos); - if (state.getBlock() instanceof AirBlock) { - if (containsBlockState(approxPlaceable, bcc.getSchematic(pos.x, pos.y, pos.z, state))) { - placeable.add(pos); - } else { - BlockState desired = bcc.getSchematic(pos.x, pos.y, pos.z, state); - missing.put(desired, 1 + missing.getOrDefault(desired, 0)); - } - } else { - if (state.getBlock() instanceof LiquidBlock) { - // if the block itself is JUST a liquid (i.e. not just a waterlogged block), we CANNOT break it - // TODO for 1.13 make sure that this only matches pure water, not waterlogged blocks - if (!MovementHelper.possiblyFlowing(state)) { - // if it's a source block then we want to replace it with a throwaway - sourceLiquids.add(pos); - } else { - flowingLiquids.add(pos); - } - } else { - breakable.add(pos); - } - } - }); - List toBreak = new ArrayList<>(); - breakable.forEach(pos -> toBreak.add(breakGoal(pos, bcc))); - List toPlace = new ArrayList<>(); - placeable.forEach(pos -> { - if (!placeable.contains(pos.below()) && !placeable.contains(pos.below(2))) { - toPlace.add(placementGoal(pos, bcc)); - } - }); - sourceLiquids.forEach(pos -> toPlace.add(new GoalBlock(pos.above()))); - - if (!toPlace.isEmpty()) { - return new JankyGoalComposite(new GoalComposite(toPlace.toArray(new Goal[0])), new GoalComposite(toBreak.toArray(new Goal[0]))); - } - if (toBreak.isEmpty()) { - if (logMissing && !missing.isEmpty()) { - logDirect("Missing materials for at least:"); - logDirect(missing.entrySet().stream() - .map(e -> String.format("%sx %s", e.getValue(), e.getKey())) - .collect(Collectors.joining("\n"))); - } - if (logMissing && !flowingLiquids.isEmpty()) { - logDirect("Unreplaceable liquids at at least:"); - logDirect(flowingLiquids.stream() - .map(p -> String.format("%s %s %s", p.x, p.y, p.z)) - .collect(Collectors.joining("\n"))); - } - return null; - } - return new GoalComposite(toBreak.toArray(new Goal[0])); - } - - public static class JankyGoalComposite implements Goal { - - private final Goal primary; - private final Goal fallback; - - public JankyGoalComposite(Goal primary, Goal fallback) { - this.primary = primary; - this.fallback = fallback; - } - - - @Override - public boolean isInGoal(int x, int y, int z) { - return primary.isInGoal(x, y, z) || fallback.isInGoal(x, y, z); - } - - @Override - public double heuristic(int x, int y, int z) { - return primary.heuristic(x, y, z); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - JankyGoalComposite goal = (JankyGoalComposite) o; - return Objects.equals(primary, goal.primary) - && Objects.equals(fallback, goal.fallback); - } - - @Override - public int hashCode() { - int hash = -1701079641; - hash = hash * 1196141026 + primary.hashCode(); - hash = hash * -80327868 + fallback.hashCode(); - return hash; - } - - @Override - public String toString() { - return "JankyComposite Primary: " + primary + " Fallback: " + fallback; - } - } - - 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 (y > this.y) { - return false; - } - // but any other adjacent works for breaking, including inside or below - return super.isInGoal(x, y, z); - } - - @Override - public String toString() { - return String.format( - "GoalBreak{x=%s,y=%s,z=%s}", - SettingsUtil.maybeCensor(x), - SettingsUtil.maybeCensor(y), - SettingsUtil.maybeCensor(z) - ); - } - - @Override - public int hashCode() { - return super.hashCode() * 1636324008; - } - } - - private Goal placementGoal(BlockPos pos, BuilderCalculationContext bcc) { - if (!(ctx.world().getBlockState(pos).getBlock() instanceof AirBlock)) { // TODO can this even happen? - return new GoalPlace(pos); - } - boolean allowSameLevel = !(ctx.world().getBlockState(pos.above()).getBlock() instanceof AirBlock); - BlockState current = ctx.world().getBlockState(pos); - for (Direction facing : Movement.HORIZONTALS_BUT_ALSO_DOWN_____SO_EVERY_DIRECTION_EXCEPT_UP) { - //noinspection ConstantConditions - if (MovementHelper.canPlaceAgainst(ctx, pos.relative(facing)) && placementPlausible(pos, bcc.getSchematic(pos.getX(), pos.getY(), pos.getZ(), current))) { - return new GoalAdjacent(pos, pos.relative(facing), allowSameLevel); - } - } - return new GoalPlace(pos); - } - - private Goal breakGoal(BlockPos pos, BuilderCalculationContext bcc) { - if (Baritone.settings().goalBreakFromAbove.value && bcc.bsi.get0(pos.above()).getBlock() instanceof AirBlock && bcc.bsi.get0(pos.above(2)).getBlock() instanceof AirBlock) { // TODO maybe possible without the up(2) check? - return new JankyGoalComposite(new GoalBreak(pos), new GoalGetToBlock(pos.above()) { - @Override - public boolean isInGoal(int x, int y, int z) { - if (y > this.y || (x == this.x && y == this.y && z == this.z)) { - return false; - } - return super.isInGoal(x, y, z); - } - }); - } - return new GoalBreak(pos); - } - - public static class GoalAdjacent extends GoalGetToBlock { - - private boolean allowSameLevel; - private BlockPos no; - - public GoalAdjacent(BlockPos pos, BlockPos no, boolean allowSameLevel) { - super(pos); - this.no = no; - this.allowSameLevel = allowSameLevel; - } - - @Override - public boolean isInGoal(int x, int y, int z) { - if (x == this.x && y == this.y && z == this.z) { - return false; - } - if (x == no.getX() && y == no.getY() && z == no.getZ()) { - return false; - } - if (!allowSameLevel && y == this.y - 1) { - return false; - } - if (y < this.y - 1) { - return false; - } - return super.isInGoal(x, y, z); - } - - @Override - public double heuristic(int x, int y, int z) { - // prioritize lower y coordinates - return this.y * 100 + super.heuristic(x, y, z); - } - - @Override - public boolean equals(Object o) { - if (!super.equals(o)) { - return false; - } - - GoalAdjacent goal = (GoalAdjacent) o; - return allowSameLevel == goal.allowSameLevel - && Objects.equals(no, goal.no); - } - - @Override - public int hashCode() { - int hash = 806368046; - hash = hash * 1412661222 + super.hashCode(); - hash = hash * 1730799370 + (int) BetterBlockPos.longHash(no.getX(), no.getY(), no.getZ()); - hash = hash * 260592149 + (allowSameLevel ? -1314802005 : 1565710265); - return hash; - } - - @Override - public String toString() { - return String.format( - "GoalAdjacent{x=%s,y=%s,z=%s}", - SettingsUtil.maybeCensor(x), - SettingsUtil.maybeCensor(y), - SettingsUtil.maybeCensor(z) - ); - } - } - - public static class GoalPlace extends GoalBlock { - - public GoalPlace(BlockPos placeAt) { - super(placeAt.above()); - } - - @Override - public double heuristic(int x, int y, int z) { - // prioritize lower y coordinates - return this.y * 100 + super.heuristic(x, y, z); - } - - @Override - public int hashCode() { - return super.hashCode() * 1910811835; - } - - @Override - public String toString() { - return String.format( - "GoalPlace{x=%s,y=%s,z=%s}", - SettingsUtil.maybeCensor(x), - SettingsUtil.maybeCensor(y), - SettingsUtil.maybeCensor(z) - ); - } - } - - @Override - public void onLostControl() { - incorrectPositions = null; - name = null; - schematic = null; - realSchematic = null; - layer = Baritone.settings().startAtLayer.value; - numRepeats = 0; - paused = false; - observedCompleted = null; - } - - @Override - public String displayName0() { - return paused ? "Builder Paused" : "Building " + name; - } - - private List approxPlaceable(int size) { - List result = new ArrayList<>(); - for (int i = 0; i < size; i++) { - ItemStack stack = ctx.player().getInventory().items.get(i); - if (stack.isEmpty() || !(stack.getItem() instanceof BlockItem)) { - result.add(Blocks.AIR.defaultBlockState()); - continue; - } - // - BlockState itemState = ((BlockItem) stack.getItem()) - .getBlock() - .getStateForPlacement( - new BlockPlaceContext( - new UseOnContext(ctx.world(), ctx.player(), InteractionHand.MAIN_HAND, stack, new BlockHitResult(new Vec3(ctx.player().position().x, ctx.player().position().y, ctx.player().position().z), Direction.UP, ctx.playerFeet(), false)) {} - ) - ); - if (itemState != null) { - result.add(itemState); - } else { - result.add(Blocks.AIR.defaultBlockState()); - } - // - } - return result; - } - - public static final Set> orientationProps = - ImmutableSet.of( - RotatedPillarBlock.AXIS, HorizontalDirectionalBlock.FACING, - StairBlock.FACING, StairBlock.HALF, StairBlock.SHAPE, - PipeBlock.NORTH, PipeBlock.EAST, PipeBlock.SOUTH, PipeBlock.WEST, PipeBlock.UP, - TrapDoorBlock.OPEN, TrapDoorBlock.HALF - ); - - private boolean sameBlockstate(BlockState first, BlockState second) { - if (first.getBlock() != second.getBlock()) { - return false; - } - boolean ignoreDirection = Baritone.settings().buildIgnoreDirection.value; - List ignoredProps = Baritone.settings().buildIgnoreProperties.value; - if (!ignoreDirection && ignoredProps.isEmpty()) { - return first.equals(second); // early return if no properties are being ignored - } - ImmutableMap, Comparable> map1 = first.getValues(); - ImmutableMap, Comparable> map2 = second.getValues(); - for (Property prop : map1.keySet()) { - if (map1.get(prop) != map2.get(prop) - && !(ignoreDirection && orientationProps.contains(prop)) - && !ignoredProps.contains(prop.getName())) { - return false; - } - } - return true; - } - - private boolean containsBlockState(Collection states, BlockState state) { - for (BlockState testee : states) { - if (sameBlockstate(testee, state)) { - return true; - } - } - return false; - } - - private boolean valid(BlockState current, BlockState desired, boolean itemVerify) { - if (desired == null) { - return true; - } - if (current.getBlock() instanceof LiquidBlock && Baritone.settings().okIfWater.value) { - return true; - } - if (current.getBlock() instanceof AirBlock && desired.getBlock() instanceof AirBlock) { - return true; - } - if (current.getBlock() instanceof AirBlock && Baritone.settings().okIfAir.value.contains(desired.getBlock())) { - return true; - } - if (desired.getBlock() instanceof AirBlock && Baritone.settings().buildIgnoreBlocks.value.contains(current.getBlock())) { - return true; - } - if (Baritone.settings().buildSkipBlocks.value.contains(desired.getBlock()) && !itemVerify) { - return true; - } - if (Baritone.settings().buildValidSubstitutes.value.getOrDefault(desired.getBlock(), Collections.emptyList()).contains(current.getBlock()) && !itemVerify) { - return true; - } - if (current.equals(desired)) { - return true; - } - return sameBlockstate(current, desired); - } - - public class BuilderCalculationContext extends CalculationContext { - - private final List placeable; - private final ISchematic schematic; - private final int originX; - private final int originY; - private final int originZ; - - public BuilderCalculationContext() { - super(BuilderProcess.this.baritone, true); // wew lad - this.placeable = approxPlaceable(9); - this.schematic = BuilderProcess.this.schematic; - this.originX = origin.getX(); - this.originY = origin.getY(); - this.originZ = origin.getZ(); - - this.jumpPenalty += 10; - this.backtrackCostFavoringCoefficient = 1; - } - - private BlockState getSchematic(int x, int y, int z, BlockState current) { - if (schematic.inSchematic(x - originX, y - originY, z - originZ, current)) { - return schematic.desiredState(x - originX, y - originY, z - originZ, current, BuilderProcess.this.approxPlaceable); - } else { - return null; - } - } - - @Override - public double costOfPlacingAt(int x, int y, int z, BlockState current) { - if (isPossiblyProtected(x, y, z) || !worldBorder.canPlaceAt(x, z)) { // make calculation fail properly if we can't build - return COST_INF; - } - BlockState sch = getSchematic(x, y, z, current); - if (sch != null && !Baritone.settings().buildSkipBlocks.value.contains(sch.getBlock())) { - // TODO this can return true even when allowPlace is off.... is that an issue? - if (sch.getBlock() instanceof AirBlock) { - // 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 (placeable.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 😎 - // i'm such an idiot that i just tried to copy and paste the epic gamer moment emoji too - // get added to unicode when? - } - if (!hasThrowaway) { - return COST_INF; - } - // 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; - } else { - return COST_INF; - } - } - } - - @Override - public double breakCostMultiplierAt(int x, int y, int z, BlockState current) { - if ((!allowBreak && !allowBreakAnyway.contains(current.getBlock())) || isPossiblyProtected(x, y, z)) { - return COST_INF; - } - BlockState sch = getSchematic(x, y, z, current); - if (sch != null && !Baritone.settings().buildSkipBlocks.value.contains(sch.getBlock())) { - if (sch.getBlock() instanceof AirBlock) { - // it should be air - // regardless of current contents, we can break it - return 1; - } - // it should be a real block - // is it already that block? - if (valid(bsi.get0(x, y, z), sch, false)) { - return Baritone.settings().breakCorrectBlockPenaltyMultiplier.value; - } 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 1; // why not lol - } - } - } -} + package baritone.process; + + import baritone.Baritone; + import baritone.api.pathing.goals.Goal; + import baritone.api.pathing.goals.GoalBlock; + import baritone.api.pathing.goals.GoalComposite; + import baritone.api.pathing.goals.GoalGetToBlock; + import baritone.api.process.IBuilderProcess; + import baritone.api.process.PathingCommand; + import baritone.api.process.PathingCommandType; + import baritone.api.schematic.FillSchematic; + import baritone.api.schematic.ISchematic; + import baritone.api.schematic.IStaticSchematic; + import baritone.api.schematic.SubstituteSchematic; + import baritone.api.schematic.format.ISchematicFormat; + import baritone.api.utils.*; + import baritone.api.utils.input.Input; + import baritone.pathing.movement.CalculationContext; + import baritone.pathing.movement.Movement; + import baritone.pathing.movement.MovementHelper; + import baritone.utils.BaritoneProcessHelper; + import baritone.utils.BlockStateInterface; + import baritone.utils.PathingCommandContext; + import baritone.utils.schematic.MapArtSchematic; + import baritone.utils.schematic.SelectionSchematic; + import baritone.utils.schematic.SchematicSystem; + import baritone.utils.schematic.format.defaults.LitematicaSchematic; + import baritone.utils.schematic.litematica.LitematicaHelper; + import baritone.utils.schematic.schematica.SchematicaHelper; + import com.google.common.collect.ImmutableMap; + import com.google.common.collect.ImmutableSet; + import it.unimi.dsi.fastutil.longs.LongOpenHashSet; + import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; + import net.minecraft.core.Vec3i; + import net.minecraft.nbt.NbtIo; + import net.minecraft.util.Tuple; + import net.minecraft.world.InteractionHand; + import net.minecraft.world.item.BlockItem; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.context.BlockPlaceContext; + import net.minecraft.world.item.context.UseOnContext; + import net.minecraft.world.level.block.AirBlock; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.HorizontalDirectionalBlock; + import net.minecraft.world.level.block.LiquidBlock; + import net.minecraft.world.level.block.PipeBlock; + import net.minecraft.world.level.block.RotatedPillarBlock; + import net.minecraft.world.level.block.StairBlock; + import net.minecraft.world.level.block.TrapDoorBlock; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.block.state.properties.Property; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.HitResult; + import net.minecraft.world.phys.Vec3; + import net.minecraft.world.phys.shapes.VoxelShape; + + import java.io.File; + import java.io.FileInputStream; + import java.nio.file.Files; + import java.util.*; + import java.util.stream.Collectors; + import java.util.stream.Stream; + + import static baritone.api.pathing.movement.ActionCosts.COST_INF; + + public final class BuilderProcess extends BaritoneProcessHelper implements IBuilderProcess { + + private HashSet incorrectPositions; + private LongOpenHashSet observedCompleted; // positions that are completed even if they're out of render distance and we can't make sure right now + private String name; + private ISchematic realSchematic; + private ISchematic schematic; + private Vec3i origin; + private int ticks; + private boolean paused; + private int layer; + private int numRepeats; + private List approxPlaceable; + public int stopAtHeight = 0; + + public BuilderProcess(Baritone baritone) { + super(baritone); + } + + @Override + public void build(String name, ISchematic schematic, Vec3i origin) { + this.name = name; + this.schematic = schematic; + this.realSchematic = null; + boolean buildingSelectionSchematic = schematic instanceof SelectionSchematic; + if (!Baritone.settings().buildSubstitutes.value.isEmpty()) { + this.schematic = new SubstituteSchematic(this.schematic, Baritone.settings().buildSubstitutes.value); + } + int x = origin.getX(); + int y = origin.getY(); + int z = origin.getZ(); + if (Baritone.settings().schematicOrientationX.value) { + x += schematic.widthX(); + } + if (Baritone.settings().schematicOrientationY.value) { + y += schematic.heightY(); + } + if (Baritone.settings().schematicOrientationZ.value) { + z += schematic.lengthZ(); + } + this.origin = new Vec3i(x, y, z); + this.paused = false; + this.layer = Baritone.settings().startAtLayer.value; + this.stopAtHeight = schematic.heightY(); + if (Baritone.settings().buildOnlySelection.value && buildingSelectionSchematic) { // currently redundant but safer maybe + if (baritone.getSelectionManager().getSelections().length == 0) { + logDirect("Poor little kitten forgot to set a selection while BuildOnlySelection is true"); + this.stopAtHeight = 0; + } else if (Baritone.settings().buildInLayers.value) { + OptionalInt minim = Stream.of(baritone.getSelectionManager().getSelections()).mapToInt(sel -> sel.min().y).min(); + OptionalInt maxim = Stream.of(baritone.getSelectionManager().getSelections()).mapToInt(sel -> sel.max().y).max(); + if (minim.isPresent() && maxim.isPresent()) { + int startAtHeight = Baritone.settings().layerOrder.value ? y + schematic.heightY() - maxim.getAsInt() : minim.getAsInt() - y; + this.stopAtHeight = (Baritone.settings().layerOrder.value ? y + schematic.heightY() - minim.getAsInt() : maxim.getAsInt() - y) + 1; + this.layer = Math.max(this.layer, startAtHeight / Baritone.settings().layerHeight.value); // startAtLayer or startAtHeight, whichever is highest + logDebug(String.format("Schematic starts at y=%s with height %s", y, schematic.heightY())); + logDebug(String.format("Selection starts at y=%s and ends at y=%s", minim.getAsInt(), maxim.getAsInt())); + logDebug(String.format("Considering relevant height %s - %s", startAtHeight, this.stopAtHeight)); + } + } + } + + this.numRepeats = 0; + this.observedCompleted = new LongOpenHashSet(); + this.incorrectPositions = null; + } + + public void resume() { + paused = false; + } + + public void pause() { + paused = true; + } + + @Override + public boolean isPaused() { + return paused; + } + + @Override + public boolean build(String name, File schematic, Vec3i origin) { + Optional format = SchematicSystem.INSTANCE.getByFile(schematic); + if (!format.isPresent()) { + return false; + } + ISchematic parsed; + try { + parsed = format.get().parse(new FileInputStream(schematic)); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + parsed = applyMapArtAndSelection(origin, (IStaticSchematic) parsed); + build(name, parsed, origin); + return true; + } + + private ISchematic applyMapArtAndSelection(Vec3i origin, IStaticSchematic parsed) { + ISchematic schematic = parsed; + if (Baritone.settings().mapArtMode.value) { + schematic = new MapArtSchematic(parsed); + } + if (Baritone.settings().buildOnlySelection.value) { + schematic = new SelectionSchematic(schematic, origin, baritone.getSelectionManager().getSelections()); + } + return schematic; + } + + @Override + public void buildOpenSchematic() { + if (SchematicaHelper.isSchematicaPresent()) { + Optional> schematic = SchematicaHelper.getOpenSchematic(); + if (schematic.isPresent()) { + IStaticSchematic s = schematic.get().getA(); + BlockPos origin = schematic.get().getB(); + ISchematic schem = Baritone.settings().mapArtMode.value ? new MapArtSchematic(s) : s; + if (Baritone.settings().buildOnlySelection.value) { + schem = new SelectionSchematic(schem, origin, baritone.getSelectionManager().getSelections()); + } + this.build( + schematic.get().getA().toString(), + schem, + origin + ); + } else { + logDirect("No schematic currently open"); + } + } else { + logDirect("Schematica is not present"); + } + } + + @Override + public void buildOpenLitematic() { + if (LitematicaHelper.isLitematicaPresent()) { + Integer selectedIndex = LitematicaHelper.getSelectedIndex(); + if (selectedIndex != -1) { + buildOpenLitematic(selectedIndex); + } else { + logDirect("No schematic currently selected"); + } + } else { + logDirect("Litematica is not present"); + } + } + + @Override + public void buildOpenLitematic(int i) { + if (LitematicaHelper.isLitematicaPresent()) { + //if java.lang.NoSuchMethodError is thrown see comment in SchematicPlacementManager + if (LitematicaHelper.hasLoadedSchematic()) { + String name = LitematicaHelper.getName(i); + try { + LitematicaSchematic schematic1 = new LitematicaSchematic(NbtIo.readCompressed(Files.newInputStream(LitematicaHelper.getSchematicFile(i).toPath())), false); + Vec3i correctedOrigin = LitematicaHelper.getCorrectedOrigin(schematic1, i); + ISchematic schematic2 = LitematicaHelper.blackMagicFuckery(schematic1, i); + schematic2 = applyMapArtAndSelection(origin, (IStaticSchematic) schematic2); + build(name, schematic2, correctedOrigin); + } catch (Exception e) { + logDirect("Schematic File could not be loaded."); + } + } else { + logDirect("No schematic currently loaded"); + } + } else { + logDirect("Litematica is not present"); + } + } + + public void clearArea(BlockPos corner1, BlockPos corner2) { + BlockPos origin = new BlockPos(Math.min(corner1.getX(), corner2.getX()), Math.min(corner1.getY(), corner2.getY()), Math.min(corner1.getZ(), corner2.getZ())); + int widthX = Math.abs(corner1.getX() - corner2.getX()) + 1; + int heightY = Math.abs(corner1.getY() - corner2.getY()) + 1; + int lengthZ = Math.abs(corner1.getZ() - corner2.getZ()) + 1; + build("clear area", new FillSchematic(widthX, heightY, lengthZ, Blocks.AIR.defaultBlockState()), origin); + } + + @Override + public List getApproxPlaceable() { + return new ArrayList<>(approxPlaceable); + } + + @Override + public boolean isActive() { + return schematic != null; + } + + public BlockState placeAt(int x, int y, int z, BlockState current) { + if (!isActive()) { + return null; + } + if (!schematic.inSchematic(x - origin.getX(), y - origin.getY(), z - origin.getZ(), current)) { + return null; + } + BlockState state = schematic.desiredState(x - origin.getX(), y - origin.getY(), z - origin.getZ(), current, this.approxPlaceable); + if (state.getBlock() instanceof AirBlock) { + return null; + } + return state; + } + + private Optional> toBreakNearPlayer(BuilderCalculationContext bcc) { + BetterBlockPos center = ctx.playerFeet(); + BetterBlockPos pathStart = baritone.getPathingBehavior().pathStart(); + for (int dx = -5; dx <= 5; dx++) { + for (int dy = Baritone.settings().breakFromAbove.value ? -1 : 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; + if (dy == -1 && x == pathStart.x && z == pathStart.z) { + continue; // dont mine what we're supported by, but not directly standing on + } + BlockState desired = bcc.getSchematic(x, y, z, bcc.bsi.get0(x, y, z)); + if (desired == null) { + continue; // irrelevant + } + BlockState curr = bcc.bsi.get0(x, y, z); + if (!(curr.getBlock() instanceof AirBlock) && !(curr.getBlock() == Blocks.WATER || curr.getBlock() == Blocks.LAVA) && !valid(curr, desired, false)) { + BetterBlockPos pos = new BetterBlockPos(x, y, z); + Optional rot = RotationUtils.reachable(ctx, pos, ctx.playerController().getBlockReachDistance()); + if (rot.isPresent()) { + return Optional.of(new Tuple<>(pos, rot.get())); + } + } + } + } + } + return Optional.empty(); + } + + public static class Placement { + + private final int hotbarSelection; + private final BlockPos placeAgainst; + private final Direction side; + private final Rotation rot; + + public Placement(int hotbarSelection, BlockPos placeAgainst, Direction side, Rotation rot) { + this.hotbarSelection = hotbarSelection; + this.placeAgainst = placeAgainst; + this.side = side; + this.rot = rot; + } + } + + private Optional searchForPlacables(BuilderCalculationContext bcc, List desirableOnHotbar) { + BetterBlockPos center = ctx.playerFeet(); + for (int dx = -5; dx <= 5; dx++) { + for (int dy = -5; dy <= 1; dy++) { + for (int dz = -5; dz <= 5; dz++) { + int x = center.x + dx; + int y = center.y + dy; + int z = center.z + dz; + BlockState desired = bcc.getSchematic(x, y, z, bcc.bsi.get0(x, y, z)); + if (desired == null) { + continue; // irrelevant + } + BlockState curr = bcc.bsi.get0(x, y, z); + if (MovementHelper.isReplaceable(x, y, z, curr, bcc.bsi) && !valid(curr, desired, false)) { + if (dy == 1 && bcc.bsi.get0(x, y + 1, z).getBlock() instanceof AirBlock) { + continue; + } + desirableOnHotbar.add(desired); + Optional opt = possibleToPlace(desired, x, y, z, bcc.bsi); + if (opt.isPresent()) { + return opt; + } + } + } + } + } + return Optional.empty(); + } + + public boolean placementPlausible(BlockPos pos, BlockState state) { + VoxelShape voxelshape = state.getCollisionShape(ctx.world(), pos); + return voxelshape.isEmpty() || ctx.world().isUnobstructed(null, voxelshape.move(pos.getX(), pos.getY(), pos.getZ())); + } + + private Optional possibleToPlace(BlockState toPlace, int x, int y, int z, BlockStateInterface bsi) { + for (Direction against : Direction.values()) { + BetterBlockPos placeAgainstPos = new BetterBlockPos(x, y, z).relative(against); + BlockState placeAgainstState = bsi.get0(placeAgainstPos); + if (MovementHelper.isReplaceable(placeAgainstPos.x, placeAgainstPos.y, placeAgainstPos.z, placeAgainstState, bsi)) { + continue; + } + if (!toPlace.canSurvive(ctx.world(), new BetterBlockPos(x, y, z))) { + continue; + } + if (!placementPlausible(new BetterBlockPos(x, y, z), toPlace)) { + continue; + } + AABB aabb = placeAgainstState.getShape(ctx.world(), placeAgainstPos).bounds(); + for (Vec3 placementMultiplier : aabbSideMultipliers(against)) { + double placeX = placeAgainstPos.x + aabb.minX * placementMultiplier.x + aabb.maxX * (1 - placementMultiplier.x); + double placeY = placeAgainstPos.y + aabb.minY * placementMultiplier.y + aabb.maxY * (1 - placementMultiplier.y); + double placeZ = placeAgainstPos.z + aabb.minZ * placementMultiplier.z + aabb.maxZ * (1 - placementMultiplier.z); + Rotation rot = RotationUtils.calcRotationFromVec3d(RayTraceUtils.inferSneakingEyePosition(ctx.player()), new Vec3(placeX, placeY, placeZ), ctx.playerRotations()); + Rotation actualRot = baritone.getLookBehavior().getAimProcessor().peekRotation(rot); + HitResult result = RayTraceUtils.rayTraceTowards(ctx.player(), actualRot, ctx.playerController().getBlockReachDistance(), true); + if (result != null && result.getType() == HitResult.Type.BLOCK && ((BlockHitResult) result).getBlockPos().equals(placeAgainstPos) && ((BlockHitResult) result).getDirection() == against.getOpposite()) { + OptionalInt hotbar = hasAnyItemThatWouldPlace(toPlace, result, actualRot); + if (hotbar.isPresent()) { + return Optional.of(new Placement(hotbar.getAsInt(), placeAgainstPos, against.getOpposite(), rot)); + } + } + } + } + return Optional.empty(); + } + + private OptionalInt hasAnyItemThatWouldPlace(BlockState desired, HitResult result, Rotation rot) { + for (int i = 0; i < 9; i++) { + ItemStack stack = ctx.player().getInventory().items.get(i); + if (stack.isEmpty() || !(stack.getItem() instanceof BlockItem)) { + continue; + } + float originalYaw = ctx.player().getYRot(); + float originalPitch = ctx.player().getXRot(); + // the state depends on the facing of the player sometimes + ctx.player().setYRot(rot.getYaw()); + ctx.player().setXRot(rot.getPitch()); + BlockPlaceContext meme = new BlockPlaceContext(new UseOnContext( + ctx.world(), + ctx.player(), + InteractionHand.MAIN_HAND, + stack, + (BlockHitResult) result + ) {}); // that {} gives us access to a protected constructor lmfao + BlockState wouldBePlaced = ((BlockItem) stack.getItem()).getBlock().getStateForPlacement(meme); + ctx.player().setYRot(originalYaw); + ctx.player().setXRot(originalPitch); + if (wouldBePlaced == null) { + continue; + } + if (!meme.canPlace()) { + continue; + } + if (valid(wouldBePlaced, desired, true)) { + return OptionalInt.of(i); + } + } + return OptionalInt.empty(); + } + + private static Vec3[] aabbSideMultipliers(Direction side) { + switch (side) { + case UP: + return new Vec3[]{new Vec3(0.5, 1, 0.5), new Vec3(0.1, 1, 0.5), new Vec3(0.9, 1, 0.5), new Vec3(0.5, 1, 0.1), new Vec3(0.5, 1, 0.9)}; + case DOWN: + return new Vec3[]{new Vec3(0.5, 0, 0.5), new Vec3(0.1, 0, 0.5), new Vec3(0.9, 0, 0.5), new Vec3(0.5, 0, 0.1), new Vec3(0.5, 0, 0.9)}; + case NORTH: + case SOUTH: + case EAST: + case WEST: + double x = side.getStepX() == 0 ? 0.5 : (1 + side.getStepX()) / 2D; + double z = side.getStepZ() == 0 ? 0.5 : (1 + side.getStepZ()) / 2D; + return new Vec3[]{new Vec3(x, 0.25, z), new Vec3(x, 0.75, z)}; + default: // null + throw new IllegalStateException(); + } + } + + @Override + public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { + return onTick(calcFailed, isSafeToCancel, 0); + } + + public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel, int recursions) { + if (recursions > 1000) { // onTick calls itself, don't crash + return new PathingCommand(null, PathingCommandType.SET_GOAL_AND_PATH); + } + approxPlaceable = approxPlaceable(36); + if (baritone.getInputOverrideHandler().isInputForcedDown(Input.CLICK_LEFT)) { + ticks = 5; + } else { + ticks--; + } + baritone.getInputOverrideHandler().clearAllKeys(); + if (paused) { + return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); + } + if (Baritone.settings().buildInLayers.value) { + if (realSchematic == null) { + realSchematic = schematic; + } + ISchematic realSchematic = this.realSchematic; // wrap this properly, dont just have the inner class refer to the builderprocess.this + int minYInclusive; + int maxYInclusive; + // layer = 0 should be nothing + // layer = realSchematic.heightY() should be everything + if (Baritone.settings().layerOrder.value) { // top to bottom + maxYInclusive = realSchematic.heightY() - 1; + minYInclusive = realSchematic.heightY() - layer * Baritone.settings().layerHeight.value; + } else { + maxYInclusive = layer * Baritone.settings().layerHeight.value - 1; + minYInclusive = 0; + } + schematic = new ISchematic() { + @Override + public BlockState desiredState(int x, int y, int z, BlockState current, List approxPlaceable) { + return realSchematic.desiredState(x, y, z, current, BuilderProcess.this.approxPlaceable); + } + + @Override + public boolean inSchematic(int x, int y, int z, BlockState currentState) { + return ISchematic.super.inSchematic(x, y, z, currentState) && y >= minYInclusive && y <= maxYInclusive && realSchematic.inSchematic(x, y, z, currentState); + } + + @Override + public void reset() { + realSchematic.reset(); + } + + @Override + public int widthX() { + return realSchematic.widthX(); + } + + @Override + public int heightY() { + return realSchematic.heightY(); + } + + @Override + public int lengthZ() { + return realSchematic.lengthZ(); + } + }; + } + BuilderCalculationContext bcc = new BuilderCalculationContext(); + if (!recalc(bcc)) { + if (Baritone.settings().buildInLayers.value && layer * Baritone.settings().layerHeight.value < stopAtHeight) { + logDirect("Starting layer " + layer); + layer++; + return onTick(calcFailed, isSafeToCancel, recursions + 1); + } + Vec3i repeat = Baritone.settings().buildRepeat.value; + int max = Baritone.settings().buildRepeatCount.value; + numRepeats++; + if (repeat.equals(new Vec3i(0, 0, 0)) || (max != -1 && numRepeats >= max)) { + logDirect("Done building"); + if (Baritone.settings().notificationOnBuildFinished.value) { + logNotification("Done building", false); + } + onLostControl(); + return null; + } + // build repeat time + layer = 0; + origin = new BlockPos(origin).offset(repeat); + if (!Baritone.settings().buildRepeatSneaky.value) { + schematic.reset(); + } + logDirect("Repeating build in vector " + repeat + ", new origin is " + origin); + return onTick(calcFailed, isSafeToCancel, recursions + 1); + } + if (Baritone.settings().distanceTrim.value) { + trim(); + } + + Optional> toBreak = toBreakNearPlayer(bcc); + if (toBreak.isPresent() && isSafeToCancel && ctx.player().isOnGround()) { + // 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().getB(); + BetterBlockPos pos = toBreak.get().getA(); + baritone.getLookBehavior().updateTarget(rot, true); + MovementHelper.switchToBestToolFor(ctx, bcc.get(pos)); + if (ctx.player().isCrouching()) { + // really horrible bug where a block is visible for breaking while sneaking but not otherwise + // so you can't see it, it goes to place something else, sneaks, then the next tick it tries to break + // and is unable since it's unsneaked in the intermediary tick + baritone.getInputOverrideHandler().setInputForceState(Input.SNEAK, true); + } + if (ctx.isLookingAt(pos) || ctx.playerRotations().isReallyCloseTo(rot)) { + baritone.getInputOverrideHandler().setInputForceState(Input.CLICK_LEFT, true); + } + return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); + } + List desirableOnHotbar = new ArrayList<>(); + Optional toPlace = searchForPlacables(bcc, desirableOnHotbar); + if (toPlace.isPresent() && isSafeToCancel && ctx.player().isOnGround() && ticks <= 0) { + Rotation rot = toPlace.get().rot; + baritone.getLookBehavior().updateTarget(rot, true); + ctx.player().getInventory().selected = toPlace.get().hotbarSelection; + baritone.getInputOverrideHandler().setInputForceState(Input.SNEAK, true); + if ((ctx.isLookingAt(toPlace.get().placeAgainst) && ((BlockHitResult) ctx.objectMouseOver()).getDirection().equals(toPlace.get().side)) || ctx.playerRotations().isReallyCloseTo(rot)) { + baritone.getInputOverrideHandler().setInputForceState(Input.CLICK_RIGHT, true); + } + return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); + } + + if (Baritone.settings().allowInventory.value) { + ArrayList usefulSlots = new ArrayList<>(); + List noValidHotbarOption = new ArrayList<>(); + outer: + for (BlockState desired : desirableOnHotbar) { + for (int i = 0; i < 9; i++) { + if (valid(approxPlaceable.get(i), desired, true)) { + usefulSlots.add(i); + continue outer; + } + } + noValidHotbarOption.add(desired); + } + + outer: + for (int i = 9; i < 36; i++) { + for (BlockState desired : noValidHotbarOption) { + if (valid(approxPlaceable.get(i), desired, true)) { + if (!baritone.getInventoryBehavior().attemptToPutOnHotbar(i, usefulSlots::contains)) { + // awaiting inventory move, so pause + return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE); + } + break outer; + } + } + } + } + + Goal goal = assemble(bcc, approxPlaceable.subList(0, 9)); + if (goal == null) { + goal = assemble(bcc, approxPlaceable, true); // we're far away, so assume that we have our whole inventory to recalculate placeable properly + if (goal == null) { + if (Baritone.settings().skipFailedLayers.value && Baritone.settings().buildInLayers.value && layer * Baritone.settings().layerHeight.value < realSchematic.heightY()) { + logDirect("Skipping layer that I cannot construct! Layer #" + layer); + layer++; + return onTick(calcFailed, isSafeToCancel, recursions + 1); + } + logDirect("Unable to do it. Pausing. resume to resume, cancel to cancel"); + paused = true; + return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE); + } + } + return new PathingCommandContext(goal, PathingCommandType.FORCE_REVALIDATE_GOAL_AND_PATH, bcc); + } + + private 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(); + } + + private void trim() { + HashSet copy = new HashSet<>(incorrectPositions); + copy.removeIf(pos -> pos.distSqr(ctx.player().blockPosition()) > 200); + if (!copy.isEmpty()) { + incorrectPositions = copy; + } + } + + private void recalcNearby(BuilderCalculationContext bcc) { + BetterBlockPos center = ctx.playerFeet(); + int radius = Baritone.settings().builderTickScanRadius.value; + for (int dx = -radius; dx <= radius; dx++) { + for (int dy = -radius; dy <= radius; dy++) { + for (int dz = -radius; dz <= radius; dz++) { + int x = center.x + dx; + int y = center.y + dy; + int z = center.z + dz; + BlockState desired = bcc.getSchematic(x, y, z, bcc.bsi.get0(x, y, z)); + if (desired != null) { + // we care about this position + BetterBlockPos pos = new BetterBlockPos(x, y, z); + if (valid(bcc.bsi.get0(x, y, z), desired, false)) { + incorrectPositions.remove(pos); + observedCompleted.add(BetterBlockPos.longHash(pos)); + } else { + incorrectPositions.add(pos); + observedCompleted.remove(BetterBlockPos.longHash(pos)); + } + } + } + } + } + } + + private 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++) { + int blockX = x + origin.getX(); + int blockY = y + origin.getY(); + int blockZ = z + origin.getZ(); + BlockState current = bcc.bsi.get0(blockX, blockY, blockZ); + if (!schematic.inSchematic(x, y, z, current)) { + continue; + } + if (bcc.bsi.worldContainsLoadedChunk(blockX, blockZ)) { // check if its in render distance, not if its in cache + // we can directly observe this block, it is in render distance + if (valid(bcc.bsi.get0(blockX, blockY, blockZ), schematic.desiredState(x, y, z, current, this.approxPlaceable), false)) { + observedCompleted.add(BetterBlockPos.longHash(blockX, blockY, blockZ)); + } else { + incorrectPositions.add(new BetterBlockPos(blockX, blockY, blockZ)); + observedCompleted.remove(BetterBlockPos.longHash(blockX, blockY, blockZ)); + if (incorrectPositions.size() > Baritone.settings().incorrectSize.value) { + return; + } + } + continue; + } + // this is not in render distance + if (!observedCompleted.contains(BetterBlockPos.longHash(blockX, blockY, blockZ)) + && !Baritone.settings().buildSkipBlocks.value.contains(schematic.desiredState(x, y, z, current, this.approxPlaceable).getBlock())) { + // and we've never seen this position be correct + // therefore mark as incorrect + incorrectPositions.add(new BetterBlockPos(blockX, blockY, blockZ)); + if (incorrectPositions.size() > Baritone.settings().incorrectSize.value) { + return; + } + } + } + } + } + } + + private Goal assemble(BuilderCalculationContext bcc, List approxPlaceable) { + return assemble(bcc, approxPlaceable, false); + } + + private Goal assemble(BuilderCalculationContext bcc, List approxPlaceable, boolean logMissing) { + List placeable = new ArrayList<>(); + List breakable = new ArrayList<>(); + List sourceLiquids = new ArrayList<>(); + List flowingLiquids = new ArrayList<>(); + Map missing = new HashMap<>(); + incorrectPositions.forEach(pos -> { + BlockState state = bcc.bsi.get0(pos); + if (state.getBlock() instanceof AirBlock) { + if (containsBlockState(approxPlaceable, bcc.getSchematic(pos.x, pos.y, pos.z, state))) { + placeable.add(pos); + } else { + BlockState desired = bcc.getSchematic(pos.x, pos.y, pos.z, state); + missing.put(desired, 1 + missing.getOrDefault(desired, 0)); + } + } else { + if (state.getBlock() instanceof LiquidBlock) { + // if the block itself is JUST a liquid (i.e. not just a waterlogged block), we CANNOT break it + // TODO for 1.13 make sure that this only matches pure water, not waterlogged blocks + if (!MovementHelper.possiblyFlowing(state)) { + // if it's a source block then we want to replace it with a throwaway + sourceLiquids.add(pos); + } else { + flowingLiquids.add(pos); + } + } else { + breakable.add(pos); + } + } + }); + List toBreak = new ArrayList<>(); + breakable.forEach(pos -> toBreak.add(breakGoal(pos, bcc))); + List toPlace = new ArrayList<>(); + placeable.forEach(pos -> { + if (!placeable.contains(pos.below()) && !placeable.contains(pos.below(2))) { + toPlace.add(placementGoal(pos, bcc)); + } + }); + sourceLiquids.forEach(pos -> toPlace.add(new GoalBlock(pos.above()))); + + if (!toPlace.isEmpty()) { + return new JankyGoalComposite(new GoalComposite(toPlace.toArray(new Goal[0])), new GoalComposite(toBreak.toArray(new Goal[0]))); + } + if (toBreak.isEmpty()) { + if (logMissing && !missing.isEmpty()) { + logDirect("Missing materials for at least:"); + logDirect(missing.entrySet().stream() + .map(e -> String.format("%sx %s", e.getValue(), e.getKey())) + .collect(Collectors.joining("\n"))); + } + if (logMissing && !flowingLiquids.isEmpty()) { + logDirect("Unreplaceable liquids at at least:"); + logDirect(flowingLiquids.stream() + .map(p -> String.format("%s %s %s", p.x, p.y, p.z)) + .collect(Collectors.joining("\n"))); + } + return null; + } + return new GoalComposite(toBreak.toArray(new Goal[0])); + } + + public static class JankyGoalComposite implements Goal { + + private final Goal primary; + private final Goal fallback; + + public JankyGoalComposite(Goal primary, Goal fallback) { + this.primary = primary; + this.fallback = fallback; + } + + + @Override + public boolean isInGoal(int x, int y, int z) { + return primary.isInGoal(x, y, z) || fallback.isInGoal(x, y, z); + } + + @Override + public double heuristic(int x, int y, int z) { + return primary.heuristic(x, y, z); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + JankyGoalComposite goal = (JankyGoalComposite) o; + return Objects.equals(primary, goal.primary) + && Objects.equals(fallback, goal.fallback); + } + + @Override + public int hashCode() { + int hash = -1701079641; + hash = hash * 1196141026 + primary.hashCode(); + hash = hash * -80327868 + fallback.hashCode(); + return hash; + } + + @Override + public String toString() { + return "JankyComposite Primary: " + primary + " Fallback: " + fallback; + } + } + + 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 (y > this.y) { + return false; + } + // but any other adjacent works for breaking, including inside or below + return super.isInGoal(x, y, z); + } + + @Override + public String toString() { + return String.format( + "GoalBreak{x=%s,y=%s,z=%s}", + SettingsUtil.maybeCensor(x), + SettingsUtil.maybeCensor(y), + SettingsUtil.maybeCensor(z) + ); + } + + @Override + public int hashCode() { + return super.hashCode() * 1636324008; + } + } + + private Goal placementGoal(BlockPos pos, BuilderCalculationContext bcc) { + if (!(ctx.world().getBlockState(pos).getBlock() instanceof AirBlock)) { // TODO can this even happen? + return new GoalPlace(pos); + } + boolean allowSameLevel = !(ctx.world().getBlockState(pos.above()).getBlock() instanceof AirBlock); + BlockState current = ctx.world().getBlockState(pos); + for (Direction facing : Movement.HORIZONTALS_BUT_ALSO_DOWN_____SO_EVERY_DIRECTION_EXCEPT_UP) { + //noinspection ConstantConditions + if (MovementHelper.canPlaceAgainst(ctx, pos.relative(facing)) && placementPlausible(pos, bcc.getSchematic(pos.getX(), pos.getY(), pos.getZ(), current))) { + return new GoalAdjacent(pos, pos.relative(facing), allowSameLevel); + } + } + return new GoalPlace(pos); + } + + private Goal breakGoal(BlockPos pos, BuilderCalculationContext bcc) { + if (Baritone.settings().goalBreakFromAbove.value && bcc.bsi.get0(pos.above()).getBlock() instanceof AirBlock && bcc.bsi.get0(pos.above(2)).getBlock() instanceof AirBlock) { // TODO maybe possible without the up(2) check? + return new JankyGoalComposite(new GoalBreak(pos), new GoalGetToBlock(pos.above()) { + @Override + public boolean isInGoal(int x, int y, int z) { + if (y > this.y || (x == this.x && y == this.y && z == this.z)) { + return false; + } + return super.isInGoal(x, y, z); + } + }); + } + return new GoalBreak(pos); + } + + public static class GoalAdjacent extends GoalGetToBlock { + + private boolean allowSameLevel; + private BlockPos no; + + public GoalAdjacent(BlockPos pos, BlockPos no, boolean allowSameLevel) { + super(pos); + this.no = no; + this.allowSameLevel = allowSameLevel; + } + + @Override + public boolean isInGoal(int x, int y, int z) { + if (x == this.x && y == this.y && z == this.z) { + return false; + } + if (x == no.getX() && y == no.getY() && z == no.getZ()) { + return false; + } + if (!allowSameLevel && y == this.y - 1) { + return false; + } + if (y < this.y - 1) { + return false; + } + return super.isInGoal(x, y, z); + } + + @Override + public double heuristic(int x, int y, int z) { + // prioritize lower y coordinates + return this.y * 100 + super.heuristic(x, y, z); + } + + @Override + public boolean equals(Object o) { + if (!super.equals(o)) { + return false; + } + + GoalAdjacent goal = (GoalAdjacent) o; + return allowSameLevel == goal.allowSameLevel + && Objects.equals(no, goal.no); + } + + @Override + public int hashCode() { + int hash = 806368046; + hash = hash * 1412661222 + super.hashCode(); + hash = hash * 1730799370 + (int) BetterBlockPos.longHash(no.getX(), no.getY(), no.getZ()); + hash = hash * 260592149 + (allowSameLevel ? -1314802005 : 1565710265); + return hash; + } + + @Override + public String toString() { + return String.format( + "GoalAdjacent{x=%s,y=%s,z=%s}", + SettingsUtil.maybeCensor(x), + SettingsUtil.maybeCensor(y), + SettingsUtil.maybeCensor(z) + ); + } + } + + public static class GoalPlace extends GoalBlock { + + public GoalPlace(BlockPos placeAt) { + super(placeAt.above()); + } + + @Override + public double heuristic(int x, int y, int z) { + // prioritize lower y coordinates + return this.y * 100 + super.heuristic(x, y, z); + } + + @Override + public int hashCode() { + return super.hashCode() * 1910811835; + } + + @Override + public String toString() { + return String.format( + "GoalPlace{x=%s,y=%s,z=%s}", + SettingsUtil.maybeCensor(x), + SettingsUtil.maybeCensor(y), + SettingsUtil.maybeCensor(z) + ); + } + } + + @Override + public void onLostControl() { + incorrectPositions = null; + name = null; + schematic = null; + realSchematic = null; + layer = Baritone.settings().startAtLayer.value; + numRepeats = 0; + paused = false; + observedCompleted = null; + } + + @Override + public String displayName0() { + return paused ? "Builder Paused" : "Building " + name; + } + + private List approxPlaceable(int size) { + List result = new ArrayList<>(); + for (int i = 0; i < size; i++) { + ItemStack stack = ctx.player().getInventory().items.get(i); + if (stack.isEmpty() || !(stack.getItem() instanceof BlockItem)) { + result.add(Blocks.AIR.defaultBlockState()); + continue; + } + // + BlockState itemState = ((BlockItem) stack.getItem()) + .getBlock() + .getStateForPlacement( + new BlockPlaceContext( + new UseOnContext(ctx.world(), ctx.player(), InteractionHand.MAIN_HAND, stack, new BlockHitResult(new Vec3(ctx.player().position().x, ctx.player().position().y, ctx.player().position().z), Direction.UP, ctx.playerFeet(), false)) {} + ) + ); + if (itemState != null) { + result.add(itemState); + } else { + result.add(Blocks.AIR.defaultBlockState()); + } + // + } + return result; + } + + public static final Set> orientationProps = + ImmutableSet.of( + RotatedPillarBlock.AXIS, HorizontalDirectionalBlock.FACING, + StairBlock.FACING, StairBlock.HALF, StairBlock.SHAPE, + PipeBlock.NORTH, PipeBlock.EAST, PipeBlock.SOUTH, PipeBlock.WEST, PipeBlock.UP, + TrapDoorBlock.OPEN, TrapDoorBlock.HALF + ); + + private boolean sameBlockstate(BlockState first, BlockState second) { + if (first.getBlock() != second.getBlock()) { + return false; + } + boolean ignoreDirection = Baritone.settings().buildIgnoreDirection.value; + List ignoredProps = Baritone.settings().buildIgnoreProperties.value; + if (!ignoreDirection && ignoredProps.isEmpty()) { + return first.equals(second); // early return if no properties are being ignored + } + ImmutableMap, Comparable> map1 = first.getValues(); + ImmutableMap, Comparable> map2 = second.getValues(); + for (Property prop : map1.keySet()) { + if (map1.get(prop) != map2.get(prop) + && !(ignoreDirection && orientationProps.contains(prop)) + && !ignoredProps.contains(prop.getName())) { + return false; + } + } + return true; + } + + private boolean containsBlockState(Collection states, BlockState state) { + for (BlockState testee : states) { + if (sameBlockstate(testee, state)) { + return true; + } + } + return false; + } + + private boolean valid(BlockState current, BlockState desired, boolean itemVerify) { + if (desired == null) { + return true; + } + if (current.getBlock() instanceof LiquidBlock && Baritone.settings().okIfWater.value) { + return true; + } + if (current.getBlock() instanceof AirBlock && desired.getBlock() instanceof AirBlock) { + return true; + } + if (current.getBlock() instanceof AirBlock && Baritone.settings().okIfAir.value.contains(desired.getBlock())) { + return true; + } + if (desired.getBlock() instanceof AirBlock && Baritone.settings().buildIgnoreBlocks.value.contains(current.getBlock())) { + return true; + } + if (!(current.getBlock() instanceof AirBlock) && Baritone.settings().buildIgnoreExisting.value && !itemVerify) { + return true; + } + if (Baritone.settings().buildSkipBlocks.value.contains(desired.getBlock()) && !itemVerify) { + return true; + } + if (Baritone.settings().buildValidSubstitutes.value.getOrDefault(desired.getBlock(), Collections.emptyList()).contains(current.getBlock()) && !itemVerify) { + return true; + } + if (current.equals(desired)) { + return true; + } + return sameBlockstate(current, desired); + } + + public class BuilderCalculationContext extends CalculationContext { + + private final List placeable; + private final ISchematic schematic; + private final int originX; + private final int originY; + private final int originZ; + + public BuilderCalculationContext() { + super(BuilderProcess.this.baritone, true); // wew lad + this.placeable = approxPlaceable(9); + this.schematic = BuilderProcess.this.schematic; + this.originX = origin.getX(); + this.originY = origin.getY(); + this.originZ = origin.getZ(); + + this.jumpPenalty += 10; + this.backtrackCostFavoringCoefficient = 1; + } + + private BlockState getSchematic(int x, int y, int z, BlockState current) { + if (schematic.inSchematic(x - originX, y - originY, z - originZ, current)) { + return schematic.desiredState(x - originX, y - originY, z - originZ, current, BuilderProcess.this.approxPlaceable); + } else { + return null; + } + } + + @Override + public double costOfPlacingAt(int x, int y, int z, BlockState current) { + if (isPossiblyProtected(x, y, z) || !worldBorder.canPlaceAt(x, z)) { // make calculation fail properly if we can't build + return COST_INF; + } + BlockState sch = getSchematic(x, y, z, current); + if (sch != null && !Baritone.settings().buildSkipBlocks.value.contains(sch.getBlock())) { + // TODO this can return true even when allowPlace is off.... is that an issue? + if (sch.getBlock() instanceof AirBlock) { + // 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 (placeable.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 😎 + // i'm such an idiot that i just tried to copy and paste the epic gamer moment emoji too + // get added to unicode when? + } + if (!hasThrowaway) { + return COST_INF; + } + // 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; + } else { + return COST_INF; + } + } + } + + @Override + public double breakCostMultiplierAt(int x, int y, int z, BlockState current) { + if ((!allowBreak && !allowBreakAnyway.contains(current.getBlock())) || isPossiblyProtected(x, y, z)) { + return COST_INF; + } + BlockState sch = getSchematic(x, y, z, current); + if (sch != null && !Baritone.settings().buildSkipBlocks.value.contains(sch.getBlock())) { + if (sch.getBlock() instanceof AirBlock) { + // it should be air + // regardless of current contents, we can break it + return 1; + } + // it should be a real block + // is it already that block? + if (valid(bsi.get0(x, y, z), sch, false)) { + return Baritone.settings().breakCorrectBlockPenaltyMultiplier.value; + } 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 1; // why not lol + } + } + } + } + \ No newline at end of file From b8b6caccb3f22107871861bc267010bffa52decd Mon Sep 17 00:00:00 2001 From: Thojo0 <53666000+thojo0@users.noreply.github.com> Date: Sun, 5 Nov 2023 14:54:56 +0000 Subject: [PATCH 3/3] fix accidentally spaces --- .../java/baritone/process/BuilderProcess.java | 2341 ++++++++--------- 1 file changed, 1170 insertions(+), 1171 deletions(-) diff --git a/src/main/java/baritone/process/BuilderProcess.java b/src/main/java/baritone/process/BuilderProcess.java index 0f5f72008..ce76d5f59 100644 --- a/src/main/java/baritone/process/BuilderProcess.java +++ b/src/main/java/baritone/process/BuilderProcess.java @@ -15,1174 +15,1173 @@ * along with Baritone. If not, see . */ - package baritone.process; - - import baritone.Baritone; - import baritone.api.pathing.goals.Goal; - import baritone.api.pathing.goals.GoalBlock; - import baritone.api.pathing.goals.GoalComposite; - import baritone.api.pathing.goals.GoalGetToBlock; - import baritone.api.process.IBuilderProcess; - import baritone.api.process.PathingCommand; - import baritone.api.process.PathingCommandType; - import baritone.api.schematic.FillSchematic; - import baritone.api.schematic.ISchematic; - import baritone.api.schematic.IStaticSchematic; - import baritone.api.schematic.SubstituteSchematic; - import baritone.api.schematic.format.ISchematicFormat; - import baritone.api.utils.*; - import baritone.api.utils.input.Input; - import baritone.pathing.movement.CalculationContext; - import baritone.pathing.movement.Movement; - import baritone.pathing.movement.MovementHelper; - import baritone.utils.BaritoneProcessHelper; - import baritone.utils.BlockStateInterface; - import baritone.utils.PathingCommandContext; - import baritone.utils.schematic.MapArtSchematic; - import baritone.utils.schematic.SelectionSchematic; - import baritone.utils.schematic.SchematicSystem; - import baritone.utils.schematic.format.defaults.LitematicaSchematic; - import baritone.utils.schematic.litematica.LitematicaHelper; - import baritone.utils.schematic.schematica.SchematicaHelper; - import com.google.common.collect.ImmutableMap; - import com.google.common.collect.ImmutableSet; - import it.unimi.dsi.fastutil.longs.LongOpenHashSet; - import net.minecraft.core.BlockPos; - import net.minecraft.core.Direction; - import net.minecraft.core.Vec3i; - import net.minecraft.nbt.NbtIo; - import net.minecraft.util.Tuple; - import net.minecraft.world.InteractionHand; - import net.minecraft.world.item.BlockItem; - import net.minecraft.world.item.ItemStack; - import net.minecraft.world.item.context.BlockPlaceContext; - import net.minecraft.world.item.context.UseOnContext; - import net.minecraft.world.level.block.AirBlock; - import net.minecraft.world.level.block.Blocks; - import net.minecraft.world.level.block.HorizontalDirectionalBlock; - import net.minecraft.world.level.block.LiquidBlock; - import net.minecraft.world.level.block.PipeBlock; - import net.minecraft.world.level.block.RotatedPillarBlock; - import net.minecraft.world.level.block.StairBlock; - import net.minecraft.world.level.block.TrapDoorBlock; - import net.minecraft.world.level.block.state.BlockState; - import net.minecraft.world.level.block.state.properties.Property; - import net.minecraft.world.phys.AABB; - import net.minecraft.world.phys.BlockHitResult; - import net.minecraft.world.phys.HitResult; - import net.minecraft.world.phys.Vec3; - import net.minecraft.world.phys.shapes.VoxelShape; - - import java.io.File; - import java.io.FileInputStream; - import java.nio.file.Files; - import java.util.*; - import java.util.stream.Collectors; - import java.util.stream.Stream; - - import static baritone.api.pathing.movement.ActionCosts.COST_INF; - - public final class BuilderProcess extends BaritoneProcessHelper implements IBuilderProcess { - - private HashSet incorrectPositions; - private LongOpenHashSet observedCompleted; // positions that are completed even if they're out of render distance and we can't make sure right now - private String name; - private ISchematic realSchematic; - private ISchematic schematic; - private Vec3i origin; - private int ticks; - private boolean paused; - private int layer; - private int numRepeats; - private List approxPlaceable; - public int stopAtHeight = 0; - - public BuilderProcess(Baritone baritone) { - super(baritone); - } - - @Override - public void build(String name, ISchematic schematic, Vec3i origin) { - this.name = name; - this.schematic = schematic; - this.realSchematic = null; - boolean buildingSelectionSchematic = schematic instanceof SelectionSchematic; - if (!Baritone.settings().buildSubstitutes.value.isEmpty()) { - this.schematic = new SubstituteSchematic(this.schematic, Baritone.settings().buildSubstitutes.value); - } - int x = origin.getX(); - int y = origin.getY(); - int z = origin.getZ(); - if (Baritone.settings().schematicOrientationX.value) { - x += schematic.widthX(); - } - if (Baritone.settings().schematicOrientationY.value) { - y += schematic.heightY(); - } - if (Baritone.settings().schematicOrientationZ.value) { - z += schematic.lengthZ(); - } - this.origin = new Vec3i(x, y, z); - this.paused = false; - this.layer = Baritone.settings().startAtLayer.value; - this.stopAtHeight = schematic.heightY(); - if (Baritone.settings().buildOnlySelection.value && buildingSelectionSchematic) { // currently redundant but safer maybe - if (baritone.getSelectionManager().getSelections().length == 0) { - logDirect("Poor little kitten forgot to set a selection while BuildOnlySelection is true"); - this.stopAtHeight = 0; - } else if (Baritone.settings().buildInLayers.value) { - OptionalInt minim = Stream.of(baritone.getSelectionManager().getSelections()).mapToInt(sel -> sel.min().y).min(); - OptionalInt maxim = Stream.of(baritone.getSelectionManager().getSelections()).mapToInt(sel -> sel.max().y).max(); - if (minim.isPresent() && maxim.isPresent()) { - int startAtHeight = Baritone.settings().layerOrder.value ? y + schematic.heightY() - maxim.getAsInt() : minim.getAsInt() - y; - this.stopAtHeight = (Baritone.settings().layerOrder.value ? y + schematic.heightY() - minim.getAsInt() : maxim.getAsInt() - y) + 1; - this.layer = Math.max(this.layer, startAtHeight / Baritone.settings().layerHeight.value); // startAtLayer or startAtHeight, whichever is highest - logDebug(String.format("Schematic starts at y=%s with height %s", y, schematic.heightY())); - logDebug(String.format("Selection starts at y=%s and ends at y=%s", minim.getAsInt(), maxim.getAsInt())); - logDebug(String.format("Considering relevant height %s - %s", startAtHeight, this.stopAtHeight)); - } - } - } - - this.numRepeats = 0; - this.observedCompleted = new LongOpenHashSet(); - this.incorrectPositions = null; - } - - public void resume() { - paused = false; - } - - public void pause() { - paused = true; - } - - @Override - public boolean isPaused() { - return paused; - } - - @Override - public boolean build(String name, File schematic, Vec3i origin) { - Optional format = SchematicSystem.INSTANCE.getByFile(schematic); - if (!format.isPresent()) { - return false; - } - ISchematic parsed; - try { - parsed = format.get().parse(new FileInputStream(schematic)); - } catch (Exception e) { - e.printStackTrace(); - return false; - } - parsed = applyMapArtAndSelection(origin, (IStaticSchematic) parsed); - build(name, parsed, origin); - return true; - } - - private ISchematic applyMapArtAndSelection(Vec3i origin, IStaticSchematic parsed) { - ISchematic schematic = parsed; - if (Baritone.settings().mapArtMode.value) { - schematic = new MapArtSchematic(parsed); - } - if (Baritone.settings().buildOnlySelection.value) { - schematic = new SelectionSchematic(schematic, origin, baritone.getSelectionManager().getSelections()); - } - return schematic; - } - - @Override - public void buildOpenSchematic() { - if (SchematicaHelper.isSchematicaPresent()) { - Optional> schematic = SchematicaHelper.getOpenSchematic(); - if (schematic.isPresent()) { - IStaticSchematic s = schematic.get().getA(); - BlockPos origin = schematic.get().getB(); - ISchematic schem = Baritone.settings().mapArtMode.value ? new MapArtSchematic(s) : s; - if (Baritone.settings().buildOnlySelection.value) { - schem = new SelectionSchematic(schem, origin, baritone.getSelectionManager().getSelections()); - } - this.build( - schematic.get().getA().toString(), - schem, - origin - ); - } else { - logDirect("No schematic currently open"); - } - } else { - logDirect("Schematica is not present"); - } - } - - @Override - public void buildOpenLitematic() { - if (LitematicaHelper.isLitematicaPresent()) { - Integer selectedIndex = LitematicaHelper.getSelectedIndex(); - if (selectedIndex != -1) { - buildOpenLitematic(selectedIndex); - } else { - logDirect("No schematic currently selected"); - } - } else { - logDirect("Litematica is not present"); - } - } - - @Override - public void buildOpenLitematic(int i) { - if (LitematicaHelper.isLitematicaPresent()) { - //if java.lang.NoSuchMethodError is thrown see comment in SchematicPlacementManager - if (LitematicaHelper.hasLoadedSchematic()) { - String name = LitematicaHelper.getName(i); - try { - LitematicaSchematic schematic1 = new LitematicaSchematic(NbtIo.readCompressed(Files.newInputStream(LitematicaHelper.getSchematicFile(i).toPath())), false); - Vec3i correctedOrigin = LitematicaHelper.getCorrectedOrigin(schematic1, i); - ISchematic schematic2 = LitematicaHelper.blackMagicFuckery(schematic1, i); - schematic2 = applyMapArtAndSelection(origin, (IStaticSchematic) schematic2); - build(name, schematic2, correctedOrigin); - } catch (Exception e) { - logDirect("Schematic File could not be loaded."); - } - } else { - logDirect("No schematic currently loaded"); - } - } else { - logDirect("Litematica is not present"); - } - } - - public void clearArea(BlockPos corner1, BlockPos corner2) { - BlockPos origin = new BlockPos(Math.min(corner1.getX(), corner2.getX()), Math.min(corner1.getY(), corner2.getY()), Math.min(corner1.getZ(), corner2.getZ())); - int widthX = Math.abs(corner1.getX() - corner2.getX()) + 1; - int heightY = Math.abs(corner1.getY() - corner2.getY()) + 1; - int lengthZ = Math.abs(corner1.getZ() - corner2.getZ()) + 1; - build("clear area", new FillSchematic(widthX, heightY, lengthZ, Blocks.AIR.defaultBlockState()), origin); - } - - @Override - public List getApproxPlaceable() { - return new ArrayList<>(approxPlaceable); - } - - @Override - public boolean isActive() { - return schematic != null; - } - - public BlockState placeAt(int x, int y, int z, BlockState current) { - if (!isActive()) { - return null; - } - if (!schematic.inSchematic(x - origin.getX(), y - origin.getY(), z - origin.getZ(), current)) { - return null; - } - BlockState state = schematic.desiredState(x - origin.getX(), y - origin.getY(), z - origin.getZ(), current, this.approxPlaceable); - if (state.getBlock() instanceof AirBlock) { - return null; - } - return state; - } - - private Optional> toBreakNearPlayer(BuilderCalculationContext bcc) { - BetterBlockPos center = ctx.playerFeet(); - BetterBlockPos pathStart = baritone.getPathingBehavior().pathStart(); - for (int dx = -5; dx <= 5; dx++) { - for (int dy = Baritone.settings().breakFromAbove.value ? -1 : 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; - if (dy == -1 && x == pathStart.x && z == pathStart.z) { - continue; // dont mine what we're supported by, but not directly standing on - } - BlockState desired = bcc.getSchematic(x, y, z, bcc.bsi.get0(x, y, z)); - if (desired == null) { - continue; // irrelevant - } - BlockState curr = bcc.bsi.get0(x, y, z); - if (!(curr.getBlock() instanceof AirBlock) && !(curr.getBlock() == Blocks.WATER || curr.getBlock() == Blocks.LAVA) && !valid(curr, desired, false)) { - BetterBlockPos pos = new BetterBlockPos(x, y, z); - Optional rot = RotationUtils.reachable(ctx, pos, ctx.playerController().getBlockReachDistance()); - if (rot.isPresent()) { - return Optional.of(new Tuple<>(pos, rot.get())); - } - } - } - } - } - return Optional.empty(); - } - - public static class Placement { - - private final int hotbarSelection; - private final BlockPos placeAgainst; - private final Direction side; - private final Rotation rot; - - public Placement(int hotbarSelection, BlockPos placeAgainst, Direction side, Rotation rot) { - this.hotbarSelection = hotbarSelection; - this.placeAgainst = placeAgainst; - this.side = side; - this.rot = rot; - } - } - - private Optional searchForPlacables(BuilderCalculationContext bcc, List desirableOnHotbar) { - BetterBlockPos center = ctx.playerFeet(); - for (int dx = -5; dx <= 5; dx++) { - for (int dy = -5; dy <= 1; dy++) { - for (int dz = -5; dz <= 5; dz++) { - int x = center.x + dx; - int y = center.y + dy; - int z = center.z + dz; - BlockState desired = bcc.getSchematic(x, y, z, bcc.bsi.get0(x, y, z)); - if (desired == null) { - continue; // irrelevant - } - BlockState curr = bcc.bsi.get0(x, y, z); - if (MovementHelper.isReplaceable(x, y, z, curr, bcc.bsi) && !valid(curr, desired, false)) { - if (dy == 1 && bcc.bsi.get0(x, y + 1, z).getBlock() instanceof AirBlock) { - continue; - } - desirableOnHotbar.add(desired); - Optional opt = possibleToPlace(desired, x, y, z, bcc.bsi); - if (opt.isPresent()) { - return opt; - } - } - } - } - } - return Optional.empty(); - } - - public boolean placementPlausible(BlockPos pos, BlockState state) { - VoxelShape voxelshape = state.getCollisionShape(ctx.world(), pos); - return voxelshape.isEmpty() || ctx.world().isUnobstructed(null, voxelshape.move(pos.getX(), pos.getY(), pos.getZ())); - } - - private Optional possibleToPlace(BlockState toPlace, int x, int y, int z, BlockStateInterface bsi) { - for (Direction against : Direction.values()) { - BetterBlockPos placeAgainstPos = new BetterBlockPos(x, y, z).relative(against); - BlockState placeAgainstState = bsi.get0(placeAgainstPos); - if (MovementHelper.isReplaceable(placeAgainstPos.x, placeAgainstPos.y, placeAgainstPos.z, placeAgainstState, bsi)) { - continue; - } - if (!toPlace.canSurvive(ctx.world(), new BetterBlockPos(x, y, z))) { - continue; - } - if (!placementPlausible(new BetterBlockPos(x, y, z), toPlace)) { - continue; - } - AABB aabb = placeAgainstState.getShape(ctx.world(), placeAgainstPos).bounds(); - for (Vec3 placementMultiplier : aabbSideMultipliers(against)) { - double placeX = placeAgainstPos.x + aabb.minX * placementMultiplier.x + aabb.maxX * (1 - placementMultiplier.x); - double placeY = placeAgainstPos.y + aabb.minY * placementMultiplier.y + aabb.maxY * (1 - placementMultiplier.y); - double placeZ = placeAgainstPos.z + aabb.minZ * placementMultiplier.z + aabb.maxZ * (1 - placementMultiplier.z); - Rotation rot = RotationUtils.calcRotationFromVec3d(RayTraceUtils.inferSneakingEyePosition(ctx.player()), new Vec3(placeX, placeY, placeZ), ctx.playerRotations()); - Rotation actualRot = baritone.getLookBehavior().getAimProcessor().peekRotation(rot); - HitResult result = RayTraceUtils.rayTraceTowards(ctx.player(), actualRot, ctx.playerController().getBlockReachDistance(), true); - if (result != null && result.getType() == HitResult.Type.BLOCK && ((BlockHitResult) result).getBlockPos().equals(placeAgainstPos) && ((BlockHitResult) result).getDirection() == against.getOpposite()) { - OptionalInt hotbar = hasAnyItemThatWouldPlace(toPlace, result, actualRot); - if (hotbar.isPresent()) { - return Optional.of(new Placement(hotbar.getAsInt(), placeAgainstPos, against.getOpposite(), rot)); - } - } - } - } - return Optional.empty(); - } - - private OptionalInt hasAnyItemThatWouldPlace(BlockState desired, HitResult result, Rotation rot) { - for (int i = 0; i < 9; i++) { - ItemStack stack = ctx.player().getInventory().items.get(i); - if (stack.isEmpty() || !(stack.getItem() instanceof BlockItem)) { - continue; - } - float originalYaw = ctx.player().getYRot(); - float originalPitch = ctx.player().getXRot(); - // the state depends on the facing of the player sometimes - ctx.player().setYRot(rot.getYaw()); - ctx.player().setXRot(rot.getPitch()); - BlockPlaceContext meme = new BlockPlaceContext(new UseOnContext( - ctx.world(), - ctx.player(), - InteractionHand.MAIN_HAND, - stack, - (BlockHitResult) result - ) {}); // that {} gives us access to a protected constructor lmfao - BlockState wouldBePlaced = ((BlockItem) stack.getItem()).getBlock().getStateForPlacement(meme); - ctx.player().setYRot(originalYaw); - ctx.player().setXRot(originalPitch); - if (wouldBePlaced == null) { - continue; - } - if (!meme.canPlace()) { - continue; - } - if (valid(wouldBePlaced, desired, true)) { - return OptionalInt.of(i); - } - } - return OptionalInt.empty(); - } - - private static Vec3[] aabbSideMultipliers(Direction side) { - switch (side) { - case UP: - return new Vec3[]{new Vec3(0.5, 1, 0.5), new Vec3(0.1, 1, 0.5), new Vec3(0.9, 1, 0.5), new Vec3(0.5, 1, 0.1), new Vec3(0.5, 1, 0.9)}; - case DOWN: - return new Vec3[]{new Vec3(0.5, 0, 0.5), new Vec3(0.1, 0, 0.5), new Vec3(0.9, 0, 0.5), new Vec3(0.5, 0, 0.1), new Vec3(0.5, 0, 0.9)}; - case NORTH: - case SOUTH: - case EAST: - case WEST: - double x = side.getStepX() == 0 ? 0.5 : (1 + side.getStepX()) / 2D; - double z = side.getStepZ() == 0 ? 0.5 : (1 + side.getStepZ()) / 2D; - return new Vec3[]{new Vec3(x, 0.25, z), new Vec3(x, 0.75, z)}; - default: // null - throw new IllegalStateException(); - } - } - - @Override - public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { - return onTick(calcFailed, isSafeToCancel, 0); - } - - public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel, int recursions) { - if (recursions > 1000) { // onTick calls itself, don't crash - return new PathingCommand(null, PathingCommandType.SET_GOAL_AND_PATH); - } - approxPlaceable = approxPlaceable(36); - if (baritone.getInputOverrideHandler().isInputForcedDown(Input.CLICK_LEFT)) { - ticks = 5; - } else { - ticks--; - } - baritone.getInputOverrideHandler().clearAllKeys(); - if (paused) { - return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); - } - if (Baritone.settings().buildInLayers.value) { - if (realSchematic == null) { - realSchematic = schematic; - } - ISchematic realSchematic = this.realSchematic; // wrap this properly, dont just have the inner class refer to the builderprocess.this - int minYInclusive; - int maxYInclusive; - // layer = 0 should be nothing - // layer = realSchematic.heightY() should be everything - if (Baritone.settings().layerOrder.value) { // top to bottom - maxYInclusive = realSchematic.heightY() - 1; - minYInclusive = realSchematic.heightY() - layer * Baritone.settings().layerHeight.value; - } else { - maxYInclusive = layer * Baritone.settings().layerHeight.value - 1; - minYInclusive = 0; - } - schematic = new ISchematic() { - @Override - public BlockState desiredState(int x, int y, int z, BlockState current, List approxPlaceable) { - return realSchematic.desiredState(x, y, z, current, BuilderProcess.this.approxPlaceable); - } - - @Override - public boolean inSchematic(int x, int y, int z, BlockState currentState) { - return ISchematic.super.inSchematic(x, y, z, currentState) && y >= minYInclusive && y <= maxYInclusive && realSchematic.inSchematic(x, y, z, currentState); - } - - @Override - public void reset() { - realSchematic.reset(); - } - - @Override - public int widthX() { - return realSchematic.widthX(); - } - - @Override - public int heightY() { - return realSchematic.heightY(); - } - - @Override - public int lengthZ() { - return realSchematic.lengthZ(); - } - }; - } - BuilderCalculationContext bcc = new BuilderCalculationContext(); - if (!recalc(bcc)) { - if (Baritone.settings().buildInLayers.value && layer * Baritone.settings().layerHeight.value < stopAtHeight) { - logDirect("Starting layer " + layer); - layer++; - return onTick(calcFailed, isSafeToCancel, recursions + 1); - } - Vec3i repeat = Baritone.settings().buildRepeat.value; - int max = Baritone.settings().buildRepeatCount.value; - numRepeats++; - if (repeat.equals(new Vec3i(0, 0, 0)) || (max != -1 && numRepeats >= max)) { - logDirect("Done building"); - if (Baritone.settings().notificationOnBuildFinished.value) { - logNotification("Done building", false); - } - onLostControl(); - return null; - } - // build repeat time - layer = 0; - origin = new BlockPos(origin).offset(repeat); - if (!Baritone.settings().buildRepeatSneaky.value) { - schematic.reset(); - } - logDirect("Repeating build in vector " + repeat + ", new origin is " + origin); - return onTick(calcFailed, isSafeToCancel, recursions + 1); - } - if (Baritone.settings().distanceTrim.value) { - trim(); - } - - Optional> toBreak = toBreakNearPlayer(bcc); - if (toBreak.isPresent() && isSafeToCancel && ctx.player().isOnGround()) { - // 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().getB(); - BetterBlockPos pos = toBreak.get().getA(); - baritone.getLookBehavior().updateTarget(rot, true); - MovementHelper.switchToBestToolFor(ctx, bcc.get(pos)); - if (ctx.player().isCrouching()) { - // really horrible bug where a block is visible for breaking while sneaking but not otherwise - // so you can't see it, it goes to place something else, sneaks, then the next tick it tries to break - // and is unable since it's unsneaked in the intermediary tick - baritone.getInputOverrideHandler().setInputForceState(Input.SNEAK, true); - } - if (ctx.isLookingAt(pos) || ctx.playerRotations().isReallyCloseTo(rot)) { - baritone.getInputOverrideHandler().setInputForceState(Input.CLICK_LEFT, true); - } - return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); - } - List desirableOnHotbar = new ArrayList<>(); - Optional toPlace = searchForPlacables(bcc, desirableOnHotbar); - if (toPlace.isPresent() && isSafeToCancel && ctx.player().isOnGround() && ticks <= 0) { - Rotation rot = toPlace.get().rot; - baritone.getLookBehavior().updateTarget(rot, true); - ctx.player().getInventory().selected = toPlace.get().hotbarSelection; - baritone.getInputOverrideHandler().setInputForceState(Input.SNEAK, true); - if ((ctx.isLookingAt(toPlace.get().placeAgainst) && ((BlockHitResult) ctx.objectMouseOver()).getDirection().equals(toPlace.get().side)) || ctx.playerRotations().isReallyCloseTo(rot)) { - baritone.getInputOverrideHandler().setInputForceState(Input.CLICK_RIGHT, true); - } - return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); - } - - if (Baritone.settings().allowInventory.value) { - ArrayList usefulSlots = new ArrayList<>(); - List noValidHotbarOption = new ArrayList<>(); - outer: - for (BlockState desired : desirableOnHotbar) { - for (int i = 0; i < 9; i++) { - if (valid(approxPlaceable.get(i), desired, true)) { - usefulSlots.add(i); - continue outer; - } - } - noValidHotbarOption.add(desired); - } - - outer: - for (int i = 9; i < 36; i++) { - for (BlockState desired : noValidHotbarOption) { - if (valid(approxPlaceable.get(i), desired, true)) { - if (!baritone.getInventoryBehavior().attemptToPutOnHotbar(i, usefulSlots::contains)) { - // awaiting inventory move, so pause - return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE); - } - break outer; - } - } - } - } - - Goal goal = assemble(bcc, approxPlaceable.subList(0, 9)); - if (goal == null) { - goal = assemble(bcc, approxPlaceable, true); // we're far away, so assume that we have our whole inventory to recalculate placeable properly - if (goal == null) { - if (Baritone.settings().skipFailedLayers.value && Baritone.settings().buildInLayers.value && layer * Baritone.settings().layerHeight.value < realSchematic.heightY()) { - logDirect("Skipping layer that I cannot construct! Layer #" + layer); - layer++; - return onTick(calcFailed, isSafeToCancel, recursions + 1); - } - logDirect("Unable to do it. Pausing. resume to resume, cancel to cancel"); - paused = true; - return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE); - } - } - return new PathingCommandContext(goal, PathingCommandType.FORCE_REVALIDATE_GOAL_AND_PATH, bcc); - } - - private 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(); - } - - private void trim() { - HashSet copy = new HashSet<>(incorrectPositions); - copy.removeIf(pos -> pos.distSqr(ctx.player().blockPosition()) > 200); - if (!copy.isEmpty()) { - incorrectPositions = copy; - } - } - - private void recalcNearby(BuilderCalculationContext bcc) { - BetterBlockPos center = ctx.playerFeet(); - int radius = Baritone.settings().builderTickScanRadius.value; - for (int dx = -radius; dx <= radius; dx++) { - for (int dy = -radius; dy <= radius; dy++) { - for (int dz = -radius; dz <= radius; dz++) { - int x = center.x + dx; - int y = center.y + dy; - int z = center.z + dz; - BlockState desired = bcc.getSchematic(x, y, z, bcc.bsi.get0(x, y, z)); - if (desired != null) { - // we care about this position - BetterBlockPos pos = new BetterBlockPos(x, y, z); - if (valid(bcc.bsi.get0(x, y, z), desired, false)) { - incorrectPositions.remove(pos); - observedCompleted.add(BetterBlockPos.longHash(pos)); - } else { - incorrectPositions.add(pos); - observedCompleted.remove(BetterBlockPos.longHash(pos)); - } - } - } - } - } - } - - private 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++) { - int blockX = x + origin.getX(); - int blockY = y + origin.getY(); - int blockZ = z + origin.getZ(); - BlockState current = bcc.bsi.get0(blockX, blockY, blockZ); - if (!schematic.inSchematic(x, y, z, current)) { - continue; - } - if (bcc.bsi.worldContainsLoadedChunk(blockX, blockZ)) { // check if its in render distance, not if its in cache - // we can directly observe this block, it is in render distance - if (valid(bcc.bsi.get0(blockX, blockY, blockZ), schematic.desiredState(x, y, z, current, this.approxPlaceable), false)) { - observedCompleted.add(BetterBlockPos.longHash(blockX, blockY, blockZ)); - } else { - incorrectPositions.add(new BetterBlockPos(blockX, blockY, blockZ)); - observedCompleted.remove(BetterBlockPos.longHash(blockX, blockY, blockZ)); - if (incorrectPositions.size() > Baritone.settings().incorrectSize.value) { - return; - } - } - continue; - } - // this is not in render distance - if (!observedCompleted.contains(BetterBlockPos.longHash(blockX, blockY, blockZ)) - && !Baritone.settings().buildSkipBlocks.value.contains(schematic.desiredState(x, y, z, current, this.approxPlaceable).getBlock())) { - // and we've never seen this position be correct - // therefore mark as incorrect - incorrectPositions.add(new BetterBlockPos(blockX, blockY, blockZ)); - if (incorrectPositions.size() > Baritone.settings().incorrectSize.value) { - return; - } - } - } - } - } - } - - private Goal assemble(BuilderCalculationContext bcc, List approxPlaceable) { - return assemble(bcc, approxPlaceable, false); - } - - private Goal assemble(BuilderCalculationContext bcc, List approxPlaceable, boolean logMissing) { - List placeable = new ArrayList<>(); - List breakable = new ArrayList<>(); - List sourceLiquids = new ArrayList<>(); - List flowingLiquids = new ArrayList<>(); - Map missing = new HashMap<>(); - incorrectPositions.forEach(pos -> { - BlockState state = bcc.bsi.get0(pos); - if (state.getBlock() instanceof AirBlock) { - if (containsBlockState(approxPlaceable, bcc.getSchematic(pos.x, pos.y, pos.z, state))) { - placeable.add(pos); - } else { - BlockState desired = bcc.getSchematic(pos.x, pos.y, pos.z, state); - missing.put(desired, 1 + missing.getOrDefault(desired, 0)); - } - } else { - if (state.getBlock() instanceof LiquidBlock) { - // if the block itself is JUST a liquid (i.e. not just a waterlogged block), we CANNOT break it - // TODO for 1.13 make sure that this only matches pure water, not waterlogged blocks - if (!MovementHelper.possiblyFlowing(state)) { - // if it's a source block then we want to replace it with a throwaway - sourceLiquids.add(pos); - } else { - flowingLiquids.add(pos); - } - } else { - breakable.add(pos); - } - } - }); - List toBreak = new ArrayList<>(); - breakable.forEach(pos -> toBreak.add(breakGoal(pos, bcc))); - List toPlace = new ArrayList<>(); - placeable.forEach(pos -> { - if (!placeable.contains(pos.below()) && !placeable.contains(pos.below(2))) { - toPlace.add(placementGoal(pos, bcc)); - } - }); - sourceLiquids.forEach(pos -> toPlace.add(new GoalBlock(pos.above()))); - - if (!toPlace.isEmpty()) { - return new JankyGoalComposite(new GoalComposite(toPlace.toArray(new Goal[0])), new GoalComposite(toBreak.toArray(new Goal[0]))); - } - if (toBreak.isEmpty()) { - if (logMissing && !missing.isEmpty()) { - logDirect("Missing materials for at least:"); - logDirect(missing.entrySet().stream() - .map(e -> String.format("%sx %s", e.getValue(), e.getKey())) - .collect(Collectors.joining("\n"))); - } - if (logMissing && !flowingLiquids.isEmpty()) { - logDirect("Unreplaceable liquids at at least:"); - logDirect(flowingLiquids.stream() - .map(p -> String.format("%s %s %s", p.x, p.y, p.z)) - .collect(Collectors.joining("\n"))); - } - return null; - } - return new GoalComposite(toBreak.toArray(new Goal[0])); - } - - public static class JankyGoalComposite implements Goal { - - private final Goal primary; - private final Goal fallback; - - public JankyGoalComposite(Goal primary, Goal fallback) { - this.primary = primary; - this.fallback = fallback; - } - - - @Override - public boolean isInGoal(int x, int y, int z) { - return primary.isInGoal(x, y, z) || fallback.isInGoal(x, y, z); - } - - @Override - public double heuristic(int x, int y, int z) { - return primary.heuristic(x, y, z); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - JankyGoalComposite goal = (JankyGoalComposite) o; - return Objects.equals(primary, goal.primary) - && Objects.equals(fallback, goal.fallback); - } - - @Override - public int hashCode() { - int hash = -1701079641; - hash = hash * 1196141026 + primary.hashCode(); - hash = hash * -80327868 + fallback.hashCode(); - return hash; - } - - @Override - public String toString() { - return "JankyComposite Primary: " + primary + " Fallback: " + fallback; - } - } - - 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 (y > this.y) { - return false; - } - // but any other adjacent works for breaking, including inside or below - return super.isInGoal(x, y, z); - } - - @Override - public String toString() { - return String.format( - "GoalBreak{x=%s,y=%s,z=%s}", - SettingsUtil.maybeCensor(x), - SettingsUtil.maybeCensor(y), - SettingsUtil.maybeCensor(z) - ); - } - - @Override - public int hashCode() { - return super.hashCode() * 1636324008; - } - } - - private Goal placementGoal(BlockPos pos, BuilderCalculationContext bcc) { - if (!(ctx.world().getBlockState(pos).getBlock() instanceof AirBlock)) { // TODO can this even happen? - return new GoalPlace(pos); - } - boolean allowSameLevel = !(ctx.world().getBlockState(pos.above()).getBlock() instanceof AirBlock); - BlockState current = ctx.world().getBlockState(pos); - for (Direction facing : Movement.HORIZONTALS_BUT_ALSO_DOWN_____SO_EVERY_DIRECTION_EXCEPT_UP) { - //noinspection ConstantConditions - if (MovementHelper.canPlaceAgainst(ctx, pos.relative(facing)) && placementPlausible(pos, bcc.getSchematic(pos.getX(), pos.getY(), pos.getZ(), current))) { - return new GoalAdjacent(pos, pos.relative(facing), allowSameLevel); - } - } - return new GoalPlace(pos); - } - - private Goal breakGoal(BlockPos pos, BuilderCalculationContext bcc) { - if (Baritone.settings().goalBreakFromAbove.value && bcc.bsi.get0(pos.above()).getBlock() instanceof AirBlock && bcc.bsi.get0(pos.above(2)).getBlock() instanceof AirBlock) { // TODO maybe possible without the up(2) check? - return new JankyGoalComposite(new GoalBreak(pos), new GoalGetToBlock(pos.above()) { - @Override - public boolean isInGoal(int x, int y, int z) { - if (y > this.y || (x == this.x && y == this.y && z == this.z)) { - return false; - } - return super.isInGoal(x, y, z); - } - }); - } - return new GoalBreak(pos); - } - - public static class GoalAdjacent extends GoalGetToBlock { - - private boolean allowSameLevel; - private BlockPos no; - - public GoalAdjacent(BlockPos pos, BlockPos no, boolean allowSameLevel) { - super(pos); - this.no = no; - this.allowSameLevel = allowSameLevel; - } - - @Override - public boolean isInGoal(int x, int y, int z) { - if (x == this.x && y == this.y && z == this.z) { - return false; - } - if (x == no.getX() && y == no.getY() && z == no.getZ()) { - return false; - } - if (!allowSameLevel && y == this.y - 1) { - return false; - } - if (y < this.y - 1) { - return false; - } - return super.isInGoal(x, y, z); - } - - @Override - public double heuristic(int x, int y, int z) { - // prioritize lower y coordinates - return this.y * 100 + super.heuristic(x, y, z); - } - - @Override - public boolean equals(Object o) { - if (!super.equals(o)) { - return false; - } - - GoalAdjacent goal = (GoalAdjacent) o; - return allowSameLevel == goal.allowSameLevel - && Objects.equals(no, goal.no); - } - - @Override - public int hashCode() { - int hash = 806368046; - hash = hash * 1412661222 + super.hashCode(); - hash = hash * 1730799370 + (int) BetterBlockPos.longHash(no.getX(), no.getY(), no.getZ()); - hash = hash * 260592149 + (allowSameLevel ? -1314802005 : 1565710265); - return hash; - } - - @Override - public String toString() { - return String.format( - "GoalAdjacent{x=%s,y=%s,z=%s}", - SettingsUtil.maybeCensor(x), - SettingsUtil.maybeCensor(y), - SettingsUtil.maybeCensor(z) - ); - } - } - - public static class GoalPlace extends GoalBlock { - - public GoalPlace(BlockPos placeAt) { - super(placeAt.above()); - } - - @Override - public double heuristic(int x, int y, int z) { - // prioritize lower y coordinates - return this.y * 100 + super.heuristic(x, y, z); - } - - @Override - public int hashCode() { - return super.hashCode() * 1910811835; - } - - @Override - public String toString() { - return String.format( - "GoalPlace{x=%s,y=%s,z=%s}", - SettingsUtil.maybeCensor(x), - SettingsUtil.maybeCensor(y), - SettingsUtil.maybeCensor(z) - ); - } - } - - @Override - public void onLostControl() { - incorrectPositions = null; - name = null; - schematic = null; - realSchematic = null; - layer = Baritone.settings().startAtLayer.value; - numRepeats = 0; - paused = false; - observedCompleted = null; - } - - @Override - public String displayName0() { - return paused ? "Builder Paused" : "Building " + name; - } - - private List approxPlaceable(int size) { - List result = new ArrayList<>(); - for (int i = 0; i < size; i++) { - ItemStack stack = ctx.player().getInventory().items.get(i); - if (stack.isEmpty() || !(stack.getItem() instanceof BlockItem)) { - result.add(Blocks.AIR.defaultBlockState()); - continue; - } - // - BlockState itemState = ((BlockItem) stack.getItem()) - .getBlock() - .getStateForPlacement( - new BlockPlaceContext( - new UseOnContext(ctx.world(), ctx.player(), InteractionHand.MAIN_HAND, stack, new BlockHitResult(new Vec3(ctx.player().position().x, ctx.player().position().y, ctx.player().position().z), Direction.UP, ctx.playerFeet(), false)) {} - ) - ); - if (itemState != null) { - result.add(itemState); - } else { - result.add(Blocks.AIR.defaultBlockState()); - } - // - } - return result; - } - - public static final Set> orientationProps = - ImmutableSet.of( - RotatedPillarBlock.AXIS, HorizontalDirectionalBlock.FACING, - StairBlock.FACING, StairBlock.HALF, StairBlock.SHAPE, - PipeBlock.NORTH, PipeBlock.EAST, PipeBlock.SOUTH, PipeBlock.WEST, PipeBlock.UP, - TrapDoorBlock.OPEN, TrapDoorBlock.HALF - ); - - private boolean sameBlockstate(BlockState first, BlockState second) { - if (first.getBlock() != second.getBlock()) { - return false; - } - boolean ignoreDirection = Baritone.settings().buildIgnoreDirection.value; - List ignoredProps = Baritone.settings().buildIgnoreProperties.value; - if (!ignoreDirection && ignoredProps.isEmpty()) { - return first.equals(second); // early return if no properties are being ignored - } - ImmutableMap, Comparable> map1 = first.getValues(); - ImmutableMap, Comparable> map2 = second.getValues(); - for (Property prop : map1.keySet()) { - if (map1.get(prop) != map2.get(prop) - && !(ignoreDirection && orientationProps.contains(prop)) - && !ignoredProps.contains(prop.getName())) { - return false; - } - } - return true; - } - - private boolean containsBlockState(Collection states, BlockState state) { - for (BlockState testee : states) { - if (sameBlockstate(testee, state)) { - return true; - } - } - return false; - } - - private boolean valid(BlockState current, BlockState desired, boolean itemVerify) { - if (desired == null) { - return true; - } - if (current.getBlock() instanceof LiquidBlock && Baritone.settings().okIfWater.value) { - return true; - } - if (current.getBlock() instanceof AirBlock && desired.getBlock() instanceof AirBlock) { - return true; - } - if (current.getBlock() instanceof AirBlock && Baritone.settings().okIfAir.value.contains(desired.getBlock())) { - return true; - } - if (desired.getBlock() instanceof AirBlock && Baritone.settings().buildIgnoreBlocks.value.contains(current.getBlock())) { - return true; - } - if (!(current.getBlock() instanceof AirBlock) && Baritone.settings().buildIgnoreExisting.value && !itemVerify) { - return true; - } - if (Baritone.settings().buildSkipBlocks.value.contains(desired.getBlock()) && !itemVerify) { - return true; - } - if (Baritone.settings().buildValidSubstitutes.value.getOrDefault(desired.getBlock(), Collections.emptyList()).contains(current.getBlock()) && !itemVerify) { - return true; - } - if (current.equals(desired)) { - return true; - } - return sameBlockstate(current, desired); - } - - public class BuilderCalculationContext extends CalculationContext { - - private final List placeable; - private final ISchematic schematic; - private final int originX; - private final int originY; - private final int originZ; - - public BuilderCalculationContext() { - super(BuilderProcess.this.baritone, true); // wew lad - this.placeable = approxPlaceable(9); - this.schematic = BuilderProcess.this.schematic; - this.originX = origin.getX(); - this.originY = origin.getY(); - this.originZ = origin.getZ(); - - this.jumpPenalty += 10; - this.backtrackCostFavoringCoefficient = 1; - } - - private BlockState getSchematic(int x, int y, int z, BlockState current) { - if (schematic.inSchematic(x - originX, y - originY, z - originZ, current)) { - return schematic.desiredState(x - originX, y - originY, z - originZ, current, BuilderProcess.this.approxPlaceable); - } else { - return null; - } - } - - @Override - public double costOfPlacingAt(int x, int y, int z, BlockState current) { - if (isPossiblyProtected(x, y, z) || !worldBorder.canPlaceAt(x, z)) { // make calculation fail properly if we can't build - return COST_INF; - } - BlockState sch = getSchematic(x, y, z, current); - if (sch != null && !Baritone.settings().buildSkipBlocks.value.contains(sch.getBlock())) { - // TODO this can return true even when allowPlace is off.... is that an issue? - if (sch.getBlock() instanceof AirBlock) { - // 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 (placeable.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 😎 - // i'm such an idiot that i just tried to copy and paste the epic gamer moment emoji too - // get added to unicode when? - } - if (!hasThrowaway) { - return COST_INF; - } - // 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; - } else { - return COST_INF; - } - } - } - - @Override - public double breakCostMultiplierAt(int x, int y, int z, BlockState current) { - if ((!allowBreak && !allowBreakAnyway.contains(current.getBlock())) || isPossiblyProtected(x, y, z)) { - return COST_INF; - } - BlockState sch = getSchematic(x, y, z, current); - if (sch != null && !Baritone.settings().buildSkipBlocks.value.contains(sch.getBlock())) { - if (sch.getBlock() instanceof AirBlock) { - // it should be air - // regardless of current contents, we can break it - return 1; - } - // it should be a real block - // is it already that block? - if (valid(bsi.get0(x, y, z), sch, false)) { - return Baritone.settings().breakCorrectBlockPenaltyMultiplier.value; - } 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 1; // why not lol - } - } - } - } - \ No newline at end of file +package baritone.process; + +import baritone.Baritone; +import baritone.api.pathing.goals.Goal; +import baritone.api.pathing.goals.GoalBlock; +import baritone.api.pathing.goals.GoalComposite; +import baritone.api.pathing.goals.GoalGetToBlock; +import baritone.api.process.IBuilderProcess; +import baritone.api.process.PathingCommand; +import baritone.api.process.PathingCommandType; +import baritone.api.schematic.FillSchematic; +import baritone.api.schematic.ISchematic; +import baritone.api.schematic.IStaticSchematic; +import baritone.api.schematic.SubstituteSchematic; +import baritone.api.schematic.format.ISchematicFormat; +import baritone.api.utils.*; +import baritone.api.utils.input.Input; +import baritone.pathing.movement.CalculationContext; +import baritone.pathing.movement.Movement; +import baritone.pathing.movement.MovementHelper; +import baritone.utils.BaritoneProcessHelper; +import baritone.utils.BlockStateInterface; +import baritone.utils.PathingCommandContext; +import baritone.utils.schematic.MapArtSchematic; +import baritone.utils.schematic.SelectionSchematic; +import baritone.utils.schematic.SchematicSystem; +import baritone.utils.schematic.format.defaults.LitematicaSchematic; +import baritone.utils.schematic.litematica.LitematicaHelper; +import baritone.utils.schematic.schematica.SchematicaHelper; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Vec3i; +import net.minecraft.nbt.NbtIo; +import net.minecraft.util.Tuple; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.level.block.AirBlock; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.HorizontalDirectionalBlock; +import net.minecraft.world.level.block.LiquidBlock; +import net.minecraft.world.level.block.PipeBlock; +import net.minecraft.world.level.block.RotatedPillarBlock; +import net.minecraft.world.level.block.StairBlock; +import net.minecraft.world.level.block.TrapDoorBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.Property; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.HitResult; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.VoxelShape; + +import java.io.File; +import java.io.FileInputStream; +import java.nio.file.Files; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static baritone.api.pathing.movement.ActionCosts.COST_INF; + +public final class BuilderProcess extends BaritoneProcessHelper implements IBuilderProcess { + + private HashSet incorrectPositions; + private LongOpenHashSet observedCompleted; // positions that are completed even if they're out of render distance and we can't make sure right now + private String name; + private ISchematic realSchematic; + private ISchematic schematic; + private Vec3i origin; + private int ticks; + private boolean paused; + private int layer; + private int numRepeats; + private List approxPlaceable; + public int stopAtHeight = 0; + + public BuilderProcess(Baritone baritone) { + super(baritone); + } + + @Override + public void build(String name, ISchematic schematic, Vec3i origin) { + this.name = name; + this.schematic = schematic; + this.realSchematic = null; + boolean buildingSelectionSchematic = schematic instanceof SelectionSchematic; + if (!Baritone.settings().buildSubstitutes.value.isEmpty()) { + this.schematic = new SubstituteSchematic(this.schematic, Baritone.settings().buildSubstitutes.value); + } + int x = origin.getX(); + int y = origin.getY(); + int z = origin.getZ(); + if (Baritone.settings().schematicOrientationX.value) { + x += schematic.widthX(); + } + if (Baritone.settings().schematicOrientationY.value) { + y += schematic.heightY(); + } + if (Baritone.settings().schematicOrientationZ.value) { + z += schematic.lengthZ(); + } + this.origin = new Vec3i(x, y, z); + this.paused = false; + this.layer = Baritone.settings().startAtLayer.value; + this.stopAtHeight = schematic.heightY(); + if (Baritone.settings().buildOnlySelection.value && buildingSelectionSchematic) { // currently redundant but safer maybe + if (baritone.getSelectionManager().getSelections().length == 0) { + logDirect("Poor little kitten forgot to set a selection while BuildOnlySelection is true"); + this.stopAtHeight = 0; + } else if (Baritone.settings().buildInLayers.value) { + OptionalInt minim = Stream.of(baritone.getSelectionManager().getSelections()).mapToInt(sel -> sel.min().y).min(); + OptionalInt maxim = Stream.of(baritone.getSelectionManager().getSelections()).mapToInt(sel -> sel.max().y).max(); + if (minim.isPresent() && maxim.isPresent()) { + int startAtHeight = Baritone.settings().layerOrder.value ? y + schematic.heightY() - maxim.getAsInt() : minim.getAsInt() - y; + this.stopAtHeight = (Baritone.settings().layerOrder.value ? y + schematic.heightY() - minim.getAsInt() : maxim.getAsInt() - y) + 1; + this.layer = Math.max(this.layer, startAtHeight / Baritone.settings().layerHeight.value); // startAtLayer or startAtHeight, whichever is highest + logDebug(String.format("Schematic starts at y=%s with height %s", y, schematic.heightY())); + logDebug(String.format("Selection starts at y=%s and ends at y=%s", minim.getAsInt(), maxim.getAsInt())); + logDebug(String.format("Considering relevant height %s - %s", startAtHeight, this.stopAtHeight)); + } + } + } + + this.numRepeats = 0; + this.observedCompleted = new LongOpenHashSet(); + this.incorrectPositions = null; + } + + public void resume() { + paused = false; + } + + public void pause() { + paused = true; + } + + @Override + public boolean isPaused() { + return paused; + } + + @Override + public boolean build(String name, File schematic, Vec3i origin) { + Optional format = SchematicSystem.INSTANCE.getByFile(schematic); + if (!format.isPresent()) { + return false; + } + ISchematic parsed; + try { + parsed = format.get().parse(new FileInputStream(schematic)); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + parsed = applyMapArtAndSelection(origin, (IStaticSchematic) parsed); + build(name, parsed, origin); + return true; + } + + private ISchematic applyMapArtAndSelection(Vec3i origin, IStaticSchematic parsed) { + ISchematic schematic = parsed; + if (Baritone.settings().mapArtMode.value) { + schematic = new MapArtSchematic(parsed); + } + if (Baritone.settings().buildOnlySelection.value) { + schematic = new SelectionSchematic(schematic, origin, baritone.getSelectionManager().getSelections()); + } + return schematic; + } + + @Override + public void buildOpenSchematic() { + if (SchematicaHelper.isSchematicaPresent()) { + Optional> schematic = SchematicaHelper.getOpenSchematic(); + if (schematic.isPresent()) { + IStaticSchematic s = schematic.get().getA(); + BlockPos origin = schematic.get().getB(); + ISchematic schem = Baritone.settings().mapArtMode.value ? new MapArtSchematic(s) : s; + if (Baritone.settings().buildOnlySelection.value) { + schem = new SelectionSchematic(schem, origin, baritone.getSelectionManager().getSelections()); + } + this.build( + schematic.get().getA().toString(), + schem, + origin + ); + } else { + logDirect("No schematic currently open"); + } + } else { + logDirect("Schematica is not present"); + } + } + + @Override + public void buildOpenLitematic() { + if (LitematicaHelper.isLitematicaPresent()) { + Integer selectedIndex = LitematicaHelper.getSelectedIndex(); + if (selectedIndex != -1) { + buildOpenLitematic(selectedIndex); + } else { + logDirect("No schematic currently selected"); + } + } else { + logDirect("Litematica is not present"); + } + } + + @Override + public void buildOpenLitematic(int i) { + if (LitematicaHelper.isLitematicaPresent()) { + //if java.lang.NoSuchMethodError is thrown see comment in SchematicPlacementManager + if (LitematicaHelper.hasLoadedSchematic()) { + String name = LitematicaHelper.getName(i); + try { + LitematicaSchematic schematic1 = new LitematicaSchematic(NbtIo.readCompressed(Files.newInputStream(LitematicaHelper.getSchematicFile(i).toPath())), false); + Vec3i correctedOrigin = LitematicaHelper.getCorrectedOrigin(schematic1, i); + ISchematic schematic2 = LitematicaHelper.blackMagicFuckery(schematic1, i); + schematic2 = applyMapArtAndSelection(origin, (IStaticSchematic) schematic2); + build(name, schematic2, correctedOrigin); + } catch (Exception e) { + logDirect("Schematic File could not be loaded."); + } + } else { + logDirect("No schematic currently loaded"); + } + } else { + logDirect("Litematica is not present"); + } + } + + public void clearArea(BlockPos corner1, BlockPos corner2) { + BlockPos origin = new BlockPos(Math.min(corner1.getX(), corner2.getX()), Math.min(corner1.getY(), corner2.getY()), Math.min(corner1.getZ(), corner2.getZ())); + int widthX = Math.abs(corner1.getX() - corner2.getX()) + 1; + int heightY = Math.abs(corner1.getY() - corner2.getY()) + 1; + int lengthZ = Math.abs(corner1.getZ() - corner2.getZ()) + 1; + build("clear area", new FillSchematic(widthX, heightY, lengthZ, Blocks.AIR.defaultBlockState()), origin); + } + + @Override + public List getApproxPlaceable() { + return new ArrayList<>(approxPlaceable); + } + + @Override + public boolean isActive() { + return schematic != null; + } + + public BlockState placeAt(int x, int y, int z, BlockState current) { + if (!isActive()) { + return null; + } + if (!schematic.inSchematic(x - origin.getX(), y - origin.getY(), z - origin.getZ(), current)) { + return null; + } + BlockState state = schematic.desiredState(x - origin.getX(), y - origin.getY(), z - origin.getZ(), current, this.approxPlaceable); + if (state.getBlock() instanceof AirBlock) { + return null; + } + return state; + } + + private Optional> toBreakNearPlayer(BuilderCalculationContext bcc) { + BetterBlockPos center = ctx.playerFeet(); + BetterBlockPos pathStart = baritone.getPathingBehavior().pathStart(); + for (int dx = -5; dx <= 5; dx++) { + for (int dy = Baritone.settings().breakFromAbove.value ? -1 : 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; + if (dy == -1 && x == pathStart.x && z == pathStart.z) { + continue; // dont mine what we're supported by, but not directly standing on + } + BlockState desired = bcc.getSchematic(x, y, z, bcc.bsi.get0(x, y, z)); + if (desired == null) { + continue; // irrelevant + } + BlockState curr = bcc.bsi.get0(x, y, z); + if (!(curr.getBlock() instanceof AirBlock) && !(curr.getBlock() == Blocks.WATER || curr.getBlock() == Blocks.LAVA) && !valid(curr, desired, false)) { + BetterBlockPos pos = new BetterBlockPos(x, y, z); + Optional rot = RotationUtils.reachable(ctx, pos, ctx.playerController().getBlockReachDistance()); + if (rot.isPresent()) { + return Optional.of(new Tuple<>(pos, rot.get())); + } + } + } + } + } + return Optional.empty(); + } + + public static class Placement { + + private final int hotbarSelection; + private final BlockPos placeAgainst; + private final Direction side; + private final Rotation rot; + + public Placement(int hotbarSelection, BlockPos placeAgainst, Direction side, Rotation rot) { + this.hotbarSelection = hotbarSelection; + this.placeAgainst = placeAgainst; + this.side = side; + this.rot = rot; + } + } + + private Optional searchForPlacables(BuilderCalculationContext bcc, List desirableOnHotbar) { + BetterBlockPos center = ctx.playerFeet(); + for (int dx = -5; dx <= 5; dx++) { + for (int dy = -5; dy <= 1; dy++) { + for (int dz = -5; dz <= 5; dz++) { + int x = center.x + dx; + int y = center.y + dy; + int z = center.z + dz; + BlockState desired = bcc.getSchematic(x, y, z, bcc.bsi.get0(x, y, z)); + if (desired == null) { + continue; // irrelevant + } + BlockState curr = bcc.bsi.get0(x, y, z); + if (MovementHelper.isReplaceable(x, y, z, curr, bcc.bsi) && !valid(curr, desired, false)) { + if (dy == 1 && bcc.bsi.get0(x, y + 1, z).getBlock() instanceof AirBlock) { + continue; + } + desirableOnHotbar.add(desired); + Optional opt = possibleToPlace(desired, x, y, z, bcc.bsi); + if (opt.isPresent()) { + return opt; + } + } + } + } + } + return Optional.empty(); + } + + public boolean placementPlausible(BlockPos pos, BlockState state) { + VoxelShape voxelshape = state.getCollisionShape(ctx.world(), pos); + return voxelshape.isEmpty() || ctx.world().isUnobstructed(null, voxelshape.move(pos.getX(), pos.getY(), pos.getZ())); + } + + private Optional possibleToPlace(BlockState toPlace, int x, int y, int z, BlockStateInterface bsi) { + for (Direction against : Direction.values()) { + BetterBlockPos placeAgainstPos = new BetterBlockPos(x, y, z).relative(against); + BlockState placeAgainstState = bsi.get0(placeAgainstPos); + if (MovementHelper.isReplaceable(placeAgainstPos.x, placeAgainstPos.y, placeAgainstPos.z, placeAgainstState, bsi)) { + continue; + } + if (!toPlace.canSurvive(ctx.world(), new BetterBlockPos(x, y, z))) { + continue; + } + if (!placementPlausible(new BetterBlockPos(x, y, z), toPlace)) { + continue; + } + AABB aabb = placeAgainstState.getShape(ctx.world(), placeAgainstPos).bounds(); + for (Vec3 placementMultiplier : aabbSideMultipliers(against)) { + double placeX = placeAgainstPos.x + aabb.minX * placementMultiplier.x + aabb.maxX * (1 - placementMultiplier.x); + double placeY = placeAgainstPos.y + aabb.minY * placementMultiplier.y + aabb.maxY * (1 - placementMultiplier.y); + double placeZ = placeAgainstPos.z + aabb.minZ * placementMultiplier.z + aabb.maxZ * (1 - placementMultiplier.z); + Rotation rot = RotationUtils.calcRotationFromVec3d(RayTraceUtils.inferSneakingEyePosition(ctx.player()), new Vec3(placeX, placeY, placeZ), ctx.playerRotations()); + Rotation actualRot = baritone.getLookBehavior().getAimProcessor().peekRotation(rot); + HitResult result = RayTraceUtils.rayTraceTowards(ctx.player(), actualRot, ctx.playerController().getBlockReachDistance(), true); + if (result != null && result.getType() == HitResult.Type.BLOCK && ((BlockHitResult) result).getBlockPos().equals(placeAgainstPos) && ((BlockHitResult) result).getDirection() == against.getOpposite()) { + OptionalInt hotbar = hasAnyItemThatWouldPlace(toPlace, result, actualRot); + if (hotbar.isPresent()) { + return Optional.of(new Placement(hotbar.getAsInt(), placeAgainstPos, against.getOpposite(), rot)); + } + } + } + } + return Optional.empty(); + } + + private OptionalInt hasAnyItemThatWouldPlace(BlockState desired, HitResult result, Rotation rot) { + for (int i = 0; i < 9; i++) { + ItemStack stack = ctx.player().getInventory().items.get(i); + if (stack.isEmpty() || !(stack.getItem() instanceof BlockItem)) { + continue; + } + float originalYaw = ctx.player().getYRot(); + float originalPitch = ctx.player().getXRot(); + // the state depends on the facing of the player sometimes + ctx.player().setYRot(rot.getYaw()); + ctx.player().setXRot(rot.getPitch()); + BlockPlaceContext meme = new BlockPlaceContext(new UseOnContext( + ctx.world(), + ctx.player(), + InteractionHand.MAIN_HAND, + stack, + (BlockHitResult) result + ) {}); // that {} gives us access to a protected constructor lmfao + BlockState wouldBePlaced = ((BlockItem) stack.getItem()).getBlock().getStateForPlacement(meme); + ctx.player().setYRot(originalYaw); + ctx.player().setXRot(originalPitch); + if (wouldBePlaced == null) { + continue; + } + if (!meme.canPlace()) { + continue; + } + if (valid(wouldBePlaced, desired, true)) { + return OptionalInt.of(i); + } + } + return OptionalInt.empty(); + } + + private static Vec3[] aabbSideMultipliers(Direction side) { + switch (side) { + case UP: + return new Vec3[]{new Vec3(0.5, 1, 0.5), new Vec3(0.1, 1, 0.5), new Vec3(0.9, 1, 0.5), new Vec3(0.5, 1, 0.1), new Vec3(0.5, 1, 0.9)}; + case DOWN: + return new Vec3[]{new Vec3(0.5, 0, 0.5), new Vec3(0.1, 0, 0.5), new Vec3(0.9, 0, 0.5), new Vec3(0.5, 0, 0.1), new Vec3(0.5, 0, 0.9)}; + case NORTH: + case SOUTH: + case EAST: + case WEST: + double x = side.getStepX() == 0 ? 0.5 : (1 + side.getStepX()) / 2D; + double z = side.getStepZ() == 0 ? 0.5 : (1 + side.getStepZ()) / 2D; + return new Vec3[]{new Vec3(x, 0.25, z), new Vec3(x, 0.75, z)}; + default: // null + throw new IllegalStateException(); + } + } + + @Override + public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { + return onTick(calcFailed, isSafeToCancel, 0); + } + + public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel, int recursions) { + if (recursions > 1000) { // onTick calls itself, don't crash + return new PathingCommand(null, PathingCommandType.SET_GOAL_AND_PATH); + } + approxPlaceable = approxPlaceable(36); + if (baritone.getInputOverrideHandler().isInputForcedDown(Input.CLICK_LEFT)) { + ticks = 5; + } else { + ticks--; + } + baritone.getInputOverrideHandler().clearAllKeys(); + if (paused) { + return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); + } + if (Baritone.settings().buildInLayers.value) { + if (realSchematic == null) { + realSchematic = schematic; + } + ISchematic realSchematic = this.realSchematic; // wrap this properly, dont just have the inner class refer to the builderprocess.this + int minYInclusive; + int maxYInclusive; + // layer = 0 should be nothing + // layer = realSchematic.heightY() should be everything + if (Baritone.settings().layerOrder.value) { // top to bottom + maxYInclusive = realSchematic.heightY() - 1; + minYInclusive = realSchematic.heightY() - layer * Baritone.settings().layerHeight.value; + } else { + maxYInclusive = layer * Baritone.settings().layerHeight.value - 1; + minYInclusive = 0; + } + schematic = new ISchematic() { + @Override + public BlockState desiredState(int x, int y, int z, BlockState current, List approxPlaceable) { + return realSchematic.desiredState(x, y, z, current, BuilderProcess.this.approxPlaceable); + } + + @Override + public boolean inSchematic(int x, int y, int z, BlockState currentState) { + return ISchematic.super.inSchematic(x, y, z, currentState) && y >= minYInclusive && y <= maxYInclusive && realSchematic.inSchematic(x, y, z, currentState); + } + + @Override + public void reset() { + realSchematic.reset(); + } + + @Override + public int widthX() { + return realSchematic.widthX(); + } + + @Override + public int heightY() { + return realSchematic.heightY(); + } + + @Override + public int lengthZ() { + return realSchematic.lengthZ(); + } + }; + } + BuilderCalculationContext bcc = new BuilderCalculationContext(); + if (!recalc(bcc)) { + if (Baritone.settings().buildInLayers.value && layer * Baritone.settings().layerHeight.value < stopAtHeight) { + logDirect("Starting layer " + layer); + layer++; + return onTick(calcFailed, isSafeToCancel, recursions + 1); + } + Vec3i repeat = Baritone.settings().buildRepeat.value; + int max = Baritone.settings().buildRepeatCount.value; + numRepeats++; + if (repeat.equals(new Vec3i(0, 0, 0)) || (max != -1 && numRepeats >= max)) { + logDirect("Done building"); + if (Baritone.settings().notificationOnBuildFinished.value) { + logNotification("Done building", false); + } + onLostControl(); + return null; + } + // build repeat time + layer = 0; + origin = new BlockPos(origin).offset(repeat); + if (!Baritone.settings().buildRepeatSneaky.value) { + schematic.reset(); + } + logDirect("Repeating build in vector " + repeat + ", new origin is " + origin); + return onTick(calcFailed, isSafeToCancel, recursions + 1); + } + if (Baritone.settings().distanceTrim.value) { + trim(); + } + + Optional> toBreak = toBreakNearPlayer(bcc); + if (toBreak.isPresent() && isSafeToCancel && ctx.player().isOnGround()) { + // 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().getB(); + BetterBlockPos pos = toBreak.get().getA(); + baritone.getLookBehavior().updateTarget(rot, true); + MovementHelper.switchToBestToolFor(ctx, bcc.get(pos)); + if (ctx.player().isCrouching()) { + // really horrible bug where a block is visible for breaking while sneaking but not otherwise + // so you can't see it, it goes to place something else, sneaks, then the next tick it tries to break + // and is unable since it's unsneaked in the intermediary tick + baritone.getInputOverrideHandler().setInputForceState(Input.SNEAK, true); + } + if (ctx.isLookingAt(pos) || ctx.playerRotations().isReallyCloseTo(rot)) { + baritone.getInputOverrideHandler().setInputForceState(Input.CLICK_LEFT, true); + } + return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); + } + List desirableOnHotbar = new ArrayList<>(); + Optional toPlace = searchForPlacables(bcc, desirableOnHotbar); + if (toPlace.isPresent() && isSafeToCancel && ctx.player().isOnGround() && ticks <= 0) { + Rotation rot = toPlace.get().rot; + baritone.getLookBehavior().updateTarget(rot, true); + ctx.player().getInventory().selected = toPlace.get().hotbarSelection; + baritone.getInputOverrideHandler().setInputForceState(Input.SNEAK, true); + if ((ctx.isLookingAt(toPlace.get().placeAgainst) && ((BlockHitResult) ctx.objectMouseOver()).getDirection().equals(toPlace.get().side)) || ctx.playerRotations().isReallyCloseTo(rot)) { + baritone.getInputOverrideHandler().setInputForceState(Input.CLICK_RIGHT, true); + } + return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); + } + + if (Baritone.settings().allowInventory.value) { + ArrayList usefulSlots = new ArrayList<>(); + List noValidHotbarOption = new ArrayList<>(); + outer: + for (BlockState desired : desirableOnHotbar) { + for (int i = 0; i < 9; i++) { + if (valid(approxPlaceable.get(i), desired, true)) { + usefulSlots.add(i); + continue outer; + } + } + noValidHotbarOption.add(desired); + } + + outer: + for (int i = 9; i < 36; i++) { + for (BlockState desired : noValidHotbarOption) { + if (valid(approxPlaceable.get(i), desired, true)) { + if (!baritone.getInventoryBehavior().attemptToPutOnHotbar(i, usefulSlots::contains)) { + // awaiting inventory move, so pause + return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE); + } + break outer; + } + } + } + } + + Goal goal = assemble(bcc, approxPlaceable.subList(0, 9)); + if (goal == null) { + goal = assemble(bcc, approxPlaceable, true); // we're far away, so assume that we have our whole inventory to recalculate placeable properly + if (goal == null) { + if (Baritone.settings().skipFailedLayers.value && Baritone.settings().buildInLayers.value && layer * Baritone.settings().layerHeight.value < realSchematic.heightY()) { + logDirect("Skipping layer that I cannot construct! Layer #" + layer); + layer++; + return onTick(calcFailed, isSafeToCancel, recursions + 1); + } + logDirect("Unable to do it. Pausing. resume to resume, cancel to cancel"); + paused = true; + return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE); + } + } + return new PathingCommandContext(goal, PathingCommandType.FORCE_REVALIDATE_GOAL_AND_PATH, bcc); + } + + private 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(); + } + + private void trim() { + HashSet copy = new HashSet<>(incorrectPositions); + copy.removeIf(pos -> pos.distSqr(ctx.player().blockPosition()) > 200); + if (!copy.isEmpty()) { + incorrectPositions = copy; + } + } + + private void recalcNearby(BuilderCalculationContext bcc) { + BetterBlockPos center = ctx.playerFeet(); + int radius = Baritone.settings().builderTickScanRadius.value; + for (int dx = -radius; dx <= radius; dx++) { + for (int dy = -radius; dy <= radius; dy++) { + for (int dz = -radius; dz <= radius; dz++) { + int x = center.x + dx; + int y = center.y + dy; + int z = center.z + dz; + BlockState desired = bcc.getSchematic(x, y, z, bcc.bsi.get0(x, y, z)); + if (desired != null) { + // we care about this position + BetterBlockPos pos = new BetterBlockPos(x, y, z); + if (valid(bcc.bsi.get0(x, y, z), desired, false)) { + incorrectPositions.remove(pos); + observedCompleted.add(BetterBlockPos.longHash(pos)); + } else { + incorrectPositions.add(pos); + observedCompleted.remove(BetterBlockPos.longHash(pos)); + } + } + } + } + } + } + + private 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++) { + int blockX = x + origin.getX(); + int blockY = y + origin.getY(); + int blockZ = z + origin.getZ(); + BlockState current = bcc.bsi.get0(blockX, blockY, blockZ); + if (!schematic.inSchematic(x, y, z, current)) { + continue; + } + if (bcc.bsi.worldContainsLoadedChunk(blockX, blockZ)) { // check if its in render distance, not if its in cache + // we can directly observe this block, it is in render distance + if (valid(bcc.bsi.get0(blockX, blockY, blockZ), schematic.desiredState(x, y, z, current, this.approxPlaceable), false)) { + observedCompleted.add(BetterBlockPos.longHash(blockX, blockY, blockZ)); + } else { + incorrectPositions.add(new BetterBlockPos(blockX, blockY, blockZ)); + observedCompleted.remove(BetterBlockPos.longHash(blockX, blockY, blockZ)); + if (incorrectPositions.size() > Baritone.settings().incorrectSize.value) { + return; + } + } + continue; + } + // this is not in render distance + if (!observedCompleted.contains(BetterBlockPos.longHash(blockX, blockY, blockZ)) + && !Baritone.settings().buildSkipBlocks.value.contains(schematic.desiredState(x, y, z, current, this.approxPlaceable).getBlock())) { + // and we've never seen this position be correct + // therefore mark as incorrect + incorrectPositions.add(new BetterBlockPos(blockX, blockY, blockZ)); + if (incorrectPositions.size() > Baritone.settings().incorrectSize.value) { + return; + } + } + } + } + } + } + + private Goal assemble(BuilderCalculationContext bcc, List approxPlaceable) { + return assemble(bcc, approxPlaceable, false); + } + + private Goal assemble(BuilderCalculationContext bcc, List approxPlaceable, boolean logMissing) { + List placeable = new ArrayList<>(); + List breakable = new ArrayList<>(); + List sourceLiquids = new ArrayList<>(); + List flowingLiquids = new ArrayList<>(); + Map missing = new HashMap<>(); + incorrectPositions.forEach(pos -> { + BlockState state = bcc.bsi.get0(pos); + if (state.getBlock() instanceof AirBlock) { + if (containsBlockState(approxPlaceable, bcc.getSchematic(pos.x, pos.y, pos.z, state))) { + placeable.add(pos); + } else { + BlockState desired = bcc.getSchematic(pos.x, pos.y, pos.z, state); + missing.put(desired, 1 + missing.getOrDefault(desired, 0)); + } + } else { + if (state.getBlock() instanceof LiquidBlock) { + // if the block itself is JUST a liquid (i.e. not just a waterlogged block), we CANNOT break it + // TODO for 1.13 make sure that this only matches pure water, not waterlogged blocks + if (!MovementHelper.possiblyFlowing(state)) { + // if it's a source block then we want to replace it with a throwaway + sourceLiquids.add(pos); + } else { + flowingLiquids.add(pos); + } + } else { + breakable.add(pos); + } + } + }); + List toBreak = new ArrayList<>(); + breakable.forEach(pos -> toBreak.add(breakGoal(pos, bcc))); + List toPlace = new ArrayList<>(); + placeable.forEach(pos -> { + if (!placeable.contains(pos.below()) && !placeable.contains(pos.below(2))) { + toPlace.add(placementGoal(pos, bcc)); + } + }); + sourceLiquids.forEach(pos -> toPlace.add(new GoalBlock(pos.above()))); + + if (!toPlace.isEmpty()) { + return new JankyGoalComposite(new GoalComposite(toPlace.toArray(new Goal[0])), new GoalComposite(toBreak.toArray(new Goal[0]))); + } + if (toBreak.isEmpty()) { + if (logMissing && !missing.isEmpty()) { + logDirect("Missing materials for at least:"); + logDirect(missing.entrySet().stream() + .map(e -> String.format("%sx %s", e.getValue(), e.getKey())) + .collect(Collectors.joining("\n"))); + } + if (logMissing && !flowingLiquids.isEmpty()) { + logDirect("Unreplaceable liquids at at least:"); + logDirect(flowingLiquids.stream() + .map(p -> String.format("%s %s %s", p.x, p.y, p.z)) + .collect(Collectors.joining("\n"))); + } + return null; + } + return new GoalComposite(toBreak.toArray(new Goal[0])); + } + + public static class JankyGoalComposite implements Goal { + + private final Goal primary; + private final Goal fallback; + + public JankyGoalComposite(Goal primary, Goal fallback) { + this.primary = primary; + this.fallback = fallback; + } + + + @Override + public boolean isInGoal(int x, int y, int z) { + return primary.isInGoal(x, y, z) || fallback.isInGoal(x, y, z); + } + + @Override + public double heuristic(int x, int y, int z) { + return primary.heuristic(x, y, z); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + JankyGoalComposite goal = (JankyGoalComposite) o; + return Objects.equals(primary, goal.primary) + && Objects.equals(fallback, goal.fallback); + } + + @Override + public int hashCode() { + int hash = -1701079641; + hash = hash * 1196141026 + primary.hashCode(); + hash = hash * -80327868 + fallback.hashCode(); + return hash; + } + + @Override + public String toString() { + return "JankyComposite Primary: " + primary + " Fallback: " + fallback; + } + } + + 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 (y > this.y) { + return false; + } + // but any other adjacent works for breaking, including inside or below + return super.isInGoal(x, y, z); + } + + @Override + public String toString() { + return String.format( + "GoalBreak{x=%s,y=%s,z=%s}", + SettingsUtil.maybeCensor(x), + SettingsUtil.maybeCensor(y), + SettingsUtil.maybeCensor(z) + ); + } + + @Override + public int hashCode() { + return super.hashCode() * 1636324008; + } + } + + private Goal placementGoal(BlockPos pos, BuilderCalculationContext bcc) { + if (!(ctx.world().getBlockState(pos).getBlock() instanceof AirBlock)) { // TODO can this even happen? + return new GoalPlace(pos); + } + boolean allowSameLevel = !(ctx.world().getBlockState(pos.above()).getBlock() instanceof AirBlock); + BlockState current = ctx.world().getBlockState(pos); + for (Direction facing : Movement.HORIZONTALS_BUT_ALSO_DOWN_____SO_EVERY_DIRECTION_EXCEPT_UP) { + //noinspection ConstantConditions + if (MovementHelper.canPlaceAgainst(ctx, pos.relative(facing)) && placementPlausible(pos, bcc.getSchematic(pos.getX(), pos.getY(), pos.getZ(), current))) { + return new GoalAdjacent(pos, pos.relative(facing), allowSameLevel); + } + } + return new GoalPlace(pos); + } + + private Goal breakGoal(BlockPos pos, BuilderCalculationContext bcc) { + if (Baritone.settings().goalBreakFromAbove.value && bcc.bsi.get0(pos.above()).getBlock() instanceof AirBlock && bcc.bsi.get0(pos.above(2)).getBlock() instanceof AirBlock) { // TODO maybe possible without the up(2) check? + return new JankyGoalComposite(new GoalBreak(pos), new GoalGetToBlock(pos.above()) { + @Override + public boolean isInGoal(int x, int y, int z) { + if (y > this.y || (x == this.x && y == this.y && z == this.z)) { + return false; + } + return super.isInGoal(x, y, z); + } + }); + } + return new GoalBreak(pos); + } + + public static class GoalAdjacent extends GoalGetToBlock { + + private boolean allowSameLevel; + private BlockPos no; + + public GoalAdjacent(BlockPos pos, BlockPos no, boolean allowSameLevel) { + super(pos); + this.no = no; + this.allowSameLevel = allowSameLevel; + } + + @Override + public boolean isInGoal(int x, int y, int z) { + if (x == this.x && y == this.y && z == this.z) { + return false; + } + if (x == no.getX() && y == no.getY() && z == no.getZ()) { + return false; + } + if (!allowSameLevel && y == this.y - 1) { + return false; + } + if (y < this.y - 1) { + return false; + } + return super.isInGoal(x, y, z); + } + + @Override + public double heuristic(int x, int y, int z) { + // prioritize lower y coordinates + return this.y * 100 + super.heuristic(x, y, z); + } + + @Override + public boolean equals(Object o) { + if (!super.equals(o)) { + return false; + } + + GoalAdjacent goal = (GoalAdjacent) o; + return allowSameLevel == goal.allowSameLevel + && Objects.equals(no, goal.no); + } + + @Override + public int hashCode() { + int hash = 806368046; + hash = hash * 1412661222 + super.hashCode(); + hash = hash * 1730799370 + (int) BetterBlockPos.longHash(no.getX(), no.getY(), no.getZ()); + hash = hash * 260592149 + (allowSameLevel ? -1314802005 : 1565710265); + return hash; + } + + @Override + public String toString() { + return String.format( + "GoalAdjacent{x=%s,y=%s,z=%s}", + SettingsUtil.maybeCensor(x), + SettingsUtil.maybeCensor(y), + SettingsUtil.maybeCensor(z) + ); + } + } + + public static class GoalPlace extends GoalBlock { + + public GoalPlace(BlockPos placeAt) { + super(placeAt.above()); + } + + @Override + public double heuristic(int x, int y, int z) { + // prioritize lower y coordinates + return this.y * 100 + super.heuristic(x, y, z); + } + + @Override + public int hashCode() { + return super.hashCode() * 1910811835; + } + + @Override + public String toString() { + return String.format( + "GoalPlace{x=%s,y=%s,z=%s}", + SettingsUtil.maybeCensor(x), + SettingsUtil.maybeCensor(y), + SettingsUtil.maybeCensor(z) + ); + } + } + + @Override + public void onLostControl() { + incorrectPositions = null; + name = null; + schematic = null; + realSchematic = null; + layer = Baritone.settings().startAtLayer.value; + numRepeats = 0; + paused = false; + observedCompleted = null; + } + + @Override + public String displayName0() { + return paused ? "Builder Paused" : "Building " + name; + } + + private List approxPlaceable(int size) { + List result = new ArrayList<>(); + for (int i = 0; i < size; i++) { + ItemStack stack = ctx.player().getInventory().items.get(i); + if (stack.isEmpty() || !(stack.getItem() instanceof BlockItem)) { + result.add(Blocks.AIR.defaultBlockState()); + continue; + } + // + BlockState itemState = ((BlockItem) stack.getItem()) + .getBlock() + .getStateForPlacement( + new BlockPlaceContext( + new UseOnContext(ctx.world(), ctx.player(), InteractionHand.MAIN_HAND, stack, new BlockHitResult(new Vec3(ctx.player().position().x, ctx.player().position().y, ctx.player().position().z), Direction.UP, ctx.playerFeet(), false)) {} + ) + ); + if (itemState != null) { + result.add(itemState); + } else { + result.add(Blocks.AIR.defaultBlockState()); + } + // + } + return result; + } + + public static final Set> orientationProps = + ImmutableSet.of( + RotatedPillarBlock.AXIS, HorizontalDirectionalBlock.FACING, + StairBlock.FACING, StairBlock.HALF, StairBlock.SHAPE, + PipeBlock.NORTH, PipeBlock.EAST, PipeBlock.SOUTH, PipeBlock.WEST, PipeBlock.UP, + TrapDoorBlock.OPEN, TrapDoorBlock.HALF + ); + + private boolean sameBlockstate(BlockState first, BlockState second) { + if (first.getBlock() != second.getBlock()) { + return false; + } + boolean ignoreDirection = Baritone.settings().buildIgnoreDirection.value; + List ignoredProps = Baritone.settings().buildIgnoreProperties.value; + if (!ignoreDirection && ignoredProps.isEmpty()) { + return first.equals(second); // early return if no properties are being ignored + } + ImmutableMap, Comparable> map1 = first.getValues(); + ImmutableMap, Comparable> map2 = second.getValues(); + for (Property prop : map1.keySet()) { + if (map1.get(prop) != map2.get(prop) + && !(ignoreDirection && orientationProps.contains(prop)) + && !ignoredProps.contains(prop.getName())) { + return false; + } + } + return true; + } + + private boolean containsBlockState(Collection states, BlockState state) { + for (BlockState testee : states) { + if (sameBlockstate(testee, state)) { + return true; + } + } + return false; + } + + private boolean valid(BlockState current, BlockState desired, boolean itemVerify) { + if (desired == null) { + return true; + } + if (current.getBlock() instanceof LiquidBlock && Baritone.settings().okIfWater.value) { + return true; + } + if (current.getBlock() instanceof AirBlock && desired.getBlock() instanceof AirBlock) { + return true; + } + if (current.getBlock() instanceof AirBlock && Baritone.settings().okIfAir.value.contains(desired.getBlock())) { + return true; + } + if (desired.getBlock() instanceof AirBlock && Baritone.settings().buildIgnoreBlocks.value.contains(current.getBlock())) { + return true; + } + if (!(current.getBlock() instanceof AirBlock) && Baritone.settings().buildIgnoreExisting.value && !itemVerify) { + return true; + } + if (Baritone.settings().buildSkipBlocks.value.contains(desired.getBlock()) && !itemVerify) { + return true; + } + if (Baritone.settings().buildValidSubstitutes.value.getOrDefault(desired.getBlock(), Collections.emptyList()).contains(current.getBlock()) && !itemVerify) { + return true; + } + if (current.equals(desired)) { + return true; + } + return sameBlockstate(current, desired); + } + + public class BuilderCalculationContext extends CalculationContext { + + private final List placeable; + private final ISchematic schematic; + private final int originX; + private final int originY; + private final int originZ; + + public BuilderCalculationContext() { + super(BuilderProcess.this.baritone, true); // wew lad + this.placeable = approxPlaceable(9); + this.schematic = BuilderProcess.this.schematic; + this.originX = origin.getX(); + this.originY = origin.getY(); + this.originZ = origin.getZ(); + + this.jumpPenalty += 10; + this.backtrackCostFavoringCoefficient = 1; + } + + private BlockState getSchematic(int x, int y, int z, BlockState current) { + if (schematic.inSchematic(x - originX, y - originY, z - originZ, current)) { + return schematic.desiredState(x - originX, y - originY, z - originZ, current, BuilderProcess.this.approxPlaceable); + } else { + return null; + } + } + + @Override + public double costOfPlacingAt(int x, int y, int z, BlockState current) { + if (isPossiblyProtected(x, y, z) || !worldBorder.canPlaceAt(x, z)) { // make calculation fail properly if we can't build + return COST_INF; + } + BlockState sch = getSchematic(x, y, z, current); + if (sch != null && !Baritone.settings().buildSkipBlocks.value.contains(sch.getBlock())) { + // TODO this can return true even when allowPlace is off.... is that an issue? + if (sch.getBlock() instanceof AirBlock) { + // 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 (placeable.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 😎 + // i'm such an idiot that i just tried to copy and paste the epic gamer moment emoji too + // get added to unicode when? + } + if (!hasThrowaway) { + return COST_INF; + } + // 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; + } else { + return COST_INF; + } + } + } + + @Override + public double breakCostMultiplierAt(int x, int y, int z, BlockState current) { + if ((!allowBreak && !allowBreakAnyway.contains(current.getBlock())) || isPossiblyProtected(x, y, z)) { + return COST_INF; + } + BlockState sch = getSchematic(x, y, z, current); + if (sch != null && !Baritone.settings().buildSkipBlocks.value.contains(sch.getBlock())) { + if (sch.getBlock() instanceof AirBlock) { + // it should be air + // regardless of current contents, we can break it + return 1; + } + // it should be a real block + // is it already that block? + if (valid(bsi.get0(x, y, z), sch, false)) { + return Baritone.settings().breakCorrectBlockPenaltyMultiplier.value; + } 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 1; // why not lol + } + } + } +}