diff --git a/src/api/java/baritone/api/Settings.java b/src/api/java/baritone/api/Settings.java index a8e4ced7d..ea37cae1b 100644 --- a/src/api/java/baritone/api/Settings.java +++ b/src/api/java/baritone/api/Settings.java @@ -73,6 +73,13 @@ 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. 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); + /** * Wait this many ticks between InventoryBehavior moving inventory items */ 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..b5790ecf8 --- /dev/null +++ b/src/api/java/baritone/api/utils/IBaritoneInventory.java @@ -0,0 +1,47 @@ +/* + * 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. 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 + */ + Stream> allSlots(); + + Stream> hotbarSlots(); + + Stream> inventorySlots(); + + Pair offhand(); + + Stream> armorSlots(); + + ItemStack itemAt(InventorySlot slot); +} diff --git a/src/api/java/baritone/api/utils/IPlayerContext.java b/src/api/java/baritone/api/utils/IPlayerContext.java index 14ca69fb9..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,12 +35,16 @@ */ public interface IPlayerContext { + IBaritone baritone(); + Minecraft minecraft(); EntityPlayerSP player(); IPlayerController playerController(); + IBaritoneInventory inventory(); + World world(); IWorldData worldData(); diff --git a/src/api/java/baritone/api/utils/InventorySlot.java b/src/api/java/baritone/api/utils/InventorySlot.java new file mode 100644 index 000000000..441563155 --- /dev/null +++ b/src/api/java/baritone/api/utils/InventorySlot.java @@ -0,0 +1,129 @@ +/* + * 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.function.ObjIntConsumer; + +/** + * @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 { + 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); + 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; + 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; + } + + /** + * 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: + return this.slotId - 36; + case INVENTORY: + return this.slotId; + default: + throw new IllegalStateException("Slot type must be either HOTBAR or INVENTORY"); + } + } + + 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 + } + throw new IllegalArgumentException(); + } + + public static InventorySlot armor(final int index) { + if (index < 0 || index >= 4) { + throw new IllegalArgumentException(); + } + return SLOTS[index + 5]; + } + + public static InventorySlot offhand() { + return SLOTS[45]; + } + + public enum Type { + CRAFTING_OUTPUT, + CRAFTING_GRID, + ARMOR, + INVENTORY, + HOTBAR, + OFFHAND + } +} 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/PerformanceCritical.java b/src/main/java/baritone/PerformanceCritical.java new file mode 100644 index 000000000..1cfee823b --- /dev/null +++ b/src/main/java/baritone/PerformanceCritical.java @@ -0,0 +1,36 @@ +/* + * 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 baritone.pathing.movement.CalculationContext; + +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. Particularly useful for methods for which this fact is not + * obvious, such as those which don't have a {@link CalculationContext} parameter. + * + * @author Brady + */ +@Target(ElementType.METHOD) +@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..5643db97b 100644 --- a/src/main/java/baritone/behavior/InventoryBehavior.java +++ b/src/main/java/baritone/behavior/InventoryBehavior.java @@ -20,25 +20,29 @@ import baritone.Baritone; import baritone.api.event.events.TickEvent; import baritone.api.utils.Helper; +import baritone.api.utils.InventorySlot; +import baritone.api.utils.Pair; +import baritone.utils.ItemInteractionHelper; import baritone.utils.ToolSet; import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; -import net.minecraft.client.entity.EntityPlayerSP; import net.minecraft.init.Blocks; import net.minecraft.inventory.ClickType; import net.minecraft.item.*; import net.minecraft.util.EnumFacing; -import net.minecraft.util.NonNullList; 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; 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); @@ -46,31 +50,51 @@ public InventoryBehavior(Baritone baritone) { @Override public void onTick(TickEvent event) { - if (!Baritone.settings().allowInventory.value) { + 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()) { + this.setupHotbar(); } + if (lastTickRequestedMove != null) { logDebug("Remembering to move " + lastTickRequestedMove[0] + " " + lastTickRequestedMove[1] + " from a previous tick"); requestSwapWithHotBar(lastTickRequestedMove[0], lastTickRequestedMove[1]); } } - public boolean attemptToPutOnHotbar(int inMainInvy, Predicate disallowedHotbar) { + 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 = this.bestToolAgainst(Blocks.STONE, ItemPickaxe.class); + if (pick != null && pick.getType() == InventorySlot.Type.INVENTORY) { + this.requestSwapWithHotBar(pick.getInventoryIndex(), 0); + } + } + + 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()) { if (!requestSwapWithHotBar(inMainInvy, destination.getAsInt())) { @@ -80,7 +104,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++) { @@ -117,114 +141,172 @@ 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 (Baritone.settings().acceptableThrowawayItems.value.contains(invy.get(i).getItem())) { - return i; - } - } - return -1; + public boolean hasGenericThrowaway() { + return this.canSelectItem(this::isThrowawayItem); } - 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; + public boolean selectThrowawayForLocation(boolean select, int x, int y, int 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 op.test(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() + )); + }) || 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 bestInd; + return op.test(this::isThrowawayItem); } - public boolean hasGenericThrowaway() { - for (Item item : Baritone.settings().acceptableThrowawayItems.value) { - if (throwaway(false, stack -> item.equals(stack.getItem()))) { - return true; - } - } - return false; + public boolean canSelectItem(Predicate desired) { + return this.resolveSelectionStrategy(this.findSlotMatching(desired)) != null; } - 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()))) { + public boolean trySelectItem(Predicate 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 return true; } - for (Item item : Baritone.settings().acceptableThrowawayItems.value) { - if (throwaway(select, stack -> item.equals(stack.getItem()))) { - return true; - } - } return false; } - public boolean throwaway(boolean select, Predicate desired) { - return throwaway(select, desired, Baritone.settings().allowInventory.value); - } + public SelectionStrategy resolveSelectionStrategy(final InventorySlot slot) { + if (slot != null) { + switch (slot.getType()) { + case HOTBAR: + return SelectionStrategy.of(SelectionType.IMMEDIATE, () -> + ctx.player().inventory.currentItem = slot.getInventoryIndex()); + case OFFHAND: + // 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(); - 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++) { - 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; - } - } - 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; + 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, () -> {}); } - return true; - } + + 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()) { + 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; + }); + } + break; + default: + break; } } + return null; + } - if (allowInventory) { - for (int i = 9; i < 36; i++) { - if (desired.test(inv.get(i))) { - if (select) { - requestSwapWithHotBar(i, 7); - p.inventory.currentItem = 7; - } - return true; + 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); + } + + /** + * 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 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 findBestSlotMatching(final Comparator comparator, final Predicate filter) { + return this.findBestMatching0(ctx.inventory().allSlots(), comparator, filter); + } + + public InventorySlot findHotbarMatching(final Predicate filter) { + return this.findBestHotbarMatching(null, filter); + } + + public InventorySlot findBestHotbarMatching(final Comparator comparator, final Predicate filter) { + return this.findBestMatching0(ctx.inventory().hotbarSlots(), comparator, filter); + } + + 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() { + 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); + } + + 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; + } + }; } + } - return false; + public enum SelectionType { + IMMEDIATE, ENQUEUED } } 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 881bb6f15..f9e49f657 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; @@ -577,23 +577,17 @@ static boolean isBottomSlab(IBlockState state) { /** * AutoTool for a specific block * - * @param ctx The player context - * @param b the blockstate to mine - */ - static void switchToBestToolFor(IPlayerContext ctx, IBlockState b) { - switchToBestToolFor(ctx, b, new ToolSet(ctx.player()), BaritoneAPI.getSettings().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 */ - static void switchToBestToolFor(IPlayerContext ctx, IBlockState b, ToolSet ts, boolean preferSilkTouch) { - if (Baritone.settings().autoTool.value && !Baritone.settings().assumeExternalAutoTool.value) { - ctx.player().inventory.currentItem = ts.getBestSlot(b.getBlock(), preferSilkTouch); + static void switchToBestToolFor(IPlayerContext ctx, IBlockState state) { + if (ToolSet.isAutoTool()) { + // TODO: Submit through InventoryBehavior, instead of executing the strategy here + 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/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: 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/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 61f11e563..128c0a4db 100644 --- a/src/main/java/baritone/utils/ToolSet.java +++ b/src/main/java/baritone/utils/ToolSet.java @@ -18,62 +18,78 @@ package baritone.utils; 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; 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; import net.minecraft.item.ItemStack; import net.minecraft.item.ItemSword; -import net.minecraft.item.ItemTool; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Function; +import java.util.Comparator; +import java.util.function.Predicate; +import java.util.function.ToDoubleFunction; /** * A cached list of the best tools on the hotbar for any block * * @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 * with this toolset, given the optimum tool is used. */ - private final Map 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; + private final IPlayerContext ctx; - public ToolSet(EntityPlayerSP player) { - breakStrengthCache = new HashMap<>(); - this.player = player; + public ToolSet(IPlayerContext ctx) { + this.ctx = ctx; + this.breakStrengthCache = new Cache(); if (Baritone.settings().considerPotionEffects.value) { - double amplifier = potionAmplifier(); - Function amplify = x -> amplifier * x; - backendCalculation = amplify.compose(this::getBestDestructionTime); + double amplifier = this.potionAmplifier(); + this.backendCalculation = block -> amplifier * this.getBestDestructionSpeed(block); } else { - backendCalculation = this::getBestDestructionTime; + this.backendCalculation = this::getBestDestructionSpeed; } } /** * 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 breakStrengthCache.computeIfAbsent(state.getBlock(), 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 speed with the best tool + */ + private double getBestDestructionSpeed(IBlockState state) { + final ItemStack stack = isAutoTool() + ? ctx.inventory().itemAt(this.getBestSlot(state, false, null)) + : ctx.player().getHeldItemMainhand(); + return calculateSpeedVsBlock(stack, state) * avoidanceMultiplier(state.getBlock()); } /** @@ -81,92 +97,55 @@ public double getStrVsBlock(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 - * @return values from 0 up + * @param stack 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) { - 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; } } - 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 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(final IBlockState state, final boolean preferSilkTouch, + final Predicate extra) { + 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()); - public int getBestSlot(Block b, boolean preferSilkTouch) { - return getBestSlot(b, preferSilkTouch, false); - } - - public int getBestSlot(Block b, boolean preferSilkTouch, boolean pathingCalculation) { - - /* - 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 player.inventory.currentItem; - } - - int best = 0; - 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) { - continue; - } - - if (Baritone.settings().itemSaver.value && (itemStack.getItemDamage() + Baritone.settings().itemSaverThreshold.value) >= itemStack.getMaxDamage() && itemStack.getMaxDamage() > 1) { - continue; - } - double speed = calculateSpeedVsBlock(itemStack, blockState); - 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 extra == null || extra.test(stack); } - } - } - 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; + public static boolean isAutoTool() { + return Baritone.settings().autoTool.value && !Baritone.settings().assumeExternalAutoTool.value; } /** @@ -175,10 +154,19 @@ private double avoidanceMultiplier(Block b) { * * @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 = 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; } @@ -199,6 +187,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. * @@ -206,11 +203,11 @@ public static double calculateSpeedVsBlock(ItemStack item, IBlockState state) { */ 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; @@ -227,4 +224,60 @@ 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 IBlockState 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 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 = (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, IBlockState k, double v) { + if (pos == this.n) { + this.containsNullKey = true; + } + 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)); + } + } + } } 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..b52bea7d9 --- /dev/null +++ b/src/main/java/baritone/utils/player/BaritoneInventory.java @@ -0,0 +1,73 @@ +/* + * 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 + */ +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(InventorySlot::inventory).map(this::itemSlotPairAt); + } + + @Override + public Stream> inventorySlots() { + 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)); + } +} diff --git a/src/main/java/baritone/utils/player/BaritonePlayerContext.java b/src/main/java/baritone/utils/player/BaritonePlayerContext.java index 282d3d8b6..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; @@ -38,11 +39,18 @@ 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 + public IBaritone baritone() { + return this.baritone; } @Override @@ -60,6 +68,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;