diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/world/AutoSmelter.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/world/AutoSmelter.java index 80c600eeeb..3d256e5c05 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/modules/world/AutoSmelter.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/world/AutoSmelter.java @@ -5,11 +5,7 @@ package meteordevelopment.meteorclient.systems.modules.world; -import meteordevelopment.meteorclient.mixininterface.IAbstractFurnaceScreenHandler; -import meteordevelopment.meteorclient.settings.BoolSetting; -import meteordevelopment.meteorclient.settings.ItemListSetting; -import meteordevelopment.meteorclient.settings.Setting; -import meteordevelopment.meteorclient.settings.SettingGroup; +import meteordevelopment.meteorclient.settings.*; import meteordevelopment.meteorclient.systems.modules.Categories; import meteordevelopment.meteorclient.systems.modules.Module; import meteordevelopment.meteorclient.utils.Utils; @@ -21,15 +17,21 @@ import net.minecraft.screen.AbstractFurnaceScreenHandler; import java.util.List; +import java.util.function.Predicate; public class AutoSmelter extends Module { private final SettingGroup sgGeneral = settings.getDefaultGroup(); + // General + private final Setting> fuelItems = sgGeneral.add(new ItemListSetting.Builder() .name("fuel-items") .description("Items to use as fuel") - .defaultValue(Items.COAL, Items.CHARCOAL) - .filter(this::fuelItemFilter) + .defaultValue( + Items.COAL, + Items.CHARCOAL + ) + .filter(this::isFuelItem) .bypassFilterWhenSavingAndLoading() .build() ); @@ -37,8 +39,15 @@ public class AutoSmelter extends Module { private final Setting> smeltableItems = sgGeneral.add(new ItemListSetting.Builder() .name("smeltable-items") .description("Items to smelt") - .defaultValue(Items.IRON_ORE, Items.GOLD_ORE, Items.COPPER_ORE, Items.RAW_IRON, Items.RAW_COPPER, Items.RAW_GOLD) - .filter(this::smeltableItemFilter) + .defaultValue( + Items.IRON_ORE, + Items.GOLD_ORE, + Items.COPPER_ORE, + Items.RAW_IRON, + Items.RAW_COPPER, + Items.RAW_GOLD + ) + .filter(this::isSmeltableItem) .bypassFilterWhenSavingAndLoading() .build() ); @@ -50,18 +59,50 @@ public class AutoSmelter extends Module { .build() ); + private final Setting smartFuelUsage = sgGeneral.add(new BoolSetting.Builder() + .name("smart-fuel-usage") + .description("Only uses the amount of fuel needed to smelt available items") + .defaultValue(false) + .build() + ); + + private final Setting fuelAmount = sgGeneral.add(new IntSetting.Builder() + .name("fuel-amount") + .description("Amount of fuel to insert in each furnace (1-64).") + .defaultValue(64) + .range(1, 64) + .sliderMax(64) + .visible(() -> !smartFuelUsage.get()) + .build() + ); + public AutoSmelter() { super(Categories.World, "auto-smelter", "Automatically smelts items from your inventory"); } - private boolean fuelItemFilter(Item item) { - if (!Utils.canUpdate()) return false; + /** + * Returns true if the item is valid fuel. + */ + private boolean isFuelItem(Item item) { + return Utils.canUpdate() && mc.getNetworkHandler().getFuelRegistry().getFuelItems().contains(item); + } - return mc.getNetworkHandler().getFuelRegistry().getFuelItems().contains(item); + /** + * Returns true if the item can be smelted. + */ + private boolean isSmeltableItem(Item item) { + return mc.world != null && mc.world.getRecipeManager() + .getPropertySet(RecipePropertySet.FURNACE_INPUT) + .canUse(item.getDefaultStack()); } - private boolean smeltableItemFilter(Item item) { - return mc.world != null && mc.world.getRecipeManager().getPropertySet(RecipePropertySet.FURNACE_INPUT).canUse(item.getDefaultStack()); + /** + * Returns how many items can be smelted per fuel item. + */ + private double getFuelEfficiency(Item item) { + int burnTime = mc.getNetworkHandler().getFuelRegistry().getFuelTicks(new ItemStack(item)); + // Each item smelts in 200 ticks + return burnTime > 0 ? burnTime / 200.0 : 0.0; } public void tick(AbstractFurnaceScreenHandler c) { @@ -78,22 +119,17 @@ public void tick(AbstractFurnaceScreenHandler c) { insertItems(c); } + /** + * Checks and refills the fuel slot in the furnace. + * Will add fuel up to the specified fuel amount setting. + * + * @param c The furnace screen handler + */ private void insertItems(AbstractFurnaceScreenHandler c) { ItemStack inputItemStack = c.slots.getFirst().getStack(); if (!inputItemStack.isEmpty()) return; - int slot = -1; - - for (int i = 3; i < c.slots.size(); i++) { - ItemStack item = c.slots.get(i).getStack(); - if (!((IAbstractFurnaceScreenHandler) c).meteor$isItemSmeltable(item)) continue; - if (!smeltableItems.get().contains(item.getItem())) continue; - if (!smeltableItemFilter(item.getItem())) continue; - - slot = i; - break; - } - + int slot = findSlot(c, smeltableItems.get(), this::isSmeltableItem); if (disableWhenOutOfItems.get() && slot == -1) { error("You do not have any items in your inventory that can be smelted. Disabling."); toggle(); @@ -103,40 +139,140 @@ private void insertItems(AbstractFurnaceScreenHandler c) { InvUtils.move().fromId(slot).toId(0); } + /** + * Checks and refills the fuel slot in the furnace. + * + * @param c The furnace screen handler + */ private void checkFuel(AbstractFurnaceScreenHandler c) { ItemStack fuelStack = c.slots.get(1).getStack(); - if (c.getFuelProgress() > 0) return; - if (!fuelStack.isEmpty()) return; + // If the furnace is burning or has fuel, don't do anything + if (!fuelStack.isEmpty() || c.getFuelProgress() > 0) return; - int slot = -1; - for (int i = 3; i < c.slots.size(); i++) { - ItemStack item = c.slots.get(i).getStack(); - if (!fuelItems.get().contains(item.getItem())) continue; - if (!fuelItemFilter(item.getItem())) continue; + // If smart fuel is enabled, calculate how much fuel we need based on smeltable items + int neededFuelCount = fuelAmount.get(); + + if (smartFuelUsage.get()) { + int smeltableItemCount = countSmeltableItems(c); + if (smeltableItemCount == 0) return; - slot = i; - break; + // Calculate fuel needed based on the burn rate of available fuel + neededFuelCount = calculateRequiredFuel(smeltableItemCount); + if (neededFuelCount == 0) return; } - if (disableWhenOutOfItems.get() && slot == -1) { + // Track how much fuel we've added so far + int remainingToAdd = neededFuelCount; + boolean foundAnyFuel = false; + + // Check inventory slots for fuel + for (int i = 3; i < c.slots.size() && remainingToAdd > 0; i++) { + ItemStack stack = c.slots.get(i).getStack(); + + // Skip empty stacks or non-fuel items + if (stack.isEmpty() || !fuelItems.get().contains(stack.getItem()) || !isFuelItem(stack.getItem())) continue; + + // Take as many items as needed (or available) + int amountToTake = Math.min(stack.getCount(), remainingToAdd); + InvUtils.move(amountToTake).fromId(i).toId(1); + + remainingToAdd -= amountToTake; + foundAnyFuel = true; + } + + // If we couldn't find any fuel and the setting is enabled, disable the module + if (!foundAnyFuel && disableWhenOutOfItems.get()) { error("You do not have any fuel in your inventory. Disabling."); toggle(); - return; } - - InvUtils.move().fromId(slot).toId(1); } + /** + * Takes the smelted results from the furnace output slot. + * Disables the module if inventory is full and can't take results. + * + * @param c The furnace screen handler + */ private void takeResults(AbstractFurnaceScreenHandler c) { ItemStack resultStack = c.slots.get(2).getStack(); if (resultStack.isEmpty()) return; InvUtils.shiftClick().slotId(2); - if (!resultStack.isEmpty()) { error("Your inventory is full. Disabling."); toggle(); } } + + /** + * Finds a slot in the player's inventory containing an item from the specified list + * that also satisfies the given filter condition. + * + * @param c The furnace screen handler + * @param items List of items to search for + * @param filter Additional condition the item must satisfy + * @return The slot ID containing a matching item, or -1 if none found + */ + private int findSlot(AbstractFurnaceScreenHandler c, List items, Predicate filter) { + for (int i = 3; i < c.slots.size(); i++) { + ItemStack item = c.slots.get(i).getStack(); + if (items.contains(item.getItem()) && filter.test(item.getItem())) return i; + } + return -1; + } + + /** + * Counts how many smeltable items are available for a single smelting operation + * + * @param c The furnace screen handler + * @return The count of smeltable items for one batch (max 64) + */ + private int countSmeltableItems(AbstractFurnaceScreenHandler c) { + // First check the input slot + ItemStack inputStack = c.slots.get(0).getStack(); + if (!inputStack.isEmpty() && isSmeltableItem(inputStack.getItem())) { + // If there's already something in the input slot, only consider that + return Math.min(inputStack.getCount(), 64); + } + + // Otherwise, find the first stack of smeltable items + for (int i = 3; i < c.slots.size(); i++) { + ItemStack stack = c.slots.get(i).getStack(); + if (!stack.isEmpty() && smeltableItems.get().contains(stack.getItem()) && isSmeltableItem(stack.getItem())) { + return Math.min(stack.getCount(), 64); + } + } + + return 0; + } + + /** + * Calculates how much fuel is needed based on the number of items to smelt + * + * @param itemCount Number of items to smelt + * @return Number of fuel items needed + */ + private int calculateRequiredFuel(int itemCount) { + // If no items to smelt, don't need fuel + if (itemCount <= 0) return 0; + + // Get the first available fuel item + Item fuelItem = fuelItems.get().stream() + .filter(item -> mc.player.currentScreenHandler.slots.stream() + .anyMatch(slot -> slot.getStack().getItem() == item)) + .findFirst() + .orElse(null); + + if (fuelItem == null) return 0; + + // Get efficiency from map with default value of 8 + double itemsPerFuel = Math.max(getFuelEfficiency(fuelItem), 8.0); + + // Calculate how many fuel items are needed + int fuelNeeded = (int) Math.ceil(itemCount / itemsPerFuel); + + // Cap at the max fuel amount setting + return Math.min(fuelNeeded, fuelAmount.get()); + } } diff --git a/src/main/java/meteordevelopment/meteorclient/utils/player/InvUtils.java b/src/main/java/meteordevelopment/meteorclient/utils/player/InvUtils.java index 2d42abf874..ef1d1a288c 100644 --- a/src/main/java/meteordevelopment/meteorclient/utils/player/InvUtils.java +++ b/src/main/java/meteordevelopment/meteorclient/utils/player/InvUtils.java @@ -176,6 +176,18 @@ public static Action move() { return ACTION; } + /** + * Moves a specific number of items between inventory slots. + * Useful for moving partial stacks. + * + * @param count The maximum number of items to move + * @return An Action object configured for moving the specified number of items + */ + public static Action move(int count) { + ACTION.count = count; + return move(); + } + public static Action click() { ACTION.type = SlotActionType.PICKUP; return ACTION; @@ -202,7 +214,8 @@ public static Action drop() { } public static void dropHand() { - if (!mc.player.currentScreenHandler.getCursorStack().isEmpty()) mc.interactionManager.clickSlot(mc.player.currentScreenHandler.syncId, ScreenHandler.EMPTY_SPACE_SLOT_INDEX, 0, SlotActionType.PICKUP, mc.player); + if (!mc.player.currentScreenHandler.getCursorStack().isEmpty()) + mc.interactionManager.clickSlot(mc.player.currentScreenHandler.syncId, ScreenHandler.EMPTY_SPACE_SLOT_INDEX, 0, SlotActionType.PICKUP, mc.player); } public static class Action { @@ -211,6 +224,8 @@ public static class Action { private int from = -1; private int to = -1; private int data = 0; + // Default to moving all items + private int count = Integer.MAX_VALUE; private boolean isRecursive = false; @@ -309,8 +324,13 @@ private void run() { } if (type != null && from != -1 && to != -1) { - click(from); - if (two) click(to); + if (type == SlotActionType.PICKUP && two && count < Integer.MAX_VALUE) { + // Use partial stack movement when count is specified + movePartialStack(); + } else { + click(from); + if (two) click(to); + } } SlotActionType preType = type; @@ -318,11 +338,7 @@ private void run() { int preFrom = from; int preTo = to; - type = null; - two = false; - from = -1; - to = -1; - data = 0; + resetActionState(); if (!isRecursive && hadEmptyCursor && preType == SlotActionType.PICKUP && preTwo && (preFrom != -1 && preTo != -1) && !mc.player.currentScreenHandler.getCursorStack().isEmpty()) { isRecursive = true; @@ -331,8 +347,54 @@ private void run() { } } + private void resetActionState() { + type = null; + two = false; + from = -1; + to = -1; + data = 0; + count = Integer.MAX_VALUE; + } + + /** + * Performs a click action on a specified slot ID in the current screen handler. + * Uses the current action type and data values for the click operation. + * + * @param id The ID of the slot to click on + */ private void click(int id) { mc.interactionManager.clickSlot(mc.player.currentScreenHandler.syncId, id, data, type, mc.player); } + + /** + * Moves a specific number of items from one slot to another. + */ + private void movePartialStack() { + // Pick up the entire stack from the source slot + click(from); + + // Get the stack we just picked up + ItemStack cursorStack = mc.player.currentScreenHandler.getCursorStack(); + if (cursorStack.isEmpty()) return; + + // Calculate how many items we can actually move (min of what we have and what we want) + int itemsToMove = Math.min(cursorStack.getCount(), count); + + // Use right-click to place items one by one + int originalData = data; + data = 1; // 1 = right-click which places one item at a time + + for (int i = 0; i < itemsToMove; i++) { + click(to); + } + + // Restore original data value + data = originalData; + + // Put back any remaining items to the original slot + if (!mc.player.currentScreenHandler.getCursorStack().isEmpty()) { + click(from); + } + } } }