Compare commits

...

21 Commits

Author SHA1 Message Date
Brady
6a6d0642be Utilize ToolSet for hotbar management's bestToolAgainst 2023-07-06 18:40:34 -07:00
Brady
dbf5058066 Move ItemInteractionHelper into its own file 2023-07-05 21:58:04 -07:00
Brady
bc18f0eabd Allow autoTool to pull tools from the inventory 2023-07-05 21:44:31 -07:00
Brady
827ce7e2a8 Modify ToolSet to accept an IPlayerContext 2023-07-05 19:39:08 -07:00
Brady
2e32c63bc2 Calculate break time based on block state 2023-07-05 18:52:53 -07:00
Brady
53651980ad Mitigate #3945 2023-07-05 11:45:04 -07:00
Brady
ec089cdeaa aha whoopsie 2023-07-05 11:24:34 -07:00
Brady
2408c9b6af Backport primitive computeIfAbsent 2023-07-05 11:14:55 -07:00
Brady
600d5e8a67 More IBaritoneInventory api methods 2023-07-05 10:13:38 -07:00
Brady
57c3e369b0 💚 appease codacy 2023-07-04 21:44:10 -07:00
Brady
827125d827 Inventory API thing and substitution of legacy code
Also stole `Pair` from elytra branch 😎
2023-07-04 21:34:57 -07:00
Brady
4d7eb003e6 Item interaction detection 2023-07-03 18:36:05 -05:00
Brady
1ef622c936 I LOVE ObjIntConsumer 2023-07-02 19:54:11 -05:00
Brady
c44701e689 Changes 2023-07-02 19:46:48 -05:00
Brady
7f27c9d85f Reorganize 2023-07-01 22:19:51 -05:00
Brady
15621ea763 Add allowHotbarManagement setting 2023-07-01 20:08:29 -05:00
Brady
4591fd7eb3 Use Reference2DoubleOpenHashMap in ToolSet 2023-07-01 17:16:51 -05:00
Brady
028405dd30 Only allow @PerformanceCritical on methods 2023-07-01 17:00:04 -05:00
Brady
2953b1cfaf Replace acceptableThrowawayItems references with isThrowawayItem method 2023-07-01 16:41:26 -05:00
Brady
b2cdd27ca2 Remove direct reference to allowInventory in BuilderProcess 2023-07-01 15:38:23 -05:00
Brady
4bf2dd6851 Some initial organization 2023-07-01 15:16:26 -05:00
15 changed files with 828 additions and 230 deletions

View File

@@ -73,6 +73,13 @@ public final class Settings {
*/
public final Setting<Boolean> 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<Boolean> allowHotbarManagement = new Setting<>(false);
/**
* Wait this many ticks between InventoryBehavior moving inventory items
*/

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Pair<InventorySlot, ItemStack>> allSlots();
Stream<Pair<InventorySlot, ItemStack>> hotbarSlots();
Stream<Pair<InventorySlot, ItemStack>> inventorySlots();
Pair<InventorySlot, ItemStack> offhand();
Stream<Pair<InventorySlot, ItemStack>> armorSlots();
ItemStack itemAt(InventorySlot slot);
}

View File

