From 1efe50bff3fbb0e6a782aaf5284fab3fd60ec637 Mon Sep 17 00:00:00 2001 From: HiZe_ Date: Thu, 10 Aug 2023 12:23:02 +0200 Subject: Merge pull request #348 * Chest Value --- src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt | 1 + .../at/hannibal2/skyhanni/config/Features.java | 25 +- .../skyhanni/config/features/InventoryConfig.java | 71 ++++++ .../skyhanni/config/features/MiscConfig.java | 1 - .../skyhanni/features/misc/BestiaryData.kt | 32 +-- .../hannibal2/skyhanni/features/misc/ChestValue.kt | 261 +++++++++++++++++++++ .../at/hannibal2/skyhanni/utils/LorenzUtils.kt | 33 ++- .../java/at/hannibal2/skyhanni/utils/NumberUtil.kt | 50 +++- .../utils/renderables/RenderLineTooltips.kt | 217 +++++++++++++++++ .../skyhanni/utils/renderables/Renderable.kt | 63 +---- 10 files changed, 642 insertions(+), 112 deletions(-) create mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/ChestValue.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/utils/renderables/RenderLineTooltips.kt (limited to 'src') diff --git a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt index 8ca36a65b..6ef789465 100644 --- a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt +++ b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt @@ -366,6 +366,7 @@ class SkyHanniMod { loadModule(LivingCaveDefenseBlocks()) loadModule(LivingCaveLivingMetalHelper()) loadModule(RiftMotesOrb()) + loadModule(ChestValue()) loadModule(SlayerBossSpawnSoon()) loadModule(RiftBloodEffigies()) loadModule(RiftWiltedBerberisHelper()) diff --git a/src/main/java/at/hannibal2/skyhanni/config/Features.java b/src/main/java/at/hannibal2/skyhanni/config/Features.java index d43b3fe39..10fe1ce42 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/Features.java +++ b/src/main/java/at/hannibal2/skyhanni/config/Features.java @@ -1,30 +1,7 @@ package at.hannibal2.skyhanni.config; import at.hannibal2.skyhanni.SkyHanniMod; -import at.hannibal2.skyhanni.config.features.About; -import at.hannibal2.skyhanni.config.features.AshfangConfig; -import at.hannibal2.skyhanni.config.features.BazaarConfig; -import at.hannibal2.skyhanni.config.features.BingoConfig; -import at.hannibal2.skyhanni.config.features.ChatConfig; -import at.hannibal2.skyhanni.config.features.CommandsConfig; -import at.hannibal2.skyhanni.config.features.DamageIndicatorConfig; -import at.hannibal2.skyhanni.config.features.DevConfig; -import at.hannibal2.skyhanni.config.features.DianaConfig; -import at.hannibal2.skyhanni.config.features.DungeonConfig; -import at.hannibal2.skyhanni.config.features.FishingConfig; -import at.hannibal2.skyhanni.config.features.GUIConfig; -import at.hannibal2.skyhanni.config.features.GardenConfig; -import at.hannibal2.skyhanni.config.features.GhostCounterConfig; -import at.hannibal2.skyhanni.config.features.InventoryConfig; -import at.hannibal2.skyhanni.config.features.ItemAbilityConfig; -import at.hannibal2.skyhanni.config.features.MarkedPlayerConfig; -import at.hannibal2.skyhanni.config.features.MinionsConfig; -import at.hannibal2.skyhanni.config.features.MiscConfig; -import at.hannibal2.skyhanni.config.features.MobsConfig; -import at.hannibal2.skyhanni.config.features.OldHidden; -import at.hannibal2.skyhanni.config.features.RiftConfig; -import at.hannibal2.skyhanni.config.features.SlayerConfig; -import at.hannibal2.skyhanni.config.features.SummoningsConfig; +import at.hannibal2.skyhanni.config.features.*; import com.google.gson.annotations.Expose; import io.github.moulberry.moulconfig.Config; import io.github.moulberry.moulconfig.Social; diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/InventoryConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/InventoryConfig.java index c3cab81db..13a6d758c 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/InventoryConfig.java +++ b/src/main/java/at/hannibal2/skyhanni/config/features/InventoryConfig.java @@ -187,6 +187,77 @@ public class InventoryConfig { public Position position = new Position(144, 139, false, true); } + @Expose + @ConfigOption(name = "Chest Value", desc = "") + @Accordion + public ChestValueConfig chestValueConfig = new ChestValueConfig(); + + public static class ChestValueConfig { + @Expose + @ConfigOption(name = "Enabled", desc = "Enabled estimated value of chest") + @ConfigEditorBoolean + public boolean enabled = false; + + @Expose + @ConfigOption(name = "Show Stacks", desc = "Show the item icon before name.") + @ConfigEditorBoolean + public boolean showStacks = true; + + @Expose + @ConfigOption(name = "Display Type", desc = "Try to align everything to look nicer.") + @ConfigEditorBoolean + public boolean alignedDisplay = true; + + @Expose + @ConfigOption(name = "Name Length", desc = "Reduce item name length to gain extra space on screen.\n§cCalculated in pixels!") + @ConfigEditorSlider(minStep = 1, minValue = 10, maxValue = 200) + public int nameLength = 100; + + @Expose + @ConfigOption(name = "Highlight slot", desc = "Highlight slot where the item is when you hover over it in the display.") + @ConfigEditorBoolean + public boolean enableHighlight = true; + + @Expose + @ConfigOption(name = "Highlight color", desc = "Choose the highlight color.") + @ConfigEditorColour + public String highlightColor = "0:249:0:255:88"; + + @Expose + @ConfigOption(name = "Sorting Type", desc = "Price sorting type.") + @ConfigEditorDropdown(values = {"Descending", "Ascending"}) + public int sortingType = 0; + + @Expose + @ConfigOption(name = "Value formatting Type", desc = "Format of the price.") + @ConfigEditorDropdown(values = {"Short", "Long"}) + public int formatType = 0; + + @Expose + @ConfigOption(name = "Item To Show", desc = "Choose how many items are displayed.\n" + + "All items in the chest are still counted for the total value.") + @ConfigEditorSlider( + minValue = 0, + maxValue = 54, + minStep = 1 + ) + public int itemToShow = 15; + + @Expose + @ConfigOption(name = "Hide below", desc = "Item item value below configured amount.\n" + + "Items are still counted for the total value.") + @ConfigEditorSlider( + minValue = 50_000, + maxValue = 10_000_000, + minStep = 50_000 + ) + public int hideBelow = 100_000; + + + @Expose + public Position position = new Position(107, 141, false, true); + } + @Expose @ConfigOption( name = "Item number", diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/MiscConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/MiscConfig.java index eb7b5d2c6..a97fe1afe 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/MiscConfig.java +++ b/src/main/java/at/hannibal2/skyhanni/config/features/MiscConfig.java @@ -673,7 +673,6 @@ public class MiscConfig { @ConfigEditorDropdown(values = {"Short", "Long"}) public int numberFormat = 0; - @Expose @ConfigOption(name = "Display type", desc = "Choose what the display should show") @ConfigEditorDropdown(values = { diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/BestiaryData.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/BestiaryData.kt index 87ab04dca..626d24f03 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/BestiaryData.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/BestiaryData.kt @@ -8,6 +8,9 @@ import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent import at.hannibal2.skyhanni.utils.* import at.hannibal2.skyhanni.utils.ItemUtils.getLore import at.hannibal2.skyhanni.utils.LorenzUtils.addAsSingletonList +import at.hannibal2.skyhanni.utils.LorenzUtils.addButton +import at.hannibal2.skyhanni.utils.LorenzUtils.toBoolean +import at.hannibal2.skyhanni.utils.LorenzUtils.toInt import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators import at.hannibal2.skyhanni.utils.NumberUtil.formatNumber import at.hannibal2.skyhanni.utils.NumberUtil.romanToDecimalIfNeeded @@ -230,7 +233,7 @@ object BestiaryData { if (isMaxed && config.hideMaxed) continue val text = getMobLine(mob, isMaxed) val tips = getMobHover(mob) - newDisplay.addAsSingletonList(Renderable.hoverTips(text, tips, false) { true }) + newDisplay.addAsSingletonList(Renderable.hoverTips(text, tips) { true }) } } @@ -413,9 +416,6 @@ object BestiaryData { else -> "0" } - private fun Int.toBoolean() = this != 0 - private fun Boolean.toInt() = if (!this) 0 else 1 - data class Category( val name: String, val familiesFound: Long, @@ -452,30 +452,6 @@ object BestiaryData { fun getNextLevel() = level.getNextLevel() } - private fun MutableList>.addButton( - prefix: String, - getName: String, - onChange: () -> Unit, - tips: List = emptyList(), - ) { - val onClick = { - if ((System.currentTimeMillis() - lastclicked) > 100) { // funny thing happen if I don't do that - onChange() - SoundUtils.playClickSound() - lastclicked = System.currentTimeMillis() - } - } - add(buildList { - add(prefix) - add("§a[") - if (tips.isEmpty()) { - add(Renderable.link("§e$getName", false, onClick)) - } else { - add(Renderable.clickAndHover("§e$getName", tips, false, onClick)) - } - add("§a]") - }) - } private fun String.romanOrInt() = romanToDecimalIfNeeded().let { if (config.replaceRoman || it == 0) it.toString() else it.toRoman() diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/ChestValue.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/ChestValue.kt new file mode 100644 index 000000000..e0f2e860e --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/ChestValue.kt @@ -0,0 +1,261 @@ +package at.hannibal2.skyhanni.features.misc + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.data.IslandType +import at.hannibal2.skyhanni.events.* +import at.hannibal2.skyhanni.features.misc.items.EstimatedItemValue +import at.hannibal2.skyhanni.utils.* +import at.hannibal2.skyhanni.utils.ItemUtils.getInternalName +import at.hannibal2.skyhanni.utils.LorenzUtils.addAsSingletonList +import at.hannibal2.skyhanni.utils.LorenzUtils.addButton +import at.hannibal2.skyhanni.utils.LorenzUtils.toBoolean +import at.hannibal2.skyhanni.utils.LorenzUtils.toInt +import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.RenderUtils.highlight +import at.hannibal2.skyhanni.utils.RenderUtils.renderStringsAndItems +import at.hannibal2.skyhanni.utils.renderables.Renderable +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.inventory.GuiChest +import net.minecraft.init.Items +import net.minecraft.item.ItemStack +import net.minecraftforge.fml.common.eventhandler.EventPriority +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import java.awt.Color + +class ChestValue { + + private val config get() = SkyHanniMod.feature.inventory.chestValueConfig + private var display = emptyList>() + private val chestItems = mutableMapOf() + private val inInventory get() = InventoryUtils.openInventoryName().isValidStorage() + + @SubscribeEvent + fun onBackgroundDraw(event: GuiRenderEvent.ChestBackgroundRenderEvent) { + if (!isEnabled()) return + if (InventoryUtils.openInventoryName() == "") return + if (inInventory) { + config.position.renderStringsAndItems( + display, + extraSpace = -1, + itemScale = 1.3, + posLabel = "Estimated Chest Value" + ) + } + } + + @SubscribeEvent + fun onTick(event: LorenzTickEvent) { + if (!isEnabled()) return + if (event.isMod(5)) { + update() + } + } + + @SubscribeEvent + fun onInventoryOpen(event: InventoryOpenEvent) { + if (!isEnabled()) return + if (inInventory) { + update() + } + } + + @SubscribeEvent + fun onInventoryClose(event: InventoryCloseEvent) { + chestItems.clear() + Renderable.list.clear() + } + + @SubscribeEvent(priority = EventPriority.LOW) + fun onDrawBackground(event: GuiContainerEvent.BackgroundDrawnEvent) { + if (!isEnabled()) return + if (!config.enableHighlight) return + if (inInventory) { + for ((_, indexes) in Renderable.list) { + for (slot in InventoryUtils.getItemsInOpenChest()) { + if (indexes.contains(slot.slotIndex)) { + slot highlight Color(SpecialColour.specialToChromaRGB(config.highlightColor), true) + } + } + } + } + } + + private fun update() { + display = drawDisplay() + } + + private fun drawDisplay(): List> { + val newDisplay = mutableListOf>() + + init() + + if (chestItems.isEmpty()) return newDisplay + + addList(newDisplay) + addButton(newDisplay) + + return newDisplay + } + + private fun addList(newDisplay: MutableList>) { + val sortedList = sortedList() + var totalPrice = 0.0 + var rendered = 0 + val amountShowing = if (config.itemToShow > sortedList.size) sortedList.size else config.itemToShow + newDisplay.addAsSingletonList("§7Estimated Chest Value: §o(Showing $amountShowing of ${sortedList.size} items)") + for ((index, amount, stack, total, tips) in sortedList) { + totalPrice += total + if (rendered >= config.itemToShow) continue + if (total < config.hideBelow) continue + val textAmount = " §7x$amount:" + val width = Minecraft.getMinecraft().fontRendererObj.getStringWidth(textAmount) + val name = "${stack.displayName.reduceStringLength((config.nameLength - width), ' ')} $textAmount" + val price = "§b${(total).formatPrice()}" + val text = if (config.alignedDisplay) + "$name $price" + else + "${stack.displayName} §7x$amount: §b${total.formatPrice()}" + newDisplay.add(buildList { + val renderable = Renderable.hoverTips( + text, + tips, + stack = stack, + indexes = index) + add(" §7- ") + if (config.showStacks) add(stack) + add(renderable) + }) + rendered++ + } + newDisplay.addAsSingletonList("§6Total value : §b${totalPrice.formatPrice()}") + } + + private fun sortedList(): MutableList { + return when (config.sortingType) { + 0 -> chestItems.values.sortedByDescending { it.total } + 1 -> chestItems.values.sortedBy { it.total } + else -> chestItems.values.sortedByDescending { it.total } + }.toMutableList() + } + + private fun addButton(newDisplay: MutableList>) { + newDisplay.addButton("§7Sorted By: ", + getName = SortType.entries[config.sortingType].longName, + onChange = { + config.sortingType = (config.sortingType + 1) % 2 + update() + }) + + newDisplay.addButton("§7Value format: ", + getName = FormatType.entries[config.formatType].type, + onChange = { + config.formatType = (config.formatType + 1) % 2 + update() + }) + + newDisplay.addButton("§7Display Type: ", + getName = DisplayType.entries[config.alignedDisplay.toInt()].type, + onChange = { + config.alignedDisplay = ((config.alignedDisplay.toInt() + 1) % 2).toBoolean() + update() + }) + } + + private fun init() { + if (inInventory) { + val isMinion = InventoryUtils.openInventoryName().contains(" Minion ") + val slots = InventoryUtils.getItemsInOpenChest().filter { + it.hasStack && it.inventory != Minecraft.getMinecraft().thePlayer.inventory && (!isMinion || it.slotNumber % 9 != 1) + } + val stacks = buildMap { + slots.forEach { + put(it.slotIndex, it.stack) + } + } + chestItems.clear() + for ((i, stack) in stacks) { + val internalName = stack.getInternalName() + if (internalName == "") continue + if (NEUItems.getItemStackOrNull(internalName) == null) continue + val list = mutableListOf() + val pair = EstimatedItemValue.getEstimatedItemPrice(stack, list) + var (total, _) = pair + if (stack.item == Items.enchanted_book) + total /= 2 + list.add("§aTotal: §6§l${total.formatPrice()}") + if (total == 0.0) continue + val item = chestItems.getOrPut(internalName) { + Item(mutableListOf(), 0, stack, 0.0, list) + } + item.index.add(i) + item.amount += stack.stackSize + item.total += total * stack.stackSize + } + } + } + + private fun Double.formatPrice(): String { + return when (config.formatType) { + 0 -> if (this > 1_000_000_000) NumberUtil.format(this, true) else NumberUtil.format(this) + 1 -> this.addSeparators() + else -> "0" + } + } + + enum class SortType(val shortName: String, val longName: String) { + PRICE_DESC("Price D", "Price Descending"), + PRICE_ASC("Price A", "Price Ascending") + ; + } + + enum class FormatType(val type: String) { + SHORT("Formatted"), + LONG("Unformatted") + ; + } + + enum class DisplayType(val type: String) { + NORMAL("Normal"), + COMPACT("Aligned") + } + + private fun String.isValidStorage() = Minecraft.getMinecraft().currentScreen is GuiChest && ((this == "Chest" || + this == "Large Chest") || + (contains("Minion") && !contains("Recipe") && LorenzUtils.skyBlockIsland == IslandType.PRIVATE_ISLAND) || + this == "Personal Vault") + + + private fun String.reduceStringLength(targetLength: Int, char: Char): String { + val mc = Minecraft.getMinecraft() + val spaceWidth = mc.fontRendererObj.getCharWidth(char) + + var currentString = this + var currentLength = mc.fontRendererObj.getStringWidth(currentString) + + while (currentLength > targetLength) { + currentString = currentString.dropLast(1) + currentLength = mc.fontRendererObj.getStringWidth(currentString) + } + + val difference = targetLength - currentLength + + if (difference > 0) { + val numSpacesToAdd = difference / spaceWidth + val spaces = " ".repeat(numSpacesToAdd) + return currentString + spaces + } + + return currentString + } + + + data class Item( + val index: MutableList, + var amount: Int, + val stack: ItemStack, + var total: Double, + val tips: MutableList + ) + + private fun isEnabled() = LorenzUtils.inSkyBlock && config.enabled +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt index 6a2dd34fc..a2bcc4020 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt @@ -57,6 +57,7 @@ object LorenzUtils { const val DEBUG_PREFIX = "[SkyHanni Debug] §7" private val log = LorenzLogger("chat/mod_sent") + var lastButtonClicked = 0L fun debug(message: String) { if (SkyHanniMod.feature.dev.debugEnabled) { @@ -333,6 +334,31 @@ object LorenzUtils { }) } + inline fun MutableList>.addButton( + prefix: String, + getName: String, + crossinline onChange: () -> Unit, + tips: List = emptyList(), + ) { + val onClick = { + if ((System.currentTimeMillis() - lastButtonClicked) > 150) { // funny thing happen if I don't do that + onChange() + SoundUtils.playClickSound() + lastButtonClicked = System.currentTimeMillis() + } + } + add(buildList { + add(prefix) + add("§a[") + if (tips.isEmpty()) { + add(Renderable.link("§e$getName", false, onClick)) + } else { + add(Renderable.clickAndHover("§e$getName", tips, false, onClick)) + } + add("§a]") + }) + } + // TODO nea? // fun dynamic(block: () -> KMutableProperty0?): ReadWriteProperty { // return object : ReadWriteProperty { @@ -383,8 +409,8 @@ object LorenzUtils { val tileSign = (this as AccessorGuiEditSign).tileSign return (tileSign.signText[1].unformattedText.removeColor() == "^^^^^^" - && tileSign.signText[2].unformattedText.removeColor() == "Set your" - && tileSign.signText[3].unformattedText.removeColor() == "speed cap!") + && tileSign.signText[2].unformattedText.removeColor() == "Set your" + && tileSign.signText[3].unformattedText.removeColor() == "speed cap!") } fun inIsland(island: IslandType) = inSkyBlock && skyBlockIsland == island @@ -448,4 +474,7 @@ object LorenzUtils { javaClass.getDeclaredField("modifiers").makeAccessible().set(this, modifiers and (Modifier.FINAL.inv())) return this } + + fun Int.toBoolean() = this != 0 + fun Boolean.toInt() = if (!this) 0 else 1 } \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt b/src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt index efdb545d2..5e40a2daf 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt @@ -39,19 +39,57 @@ object NumberUtil { * @link https://stackoverflow.com/a/30661479 * @author assylias */ + @JvmStatic - fun format(value: Number): String { + fun format(value: Number, preciseBillions: Boolean = false): String { @Suppress("NAME_SHADOWING") val value = value.toLong() - //Long.MIN_VALUE == -Long.MIN_VALUE, so we need an adjustment here - if (value == Long.MIN_VALUE) return format(Long.MIN_VALUE + 1) - if (value < 0) return "-" + format(-value) + //Long.MIN_VALUE == -Long.MIN_VALUE so we need an adjustment here + if (value == Long.MIN_VALUE) return format(Long.MIN_VALUE + 1, preciseBillions) + if (value < 0) return "-" + format(-value, preciseBillions) + if (value < 1000) return value.toString() //deal with small numbers + val (divideBy, suffix) = suffixes.floorEntry(value) + val truncated = value / (divideBy / 10) //the number part of the output times 10 - val truncatedAt = if (suffix == "M") 1000 else 100 + + val truncatedAt = if (suffix == "M") 1000 else if (suffix == "B") 1000000 else 100 + + val hasDecimal = truncated < truncatedAt && truncated / 10.0 != (truncated / 10).toDouble() + + return if (value > 1_000_000_000 && hasDecimal && preciseBillions) { + val decimalPart = (value % 1_000_000_000) / 1_000_000 + "${truncated / 10}.$decimalPart$suffix" + } else { + if (hasDecimal) (truncated / 10.0).toString() + suffix else (truncated / 10).toString() + suffix + } + } + + @JvmStatic + fun format3(value: Number, digit: Int): String { + @Suppress("NAME_SHADOWING") + val value = value.toLong() + //Long.MIN_VALUE == -Long.MIN_VALUE so we need an adjustment here + if (value == Long.MIN_VALUE) return format3(Long.MIN_VALUE + 1, digit) + if (value < 0) return "-" + format3(-value, digit) + if (value < 1000) return value.toString() //deal with small numbers + + val (divideBy, suffix) = suffixes.floorEntry(value) + + var truncated = value / (divideBy / 10) //the number part of the output times 10 + + val truncatedAt = if (suffix == "M") 1000 else if (suffix == "B") 1000000 else 100 + val hasDecimal = truncated < truncatedAt && truncated / 10.0 != (truncated / 10).toDouble() - return if (hasDecimal) (truncated / 10.0).toString() + suffix else (truncated / 10).toString() + suffix + + // Add check for value greater than 1000000000 (1 Billion) + return if (value > 1000000000 && hasDecimal) { + val decimalPart = (value % 1000000000) / 1000000 // Extract 3 digits after the decimal point + "${(truncated / 10).toDouble().toString().take(digit + 2)}$suffix" + } else { + if (hasDecimal) (truncated / 10.0).toString().take(digit + 2) + suffix else (truncated / 10).toString() + suffix + } } /** diff --git a/src/main/java/at/hannibal2/skyhanni/utils/renderables/RenderLineTooltips.kt b/src/main/java/at/hannibal2/skyhanni/utils/renderables/RenderLineTooltips.kt new file mode 100644 index 000000000..2cf20ee72 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/renderables/RenderLineTooltips.kt @@ -0,0 +1,217 @@ +package at.hannibal2.skyhanni.utils.renderables + +import at.hannibal2.skyhanni.utils.ItemUtils.getLore +import io.github.moulberry.notenoughupdates.util.Utils +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.ScaledResolution +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.client.renderer.RenderHelper +import net.minecraft.client.renderer.Tessellator +import net.minecraft.client.renderer.vertex.DefaultVertexFormats +import net.minecraft.item.ItemStack +import java.awt.Color + +object RenderLineTooltips { + + fun drawHoveringText(posX: Int, posY: Int, tips: List, stack: ItemStack? = null) { + if (tips.isNotEmpty()) { + var textLines = tips + val x = Utils.getMouseX() + 12 - posX + val y = Utils.getMouseY() - 10 - posY + val color: Char = stack?.getLore()?.lastOrNull()?.take(4)?.get(1) + ?: Utils.getPrimaryColourCode(textLines[0]) + val colourInt = Minecraft.getMinecraft().fontRendererObj.getColorCode(color) + val borderColorStart = Color(colourInt).darker().rgb and 0x00FFFFFF or (200 shl 24) + val font = Minecraft.getMinecraft().fontRendererObj + val scaled = ScaledResolution(Minecraft.getMinecraft()) + GlStateManager.disableRescaleNormal() + RenderHelper.disableStandardItemLighting() + GlStateManager.disableLighting() + GlStateManager.enableDepth() + var tooltipTextWidth = 0 + for (textLine in textLines) { + val textLineWidth = font.getStringWidth(textLine) + if (textLineWidth > tooltipTextWidth) { + tooltipTextWidth = textLineWidth + } + } + var needsWrap = false + var titleLinesCount = 1 + var tooltipX = x + if (tooltipX + tooltipTextWidth + 4 > scaled.scaledWidth) { + tooltipX = x - 16 - tooltipTextWidth + if (tooltipX < 4) { + tooltipTextWidth = if (x > scaled.scaledWidth / 2) { + x - 12 - 8 + } else { + scaled.scaledWidth - 16 - x + } + needsWrap = true + } + } + if (needsWrap) { + var wrappedTooltipWidth = 0 + val wrappedTextLines: MutableList = ArrayList() + for (i in textLines.indices) { + val textLine = textLines[i] + val wrappedLine = font.listFormattedStringToWidth(textLine, tooltipTextWidth) + if (i == 0) { + titleLinesCount = wrappedLine.size + } + for (line in wrappedLine) { + val lineWidth = font.getStringWidth(line) + if (lineWidth > wrappedTooltipWidth) { + wrappedTooltipWidth = lineWidth + } + wrappedTextLines.add(line) + } + } + tooltipTextWidth = wrappedTooltipWidth + textLines = wrappedTextLines.toList() + tooltipX = if (x > scaled.scaledWidth / 2) { + x - 16 - tooltipTextWidth + } else { + x + 12 + } + } + var tooltipY = y - 12 + var tooltipHeight = 8 + if (textLines.size > 1) { + tooltipHeight += (textLines.size - 1) * 10 + if (textLines.size > titleLinesCount) { + tooltipHeight += 2 + } + } + + if (tooltipY + tooltipHeight + 6 > scaled.scaledHeight) { + tooltipY = scaled.scaledHeight - tooltipHeight - 6 + } + val zLevel = 300 + val backgroundColor = -0xfeffff0 + drawGradientRect( + zLevel, + tooltipX - 3, + tooltipY - 4, + tooltipX + tooltipTextWidth + 3, + tooltipY - 3, + backgroundColor, + backgroundColor + ) + drawGradientRect( + zLevel, + tooltipX - 3, + tooltipY + tooltipHeight + 3, + tooltipX + tooltipTextWidth + 3, + tooltipY + tooltipHeight + 4, + backgroundColor, + backgroundColor + ) + drawGradientRect( + zLevel, + tooltipX - 3, + tooltipY - 3, + tooltipX + tooltipTextWidth + 3, + tooltipY + tooltipHeight + 3, + backgroundColor, + backgroundColor + ) + drawGradientRect( + zLevel, + tooltipX - 4, + tooltipY - 3, + tooltipX - 3, + tooltipY + tooltipHeight + 3, + backgroundColor, + backgroundColor + ) + drawGradientRect( + zLevel, + tooltipX + tooltipTextWidth + 3, + tooltipY - 3, + tooltipX + tooltipTextWidth + 4, + tooltipY + tooltipHeight + 3, + backgroundColor, + backgroundColor + ) + val borderColorEnd = borderColorStart and 0xFEFEFE shr 1 or (borderColorStart and -0x1000000) + drawGradientRect( + zLevel, + tooltipX - 3, + tooltipY - 3 + 1, + tooltipX - 3 + 1, + tooltipY + tooltipHeight + 3 - 1, + borderColorStart, + borderColorEnd + ) + drawGradientRect( + zLevel, + tooltipX + tooltipTextWidth + 2, + tooltipY - 3 + 1, + tooltipX + tooltipTextWidth + 3, + tooltipY + tooltipHeight + 3 - 1, + borderColorStart, + borderColorEnd + ) + drawGradientRect( + zLevel, + tooltipX - 3, + tooltipY - 3, + tooltipX + tooltipTextWidth + 3, + tooltipY - 3 + 1, + borderColorStart, + borderColorStart + ) + drawGradientRect( + zLevel, + tooltipX - 3, + tooltipY + tooltipHeight + 2, + tooltipX + tooltipTextWidth + 3, + tooltipY + tooltipHeight + 3, + borderColorEnd, + borderColorEnd + ) + GlStateManager.disableDepth() + for (lineNumber in textLines.indices) { + val line = textLines[lineNumber] + font.drawStringWithShadow(line, 1f + tooltipX.toFloat(), 1f + tooltipY.toFloat(), -1) + if (lineNumber + 1 == titleLinesCount) { + tooltipY += 2 + } + tooltipY += 10 + } + GlStateManager.enableLighting() + GlStateManager.enableDepth() + RenderHelper.enableStandardItemLighting() + GlStateManager.enableRescaleNormal() + } + GlStateManager.disableLighting() + } + + private fun drawGradientRect(zLevel: Int, left: Int, top: Int, right: Int, bottom: Int, startColor: Int, endColor: Int) { + val startAlpha = (startColor shr 24 and 255).toFloat() / 255.0f + val startRed = (startColor shr 16 and 255).toFloat() / 255.0f + val startGreen = (startColor shr 8 and 255).toFloat() / 255.0f + val startBlue = (startColor and 255).toFloat() / 255.0f + val endAlpha = (endColor shr 24 and 255).toFloat() / 255.0f + val endRed = (endColor shr 16 and 255).toFloat() / 255.0f + val endGreen = (endColor shr 8 and 255).toFloat() / 255.0f + val endBlue = (endColor and 255).toFloat() / 255.0f + GlStateManager.disableTexture2D() + GlStateManager.enableBlend() + GlStateManager.disableAlpha() + GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0) + GlStateManager.shadeModel(7425) + val tessellator = Tessellator.getInstance() + val worldrenderer = tessellator.worldRenderer + worldrenderer.begin(7, DefaultVertexFormats.POSITION_COLOR) + worldrenderer.pos(right.toDouble(), top.toDouble(), zLevel.toDouble()).color(startRed, startGreen, startBlue, startAlpha).endVertex() + worldrenderer.pos(left.toDouble(), top.toDouble(), zLevel.toDouble()).color(startRed, startGreen, startBlue, startAlpha).endVertex() + worldrenderer.pos(left.toDouble(), bottom.toDouble(), zLevel.toDouble()).color(endRed, endGreen, endBlue, endAlpha).endVertex() + worldrenderer.pos(right.toDouble(), bottom.toDouble(), zLevel.toDouble()).color(endRed, endGreen, endBlue, endAlpha).endVertex() + tessellator.draw() + GlStateManager.shadeModel(7424) + GlStateManager.disableBlend() + GlStateManager.enableAlpha() + GlStateManager.enableTexture2D() + } +} \ No newline at end of file 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 c554b8bde..dabf428d4 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/renderables/Renderable.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/renderables/Renderable.kt @@ -2,14 +2,12 @@ package at.hannibal2.skyhanni.utils.renderables import at.hannibal2.skyhanni.config.core.config.gui.GuiPositionEditor import at.hannibal2.skyhanni.data.ToolTipData -import at.hannibal2.skyhanni.utils.LorenzColor import at.hannibal2.skyhanni.utils.LorenzLogger import at.hannibal2.skyhanni.utils.NEUItems.renderOnScreen import io.github.moulberry.moulconfig.gui.GuiScreenElementWrapper import io.github.moulberry.notenoughupdates.util.Utils import net.minecraft.client.Minecraft import net.minecraft.client.gui.Gui -import net.minecraft.client.gui.GuiScreen import net.minecraft.client.gui.inventory.GuiEditSign import net.minecraft.client.renderer.GlStateManager import net.minecraft.item.ItemStack @@ -21,7 +19,7 @@ interface Renderable { val height: Int fun isHovered(posX: Int, posY: Int) = Utils.getMouseX() in (posX..posX + width) - && Utils.getMouseY() in (posY..posY + height) // TODO: adjust for variable height? + && Utils.getMouseY() in (posY..posY + height) // TODO: adjust for variable height? /** * N.B.: the offset is absolute, not relative to the position and shouldn't be used for rendering @@ -31,6 +29,7 @@ interface Renderable { companion object { val logger = LorenzLogger("debug/renderable") + val list = mutableMapOf, List>() fun fromAny(any: Any?, itemScale: Double = 1.0): Renderable? = when (any) { null -> placeholder(12) @@ -101,12 +100,8 @@ interface Renderable { } } - fun hoverTips( - text: String, - tips: List, - bypassChecks: Boolean = false, - condition: () -> Boolean = { true } - ): Renderable { + fun hoverTips(text: String, tips: List, indexes: List = listOf(), stack: ItemStack? = null, bypassChecks: Boolean = false, condition: () -> Boolean = { true }): Renderable { + val render = string(text) return object : Renderable { override val width: Int @@ -117,53 +112,18 @@ interface Renderable { render.render(posX, posY) if (isHovered(posX, posY)) { if (condition() && shouldAllowLink(true, bypassChecks)) { - renderToolTips(posX, posY, tips) + list[Pair(posX, posY)] = indexes + RenderLineTooltips.drawHoveringText(posX, posY, tips, stack) + } + } else { + if (list.contains(Pair(posX, posY))) { + list.remove(Pair(posX, posY)) } } } } } - private fun renderToolTips(posX: Int, posY: Int, tips: List, border: Int = 1) { - GlStateManager.pushMatrix() -// GlStateManager.translate(0f, 0f, 2f) -// GuiRenderUtils.drawTooltip(tips, posX, posY, 0) -// GlStateManager.translate(0f, 0f, -2f) - - val x = Utils.getMouseX() - posX + 10 - val startY = Utils.getMouseY() - posY - 10 - var maxX = 0 - var y = startY - val renderer = Minecraft.getMinecraft().fontRendererObj - - GlStateManager.translate(0f, 0f, 2f) - for (line in tips) { - renderer.drawStringWithShadow( - "§f$line", - 1f + x, - 1f + y, - 0 - ) - val currentX = renderer.getStringWidth(line) - if (currentX > maxX) { - maxX = currentX - } - y += 10 - } - GlStateManager.translate(0f, 0f, -1f) - - GuiScreen.drawRect( - x - border, - startY - border, - x + maxX + 10 + border, - y + border, - LorenzColor.DARK_GRAY.toColor().rgb - ) - GlStateManager.translate(0f, 0f, -1f) - - GlStateManager.popMatrix() - } - private fun shouldAllowLink(debug: Boolean = false, bypassChecks: Boolean): Boolean { val isGuiScreen = Minecraft.getMinecraft().currentScreen != null if (bypassChecks) { @@ -252,4 +212,5 @@ interface Renderable { } } } -} \ No newline at end of file +} + -- cgit