package at.hannibal2.skyhanni.features.misc import at.hannibal2.skyhanni.data.ProfileStorageData import at.hannibal2.skyhanni.events.GuiContainerEvent import at.hannibal2.skyhanni.events.InventoryCloseEvent import at.hannibal2.skyhanni.events.InventoryOpenEvent import at.hannibal2.skyhanni.events.LorenzToolTipEvent import at.hannibal2.skyhanni.events.render.gui.ReplaceItemEvent import at.hannibal2.skyhanni.features.skillprogress.SkillType import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.CollectionUtils.addOrPut import at.hannibal2.skyhanni.utils.ItemUtils.getLore import at.hannibal2.skyhanni.utils.ItemUtils.name import at.hannibal2.skyhanni.utils.LorenzUtils import at.hannibal2.skyhanni.utils.LorenzUtils.round 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.RegexUtils.matchMatcher import at.hannibal2.skyhanni.utils.SimpleTimeMark import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern import io.github.moulberry.notenoughupdates.util.Utils import net.minecraft.client.player.inventory.ContainerLocalMenu import net.minecraft.item.ItemStack import net.minecraftforge.fml.common.eventhandler.SubscribeEvent import kotlin.time.Duration.Companion.seconds @SkyHanniModule object UserLuckBreakdown { private var inMiscStats = false private var replaceSlot: Int? = null private var itemCreateCoolDown = SimpleTimeMark.farPast() private var skillCalcCoolDown = SimpleTimeMark.farPast() private val storage get() = ProfileStorageData.playerSpecific private lateinit var mainLuckItem: ItemStack private val mainLuckID = "ENDER_PEARL".asInternalName() private val mainLuckName = "§a✴ SkyHanni User Luck" private lateinit var fillerItem: ItemStack private var fillerID = "STAINED_GLASS_PANE".asInternalName() private val fillerName = " " private lateinit var limboItem: ItemStack private var limboID = "ENDER_PEARL".asInternalName() private val limboName = "§a✴ Limbo Personal Best" private lateinit var skillsItem: ItemStack private var skillsID = "DIAMOND_SWORD".asInternalName() private val skillsName = "§a✴ Category: Skills" private var showAllStats = true /** * REGEX-TEST: §7Show all stats: §aYes * REGEX-TEST: §7Show all stats: §cNope */ private val showAllStatsPattern by RepoPattern.pattern( "misc.statsbreakdown.showallstats", "§7Show all stats: §.(?.*)", ) private val luckTooltipString = "§5§o §a✴ SkyHanni User Luck §f" private var inCustomBreakdown = false private val validItemSlots = (10..53).filter { it !in listOf(17, 18, 26, 27, 35, 36) && it !in 44..53 } private val invalidItemSlots = (0..53).filter { it !in validItemSlots } private var skillOverflowLuck = mutableMapOf() @SubscribeEvent fun replaceItem(event: ReplaceItemEvent) { if (event.inventory !is ContainerLocalMenu) return if (!inMiscStats) return if (event.slot == replaceSlot && !inCustomBreakdown) { val limboUserLuck = storage?.limbo?.userLuck ?: 0.0f if (limboUserLuck == 0.0f && !showAllStats) return if (itemCreateCoolDown.passedSince() > 3.seconds) { itemCreateCoolDown = SimpleTimeMark.now() createItems() } event.replace(mainLuckItem) return } if (inCustomBreakdown) { if (itemCreateCoolDown.passedSince() > 3.seconds) { itemCreateCoolDown = SimpleTimeMark.now() createItems() } checkItemSlot(event) } } private fun checkItemSlot(event: ReplaceItemEvent) { when (event.slot) { 48, 49 -> return 10 -> event.replace(skillsItem) 11 -> event.replace(limboItem) in validItemSlots -> event.replace(null) in invalidItemSlots -> { if (event.originalItem.item == limboID.getItemStack().item) return event.replace(fillerItem) return } } } @SubscribeEvent fun openInventory(event: InventoryOpenEvent) { if (event.inventoryName != "Your Stats Breakdown") { inMiscStats = false return } val inventoryName = event.inventoryItems[4]?.name ?: "" if (inventoryName != "§dMisc Stats") return inMiscStats = true replaceSlot = findValidSlot(event.inventoryItems) val showAllStatsLore = event.inventoryItems[50]?.getLore() ?: listOf("") for (line in showAllStatsLore) { showAllStatsPattern.matchMatcher(line) { showAllStats = when (group("toggle")) { "Yes" -> true else -> false } } } return } @SubscribeEvent fun closeInventory(event: InventoryCloseEvent) { inMiscStats = false inCustomBreakdown = false } private fun findValidSlot(input: Map): Int? { for (slot in input.keys) { if (slot !in validItemSlots && slot < 44) continue val itemStack = input[slot] if (itemStack?.name == " ") { return slot } } return null } @SubscribeEvent fun onHoverItem(event: LorenzToolTipEvent) { if (!LorenzUtils.inSkyBlock) return if (skillCalcCoolDown.passedSince() > 3.seconds) { skillCalcCoolDown = SimpleTimeMark.now() calcSkillLuck() } val limboLuck = storage?.limbo?.userLuck?.round(1) ?: 0.0f when (event.slot.inventory.name) { "Your Equipment and Stats" -> equipmentMenuTooltip(event, limboLuck) "Your Stats Breakdown" -> statsBreakdownLoreTooltip(event, limboLuck) "SkyBlock Menu" -> skyblockMenuTooltip(event, limboLuck) } } private fun equipmentMenuTooltip(event: LorenzToolTipEvent, limboLuck: Float) { if (event.slot.slotIndex != 25) return if (limboLuck == 0.0f && !showAllStats) return val skillLuck = skillOverflowLuck.values.sum() val totalLuck = skillLuck + limboLuck val lastIndex = event.toolTip.indexOfLast { it == "§5§o" } if (lastIndex == -1) return val luckString = tryTruncateFloat(totalLuck) event.toolTip.add(lastIndex, "$luckTooltipString$luckString") } private fun statsBreakdownLoreTooltip(event: LorenzToolTipEvent, limboLuck: Float) { if (!inMiscStats) return if (inCustomBreakdown && event.slot.slotIndex == 48) { event.toolTip[1] = "§7To Your Stats Breakdown" } if (event.slot.slotIndex != 4) return if (limboLuck == 0.0f && !showAllStats) return val skillLuck = skillOverflowLuck.values.sum() val totalLuck = skillLuck + limboLuck val luckString = tryTruncateFloat(totalLuck) event.toolTip.add("§5§o §a✴ SkyHanni User Luck §f$luckString") } private fun skyblockMenuTooltip(event: LorenzToolTipEvent, limboLuck: Float) { if (event.slot.slotIndex != 13) return val lastIndex = event.toolTip.indexOfLast { it == "§5§o" } if (lastIndex == -1) return val skillLuck = skillOverflowLuck.values.sum() val totalLuck = skillLuck + limboLuck if (totalLuck == 0f) return val luckString = tryTruncateFloat(totalLuck) event.toolTip.add(lastIndex, "$luckTooltipString$luckString") } private fun tryTruncateFloat(input: Float): String { val string = input.addSeparators() return if (string.endsWith(".0")) return string.dropLast(2) else string } @SubscribeEvent fun onStackClick(event: GuiContainerEvent.SlotClickEvent) { if (!inMiscStats) return val limboUserLuck = storage?.limbo?.userLuck ?: 0.0f if (limboUserLuck == 0.0f && !showAllStats) return if (inCustomBreakdown && event.slotId != 49) event.cancel() when (event.slotId) { replaceSlot -> { if (inCustomBreakdown) return event.cancel() inCustomBreakdown = true } 48 -> { if (!inCustomBreakdown) return inCustomBreakdown = false } else -> return } } private fun createItems() { fillerItem = Utils.createItemStack( fillerID.getItemStack().item, fillerName, 15, ) val limboLuck = storage?.limbo?.userLuck ?: 0.0f val skillLuck = skillOverflowLuck.values.sum() val totalLuck = skillLuck + limboLuck mainLuckItem = Utils.createItemStack( mainLuckID.getItemStack().item, "$mainLuckName §f${tryTruncateFloat(totalLuck)}", *createItemLore("mainMenu", totalLuck), ) limboItem = Utils.createItemStack( limboID.getItemStack().item, limboName, *createItemLore("limbo", limboLuck), ) skillsItem = Utils.createItemStack( skillsID.getItemStack().item, skillsName, *createItemLore("skills"), ) } private fun createItemLore(type: String, luckInput: Float = 0.0f): Array { calcSkillLuck() return when (type) { "mainMenu" -> { val luckString = tryTruncateFloat(luckInput.round(2)) if (luckInput == 0.0f) { arrayOf( "§7SkyHanni User Luck is the best stat.", "", "§7Flat: §a+$luckString✴", "", "§8You have none of this stat!", "§eClick to view!", ) } else { arrayOf( "§7SkyHanni User Luck increases your", "§7overall fortune around Hypixel SkyBlock.", "", "§7(Disclaimer: May not affect real drop chances)", "", "§eClick to view!", ) } } "limbo" -> { val luckString = tryTruncateFloat(luckInput.round(2)) arrayOf( "§8Action", "", "§7Value: §a+$luckString✴", "", "§8Gain more by going to Limbo,", "§8and obtaining a higher Personal Best§8.", ) } "skills" -> { val luckString = skillOverflowLuck.values.sum() val firstHalf = arrayOf( "§8Grouped", "", "§7Value: §a+$luckString✴", "", ) val secondHalf = arrayOf( "§8Stats from your overflow skills.", "§8Obtain more each 5 overflow levels!", ) val sourcesList = mutableListOf() for ((skillType, luck) in skillOverflowLuck) { if (luck == 0) continue sourcesList.add(" §a+$luck✴ §f${skillType.displayName} Skill") } val finalList = mutableListOf() finalList.addAll(firstHalf) if (sourcesList.isNotEmpty()) { finalList.addAll(sourcesList) finalList.add("") } finalList.addAll(secondHalf) finalList.toTypedArray() } else -> arrayOf("") } } private fun calcSkillLuck() { val storage = ProfileStorageData.profileSpecific?.skillData ?: return skillOverflowLuck.clear() for ((skillType, skillInfo) in storage) { val level = skillInfo.level val overflow = skillInfo.overflowLevel val luck = ((overflow - level) / 5) * 50 skillOverflowLuck.addOrPut(skillType, luck) } } }