@@ -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 @@ import java.util.Optional;
*/
public interface IPlayerContext {
IBaritone baritone();
Minecraft minecraft();
EntityPlayerSP player();
IPlayerController playerController();
IBaritoneInventory inventory();
World world();
IWorldData worldData();

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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.
* <table width="100%">
* <tr>
* <th width="10%">Index</th>
* <th>Description</th>
* </tr>
* <tr><td>0</td><td>crafting output</td></tr>
* <tr><td>1-4</td><td>crafting grid</td></tr>
* <tr><td>5-8</td><td>armor</td></tr>
* <tr><td>9-35</td><td>inventory (index 9-35)</td></tr>
* <tr><td>36-44</td><td>hotbar (index 0-8)</td></tr>
* <tr><td>45</td><td>off-hand</td></tr>
* </table>
*/
private static final InventorySlot[] SLOTS = new InventorySlot[46];
static {
final ObjIntConsumer<Type> populate = new ObjIntConsumer<Type>() {
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
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
package baritone.api.utils;
import java.util.Objects;
/**
* @author Brady
*/
public final class Pair<A, B> {
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);
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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 {}

View File

@@ -20,25 +20,29 @@ package baritone.behavior;
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 final class InventoryBehavior extends Behavior implements Helper {
@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<Integer> 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<? extends ItemTool> 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 final class InventoryBehavior extends Behavior implements Helper {
return true;
}
public OptionalInt getTempHotbarSlot(Predicate<Integer> disallowedHotbar) {
public OptionalInt getTempHotbarSlot(IntPredicate disallowedHotbar) {
// we're using 0 and 8 for pickaxe and throwaway
ArrayList<Integer> candidates = new ArrayList<>();
for (int i = 1; i < 8; i++) {
@@ -117,114 +141,172 @@ public final class InventoryBehavior extends Behavior implements Helper {
return true;
}
private int firstValidThrowaway() { // TODO offhand idk
NonNullList<ItemStack> 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;
}
private int bestToolAgainst(Block against, Class<? extends ItemTool> cla$$) {
NonNullList<ItemStack> 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;
}
}
}
return bestInd;
}
public boolean hasGenericThrowaway() {
for (Item item : Baritone.settings().acceptableThrowawayItems.value) {
if (throwaway(false, 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) {
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
final Predicate<Predicate<? super ItemStack>> 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());
});
}
if (maybe != null && throwaway(select, stack -> stack.getItem() instanceof ItemBlock && ((ItemBlock) stack.getItem()).getBlock().equals(maybe.getBlock()))) {
return op.test(this::isThrowawayItem);
}
public boolean canSelectItem(Predicate<? super ItemStack> desired) {
return this.resolveSelectionStrategy(this.findSlotMatching(desired)) != null;
}
public boolean trySelectItem(Predicate<? super ItemStack> 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<? super ItemStack> 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();
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()) {
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;
}
public boolean throwaway(boolean select, Predicate<? super ItemStack> desired, boolean allowInventory) {
EntityPlayerSP p = ctx.player();
NonNullList<ItemStack> 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;
}
return true;
}
}
}
public InventorySlot findBestAccessibleMatching(final Comparator<? super ItemStack> comparator,
final Predicate<? super ItemStack> filter) {
final Stream<Pair<InventorySlot, ItemStack>> accessible = this.canAccessInventory()
? ctx.inventory().allSlots()
: Stream.concat(ctx.inventory().hotbarSlots(), Stream.of(ctx.inventory().offhand()));
return this.findBestMatching0(accessible, comparator, filter);
}
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 findSlotMatching(final Predicate<? super ItemStack> filter) {
return this.findBestSlotMatching(null, filter);
}
return false;
/**
* 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<? super ItemStack> comparator, final Predicate<? super ItemStack> filter) {
return this.findBestMatching0(ctx.inventory().allSlots(), comparator, filter);
}
public InventorySlot findHotbarMatching(final Predicate<? super ItemStack> filter) {
return this.findBestHotbarMatching(null, filter);
}
public InventorySlot findBestHotbarMatching(final Comparator<? super ItemStack> comparator, final Predicate<? super ItemStack> filter) {
return this.findBestMatching0(ctx.inventory().hotbarSlots(), comparator, filter);
}
private InventorySlot findBestMatching0(final Stream<Pair<InventorySlot, ItemStack>> slots,
final Comparator<? super ItemStack> comparator,
final Predicate<? super ItemStack> filter) {
final Stream<Pair<InventorySlot, ItemStack>> 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;
}
};
}
}
public enum SelectionType {
IMMEDIATE, ENQUEUED
}
}

View File

@@ -93,7 +93,7 @@ public class CalculationContext {
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;

View File

@@ -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 @@ public interface MovementHelper extends ActionCosts, Helper {
/**
* 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);
}
/**
* 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
*/
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();
}
}
}

