/* * This file is part of Baritone. * * Baritone is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Baritone is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Baritone. If not, see . */ package baritone.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.SubstituteSchematic; import baritone.api.schematic.ISchematic; import baritone.api.schematic.IStaticSchematic; import baritone.api.schematic.format.ISchematicFormat; import baritone.api.utils.BetterBlockPos; import baritone.api.utils.RayTraceUtils; import baritone.api.utils.Rotation; import baritone.api.utils.RotationUtils; import baritone.api.utils.input.Input; import baritone.pathing.movement.CalculationContext; import baritone.pathing.movement.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.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.block.*; import net.minecraft.block.properties.IProperty; import net.minecraft.block.state.IBlockState; import net.minecraft.init.Blocks; import net.minecraft.item.ItemBlock; import net.minecraft.item.ItemStack; import net.minecraft.util.EnumFacing; import net.minecraft.util.Tuple; import net.minecraft.util.math.*; import java.io.File; import java.io.FileInputStream; import java.util.*; import java.util.stream.Collectors; 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 BuilderProcess(Baritone baritone) { super(baritone); } @Override public void build(String name, ISchematic schematic, Vec3i origin) { this.name = name; this.schematic = schematic; this.realSchematic = null; 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.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; } if (Baritone.settings().mapArtMode.value) { parsed = new MapArtSchematic((IStaticSchematic) parsed); } if (Baritone.settings().buildOnlySelection.value) { parsed = new SelectionSchematic(parsed, origin, baritone.getSelectionManager().getSelections()); } build(name, parsed, origin); return true; } @Override public void buildOpenSchematic() { if (SchematicaHelper.isSchematicaPresent()) { Optional> schematic = SchematicaHelper.getOpenSchematic(); if (schematic.isPresent()) { IStaticSchematic s = schematic.get().getFirst(); BlockPos origin = schematic.get().getSecond(); 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().getFirst().toString(), schem, origin ); } else { logDirect("No schematic currently open"); } } else { logDirect("Schematica is not present"); } } @Override public void buildOpenLitematic() { logDirect("start building open litematic"); if (LitematicaHelper.isLitematicaPresent()) { logDirect("litematica is present"); //TODO debug line remove if (LitematicaHelper.hasLoadedSchematic()) { logDirect("a schematic is present"); //TODO debug line remove String name = LitematicaHelper.getName(0); File schemFile = LitematicaHelper.getSchematicFile(0); Vec3i origin = LitematicaHelper.getOrigin(0); boolean success = build(name, schemFile, origin); if (success) { logDirect(String.format("Building Schematic: %s\nOrigion: %s",name,origin)); } else { logDirect("Couldn't load the schematic. That is strange."); //this should happen as invalid schematics should not be abel to be loaded in litematica in the first place } } 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.getDefaultState()), origin); } @Override public List getApproxPlaceable() { return new ArrayList<>(approxPlaceable); } @Override public boolean isActive() { return schematic != null; } public IBlockState placeAt(int x, int y, int z, IBlockState current) { if (!isActive()) { return null; } if (!schematic.inSchematic(x - origin.getX(), y - origin.getY(), z - origin.getZ(), current)) { return null; } IBlockState state = schematic.desiredState(x - origin.getX(), y - origin.getY(), z - origin.getZ(), current, this.approxPlaceable); if (state.getBlock() == Blocks.AIR) { 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 } IBlockState desired = bcc.getSchematic(x, y, z, bcc.bsi.get0(x, y, z)); if (desired == null) { continue; // irrelevant } IBlockState curr = bcc.bsi.get0(x, y, z); if (curr.getBlock() != Blocks.AIR && !(curr.getBlock() instanceof BlockLiquid) && !valid(curr, desired, false)) { BetterBlockPos pos = new BetterBlockPos(x, y, z); Optional rot = RotationUtils.reachable(ctx.player(), pos, ctx.playerController().getBlockReachDistance()); if (rot.isPresent()) { return Optional.of(new Tuple<>(pos, rot.get())); } } } } } return Optional.empty(); } public static class Placement { private final int hotbarSelection; private final BlockPos placeAgainst; private final EnumFacing side; private final Rotation rot; public Placement(int hotbarSelection, BlockPos placeAgainst, EnumFacing side, Rotation rot) { this.hotbarSelection = hotbarSelection; this.placeAgainst = placeAgainst; this.side = side; this.rot = rot; } } private Optional searchForPlaceables(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; IBlockState desired = bcc.getSchematic(x, y, z, bcc.bsi.get0(x, y, z)); if (desired == null) { continue; // irrelevant } IBlockState 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() == Blocks.AIR) { continue; } desirableOnHotbar.add(desired); Optional opt = possibleToPlace(desired, x, y, z, bcc.bsi); if (opt.isPresent()) { return opt; } } } } } return Optional.empty(); } private Optional possibleToPlace(IBlockState toPlace, int x, int y, int z, BlockStateInterface bsi) { for (EnumFacing against : EnumFacing.values()) { BetterBlockPos placeAgainstPos = new BetterBlockPos(x, y, z).offset(against); IBlockState placeAgainstState = bsi.get0(placeAgainstPos); if (MovementHelper.isReplaceable(placeAgainstPos.x, placeAgainstPos.y, placeAgainstPos.z, placeAgainstState, bsi)) { continue; } if (!ctx.world().mayPlace(toPlace.getBlock(), new BetterBlockPos(x, y, z), false, against, null)) { continue; } AxisAlignedBB aabb = placeAgainstState.getBoundingBox(ctx.world(), placeAgainstPos); for (Vec3d 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 Vec3d(placeX, placeY, placeZ), ctx.playerRotations()); RayTraceResult result = RayTraceUtils.rayTraceTowards(ctx.player(), rot, ctx.playerController().getBlockReachDistance(), true); if (result != null && result.typeOfHit == RayTraceResult.Type.BLOCK && result.getBlockPos().equals(placeAgainstPos) && result.sideHit == against.getOpposite()) { OptionalInt hotbar = hasAnyItemThatWouldPlace(toPlace, result, rot); if (hotbar.isPresent()) { return Optional.of(new Placement(hotbar.getAsInt(), placeAgainstPos, against.getOpposite(), rot)); } } } } return Optional.empty(); } private OptionalInt hasAnyItemThatWouldPlace(IBlockState desired, RayTraceResult result, Rotation rot) { for (int i = 0; i < 9; i++) { ItemStack stack = ctx.player().inventory.mainInventory.get(i); if (stack.isEmpty() || !(stack.getItem() instanceof ItemBlock)) { continue; } float originalYaw = ctx.player().rotationYaw; float originalPitch = ctx.player().rotationPitch; // the state depends on the facing of the player sometimes ctx.player().rotationYaw = rot.getYaw(); ctx.player().rotationPitch = rot.getPitch(); IBlockState wouldBePlaced = ((ItemBlock) stack.getItem()).getBlock().getStateForPlacement( ctx.world(), result.getBlockPos().offset(result.sideHit), result.sideHit, (float) result.hitVec.x - result.getBlockPos().getX(), // as in PlayerControllerMP (float) result.hitVec.y - result.getBlockPos().getY(), (float) result.hitVec.z - result.getBlockPos().getZ(), stack.getItem().getMetadata(stack.getMetadata()), ctx.player() ); ctx.player().rotationYaw = originalYaw; ctx.player().rotationPitch = originalPitch; if (valid(wouldBePlaced, desired, true)) { return OptionalInt.of(i); } } return OptionalInt.empty(); } private static Vec3d[] aabbSideMultipliers(EnumFacing side) { switch (side) { case UP: return new Vec3d[]{new Vec3d(0.5, 1, 0.5), new Vec3d(0.1, 1, 0.5), new Vec3d(0.9, 1, 0.5), new Vec3d(0.5, 1, 0.1), new Vec3d(0.5, 1, 0.9)}; case DOWN: return new Vec3d[]{new Vec3d(0.5, 0, 0.5), new Vec3d(0.1, 0, 0.5), new Vec3d(0.9, 0, 0.5), new Vec3d(0.5, 0, 0.1), new Vec3d(0.5, 0, 0.9)}; case NORTH: case SOUTH: case EAST: case WEST: double x = side.getXOffset() == 0 ? 0.5 : (1 + side.getXOffset()) / 2D; double z = side.getZOffset() == 0 ? 0.5 : (1 + side.getZOffset()) / 2D; return new Vec3d[]{new Vec3d(x, 0.25, z), new Vec3d(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 IBlockState desiredState(int x, int y, int z, IBlockState current, List approxPlaceable) { return realSchematic.desiredState(x, y, z, current, BuilderProcess.this.approxPlaceable); } @Override public boolean inSchematic(int x, int y, int z, IBlockState 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 < realSchematic.heightY()) { 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).add(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().getSecond(); BetterBlockPos pos = toBreak.get().getFirst(); baritone.getLookBehavior().updateTarget(rot, true); MovementHelper.switchToBestToolFor(ctx, bcc.get(pos)); if (ctx.player().isSneaking()) { // 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 = searchForPlaceables(bcc, desirableOnHotbar); if (toPlace.isPresent() && isSafeToCancel && ctx.player().onGround && ticks <= 0) { Rotation rot = toPlace.get().rot; baritone.getLookBehavior().updateTarget(rot, true); ctx.player().inventory.currentItem = toPlace.get().hotbarSelection; baritone.getInputOverrideHandler().setInputForceState(Input.SNEAK, true); if ((ctx.isLookingAt(toPlace.get().placeAgainst) && ctx.objectMouseOver().sideHit.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 (IBlockState 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 (IBlockState desired : noValidHotbarOption) { if (valid(approxPlaceable.get(i), desired, true)) { baritone.getInventoryBehavior().attemptToPutOnHotbar(i, usefulSlots::contains); 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.distanceSq(ctx.player().posX, ctx.player().posY, ctx.player().posZ) > 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; IBlockState 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(); IBlockState 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 -> { IBlockState state = bcc.bsi.get0(pos); if (state.getBlock() instanceof BlockAir) { if (approxPlaceable.contains(bcc.getSchematic(pos.x, pos.y, pos.z, state))) { placeable.add(pos); } else { IBlockState desired = bcc.getSchematic(pos.x, pos.y, pos.z, state); missing.put(desired, 1 + missing.getOrDefault(desired, 0)); } } else { if (state.getBlock() instanceof BlockLiquid) { // 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.down()) && !placeable.contains(pos.down(2))) { toPlace.add(placementGoal(pos, bcc)); } }); sourceLiquids.forEach(pos -> toPlace.add(new GoalBlock(pos.up()))); 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 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); } } private Goal placementGoal(BlockPos pos, BuilderCalculationContext bcc) { if (ctx.world().getBlockState(pos).getBlock() != Blocks.AIR) { // TODO can this even happen? return new GoalPlace(pos); } boolean allowSameLevel = ctx.world().getBlockState(pos.up()).getBlock() != Blocks.AIR; IBlockState current = ctx.world().getBlockState(pos); for (EnumFacing facing : Movement.HORIZONTALS_BUT_ALSO_DOWN_____SO_EVERY_DIRECTION_EXCEPT_UP) { //noinspection ConstantConditions if (MovementHelper.canPlaceAgainst(ctx, pos.offset(facing)) && ctx.world().mayPlace(bcc.getSchematic(pos.getX(), pos.getY(), pos.getZ(), current).getBlock(), pos, false, facing, null)) { return new GoalAdjacent(pos, pos.offset(facing), allowSameLevel); } } return new GoalPlace(pos); } private Goal breakGoal(BlockPos pos, BuilderCalculationContext bcc) { if (Baritone.settings().goalBreakFromAbove.value && bcc.bsi.get0(pos.up()).getBlock() instanceof BlockAir && bcc.bsi.get0(pos.up(2)).getBlock() instanceof BlockAir) { // TODO maybe possible without the up(2) check? return new JankyGoalComposite(new GoalBreak(pos), new GoalGetToBlock(pos.up()) { @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; } 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); } public double heuristic(int x, int y, int z) { // prioritize lower y coordinates return this.y * 100 + super.heuristic(x, y, z); } } public static class GoalPlace extends GoalBlock { public GoalPlace(BlockPos placeAt) { super(placeAt.up()); } public double heuristic(int x, int y, int z) { // prioritize lower y coordinates return this.y * 100 + super.heuristic(x, y, 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().inventory.mainInventory.get(i); if (stack.isEmpty() || !(stack.getItem() instanceof ItemBlock)) { result.add(Blocks.AIR.getDefaultState()); continue; } // result.add(((ItemBlock) stack.getItem()).getBlock().getStateForPlacement(ctx.world(), ctx.playerFeet(), EnumFacing.UP, (float) ctx.player().posX, (float) ctx.player().posY, (float) ctx.player().posZ, stack.getItem().getMetadata(stack.getMetadata()), ctx.player())); // } return result; } public static final Set> orientationProps = ImmutableSet.of(BlockRotatedPillar.AXIS, BlockLog.LOG_AXIS, BlockHorizontal.FACING, BlockStairs.FACING, BlockStairs.HALF, BlockStairs.SHAPE, BlockPane.NORTH, BlockPane.EAST, BlockPane.SOUTH, BlockPane.WEST, BlockVine.UP, BlockTrapDoor.OPEN, BlockTrapDoor.HALF ); private boolean sameWithoutOrientation(IBlockState first, IBlockState second) { if (first.getBlock() != second.getBlock()) { return false; } ImmutableMap, Comparable> map1 = first.getProperties(); ImmutableMap, Comparable> map2 = second.getProperties(); for (IProperty prop : map1.keySet()) { if (map1.get(prop) != map2.get(prop) && !orientationProps.contains(prop)) { return false; } } return true; } private boolean valid(IBlockState current, IBlockState desired, boolean itemVerify) { if (desired == null) { return true; } if (current.getBlock() instanceof BlockLiquid && Baritone.settings().okIfWater.value) { return true; } if (current.getBlock() instanceof BlockAir && Baritone.settings().okIfAir.value.contains(desired.getBlock())) { return true; } if (desired.getBlock() instanceof BlockAir && Baritone.settings().buildIgnoreBlocks.value.contains(current.getBlock())) { return true; } if (!(current.getBlock() instanceof BlockAir) && 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 Baritone.settings().buildIgnoreDirection.value && sameWithoutOrientation(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 IBlockState getSchematic(int x, int y, int z, IBlockState 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, IBlockState current) { if (isPossiblyProtected(x, y, z) || !worldBorder.canPlaceAt(x, z)) { // make calculation fail properly if we can't build return COST_INF; } IBlockState 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() == Blocks.AIR) { // we want this to be air, but they're asking if they can place here // this won't be a schematic block, this will be a throwaway return placeBlockCost * 2; // we're going to have to break it eventually } if (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, IBlockState current) { if ((!allowBreak && !allowBreakAnyway.contains(current.getBlock())) || isPossiblyProtected(x, y, z)) { return COST_INF; } IBlockState sch = getSchematic(x, y, z, current); if (sch != null && !Baritone.settings().buildSkipBlocks.value.contains(sch.getBlock())) { if (sch.getBlock() == Blocks.AIR) { // 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 } } } }