From 6f7845a819f82bf2a1c0ea3cf3855b6cc5cc7eb2 Mon Sep 17 00:00:00 2001 From: Katinuka <9a523ab@mailfence.com> Date: Wed, 12 Feb 2025 23:05:59 +0200 Subject: [PATCH 01/10] temporary solution --- .../ccbluex/liquidbounce/utils/item/ArmorComparator.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/item/ArmorComparator.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/item/ArmorComparator.kt index 37770a4e965..b4dd9a5b482 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/utils/item/ArmorComparator.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/item/ArmorComparator.kt @@ -103,9 +103,18 @@ class ArmorComparator( Enchantments.UNBREAKING ) private val OTHER_ENCHANTMENT_PER_LEVEL = floatArrayOf(3.0f, 1.0f, 0.1f, 0.05f, 0.01f) + + /** + * The minimum durability an armor piece must have to be prioritized for use. + * If an armor piece's remaining durability is lower than this threshold, + * the piece is not prioritized anymore, and it can be replaced with another piece + * so that this piece can be preserved. + */ + private const val DURABILITY_THRESHOLD = 24 } private val comparator = ComparatorChain( + compareBy { it.itemSlot.itemStack.maxDamage - it.itemSlot.itemStack.damage > DURABILITY_THRESHOLD }, compareByDescending { round(getThresholdedDamageReduction(it.itemSlot.itemStack).toDouble(), 3) }, compareBy { round(getEnchantmentThreshold(it.itemSlot.itemStack).toDouble(), 3) }, compareBy { it.itemSlot.itemStack.getEnchantmentCount() }, From b2c79b2cd486c64652e76c7e4dd26bfc5bda8270 Mon Sep 17 00:00:00 2001 From: Katinuka <9a523ab@mailfence.com> Date: Thu, 13 Feb 2025 00:09:55 +0200 Subject: [PATCH 02/10] auto inventory open --- .../combat/autoarmor/ModuleAutoArmor.kt | 33 ++++++++++++++++++- .../utils/inventory/InventoryUtils.kt | 2 +- .../utils/item/ArmorComparator.kt | 4 +-- .../liquidbounce/utils/item/ItemExtensions.kt | 3 ++ 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt index 6a91cc52b5d..457ced88110 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt @@ -20,21 +20,27 @@ package net.ccbluex.liquidbounce.features.module.modules.combat.autoarmor import net.ccbluex.liquidbounce.event.events.ScheduleInventoryActionEvent import net.ccbluex.liquidbounce.event.handler +import net.ccbluex.liquidbounce.event.tickHandler import net.ccbluex.liquidbounce.features.module.Category import net.ccbluex.liquidbounce.features.module.ClientModule import net.ccbluex.liquidbounce.utils.inventory.ArmorItemSlot import net.ccbluex.liquidbounce.utils.inventory.HotbarItemSlot import net.ccbluex.liquidbounce.utils.inventory.ItemSlot import net.ccbluex.liquidbounce.utils.inventory.* +import net.ccbluex.liquidbounce.utils.item.ArmorComparator.Companion.DURABILITY_THRESHOLD import net.ccbluex.liquidbounce.utils.item.ArmorPiece +import net.ccbluex.liquidbounce.utils.item.durability import net.ccbluex.liquidbounce.utils.item.isNothing +import net.ccbluex.liquidbounce.utils.item.type import net.ccbluex.liquidbounce.utils.kotlin.Priority +import net.minecraft.client.gui.screen.ingame.InventoryScreen +import net.minecraft.item.ArmorItem import net.minecraft.item.Items /** * AutoArmor module * - * Automatically put on the best armor. + * Automatically puts on the best armor. */ object ModuleAutoArmor : ClientModule("AutoArmor", Category.COMBAT) { @@ -46,6 +52,31 @@ object ModuleAutoArmor : ClientModule("AutoArmor", Category.COMBAT) { */ private val useHotbar by boolean("Hotbar", true) + // TODO: ideally, this value should be visible only if [inventoryConstraints] requires open inventory + // because if there is no such requirement, the armor will be replaced and saved automatically. + private val autoOpenInventoryToSaveArmor by boolean("AutoOpenInvToSaveArmor", false) + + private val repeatable = tickHandler { + val armorToEquip = ArmorEvaluation.findBestArmorPieces().values.filterNotNull().filter { + !it.isAlreadyEquipped + } + + val hasArmorToRepair = player.inventory.armor.filter { it.item is ArmorItem } + .any { armorStack -> + armorStack.durability <= DURABILITY_THRESHOLD && armorToEquip + .filter { it.itemSlot.itemStack.item is ArmorItem } + .any { (it.itemSlot.itemStack.item as ArmorItem).type() == (armorStack.item as ArmorItem).type() } + } + + if (autoOpenInventoryToSaveArmor && inventoryConstraints.requiresOpenInventory && hasArmorToRepair) { + if (mc.currentScreen != null) { + player.closeHandledScreen() + } + // TODO: make sure it's legit and undetectable. + mc.setScreen(InventoryScreen(player)) + } + } + private val scheduleHandler = handler { event -> // Filter out already equipped armor pieces val armorToEquip = ArmorEvaluation.findBestArmorPieces().values.filterNotNull().filter { diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/inventory/InventoryUtils.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/inventory/InventoryUtils.kt index 6a9aed4da88..c1301bb6612 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/utils/inventory/InventoryUtils.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/inventory/InventoryUtils.kt @@ -85,7 +85,7 @@ class PlayerInventoryConstraints : InventoryConstraints() { * Sad. * :( */ - private val requiresOpenInventory by boolean("RequiresInventoryOpen", false) + val requiresOpenInventory by boolean("RequiresInventoryOpen", false) override fun passesRequirements(action: InventoryAction) = super.passesRequirements(action) && diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/item/ArmorComparator.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/item/ArmorComparator.kt index b4dd9a5b482..757ef4d6b0c 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/utils/item/ArmorComparator.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/item/ArmorComparator.kt @@ -110,11 +110,11 @@ class ArmorComparator( * the piece is not prioritized anymore, and it can be replaced with another piece * so that this piece can be preserved. */ - private const val DURABILITY_THRESHOLD = 24 + const val DURABILITY_THRESHOLD = 24 } private val comparator = ComparatorChain( - compareBy { it.itemSlot.itemStack.maxDamage - it.itemSlot.itemStack.damage > DURABILITY_THRESHOLD }, + compareBy { it.itemSlot.itemStack.durability > DURABILITY_THRESHOLD }, compareByDescending { round(getThresholdedDamageReduction(it.itemSlot.itemStack).toDouble(), 3) }, compareBy { round(getEnchantmentThreshold(it.itemSlot.itemStack).toDouble(), 3) }, compareBy { it.itemSlot.itemStack.getEnchantmentCount() }, diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/item/ItemExtensions.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/item/ItemExtensions.kt index 2596771c9b9..38825614247 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/utils/item/ItemExtensions.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/item/ItemExtensions.kt @@ -149,6 +149,9 @@ fun ItemStack.getSharpnessDamage(level: Int = sharpnessLevel) = if (level == 0) val ItemStack.attackSpeed: Float get() = item.getAttributeValue(EntityAttributes.ATTACK_SPEED) +val ItemStack.durability + get() = this.maxDamage - this.damage + private fun Item.getAttributeValue(attribute: RegistryEntry): Float { val attribInstance = EntityAttributeInstance(attribute) {} From b865f1a2580eb6b0ae56832547b15de78b3d777a Mon Sep 17 00:00:00 2001 From: Katinuka <9a523ab@mailfence.com> Date: Sun, 16 Feb 2025 23:28:21 +0200 Subject: [PATCH 03/10] some progress --- .../combat/autoarmor/ArmorEvaluation.kt | 13 ++-- .../combat/autoarmor/AutoArmorSaveArmor.kt | 8 ++ .../combat/autoarmor/ModuleAutoArmor.kt | 73 +++++++++++++++---- .../utils/item/ArmorComparator.kt | 18 ++--- 4 files changed, 80 insertions(+), 32 deletions(-) create mode 100644 src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/AutoArmorSaveArmor.kt diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ArmorEvaluation.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ArmorEvaluation.kt index 5d92e30ffc8..30818a32beb 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ArmorEvaluation.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ArmorEvaluation.kt @@ -15,7 +15,8 @@ object ArmorEvaluation { private const val EXPECTED_DAMAGE: Float = 6.0F fun findBestArmorPieces( - slots: List = Slots.All + slots: List = Slots.All, + durabilityThreshold: Int = Int.MIN_VALUE ): Map { val armorPiecesGroupedByType = groupArmorByType(slots) @@ -26,7 +27,7 @@ object ArmorEvaluation { // Run some passes in which we try to find best armor pieces based on the parameters of the last pass for (ignored in 0 until 2) { - val comparator = getArmorComparatorFor(currentBestPieces) + val comparator = getArmorComparatorFor(currentBestPieces, durabilityThreshold) currentBestPieces = armorPiecesGroupedByType.mapValues { it.value.maxWithOrNull(comparator) } } @@ -56,12 +57,12 @@ object ArmorEvaluation { return armorPiecesGroupedByType } - fun getArmorComparatorFor(currentKit: Map): ArmorComparator { - return getArmorComparatorForParameters(ArmorKitParameters.getParametersForSlots(currentKit)) + fun getArmorComparatorFor(currentKit: Map, durabilityThreshold: Int = Int.MIN_VALUE): ArmorComparator { + return getArmorComparatorForParameters(ArmorKitParameters.getParametersForSlots(currentKit), durabilityThreshold) } - fun getArmorComparatorForParameters(currentParameters: ArmorKitParameters): ArmorComparator { - return ArmorComparator(EXPECTED_DAMAGE, currentParameters) + fun getArmorComparatorForParameters(currentParameters: ArmorKitParameters, durabilityThreshold: Int = Int.MIN_VALUE): ArmorComparator { + return ArmorComparator(EXPECTED_DAMAGE, currentParameters, durabilityThreshold) } diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/AutoArmorSaveArmor.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/AutoArmorSaveArmor.kt new file mode 100644 index 00000000000..54b4593eed4 --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/AutoArmorSaveArmor.kt @@ -0,0 +1,8 @@ +package net.ccbluex.liquidbounce.features.module.modules.combat.autoarmor + +import net.ccbluex.liquidbounce.config.types.ToggleableConfigurable + +object AutoArmorSaveArmor : ToggleableConfigurable(ModuleAutoArmor, "SaveArmor", true) { + val durabilityThreshold by int("DurabilityThreshold", 24, 0..100) + val autoOpen by boolean("AutoOpenInventory", true) +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt index 457ced88110..7aedcfabe76 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt @@ -23,11 +23,11 @@ import net.ccbluex.liquidbounce.event.handler import net.ccbluex.liquidbounce.event.tickHandler import net.ccbluex.liquidbounce.features.module.Category import net.ccbluex.liquidbounce.features.module.ClientModule +import net.ccbluex.liquidbounce.integration.VrScreen import net.ccbluex.liquidbounce.utils.inventory.ArmorItemSlot import net.ccbluex.liquidbounce.utils.inventory.HotbarItemSlot import net.ccbluex.liquidbounce.utils.inventory.ItemSlot import net.ccbluex.liquidbounce.utils.inventory.* -import net.ccbluex.liquidbounce.utils.item.ArmorComparator.Companion.DURABILITY_THRESHOLD import net.ccbluex.liquidbounce.utils.item.ArmorPiece import net.ccbluex.liquidbounce.utils.item.durability import net.ccbluex.liquidbounce.utils.item.isNothing @@ -51,29 +51,70 @@ object ModuleAutoArmor : ClientModule("AutoArmor", Category.COMBAT) { * If disabled, it will only use inventory moves. */ private val useHotbar by boolean("Hotbar", true) + private var hasOpenedInventory = false - // TODO: ideally, this value should be visible only if [inventoryConstraints] requires open inventory - // because if there is no such requirement, the armor will be replaced and saved automatically. - private val autoOpenInventoryToSaveArmor by boolean("AutoOpenInvToSaveArmor", false) + init { + tree(AutoArmorSaveArmor) + } - private val repeatable = tickHandler { - val armorToEquip = ArmorEvaluation.findBestArmorPieces().values.filterNotNull().filter { - !it.isAlreadyEquipped + @Suppress("unused") + private val armorAutoSaveHandler = tickHandler { + if (!AutoArmorSaveArmor.enabled) { + return@tickHandler + } + + // the module will save armor automatically if open inventory isn't required + if (!inventoryConstraints.requiresOpenInventory || !AutoArmorSaveArmor.autoOpen) { + return@tickHandler } - val hasArmorToRepair = player.inventory.armor.filter { it.item is ArmorItem } - .any { armorStack -> - armorStack.durability <= DURABILITY_THRESHOLD && armorToEquip - .filter { it.itemSlot.itemStack.item is ArmorItem } - .any { (it.itemSlot.itemStack.item as ArmorItem).type() == (armorStack.item as ArmorItem).type() } + val armorToEquip = ArmorEvaluation.findBestArmorPieces().values + .filterNotNull() + .filter { !it.isAlreadyEquipped && it.itemSlot.itemStack.item is ArmorItem } + .map { it.itemSlot.itemStack.item as ArmorItem } + + val playerArmor = player.inventory.armor.filter { it.item is ArmorItem } + + val hasArmorToReplace = playerArmor.any { armorStack -> + armorStack.durability <= AutoArmorSaveArmor.durabilityThreshold && + armorToEquip.any { it.type() == (armorStack.item as ArmorItem).type() } } - if (autoOpenInventoryToSaveArmor && inventoryConstraints.requiresOpenInventory && hasArmorToRepair) { - if (mc.currentScreen != null) { + // closes the inventory after the armor is replaced + if (hasOpenedInventory && armorToEquip.isEmpty()) { + hasOpenedInventory = false + waitTicks(inventoryConstraints.closeDelay.random()) + + // the current screen might change while the module is waiting + if (mc.currentScreen is InventoryScreen) { player.closeHandledScreen() } - // TODO: make sure it's legit and undetectable. - mc.setScreen(InventoryScreen(player)) + } + + // tries to close the previous screen and open the inventory + while (hasArmorToReplace && mc.currentScreen !is InventoryScreen) { + if (mc.currentScreen is VrScreen) { + // closes ClickGUI to save some armor :) + (mc.currentScreen as VrScreen).close() + } else { + // closes any other screen. + // TODO: well, it doesn't... :( + // When the player is in a chest/anvil/crafting table/etc., + // hasArmorToReplace is always false... + // The server simply doesn't let the player know anything new about his armor :/ + // the client knows only the state of the armor before opening the screen, + // the client doesn't receive any updates on the armor slots until the screen is closed. + // However, the client still gets updates on the armor of other players :/ + player.closeHandledScreen() + } + + waitTicks(1) // TODO: custom delay? + + // again, the current screen might change while the module is waiting + if (mc.currentScreen == null) { + mc.setScreen(InventoryScreen(player)) + hasOpenedInventory = true + } } } diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/item/ArmorComparator.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/item/ArmorComparator.kt index 757ef4d6b0c..a881d13636a 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/utils/item/ArmorComparator.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/item/ArmorComparator.kt @@ -81,10 +81,16 @@ class ArmorKitParameters( * @property armorKitParametersForSlot armor (i.e. iron with Protection II vs plain diamond) behaves differently based * on the other armor pieces. Thus, the expected defense points and toughness have to be provided. Since those are * dependent on the other armor pieces, the armor parameters have to be provided slot-wise. + * @property durabilityThreshold the minimum durability an armor piece must have to be prioritized for use. + * If an armor piece's remaining durability is lower than this threshold, + * the piece is not prioritized anymore, and it can be replaced with another piece + * so that this piece can be preserved. */ class ArmorComparator( private val expectedDamage: Float, - private val armorKitParametersForSlot: ArmorKitParameters + private val armorKitParametersForSlot: ArmorKitParameters, + private val durabilityThreshold : Int = Int.MIN_VALUE + ) : Comparator { companion object { private val DAMAGE_REDUCTION_ENCHANTMENTS: Array> = arrayOf( @@ -103,18 +109,10 @@ class ArmorComparator( Enchantments.UNBREAKING ) private val OTHER_ENCHANTMENT_PER_LEVEL = floatArrayOf(3.0f, 1.0f, 0.1f, 0.05f, 0.01f) - - /** - * The minimum durability an armor piece must have to be prioritized for use. - * If an armor piece's remaining durability is lower than this threshold, - * the piece is not prioritized anymore, and it can be replaced with another piece - * so that this piece can be preserved. - */ - const val DURABILITY_THRESHOLD = 24 } private val comparator = ComparatorChain( - compareBy { it.itemSlot.itemStack.durability > DURABILITY_THRESHOLD }, + compareBy { it.itemSlot.itemStack.durability > durabilityThreshold }, compareByDescending { round(getThresholdedDamageReduction(it.itemSlot.itemStack).toDouble(), 3) }, compareBy { round(getEnchantmentThreshold(it.itemSlot.itemStack).toDouble(), 3) }, compareBy { it.itemSlot.itemStack.getEnchantmentCount() }, From 86f7d15ac08b7831997da624ed18f473ed22553e Mon Sep 17 00:00:00 2001 From: Katinuka <9a523ab@mailfence.com> Date: Mon, 17 Feb 2025 00:04:23 +0200 Subject: [PATCH 04/10] fixed some bugs :) --- .../combat/autoarmor/ModuleAutoArmor.kt | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt index 7aedcfabe76..24392ecd0aa 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt @@ -23,7 +23,6 @@ import net.ccbluex.liquidbounce.event.handler import net.ccbluex.liquidbounce.event.tickHandler import net.ccbluex.liquidbounce.features.module.Category import net.ccbluex.liquidbounce.features.module.ClientModule -import net.ccbluex.liquidbounce.integration.VrScreen import net.ccbluex.liquidbounce.utils.inventory.ArmorItemSlot import net.ccbluex.liquidbounce.utils.inventory.HotbarItemSlot import net.ccbluex.liquidbounce.utils.inventory.ItemSlot @@ -33,6 +32,7 @@ import net.ccbluex.liquidbounce.utils.item.durability import net.ccbluex.liquidbounce.utils.item.isNothing import net.ccbluex.liquidbounce.utils.item.type import net.ccbluex.liquidbounce.utils.kotlin.Priority +import net.minecraft.client.gui.screen.ingame.HandledScreen import net.minecraft.client.gui.screen.ingame.InventoryScreen import net.minecraft.item.ArmorItem import net.minecraft.item.Items @@ -68,7 +68,9 @@ object ModuleAutoArmor : ClientModule("AutoArmor", Category.COMBAT) { return@tickHandler } - val armorToEquip = ArmorEvaluation.findBestArmorPieces().values + val armorToEquip = ArmorEvaluation + .findBestArmorPieces(durabilityThreshold = AutoArmorSaveArmor.durabilityThreshold) + .values .filterNotNull() .filter { !it.isAlreadyEquipped && it.itemSlot.itemStack.item is ArmorItem } .map { it.itemSlot.itemStack.item as ArmorItem } @@ -93,11 +95,9 @@ object ModuleAutoArmor : ClientModule("AutoArmor", Category.COMBAT) { // tries to close the previous screen and open the inventory while (hasArmorToReplace && mc.currentScreen !is InventoryScreen) { - if (mc.currentScreen is VrScreen) { - // closes ClickGUI to save some armor :) - (mc.currentScreen as VrScreen).close() - } else { - // closes any other screen. + + if (mc.currentScreen is HandledScreen<*>) { + // closes chests/crating tables/etc. // TODO: well, it doesn't... :( // When the player is in a chest/anvil/crafting table/etc., // hasArmorToReplace is always false... @@ -106,6 +106,9 @@ object ModuleAutoArmor : ClientModule("AutoArmor", Category.COMBAT) { // the client doesn't receive any updates on the armor slots until the screen is closed. // However, the client still gets updates on the armor of other players :/ player.closeHandledScreen() + } else if (mc.currentScreen != null) { + // closes ClickGUI, game chat, etc. to save some armor :) + mc.currentScreen!!.close() } waitTicks(1) // TODO: custom delay? @@ -120,9 +123,11 @@ object ModuleAutoArmor : ClientModule("AutoArmor", Category.COMBAT) { private val scheduleHandler = handler { event -> // Filter out already equipped armor pieces - val armorToEquip = ArmorEvaluation.findBestArmorPieces().values.filterNotNull().filter { - !it.isAlreadyEquipped - } + val armorToEquip = ArmorEvaluation + .findBestArmorPieces(durabilityThreshold = AutoArmorSaveArmor.durabilityThreshold) + .values.filterNotNull().filter { + !it.isAlreadyEquipped + } for (armorPiece in armorToEquip) { event.schedule( From 541fc364af240d4e5b141b5e0d0f0b42d43c7ea3 Mon Sep 17 00:00:00 2001 From: Katinuka <9a523ab@mailfence.com> Date: Mon, 17 Feb 2025 00:06:27 +0200 Subject: [PATCH 05/10] line length --- .../modules/combat/autoarmor/ArmorEvaluation.kt | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ArmorEvaluation.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ArmorEvaluation.kt index 30818a32beb..2bb2fcf2877 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ArmorEvaluation.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ArmorEvaluation.kt @@ -57,11 +57,20 @@ object ArmorEvaluation { return armorPiecesGroupedByType } - fun getArmorComparatorFor(currentKit: Map, durabilityThreshold: Int = Int.MIN_VALUE): ArmorComparator { - return getArmorComparatorForParameters(ArmorKitParameters.getParametersForSlots(currentKit), durabilityThreshold) + fun getArmorComparatorFor( + currentKit: Map, + durabilityThreshold: Int = Int.MIN_VALUE + ): ArmorComparator { + return getArmorComparatorForParameters( + ArmorKitParameters.getParametersForSlots(currentKit), + durabilityThreshold + ) } - fun getArmorComparatorForParameters(currentParameters: ArmorKitParameters, durabilityThreshold: Int = Int.MIN_VALUE): ArmorComparator { + fun getArmorComparatorForParameters( + currentParameters: ArmorKitParameters, + durabilityThreshold: Int = Int.MIN_VALUE + ): ArmorComparator { return ArmorComparator(EXPECTED_DAMAGE, currentParameters, durabilityThreshold) } From 567e659541ad7f0af079759ad9343266682917cd Mon Sep 17 00:00:00 2001 From: Katinuka <9a523ab@mailfence.com> Date: Mon, 17 Feb 2025 22:52:39 +0200 Subject: [PATCH 06/10] added hotbar support + new swap from 1.19.4+ --- .../utils/client/vfp/VfpCompatibility.java | 12 +++++ .../combat/autoarmor/ModuleAutoArmor.kt | 53 +++++++++++++------ .../liquidbounce/utils/client/ProtocolUtil.kt | 8 +++ .../utils/item/ArmorComparator.kt | 1 - 4 files changed, 58 insertions(+), 16 deletions(-) diff --git a/src/main/java/net/ccbluex/liquidbounce/utils/client/vfp/VfpCompatibility.java b/src/main/java/net/ccbluex/liquidbounce/utils/client/vfp/VfpCompatibility.java index 302da17a248..ba5b0a4030b 100644 --- a/src/main/java/net/ccbluex/liquidbounce/utils/client/vfp/VfpCompatibility.java +++ b/src/main/java/net/ccbluex/liquidbounce/utils/client/vfp/VfpCompatibility.java @@ -151,4 +151,16 @@ public boolean isOlderThanOrEqual1_11_1() { } } + public boolean isNewerThanOrEqual1_19_4() { + try { + var version = ViaFabricPlus.getImpl().getTargetVersion(); + + // Check if the version is older or equal than 1.19.4 + return version.newerThanOrEqualTo(ProtocolVersion.v1_19_4); + } catch (Throwable throwable) { + LiquidBounce.INSTANCE.getLogger().error("Failed to check if 1.19.4", throwable); + return false; + } + } + } diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt index 24392ecd0aa..57cffd118dd 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt @@ -23,9 +23,9 @@ import net.ccbluex.liquidbounce.event.handler import net.ccbluex.liquidbounce.event.tickHandler import net.ccbluex.liquidbounce.features.module.Category import net.ccbluex.liquidbounce.features.module.ClientModule +import net.ccbluex.liquidbounce.utils.client.isNewerThanOrEqual1_19_4 import net.ccbluex.liquidbounce.utils.inventory.ArmorItemSlot import net.ccbluex.liquidbounce.utils.inventory.HotbarItemSlot -import net.ccbluex.liquidbounce.utils.inventory.ItemSlot import net.ccbluex.liquidbounce.utils.inventory.* import net.ccbluex.liquidbounce.utils.item.ArmorPiece import net.ccbluex.liquidbounce.utils.item.durability @@ -57,6 +57,15 @@ object ModuleAutoArmor : ClientModule("AutoArmor", Category.COMBAT) { tree(AutoArmorSaveArmor) } + /** + * Opens the inventory to save armor (as if the player has opened it manually) if the following conditions are met: + * - The module is told to save armor and there is a replacement :) + * - The inventory constraints require open inventory + * (Otherwise, the inventory will be open automatically in a silent way and the armor will be saved) + * - There is no replacement from the hotbar + * (If there are some pieces that can be replaced by the pieces from the hotbar, + * they will be used first, without opening the inventory) + */ @Suppress("unused") private val armorAutoSaveHandler = tickHandler { if (!AutoArmorSaveArmor.enabled) { @@ -68,14 +77,22 @@ object ModuleAutoArmor : ClientModule("AutoArmor", Category.COMBAT) { return@tickHandler } - val armorToEquip = ArmorEvaluation + val armorToEquipWithSlots = ArmorEvaluation .findBestArmorPieces(durabilityThreshold = AutoArmorSaveArmor.durabilityThreshold) .values .filterNotNull() .filter { !it.isAlreadyEquipped && it.itemSlot.itemStack.item is ArmorItem } - .map { it.itemSlot.itemStack.item as ArmorItem } + + val hasAnyHotBarReplacement = useHotbar && isNewerThanOrEqual1_19_4 && armorToEquipWithSlots.any { it.itemSlot is HotbarItemSlot } + if (hasAnyHotBarReplacement) { + // the new pieces from the hotbar have a higher priority + // due to the replacement speed (it's much faster, it makes sense to replace them first), + // so it waits until all pieces from hotbar are replaced + return@tickHandler + } val playerArmor = player.inventory.armor.filter { it.item is ArmorItem } + val armorToEquip = armorToEquipWithSlots.map { it.itemSlot.itemStack.item as ArmorItem } val hasArmorToReplace = playerArmor.any { armorStack -> armorStack.durability <= AutoArmorSaveArmor.durabilityThreshold && @@ -105,6 +122,10 @@ object ModuleAutoArmor : ClientModule("AutoArmor", Category.COMBAT) { // the client knows only the state of the armor before opening the screen, // the client doesn't receive any updates on the armor slots until the screen is closed. // However, the client still gets updates on the armor of other players :/ + + // TODO: since the client get no updates on the armor while a chest/crating table/etc. is open, + // try to approximately track the durability of the player's armor manually + // when the player receives damage and chest/crating table/etc. is open :) player.closeHandledScreen() } else if (mc.currentScreen != null) { // closes ClickGUI, game chat, etc. to save some armor :) @@ -123,8 +144,10 @@ object ModuleAutoArmor : ClientModule("AutoArmor", Category.COMBAT) { private val scheduleHandler = handler { event -> // Filter out already equipped armor pieces + val durabilityThreshold = if (AutoArmorSaveArmor.enabled) { AutoArmorSaveArmor.durabilityThreshold } else Int.MIN_VALUE + val armorToEquip = ArmorEvaluation - .findBestArmorPieces(durabilityThreshold = AutoArmorSaveArmor.durabilityThreshold) + .findBestArmorPieces(durabilityThreshold = durabilityThreshold) .values.filterNotNull().filter { !it.isAlreadyEquipped } @@ -152,15 +175,12 @@ object ModuleAutoArmor : ClientModule("AutoArmor", Category.COMBAT) { return null } - val inventorySlot = armorPiece.itemSlot - val armorPieceSlot = ArmorItemSlot(armorPiece.entitySlotId) - return if (!stackInArmor.isNothing()) { // Clear current armor - performMoveOrHotbarClick(armorPieceSlot, isInArmorSlot = true) + performMoveOrHotbarClick(armorPiece, isInArmorSlot = true) } else { // Equip new armor - performMoveOrHotbarClick(inventorySlot, isInArmorSlot = false) + performMoveOrHotbarClick(armorPiece, isInArmorSlot = false) } } @@ -175,21 +195,24 @@ object ModuleAutoArmor : ClientModule("AutoArmor", Category.COMBAT) { * @return True if a move occurred. */ private fun performMoveOrHotbarClick( - slot: ItemSlot, + armorPiece: ArmorPiece, isInArmorSlot: Boolean ): InventoryAction { - val canTryHotbarMove = !isInArmorSlot && useHotbar && !InventoryManager.isInventoryOpen - if (slot is HotbarItemSlot && canTryHotbarMove) { - return UseInventoryAction(slot) + val inventorySlot = armorPiece.itemSlot + val armorPieceSlot = if (isInArmorSlot) {ArmorItemSlot(armorPiece.entitySlotId)} else {inventorySlot} + + val canTryHotbarMove = (!isInArmorSlot || isNewerThanOrEqual1_19_4) && useHotbar && !InventoryManager.isInventoryOpen + if (inventorySlot is HotbarItemSlot && canTryHotbarMove) { + return UseInventoryAction(inventorySlot) } // Should the item be just thrown out of the inventory val shouldThrow = isInArmorSlot && !hasInventorySpace() return if (shouldThrow) { - ClickInventoryAction.performThrow(screen = null, slot) + ClickInventoryAction.performThrow(screen = null, armorPieceSlot) } else { - ClickInventoryAction.performQuickMove(screen = null, slot) + ClickInventoryAction.performQuickMove(screen = null, armorPieceSlot) } } diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/client/ProtocolUtil.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/client/ProtocolUtil.kt index 1412e1ee4f1..0f04e7cad7e 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/utils/client/ProtocolUtil.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/client/ProtocolUtil.kt @@ -115,6 +115,14 @@ val isOlderThanOrEqual1_11_1: Boolean logger.error("Failed to check if the server is using 1.11.1", it) }.getOrDefault(false) +val isNewerThanOrEqual1_19_4: Boolean + get() = runCatching { + // Check if the ViaFabricPlus mod is loaded - prevents from causing too many exceptions + usesViaFabricPlus && VfpCompatibility.INSTANCE.isNewerThanOrEqual1_19_4 + }.onFailure { + logger.error("Failed to check if the server is using 1.19.4", it) + }.getOrDefault(false) + fun selectProtocolVersion(protocolId: Int) { // Check if the ViaFabricPlus mod is loaded - prevents from causing too many exceptions if (usesViaFabricPlus) { diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/item/ArmorComparator.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/item/ArmorComparator.kt index a881d13636a..b9c2cdb1ced 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/utils/item/ArmorComparator.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/item/ArmorComparator.kt @@ -90,7 +90,6 @@ class ArmorComparator( private val expectedDamage: Float, private val armorKitParametersForSlot: ArmorKitParameters, private val durabilityThreshold : Int = Int.MIN_VALUE - ) : Comparator { companion object { private val DAMAGE_REDUCTION_ENCHANTMENTS: Array> = arrayOf( From edc130394fbd74ca93ad5575372253861dde760e Mon Sep 17 00:00:00 2001 From: Katinuka <9a523ab@mailfence.com> Date: Tue, 18 Feb 2025 12:42:04 +0200 Subject: [PATCH 07/10] some refactor --- .../combat/autoarmor/AutoArmorSaveArmor.kt | 116 +++++++++++++++++- .../combat/autoarmor/ModuleAutoArmor.kt | 114 ++--------------- 2 files changed, 123 insertions(+), 107 deletions(-) diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/AutoArmorSaveArmor.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/AutoArmorSaveArmor.kt index 54b4593eed4..60cdff44037 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/AutoArmorSaveArmor.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/AutoArmorSaveArmor.kt @@ -1,8 +1,122 @@ package net.ccbluex.liquidbounce.features.module.modules.combat.autoarmor import net.ccbluex.liquidbounce.config.types.ToggleableConfigurable +import net.ccbluex.liquidbounce.event.Sequence +import net.ccbluex.liquidbounce.event.tickHandler +import net.ccbluex.liquidbounce.utils.client.isNewerThanOrEqual1_19_4 +import net.ccbluex.liquidbounce.utils.inventory.HotbarItemSlot +import net.ccbluex.liquidbounce.utils.item.durability +import net.ccbluex.liquidbounce.utils.item.type +import net.minecraft.client.gui.screen.ingame.HandledScreen +import net.minecraft.client.gui.screen.ingame.InventoryScreen +import net.minecraft.item.ArmorItem object AutoArmorSaveArmor : ToggleableConfigurable(ModuleAutoArmor, "SaveArmor", true) { val durabilityThreshold by int("DurabilityThreshold", 24, 0..100) - val autoOpen by boolean("AutoOpenInventory", true) + private val autoOpen by boolean("AutoOpenInventory", true) + + private var hasOpenedInventory = false + + /** + * Opens the inventory to save armor (as if the player has opened it manually) if the following conditions are met: + * - The module is told to save armor and there is a replacement :) + * - The inventory constraints require open inventory + * (Otherwise, the inventory will be open automatically in a silent way and the armor will be saved) + * - There is no replacement from the hotbar + * (If there are some pieces that can be replaced by the pieces from the hotbar, + * they will be used first, without opening the inventory) + */ + @Suppress("unused") + private val armorAutoSaveHandler = tickHandler { + if (!ModuleAutoArmor.running || !AutoArmorSaveArmor.enabled) { + return@tickHandler + } + + // the module will save armor automatically if open inventory isn't required + if (!ModuleAutoArmor.inventoryConstraints.requiresOpenInventory || !autoOpen) { + return@tickHandler + } + + val armorToEquipWithSlots = ArmorEvaluation + .findBestArmorPieces(durabilityThreshold = durabilityThreshold) + .values + .filterNotNull() + .filter { !it.isAlreadyEquipped && it.itemSlot.itemStack.item is ArmorItem } + + val hasAnyHotBarReplacement = ModuleAutoArmor.useHotbar && isNewerThanOrEqual1_19_4 && + armorToEquipWithSlots.any { it.itemSlot is HotbarItemSlot } + + // the new pieces from the hotbar have a higher priority + // due to the replacement speed (it's much faster, it makes sense to replace them first), + // so it waits until all pieces from hotbar are replaced + if (hasAnyHotBarReplacement) { + return@tickHandler + } + + val playerArmor = player.inventory.armor.filter { it.item is ArmorItem } + val armorToEquip = armorToEquipWithSlots.map { it.itemSlot.itemStack.item as ArmorItem } + + val hasArmorToReplace = playerArmor.any { armorStack -> + armorStack.durability <= durabilityThreshold && + armorToEquip.any { it.type() == (armorStack.item as ArmorItem).type() } + } + + // closes the inventory if the armor is replaced. + closeInventory(hasArmorToEquip = armorToEquip.isNotEmpty()) + + // tries to close the previous screen and open the inventory + openInventory(hasArmorToReplace = hasArmorToReplace) + } + + /** + * Waits and closes the inventory after the armor is replaced. + */ + private suspend fun Sequence.closeInventory(hasArmorToEquip: Boolean) { + if (!hasOpenedInventory || hasArmorToEquip) { + return + } + + this@AutoArmorSaveArmor.hasOpenedInventory = false + waitTicks(ModuleAutoArmor.inventoryConstraints.closeDelay.random()) + + // the current screen might change while the module is waiting + if (mc.currentScreen is InventoryScreen) { + player.closeHandledScreen() + } + } + + /** + * Closes the previous game screen and opens the inventory. + */ + private suspend fun Sequence.openInventory(hasArmorToReplace : Boolean) { + while (hasArmorToReplace && mc.currentScreen !is InventoryScreen) { + + if (mc.currentScreen is HandledScreen<*>) { + // closes chests/crating tables/etc. + // TODO: well, it doesn't... :( + // When the player is in a chest/anvil/crafting table/etc., + // hasArmorToReplace is always false... + // The server simply doesn't let the player know anything new about his armor :/ + // the client knows only the state of the armor before opening the screen, + // the client doesn't receive any updates on the armor slots until the screen is closed. + // However, the client still gets updates on the armor of other players :/ + + // TODO: since the client get no updates on the armor while a chest/crating table/etc. is open, + // try to approximately track the durability of the player's armor manually + // when the player receives damage and chest/crating table/etc. is open :) + player.closeHandledScreen() + } else if (mc.currentScreen != null) { + // closes ClickGUI, game chat, etc. to save some armor :) + mc.currentScreen!!.close() + } + + waitTicks(1) // TODO: custom delay? + + // again, the current screen might change while the module is waiting + if (mc.currentScreen == null) { + mc.setScreen(InventoryScreen(player)) + hasOpenedInventory = true + } + } + } } diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt index 57cffd118dd..db95db589fe 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt @@ -20,21 +20,16 @@ package net.ccbluex.liquidbounce.features.module.modules.combat.autoarmor import net.ccbluex.liquidbounce.event.events.ScheduleInventoryActionEvent import net.ccbluex.liquidbounce.event.handler -import net.ccbluex.liquidbounce.event.tickHandler import net.ccbluex.liquidbounce.features.module.Category import net.ccbluex.liquidbounce.features.module.ClientModule +import net.ccbluex.liquidbounce.features.module.modules.combat.autoarmor.AutoArmorSaveArmor.durabilityThreshold import net.ccbluex.liquidbounce.utils.client.isNewerThanOrEqual1_19_4 import net.ccbluex.liquidbounce.utils.inventory.ArmorItemSlot import net.ccbluex.liquidbounce.utils.inventory.HotbarItemSlot import net.ccbluex.liquidbounce.utils.inventory.* import net.ccbluex.liquidbounce.utils.item.ArmorPiece -import net.ccbluex.liquidbounce.utils.item.durability import net.ccbluex.liquidbounce.utils.item.isNothing -import net.ccbluex.liquidbounce.utils.item.type import net.ccbluex.liquidbounce.utils.kotlin.Priority -import net.minecraft.client.gui.screen.ingame.HandledScreen -import net.minecraft.client.gui.screen.ingame.InventoryScreen -import net.minecraft.item.ArmorItem import net.minecraft.item.Items /** @@ -44,113 +39,26 @@ import net.minecraft.item.Items */ object ModuleAutoArmor : ClientModule("AutoArmor", Category.COMBAT) { - private val inventoryConstraints = tree(PlayerInventoryConstraints()) + val inventoryConstraints = tree(PlayerInventoryConstraints()) /** - * Should the module use the hotbar to equip armor pieces. + * Should the module use the hotbar to equip armor pieces? * If disabled, it will only use inventory moves. */ - private val useHotbar by boolean("Hotbar", true) - private var hasOpenedInventory = false + val useHotbar by boolean("Hotbar", true) init { tree(AutoArmorSaveArmor) } - /** - * Opens the inventory to save armor (as if the player has opened it manually) if the following conditions are met: - * - The module is told to save armor and there is a replacement :) - * - The inventory constraints require open inventory - * (Otherwise, the inventory will be open automatically in a silent way and the armor will be saved) - * - There is no replacement from the hotbar - * (If there are some pieces that can be replaced by the pieces from the hotbar, - * they will be used first, without opening the inventory) - */ @Suppress("unused") - private val armorAutoSaveHandler = tickHandler { - if (!AutoArmorSaveArmor.enabled) { - return@tickHandler - } - - // the module will save armor automatically if open inventory isn't required - if (!inventoryConstraints.requiresOpenInventory || !AutoArmorSaveArmor.autoOpen) { - return@tickHandler - } - - val armorToEquipWithSlots = ArmorEvaluation - .findBestArmorPieces(durabilityThreshold = AutoArmorSaveArmor.durabilityThreshold) - .values - .filterNotNull() - .filter { !it.isAlreadyEquipped && it.itemSlot.itemStack.item is ArmorItem } - - val hasAnyHotBarReplacement = useHotbar && isNewerThanOrEqual1_19_4 && armorToEquipWithSlots.any { it.itemSlot is HotbarItemSlot } - if (hasAnyHotBarReplacement) { - // the new pieces from the hotbar have a higher priority - // due to the replacement speed (it's much faster, it makes sense to replace them first), - // so it waits until all pieces from hotbar are replaced - return@tickHandler - } - - val playerArmor = player.inventory.armor.filter { it.item is ArmorItem } - val armorToEquip = armorToEquipWithSlots.map { it.itemSlot.itemStack.item as ArmorItem } - - val hasArmorToReplace = playerArmor.any { armorStack -> - armorStack.durability <= AutoArmorSaveArmor.durabilityThreshold && - armorToEquip.any { it.type() == (armorStack.item as ArmorItem).type() } - } - - // closes the inventory after the armor is replaced - if (hasOpenedInventory && armorToEquip.isEmpty()) { - hasOpenedInventory = false - waitTicks(inventoryConstraints.closeDelay.random()) - - // the current screen might change while the module is waiting - if (mc.currentScreen is InventoryScreen) { - player.closeHandledScreen() - } - } - - // tries to close the previous screen and open the inventory - while (hasArmorToReplace && mc.currentScreen !is InventoryScreen) { - - if (mc.currentScreen is HandledScreen<*>) { - // closes chests/crating tables/etc. - // TODO: well, it doesn't... :( - // When the player is in a chest/anvil/crafting table/etc., - // hasArmorToReplace is always false... - // The server simply doesn't let the player know anything new about his armor :/ - // the client knows only the state of the armor before opening the screen, - // the client doesn't receive any updates on the armor slots until the screen is closed. - // However, the client still gets updates on the armor of other players :/ - - // TODO: since the client get no updates on the armor while a chest/crating table/etc. is open, - // try to approximately track the durability of the player's armor manually - // when the player receives damage and chest/crating table/etc. is open :) - player.closeHandledScreen() - } else if (mc.currentScreen != null) { - // closes ClickGUI, game chat, etc. to save some armor :) - mc.currentScreen!!.close() - } - - waitTicks(1) // TODO: custom delay? - - // again, the current screen might change while the module is waiting - if (mc.currentScreen == null) { - mc.setScreen(InventoryScreen(player)) - hasOpenedInventory = true - } - } - } - private val scheduleHandler = handler { event -> // Filter out already equipped armor pieces - val durabilityThreshold = if (AutoArmorSaveArmor.enabled) { AutoArmorSaveArmor.durabilityThreshold } else Int.MIN_VALUE + val durabilityThreshold = if (AutoArmorSaveArmor.enabled) { durabilityThreshold } else Int.MIN_VALUE val armorToEquip = ArmorEvaluation .findBestArmorPieces(durabilityThreshold = durabilityThreshold) - .values.filterNotNull().filter { - !it.isAlreadyEquipped - } + .values.filterNotNull().filter { !it.isAlreadyEquipped } for (armorPiece in armorToEquip) { event.schedule( @@ -175,13 +83,7 @@ object ModuleAutoArmor : ClientModule("AutoArmor", Category.COMBAT) { return null } - return if (!stackInArmor.isNothing()) { - // Clear current armor - performMoveOrHotbarClick(armorPiece, isInArmorSlot = true) - } else { - // Equip new armor - performMoveOrHotbarClick(armorPiece, isInArmorSlot = false) - } + return performMoveOrHotbarClick(armorPiece, isInArmorSlot = !stackInArmor.isNothing()) } /** @@ -199,7 +101,7 @@ object ModuleAutoArmor : ClientModule("AutoArmor", Category.COMBAT) { isInArmorSlot: Boolean ): InventoryAction { val inventorySlot = armorPiece.itemSlot - val armorPieceSlot = if (isInArmorSlot) {ArmorItemSlot(armorPiece.entitySlotId)} else {inventorySlot} + val armorPieceSlot = if (isInArmorSlot) { ArmorItemSlot(armorPiece.entitySlotId) } else { inventorySlot } val canTryHotbarMove = (!isInArmorSlot || isNewerThanOrEqual1_19_4) && useHotbar && !InventoryManager.isInventoryOpen if (inventorySlot is HotbarItemSlot && canTryHotbarMove) { From e3bc14248945b74561c11cb01816a8de9158a3e9 Mon Sep 17 00:00:00 2001 From: Katinuka <9a523ab@mailfence.com> Date: Tue, 18 Feb 2025 12:48:46 +0200 Subject: [PATCH 08/10] extra braces --- .../module/modules/combat/autoarmor/ModuleAutoArmor.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt index db95db589fe..24c80bbf5cd 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt @@ -54,7 +54,7 @@ object ModuleAutoArmor : ClientModule("AutoArmor", Category.COMBAT) { @Suppress("unused") private val scheduleHandler = handler { event -> // Filter out already equipped armor pieces - val durabilityThreshold = if (AutoArmorSaveArmor.enabled) { durabilityThreshold } else Int.MIN_VALUE + val durabilityThreshold = if (AutoArmorSaveArmor.enabled) durabilityThreshold else Int.MIN_VALUE val armorToEquip = ArmorEvaluation .findBestArmorPieces(durabilityThreshold = durabilityThreshold) @@ -101,7 +101,7 @@ object ModuleAutoArmor : ClientModule("AutoArmor", Category.COMBAT) { isInArmorSlot: Boolean ): InventoryAction { val inventorySlot = armorPiece.itemSlot - val armorPieceSlot = if (isInArmorSlot) { ArmorItemSlot(armorPiece.entitySlotId) } else { inventorySlot } + val armorPieceSlot = if (isInArmorSlot) ArmorItemSlot(armorPiece.entitySlotId) else inventorySlot val canTryHotbarMove = (!isInArmorSlot || isNewerThanOrEqual1_19_4) && useHotbar && !InventoryManager.isInventoryOpen if (inventorySlot is HotbarItemSlot && canTryHotbarMove) { From d6ad64addd616bc772e6bd8b4164f5d2ebbe897a Mon Sep 17 00:00:00 2001 From: Katinuka <9a523ab@mailfence.com> Date: Fri, 21 Feb 2025 00:19:42 +0200 Subject: [PATCH 09/10] some progress --- .../combat/autoarmor/AutoArmorSaveArmor.kt | 49 ++++++++++++++----- .../combat/autoarmor/ModuleAutoArmor.kt | 8 ++- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/AutoArmorSaveArmor.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/AutoArmorSaveArmor.kt index 60cdff44037..78b70f55203 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/AutoArmorSaveArmor.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/AutoArmorSaveArmor.kt @@ -16,6 +16,7 @@ object AutoArmorSaveArmor : ToggleableConfigurable(ModuleAutoArmor, "SaveArmor", private val autoOpen by boolean("AutoOpenInventory", true) private var hasOpenedInventory = false + private var prevArmor = 0 /** * Opens the inventory to save armor (as if the player has opened it manually) if the following conditions are met: @@ -28,6 +29,10 @@ object AutoArmorSaveArmor : ToggleableConfigurable(ModuleAutoArmor, "SaveArmor", */ @Suppress("unused") private val armorAutoSaveHandler = tickHandler { + if (player.isCreative || player.isSpectator) { + return@tickHandler + } + if (!ModuleAutoArmor.running || !AutoArmorSaveArmor.enabled) { return@tickHandler } @@ -37,6 +42,36 @@ object AutoArmorSaveArmor : ToggleableConfigurable(ModuleAutoArmor, "SaveArmor", return@tickHandler } + /** + * The server doesn't let the client know about the state of its armor items + * when the player in a handled screen⁽¹⁾ like a chest, crafting table, anvil, etc., + * making the armor saving process not work at all when a handled screen is open. + * In other words, `{player.inventory.armor}` doesn't get updated in such case. + * + * So, it's necessary to track the armor items and update their state (e.g. durability) + * on the client side when the player receives damage, isn't it? :) + * Well, unfortunately, it's not possible to do this accurately, not even close. + * The server doesn't provide the client with enough data + * to calculate the armor durability on the client side. :( + * + * Nonetheless, if the player is still in a handled screen⁽¹⁾ + * and gets an armor piece broken, his armor attribute, `{player.armor}`, gets updated. + * This update lets the module know exactly when it should close the handled screen⁽¹⁾ + * so that the player equips a new armor piece. + * Yes, this won't save armor pieces but might save the player's life. + * + * (1) - not including the player's own inventory which is also a handled screen. + */ + val hasLostArmorPiece = shouldTrackArmor && player.armor < prevArmor + prevArmor = player.armor + + // closes the current screen so that the armor slots are synced again + // TODO: if possible, make it close the screen only if there is a replacement + if (hasLostArmorPiece) { + player.closeHandledScreen() + return@tickHandler + } + val armorToEquipWithSlots = ArmorEvaluation .findBestArmorPieces(durabilityThreshold = durabilityThreshold) .values @@ -93,17 +128,6 @@ object AutoArmorSaveArmor : ToggleableConfigurable(ModuleAutoArmor, "SaveArmor", if (mc.currentScreen is HandledScreen<*>) { // closes chests/crating tables/etc. - // TODO: well, it doesn't... :( - // When the player is in a chest/anvil/crafting table/etc., - // hasArmorToReplace is always false... - // The server simply doesn't let the player know anything new about his armor :/ - // the client knows only the state of the armor before opening the screen, - // the client doesn't receive any updates on the armor slots until the screen is closed. - // However, the client still gets updates on the armor of other players :/ - - // TODO: since the client get no updates on the armor while a chest/crating table/etc. is open, - // try to approximately track the durability of the player's armor manually - // when the player receives damage and chest/crating table/etc. is open :) player.closeHandledScreen() } else if (mc.currentScreen != null) { // closes ClickGUI, game chat, etc. to save some armor :) @@ -119,4 +143,7 @@ object AutoArmorSaveArmor : ToggleableConfigurable(ModuleAutoArmor, "SaveArmor", } } } + + private val shouldTrackArmor : Boolean + get() = mc.currentScreen !is InventoryScreen && mc.currentScreen is HandledScreen<*> } diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt index 24c80bbf5cd..e14134527ca 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/ModuleAutoArmor.kt @@ -53,6 +53,10 @@ object ModuleAutoArmor : ClientModule("AutoArmor", Category.COMBAT) { @Suppress("unused") private val scheduleHandler = handler { event -> + if (player.isCreative || player.isSpectator) { + return@handler + } + // Filter out already equipped armor pieces val durabilityThreshold = if (AutoArmorSaveArmor.enabled) durabilityThreshold else Int.MIN_VALUE @@ -103,7 +107,9 @@ object ModuleAutoArmor : ClientModule("AutoArmor", Category.COMBAT) { val inventorySlot = armorPiece.itemSlot val armorPieceSlot = if (isInArmorSlot) ArmorItemSlot(armorPiece.entitySlotId) else inventorySlot - val canTryHotbarMove = (!isInArmorSlot || isNewerThanOrEqual1_19_4) && useHotbar && !InventoryManager.isInventoryOpen + val canTryHotbarMove = (!isInArmorSlot || isNewerThanOrEqual1_19_4) + && useHotbar && !InventoryManager.isInventoryOpen + if (inventorySlot is HotbarItemSlot && canTryHotbarMove) { return UseInventoryAction(inventorySlot) } From b124d1e5fcd4f392562a8d33dec623acbc476c5d Mon Sep 17 00:00:00 2001 From: Katinuka <9a523ab@mailfence.com> Date: Sat, 22 Feb 2025 18:14:50 +0200 Subject: [PATCH 10/10] some progress --- .../module/modules/combat/autoarmor/AutoArmorSaveArmor.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/AutoArmorSaveArmor.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/AutoArmorSaveArmor.kt index 78b70f55203..ee1e1952072 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/AutoArmorSaveArmor.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/autoarmor/AutoArmorSaveArmor.kt @@ -66,7 +66,6 @@ object AutoArmorSaveArmor : ToggleableConfigurable(ModuleAutoArmor, "SaveArmor", prevArmor = player.armor // closes the current screen so that the armor slots are synced again - // TODO: if possible, make it close the screen only if there is a replacement if (hasLostArmorPiece) { player.closeHandledScreen() return@tickHandler @@ -127,14 +126,14 @@ object AutoArmorSaveArmor : ToggleableConfigurable(ModuleAutoArmor, "SaveArmor", while (hasArmorToReplace && mc.currentScreen !is InventoryScreen) { if (mc.currentScreen is HandledScreen<*>) { - // closes chests/crating tables/etc. + // closes chests/crating tables/etc. (it never happens) player.closeHandledScreen() } else if (mc.currentScreen != null) { // closes ClickGUI, game chat, etc. to save some armor :) mc.currentScreen!!.close() } - waitTicks(1) // TODO: custom delay? + waitTicks(1) // again, the current screen might change while the module is waiting if (mc.currentScreen == null) {