View File

@@ -542,7 +542,7 @@ public final class BuilderProcess extends BaritoneProcessHelper implements IBuil
return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL);
}
if (Baritone.settings().allowInventory.value) {
if (baritone.getInventoryBehavior().canAccessInventory()) {
ArrayList<Integer> usefulSlots = new ArrayList<>();
List<IBlockState> noValidHotbarOption = new ArrayList<>();
outer:

View File

@@ -269,7 +269,7 @@ public final class FarmProcess extends BaritoneProcessHelper implements IFarmPro
for (BlockPos pos : both) {
boolean soulsand = openSoulsand.contains(pos);
Optional<Rotation> 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 final class FarmProcess extends BaritoneProcessHelper implements IFarmPro
}
Vec3d faceCenter = new Vec3d(pos).add(0.5, 0.5, 0.5).add(new Vec3d(dir.getDirectionVec()).scale(0.5));
Optional<Rotation> 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 final class FarmProcess extends BaritoneProcessHelper implements IFarmPro
}
for (BlockPos pos : bonemealable) {
Optional<Rotation> 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 final class FarmProcess extends BaritoneProcessHelper implements IFarmPro
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 final class FarmProcess extends BaritoneProcessHelper implements IFarmPro
}
}
}
if (baritone.getInventoryBehavior().throwaway(false, this::isBoneMeal)) {
if (baritone.getInventoryBehavior().canSelectItem(this::isBoneMeal)) {
for (BlockPos pos : bonemealable) {
goalz.add(new GoalBlock(pos));
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Class<? extends Item>> 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<ItemStack> onItemRightClick(@Nonnull World worldIn, @Nonnull EntityPlayer playerIn,
@Nonnull EnumHand handIn) {
return super.onItemRightClick(worldIn, playerIn, handIn);
}
}
}

View File

@@ -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<Block, Double> breakStrengthCache;
private final Cache breakStrengthCache;
/**
* My buddy leijurv owned me so we have this to not create a new lambda instance.
*/
private final Function<Block, Double> backendCalculation;
private final ToDoubleFunction<IBlockState> 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<Double, Double> 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 class ToolSet {
* 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<? super ItemStack> extra) {
final Comparator<ItemStack> compare = Comparator
// Prioritize mining speed over everything
.<ItemStack>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 @@ public class ToolSet {
*
* @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 class ToolSet {
}
}
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 class ToolSet {
*/
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 @@ public class ToolSet {
}
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<IBlockState> {
public double computeIfAbsent(final IBlockState key, final ToDoubleFunction<IBlockState> 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));
}
}
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Pair<InventorySlot, ItemStack>> allSlots() {
return Stream.concat(this.hotbarSlots(), Stream.concat(Stream.of(this.offhand()), this.inventorySlots()));
}
@Override
public Stream<Pair<InventorySlot, ItemStack>> hotbarSlots() {
return IntStream.range(0, 9).mapToObj(InventorySlot::inventory).map(this::itemSlotPairAt);
}
@Override
public Stream<Pair<InventorySlot, ItemStack>> inventorySlots() {
return IntStream.range(9, 36).mapToObj(InventorySlot::inventory).map(this::itemSlotPairAt);
}
@Override
public Pair<InventorySlot, ItemStack> offhand() {
return new Pair<>(InventorySlot.offhand(), ctx.player().inventory.offHandInventory.get(0));
}
@Override
public Stream<Pair<InventorySlot, ItemStack>> 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<InventorySlot, ItemStack> itemSlotPairAt(InventorySlot slot) {
return new Pair<>(slot, this.itemAt(slot));
}
}

View File

@@ -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 final class BaritonePlayerContext implements IPlayerContext {
return this.playerController;
}
@Override
public IBaritoneInventory inventory() {
return this.player() == null ? null : this.inventory;
}
@Override
public World world() {
return this.mc.world;