preliminary schematic stuff

This commit is contained in:
Leijurv 2019-01-08 20:45:02 -08:00
parent 8ed2431f37
commit 702b916948
No known key found for this signature in database
GPG Key ID: 44A3EA646EADAC6A
10 changed files with 346 additions and 43 deletions

View File

@ -28,9 +28,9 @@ import net.minecraft.util.math.BlockPos;
*/
public class GoalGetToBlock implements Goal, IGoalRenderPos {
private final int x;
private final int y;
private final int z;
public final int x;
public final int y;
public final int z;
public GoalGetToBlock(BlockPos pos) {
this.x = pos.getX();

View File

@ -57,6 +57,7 @@ public final class PathingBehavior extends Behavior implements IPathingBehavior,
private boolean safeToCancel;
private boolean pauseRequestedLastTick;
private boolean unpausedLastTick;
private boolean cancelRequested;
private boolean calcFailedLastTick;
@ -102,10 +103,14 @@ public final class PathingBehavior extends Behavior implements IPathingBehavior,
private void tickPath() {
if (pauseRequestedLastTick && safeToCancel) {
pauseRequestedLastTick = false;
baritone.getInputOverrideHandler().clearAllKeys();
baritone.getInputOverrideHandler().getBlockBreakHelper().stopBreakingBlock();
if (unpausedLastTick) {
baritone.getInputOverrideHandler().clearAllKeys();
baritone.getInputOverrideHandler().getBlockBreakHelper().stopBreakingBlock();
}
unpausedLastTick = false;
return;
}
unpausedLastTick = true;
if (cancelRequested) {
cancelRequested = false;
baritone.getInputOverrideHandler().clearAllKeys();

View File

@ -142,11 +142,14 @@ public class CalculationContext {
return placeBlockCost;
}
public boolean canBreakAt(int x, int y, int z) {
public double breakCostMultiplierAt(int x, int y, int z) {
if (!allowBreak) {
return false;
return COST_INF;
}
return !isPossiblyProtected(x, y, z);
if (isPossiblyProtected(x, y, z)) {
return COST_INF;
}
return 1;
}
public double placeBucketCost() {

View File

@ -341,7 +341,8 @@ public interface MovementHelper extends ActionCosts, Helper {
static double getMiningDurationTicks(CalculationContext context, int x, int y, int z, IBlockState state, boolean includeFalling) {
Block block = state.getBlock();
if (!canWalkThrough(context.bsi, x, y, z, state)) {
if (!context.canBreakAt(x, y, z)) {
double mult = context.breakCostMultiplierAt(x, y, z);
if (mult >= COST_INF) {
return COST_INF;
}
if (avoidBreaking(context.bsi, x, y, z, state)) {
@ -358,6 +359,7 @@ public interface MovementHelper extends ActionCosts, Helper {
double result = m / strVsBlock;
result += context.breakBlockAdditionalCost;
result *= mult;
if (includeFalling) {
IBlockState above = context.get(x, y + 1, z);
if (above.getBlock() instanceof BlockFalling) {

View File

@ -240,4 +240,10 @@ public class MovementAscend extends Movement {
}
return true;
}
@Override
public boolean safeToCancel(MovementState state) {
// if we had to place, don't allow pause
return state.getStatus() != MovementStatus.RUNNING || ticksWithoutPlacement == 0;
}
}

View File

@ -35,6 +35,8 @@ import net.minecraft.init.Blocks;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import java.util.Objects;
public class MovementPillar extends Movement {
public MovementPillar(IBaritone baritone, BetterBlockPos start, BetterBlockPos end) {
@ -229,7 +231,7 @@ public class MovementPillar extends Movement {
if (!(fr instanceof BlockAir || fr.isReplaceable(ctx.world(), src))) {
state.setInput(Input.CLICK_LEFT, true);
blockIsThere = false;
} else if (ctx.player().isSneaking()) { // 1 tick after we're able to place
} else if (ctx.player().isSneaking() && (Objects.equals(src.down(), ctx.objectMouseOver().getBlockPos()) || Objects.equals(src, ctx.objectMouseOver().getBlockPos()))) { // 1 tick after we're able to place
state.setInput(Input.CLICK_RIGHT, true);
}
}

View File

@ -25,11 +25,15 @@ import baritone.api.pathing.goals.GoalGetToBlock;
import baritone.api.process.PathingCommand;
import baritone.api.process.PathingCommandType;
import baritone.api.utils.BetterBlockPos;
import baritone.api.utils.Rotation;
import baritone.api.utils.RotationUtils;
import baritone.api.utils.input.Input;
import baritone.pathing.movement.CalculationContext;
import baritone.pathing.movement.MovementHelper;
import baritone.utils.BaritoneProcessHelper;
import baritone.utils.BlockStateInterface;
import baritone.utils.ISchematic;
import baritone.utils.PathingCommandContext;
import baritone.utils.Schematic;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.init.Blocks;
@ -38,14 +42,15 @@ import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompressedStreamTools;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.Tuple;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3i;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
import static baritone.api.pathing.movement.ActionCosts.COST_INF;
@ -80,7 +85,7 @@ public class BuilderProcess extends BaritoneProcessHelper {
}
private static ISchematic parse(NBTTagCompound schematic) {
throw new UnsupportedOperationException("would rather die than parse " + schematic);
return new Schematic(schematic);
}
@Override
@ -88,6 +93,32 @@ public class BuilderProcess extends BaritoneProcessHelper {
return schematic != null;
}
public Optional<Tuple<BetterBlockPos, Rotation>> toBreakNearPlayer(BuilderCalculationContext bcc) {
BetterBlockPos center = ctx.playerFeet();
for (int dx = -5; dx <= 5; dx++) {
for (int dy = 0; dy <= 5; dy++) {
for (int dz = -5; dz <= 5; dz++) {
int x = center.x + dx;
int y = center.y + dy;
int z = center.z + dz;
IBlockState desired = bcc.getSchematic(x, y, z);
if (desired == null) {
continue; // irrelevant
}
IBlockState curr = bcc.bsi.get0(x, y, z);
if (curr.getBlock() != Blocks.AIR && !valid(curr, desired)) {
BetterBlockPos pos = new BetterBlockPos(x, y, z);
Optional<Rotation> rot = RotationUtils.reachable(ctx.player(), pos, ctx.playerController().getBlockReachDistance());
if (rot.isPresent()) {
return Optional.of(new Tuple<>(pos, rot.get()));
}
}
}
}
}
return Optional.empty();
}
@Override
public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) {
// TODO somehow tell inventorybehavior what we'd like to have on the hotbar
@ -97,22 +128,113 @@ public class BuilderProcess extends BaritoneProcessHelper {
// this will work as is, but it'll be trashy
// need to iterate over incorrectPositions and see which ones we can "correct" from our current standing position
// considerations:
// shouldn't break blocks that are supporting our current path segment, maybe?
//
return new PathingCommandContext(new GoalComposite(assemble()), PathingCommandType.FORCE_REVALIDATE_GOAL_AND_PATH, new BuilderCalculationContext(schematic, origin));
BuilderCalculationContext bcc = new BuilderCalculationContext(schematic, origin);
if (!recalc(bcc)) {
logDirect("Done building");
onLostControl();
return null;
}
Optional<Tuple<BetterBlockPos, Rotation>> toBreak = toBreakNearPlayer(bcc);
baritone.getInputOverrideHandler().clearAllKeys();
if (toBreak.isPresent() && isSafeToCancel && ctx.player().onGround) {
// we'd like to pause to break this block
// only change look direction if it's safe (don't want to fuck up an in progress parkour for example
Rotation rot = toBreak.get().getSecond();
BetterBlockPos pos = toBreak.get().getFirst();
baritone.getLookBehavior().updateTarget(rot, true);
MovementHelper.switchToBestToolFor(ctx, bcc.get(pos));
if (Objects.equals(ctx.objectMouseOver().getBlockPos(), rot) || ctx.playerRotations().isReallyCloseTo(rot)) {
baritone.getInputOverrideHandler().setInputForceState(Input.CLICK_LEFT, true);
}
return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE);
}
Goal[] goals = assemble(bcc);
if (goals.length == 0) {
logDirect("Unable to do it =(");
onLostControl();
return null;
}
return new PathingCommandContext(new GoalComposite(goals), PathingCommandType.FORCE_REVALIDATE_GOAL_AND_PATH, bcc);
}
private Goal[] assemble() {
BlockStateInterface bsi = new CalculationContext(baritone).bsi;
return incorrectPositions.stream().map(pos ->
bsi.get0(pos).getBlock() == Blocks.AIR ?
// it's air and it shouldn't be
new GoalBlock(pos.up())
// it's a block and it shouldn't be
// todo disallow right above
: new GoalGetToBlock(pos) // replace with GoalTwoBlocks to mine using pathfinding system only
).toArray(Goal[]::new);
public boolean recalc(BuilderCalculationContext bcc) {
if (incorrectPositions == null) {
incorrectPositions = new HashSet<>();
fullRecalc(bcc);
if (incorrectPositions.isEmpty()) {
return false;
}
}
recalcNearby(bcc);
if (incorrectPositions.isEmpty()) {
fullRecalc(bcc);
}
return !incorrectPositions.isEmpty();
}
public void recalcNearby(BuilderCalculationContext bcc) {
BetterBlockPos center = ctx.playerFeet();
for (int dx = -5; dx <= 5; dx++) {
for (int dy = -5; dy <= 5; dy++) {
for (int dz = -5; dz <= 5; dz++) {
int x = center.x + dx;
int y = center.y + dy;
int z = center.z + dz;
IBlockState desired = bcc.getSchematic(x, y, z);
if (desired != null) {
// we care about this position
if (valid(bcc.bsi.get0(x, y, z), desired)) {
incorrectPositions.remove(new BetterBlockPos(x, y, z));
} else {
incorrectPositions.add(new BetterBlockPos(x, y, z));
}
}
}
}
}
}
public void fullRecalc(BuilderCalculationContext bcc) {
incorrectPositions = new HashSet<>();
for (int y = 0; y < schematic.heightY(); y++) {
for (int z = 0; z < schematic.lengthZ(); z++) {
for (int x = 0; x < schematic.widthX(); x++) {
if (schematic.inSchematic(x, y, z)) {
if (!valid(bcc.bsi.get0(x + origin.getX(), y + origin.getY(), z + origin.getZ()), schematic.desiredState(x, y, z))) {
incorrectPositions.add(new BetterBlockPos(x + origin.getX(), y + origin.getY(), z + origin.getZ()));
}
}
}
}
}
}
private Goal[] assemble(BuilderCalculationContext bcc) {
List<IBlockState> approxPlacable = placable();
List<BetterBlockPos> placable = incorrectPositions.stream().filter(pos -> bcc.bsi.get0(pos).getBlock() == Blocks.AIR && approxPlacable.contains(bcc.getSchematic(pos.x, pos.y, pos.z))).collect(Collectors.toList());
if (!placable.isEmpty()) {
return placable.stream().filter(pos -> !placable.contains(pos.down()) && !placable.contains(pos.down(2))).map(BetterBlockPos::up).map(GoalBlock::new).toArray(Goal[]::new);
}
return incorrectPositions.stream().filter(pos -> bcc.bsi.get0(pos).getBlock() != Blocks.AIR).map(GoalBreak::new).toArray(Goal[]::new);
}
public static class GoalBreak extends GoalGetToBlock {
public GoalBreak(BlockPos pos) {
super(pos);
}
@Override
public boolean isInGoal(int x, int y, int z) {
// can't stand right on top of a block, that might not work (what if it's unsupported, can't break then)
if (x == this.x && y == this.y + 1 && z == this.z) {
return false;
}
// but any other adjacent works for breaking, including inside or below
return super.isInGoal(x, y, z);
}
}
@Override
@ -149,6 +271,11 @@ public class BuilderProcess extends BaritoneProcessHelper {
return result;
}
public boolean valid(IBlockState current, IBlockState desired) {
// TODO more complicated comparison logic I guess
return desired == null || current.equals(desired);
}
public class BuilderCalculationContext extends CalculationContext {
private final List<IBlockState> placable;
private final ISchematic schematic;
@ -181,6 +308,11 @@ public class BuilderProcess extends BaritoneProcessHelper {
IBlockState sch = getSchematic(x, y, z);
if (sch != null) {
// TODO this can return true even when allowPlace is off.... is that an issue?
if (sch.getBlock() == Blocks.AIR) {
// we want this to be air, but they're asking if they can place here
// this won't be a schematic block, this will be a throwaway
return placeBlockCost * 2; // we're going to have to break it eventually
}
if (placable.contains(sch)) {
return 0; // thats right we gonna make it FREE to place a block where it should go in a structure
// no place block penalty at all 😎
@ -190,15 +322,9 @@ public class BuilderProcess extends BaritoneProcessHelper {
if (!hasThrowaway) {
return COST_INF;
}
if (sch.getBlock() == Blocks.AIR) {
// we want this to be air, but they're asking if they can place here
// this won't be a schematic block, this will be a throwaway
return placeBlockCost * 2; // we're going to have to break it eventually
} else {
// we want it to be something that we don't have
// even more of a pain to place something wrong
return placeBlockCost * 3;
}
// we want it to be something that we don't have
// even more of a pain to place something wrong
return placeBlockCost * 3;
} else {
if (hasThrowaway) {
return placeBlockCost;
@ -209,24 +335,32 @@ public class BuilderProcess extends BaritoneProcessHelper {
}
@Override
public boolean canBreakAt(int x, int y, int z) {
public double breakCostMultiplierAt(int x, int y, int z) {
if (!allowBreak || isPossiblyProtected(x, y, z)) {
return false;
return COST_INF;
}
IBlockState sch = getSchematic(x, y, z);
if (sch != null) {
if (sch.getBlock() == Blocks.AIR) {
// it should be air
// regardless of current contents, we can break it
return true;
return 1;
}
// it should be a real block
// is it already that block?
return !bsi.get0(x, y, z).equals(sch); // can break if it's wrong
if (valid(bsi.get0(x, y, z), sch)) {
return 3;
} else {
// can break if it's wrong
// would be great to return less than 1 here, but that would actually make the cost calculation messed up
// since we're breaking a block, if we underestimate the cost, then it'll fail when it really takes the correct amount of time
return 1;
}
// TODO do blocks in render distace only?
// TODO allow breaking blocks that we have a tool to harvest and immediately place back?
} else {
return true; // why not lol
return 1; // why not lol
}
}
}

View File

@ -36,4 +36,10 @@ public interface ISchematic {
boolean inSchematic(int x, int y, int z);
IBlockState desiredState(int x, int y, int z);
int widthX();
int heightY();
int lengthZ();
}

View File

@ -0,0 +1,52 @@
/*
* This file is part of Baritone.
*
* Baritone is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Baritone is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
*/
package baritone.utils;
import net.minecraft.block.state.IBlockState;
import net.minecraft.init.Blocks;
import net.minecraft.nbt.NBTTagCompound;
public class MapArtSchematic extends Schematic {
private final int[][] heightMap;
public MapArtSchematic(NBTTagCompound schematic) {
super(schematic);
heightMap = new int[widthX][lengthZ];
for (int x = 0; x < widthX; x++) {
https:
for (int z = 0; z < lengthZ; z++) {
IBlockState[] column = states[x][z];
for (int y = heightY - 1; y >= 0; y--) {
if (column[y].getBlock() != Blocks.AIR) {
heightMap[x][z] = y;
continue https;
}
}
System.out.println("Column " + x + "," + z + " has no blocks, but it's apparently map art? wtf");
System.out.println("Letting it be whatever");
heightMap[x][z] = 256;
}
}
}
@Override
public boolean inSchematic(int x, int y, int z) {
// in map art, we only care about coordinates in or above the art
return super.inSchematic(x, y, z) && y >= heightMap[x][z];
}
}

View File

@ -0,0 +1,93 @@
/*
* This file is part of Baritone.
*
* Baritone is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Baritone is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
*/
package baritone.utils;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.nbt.NBTTagCompound;
public class Schematic implements ISchematic {
public final int widthX;
public final int heightY;
public final int lengthZ;
protected final IBlockState[][][] states;
public Schematic(NBTTagCompound schematic) {
String type = schematic.getString("Materials");
if (!type.equals("Alpha")) {
throw new IllegalStateException("bad schematic " + type);
}
widthX = schematic.getInteger("Width");
heightY = schematic.getInteger("Height");
lengthZ = schematic.getInteger("Length");
byte[] blocks = schematic.getByteArray("Blocks");
byte[] metadata = schematic.getByteArray("Data");
byte[] additional = null;
if (schematic.hasKey("AddBlocks")) {
byte[] addBlocks = schematic.getByteArray("AddBlocks");
additional = new byte[addBlocks.length * 2];
for (int i = 0; i < addBlocks.length; i++) {
additional[i * 2 + 0] = (byte) ((addBlocks[i] >> 4) & 0xF); // lower nibble
additional[i * 2 + 1] = (byte) ((addBlocks[i] >> 0) & 0xF); // upper nibble
}
}
states = new IBlockState[widthX][lengthZ][heightY];
for (int y = 0; y < heightY; y++) {
for (int z = 0; z < lengthZ; z++) {
for (int x = 0; x < widthX; x++) {
int blockInd = (y * lengthZ + z) * widthX + x;
int blockID = blocks[blockInd] & 0xFF;
if (additional != null) {
// additional is 0 through 15 inclusive since it's & 0xF above
blockID |= additional[blockInd] << 8;
}
Block block = Block.REGISTRY.getObjectById(blockID);
int meta = metadata[blockInd] & 0xFF;
states[x][z][y] = block.getStateFromMeta(meta);
}
}
}
}
@Override
public boolean inSchematic(int x, int y, int z) {
return x >= 0 && x < widthX && y >= 0 && y < heightY && z >= 0 && z < lengthZ;
}
@Override
public IBlockState desiredState(int x, int y, int z) {
return states[x][z][y];
}
@Override
public int widthX() {
return widthX;
}
@Override
public int heightY() {
return heightY;
}
@Override
public int lengthZ() {
return lengthZ;
}
}