From f13d20064e990e2517c109ac0ccfbe36ce18abe1 Mon Sep 17 00:00:00 2001 From: superblaubeere27 Date: Mon, 26 Aug 2024 02:13:53 +0200 Subject: [PATCH] feat: Improve Inventory Cleaner item count constraints interface (#3796) * Improved item limits * Fix detekt --- .../player/invcleaner/CleanupPlanGenerator.kt | 48 ++++++++---- .../invcleaner/ItemNumberConstraints.kt | 77 +++++++++++++++++++ .../modules/player/invcleaner/ItemPacker.kt | 42 +++++++--- .../invcleaner/ModuleInventoryCleaner.kt | 52 +++++++++++-- 4 files changed, 192 insertions(+), 27 deletions(-) create mode 100644 src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/player/invcleaner/ItemNumberConstraints.kt diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/player/invcleaner/CleanupPlanGenerator.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/player/invcleaner/CleanupPlanGenerator.kt index 9c4133fd34b..6f46217c5b0 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/player/invcleaner/CleanupPlanGenerator.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/player/invcleaner/CleanupPlanGenerator.kt @@ -24,11 +24,13 @@ import net.ccbluex.liquidbounce.utils.item.isNothing class CleanupPlanGenerator( private val template: CleanupPlanPlacementTemplate, private val availableItems: List, -) { +) : ItemPacker.ItemAmountContraintProvider { private val hotbarSwaps: ArrayList = ArrayList() private val packer = ItemPacker() + private val currentLimit = HashMap() + // TODO Implement greedy check /** * Keeps track of where a specific type of item should be placed. e.g. BLOCK -> [Hotbar 7, Hotbar 8] @@ -72,13 +74,6 @@ class CleanupPlanGenerator( category: ItemCategory, availableItems: List, ) { - val maxItemCount = - if (category.type.oneIsSufficient) { - if (this.packer.alreadyProviededFunctions.contains(category.type.providedFunction)) 0 else 1 - } else { - template.itemLimitPerCategory[category] ?: Int.MAX_VALUE - } - val hotbarSlotsToFill = this.categoryToSlotsMap[category] // We need to fill all hotbar slots with this item type. @@ -91,9 +86,8 @@ class CleanupPlanGenerator( this.packer.packItems( itemsToFillIn = prioritizedItemList, hotbarSlotsToFill = hotbarSlotsToFill, - maxItemCount = maxItemCount, + contraintProvider = this, forbiddenSlots = this.template.forbiddenSlots, - requiredStackCount = hotbarSlotsToFill?.size ?: 0, ) this.hotbarSwaps.addAll(requiredMoves) @@ -120,6 +114,34 @@ class CleanupPlanGenerator( return itemsByType } + + override fun getSatisfactionStatus(item: ItemFacet): ItemPacker.ItemAmountContraintProvider.SatisfactionStatus { + val constraints = this.template.itemAmountConstraintProvider(item) + + constraints.sortBy { it.group.priority } + + for (constraintInfo in constraints) { + val currentCount = this.currentLimit[constraintInfo.group] ?: 0 + + if (currentCount > constraintInfo.group.acceptableRange.last) { + return ItemPacker.ItemAmountContraintProvider.SatisfactionStatus.OVERSATURATED + } else if (currentCount < constraintInfo.group.acceptableRange.first) { + return ItemPacker.ItemAmountContraintProvider.SatisfactionStatus.NOT_SATISFIED + } + } + + return ItemPacker.ItemAmountContraintProvider.SatisfactionStatus.SATISFIED + } + + override fun addItem(item: ItemFacet) { + val constraints = this.template.itemAmountConstraintProvider(item) + + for (constraintInfo in constraints) { + val current = this.currentLimit.getOrDefault(constraintInfo.group, 0) + + this.currentLimit[constraintInfo.group] = current + constraintInfo.amountAddedByItem + } + } } class CleanupPlanPlacementTemplate( @@ -128,10 +150,10 @@ class CleanupPlanPlacementTemplate( */ val slotContentMap: Map, /** - * Contains an item limit for each category. e.g. BLOCK -> 128 will cause every stack above two to be thrown out. - * If an item is not in this map, there is no limit. + * A function which provides constraint groups for each item category and the number which the item counts against + * the given constraint. More info on how constraints work at [ItemNumberContraintGroup]. */ - val itemLimitPerCategory: Map, + val itemAmountConstraintProvider: (ItemFacet) -> ArrayList, /** * If false, slots which also contains items of that category, those items are not replaced with other items. */ diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/player/invcleaner/ItemNumberConstraints.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/player/invcleaner/ItemNumberConstraints.kt new file mode 100644 index 00000000000..d366213f3ba --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/player/invcleaner/ItemNumberConstraints.kt @@ -0,0 +1,77 @@ +package net.ccbluex.liquidbounce.features.module.modules.player.invcleaner + +/** + * Defines an item constraint group. + * + * For example if we had two constraints: + * - `BLOCKS` -> `128..` + * - `TNT` -> `..64` + * + * Imagine a situation where the player has 125 TNT: + * - If the TNT was processed first it would be thrown out since the TNT limit says that we have too much TNT. + * - If the BLOCKS constraint was processed first, the TNT would be kept since the BLOCKS constraint is not yet + * satisfied. + */ +abstract class ItemNumberContraintGroup( + /** + * The range of desired item amounts (which might be raw item counts, food saturation, etc.): + * - The lower limit defines the desired amount of items (=> any more items *might* be thrown out) + * - The upper limit defines the maximum amount of items (=> any more items *will* be thrown out) + */ + val acceptableRange: IntRange, + /** + * The priority of this constraint group. Lower values are processed first. + * Affects the order in which items are processed. + */ + val priority: Int, +) { + abstract override fun hashCode(): Int + abstract override fun equals(other: Any?): Boolean +} + +class ItemCategoryConstraintGroup( + acceptableRange: IntRange, + priority: Int, + val category: ItemCategory, +) : ItemNumberContraintGroup(acceptableRange, priority) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ItemCategoryConstraintGroup + + if (category != other.category) return false + + return true + } + + override fun hashCode(): Int { + return category.hashCode() + } +} + +class ItemFunctionCategoryConstraintGroup( + acceptableRange: IntRange, + priority: Int, + val function: ItemFunction, +) : ItemNumberContraintGroup(acceptableRange, priority) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ItemFunctionCategoryConstraintGroup + + if (function != other.function) return false + + return true + } + + override fun hashCode(): Int { + return function.hashCode() + } +} + +class ItemConstraintInfo( + val group: ItemNumberContraintGroup, + val amountAddedByItem: Int +) diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/player/invcleaner/ItemPacker.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/player/invcleaner/ItemPacker.kt index 5e34ba6a742..db665a3fb56 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/player/invcleaner/ItemPacker.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/player/invcleaner/ItemPacker.kt @@ -1,5 +1,7 @@ package net.ccbluex.liquidbounce.features.module.modules.player.invcleaner +import net.ccbluex.liquidbounce.features.module.modules.player.invcleaner.ItemPacker.ItemAmountContraintProvider.SatisfactionStatus.OVERSATURATED +import net.ccbluex.liquidbounce.features.module.modules.player.invcleaner.ItemPacker.ItemAmountContraintProvider.SatisfactionStatus.SATISFIED import net.ccbluex.liquidbounce.features.module.modules.player.invcleaner.items.ItemFacet import net.minecraft.item.ItemStack @@ -21,8 +23,6 @@ class ItemPacker { */ val usefulItems = HashSet() - val alreadyProviededFunctions = HashSet() - /** * Takes items from the [itemsToFillIn] list until it collected [maxItemCount] items is and [requiredStackCount] * stacks. The items are marked as useful and fills in hotbar slots if there are still slots to fill. @@ -33,11 +33,12 @@ class ItemPacker { itemsToFillIn: List, hotbarSlotsToFill: List?, forbiddenSlots: Set, - maxItemCount: Int, - requiredStackCount: Int, + contraintProvider: ItemAmountContraintProvider ): List { val moves = ArrayList() + val requriedStackCount = hotbarSlotsToFill?.size ?: 0 + var currentStackCount = 0 var currentItemCount = 0 @@ -45,11 +46,11 @@ class ItemPacker { val leftHotbarSlotIterator = hotbarSlotsToFill?.iterator() for (filledInItem in itemsToFillIn) { - val maxCountReached = currentItemCount >= maxItemCount - val allStacksFilled = currentStackCount >= requiredStackCount + val constraintsSatisfied = contraintProvider.getSatisfactionStatus(filledInItem) + val allStacksFilled = currentStackCount >= requriedStackCount - if (maxCountReached && allStacksFilled) { - break + if (allStacksFilled && constraintsSatisfied == SATISFIED || constraintsSatisfied == OVERSATURATED) { + continue } val filledInItemSlot = filledInItem.itemSlot @@ -61,7 +62,7 @@ class ItemPacker { usefulItems.add(filledInItemSlot) - alreadyProviededFunctions.addAll(filledInItem.providedItemFunctions) + contraintProvider.addItem(filledInItem) currentItemCount += filledInItem.itemStack.count currentStackCount++ @@ -114,6 +115,7 @@ class ItemPacker { return null } + areStacksSame -> { // We mark the slot as used to prevent it being used for another slot. alreadyAllocatedItems.add(hotbarSlotToFill) @@ -135,4 +137,26 @@ class ItemPacker { // We found no target slot return null } + + interface ItemAmountContraintProvider { + fun getSatisfactionStatus(item: ItemFacet): SatisfactionStatus + fun addItem(item: ItemFacet) + + enum class SatisfactionStatus { + /** + * Keep the item + */ + NOT_SATISFIED, + + /** + * The item is not needed - except for filling slots. + */ + SATISFIED, + + /** + * The item shouldn't be kept - even if there are still slots to fill. + */ + OVERSATURATED, + } + } } diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/player/invcleaner/ModuleInventoryCleaner.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/player/invcleaner/ModuleInventoryCleaner.kt index c768599eacb..5ba669a98fd 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/player/invcleaner/ModuleInventoryCleaner.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/player/invcleaner/ModuleInventoryCleaner.kt @@ -22,11 +22,11 @@ import net.ccbluex.liquidbounce.event.events.ScheduleInventoryActionEvent import net.ccbluex.liquidbounce.event.handler import net.ccbluex.liquidbounce.features.module.Category import net.ccbluex.liquidbounce.features.module.Module +import net.ccbluex.liquidbounce.features.module.modules.player.invcleaner.items.ItemFacet import net.ccbluex.liquidbounce.utils.inventory.ClickInventoryAction import net.ccbluex.liquidbounce.utils.inventory.PlayerInventoryConstraints import net.ccbluex.liquidbounce.utils.inventory.findNonEmptySlotsInInventory import net.minecraft.screen.slot.SlotActionType -import java.util.HashMap /** * InventoryCleaner module @@ -79,14 +79,17 @@ object ModuleInventoryCleaner : Module("InventoryCleaner", Category.PLAYER) { forbiddenSlots.add(ArmorItemSlot(armorSlot)) } - return CleanupPlanPlacementTemplate( - slotTargets, - itemLimitPerCategory = + val constraintProvider = AmountConstraintProvider( hashMapOf( Pair(ItemSortChoice.BLOCK.category!!, maxBlocks), Pair(ItemSortChoice.THROWABLES.category!!, maxThrowables), Pair(ItemCategory(ItemType.ARROW, 0), maxArrows), - ), + ) + ) + + return CleanupPlanPlacementTemplate( + slotTargets, + itemAmountConstraintProvider = constraintProvider::getConstraints, forbiddenSlots = forbiddenSlots, isGreedy = isGreedy, ) @@ -139,4 +142,43 @@ object ModuleInventoryCleaner : Module("InventoryCleaner", Category.PLAYER) { itemsInInv: List, ) = itemsInInv.filter { it !in cleanupPlan.usefulItems } + private class AmountConstraintProvider( + val maxItemsPerCategory: HashMap + ) { + fun getConstraints(facet: ItemFacet): ArrayList { + val constraints = ArrayList() + + if (facet.providedItemFunctions.isEmpty()) { + val defaultMin = if (facet.category.type.oneIsSufficient) 1 else Integer.MAX_VALUE + val minValue = this.maxItemsPerCategory[facet.category] ?: defaultMin + + val info = ItemConstraintInfo( + group = ItemCategoryConstraintGroup( + minValue..Integer.MAX_VALUE, + 10, + facet.category + ), + amountAddedByItem = facet.itemStack.count + ) + + constraints.add(info) + } else { + for (function in facet.providedItemFunctions) { + val info = ItemConstraintInfo( + group = ItemFunctionCategoryConstraintGroup( + 1..Integer.MAX_VALUE, + 10, + function + ), + amountAddedByItem = facet.itemStack.count + ) + + constraints.add(info) + } + } + + return constraints + } + } + }