aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhoebe <77941535+catgirlseraid@users.noreply.github.com>2024-07-26 23:04:12 +1200
committerGitHub <noreply@github.com>2024-07-26 13:04:12 +0200
commit3cbb6b8b640d0f78cb3c316de1af92a9a0d8fed4 (patch)
tree5af597ab89f3636ae9668ddd21ee7a8ed25bd561
parent4558f7b79dbd6097747e4742e13dcbdec623612f (diff)
downloadskyhanni-3cbb6b8b640d0f78cb3c316de1af92a9a0d8fed4.tar.gz
skyhanni-3cbb6b8b640d0f78cb3c316de1af92a9a0d8fed4.tar.bz2
skyhanni-3cbb6b8b640d0f78cb3c316de1af92a9a0d8fed4.zip
Feature: Item Pickup Log (#1937)
Co-authored-by: SeRaid <77941535+SeRaid743@users.noreply.github.com> Co-authored-by: blahajenjoyer7 <171753706+blahajenjoyer7@users.noreply.github.com>
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/inventory/InventoryConfig.java5
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/inventory/ItemPickupLogConfig.java80
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/inventory/ItemPickupLog.kt340
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/renderables/Renderable.kt18
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,