From 4bf2dd6851692dceb865fd2ec5c81439161ae3d7 Mon Sep 17 00:00:00 2001 From: Brady Date: Sat, 1 Jul 2023 15:16:26 -0500 Subject: [PATCH 01/21] Some initial organization --- .../java/baritone/PerformanceCritical.java | 33 +++++++++++++++++++ .../baritone/behavior/InventoryBehavior.java | 17 +++++++--- .../java/baritone/process/FarmProcess.java | 14 ++++---- src/main/java/baritone/utils/ToolSet.java | 3 +- 4 files changed, 55 insertions(+), 12 deletions(-) create mode 100644 src/main/java/baritone/PerformanceCritical.java diff --git a/src/main/java/baritone/PerformanceCritical.java b/src/main/java/baritone/PerformanceCritical.java new file mode 100644 index 000000000..5d5dd9eb4 --- /dev/null +++ b/src/main/java/baritone/PerformanceCritical.java @@ -0,0 +1,33 @@ +/* + * 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; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation that should be used on methods which are performance critical (i.e. called millions of times per second + * by the pathfinder) and should be modified with care. + * + * @author Brady + */ +@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) +@Retention(RetentionPolicy.SOURCE) +public @interface PerformanceCritical {} diff --git a/src/main/java/baritone/behavior/InventoryBehavior.java b/src/main/java/baritone/behavior/InventoryBehavior.java index 93dc200cc..9e1b77f0f 100644 --- a/src/main/java/baritone/behavior/InventoryBehavior.java +++ b/src/main/java/baritone/behavior/InventoryBehavior.java @@ -33,6 +33,7 @@ import java.util.ArrayList; import java.util.OptionalInt; import java.util.Random; +import java.util.function.IntPredicate; import java.util.function.Predicate; public final class InventoryBehavior extends Behavior implements Helper { @@ -70,7 +71,7 @@ public void onTick(TickEvent event) { } } - public boolean attemptToPutOnHotbar(int inMainInvy, Predicate disallowedHotbar) { + public boolean attemptToPutOnHotbar(int inMainInvy, IntPredicate disallowedHotbar) { OptionalInt destination = getTempHotbarSlot(disallowedHotbar); if (destination.isPresent()) { if (!requestSwapWithHotBar(inMainInvy, destination.getAsInt())) { @@ -80,7 +81,7 @@ public boolean attemptToPutOnHotbar(int inMainInvy, Predicate disallowe return true; } - public OptionalInt getTempHotbarSlot(Predicate disallowedHotbar) { + public OptionalInt getTempHotbarSlot(IntPredicate disallowedHotbar) { // we're using 0 and 8 for pickaxe and throwaway ArrayList candidates = new ArrayList<>(); for (int i = 1; i < 8; i++) { @@ -152,7 +153,7 @@ private int bestToolAgainst(Block against, Class cla$$) { public boolean hasGenericThrowaway() { for (Item item : Baritone.settings().acceptableThrowawayItems.value) { - if (throwaway(false, stack -> item.equals(stack.getItem()))) { + if (this.canSelectItem(stack -> item.equals(stack.getItem()))) { return true; } } @@ -175,8 +176,16 @@ public boolean selectThrowawayForLocation(boolean select, int x, int y, int z) { return false; } + public boolean canSelectItem(Predicate desired) { + return this.throwaway(false, desired); + } + + public boolean trySelectItem(Predicate desired) { + return this.throwaway(true, desired); + } + public boolean throwaway(boolean select, Predicate desired) { - return throwaway(select, desired, Baritone.settings().allowInventory.value); + return this.throwaway(select, desired, Baritone.settings().allowInventory.value); } public boolean throwaway(boolean select, Predicate desired, boolean allowInventory) { diff --git a/src/main/java/baritone/process/FarmProcess.java b/src/main/java/baritone/process/FarmProcess.java index 1536bdb61..1213198b9 100644 --- a/src/main/java/baritone/process/FarmProcess.java +++ b/src/main/java/baritone/process/FarmProcess.java @@ -269,7 +269,7 @@ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { for (BlockPos pos : both) { boolean soulsand = openSoulsand.contains(pos); Optional rot = RotationUtils.reachableOffset(ctx, pos, new Vec3d(pos.getX() + 0.5, pos.getY() + 1, pos.getZ() + 0.5), ctx.playerController().getBlockReachDistance(), false); - if (rot.isPresent() && isSafeToCancel && baritone.getInventoryBehavior().throwaway(true, soulsand ? this::isNetherWart : this::isPlantable)) { + if (rot.isPresent() && isSafeToCancel && baritone.getInventoryBehavior().trySelectItem(soulsand ? this::isNetherWart : this::isPlantable)) { RayTraceResult result = RayTraceUtils.rayTraceTowards(ctx.player(), rot.get(), ctx.playerController().getBlockReachDistance()); if (result.typeOfHit == RayTraceResult.Type.BLOCK && result.sideHit == EnumFacing.UP) { baritone.getLookBehavior().updateTarget(rot.get(), true); @@ -287,7 +287,7 @@ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { } Vec3d faceCenter = new Vec3d(pos).add(0.5, 0.5, 0.5).add(new Vec3d(dir.getDirectionVec()).scale(0.5)); Optional rot = RotationUtils.reachableOffset(ctx, pos, faceCenter, ctx.playerController().getBlockReachDistance(), false); - if (rot.isPresent() && isSafeToCancel && baritone.getInventoryBehavior().throwaway(true, this::isCocoa)) { + if (rot.isPresent() && isSafeToCancel && baritone.getInventoryBehavior().trySelectItem(this::isCocoa)) { RayTraceResult result = RayTraceUtils.rayTraceTowards(ctx.player(), rot.get(), ctx.playerController().getBlockReachDistance()); if (result.typeOfHit == RayTraceResult.Type.BLOCK && result.sideHit == dir) { baritone.getLookBehavior().updateTarget(rot.get(), true); @@ -301,7 +301,7 @@ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { } for (BlockPos pos : bonemealable) { Optional rot = RotationUtils.reachable(ctx, pos); - if (rot.isPresent() && isSafeToCancel && baritone.getInventoryBehavior().throwaway(true, this::isBoneMeal)) { + if (rot.isPresent() && isSafeToCancel && baritone.getInventoryBehavior().trySelectItem(this::isBoneMeal)) { baritone.getLookBehavior().updateTarget(rot.get(), true); if (ctx.isLookingAt(pos)) { baritone.getInputOverrideHandler().setInputForceState(Input.CLICK_RIGHT, true); @@ -323,17 +323,17 @@ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { for (BlockPos pos : toBreak) { goalz.add(new BuilderProcess.GoalBreak(pos)); } - if (baritone.getInventoryBehavior().throwaway(false, this::isPlantable)) { + if (baritone.getInventoryBehavior().canSelectItem(this::isPlantable)) { for (BlockPos pos : openFarmland) { goalz.add(new GoalBlock(pos.up())); } } - if (baritone.getInventoryBehavior().throwaway(false, this::isNetherWart)) { + if (baritone.getInventoryBehavior().canSelectItem(this::isNetherWart)) { for (BlockPos pos : openSoulsand) { goalz.add(new GoalBlock(pos.up())); } } - if (baritone.getInventoryBehavior().throwaway(false, this::isCocoa)) { + if (baritone.getInventoryBehavior().canSelectItem(this::isCocoa)) { for (BlockPos pos : openLog) { for (EnumFacing direction : EnumFacing.Plane.HORIZONTAL) { if (ctx.world().getBlockState(pos.offset(direction)).getBlock() instanceof BlockAir) { @@ -342,7 +342,7 @@ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { } } } - if (baritone.getInventoryBehavior().throwaway(false, this::isBoneMeal)) { + if (baritone.getInventoryBehavior().canSelectItem(this::isBoneMeal)) { for (BlockPos pos : bonemealable) { goalz.add(new GoalBlock(pos)); } diff --git a/src/main/java/baritone/utils/ToolSet.java b/src/main/java/baritone/utils/ToolSet.java index 61f11e563..e9b9043bb 100644 --- a/src/main/java/baritone/utils/ToolSet.java +++ b/src/main/java/baritone/utils/ToolSet.java @@ -18,6 +18,7 @@ package baritone.utils; import baritone.Baritone; +import baritone.PerformanceCritical; import baritone.utils.accessor.IItemTool; import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; @@ -72,6 +73,7 @@ public ToolSet(EntityPlayerSP player) { * @param state the blockstate to be mined * @return the speed of how fast we'll mine it. 1/(time in ticks) */ + @PerformanceCritical public double getStrVsBlock(IBlockState state) { return breakStrengthCache.computeIfAbsent(state.getBlock(), backendCalculation); } @@ -104,7 +106,6 @@ public boolean hasSilkTouch(ItemStack stack) { * @param b the blockstate to be mined * @return An int containing the index in the tools array that worked best */ - public int getBestSlot(Block b, boolean preferSilkTouch) { return getBestSlot(b, preferSilkTouch, false); } From b2cdd27ca29f9671e39619487a8f144755e72a4e Mon Sep 17 00:00:00 2001 From: Brady Date: Sat, 1 Jul 2023 15:38:23 -0500 Subject: [PATCH 02/21] Remove direct reference to `allowInventory` in `BuilderProcess` --- .../java/baritone/behavior/InventoryBehavior.java | 11 ++++++----- src/main/java/baritone/process/BuilderProcess.java | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/baritone/behavior/InventoryBehavior.java b/src/main/java/baritone/behavior/InventoryBehavior.java index 9e1b77f0f..6c55663a6 100644 --- a/src/main/java/baritone/behavior/InventoryBehavior.java +++ b/src/main/java/baritone/behavior/InventoryBehavior.java @@ -185,10 +185,6 @@ public boolean trySelectItem(Predicate desired) { } public boolean throwaway(boolean select, Predicate desired) { - return this.throwaway(select, desired, Baritone.settings().allowInventory.value); - } - - public boolean throwaway(boolean select, Predicate desired, boolean allowInventory) { EntityPlayerSP p = ctx.player(); NonNullList inv = p.inventory.mainInventory; for (int i = 0; i < 9; i++) { @@ -205,6 +201,7 @@ public boolean throwaway(boolean select, Predicate desired, b return true; } } + if (desired.test(p.inventory.offHandInventory.get(0))) { // main hand takes precedence over off hand // that means that if we have block A selected in main hand and block B in off hand, right clicking places block B @@ -222,7 +219,7 @@ public boolean throwaway(boolean select, Predicate desired, b } } - if (allowInventory) { + if (this.canAccessInventory()) { for (int i = 9; i < 36; i++) { if (desired.test(inv.get(i))) { if (select) { @@ -236,4 +233,8 @@ public boolean throwaway(boolean select, Predicate desired, b return false; } + + public boolean canAccessInventory() { + return Baritone.settings().allowInventory.value; + } } diff --git a/src/main/java/baritone/process/BuilderProcess.java b/src/main/java/baritone/process/BuilderProcess.java index 60066971e..0a46774d4 100644 --- a/src/main/java/baritone/process/BuilderProcess.java +++ b/src/main/java/baritone/process/BuilderProcess.java @@ -542,7 +542,7 @@ public int lengthZ() { return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); } - if (Baritone.settings().allowInventory.value) { + if (baritone.getInventoryBehavior().canAccessInventory()) { ArrayList usefulSlots = new ArrayList<>(); List noValidHotbarOption = new ArrayList<>(); outer: From 2953b1cfafd26adae3d72983611c3a29c222bbf2 Mon Sep 17 00:00:00 2001 From: Brady Date: Sat, 1 Jul 2023 16:41:26 -0500 Subject: [PATCH 03/21] Replace `acceptableThrowawayItems` references with `isThrowawayItem` method --- .../baritone/behavior/InventoryBehavior.java | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/main/java/baritone/behavior/InventoryBehavior.java b/src/main/java/baritone/behavior/InventoryBehavior.java index 6c55663a6..d6f0ac059 100644 --- a/src/main/java/baritone/behavior/InventoryBehavior.java +++ b/src/main/java/baritone/behavior/InventoryBehavior.java @@ -47,7 +47,7 @@ public InventoryBehavior(Baritone baritone) { @Override public void onTick(TickEvent event) { - if (!Baritone.settings().allowInventory.value) { + if (!this.canAccessInventory()) { return; } if (event.getType() == TickEvent.Type.OUT) { @@ -121,7 +121,7 @@ private boolean requestSwapWithHotBar(int inInventory, int inHotbar) { private int firstValidThrowaway() { // TODO offhand idk NonNullList invy = ctx.player().inventory.mainInventory; for (int i = 0; i < invy.size(); i++) { - if (Baritone.settings().acceptableThrowawayItems.value.contains(invy.get(i).getItem())) { + if (this.isThrowawayItem(invy.get(i))) { return i; } } @@ -152,12 +152,7 @@ private int bestToolAgainst(Block against, Class cla$$) { } public boolean hasGenericThrowaway() { - for (Item item : Baritone.settings().acceptableThrowawayItems.value) { - if (this.canSelectItem(stack -> item.equals(stack.getItem()))) { - return true; - } - } - return false; + return this.canSelectItem(this::isThrowawayItem); } public boolean selectThrowawayForLocation(boolean select, int x, int y, int z) { @@ -168,12 +163,7 @@ public boolean selectThrowawayForLocation(boolean select, int x, int y, int z) { if (maybe != null && throwaway(select, stack -> stack.getItem() instanceof ItemBlock && ((ItemBlock) stack.getItem()).getBlock().equals(maybe.getBlock()))) { return true; } - for (Item item : Baritone.settings().acceptableThrowawayItems.value) { - if (throwaway(select, stack -> item.equals(stack.getItem()))) { - return true; - } - } - return false; + return throwaway(select, this::isThrowawayItem); } public boolean canSelectItem(Predicate desired) { @@ -237,4 +227,12 @@ public boolean throwaway(boolean select, Predicate desired) { public boolean canAccessInventory() { return Baritone.settings().allowInventory.value; } + + public boolean isThrowawayItem(ItemStack stack) { + return this.isThrowawayItem(stack.getItem()); + } + + public boolean isThrowawayItem(Item item) { + return Baritone.settings().acceptableThrowawayItems.value.contains(item); + } } From 028405dd309c512ec5d2087531faa0a5d0f2dbd9 Mon Sep 17 00:00:00 2001 From: Brady Date: Sat, 1 Jul 2023 17:00:04 -0500 Subject: [PATCH 04/21] Only allow `@PerformanceCritical` on methods --- src/main/java/baritone/PerformanceCritical.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/baritone/PerformanceCritical.java b/src/main/java/baritone/PerformanceCritical.java index 5d5dd9eb4..75b972d1d 100644 --- a/src/main/java/baritone/PerformanceCritical.java +++ b/src/main/java/baritone/PerformanceCritical.java @@ -28,6 +28,6 @@ * * @author Brady */ -@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) +@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface PerformanceCritical {} From 4591fd7eb36f7a548c51955fa38ca18f85f819e8 Mon Sep 17 00:00:00 2001 From: Brady Date: Sat, 1 Jul 2023 17:16:51 -0500 Subject: [PATCH 05/21] Use `Reference2DoubleOpenHashMap` in `ToolSet` --- src/main/java/baritone/utils/ToolSet.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/baritone/utils/ToolSet.java b/src/main/java/baritone/utils/ToolSet.java index e9b9043bb..d28d949db 100644 --- a/src/main/java/baritone/utils/ToolSet.java +++ b/src/main/java/baritone/utils/ToolSet.java @@ -20,6 +20,7 @@ import baritone.Baritone; import baritone.PerformanceCritical; import baritone.utils.accessor.IItemTool; +import it.unimi.dsi.fastutil.objects.Reference2DoubleOpenHashMap; import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; import net.minecraft.client.entity.EntityPlayerSP; @@ -30,8 +31,6 @@ import net.minecraft.item.ItemSword; import net.minecraft.item.ItemTool; -import java.util.HashMap; -import java.util.Map; import java.util.function.Function; /** @@ -45,7 +44,7 @@ public class ToolSet { * A cache mapping a {@link Block} to how long it will take to break * with this toolset, given the optimum tool is used. */ - private final Map breakStrengthCache; + private final Reference2DoubleOpenHashMap breakStrengthCache; /** * My buddy leijurv owned me so we have this to not create a new lambda instance. @@ -55,15 +54,15 @@ public class ToolSet { private final EntityPlayerSP player; public ToolSet(EntityPlayerSP player) { - breakStrengthCache = new HashMap<>(); + this.breakStrengthCache = new Reference2DoubleOpenHashMap<>(); this.player = player; if (Baritone.settings().considerPotionEffects.value) { double amplifier = potionAmplifier(); Function amplify = x -> amplifier * x; - backendCalculation = amplify.compose(this::getBestDestructionTime); + this.backendCalculation = amplify.compose(this::getBestDestructionTime); } else { - backendCalculation = this::getBestDestructionTime; + this.backendCalculation = this::getBestDestructionTime; } } @@ -75,7 +74,9 @@ public ToolSet(EntityPlayerSP player) { */ @PerformanceCritical public double getStrVsBlock(IBlockState state) { - return breakStrengthCache.computeIfAbsent(state.getBlock(), backendCalculation); + // fastutil 8+ has a computeIfAbsent overload that uses a primitive mapping function + // for now, we're stuck with the boxed implementation + return this.breakStrengthCache.computeIfAbsent(state.getBlock(), this.backendCalculation); } /** From 15621ea7635056929bd4afb44ffaa271bfe92598 Mon Sep 17 00:00:00 2001 From: Brady Date: Sat, 1 Jul 2023 20:08:29 -0500 Subject: [PATCH 06/21] Add `allowHotbarManagement` setting --- src/api/java/baritone/api/Settings.java | 6 ++++ .../baritone/behavior/InventoryBehavior.java | 31 ++++++++++++------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/api/java/baritone/api/Settings.java b/src/api/java/baritone/api/Settings.java index a8e4ced7d..37e1d7997 100644 --- a/src/api/java/baritone/api/Settings.java +++ b/src/api/java/baritone/api/Settings.java @@ -73,6 +73,12 @@ public final class Settings { */ public final Setting allowInventory = new Setting<>(false); + /** + * Allow Baritone to automatically put useful items (such as tools and throwaway blocks) on the hotbar while + * pathing. Having this setting enabled implies {@link #allowInventory}. + */ + public final Setting allowHotbarManagement = new Setting<>(false); + /** * Wait this many ticks between InventoryBehavior moving inventory items */ diff --git a/src/main/java/baritone/behavior/InventoryBehavior.java b/src/main/java/baritone/behavior/InventoryBehavior.java index d6f0ac059..b88dd3d36 100644 --- a/src/main/java/baritone/behavior/InventoryBehavior.java +++ b/src/main/java/baritone/behavior/InventoryBehavior.java @@ -38,8 +38,8 @@ public final class InventoryBehavior extends Behavior implements Helper { - int ticksSinceLastInventoryMove; - int[] lastTickRequestedMove; // not everything asks every tick, so remember the request while coming to a halt + private int ticksSinceLastInventoryMove; + private int[] lastTickRequestedMove; // not everything asks every tick, so remember the request while coming to a halt public InventoryBehavior(Baritone baritone) { super(baritone); @@ -47,24 +47,31 @@ public InventoryBehavior(Baritone baritone) { @Override public void onTick(TickEvent event) { - if (!this.canAccessInventory()) { + if (event.getType() == TickEvent.Type.OUT) { return; } - if (event.getType() == TickEvent.Type.OUT) { + ticksSinceLastInventoryMove++; + + // TODO: Move these checks into "requestSwapWithHotBar" or whatever supersedes it + if (!this.canAccessInventory()) { return; } if (ctx.player().openContainer != ctx.player().inventoryContainer) { // we have a crafting table or a chest or something open return; } - ticksSinceLastInventoryMove++; - if (firstValidThrowaway() >= 9) { // aka there are none on the hotbar, but there are some in main inventory - requestSwapWithHotBar(firstValidThrowaway(), 8); - } - int pick = bestToolAgainst(Blocks.STONE, ItemPickaxe.class); - if (pick >= 9) { - requestSwapWithHotBar(pick, 0); + + if (Baritone.settings().allowHotbarManagement.value && baritone.getPathingBehavior().isPathing()) { + // TODO: Some way of indicating which slots are currently reserved by this setting + if (firstValidThrowaway() >= 9) { // aka there are none on the hotbar, but there are some in main inventory + requestSwapWithHotBar(firstValidThrowaway(), 8); + } + int pick = bestToolAgainst(Blocks.STONE, ItemPickaxe.class); + if (pick >= 9) { + requestSwapWithHotBar(pick, 0); + } } + if (lastTickRequestedMove != null) { logDebug("Remembering to move " + lastTickRequestedMove[0] + " " + lastTickRequestedMove[1] + " from a previous tick"); requestSwapWithHotBar(lastTickRequestedMove[0], lastTickRequestedMove[1]); @@ -225,7 +232,7 @@ public boolean throwaway(boolean select, Predicate desired) { } public boolean canAccessInventory() { - return Baritone.settings().allowInventory.value; + return Baritone.settings().allowInventory.value || Baritone.settings().allowHotbarManagement.value; } public boolean isThrowawayItem(ItemStack stack) { From 7f27c9d85f62b474357fe1ab003c2c234d1496a5 Mon Sep 17 00:00:00 2001 From: Brady Date: Sat, 1 Jul 2023 22:19:51 -0500 Subject: [PATCH 07/21] Reorganize --- .../baritone/behavior/InventoryBehavior.java | 26 ++++++++++++++----- src/main/java/baritone/utils/ToolSet.java | 2 +- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/main/java/baritone/behavior/InventoryBehavior.java b/src/main/java/baritone/behavior/InventoryBehavior.java index b88dd3d36..a27d6c877 100644 --- a/src/main/java/baritone/behavior/InventoryBehavior.java +++ b/src/main/java/baritone/behavior/InventoryBehavior.java @@ -164,13 +164,27 @@ public boolean hasGenericThrowaway() { public boolean selectThrowawayForLocation(boolean select, int x, int y, int z) { IBlockState maybe = baritone.getBuilderProcess().placeAt(x, y, z, baritone.bsi.get0(x, y, z)); - if (maybe != null && throwaway(select, stack -> stack.getItem() instanceof ItemBlock && maybe.equals(((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 true; // gotem - } - if (maybe != null && throwaway(select, stack -> stack.getItem() instanceof ItemBlock && ((ItemBlock) stack.getItem()).getBlock().equals(maybe.getBlock()))) { - return true; + if (maybe != null) { + return this.throwaway(select, stack -> { + if (!(stack.getItem() instanceof ItemBlock)) { + return false; + } + Block block = ((ItemBlock) stack.getItem()).getBlock(); + return maybe.equals(block.getStateForPlacement( + ctx.world(), + ctx.playerFeet(), + EnumFacing.UP, + 0.5f, 1.0f, 0.5f, + stack.getItem().getMetadata(stack.getMetadata()), + ctx.player() + )); + }) || this.throwaway(select, stack -> { + // Since a stack didn't match the desired block state, accept a match of just the block + return stack.getItem() instanceof ItemBlock + && ((ItemBlock) stack.getItem()).getBlock().equals(maybe.getBlock()); + }); } - return throwaway(select, this::isThrowawayItem); + return this.throwaway(select, this::isThrowawayItem); } public boolean canSelectItem(Predicate desired) { diff --git a/src/main/java/baritone/utils/ToolSet.java b/src/main/java/baritone/utils/ToolSet.java index d28d949db..d20bc6e56 100644 --- a/src/main/java/baritone/utils/ToolSet.java +++ b/src/main/java/baritone/utils/ToolSet.java @@ -38,7 +38,7 @@ * * @author Avery, Brady, leijurv */ -public class ToolSet { +public final class ToolSet { /** * A cache mapping a {@link Block} to how long it will take to break From c44701e6891ed2ae4fb84c94f38ab01ff81270ac Mon Sep 17 00:00:00 2001 From: Brady Date: Sun, 2 Jul 2023 19:46:48 -0500 Subject: [PATCH 08/21] Changes --- src/api/java/baritone/api/Settings.java | 2 +- .../baritone/behavior/InventoryBehavior.java | 152 +++++++++++++----- .../java/baritone/utils/InventorySlot.java | 115 +++++++++++++ 3 files changed, 228 insertions(+), 41 deletions(-) create mode 100644 src/main/java/baritone/utils/InventorySlot.java diff --git a/src/api/java/baritone/api/Settings.java b/src/api/java/baritone/api/Settings.java index 37e1d7997..4b614e106 100644 --- a/src/api/java/baritone/api/Settings.java +++ b/src/api/java/baritone/api/Settings.java @@ -75,7 +75,7 @@ public final class Settings { /** * Allow Baritone to automatically put useful items (such as tools and throwaway blocks) on the hotbar while - * pathing. Having this setting enabled implies {@link #allowInventory}. + * pathing. Requires {@link #allowInventory}. */ public final Setting allowHotbarManagement = new Setting<>(false); diff --git a/src/main/java/baritone/behavior/InventoryBehavior.java b/src/main/java/baritone/behavior/InventoryBehavior.java index a27d6c877..199b314f8 100644 --- a/src/main/java/baritone/behavior/InventoryBehavior.java +++ b/src/main/java/baritone/behavior/InventoryBehavior.java @@ -20,6 +20,7 @@ import baritone.Baritone; import baritone.api.event.events.TickEvent; import baritone.api.utils.Helper; +import baritone.utils.InventorySlot; import baritone.utils.ToolSet; import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; @@ -27,8 +28,10 @@ import net.minecraft.init.Blocks; import net.minecraft.inventory.ClickType; import net.minecraft.item.*; +import net.minecraft.network.play.client.CPacketPlayerDigging; import net.minecraft.util.EnumFacing; import net.minecraft.util.NonNullList; +import net.minecraft.util.math.BlockPos; import java.util.ArrayList; import java.util.OptionalInt; @@ -163,9 +166,11 @@ public boolean hasGenericThrowaway() { } public boolean selectThrowawayForLocation(boolean select, int x, int y, int z) { - IBlockState maybe = baritone.getBuilderProcess().placeAt(x, y, z, baritone.bsi.get0(x, y, z)); + final Predicate> op = select ? this::trySelectItem : this::canSelectItem; + + final IBlockState maybe = baritone.getBuilderProcess().placeAt(x, y, z, baritone.bsi.get0(x, y, z)); if (maybe != null) { - return this.throwaway(select, stack -> { + return op.test(stack -> { if (!(stack.getItem() instanceof ItemBlock)) { return false; } @@ -178,75 +183,116 @@ public boolean selectThrowawayForLocation(boolean select, int x, int y, int z) { stack.getItem().getMetadata(stack.getMetadata()), ctx.player() )); - }) || this.throwaway(select, stack -> { + }) || op.test(stack -> { // Since a stack didn't match the desired block state, accept a match of just the block return stack.getItem() instanceof ItemBlock && ((ItemBlock) stack.getItem()).getBlock().equals(maybe.getBlock()); }); } - return this.throwaway(select, this::isThrowawayItem); + return op.test(this::isThrowawayItem); } public boolean canSelectItem(Predicate desired) { - return this.throwaway(false, desired); + return this.resolveSelectionStrategy(desired) != null; } public boolean trySelectItem(Predicate desired) { - return this.throwaway(true, desired); + final SelectionStrategy strategy = this.resolveSelectionStrategy(desired); + if (strategy != null) { + strategy.run(); + // TODO: Consider cases where returning the SelectionType is needed/useful to the caller + return true; + } + return false; + } + + public SelectionStrategy resolveSelectionStrategy(Predicate desired) { + final InventorySlot slot = this.findSlotMatching(desired); + if (slot != null) { + switch (slot.getType()) { + case HOTBAR: + return SelectionStrategy.of(SelectionType.IMMEDIATE, () -> + ctx.player().inventory.currentItem = slot.getInventoryIndex()); + case OFFHAND: + // main hand takes precedence over off hand + // that means that if we have block A selected in main hand and block B in off hand, right clicking places block B + // we've already checked above ^ and the main hand can't possible have an acceptablethrowawayitem + // so we need to select in the main hand something that doesn't right click + // so not a shovel, not a hoe, not a block, etc +// for (int i = 0; i < 9; i++) { +// ItemStack item = ctx.player().inventory.mainInventory.get(i); +// if (item.isEmpty() || item.getItem() instanceof ItemPickaxe) { +// ctx.player().inventory.currentItem = i; +// break; +// } +// } + // TODO: This method of acquiring an offhand item is temporary and just guarantees that there won't + // be any item usage issues. It's probably worth adding a parameter to this method so that the + // caller can indicate what the purpose of the item is. for example, attacks are always done + // using the main hand. + // If there is an empty slot on the hotbar, switch to that slot and swap the offhand into it + final InventorySlot empty = this.findSlotMatching(ItemStack::isEmpty); + if (empty != null && empty.getType() == InventorySlot.Type.HOTBAR) { + return SelectionStrategy.of(SelectionType.ENQUEUED, () -> { + ctx.player().inventory.currentItem = empty.getInventoryIndex(); + ctx.playerController().syncHeldItem(); + ctx.player().connection.sendPacket(new CPacketPlayerDigging( + CPacketPlayerDigging.Action.SWAP_HELD_ITEMS, + BlockPos.ORIGIN, + EnumFacing.DOWN + )); + }); + } + break; + case INVENTORY: + if (this.canAccessInventory()) { + return SelectionStrategy.of(SelectionType.ENQUEUED, () -> { + // TODO: Determine if hotbar swap can be immediate, and return type accordingly + requestSwapWithHotBar(slot.getInventoryIndex(), 7); + ctx.player().inventory.currentItem = 7; + }); + } + break; + } + } + return null; } - public boolean throwaway(boolean select, Predicate desired) { - EntityPlayerSP p = ctx.player(); - NonNullList inv = p.inventory.mainInventory; + /** + * Returns an {@link InventorySlot} that contains a stack matching the given predicate. The priority of the + * returned slot is the hotbar, offhand, and finally the main inventory, if {@link #canAccessInventory()} is + * {@code true}. Additionally, for the hotbar and main inventory, slots with a lower index will be returned. + * + * @param desired The predicate to match + * @return A matching slot, or {@code null} if none. + */ + public InventorySlot findSlotMatching(final Predicate desired) { + final EntityPlayerSP p = ctx.player(); + final NonNullList inv = p.inventory.mainInventory; + for (int i = 0; i < 9; i++) { ItemStack item = inv.get(i); - // this usage of settings() is okay because it's only called once during pathing - // (while creating the CalculationContext at the very beginning) - // and then it's called during execution - // since this function is never called during cost calculation, we don't need to migrate - // acceptableThrowawayItems to the CalculationContext if (desired.test(item)) { - if (select) { - p.inventory.currentItem = i; - } - return true; + return InventorySlot.hotbar(i); } } if (desired.test(p.inventory.offHandInventory.get(0))) { - // main hand takes precedence over off hand - // that means that if we have block A selected in main hand and block B in off hand, right clicking places block B - // we've already checked above ^ and the main hand can't possible have an acceptablethrowawayitem - // so we need to select in the main hand something that doesn't right click - // so not a shovel, not a hoe, not a block, etc - for (int i = 0; i < 9; i++) { - ItemStack item = inv.get(i); - if (item.isEmpty() || item.getItem() instanceof ItemPickaxe) { - if (select) { - p.inventory.currentItem = i; - } - return true; - } - } + return InventorySlot.offhand(); } if (this.canAccessInventory()) { for (int i = 9; i < 36; i++) { if (desired.test(inv.get(i))) { - if (select) { - requestSwapWithHotBar(i, 7); - p.inventory.currentItem = 7; - } - return true; + return InventorySlot.inventory(i); } } } - - return false; + return null; } public boolean canAccessInventory() { - return Baritone.settings().allowInventory.value || Baritone.settings().allowHotbarManagement.value; + return Baritone.settings().allowInventory.value; } public boolean isThrowawayItem(ItemStack stack) { @@ -256,4 +302,30 @@ public boolean isThrowawayItem(ItemStack stack) { public boolean isThrowawayItem(Item item) { return Baritone.settings().acceptableThrowawayItems.value.contains(item); } + + public interface SelectionStrategy extends Runnable { + + @Override + void run(); + + SelectionType getType(); + + static SelectionStrategy of(final SelectionType type, final Runnable runnable) { + return new SelectionStrategy() { + @Override + public void run() { + runnable.run(); + } + + @Override + public SelectionType getType() { + return type; + } + }; + } + } + + public enum SelectionType { + IMMEDIATE, ENQUEUED + } } diff --git a/src/main/java/baritone/utils/InventorySlot.java b/src/main/java/baritone/utils/InventorySlot.java new file mode 100644 index 000000000..a09a38075 --- /dev/null +++ b/src/main/java/baritone/utils/InventorySlot.java @@ -0,0 +1,115 @@ +/* + * This file is part of Baritone. + * + * Baritone is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Baritone is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Baritone. If not, see . + */ + +package baritone.utils; + +/** + * @author Brady + */ +public final class InventorySlot { + + /** + * Maps directly to the slot ids of the player's inventory container. + * + * + * + * + * + * + * + * + * + * + * + *
IndexDescription
0crafting output
1-4crafting grid
5-8armor
9-35inventory (index 9-35)
36-44hotbar (index 0-8)
45off-hand
+ */ + private static final InventorySlot[] SLOTS = new InventorySlot[46]; + + static { + SLOTS[0] = new InventorySlot(0, Type.CRAFTING_OUTPUT); + for (int i = 0; i < 4; i++) { + SLOTS[i + 1] = new InventorySlot(i + 1, Type.CRAFTING_GRID); + } + for (int i = 0; i < 4; i++) { + SLOTS[i + 5] = new InventorySlot(i + 5, Type.ARMOR); + } + for (int i = 0; i < 27; i++) { + SLOTS[i + 9] = new InventorySlot(i + 9, Type.INVENTORY); + } + for (int i = 0; i < 9; i++) { + SLOTS[i + 36] = new InventorySlot(i + 36, Type.HOTBAR); + } + SLOTS[45] = new InventorySlot(45, Type.OFFHAND); + } + + private final int slotId; + private final Type type; + + private InventorySlot(int slotId, Type type) { + this.slotId = slotId; + this.type = type; + } + + /** + * @return The ID of this slot, as used by {@code ContainerPlayer} + */ + public int getSlotId() { + return this.slotId; + } + + public Type getType() { + return this.type; + } + + public int getInventoryIndex() { + switch (this.getType()) { + case HOTBAR: + return this.slotId - 36; + case INVENTORY: + return this.slotId; + default: + throw new IllegalStateException("Slot type must be either HOTBAR or INVENTORY"); + } + } + + public static InventorySlot hotbar(final int index) { + if (index < 0 || index >= 9) { + throw new IllegalArgumentException(); + } + return SLOTS[index + 36]; + } + + public static InventorySlot inventory(final int index) { + if (index < 9 || index >= 36) { + throw new IllegalArgumentException(); + } + return SLOTS[index]; + } + + public static InventorySlot offhand() { + return SLOTS[45]; + } + + public enum Type { + CRAFTING_OUTPUT, + CRAFTING_GRID, + ARMOR, + INVENTORY, + HOTBAR, + OFFHAND + } +} From 1ef622c936dc2213c39a75f1ecbb382cb4a30996 Mon Sep 17 00:00:00 2001 From: Brady Date: Sun, 2 Jul 2023 19:54:11 -0500 Subject: [PATCH 09/21] I LOVE `ObjIntConsumer` --- .../java/baritone/utils/InventorySlot.java | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/main/java/baritone/utils/InventorySlot.java b/src/main/java/baritone/utils/InventorySlot.java index a09a38075..11987aceb 100644 --- a/src/main/java/baritone/utils/InventorySlot.java +++ b/src/main/java/baritone/utils/InventorySlot.java @@ -17,6 +17,8 @@ package baritone.utils; +import java.util.function.ObjIntConsumer; + /** * @author Brady */ @@ -40,20 +42,24 @@ public final class InventorySlot { private static final InventorySlot[] SLOTS = new InventorySlot[46]; static { - SLOTS[0] = new InventorySlot(0, Type.CRAFTING_OUTPUT); - for (int i = 0; i < 4; i++) { - SLOTS[i + 1] = new InventorySlot(i + 1, Type.CRAFTING_GRID); - } - for (int i = 0; i < 4; i++) { - SLOTS[i + 5] = new InventorySlot(i + 5, Type.ARMOR); - } - for (int i = 0; i < 27; i++) { - SLOTS[i + 9] = new InventorySlot(i + 9, Type.INVENTORY); - } - for (int i = 0; i < 9; i++) { - SLOTS[i + 36] = new InventorySlot(i + 36, Type.HOTBAR); - } - SLOTS[45] = new InventorySlot(45, Type.OFFHAND); + final ObjIntConsumer populate = new ObjIntConsumer() { + private int index; + + @Override + public void accept(Type type, int count) { + for (int i = 0; i < count; i++) { + SLOTS[this.index] = new InventorySlot(this.index, type); + System.out.println(this.index); + this.index++; + } + } + }; + populate.accept(Type.CRAFTING_OUTPUT, 1); + populate.accept(Type.CRAFTING_GRID, 4); + populate.accept(Type.ARMOR, 4); + populate.accept(Type.INVENTORY, 27); + populate.accept(Type.HOTBAR, 9); + populate.accept(Type.OFFHAND, 1); } private final int slotId; From 4d7eb003e612f1db60ad4dda234652318d40f28a Mon Sep 17 00:00:00 2001 From: Brady Date: Mon, 3 Jul 2023 18:36:05 -0500 Subject: [PATCH 10/21] Item interaction detection --- .../baritone/behavior/InventoryBehavior.java | 140 +++++++++++++----- .../java/baritone/utils/InventorySlot.java | 1 - 2 files changed, 102 insertions(+), 39 deletions(-) diff --git a/src/main/java/baritone/behavior/InventoryBehavior.java b/src/main/java/baritone/behavior/InventoryBehavior.java index 199b314f8..721322671 100644 --- a/src/main/java/baritone/behavior/InventoryBehavior.java +++ b/src/main/java/baritone/behavior/InventoryBehavior.java @@ -22,18 +22,22 @@ import baritone.api.utils.Helper; import baritone.utils.InventorySlot; import baritone.utils.ToolSet; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Blocks; import net.minecraft.inventory.ClickType; import net.minecraft.item.*; -import net.minecraft.network.play.client.CPacketPlayerDigging; -import net.minecraft.util.EnumFacing; -import net.minecraft.util.NonNullList; +import net.minecraft.util.*; import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import javax.annotation.Nonnull; +import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Map; import java.util.OptionalInt; import java.util.Random; import java.util.function.IntPredicate; @@ -214,35 +218,23 @@ public SelectionStrategy resolveSelectionStrategy(Predicate d return SelectionStrategy.of(SelectionType.IMMEDIATE, () -> ctx.player().inventory.currentItem = slot.getInventoryIndex()); case OFFHAND: - // main hand takes precedence over off hand - // that means that if we have block A selected in main hand and block B in off hand, right clicking places block B - // we've already checked above ^ and the main hand can't possible have an acceptablethrowawayitem - // so we need to select in the main hand something that doesn't right click - // so not a shovel, not a hoe, not a block, etc -// for (int i = 0; i < 9; i++) { -// ItemStack item = ctx.player().inventory.mainInventory.get(i); -// if (item.isEmpty() || item.getItem() instanceof ItemPickaxe) { -// ctx.player().inventory.currentItem = i; -// break; -// } -// } - // TODO: This method of acquiring an offhand item is temporary and just guarantees that there won't - // be any item usage issues. It's probably worth adding a parameter to this method so that the - // caller can indicate what the purpose of the item is. for example, attacks are always done - // using the main hand. - // If there is an empty slot on the hotbar, switch to that slot and swap the offhand into it - final InventorySlot empty = this.findSlotMatching(ItemStack::isEmpty); - if (empty != null && empty.getType() == InventorySlot.Type.HOTBAR) { - return SelectionStrategy.of(SelectionType.ENQUEUED, () -> { - ctx.player().inventory.currentItem = empty.getInventoryIndex(); - ctx.playerController().syncHeldItem(); - ctx.player().connection.sendPacket(new CPacketPlayerDigging( - CPacketPlayerDigging.Action.SWAP_HELD_ITEMS, - BlockPos.ORIGIN, - EnumFacing.DOWN - )); - }); + // TODO: It's probably worth adding a parameter to this method so that the caller can indicate what + // the purpose of the item is. For example, attacks are always done using the main hand, so + // just switching to a non-interacting hotbar item isn't sufficient. + final ItemStack heldItem = ctx.player().inventory.getCurrentItem(); + + if (!ItemInteractionHelper.couldInteract(heldItem)) { + // Don't need to do anything, the item held in the main hand doesn't have any interaction. + return SelectionStrategy.of(SelectionType.IMMEDIATE, () -> {}); + } + + final InventorySlot hotbar = this.findHotbarMatching(item -> !ItemInteractionHelper.couldInteract(item)); + if (hotbar != null) { + return SelectionStrategy.of(SelectionType.IMMEDIATE, () -> + ctx.player().inventory.currentItem = hotbar.getInventoryIndex()); } + + // TODO: Swap offhand with an unimportant item break; case INVENTORY: if (this.canAccessInventory()) { @@ -267,16 +259,14 @@ public SelectionStrategy resolveSelectionStrategy(Predicate d * @return A matching slot, or {@code null} if none. */ public InventorySlot findSlotMatching(final Predicate desired) { + final InventorySlot hotbar = this.findHotbarMatching(desired); + if (hotbar != null) { + return hotbar; + } + final EntityPlayerSP p = ctx.player(); final NonNullList inv = p.inventory.mainInventory; - for (int i = 0; i < 9; i++) { - ItemStack item = inv.get(i); - if (desired.test(item)) { - return InventorySlot.hotbar(i); - } - } - if (desired.test(p.inventory.offHandInventory.get(0))) { return InventorySlot.offhand(); } @@ -291,6 +281,18 @@ public InventorySlot findSlotMatching(final Predicate desired return null; } + public InventorySlot findHotbarMatching(final Predicate desired) { + final EntityPlayerSP p = ctx.player(); + final NonNullList inv = p.inventory.mainInventory; + for (int i = 0; i < 9; i++) { + ItemStack item = inv.get(i); + if (desired.test(item)) { + return InventorySlot.hotbar(i); + } + } + return null; + } + public boolean canAccessInventory() { return Baritone.settings().allowInventory.value; } @@ -328,4 +330,66 @@ public SelectionType getType() { public enum SelectionType { IMMEDIATE, ENQUEUED } + + private static final class ItemInteractionHelper { + + private static final Map, Boolean> CACHE = new Reference2ReferenceOpenHashMap<>(); + + public static boolean couldInteract(final ItemStack stack) { + if (stack.isEmpty()) { + return false; + } + + return CACHE.computeIfAbsent(stack.getItem().getClass(), itemClass -> { + try { + final Method onItemUse = itemClass.getMethod(Helper1.name, Helper1.parameters); + final Method onItemRightClick = itemClass.getMethod(Helper2.name, Helper2.parameters); + + // If the declaring class isn't Item, then the method is overriden + return onItemUse.getDeclaringClass() != Item.class + || onItemRightClick.getDeclaringClass() != Item.class; + } catch (NoSuchMethodException ignored) { + // this shouldn't happen + return true; + } + }); + } + + private static final class Helper1 extends Item { + + public static final String name; + public static final Class[] parameters; + static { + final Method method = Helper1.class.getDeclaredMethods()[0]; + name = method.getName(); + parameters = method.getParameterTypes(); + } + + @Nonnull + @Override + public EnumActionResult onItemUse(@Nonnull EntityPlayer player, @Nonnull World worldIn, + @Nonnull BlockPos pos, @Nonnull EnumHand hand, + @Nonnull EnumFacing facing, float hitX, float hitY, float hitZ) { + return super.onItemUse(player, worldIn, pos, hand, facing, hitX, hitY, hitZ); + } + } + + private static final class Helper2 extends Item { + + public static final String name; + public static final Class[] parameters; + static { + final Method method = Helper2.class.getDeclaredMethods()[0]; + name = method.getName(); + parameters = method.getParameterTypes(); + } + + @Nonnull + @Override + public ActionResult onItemRightClick(@Nonnull World worldIn, @Nonnull EntityPlayer playerIn, + @Nonnull EnumHand handIn) { + return super.onItemRightClick(worldIn, playerIn, handIn); + } + } + } } diff --git a/src/main/java/baritone/utils/InventorySlot.java b/src/main/java/baritone/utils/InventorySlot.java index 11987aceb..d2637ddc6 100644 --- a/src/main/java/baritone/utils/InventorySlot.java +++ b/src/main/java/baritone/utils/InventorySlot.java @@ -49,7 +49,6 @@ public final class InventorySlot { public void accept(Type type, int count) { for (int i = 0; i < count; i++) { SLOTS[this.index] = new InventorySlot(this.index, type); - System.out.println(this.index); this.index++; } } From 827125d82710c862bd792dad1a103ddbc4643e20 Mon Sep 17 00:00:00 2001 From: Brady Date: Tue, 4 Jul 2023 21:34:57 -0700 Subject: [PATCH 11/21] Inventory API thing and substitution of legacy code Also stole `Pair` from elytra branch :sunglasses: --- .../api/utils/IBaritoneInventory.java | 42 ++++++ .../baritone/api/utils/IPlayerContext.java | 2 + .../baritone/api}/utils/InventorySlot.java | 2 +- src/api/java/baritone/api/utils/Pair.java | 59 ++++++++ .../baritone/behavior/InventoryBehavior.java | 139 ++++++++---------- .../utils/player/BaritoneInventory.java | 62 ++++++++ .../utils/player/BaritonePlayerContext.java | 7 + 7 files changed, 235 insertions(+), 78 deletions(-) create mode 100644 src/api/java/baritone/api/utils/IBaritoneInventory.java rename src/{main/java/baritone => api/java/baritone/api}/utils/InventorySlot.java (99%) create mode 100644 src/api/java/baritone/api/utils/Pair.java create mode 100644 src/main/java/baritone/utils/player/BaritoneInventory.java diff --git a/src/api/java/baritone/api/utils/IBaritoneInventory.java b/src/api/java/baritone/api/utils/IBaritoneInventory.java new file mode 100644 index 000000000..a30fd97d0 --- /dev/null +++ b/src/api/java/baritone/api/utils/IBaritoneInventory.java @@ -0,0 +1,42 @@ +/* + * 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.api.utils; + +import net.minecraft.item.ItemStack; + +import java.util.stream.Stream; + +/** + * @author Brady + */ +public interface IBaritoneInventory { + + /** + * Returns a stream containing all the player's regular inventory slots and items. In the order of hotbar, offhand, + * then main inventory, for a total of 37 slots. This explicitly does not contain the armor slots or crafting grid. + * + * @return All the player's inventory slots and items + */ + Stream> allSlots(); + + Stream> hotbarSlots(); + + Stream> inventorySlots(); + + Pair offhand(); +} diff --git a/src/api/java/baritone/api/utils/IPlayerContext.java b/src/api/java/baritone/api/utils/IPlayerContext.java index 14ca69fb9..02c41255f 100644 --- a/src/api/java/baritone/api/utils/IPlayerContext.java +++ b/src/api/java/baritone/api/utils/IPlayerContext.java @@ -40,6 +40,8 @@ public interface IPlayerContext { IPlayerController playerController(); + IBaritoneInventory inventory(); + World world(); IWorldData worldData(); diff --git a/src/main/java/baritone/utils/InventorySlot.java b/src/api/java/baritone/api/utils/InventorySlot.java similarity index 99% rename from src/main/java/baritone/utils/InventorySlot.java rename to src/api/java/baritone/api/utils/InventorySlot.java index d2637ddc6..87c730f43 100644 --- a/src/main/java/baritone/utils/InventorySlot.java +++ b/src/api/java/baritone/api/utils/InventorySlot.java @@ -15,7 +15,7 @@ * along with Baritone. If not, see . */ -package baritone.utils; +package baritone.api.utils; import java.util.function.ObjIntConsumer; diff --git a/src/api/java/baritone/api/utils/Pair.java b/src/api/java/baritone/api/utils/Pair.java new file mode 100644 index 000000000..ca7259520 --- /dev/null +++ b/src/api/java/baritone/api/utils/Pair.java @@ -0,0 +1,59 @@ +/* + * 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.api.utils; + +import java.util.Objects; + +/** + * @author Brady + */ +public final class Pair { + + private final A a; + private final B b; + + public Pair(A a, B b) { + this.a = a; + this.b = b; + } + + public A first() { + return this.a; + } + + public B second() { + return this.b; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || o.getClass() != Pair.class) { + return false; + } + Pair pair = (Pair) o; + return Objects.equals(this.a, pair.a) && Objects.equals(this.b, pair.b); + } + + @Override + public int hashCode() { + return 31 * Objects.hashCode(this.a) + Objects.hashCode(this.b); + } +} diff --git a/src/main/java/baritone/behavior/InventoryBehavior.java b/src/main/java/baritone/behavior/InventoryBehavior.java index 721322671..2641ca271 100644 --- a/src/main/java/baritone/behavior/InventoryBehavior.java +++ b/src/main/java/baritone/behavior/InventoryBehavior.java @@ -20,12 +20,12 @@ import baritone.Baritone; import baritone.api.event.events.TickEvent; import baritone.api.utils.Helper; -import baritone.utils.InventorySlot; +import baritone.api.utils.InventorySlot; +import baritone.api.utils.Pair; import baritone.utils.ToolSet; import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; -import net.minecraft.client.entity.EntityPlayerSP; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Blocks; import net.minecraft.inventory.ClickType; @@ -36,12 +36,10 @@ import javax.annotation.Nonnull; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Map; -import java.util.OptionalInt; -import java.util.Random; +import java.util.*; import java.util.function.IntPredicate; import java.util.function.Predicate; +import java.util.stream.Stream; public final class InventoryBehavior extends Behavior implements Helper { @@ -69,14 +67,7 @@ public void onTick(TickEvent event) { } if (Baritone.settings().allowHotbarManagement.value && baritone.getPathingBehavior().isPathing()) { - // TODO: Some way of indicating which slots are currently reserved by this setting - if (firstValidThrowaway() >= 9) { // aka there are none on the hotbar, but there are some in main inventory - requestSwapWithHotBar(firstValidThrowaway(), 8); - } - int pick = bestToolAgainst(Blocks.STONE, ItemPickaxe.class); - if (pick >= 9) { - requestSwapWithHotBar(pick, 0); - } + this.setupHotbar(); } if (lastTickRequestedMove != null) { @@ -85,6 +76,22 @@ public void onTick(TickEvent event) { } } + private void setupHotbar() { + // TODO: Some way of indicating which slots are currently reserved by this setting + + final InventorySlot throwaway = this.findSlotMatching(this::isThrowawayItem); + if (throwaway != null && throwaway.getType() == InventorySlot.Type.INVENTORY) { + // aka there are none on the hotbar, but there are some in main inventory + this.requestSwapWithHotBar(throwaway.getInventoryIndex(), 8); + return; + } + + final InventorySlot pick = bestToolAgainst(Blocks.STONE, ItemPickaxe.class); + if (pick != null && pick.getType() == InventorySlot.Type.INVENTORY) { + requestSwapWithHotBar(pick.getInventoryIndex(), 0); + } + } + public boolean attemptToPutOnHotbar(int inMainInvy, IntPredicate disallowedHotbar) { OptionalInt destination = getTempHotbarSlot(disallowedHotbar); if (destination.isPresent()) { @@ -132,37 +139,22 @@ private boolean requestSwapWithHotBar(int inInventory, int inHotbar) { return true; } - private int firstValidThrowaway() { // TODO offhand idk - NonNullList invy = ctx.player().inventory.mainInventory; - for (int i = 0; i < invy.size(); i++) { - if (this.isThrowawayItem(invy.get(i))) { - return i; - } - } - return -1; - } - - private int bestToolAgainst(Block against, Class cla$$) { - NonNullList invy = ctx.player().inventory.mainInventory; - int bestInd = -1; - double bestSpeed = -1; - for (int i = 0; i < invy.size(); i++) { - ItemStack stack = invy.get(i); - if (stack.isEmpty()) { - continue; - } - if (Baritone.settings().itemSaver.value && (stack.getItemDamage() + Baritone.settings().itemSaverThreshold.value) >= stack.getMaxDamage() && stack.getMaxDamage() > 1) { - continue; - } - if (cla$$.isInstance(stack.getItem())) { - double speed = ToolSet.calculateSpeedVsBlock(stack, against.getDefaultState()); // takes into account enchants - if (speed > bestSpeed) { - bestSpeed = speed; - bestInd = i; + private InventorySlot bestToolAgainst(final Block against, final Class cla$$) { + return this.findBestSlotMatching( + Comparator.comparingDouble(stack -> ToolSet.calculateSpeedVsBlock(stack, against.getDefaultState())), + stack -> { + if (stack.isEmpty()) { + return false; + } + if (Baritone.settings().itemSaver.value + && stack.getItemDamage() + Baritone.settings().itemSaverThreshold.value >= stack.getMaxDamage() + && stack.getMaxDamage() > 1 + ) { + return false; + } + return cla$$.isInstance(stack.getItem()); } - } - } - return bestInd; + ); } public boolean hasGenericThrowaway() { @@ -250,47 +242,40 @@ public SelectionStrategy resolveSelectionStrategy(Predicate d return null; } + public InventorySlot findSlotMatching(final Predicate filter) { + return this.findBestSlotMatching(null, filter); + } + /** - * Returns an {@link InventorySlot} that contains a stack matching the given predicate. The priority of the - * returned slot is the hotbar, offhand, and finally the main inventory, if {@link #canAccessInventory()} is - * {@code true}. Additionally, for the hotbar and main inventory, slots with a lower index will be returned. + * Returns an {@link InventorySlot} that contains a stack matching the given predicate. A comparator may be + * specified to prioritize slot selection. The comparator may be {@code null}, in which case, the first slot + * matching the predicate is returned. The considered slots are in the order of hotbar, offhand, and finally the + * main inventory (if {@link #canAccessInventory()} is {@code true}). * - * @param desired The predicate to match + * @param comparator A comparator to find the best element, may be {@code null} + * @param filter The predicate to match * @return A matching slot, or {@code null} if none. */ - public InventorySlot findSlotMatching(final Predicate desired) { - final InventorySlot hotbar = this.findHotbarMatching(desired); - if (hotbar != null) { - return hotbar; - } - - final EntityPlayerSP p = ctx.player(); - final NonNullList inv = p.inventory.mainInventory; + public InventorySlot findBestSlotMatching(final Comparator comparator, final Predicate filter) { + return this.findBestMatching0(ctx.inventory().allSlots(), comparator, filter); + } - if (desired.test(p.inventory.offHandInventory.get(0))) { - return InventorySlot.offhand(); - } + public InventorySlot findHotbarMatching(final Predicate filter) { + return this.findBestHotbarMatching(null, filter); + } - if (this.canAccessInventory()) { - for (int i = 9; i < 36; i++) { - if (desired.test(inv.get(i))) { - return InventorySlot.inventory(i); - } - } - } - return null; + public InventorySlot findBestHotbarMatching(final Comparator comparator, final Predicate filter) { + return this.findBestMatching0(ctx.inventory().hotbarSlots(), comparator, filter); } - public InventorySlot findHotbarMatching(final Predicate desired) { - final EntityPlayerSP p = ctx.player(); - final NonNullList inv = p.inventory.mainInventory; - for (int i = 0; i < 9; i++) { - ItemStack item = inv.get(i); - if (desired.test(item)) { - return InventorySlot.hotbar(i); - } - } - return null; + private InventorySlot findBestMatching0(final Stream> slots, + final Comparator comparator, + final Predicate filter) { + final Stream> filtered = slots.filter(slot -> filter.test(slot.second())); + return (comparator != null + ? filtered.max((a, b) -> comparator.compare(a.second(), b.second())) + : filtered.findFirst() + ).map(Pair::first).orElse(null); } public boolean canAccessInventory() { diff --git a/src/main/java/baritone/utils/player/BaritoneInventory.java b/src/main/java/baritone/utils/player/BaritoneInventory.java new file mode 100644 index 000000000..58f1e992d --- /dev/null +++ b/src/main/java/baritone/utils/player/BaritoneInventory.java @@ -0,0 +1,62 @@ +/* + * This file is part of Baritone. + * + * Baritone is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Baritone is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Baritone. If not, see . + */ + +package baritone.utils.player; + +import baritone.api.utils.IBaritoneInventory; +import baritone.api.utils.IPlayerContext; +import baritone.api.utils.InventorySlot; +import baritone.api.utils.Pair; +import net.minecraft.item.ItemStack; + +import java.util.stream.IntStream; +import java.util.stream.Stream; + +/** + * @author Brady + * @since 7/4/2023 + */ +public final class BaritoneInventory implements IBaritoneInventory { + + private final IPlayerContext ctx; + + public BaritoneInventory(IPlayerContext ctx) { + this.ctx = ctx; + } + + @Override + public Stream> allSlots() { + return Stream.concat(this.hotbarSlots(), Stream.concat(Stream.of(this.offhand()), this.inventorySlots())); + } + + @Override + public Stream> hotbarSlots() { + return IntStream.range(0, 9).mapToObj(i -> + new Pair<>(InventorySlot.hotbar(i), ctx.player().inventory.mainInventory.get(i))); + } + + @Override + public Stream> inventorySlots() { + return IntStream.range(9, 36).mapToObj(i -> + new Pair<>(InventorySlot.inventory(i), ctx.player().inventory.mainInventory.get(i))); + } + + @Override + public Pair offhand() { + return new Pair<>(InventorySlot.offhand(), ctx.player().inventory.offHandInventory.get(0)); + } +} diff --git a/src/main/java/baritone/utils/player/BaritonePlayerContext.java b/src/main/java/baritone/utils/player/BaritonePlayerContext.java index 282d3d8b6..e7eaecb52 100644 --- a/src/main/java/baritone/utils/player/BaritonePlayerContext.java +++ b/src/main/java/baritone/utils/player/BaritonePlayerContext.java @@ -38,11 +38,13 @@ public final class BaritonePlayerContext implements IPlayerContext { private final Baritone baritone; private final Minecraft mc; private final IPlayerController playerController; + private final IBaritoneInventory inventory; public BaritonePlayerContext(Baritone baritone, Minecraft mc) { this.baritone = baritone; this.mc = mc; this.playerController = new BaritonePlayerController(mc); + this.inventory = new BaritoneInventory(this); } @Override @@ -60,6 +62,11 @@ public IPlayerController playerController() { return this.playerController; } + @Override + public IBaritoneInventory inventory() { + return this.player() == null ? null : this.inventory; + } + @Override public World world() { return this.mc.world; From 57c3e369b0650cdad14fc54fe4795e6ed6d90c18 Mon Sep 17 00:00:00 2001 From: Brady Date: Tue, 4 Jul 2023 21:44:10 -0700 Subject: [PATCH 12/21] =?UTF-8?q?=F0=9F=92=9A=20appease=20codacy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/baritone/behavior/InventoryBehavior.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/baritone/behavior/InventoryBehavior.java b/src/main/java/baritone/behavior/InventoryBehavior.java index 2641ca271..6a484ea87 100644 --- a/src/main/java/baritone/behavior/InventoryBehavior.java +++ b/src/main/java/baritone/behavior/InventoryBehavior.java @@ -237,6 +237,8 @@ public SelectionStrategy resolveSelectionStrategy(Predicate d }); } break; + default: + break; } } return null; From 600d5e8a6794625af19476c18c023537bebb13f7 Mon Sep 17 00:00:00 2001 From: Brady Date: Wed, 5 Jul 2023 10:13:38 -0700 Subject: [PATCH 13/21] More `IBaritoneInventory` api methods --- .../api/utils/IBaritoneInventory.java | 9 ++++++-- .../baritone/api/utils/InventorySlot.java | 23 +++++++++++++------ .../baritone/behavior/InventoryBehavior.java | 4 ++-- .../utils/player/BaritoneInventory.java | 20 ++++++++++++---- 4 files changed, 41 insertions(+), 15 deletions(-) diff --git a/src/api/java/baritone/api/utils/IBaritoneInventory.java b/src/api/java/baritone/api/utils/IBaritoneInventory.java index a30fd97d0..b5790ecf8 100644 --- a/src/api/java/baritone/api/utils/IBaritoneInventory.java +++ b/src/api/java/baritone/api/utils/IBaritoneInventory.java @@ -27,8 +27,9 @@ public interface IBaritoneInventory { /** - * Returns a stream containing all the player's regular inventory slots and items. In the order of hotbar, offhand, - * then main inventory, for a total of 37 slots. This explicitly does not contain the armor slots or crafting grid. + * Returns a stream containing all the player's regular inventory slots and items. The elements of the stream are in + * the order of hotbar, offhand, then main inventory, for a total of 37 slots. This explicitly does not contain the + * armor slots or crafting grid, which may otherwise be accessed with {@link #armorSlots()} and/or {@link #itemAt}. * * @return All the player's inventory slots and items */ @@ -39,4 +40,8 @@ public interface IBaritoneInventory { Stream> inventorySlots(); Pair offhand(); + + Stream> armorSlots(); + + ItemStack itemAt(InventorySlot slot); } diff --git a/src/api/java/baritone/api/utils/InventorySlot.java b/src/api/java/baritone/api/utils/InventorySlot.java index 87c730f43..441563155 100644 --- a/src/api/java/baritone/api/utils/InventorySlot.java +++ b/src/api/java/baritone/api/utils/InventorySlot.java @@ -80,6 +80,13 @@ public Type getType() { return this.type; } + /** + * Returns the index of this slot in {@code mainInventory}. If this slot does not correspond to an index into + * {@code mainInventory}, then an {@link IllegalArgumentException} is thrown. + * + * @return The index of this slot in the player's {@code mainInventory} + * @throws IllegalArgumentException if type is not {@link Type#HOTBAR} or {@link Type#INVENTORY} + */ public int getInventoryIndex() { switch (this.getType()) { case HOTBAR: @@ -91,18 +98,20 @@ public int getInventoryIndex() { } } - public static InventorySlot hotbar(final int index) { - if (index < 0 || index >= 9) { - throw new IllegalArgumentException(); + public static InventorySlot inventory(final int index) { + if (index >= 0 && index < 9) { + return SLOTS[index + 36]; // HOTBAR + } else if (index >= 9 && index < 36) { + return SLOTS[index]; // INVENTORY } - return SLOTS[index + 36]; + throw new IllegalArgumentException(); } - public static InventorySlot inventory(final int index) { - if (index < 9 || index >= 36) { + public static InventorySlot armor(final int index) { + if (index < 0 || index >= 4) { throw new IllegalArgumentException(); } - return SLOTS[index]; + return SLOTS[index + 5]; } public static InventorySlot offhand() { diff --git a/src/main/java/baritone/behavior/InventoryBehavior.java b/src/main/java/baritone/behavior/InventoryBehavior.java index 6a484ea87..898c2d0f6 100644 --- a/src/main/java/baritone/behavior/InventoryBehavior.java +++ b/src/main/java/baritone/behavior/InventoryBehavior.java @@ -86,9 +86,9 @@ private void setupHotbar() { return; } - final InventorySlot pick = bestToolAgainst(Blocks.STONE, ItemPickaxe.class); + final InventorySlot pick = this.bestToolAgainst(Blocks.STONE, ItemPickaxe.class); if (pick != null && pick.getType() == InventorySlot.Type.INVENTORY) { - requestSwapWithHotBar(pick.getInventoryIndex(), 0); + this.requestSwapWithHotBar(pick.getInventoryIndex(), 0); } } diff --git a/src/main/java/baritone/utils/player/BaritoneInventory.java b/src/main/java/baritone/utils/player/BaritoneInventory.java index 58f1e992d..fb042864f 100644 --- a/src/main/java/baritone/utils/player/BaritoneInventory.java +++ b/src/main/java/baritone/utils/player/BaritoneInventory.java @@ -45,18 +45,30 @@ public Stream> allSlots() { @Override public Stream> hotbarSlots() { - return IntStream.range(0, 9).mapToObj(i -> - new Pair<>(InventorySlot.hotbar(i), ctx.player().inventory.mainInventory.get(i))); + return IntStream.range(0, 9).mapToObj(InventorySlot::inventory).map(this::itemSlotPairAt); } @Override public Stream> inventorySlots() { - return IntStream.range(9, 36).mapToObj(i -> - new Pair<>(InventorySlot.inventory(i), ctx.player().inventory.mainInventory.get(i))); + return IntStream.range(9, 36).mapToObj(InventorySlot::inventory).map(this::itemSlotPairAt); } @Override public Pair offhand() { return new Pair<>(InventorySlot.offhand(), ctx.player().inventory.offHandInventory.get(0)); } + + @Override + public Stream> armorSlots() { + return IntStream.range(0, 4).mapToObj(InventorySlot::armor).map(this::itemSlotPairAt); + } + + @Override + public ItemStack itemAt(InventorySlot slot) { + return ctx.player().inventoryContainer.getSlot(slot.getSlotId()).getStack(); + } + + private Pair itemSlotPairAt(InventorySlot slot) { + return new Pair<>(slot, this.itemAt(slot)); + } } From 2408c9b6af90de90231d449e0b2932aefc004146 Mon Sep 17 00:00:00 2001 From: Brady Date: Wed, 5 Jul 2023 11:14:55 -0700 Subject: [PATCH 14/21] Backport primitive `computeIfAbsent` --- src/main/java/baritone/utils/ToolSet.java | 73 +++++++++++++++++++---- 1 file changed, 63 insertions(+), 10 deletions(-) diff --git a/src/main/java/baritone/utils/ToolSet.java b/src/main/java/baritone/utils/ToolSet.java index d20bc6e56..490cbaef8 100644 --- a/src/main/java/baritone/utils/ToolSet.java +++ b/src/main/java/baritone/utils/ToolSet.java @@ -20,6 +20,7 @@ import baritone.Baritone; import baritone.PerformanceCritical; import baritone.utils.accessor.IItemTool; +import it.unimi.dsi.fastutil.HashCommon; import it.unimi.dsi.fastutil.objects.Reference2DoubleOpenHashMap; import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; @@ -31,7 +32,7 @@ import net.minecraft.item.ItemSword; import net.minecraft.item.ItemTool; -import java.util.function.Function; +import java.util.function.ToDoubleFunction; /** * A cached list of the best tools on the hotbar for any block @@ -44,23 +45,22 @@ public final class ToolSet { * A cache mapping a {@link Block} to how long it will take to break * with this toolset, given the optimum tool is used. */ - private final Reference2DoubleOpenHashMap breakStrengthCache; + private final Cache breakStrengthCache; /** * My buddy leijurv owned me so we have this to not create a new lambda instance. */ - private final Function backendCalculation; + private final ToDoubleFunction backendCalculation; private final EntityPlayerSP player; public ToolSet(EntityPlayerSP player) { - this.breakStrengthCache = new Reference2DoubleOpenHashMap<>(); + this.breakStrengthCache = new Cache(); this.player = player; if (Baritone.settings().considerPotionEffects.value) { - double amplifier = potionAmplifier(); - Function amplify = x -> amplifier * x; - this.backendCalculation = amplify.compose(this::getBestDestructionTime); + double amplifier = this.potionAmplifier(); + this.backendCalculation = block -> amplifier * this.getBestDestructionTime(block); } else { this.backendCalculation = this::getBestDestructionTime; } @@ -74,8 +74,6 @@ public ToolSet(EntityPlayerSP player) { */ @PerformanceCritical public double getStrVsBlock(IBlockState state) { - // fastutil 8+ has a computeIfAbsent overload that uses a primitive mapping function - // for now, we're stuck with the boxed implementation return this.breakStrengthCache.computeIfAbsent(state.getBlock(), this.backendCalculation); } @@ -85,7 +83,7 @@ public double getStrVsBlock(IBlockState state) { * but in that case we don't really care. * * @param itemStack a possibly empty ItemStack - * @return values from 0 up + * @return The tool's harvest level, or {@code -1} if the stack isn't a tool */ private int getMaterialCost(ItemStack itemStack) { if (itemStack.getItem() instanceof ItemTool) { @@ -229,4 +227,59 @@ private double potionAmplifier() { } return speed; } + + /* + * Copyright (C) 2002-2022 Sebastiano Vigna + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + private static final class Cache extends Reference2DoubleOpenHashMap { + + public double computeIfAbsent(final Block key, final ToDoubleFunction mappingFunction) { + int pos = this.find(key); + if (pos >= 0) { + return this.value[pos]; + } else { + double newValue = mappingFunction.applyAsDouble(key); + this.insert(-pos - 1, key, newValue); + return newValue; + } + } + + private int find(final Block k) { + if (((k) == (null))) return containsNullKey ? n : -(n + 1); + Block curr; + final Block[] key = this.key; + int pos; + // The starting point. + if (((curr = key[pos = (it.unimi.dsi.fastutil.HashCommon.mix(System.identityHashCode(k))) & mask]) == (null))) return -(pos + 1); + if (((k) == (curr))) return pos; + // There's always an unused entry. + while (true) { + if (((curr = key[pos = (pos + 1) & mask]) == (null))) return -(pos + 1); + if (((k) == (curr))) return pos; + } + } + + private void insert(int pos, Block k, double v) { + if (pos == this.n) { + this.containsNullKey = true; + } + this.key[pos] = k; + this.value[pos] = v; + if (this.size++ >= this.maxFill) { + this.rehash(HashCommon.arraySize(this.size + 1, this.f)); + } + } + } } From ec089cdeaafd2f68a56bb91af9d73959a3a0b660 Mon Sep 17 00:00:00 2001 From: Brady Date: Wed, 5 Jul 2023 11:24:34 -0700 Subject: [PATCH 15/21] aha whoopsie --- src/main/java/baritone/utils/ToolSet.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/baritone/utils/ToolSet.java b/src/main/java/baritone/utils/ToolSet.java index 490cbaef8..79f511191 100644 --- a/src/main/java/baritone/utils/ToolSet.java +++ b/src/main/java/baritone/utils/ToolSet.java @@ -258,8 +258,8 @@ public double computeIfAbsent(final Block key, final ToDoubleFunction map private int find(final Block k) { if (((k) == (null))) return containsNullKey ? n : -(n + 1); - Block curr; - final Block[] key = this.key; + Object curr; + final Object[] key = this.key; int pos; // The starting point. if (((curr = key[pos = (it.unimi.dsi.fastutil.HashCommon.mix(System.identityHashCode(k))) & mask]) == (null))) return -(pos + 1); @@ -275,7 +275,8 @@ private void insert(int pos, Block k, double v) { if (pos == this.n) { this.containsNullKey = true; } - this.key[pos] = k; + final Object[] key = this.key; + key[pos] = k; this.value[pos] = v; if (this.size++ >= this.maxFill) { this.rehash(HashCommon.arraySize(this.size + 1, this.f)); From 53651980ad728570f284eba9617a4961842b9be0 Mon Sep 17 00:00:00 2001 From: Brady Date: Wed, 5 Jul 2023 11:45:04 -0700 Subject: [PATCH 16/21] Mitigate #3945 --- src/main/java/baritone/utils/ToolSet.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/baritone/utils/ToolSet.java b/src/main/java/baritone/utils/ToolSet.java index 79f511191..245043568 100644 --- a/src/main/java/baritone/utils/ToolSet.java +++ b/src/main/java/baritone/utils/ToolSet.java @@ -178,7 +178,16 @@ private double avoidanceMultiplier(Block b) { * @return how long it would take in ticks */ public static double calculateSpeedVsBlock(ItemStack item, IBlockState state) { - float hardness = state.getBlockHardness(null, null); + float hardness; + try { + // noinspection DataFlowIssue + hardness = state.getBlockHardness(null, null); + } catch (NullPointerException ignored) { + // Just catch the exception and act as if the block is unbreakable. Even in situations where we could + // reasonably determine the hardness by passing the correct world/position (not via 'getStrVsBlock' during + // performance critical cost calculation), it's not worth it for the sake of consistency. + return -1; + } if (hardness < 0) { return -1; } From 2e32c63bc27cf365cdd26e1754cc7e231bea3fc3 Mon Sep 17 00:00:00 2001 From: Brady Date: Wed, 5 Jul 2023 18:52:53 -0700 Subject: [PATCH 17/21] Calculate break time based on block state --- .../baritone/behavior/InventoryBehavior.java | 2 +- .../pathing/movement/MovementHelper.java | 18 ++--- src/main/java/baritone/utils/ToolSet.java | 72 +++++++++---------- .../utils/player/BaritoneInventory.java | 1 - 4 files changed, 45 insertions(+), 48 deletions(-) diff --git a/src/main/java/baritone/behavior/InventoryBehavior.java b/src/main/java/baritone/behavior/InventoryBehavior.java index 898c2d0f6..a07b5db28 100644 --- a/src/main/java/baritone/behavior/InventoryBehavior.java +++ b/src/main/java/baritone/behavior/InventoryBehavior.java @@ -332,7 +332,7 @@ public static boolean couldInteract(final ItemStack stack) { final Method onItemUse = itemClass.getMethod(Helper1.name, Helper1.parameters); final Method onItemRightClick = itemClass.getMethod(Helper2.name, Helper2.parameters); - // If the declaring class isn't Item, then the method is overriden + // If the declaring class isn't Item, then the method is overridden return onItemUse.getDeclaringClass() != Item.class || onItemRightClick.getDeclaringClass() != Item.class; } catch (NoSuchMethodException ignored) { diff --git a/src/main/java/baritone/pathing/movement/MovementHelper.java b/src/main/java/baritone/pathing/movement/MovementHelper.java index 881bb6f15..05ec32170 100644 --- a/src/main/java/baritone/pathing/movement/MovementHelper.java +++ b/src/main/java/baritone/pathing/movement/MovementHelper.java @@ -577,23 +577,23 @@ static boolean isBottomSlab(IBlockState state) { /** * AutoTool for a specific block * - * @param ctx The player context - * @param b the blockstate to mine + * @param ctx The player context + * @param state The blockstate to mine */ - static void switchToBestToolFor(IPlayerContext ctx, IBlockState b) { - switchToBestToolFor(ctx, b, new ToolSet(ctx.player()), BaritoneAPI.getSettings().preferSilkTouch.value); + static void switchToBestToolFor(IPlayerContext ctx, IBlockState state) { + switchToBestToolFor(ctx, state, new ToolSet(ctx.player()), Baritone.settings().preferSilkTouch.value); } /** * AutoTool for a specific block with precomputed ToolSet data * - * @param ctx The player context - * @param b the blockstate to mine - * @param ts previously calculated ToolSet + * @param ctx The player context + * @param state The blockstate to mine + * @param ts Previously calculated ToolSet */ - static void switchToBestToolFor(IPlayerContext ctx, IBlockState b, ToolSet ts, boolean preferSilkTouch) { + static void switchToBestToolFor(IPlayerContext ctx, IBlockState state, ToolSet ts, boolean preferSilkTouch) { if (Baritone.settings().autoTool.value && !Baritone.settings().assumeExternalAutoTool.value) { - ctx.player().inventory.currentItem = ts.getBestSlot(b.getBlock(), preferSilkTouch); + ctx.player().inventory.currentItem = ts.getBestSlot(state, preferSilkTouch, false); } } diff --git a/src/main/java/baritone/utils/ToolSet.java b/src/main/java/baritone/utils/ToolSet.java index 245043568..5fd62371a 100644 --- a/src/main/java/baritone/utils/ToolSet.java +++ b/src/main/java/baritone/utils/ToolSet.java @@ -50,7 +50,7 @@ public final class ToolSet { /** * My buddy leijurv owned me so we have this to not create a new lambda instance. */ - private final ToDoubleFunction backendCalculation; + private final ToDoubleFunction backendCalculation; private final EntityPlayerSP player; @@ -69,12 +69,23 @@ public ToolSet(EntityPlayerSP player) { /** * Using the best tool on the hotbar, how fast we can mine this block * - * @param state the blockstate to be mined + * @param state the state to be mined * @return the speed of how fast we'll mine it. 1/(time in ticks) */ @PerformanceCritical public double getStrVsBlock(IBlockState state) { - return this.breakStrengthCache.computeIfAbsent(state.getBlock(), this.backendCalculation); + return this.breakStrengthCache.computeIfAbsent(state, this.backendCalculation); + } + + /** + * Calculate how effectively a block can be destroyed + * + * @param state the block state to be mined + * @return A double containing the destruction ticks with the best tool + */ + private double getBestDestructionTime(IBlockState state) { + final ItemStack stack = player.inventory.getStackInSlot(this.getBestSlot(state, false, true)); + return calculateSpeedVsBlock(stack, state) * avoidanceMultiplier(state.getBlock()); } /** @@ -85,7 +96,7 @@ public double getStrVsBlock(IBlockState state) { * @param itemStack a possibly empty ItemStack * @return The tool's harvest level, or {@code -1} if the stack isn't a tool */ - private int getMaterialCost(ItemStack itemStack) { + private static int getMaterialCost(ItemStack itemStack) { if (itemStack.getItem() instanceof ItemTool) { ItemTool tool = (ItemTool) itemStack.getItem(); return ((IItemTool) tool).getHarvestLevel(); @@ -94,22 +105,16 @@ private int getMaterialCost(ItemStack itemStack) { } } - public boolean hasSilkTouch(ItemStack stack) { - return EnchantmentHelper.getEnchantmentLevel(Enchantments.SILK_TOUCH, stack) > 0; - } - /** * Calculate which tool on the hotbar is best for mining, depending on an override setting, * related to auto tool movement cost, it will either return current selected slot, or the best slot. * - * @param b the blockstate to be mined + * @param state the blockstate to be mined + * @param preferSilkTouch whether to prefer silk touch tools + * @param pathingCalculation whether the call to this method is for pathing calculation * @return An int containing the index in the tools array that worked best */ - public int getBestSlot(Block b, boolean preferSilkTouch) { - return getBestSlot(b, preferSilkTouch, false); - } - - public int getBestSlot(Block b, boolean preferSilkTouch, boolean pathingCalculation) { + public int getBestSlot(IBlockState state, boolean preferSilkTouch, boolean pathingCalculation) { /* If we actually want know what efficiency our held item has instead of the best one @@ -123,7 +128,6 @@ possible, this lets us make pathing depend on the actual tool to be used (if aut double highestSpeed = Double.NEGATIVE_INFINITY; int lowestCost = Integer.MIN_VALUE; boolean bestSilkTouch = false; - IBlockState blockState = b.getDefaultState(); for (int i = 0; i < 9; i++) { ItemStack itemStack = player.inventory.getStackInSlot(i); if (!Baritone.settings().useSwordToMine.value && itemStack.getItem() instanceof ItemSword) { @@ -133,7 +137,7 @@ possible, this lets us make pathing depend on the actual tool to be used (if aut if (Baritone.settings().itemSaver.value && (itemStack.getItemDamage() + Baritone.settings().itemSaverThreshold.value) >= itemStack.getMaxDamage() && itemStack.getMaxDamage() > 1) { continue; } - double speed = calculateSpeedVsBlock(itemStack, blockState); + double speed = calculateSpeedVsBlock(itemStack, state); boolean silkTouch = hasSilkTouch(itemStack); if (speed > highestSpeed) { highestSpeed = speed; @@ -154,28 +158,13 @@ possible, this lets us make pathing depend on the actual tool to be used (if aut return best; } - /** - * Calculate how effectively a block can be destroyed - * - * @param b the blockstate to be mined - * @return A double containing the destruction ticks with the best tool - */ - private double getBestDestructionTime(Block b) { - ItemStack stack = player.inventory.getStackInSlot(getBestSlot(b, false, true)); - return calculateSpeedVsBlock(stack, b.getDefaultState()) * avoidanceMultiplier(b); - } - - private double avoidanceMultiplier(Block b) { - return Baritone.settings().blocksToAvoidBreaking.value.contains(b) ? Baritone.settings().avoidBreakingMultiplier.value : 1; - } - /** * Calculates how long would it take to mine the specified block given the best tool * in this toolset is used. A negative value is returned if the specified block is unbreakable. * * @param item the item to mine it with * @param state the blockstate to be mined - * @return how long it would take in ticks + * @return the speed of how fast we'll mine it. 1/(time in ticks) */ public static double calculateSpeedVsBlock(ItemStack item, IBlockState state) { float hardness; @@ -208,6 +197,15 @@ public static double calculateSpeedVsBlock(ItemStack item, IBlockState state) { } } + private static double avoidanceMultiplier(Block block) { + return Baritone.settings().blocksToAvoidBreaking.value.contains(block) + ? Baritone.settings().avoidBreakingMultiplier.value : 1; + } + + private static boolean hasSilkTouch(ItemStack stack) { + return EnchantmentHelper.getEnchantmentLevel(Enchantments.SILK_TOUCH, stack) > 0; + } + /** * Calculates any modifier to breaking time based on status effects. * @@ -252,9 +250,9 @@ private double potionAmplifier() { * See the License for the specific language governing permissions and * limitations under the License. */ - private static final class Cache extends Reference2DoubleOpenHashMap { + private static final class Cache extends Reference2DoubleOpenHashMap { - public double computeIfAbsent(final Block key, final ToDoubleFunction mappingFunction) { + public double computeIfAbsent(final IBlockState key, final ToDoubleFunction mappingFunction) { int pos = this.find(key); if (pos >= 0) { return this.value[pos]; @@ -265,13 +263,13 @@ public double computeIfAbsent(final Block key, final ToDoubleFunction map } } - private int find(final Block k) { + private int find(final IBlockState k) { if (((k) == (null))) return containsNullKey ? n : -(n + 1); Object curr; final Object[] key = this.key; int pos; // The starting point. - if (((curr = key[pos = (it.unimi.dsi.fastutil.HashCommon.mix(System.identityHashCode(k))) & mask]) == (null))) return -(pos + 1); + if (((curr = key[pos = (HashCommon.mix(System.identityHashCode(k))) & mask]) == (null))) return -(pos + 1); if (((k) == (curr))) return pos; // There's always an unused entry. while (true) { @@ -280,7 +278,7 @@ private int find(final Block k) { } } - private void insert(int pos, Block k, double v) { + private void insert(int pos, IBlockState k, double v) { if (pos == this.n) { this.containsNullKey = true; } diff --git a/src/main/java/baritone/utils/player/BaritoneInventory.java b/src/main/java/baritone/utils/player/BaritoneInventory.java index fb042864f..b52bea7d9 100644 --- a/src/main/java/baritone/utils/player/BaritoneInventory.java +++ b/src/main/java/baritone/utils/player/BaritoneInventory.java @@ -28,7 +28,6 @@ /** * @author Brady - * @since 7/4/2023 */ public final class BaritoneInventory implements IBaritoneInventory { From 827ce7e2a81c2c847095cc354975160ca12e088a Mon Sep 17 00:00:00 2001 From: Brady Date: Wed, 5 Jul 2023 19:39:08 -0700 Subject: [PATCH 18/21] Modify `ToolSet` to accept an `IPlayerContext` --- .../baritone/api/utils/IPlayerContext.java | 3 ++ .../pathing/movement/CalculationContext.java | 2 +- .../pathing/movement/MovementHelper.java | 2 +- src/main/java/baritone/utils/ToolSet.java | 30 +++++++++---------- .../utils/player/BaritonePlayerContext.java | 6 ++++ 5 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/api/java/baritone/api/utils/IPlayerContext.java b/src/api/java/baritone/api/utils/IPlayerContext.java index 02c41255f..f61de8c6e 100644 --- a/src/api/java/baritone/api/utils/IPlayerContext.java +++ b/src/api/java/baritone/api/utils/IPlayerContext.java @@ -17,6 +17,7 @@ package baritone.api.utils; +import baritone.api.IBaritone; import baritone.api.cache.IWorldData; import net.minecraft.block.BlockSlab; import net.minecraft.client.Minecraft; @@ -34,6 +35,8 @@ */ public interface IPlayerContext { + IBaritone baritone(); + Minecraft minecraft(); EntityPlayerSP player(); diff --git a/src/main/java/baritone/pathing/movement/CalculationContext.java b/src/main/java/baritone/pathing/movement/CalculationContext.java index 129f00e20..5ed8f928f 100644 --- a/src/main/java/baritone/pathing/movement/CalculationContext.java +++ b/src/main/java/baritone/pathing/movement/CalculationContext.java @@ -93,7 +93,7 @@ public CalculationContext(IBaritone baritone, boolean forUseOnAnotherThread) { this.world = baritone.getPlayerContext().world(); this.worldData = (WorldData) baritone.getPlayerContext().worldData(); this.bsi = new BlockStateInterface(baritone.getPlayerContext(), forUseOnAnotherThread); - this.toolSet = new ToolSet(player); + this.toolSet = new ToolSet(baritone.getPlayerContext()); this.hasThrowaway = Baritone.settings().allowPlace.value && ((Baritone) baritone).getInventoryBehavior().hasGenericThrowaway(); this.hasWaterBucket = Baritone.settings().allowWaterBucketFall.value && InventoryPlayer.isHotbar(player.inventory.getSlotFor(STACK_BUCKET_WATER)) && !world.provider.isNether(); this.canSprint = Baritone.settings().allowSprint.value && player.getFoodStats().getFoodLevel() > 6; diff --git a/src/main/java/baritone/pathing/movement/MovementHelper.java b/src/main/java/baritone/pathing/movement/MovementHelper.java index 05ec32170..7e8044e9b 100644 --- a/src/main/java/baritone/pathing/movement/MovementHelper.java +++ b/src/main/java/baritone/pathing/movement/MovementHelper.java @@ -581,7 +581,7 @@ static boolean isBottomSlab(IBlockState state) { * @param state The blockstate to mine */ static void switchToBestToolFor(IPlayerContext ctx, IBlockState state) { - switchToBestToolFor(ctx, state, new ToolSet(ctx.player()), Baritone.settings().preferSilkTouch.value); + switchToBestToolFor(ctx, state, new ToolSet(ctx), Baritone.settings().preferSilkTouch.value); } /** diff --git a/src/main/java/baritone/utils/ToolSet.java b/src/main/java/baritone/utils/ToolSet.java index 5fd62371a..95ace30cf 100644 --- a/src/main/java/baritone/utils/ToolSet.java +++ b/src/main/java/baritone/utils/ToolSet.java @@ -19,12 +19,12 @@ import baritone.Baritone; import baritone.PerformanceCritical; +import baritone.api.utils.IPlayerContext; import baritone.utils.accessor.IItemTool; import it.unimi.dsi.fastutil.HashCommon; import it.unimi.dsi.fastutil.objects.Reference2DoubleOpenHashMap; import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; -import net.minecraft.client.entity.EntityPlayerSP; import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.init.Enchantments; import net.minecraft.init.MobEffects; @@ -52,17 +52,17 @@ public final class ToolSet { */ private final ToDoubleFunction backendCalculation; - private final EntityPlayerSP player; + private final IPlayerContext ctx; - public ToolSet(EntityPlayerSP player) { + public ToolSet(IPlayerContext ctx) { + this.ctx = ctx; this.breakStrengthCache = new Cache(); - this.player = player; if (Baritone.settings().considerPotionEffects.value) { double amplifier = this.potionAmplifier(); - this.backendCalculation = block -> amplifier * this.getBestDestructionTime(block); + this.backendCalculation = block -> amplifier * this.getBestDestructionSpeed(block); } else { - this.backendCalculation = this::getBestDestructionTime; + this.backendCalculation = this::getBestDestructionSpeed; } } @@ -81,10 +81,10 @@ public double getStrVsBlock(IBlockState state) { * Calculate how effectively a block can be destroyed * * @param state the block state to be mined - * @return A double containing the destruction ticks with the best tool + * @return A double containing the destruction speed with the best tool */ - private double getBestDestructionTime(IBlockState state) { - final ItemStack stack = player.inventory.getStackInSlot(this.getBestSlot(state, false, true)); + private double getBestDestructionSpeed(IBlockState state) { + final ItemStack stack = ctx.player().inventory.getStackInSlot(this.getBestSlot(state, false, true)); return calculateSpeedVsBlock(stack, state) * avoidanceMultiplier(state.getBlock()); } @@ -121,7 +121,7 @@ public int getBestSlot(IBlockState state, boolean preferSilkTouch, boolean pathi possible, this lets us make pathing depend on the actual tool to be used (if auto tool is disabled) */ if (!Baritone.settings().autoTool.value && pathingCalculation) { - return player.inventory.currentItem; + return ctx.player().inventory.currentItem; } int best = 0; @@ -129,7 +129,7 @@ possible, this lets us make pathing depend on the actual tool to be used (if aut int lowestCost = Integer.MIN_VALUE; boolean bestSilkTouch = false; for (int i = 0; i < 9; i++) { - ItemStack itemStack = player.inventory.getStackInSlot(i); + ItemStack itemStack = ctx.player().inventory.getStackInSlot(i); if (!Baritone.settings().useSwordToMine.value && itemStack.getItem() instanceof ItemSword) { continue; } @@ -213,11 +213,11 @@ private static boolean hasSilkTouch(ItemStack stack) { */ private double potionAmplifier() { double speed = 1; - if (player.isPotionActive(MobEffects.HASTE)) { - speed *= 1 + (player.getActivePotionEffect(MobEffects.HASTE).getAmplifier() + 1) * 0.2; + if (ctx.player().isPotionActive(MobEffects.HASTE)) { + speed *= 1 + (ctx.player().getActivePotionEffect(MobEffects.HASTE).getAmplifier() + 1) * 0.2; } - if (player.isPotionActive(MobEffects.MINING_FATIGUE)) { - switch (player.getActivePotionEffect(MobEffects.MINING_FATIGUE).getAmplifier()) { + if (ctx.player().isPotionActive(MobEffects.MINING_FATIGUE)) { + switch (ctx.player().getActivePotionEffect(MobEffects.MINING_FATIGUE).getAmplifier()) { case 0: speed *= 0.3; break; diff --git a/src/main/java/baritone/utils/player/BaritonePlayerContext.java b/src/main/java/baritone/utils/player/BaritonePlayerContext.java index e7eaecb52..4e7e1ed37 100644 --- a/src/main/java/baritone/utils/player/BaritonePlayerContext.java +++ b/src/main/java/baritone/utils/player/BaritonePlayerContext.java @@ -18,6 +18,7 @@ package baritone.utils.player; import baritone.Baritone; +import baritone.api.IBaritone; import baritone.api.cache.IWorldData; import baritone.api.utils.*; import net.minecraft.client.Minecraft; @@ -47,6 +48,11 @@ public BaritonePlayerContext(Baritone baritone, Minecraft mc) { this.inventory = new BaritoneInventory(this); } + @Override + public IBaritone baritone() { + return this.baritone; + } + @Override public Minecraft minecraft() { return this.mc; From bc18f0eabd4be03eea6984d333326d8646140505 Mon Sep 17 00:00:00 2001 From: Brady Date: Wed, 5 Jul 2023 21:44:31 -0700 Subject: [PATCH 19/21] Allow `autoTool` to pull tools from the inventory --- .../baritone/behavior/InventoryBehavior.java | 22 +++-- .../pathing/movement/MovementHelper.java | 11 ++- src/main/java/baritone/utils/ToolSet.java | 83 ++++++++----------- 3 files changed, 60 insertions(+), 56 deletions(-) diff --git a/src/main/java/baritone/behavior/InventoryBehavior.java b/src/main/java/baritone/behavior/InventoryBehavior.java index a07b5db28..c0a3abc92 100644 --- a/src/main/java/baritone/behavior/InventoryBehavior.java +++ b/src/main/java/baritone/behavior/InventoryBehavior.java @@ -23,7 +23,8 @@ import baritone.api.utils.InventorySlot; import baritone.api.utils.Pair; import baritone.utils.ToolSet; -import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.objects.Reference2BooleanMap; +import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap; import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.player.EntityPlayer; @@ -140,6 +141,7 @@ private boolean requestSwapWithHotBar(int inInventory, int inHotbar) { } private InventorySlot bestToolAgainst(final Block against, final Class cla$$) { + // TODO: Replace with ToolSet.getBestSlot return this.findBestSlotMatching( Comparator.comparingDouble(stack -> ToolSet.calculateSpeedVsBlock(stack, against.getDefaultState())), stack -> { @@ -189,11 +191,11 @@ public boolean selectThrowawayForLocation(boolean select, int x, int y, int z) { } public boolean canSelectItem(Predicate desired) { - return this.resolveSelectionStrategy(desired) != null; + return this.resolveSelectionStrategy(this.findSlotMatching(desired)) != null; } public boolean trySelectItem(Predicate desired) { - final SelectionStrategy strategy = this.resolveSelectionStrategy(desired); + final SelectionStrategy strategy = this.resolveSelectionStrategy(this.findSlotMatching(desired)); if (strategy != null) { strategy.run(); // TODO: Consider cases where returning the SelectionType is needed/useful to the caller @@ -202,8 +204,7 @@ public boolean trySelectItem(Predicate desired) { return false; } - public SelectionStrategy resolveSelectionStrategy(Predicate desired) { - final InventorySlot slot = this.findSlotMatching(desired); + public SelectionStrategy resolveSelectionStrategy(final InventorySlot slot) { if (slot != null) { switch (slot.getType()) { case HOTBAR: @@ -232,6 +233,7 @@ public SelectionStrategy resolveSelectionStrategy(Predicate d if (this.canAccessInventory()) { return SelectionStrategy.of(SelectionType.ENQUEUED, () -> { // TODO: Determine if hotbar swap can be immediate, and return type accordingly + // Also don't only swap into slot 7 that's silly requestSwapWithHotBar(slot.getInventoryIndex(), 7); ctx.player().inventory.currentItem = 7; }); @@ -244,6 +246,14 @@ public SelectionStrategy resolveSelectionStrategy(Predicate d return null; } + public InventorySlot findBestAccessibleMatching(final Comparator comparator, + final Predicate filter) { + final Stream> accessible = this.canAccessInventory() + ? ctx.inventory().allSlots() + : Stream.concat(ctx.inventory().hotbarSlots(), Stream.of(ctx.inventory().offhand())); + return this.findBestMatching0(accessible, comparator, filter); + } + public InventorySlot findSlotMatching(final Predicate filter) { return this.findBestSlotMatching(null, filter); } @@ -320,7 +330,7 @@ public enum SelectionType { private static final class ItemInteractionHelper { - private static final Map, Boolean> CACHE = new Reference2ReferenceOpenHashMap<>(); + private static final Reference2BooleanMap> CACHE = new Reference2BooleanOpenHashMap<>(); public static boolean couldInteract(final ItemStack stack) { if (stack.isEmpty()) { diff --git a/src/main/java/baritone/pathing/movement/MovementHelper.java b/src/main/java/baritone/pathing/movement/MovementHelper.java index 7e8044e9b..63abf3a59 100644 --- a/src/main/java/baritone/pathing/movement/MovementHelper.java +++ b/src/main/java/baritone/pathing/movement/MovementHelper.java @@ -18,12 +18,12 @@ package baritone.pathing.movement; import baritone.Baritone; -import baritone.api.BaritoneAPI; import baritone.api.IBaritone; import baritone.api.pathing.movement.ActionCosts; import baritone.api.pathing.movement.MovementStatus; import baritone.api.utils.*; import baritone.api.utils.input.Input; +import baritone.behavior.InventoryBehavior; import baritone.pathing.movement.MovementState.MovementTarget; import baritone.pathing.precompute.Ternary; import baritone.utils.BlockStateInterface; @@ -592,8 +592,13 @@ static void switchToBestToolFor(IPlayerContext ctx, IBlockState state) { * @param ts Previously calculated ToolSet */ static void switchToBestToolFor(IPlayerContext ctx, IBlockState state, ToolSet ts, boolean preferSilkTouch) { - if (Baritone.settings().autoTool.value && !Baritone.settings().assumeExternalAutoTool.value) { - ctx.player().inventory.currentItem = ts.getBestSlot(state, preferSilkTouch, false); + if (ToolSet.isAutoTool()) { + // TODO: Submit through InventoryBehavior, instead of executing the strategy here + final InventorySlot slot = ts.getBestSlot(state, preferSilkTouch); + final InventoryBehavior.SelectionStrategy strategy = ((Baritone) ctx.baritone()).getInventoryBehavior().resolveSelectionStrategy(slot); + if (strategy != null) { + strategy.run(); + } } } diff --git a/src/main/java/baritone/utils/ToolSet.java b/src/main/java/baritone/utils/ToolSet.java index 95ace30cf..267d58af6 100644 --- a/src/main/java/baritone/utils/ToolSet.java +++ b/src/main/java/baritone/utils/ToolSet.java @@ -20,6 +20,7 @@ import baritone.Baritone; import baritone.PerformanceCritical; import baritone.api.utils.IPlayerContext; +import baritone.api.utils.InventorySlot; import baritone.utils.accessor.IItemTool; import it.unimi.dsi.fastutil.HashCommon; import it.unimi.dsi.fastutil.objects.Reference2DoubleOpenHashMap; @@ -27,11 +28,13 @@ import net.minecraft.block.state.IBlockState; import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.init.Enchantments; +import net.minecraft.init.Items; import net.minecraft.init.MobEffects; import net.minecraft.item.ItemStack; import net.minecraft.item.ItemSword; import net.minecraft.item.ItemTool; +import java.util.Comparator; import java.util.function.ToDoubleFunction; /** @@ -84,7 +87,9 @@ public double getStrVsBlock(IBlockState state) { * @return A double containing the destruction speed with the best tool */ private double getBestDestructionSpeed(IBlockState state) { - final ItemStack stack = ctx.player().inventory.getStackInSlot(this.getBestSlot(state, false, true)); + final ItemStack stack = isAutoTool() + ? ctx.inventory().itemAt(this.getBestSlot(state, false)) + : ctx.player().getHeldItemMainhand(); return calculateSpeedVsBlock(stack, state) * avoidanceMultiplier(state.getBlock()); } @@ -93,13 +98,12 @@ private double getBestDestructionSpeed(IBlockState state) { * harvest level order; there is a chance for multiple at the same with modded tools * but in that case we don't really care. * - * @param itemStack a possibly empty ItemStack + * @param stack a possibly empty ItemStack * @return The tool's harvest level, or {@code -1} if the stack isn't a tool */ - private static int getMaterialCost(ItemStack itemStack) { - if (itemStack.getItem() instanceof ItemTool) { - ItemTool tool = (ItemTool) itemStack.getItem(); - return ((IItemTool) tool).getHarvestLevel(); + private static int getMaterialCost(ItemStack stack) { + if (stack.getItem() instanceof IItemTool) { + return ((IItemTool) stack.getItem()).getHarvestLevel(); } else { return -1; } @@ -111,51 +115,36 @@ private static int getMaterialCost(ItemStack itemStack) { * * @param state the blockstate to be mined * @param preferSilkTouch whether to prefer silk touch tools - * @param pathingCalculation whether the call to this method is for pathing calculation * @return An int containing the index in the tools array that worked best */ - public int getBestSlot(IBlockState state, boolean preferSilkTouch, boolean pathingCalculation) { + public InventorySlot getBestSlot(IBlockState state, boolean preferSilkTouch) { + final Comparator compare = Comparator + // Prioritize mining speed over everything + .comparingDouble(stack -> calculateSpeedVsBlock(stack, state)) + // Prioritize silk touch tools, if preferSilkTouch is true, over reduced material cost + .thenComparing(ToolSet::hasSilkTouch, (a, b) -> preferSilkTouch ? Boolean.compare(a, b) : 0) + // Minimize material cost + .thenComparing(Comparator.comparingInt(ToolSet::getMaterialCost).reversed()); - /* - If we actually want know what efficiency our held item has instead of the best one - possible, this lets us make pathing depend on the actual tool to be used (if auto tool is disabled) - */ - if (!Baritone.settings().autoTool.value && pathingCalculation) { - return ctx.player().inventory.currentItem; - } - - int best = 0; - double highestSpeed = Double.NEGATIVE_INFINITY; - int lowestCost = Integer.MIN_VALUE; - boolean bestSilkTouch = false; - for (int i = 0; i < 9; i++) { - ItemStack itemStack = ctx.player().inventory.getStackInSlot(i); - if (!Baritone.settings().useSwordToMine.value && itemStack.getItem() instanceof ItemSword) { - continue; - } - - if (Baritone.settings().itemSaver.value && (itemStack.getItemDamage() + Baritone.settings().itemSaverThreshold.value) >= itemStack.getMaxDamage() && itemStack.getMaxDamage() > 1) { - continue; - } - double speed = calculateSpeedVsBlock(itemStack, state); - boolean silkTouch = hasSilkTouch(itemStack); - if (speed > highestSpeed) { - highestSpeed = speed; - best = i; - lowestCost = getMaterialCost(itemStack); - bestSilkTouch = silkTouch; - } else if (speed == highestSpeed) { - int cost = getMaterialCost(itemStack); - if ((cost < lowestCost && (silkTouch || !bestSilkTouch)) || - (preferSilkTouch && !bestSilkTouch && silkTouch)) { - highestSpeed = speed; - best = i; - lowestCost = cost; - bestSilkTouch = silkTouch; + return ((Baritone) ctx.baritone()).getInventoryBehavior().findBestAccessibleMatching( + compare, + stack -> { + if (!Baritone.settings().useSwordToMine.value && stack.getItem() instanceof ItemSword) { + return false; + } + if (Baritone.settings().itemSaver.value + && stack.getItemDamage() + Baritone.settings().itemSaverThreshold.value >= stack.getMaxDamage() + && stack.getMaxDamage() > 1 + ) { + return false; + } + return true; } - } - } - return best; + ); + } + + public static boolean isAutoTool() { + return Baritone.settings().autoTool.value && !Baritone.settings().assumeExternalAutoTool.value; } /** From dbf505806688e0a0d54dc2961ddc3dcf952f4568 Mon Sep 17 00:00:00 2001 From: Brady Date: Wed, 5 Jul 2023 21:58:04 -0700 Subject: [PATCH 20/21] Move `ItemInteractionHelper` into its own file --- src/api/java/baritone/api/Settings.java | 3 +- .../java/baritone/PerformanceCritical.java | 5 +- .../baritone/behavior/InventoryBehavior.java | 77 ++------------ .../baritone/utils/ItemInteractionHelper.java | 100 ++++++++++++++++++ src/main/java/baritone/utils/ToolSet.java | 2 - 5 files changed, 112 insertions(+), 75 deletions(-) create mode 100644 src/main/java/baritone/utils/ItemInteractionHelper.java diff --git a/src/api/java/baritone/api/Settings.java b/src/api/java/baritone/api/Settings.java index 4b614e106..ea37cae1b 100644 --- a/src/api/java/baritone/api/Settings.java +++ b/src/api/java/baritone/api/Settings.java @@ -75,7 +75,8 @@ public final class Settings { /** * Allow Baritone to automatically put useful items (such as tools and throwaway blocks) on the hotbar while - * pathing. Requires {@link #allowInventory}. + * pathing. This can reduce delays when retrieving items due settings like {@link #ticksBetweenInventoryMoves} and + * {@link #inventoryMoveOnlyIfStationary}. Requires {@link #allowInventory}. */ public final Setting allowHotbarManagement = new Setting<>(false); diff --git a/src/main/java/baritone/PerformanceCritical.java b/src/main/java/baritone/PerformanceCritical.java index 75b972d1d..1cfee823b 100644 --- a/src/main/java/baritone/PerformanceCritical.java +++ b/src/main/java/baritone/PerformanceCritical.java @@ -17,6 +17,8 @@ package baritone; +import baritone.pathing.movement.CalculationContext; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -24,7 +26,8 @@ /** * Annotation that should be used on methods which are performance critical (i.e. called millions of times per second - * by the pathfinder) and should be modified with care. + * by the pathfinder) and should be modified with care. Particularly useful for methods for which this fact is not + * obvious, such as those which don't have a {@link CalculationContext} parameter. * * @author Brady */ diff --git a/src/main/java/baritone/behavior/InventoryBehavior.java b/src/main/java/baritone/behavior/InventoryBehavior.java index c0a3abc92..0eabe84c3 100644 --- a/src/main/java/baritone/behavior/InventoryBehavior.java +++ b/src/main/java/baritone/behavior/InventoryBehavior.java @@ -22,22 +22,19 @@ import baritone.api.utils.Helper; import baritone.api.utils.InventorySlot; import baritone.api.utils.Pair; +import baritone.utils.ItemInteractionHelper; import baritone.utils.ToolSet; -import it.unimi.dsi.fastutil.objects.Reference2BooleanMap; -import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap; import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; -import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Blocks; import net.minecraft.inventory.ClickType; import net.minecraft.item.*; -import net.minecraft.util.*; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.World; +import net.minecraft.util.EnumFacing; -import javax.annotation.Nonnull; -import java.lang.reflect.Method; -import java.util.*; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.OptionalInt; +import java.util.Random; import java.util.function.IntPredicate; import java.util.function.Predicate; import java.util.stream.Stream; @@ -327,66 +324,4 @@ public SelectionType getType() { public enum SelectionType { IMMEDIATE, ENQUEUED } - - private static final class ItemInteractionHelper { - - private static final Reference2BooleanMap> CACHE = new Reference2BooleanOpenHashMap<>(); - - public static boolean couldInteract(final ItemStack stack) { - if (stack.isEmpty()) { - return false; - } - - return CACHE.computeIfAbsent(stack.getItem().getClass(), itemClass -> { - try { - final Method onItemUse = itemClass.getMethod(Helper1.name, Helper1.parameters); - final Method onItemRightClick = itemClass.getMethod(Helper2.name, Helper2.parameters); - - // If the declaring class isn't Item, then the method is overridden - return onItemUse.getDeclaringClass() != Item.class - || onItemRightClick.getDeclaringClass() != Item.class; - } catch (NoSuchMethodException ignored) { - // this shouldn't happen - return true; - } - }); - } - - private static final class Helper1 extends Item { - - public static final String name; - public static final Class[] parameters; - static { - final Method method = Helper1.class.getDeclaredMethods()[0]; - name = method.getName(); - parameters = method.getParameterTypes(); - } - - @Nonnull - @Override - public EnumActionResult onItemUse(@Nonnull EntityPlayer player, @Nonnull World worldIn, - @Nonnull BlockPos pos, @Nonnull EnumHand hand, - @Nonnull EnumFacing facing, float hitX, float hitY, float hitZ) { - return super.onItemUse(player, worldIn, pos, hand, facing, hitX, hitY, hitZ); - } - } - - private static final class Helper2 extends Item { - - public static final String name; - public static final Class[] parameters; - static { - final Method method = Helper2.class.getDeclaredMethods()[0]; - name = method.getName(); - parameters = method.getParameterTypes(); - } - - @Nonnull - @Override - public ActionResult onItemRightClick(@Nonnull World worldIn, @Nonnull EntityPlayer playerIn, - @Nonnull EnumHand handIn) { - return super.onItemRightClick(worldIn, playerIn, handIn); - } - } - } } diff --git a/src/main/java/baritone/utils/ItemInteractionHelper.java b/src/main/java/baritone/utils/ItemInteractionHelper.java new file mode 100644 index 000000000..301ef6d85 --- /dev/null +++ b/src/main/java/baritone/utils/ItemInteractionHelper.java @@ -0,0 +1,100 @@ +/* + * This file is part of Baritone. + * + * Baritone is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Baritone is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Baritone. If not, see . + */ + +package baritone.utils; + +import it.unimi.dsi.fastutil.objects.Reference2BooleanMap; +import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ActionResult; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import java.lang.reflect.Method; + +/** + * @author Brady + */ +public final class ItemInteractionHelper { + + private ItemInteractionHelper() {} + + private static final Reference2BooleanMap> CACHE = new Reference2BooleanOpenHashMap<>(); + + public static boolean couldInteract(final ItemStack stack) { + if (stack.isEmpty()) { + return false; + } + + return CACHE.computeIfAbsent(stack.getItem().getClass(), itemClass -> { + try { + final Method onItemUse = itemClass.getMethod(Helper1.name, Helper1.parameters); + final Method onItemRightClick = itemClass.getMethod(Helper2.name, Helper2.parameters); + + // If the declaring class isn't Item, then the method is overridden + return onItemUse.getDeclaringClass() != Item.class + || onItemRightClick.getDeclaringClass() != Item.class; + } catch (NoSuchMethodException ignored) { + // this shouldn't happen + return true; + } + }); + } + + private static final class Helper1 extends Item { + + public static final String name; + public static final Class[] parameters; + static { + final Method method = Helper1.class.getDeclaredMethods()[0]; + name = method.getName(); + parameters = method.getParameterTypes(); + } + + @Nonnull + @Override + public EnumActionResult onItemUse(@Nonnull EntityPlayer player, @Nonnull World worldIn, + @Nonnull BlockPos pos, @Nonnull EnumHand hand, + @Nonnull EnumFacing facing, float hitX, float hitY, float hitZ) { + return super.onItemUse(player, worldIn, pos, hand, facing, hitX, hitY, hitZ); + } + } + + private static final class Helper2 extends Item { + + public static final String name; + public static final Class[] parameters; + static { + final Method method = Helper2.class.getDeclaredMethods()[0]; + name = method.getName(); + parameters = method.getParameterTypes(); + } + + @Nonnull + @Override + public ActionResult onItemRightClick(@Nonnull World worldIn, @Nonnull EntityPlayer playerIn, + @Nonnull EnumHand handIn) { + return super.onItemRightClick(worldIn, playerIn, handIn); + } + } +} diff --git a/src/main/java/baritone/utils/ToolSet.java b/src/main/java/baritone/utils/ToolSet.java index 267d58af6..cb2796d4a 100644 --- a/src/main/java/baritone/utils/ToolSet.java +++ b/src/main/java/baritone/utils/ToolSet.java @@ -28,11 +28,9 @@ import net.minecraft.block.state.IBlockState; import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.init.Enchantments; -import net.minecraft.init.Items; import net.minecraft.init.MobEffects; import net.minecraft.item.ItemStack; import net.minecraft.item.ItemSword; -import net.minecraft.item.ItemTool; import java.util.Comparator; import java.util.function.ToDoubleFunction; From 6a6d0642be0dccfb3106c58f7c107fe4a5064048 Mon Sep 17 00:00:00 2001 From: Brady Date: Thu, 6 Jul 2023 18:40:34 -0700 Subject: [PATCH 21/21] Utilize ToolSet for hotbar management's `bestToolAgainst` --- .../baritone/behavior/InventoryBehavior.java | 23 ++++--------------- .../pathing/movement/MovementHelper.java | 13 +---------- src/main/java/baritone/utils/ToolSet.java | 9 +++++--- 3 files changed, 11 insertions(+), 34 deletions(-) diff --git a/src/main/java/baritone/behavior/InventoryBehavior.java b/src/main/java/baritone/behavior/InventoryBehavior.java index 0eabe84c3..5643db97b 100644 --- a/src/main/java/baritone/behavior/InventoryBehavior.java +++ b/src/main/java/baritone/behavior/InventoryBehavior.java @@ -90,6 +90,10 @@ private void setupHotbar() { } } + private InventorySlot bestToolAgainst(final Block against, final Class cla$$) { + return new ToolSet(ctx).getBestSlot(against.getDefaultState(), Baritone.settings().preferSilkTouch.value, stack -> cla$$.isInstance(stack.getItem())); + } + public boolean attemptToPutOnHotbar(int inMainInvy, IntPredicate disallowedHotbar) { OptionalInt destination = getTempHotbarSlot(disallowedHotbar); if (destination.isPresent()) { @@ -137,25 +141,6 @@ private boolean requestSwapWithHotBar(int inInventory, int inHotbar) { return true; } - private InventorySlot bestToolAgainst(final Block against, final Class cla$$) { - // TODO: Replace with ToolSet.getBestSlot - return this.findBestSlotMatching( - Comparator.comparingDouble(stack -> ToolSet.calculateSpeedVsBlock(stack, against.getDefaultState())), - stack -> { - if (stack.isEmpty()) { - return false; - } - if (Baritone.settings().itemSaver.value - && stack.getItemDamage() + Baritone.settings().itemSaverThreshold.value >= stack.getMaxDamage() - && stack.getMaxDamage() > 1 - ) { - return false; - } - return cla$$.isInstance(stack.getItem()); - } - ); - } - public boolean hasGenericThrowaway() { return this.canSelectItem(this::isThrowawayItem); } diff --git a/src/main/java/baritone/pathing/movement/MovementHelper.java b/src/main/java/baritone/pathing/movement/MovementHelper.java index 63abf3a59..f9e49f657 100644 --- a/src/main/java/baritone/pathing/movement/MovementHelper.java +++ b/src/main/java/baritone/pathing/movement/MovementHelper.java @@ -581,20 +581,9 @@ static boolean isBottomSlab(IBlockState state) { * @param state The blockstate to mine */ static void switchToBestToolFor(IPlayerContext ctx, IBlockState state) { - switchToBestToolFor(ctx, state, new ToolSet(ctx), Baritone.settings().preferSilkTouch.value); - } - - /** - * AutoTool for a specific block with precomputed ToolSet data - * - * @param ctx The player context - * @param state The blockstate to mine - * @param ts Previously calculated ToolSet - */ - static void switchToBestToolFor(IPlayerContext ctx, IBlockState state, ToolSet ts, boolean preferSilkTouch) { if (ToolSet.isAutoTool()) { // TODO: Submit through InventoryBehavior, instead of executing the strategy here - final InventorySlot slot = ts.getBestSlot(state, preferSilkTouch); + final InventorySlot slot = new ToolSet(ctx).getBestSlot(state, Baritone.settings().preferSilkTouch.value, null); final InventoryBehavior.SelectionStrategy strategy = ((Baritone) ctx.baritone()).getInventoryBehavior().resolveSelectionStrategy(slot); if (strategy != null) { strategy.run(); diff --git a/src/main/java/baritone/utils/ToolSet.java b/src/main/java/baritone/utils/ToolSet.java index cb2796d4a..128c0a4db 100644 --- a/src/main/java/baritone/utils/ToolSet.java +++ b/src/main/java/baritone/utils/ToolSet.java @@ -33,6 +33,7 @@ import net.minecraft.item.ItemSword; import java.util.Comparator; +import java.util.function.Predicate; import java.util.function.ToDoubleFunction; /** @@ -86,7 +87,7 @@ public double getStrVsBlock(IBlockState state) { */ private double getBestDestructionSpeed(IBlockState state) { final ItemStack stack = isAutoTool() - ? ctx.inventory().itemAt(this.getBestSlot(state, false)) + ? ctx.inventory().itemAt(this.getBestSlot(state, false, null)) : ctx.player().getHeldItemMainhand(); return calculateSpeedVsBlock(stack, state) * avoidanceMultiplier(state.getBlock()); } @@ -113,9 +114,11 @@ private static int getMaterialCost(ItemStack stack) { * * @param state the blockstate to be mined * @param preferSilkTouch whether to prefer silk touch tools + * @param extra An additional filter to apply on top of the default, setting-based ones, may be {@code null} * @return An int containing the index in the tools array that worked best */ - public InventorySlot getBestSlot(IBlockState state, boolean preferSilkTouch) { + public InventorySlot getBestSlot(final IBlockState state, final boolean preferSilkTouch, + final Predicate extra) { final Comparator compare = Comparator // Prioritize mining speed over everything .comparingDouble(stack -> calculateSpeedVsBlock(stack, state)) @@ -136,7 +139,7 @@ public InventorySlot getBestSlot(IBlockState state, boolean preferSilkTouch) { ) { return false; } - return true; + return extra == null || extra.test(stack); } ); }