mirror of https://github.com/cabaletta/baritone
256 lines
10 KiB
Java
256 lines
10 KiB
Java
/*
|
|
* 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.process;
|
|
|
|
import baritone.Baritone;
|
|
import baritone.api.pathing.goals.*;
|
|
import baritone.api.process.IGetToBlockProcess;
|
|
import baritone.api.process.PathingCommand;
|
|
import baritone.api.process.PathingCommandType;
|
|
import baritone.api.utils.BlockOptionalMeta;
|
|
import baritone.api.utils.BlockOptionalMetaLookup;
|
|
import baritone.api.utils.Rotation;
|
|
import baritone.api.utils.RotationUtils;
|
|
import baritone.api.utils.input.Input;
|
|
import baritone.pathing.movement.CalculationContext;
|
|
import baritone.utils.BaritoneProcessHelper;
|
|
import net.minecraft.block.Block;
|
|
import net.minecraft.block.state.IBlockState;
|
|
import net.minecraft.init.Blocks;
|
|
import net.minecraft.inventory.ContainerPlayer;
|
|
import net.minecraft.util.math.BlockPos;
|
|
|
|
import java.util.*;
|
|
|
|
public final class GetToBlockProcess extends BaritoneProcessHelper implements IGetToBlockProcess {
|
|
|
|
private BlockOptionalMeta gettingTo;
|
|
private List<BlockPos> knownLocations;
|
|
private List<BlockPos> blacklist; // locations we failed to calc to
|
|
private BlockPos start;
|
|
|
|
private int tickCount = 0;
|
|
private int arrivalTickCount = 0;
|
|
|
|
public GetToBlockProcess(Baritone baritone) {
|
|
super(baritone);
|
|
}
|
|
|
|
@Override
|
|
public void getToBlock(BlockOptionalMeta block) {
|
|
onLostControl();
|
|
gettingTo = block;
|
|
start = ctx.playerFeet();
|
|
blacklist = new ArrayList<>();
|
|
arrivalTickCount = 0;
|
|
rescan(new ArrayList<>(), new GetToBlockCalculationContext(false));
|
|
}
|
|
|
|
@Override
|
|
public boolean isActive() {
|
|
return gettingTo != null;
|
|
}
|
|
|
|
@Override
|
|
public synchronized PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) {
|
|
if (knownLocations == null) {
|
|
rescan(new ArrayList<>(), new GetToBlockCalculationContext(false));
|
|
}
|
|
if (knownLocations.isEmpty()) {
|
|
if (Baritone.settings().exploreForBlocks.value && !calcFailed) {
|
|
return new PathingCommand(new GoalRunAway(1, start) {
|
|
@Override
|
|
public boolean isInGoal(int x, int y, int z) {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public double heuristic() {
|
|
return Double.NEGATIVE_INFINITY;
|
|
}
|
|
}, PathingCommandType.FORCE_REVALIDATE_GOAL_AND_PATH);
|
|
}
|
|
logDirect("No known locations of " + gettingTo + ", canceling GetToBlock");
|
|
if (isSafeToCancel) {
|
|
onLostControl();
|
|
}
|
|
return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL);
|
|
}
|
|
Goal goal = new GoalComposite(knownLocations.stream().map(this::createGoal).toArray(Goal[]::new));
|
|
if (calcFailed) {
|
|
if (Baritone.settings().blacklistClosestOnFailure.value) {
|
|
logDirect("Unable to find any path to " + gettingTo + ", blacklisting presumably unreachable closest instances...");
|
|
blacklistClosest();
|
|
return onTick(false, isSafeToCancel); // gamer moment
|
|
} else {
|
|
logDirect("Unable to find any path to " + gettingTo + ", canceling GetToBlock");
|
|
if (isSafeToCancel) {
|
|
onLostControl();
|
|
}
|
|
return new PathingCommand(goal, PathingCommandType.CANCEL_AND_SET_GOAL);
|
|
}
|
|
}
|
|
int mineGoalUpdateInterval = Baritone.settings().mineGoalUpdateInterval.value;
|
|
if (mineGoalUpdateInterval != 0 && tickCount++ % mineGoalUpdateInterval == 0) { // big brain
|
|
List<BlockPos> current = new ArrayList<>(knownLocations);
|
|
CalculationContext context = new GetToBlockCalculationContext(true);
|
|
Baritone.getExecutor().execute(() -> rescan(current, context));
|
|
}
|
|
if (goal.isInGoal(ctx.playerFeet()) && goal.isInGoal(baritone.getPathingBehavior().pathStart()) && isSafeToCancel) {
|
|
// we're there
|
|
if (rightClickOnArrival(gettingTo.getBlock())) {
|
|
if (rightClick()) {
|
|
onLostControl();
|
|
return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL);
|
|
}
|
|
} else {
|
|
onLostControl();
|
|
return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL);
|
|
}
|
|
}
|
|
return new PathingCommand(goal, PathingCommandType.REVALIDATE_GOAL_AND_PATH);
|
|
}
|
|
|
|
// blacklist the closest block and its adjacent blocks
|
|
public synchronized boolean blacklistClosest() {
|
|
List<BlockPos> newBlacklist = new ArrayList<>();
|
|
knownLocations.stream().min(Comparator.comparingDouble(ctx.player()::getDistanceSq)).ifPresent(newBlacklist::add);
|
|
outer:
|
|
while (true) {
|
|
for (BlockPos known : knownLocations) {
|
|
for (BlockPos blacklist : newBlacklist) {
|
|
if (areAdjacent(known, blacklist)) { // directly adjacent
|
|
newBlacklist.add(known);
|
|
knownLocations.remove(known);
|
|
continue outer;
|
|
}
|
|
}
|
|
}
|
|
// i can't do break; (codacy gets mad), and i can't do if(true){break}; (codacy gets mad)
|
|
// so i will do this
|
|
switch (newBlacklist.size()) {
|
|
default:
|
|
break outer;
|
|
}
|
|
}
|
|
logDebug("Blacklisting unreachable locations " + newBlacklist);
|
|
blacklist.addAll(newBlacklist);
|
|
return !newBlacklist.isEmpty();
|
|
}
|
|
|
|
// this is to signal to MineProcess that we don't care about the allowBreak setting
|
|
// it is NOT to be used to actually calculate a path
|
|
public class GetToBlockCalculationContext extends CalculationContext {
|
|
|
|
public GetToBlockCalculationContext(boolean forUseOnAnotherThread) {
|
|
super(GetToBlockProcess.super.baritone, forUseOnAnotherThread);
|
|
}
|
|
|
|
@Override
|
|
public double breakCostMultiplierAt(int x, int y, int z, IBlockState current) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// safer than direct double comparison from distanceSq
|
|
private boolean areAdjacent(BlockPos posA, BlockPos posB) {
|
|
int diffX = Math.abs(posA.getX() - posB.getX());
|
|
int diffY = Math.abs(posA.getY() - posB.getY());
|
|
int diffZ = Math.abs(posA.getZ() - posB.getZ());
|
|
return (diffX + diffY + diffZ) == 1;
|
|
}
|
|
|
|
@Override
|
|
public synchronized void onLostControl() {
|
|
gettingTo = null;
|
|
knownLocations = null;
|
|
start = null;
|
|
blacklist = null;
|
|
baritone.getInputOverrideHandler().clearAllKeys();
|
|
}
|
|
|
|
@Override
|
|
public String displayName0() {
|
|
if (knownLocations.isEmpty()) {
|
|
return "Exploring randomly to find " + gettingTo + ", no known locations";
|
|
}
|
|
return "Get To " + gettingTo + ", " + knownLocations.size() + " known locations";
|
|
}
|
|
|
|
private synchronized void rescan(List<BlockPos> known, CalculationContext context) {
|
|
List<BlockPos> positions = MineProcess.searchWorld(context, new BlockOptionalMetaLookup(gettingTo), 64, known, blacklist, Collections.emptyList());
|
|
positions.removeIf(blacklist::contains);
|
|
knownLocations = positions;
|
|
}
|
|
|
|
private Goal createGoal(BlockPos pos) {
|
|
if (walkIntoInsteadOfAdjacent(gettingTo.getBlock())) {
|
|
return new GoalTwoBlocks(pos);
|
|
}
|
|
if (blockOnTopMustBeRemoved(gettingTo.getBlock()) && baritone.bsi.get0(pos.up()).isBlockNormalCube()) {
|
|
return new GoalBlock(pos.up());
|
|
}
|
|
return new GoalGetToBlock(pos);
|
|
}
|
|
|
|
private boolean rightClick() {
|
|
for (BlockPos pos : knownLocations) {
|
|
Optional<Rotation> reachable = RotationUtils.reachable(ctx.player(), pos, ctx.playerController().getBlockReachDistance());
|
|
if (reachable.isPresent()) {
|
|
baritone.getLookBehavior().updateTarget(reachable.get(), true);
|
|
if (knownLocations.contains(ctx.getSelectedBlock().orElse(null))) {
|
|
baritone.getInputOverrideHandler().setInputForceState(Input.CLICK_RIGHT, true); // TODO find some way to right click even if we're in an ESC menu
|
|
System.out.println(ctx.player().openContainer);
|
|
if (!(ctx.player().openContainer instanceof ContainerPlayer)) {
|
|
return true;
|
|
}
|
|
}
|
|
if (arrivalTickCount++ > 20) {
|
|
logDirect("Right click timed out");
|
|
return true;
|
|
}
|
|
return false; // trying to right click, will do it next tick or so
|
|
}
|
|
}
|
|
logDirect("Arrived but failed to right click open");
|
|
return true;
|
|
}
|
|
|
|
private boolean walkIntoInsteadOfAdjacent(Block block) {
|
|
if (!Baritone.settings().enterPortal.value) {
|
|
return false;
|
|
}
|
|
return block == Blocks.PORTAL;
|
|
}
|
|
|
|
private boolean rightClickOnArrival(Block block) {
|
|
if (!Baritone.settings().rightClickContainerOnArrival.value) {
|
|
return false;
|
|
}
|
|
return block == Blocks.CRAFTING_TABLE || block == Blocks.FURNACE || block == Blocks.LIT_FURNACE || block == Blocks.ENDER_CHEST || block == Blocks.CHEST || block == Blocks.TRAPPED_CHEST;
|
|
}
|
|
|
|
private boolean blockOnTopMustBeRemoved(Block block) {
|
|
if (!rightClickOnArrival(block)) { // only if we plan to actually open it on arrival
|
|
return false;
|
|
}
|
|
// only these chests; you can open a crafting table or furnace even with a block on top
|
|
return block == Blocks.ENDER_CHEST || block == Blocks.CHEST || block == Blocks.TRAPPED_CHEST;
|
|
}
|
|
}
|