diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/ModuleManager.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/ModuleManager.kt index e58943fc617..7c92a2f9004 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/ModuleManager.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/ModuleManager.kt @@ -326,6 +326,7 @@ object ModuleManager : EventListener, Iterable by modules { ModuleReach, ModuleAutoQueue, ModuleSmartEat, + ModuleReplenish, // Render ModuleAnimations, diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/player/ModuleReplenish.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/player/ModuleReplenish.kt new file mode 100644 index 00000000000..1f44da20953 --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/player/ModuleReplenish.kt @@ -0,0 +1,160 @@ +/* + * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce) + * + * Copyright (c) 2015 - 2025 CCBlueX + * + * LiquidBounce is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiquidBounce 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LiquidBounce. If not, see . + */ +package net.ccbluex.liquidbounce.features.module.modules.player + +import net.ccbluex.liquidbounce.event.events.ScheduleInventoryActionEvent +import net.ccbluex.liquidbounce.event.events.ScreenEvent +import net.ccbluex.liquidbounce.event.events.WorldChangeEvent +import net.ccbluex.liquidbounce.event.handler +import net.ccbluex.liquidbounce.features.module.Category +import net.ccbluex.liquidbounce.features.module.ClientModule +import net.ccbluex.liquidbounce.utils.client.Chronometer +import net.ccbluex.liquidbounce.utils.inventory.* +import net.minecraft.client.gui.screen.ingame.HandledScreen +import net.minecraft.client.gui.screen.ingame.InventoryScreen +import net.minecraft.item.Item +import net.minecraft.item.Items +import net.minecraft.screen.slot.SlotActionType + +/** + * Module Replenish + * + * Automatically refills your hotbar with items from your inventory when the count drops to a certain threshold. + * + * @author ccetl + */ +object ModuleReplenish : ClientModule("Replenish", Category.PLAYER) { + + private val constraints = tree(PlayerInventoryConstraints()) + private val itemThreshold by int("ItemThreshold", 5, 0..63) + private val delay by int("Delay", 40, 0..1000, "ms") + private val cleanUp by boolean("CleanUp", true) + private val usePickupAll by boolean("UsePickupAll", false) + private val insideOfInventories by boolean("InsideOfInventories", false) + private val insideOfChests by boolean("InsideOfChests", false) + + private val trackedHotbarItems = Array(9) { Items.AIR } + private val chronometer = Chronometer() + + override fun enable() { + trackedHotbarItems.fill(Items.AIR) + } + + @Suppress("unused") + private val worldChangeHandler = handler { + trackedHotbarItems.fill(Items.AIR) + } + + @Suppress("unused") + private val screenHandler = handler { event -> + if (event.screen is HandledScreen<*>) { + trackedHotbarItems.fill(Items.AIR) + } + } + + @Suppress("unused") + private val inventoryScheduleHandler = handler { event -> + if (!chronometer.hasElapsed(delay.toLong())) { + return@handler + } + + chronometer.reset() + + Slots.Hotbar.slots.forEach { slot -> + val itemStack = slot.itemStack + + // find the desired item + val item = itemStack.item.takeUnless { it == Items.AIR } ?: trackedHotbarItems[slot.hotbarSlot] + if (item == Items.AIR) { + return@forEach + } + + val currentStackNotEmpty = !itemStack.isEmpty + + // check if the current stack, if not empty, is allowed to be refilled + val unsupportedStackSize = item.maxCount <= itemThreshold + if (currentStackNotEmpty && (unsupportedStackSize || itemStack.count > itemThreshold)) { + trackedHotbarItems[slot.hotbarSlot] = itemStack.item + return@forEach + } + + // find replacement items + val inventorySlots = Slots.Inventory.slots + .filter { it.itemStack.item == item } + .sortedWith( + // clean up small stacks first when cleanUp is enabled otherwise prioritize larger stacks + if (cleanUp) compareBy { it.itemStack.count } else compareByDescending { it.itemStack.count } + ) + + // no stack to refill found + if (inventorySlots.isEmpty()) { + trackedHotbarItems[slot.hotbarSlot] = itemStack.item + return@forEach + } + + // refill + if (usePickupAll && currentStackNotEmpty) { + event.schedule( + constraints, + ClickInventoryAction.click(null, slot, 0, SlotActionType.PICKUP), + ClickInventoryAction.click(null, slot, 0, SlotActionType.PICKUP_ALL), + ClickInventoryAction.click(null, slot, 0, SlotActionType.PICKUP) + ) + } else { + refillNormal(item, if (currentStackNotEmpty) itemStack.count else 0, inventorySlots, slot, event) + } + + trackedHotbarItems[slot.hotbarSlot] = item + return@handler + } + } + + private fun refillNormal( + item: Item, + count: Int, + inventorySlots: List, + slot: HotbarItemSlot, + event: ScheduleInventoryActionEvent + ) { + var neededToRefill = item.maxCount - count + inventorySlots.forEach { inventorySlot -> + neededToRefill -= inventorySlot.itemStack.count + val actions = mutableListOf( + ClickInventoryAction.click(null, inventorySlot, 0, SlotActionType.PICKUP), + ClickInventoryAction.click(null, slot, 0, SlotActionType.PICKUP) + ) + + if (neededToRefill < 0) { + actions += ClickInventoryAction.click(null, slot, 0, SlotActionType.PICKUP) + } + + event.schedule(constraints, actions) + + if (neededToRefill <= 0) { + return + } + } + } + + override val running: Boolean + get() = super.running && + (insideOfChests || (mc.currentScreen !is HandledScreen<*> || mc.currentScreen is InventoryScreen)) && + (insideOfInventories || mc.currentScreen !is InventoryScreen) + +} diff --git a/src/main/resources/resources/liquidbounce/lang/en_us.json b/src/main/resources/resources/liquidbounce/lang/en_us.json index 515a1394880..807b2d7e9ff 100644 --- a/src/main/resources/resources/liquidbounce/lang/en_us.json +++ b/src/main/resources/resources/liquidbounce/lang/en_us.json @@ -627,5 +627,6 @@ "liquidbounce.module.itemChams.description": "Applies visual effects to your held items.", "liquidbounce.module.betterTab.description": "Multiple improvements to the tab list.", "liquidbounce.module.autoPearl.description": "Aims and throws a pearl at an enemies pearl trajectory.", + "liquidbounce.module.replenish.description": "Automatically refills your hotbar with items from your inventory when the count drops to a certain threshold.", "liquidbounce.module.anchor.description": "Pulls you into safe holes for crystal PvP." }