diff options
Diffstat (limited to 'src/main/java/at/hannibal2')
5 files changed, 444 insertions, 1 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/inventory/InventoryConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/inventory/InventoryConfig.java index 684e2bb79..a3f88fca6 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/inventory/InventoryConfig.java +++ b/src/main/java/at/hannibal2/skyhanni/config/features/inventory/InventoryConfig.java @@ -59,6 +59,11 @@ public class InventoryConfig { public ChocolateFactoryConfig chocolateFactory = new ChocolateFactoryConfig(); @Expose + @ConfigOption(name = "Item Pickup Log", desc = "Logs all the picked up and dropped items") + @Accordion + public ItemPickupLogConfig itemPickupLogConfig = new ItemPickupLogConfig(); + + @Expose @Category(name = "Craftable Item List", desc = "") @Accordion public CraftableItemListConfig craftableItemList = new CraftableItemListConfig(); diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/inventory/ItemPickupLogConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/inventory/ItemPickupLogConfig.java new file mode 100644 index 000000000..24dc1dc11 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/inventory/ItemPickupLogConfig.java @@ -0,0 +1,80 @@ +package at.hannibal2.skyhanni.config.features.inventory; + +import at.hannibal2.skyhanni.config.FeatureToggle; +import at.hannibal2.skyhanni.config.core.config.Position; +import at.hannibal2.skyhanni.features.inventory.ItemPickupLog; +import at.hannibal2.skyhanni.utils.RenderUtils; +import com.google.gson.annotations.Expose; +import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorBoolean; +import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorDraggableList; +import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorDropdown; +import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorSlider; +import io.github.notenoughupdates.moulconfig.annotations.ConfigLink; +import io.github.notenoughupdates.moulconfig.annotations.ConfigOption; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class ItemPickupLogConfig { + + @Expose + @ConfigOption(name = "Item Pickup Log", desc = "Show a log of what items you pick up/drop and their amounts.") + @ConfigEditorBoolean + @FeatureToggle + public boolean enabled = true; + + @Expose + @ConfigOption(name = "Compact Lines", desc = "Combine the §a+ §7and §c- §7lines into a single line.") + @ConfigEditorBoolean + public boolean compactLines = true; + + @Expose + @ConfigOption(name = "Compact Numbers", desc = "Compact the amounts added and removed.") + @ConfigEditorBoolean + public boolean shorten = false; + + @Expose + @ConfigOption(name = "Sacks", desc = "Show items added and removed from stacks.") + @ConfigEditorBoolean + public boolean sack = false; + + @Expose + @ConfigOption(name = "Coins", desc = "Show coins added and removed from purse.") + @ConfigEditorBoolean + public boolean coins = false; + + @Expose + @ConfigOption( + name = "Alignment", + desc = "How the item pickup log should be aligned. §d:3" + ) + @ConfigEditorDropdown + public RenderUtils.VerticalAlignment alignment = RenderUtils.VerticalAlignment.TOP; + + @Expose + @ConfigOption( + name = "Layout", + desc = "Drag text to change the layout. List will be rendered horizontally" + ) + @ConfigEditorDraggableList(requireNonEmpty = true) + public List<ItemPickupLog.DisplayLayout> displayLayout = new ArrayList<>(Arrays.asList( + ItemPickupLog.DisplayLayout.CHANGE_AMOUNT, + ItemPickupLog.DisplayLayout.ICON, + ItemPickupLog.DisplayLayout.ITEM_NAME + )); + + @Expose + @ConfigOption( + name = "Expire After", + desc = "How long items show for after being picked up or dropped, in seconds." + ) + @ConfigEditorSlider(minValue = 1, maxValue = 20, minStep = 1) + public int expireAfter = 10; + + @Expose + @ConfigLink(owner = ItemPickupLogConfig.class, field = "enabled") + public Position pos = new Position(-256, 140, false, true); +} + + diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/ItemPickupLog.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/ItemPickupLog.kt new file mode 100644 index 000000000..1ce530a17 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/ItemPickupLog.kt @@ -0,0 +1,340 @@ +package at.hannibal2.skyhanni.features.inventory + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.events.GuiRenderEvent +import at.hannibal2.skyhanni.events.LorenzTickEvent +import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent +import at.hannibal2.skyhanni.events.PurseChangeEvent +import at.hannibal2.skyhanni.events.SackChangeEvent +import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule +import at.hannibal2.skyhanni.utils.CollectionUtils.addItemStack +import at.hannibal2.skyhanni.utils.InventoryUtils +import at.hannibal2.skyhanni.utils.ItemCategory +import at.hannibal2.skyhanni.utils.ItemNameResolver +import at.hannibal2.skyhanni.utils.ItemUtils.getInternalName +import at.hannibal2.skyhanni.utils.ItemUtils.getInternalNameOrNull +import at.hannibal2.skyhanni.utils.ItemUtils.getItemCategoryOrNull +import at.hannibal2.skyhanni.utils.ItemUtils.getItemRarityOrNull +import at.hannibal2.skyhanni.utils.ItemUtils.itemName +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.NEUInternalName +import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName +import at.hannibal2.skyhanni.utils.NEUItems.getItemStack +import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.shortFormat +import at.hannibal2.skyhanni.utils.RenderUtils.renderRenderable +import at.hannibal2.skyhanni.utils.SimpleTimeMark +import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.getExtraAttributes +import at.hannibal2.skyhanni.utils.StringUtils.removeColor +import at.hannibal2.skyhanni.utils.renderables.Renderable +import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern +import net.minecraft.client.Minecraft +import net.minecraft.item.ItemStack +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import java.util.Objects +import kotlin.math.absoluteValue +import kotlin.time.Duration.Companion.seconds + +@SkyHanniModule +object ItemPickupLog { + enum class DisplayLayout(private val display: String) { + CHANGE_AMOUNT("§a+256"), + ICON("§e✎"), + ITEM_NAME("§d[:3] TransRights's Cake Soul"), + ; + + override fun toString() = display + } + + private data class PickupEntry(val name: String, var amount: Long, val neuInternalName: NEUInternalName?) { + var timeUntilExpiry = SimpleTimeMark.now() + + fun updateAmount(change: Long) { + amount += change + timeUntilExpiry = SimpleTimeMark.now() + } + + fun isExpired() = timeUntilExpiry.passedSince() > config.expireAfter.seconds + } + + private val config get() = SkyHanniMod.feature.inventory.itemPickupLogConfig + private val coinIcon = "COIN_TALISMAN".asInternalName() + + private var itemList = mutableMapOf<Int, Pair<ItemStack, Int>>() + private var itemsAddedToInventory = mutableMapOf<Int, PickupEntry>() + private var itemsRemovedFromInventory = mutableMapOf<Int, PickupEntry>() + private var display: Renderable? = null + + private val patternGroup = RepoPattern.group("itempickuplog") + private val shopPattern by patternGroup.pattern( + "shoppattern", + "^(?<itemName>.+?)(?: x\\d+)?\$", + ) + + private val bannedItemsPattern by patternGroup.list( + "banneditems", + "SKYBLOCK_MENU", + "CANCEL_PARKOUR_ITEM", + "CANCEL_RACE_ITEM", + ) + private val bannedItemsConverted = bannedItemsPattern.map { it.toString().asInternalName() } + + @SubscribeEvent + fun onRenderOverlay(event: GuiRenderEvent) { + if (!isEnabled()) return + display?.let { config.pos.renderRenderable(it, posLabel = "Item Pickup Log Display") } + } + + @SubscribeEvent + fun onWorldChange(event: LorenzWorldChangeEvent) { + if (!isEnabled()) return + itemList.clear() + itemsAddedToInventory.clear() + itemsRemovedFromInventory.clear() + } + + @SubscribeEvent + fun onSackChange(event: SackChangeEvent) { + if (!isEnabled() || !config.sack) return + + event.sackChanges.forEach { + val itemStack = (it.internalName.getItemStack()) + val item = PickupEntry(itemStack.dynamicName(), it.delta.absoluteValue.toLong(), it.internalName) + + updateItem(itemStack.hash(), item, itemStack, it.delta < 0) + } + } + + @SubscribeEvent + fun onPurseChange(event: PurseChangeEvent) { + if (!isEnabled() || !config.coins || !worldChangeCooldown()) return + + updateItem(0, PickupEntry("§6Coins", event.coins.absoluteValue.toLong(), coinIcon), coinIcon.getItemStack(), event.coins < 0) + } + + @SubscribeEvent + fun onTick(event: LorenzTickEvent) { + if (!isEnabled()) return + + val oldItemList = mutableMapOf<Int, Pair<ItemStack, Int>>() + + oldItemList.putAll(itemList) + + itemList.clear() + + val inventoryItems = InventoryUtils.getItemsInOwnInventory().toMutableList() + val cursorItem = Minecraft.getMinecraft().thePlayer.inventory?.itemStack + + if (cursorItem != null) { + val hash = cursorItem.hash() + //this prevents items inside hypixel guis counting when picked up + if (oldItemList.contains(hash)) { + inventoryItems.add(cursorItem) + } + } + + for (itemStack in inventoryItems) { + val hash = itemStack.hash() + val old = itemList[hash] + if (old != null) { + itemList[hash] = old.copy(second = old.second + itemStack.stackSize) + } else { + itemList[hash] = itemStack to itemStack.stackSize + } + } + + if (!worldChangeCooldown()) return + + checkForDuplicateItems(itemList, oldItemList, false) + checkForDuplicateItems(oldItemList, itemList, true) + + val itemsRemovedUpdated = itemsRemovedFromInventory.values.removeIf { it.isExpired() } + val itemsAddedUpdated = itemsAddedToInventory.values.removeIf { it.isExpired() } + + if (itemsRemovedUpdated || itemsAddedUpdated || itemList != oldItemList) { + updateDisplay() + } + } + + private fun updateItem(hash: Int, itemInfo: PickupEntry, item: ItemStack, removed: Boolean) { + if (isBannedItem(item)) return + + val targetInventory = if (removed) itemsRemovedFromInventory else itemsAddedToInventory + val oppositeInventory = if (removed) itemsAddedToInventory else itemsRemovedFromInventory + + oppositeInventory[hash]?.let { existingItem -> + existingItem.timeUntilExpiry = SimpleTimeMark.now() + } + + targetInventory[hash]?.let { existingItem -> + existingItem.updateAmount(itemInfo.amount) + return + } + + targetInventory[hash] = itemInfo + } + + private fun renderList(prefix: String, entry: PickupEntry) = Renderable.horizontalContainer( + buildList { + val displayLayout: List<DisplayLayout> = config.displayLayout + for (item in displayLayout) { + when (item) { + DisplayLayout.ICON -> { + val itemIcon = entry.neuInternalName?.getItemStack() + if (itemIcon != null) { + addItemStack(itemIcon) + } else { + ItemNameResolver.getInternalNameOrNull(entry.name)?.let { addItemStack(it) } + } + } + + DisplayLayout.CHANGE_AMOUNT -> { + val formattedAmount = if (config.shorten) entry.amount.shortFormat() else entry.amount.addSeparators() + add(Renderable.string("${prefix}${formattedAmount}")) + } + + DisplayLayout.ITEM_NAME -> { + add(Renderable.string(entry.name)) + } + } + } + }, + ) + + private fun checkForDuplicateItems( + list: MutableMap<Int, Pair<ItemStack, Int>>, + listToCheckAgainst: MutableMap<Int, Pair<ItemStack, Int>>, + add: Boolean, + ) { + for ((key, value) in list) { + val stack = value.first + val oldAmount = value.second + + if (!listToCheckAgainst.containsKey(key)) { + val item = PickupEntry(stack.dynamicName(), oldAmount.toLong(), stack.getInternalNameOrNull()) + updateItem(key, item, stack, add) + } else if (oldAmount > listToCheckAgainst[key]!!.second) { + val amount = (oldAmount - listToCheckAgainst[key]?.second!!) + val item = PickupEntry(stack.dynamicName(), amount.toLong(), stack.getInternalNameOrNull()) + updateItem(key, item, stack, add) + } + } + } + + private fun isBannedItem(item: ItemStack): Boolean { + if (item.getInternalNameOrNull()?.startsWith("MAP") == true) { + return true + } + + if (bannedItemsConverted.contains(item.getInternalNameOrNull())) { + return true + } + + if (item.getExtraAttributes()?.hasKey("quiver_arrow") == true) { + return true + } + return false + } + + private fun ItemStack.dynamicName(): String { + val compact = when (getItemCategoryOrNull()) { + ItemCategory.ENCHANTED_BOOK -> true + ItemCategory.PET -> true + else -> false + } + return if (compact) getInternalName().itemName else displayName + } + + private fun ItemStack.hash(): Int { + var displayName = this.displayName.removeColor() + val matcher = shopPattern.matcher(displayName) + if (matcher.matches()) { + displayName = matcher.group("itemName") + } + return Objects.hash( + this.getInternalNameOrNull(), + displayName.removeColor(), + this.getItemRarityOrNull(), + ) + } + + private fun updateDisplay() { + if (!isEnabled()) return + + val display = mutableListOf<Renderable>() + + val removedItemsToNoLongerShow = itemsRemovedFromInventory.toMutableMap() + val addedItemsToNoLongerShow = itemsAddedToInventory.toMutableMap() + + if (config.compactLines) { + handleCompactLines(display, addedItemsToNoLongerShow, removedItemsToNoLongerShow) + } else { + handleNormalLines(display, addedItemsToNoLongerShow, removedItemsToNoLongerShow) + } + + addRemainingRemovedItems(display, removedItemsToNoLongerShow) + + if (display.isEmpty()) { + this.display = null + } else { + val renderable = Renderable.verticalContainer(display, verticalAlign = config.alignment) + this.display = Renderable.fixedSizeColumn(renderable, 30) + } + } + + private fun handleCompactLines( + display: MutableList<Renderable>, + addedItems: MutableMap<Int, PickupEntry>, + removedItems: MutableMap<Int, PickupEntry>, + ) { + val iterator = addedItems.iterator() + while (iterator.hasNext()) { + val item = iterator.next() + + if (removedItems.containsKey(item.key)) { + val currentTotalValue = item.value.amount - (removedItems[item.key]?.amount ?: 0) + val entry = PickupEntry(item.value.name, currentTotalValue, item.value.neuInternalName) + + if (currentTotalValue > 0) { + display.add(renderList("§a+", entry)) + } else if (currentTotalValue < 0) { + display.add(renderList("§c", entry)) + } else { + itemsAddedToInventory.remove(item.key) + itemsRemovedFromInventory.remove(item.key) + } + removedItems.remove(item.key) + iterator.remove() + } else { + display.add(renderList("§a+", item.value)) + } + } + } + + private fun handleNormalLines( + display: MutableList<Renderable>, + addedItems: MutableMap<Int, PickupEntry>, + removedItems: MutableMap<Int, PickupEntry>, + ) { + for (item in addedItems) { + display.add(renderList("§a+", item.value)) + removedItems[item.key]?.let { + display.add(renderList("§c-", it)) + removedItems.remove(item.key) + } + } + } + + private fun addRemainingRemovedItems( + display: MutableList<Renderable>, + removedItems: MutableMap<Int, PickupEntry>, + ) { + for (item in removedItems) { + display.add(renderList("§c-", item.value)) + } + } + + private fun worldChangeCooldown(): Boolean = LorenzUtils.lastWorldSwitch.passedSince() > 2.seconds + + private fun isEnabled() = LorenzUtils.inSkyBlock && config.enabled +} diff --git a/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt index 0191b497d..102013758 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt @@ -640,7 +640,7 @@ object RenderUtils { renderable.render(0, 0) } GlStateManager.popMatrix() - if (addToGuiManager) GuiEditManager.add(this, posLabel, renderable.width, 0) + if (addToGuiManager) GuiEditManager.add(this, posLabel, renderable.width, renderable.height) } /** This function is discouraged to be used. Please use renderRenderables with List<Renderable> instead with horizontal container.*/ diff --git a/src/main/java/at/hannibal2/skyhanni/utils/renderables/Renderable.kt b/src/main/java/at/hannibal2/skyhanni/utils/renderables/Renderable.kt index 464515241..74efcc316 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/renderables/Renderable.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/renderables/Renderable.kt @@ -672,6 +672,24 @@ interface Renderable { } } + fun fixedSizeBox( + content: Renderable, + height: Int, + width: Int, + horizontalAlign: HorizontalAlignment = HorizontalAlignment.LEFT, + verticalAlign: VerticalAlignment = VerticalAlignment.TOP, + ) = object : Renderable { + val render = content + + override val width = width + override val height = height + override val horizontalAlign = horizontalAlign + override val verticalAlign = verticalAlign + override fun render(posX: Int, posY: Int) { + render.renderXYAligned(posX, posY, height, width) + } + } + fun horizontalContainer( content: List<Renderable>, spacing: Int = 0, |