diff --git a/src/main/kotlin/com/odtheking/odin/features/ModuleManager.kt b/src/main/kotlin/com/odtheking/odin/features/ModuleManager.kt index c4130bb2..330ede9d 100644 --- a/src/main/kotlin/com/odtheking/odin/features/ModuleManager.kt +++ b/src/main/kotlin/com/odtheking/odin/features/ModuleManager.kt @@ -77,6 +77,7 @@ object ModuleManager { // nether SupplyHelper, BuildHelper, RemovePerks, NoPre, PearlWaypoints, FreshTools, KuudraInfo, Misc, Vesuvius, + KuudraTracker, RenderTest, ) diff --git a/src/main/kotlin/com/odtheking/odin/features/impl/dungeon/Croesus.kt b/src/main/kotlin/com/odtheking/odin/features/impl/dungeon/Croesus.kt index a9c18f06..448d05ef 100644 --- a/src/main/kotlin/com/odtheking/odin/features/impl/dungeon/Croesus.kt +++ b/src/main/kotlin/com/odtheking/odin/features/impl/dungeon/Croesus.kt @@ -22,7 +22,6 @@ import com.odtheking.odin.utils.render.textDim import com.odtheking.odin.utils.skyblock.dungeon.DungeonUtils import kotlinx.coroutines.launch import net.minecraft.client.gui.GuiGraphicsExtractor -import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen import net.minecraft.network.chat.Component import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket import net.minecraft.world.item.ItemStack @@ -140,11 +139,10 @@ object Croesus : Module( on { val screenTitle = mc.screen?.title?.string ?: return@on - val items = (mc.screen as? AbstractContainerScreen<*>)?.menu?.items ?: return@on when { - screenTitle.matches(chestNameRegex) -> handleChestContents(items) - screenTitle.matches(chestPreviewScreenRegex) -> handleCroesusScreen(items) + screenTitle.matches(chestNameRegex) -> handleChestContents(menu.items) + screenTitle.matches(chestPreviewScreenRegex) -> handleCroesusScreen(menu.items) } } diff --git a/src/main/kotlin/com/odtheking/odin/features/impl/nether/KuudraTracker.kt b/src/main/kotlin/com/odtheking/odin/features/impl/nether/KuudraTracker.kt new file mode 100644 index 00000000..a010f6b8 --- /dev/null +++ b/src/main/kotlin/com/odtheking/odin/features/impl/nether/KuudraTracker.kt @@ -0,0 +1,272 @@ +package com.odtheking.odin.features.impl.nether + +import com.odtheking.odin.clickgui.settings.impl.ActionSetting +import com.odtheking.odin.clickgui.settings.impl.ListSetting +import com.odtheking.odin.clickgui.settings.impl.MapSetting +import com.odtheking.odin.clickgui.settings.impl.NumberSetting +import com.odtheking.odin.events.GuiEvent +import com.odtheking.odin.events.MessageEvent +import com.odtheking.odin.events.core.on +import com.odtheking.odin.events.core.onSend +import com.odtheking.odin.features.Module +import com.odtheking.odin.features.ModuleManager +import com.odtheking.odin.features.impl.nether.Vesuvius.getPriceOfKey +import com.odtheking.odin.features.impl.nether.Vesuvius.keys +import com.odtheking.odin.utils.Colors +import com.odtheking.odin.utils.equalsOneOf +import com.odtheking.odin.utils.lore +import com.odtheking.odin.utils.render.text +import net.minecraft.ChatFormatting +import net.minecraft.client.gui.GuiGraphicsExtractor +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.MutableComponent +import net.minecraft.network.protocol.game.ServerboundContainerClickPacket +import net.minecraft.world.item.Items + +object KuudraTracker : Module( + name = "Kuudra Tracker", + description = "Tracks your Kuudra runs." +) { + private val lineAmount by NumberSetting("Lines", 10, 0,40, 1, "The amount of items to display in the profit tracker.") + private val reset by ActionSetting("Reset Profit", "Resets the Profit Tracker") { + totalKeys = mutableListOf(0, 0, 0, 0, 0) + singleItems.clear() + multiItems.clear() + paid = 0 + free = 0 + last = LastAdded(mutableListOf(), mutableListOf(), 0) + + updateDisplay() + ModuleManager.saveConfigurations() + } + + private val chestRegex = Regex("(Free|Paid) Chest") + private val hudRegex = Regex("^((Free|Paid) Chest)|(Kuudra - .+)|(.+)?Vesuvius$") + private val uselessLinesRegex = Regex("^Contents|Cost|Click to open!|FREE|Already opened!|Can't open another chest!|Paid Chest$") + private val amountRegex = Regex("(.+)? x(\\d+)$") + + private val lightPurpleRegex = Regex("Fatal Tempo|Inferno|Crimson Essence") + private val whiteRegex = Regex("Book") + private val darkPurpleRegex = Regex("Kuudra Teeth|Fire Eel Shard|Kuudra Mandible|Barbarian Duke X Shard|XYZ Shard|Hellstorm Wand|Tentacle Dye") + private val blueRegex = Regex("Kada Knight Shard|Wheel of Fate|Ananke Feather|Burning Kuudra Core|Matcho Shard|Kuudra Tentacle|Lava Flame Shard|Wither Spectre Shard") + private val goldRegex = Regex("Boots|Leggings|Chestplate|Helmet|Cloak|Aurora Staff|Hollow Wand|Heavy Pearl|Kraken Shard|Hellwisp Shard|Ananke Shard|Moltenfish Shard|Tormentor") + + val profitHud by HUD("Kuudra Tracker HUD", "Displays the profit of all your Kudura chests.") { + if (!it) return@HUD 0 to 0 + drawOverlay(true) + } + + private var singleItems by MapSetting("Single Items",mutableMapOf()) + private var multiItems by MapSetting("Multi Items", mutableMapOf()) + + private var paid by NumberSetting("Paid Chests", 0, 0, Int.MAX_VALUE, 1, "Amount of Paid Chests opened.").hide() + private var free by NumberSetting("Free Chests", 0, 0, Int.MAX_VALUE, 1, "Amount of Paid Chests opened").hide() + + private var toDisplay = mutableListOf>() + private var totalKeys by ListSetting("Total Keys", mutableListOf(0, 0, 0, 0, 0)).hide() //Tier 1-5 + + private var last = LastAdded(mutableListOf(), mutableListOf(), 0) + + init { + updateDisplay() + + on { + val title = screen.title.string + if (enabled && title.matches(hudRegex)) { + guiGraphics.pose().pushMatrix() + val sf = mc.window.guiScale + guiGraphics.pose().scale(1f / sf, 1f / sf) + guiGraphics.pose().translate(profitHud.x.toFloat(), profitHud.y.toFloat()) + guiGraphics.pose().scale(profitHud.scale) + + guiGraphics.drawOverlay(false) + + guiGraphics.pose().popMatrix() + } + } + + on { + val title = mc.screen?.title?.string ?: return@on + if (message.equalsOneOf("You cannot afford this!", "Whoa! Slow down there!") && title.matches(chestRegex)) { + for (single in last.single) { + singleItems[single] = (singleItems.getOrDefault(single, 0) - 1).coerceAtLeast(0) + } + for ((itemComponent, amount) in last.multi) { + multiItems[itemComponent] = (multiItems.getOrDefault(itemComponent, 0) - amount).coerceAtLeast(0) + } + totalKeys[last.key - 1]-- + paid-- + + updateDisplay() + ModuleManager.saveConfigurations() + } + } + + onSend { + val title = mc.screen?.title?.string ?: return@onSend + if (!title.matches(chestRegex)) return@onSend + + val cursorStack = mc.player?.containerMenu?.carried ?: return@onSend + + if (!cursorStack.`is`(Items.CHEST)) return@onSend + + val loreLines = cursorStack.lore + + updateDisplay() + + if (loreLines.any { it.string.equalsOneOf("Already opened!", "Can't open another chest!")}) return@onSend + + last = LastAdded(mutableListOf(), mutableListOf(), 0) + + for (lore in loreLines) { + if (lore.string.matches(uselessLinesRegex)) continue + + if (lore.string.contains("Kuudra Key")) { + keys.find { it.type == lore.string }?.let { key -> + totalKeys[key.tier - 1]++ + last.key = key.tier + continue + } + } + + amountRegex.find(lore.string)?.destructured?.let { (name, amount) -> + multiItems[name] = multiItems.getOrDefault(name, 0) + (amount.toIntOrNull() ?: 0) + continue + } + + singleItems[lore.string] = singleItems.getOrDefault(lore.string, 0) + 1 + } + + if (title == "Paid Chest") paid++ + if (title == "Free Chest") free++ + + updateDisplay() + ModuleManager.saveConfigurations() + } + } + + private fun updateDisplay() { + toDisplay.clear() + + singleItems.forEach { (name, amount) -> + if (amount == 0) return@forEach + + val price = Vesuvius.parseItemValue(Component.literal(name))?.times(amount) ?: return@forEach + + toDisplay.add(Component.literal(name).withStyle(getColor(name)).append(Component.literal(" x$amount").withStyle(ChatFormatting.DARK_GRAY)) to price) + } + + multiItems.forEach { (name, amount) -> + if (amount == 0) return@forEach + + val built = Component.literal(name).append(Component.literal(" x$amount").withStyle(ChatFormatting.DARK_GRAY)) + + val price = Vesuvius.parseItemValue(built) ?: return@forEach + + toDisplay.add(built.withStyle(getColor(name)) to price) + } + } + + private fun getKeyPrices(): Double { + return getPriceOfKey("Kuudra Key") * totalKeys[0] + + getPriceOfKey("Hot Kuudra Key") * totalKeys[1] + + getPriceOfKey("Burning Kuudra Key") * totalKeys[2] + + getPriceOfKey("Fiery Kuudra Key") * totalKeys[3] + + getPriceOfKey("Infernal Kuudra Key") * totalKeys[4] + } + + private fun GuiGraphicsExtractor.drawOverlay(isEditing: Boolean): Pair { + var yOffset = 0 + val maxWidth = 300 + + var profit = 0.0 + + val display = if (isEditing) { sampleProfitData } else toDisplay + + for ((index, pair) in display + .sortedByDescending { it.second } + .withIndex() + ) { + val price = "%,.0f".format(pair.second) + + profit += pair.second + + if (index >= lineAmount) continue + + text(mc.font, pair.first, 0, yOffset, -1) + text(mc.font, price, maxWidth - mc.font.width(price), yOffset, Colors.MINECRAFT_DARK_GRAY.rgba) + + yOffset += 9 + } + + yOffset += 6 + + val keyPrices = if (isEditing) getPriceOfKey("Infernal Kuudra Key").toString() else "%,.0f".format(getKeyPrices()) + + text(mc.font, "§cTotal Key Costs:", 0, yOffset, -1) + text(mc.font, keyPrices, maxWidth - mc.font.width(keyPrices), yOffset, Colors.MINECRAFT_RED.rgba) + + yOffset += 12 + + val profitStr = "%,.0f".format(profit - getKeyPrices()) + + text("§aProfit:", 0, yOffset) + text(profitStr, maxWidth - mc.font.width(profitStr), yOffset, Colors.MINECRAFT_GREEN) + + yOffset += 15 + + val freeChests = if (isEditing) 0 else free + val paidChests = if (isEditing) 1 else paid + + val avg = "%,.0f".format((profit - getKeyPrices()) / (freeChests + paidChests)) + + text(mc.font, "Total Chests: Free [$freeChests], Paid [$paidChests]", 0, yOffset, Colors.MINECRAFT_YELLOW.rgba) + text(mc.font, "Avg [$avg]", maxWidth - mc.font.width("Avg [$avg]"), yOffset, Colors.MINECRAFT_GOLD.rgba) + + yOffset += 9 + + return maxWidth to yOffset + } + + private fun getColor(name: String): ChatFormatting { + if (name.contains(whiteRegex)) return ChatFormatting.WHITE + if (name.contains(goldRegex)) return ChatFormatting.GOLD + if (name.contains(darkPurpleRegex)) return ChatFormatting.DARK_PURPLE + if (name.contains(lightPurpleRegex)) return ChatFormatting.LIGHT_PURPLE + if (name.contains(blueRegex)) return ChatFormatting.BLUE + + return ChatFormatting.WHITE + } + + data class LastAdded(var single: MutableList, var multi: MutableList, var key: Int) + data class Multi(val component: String, val amount: Int) + + private val sampleProfitData = listOf( + Pair( + Component.literal("Fervor Helmet") + .withStyle(ChatFormatting.GOLD), + 748000.0 + ), + Pair( + Component.literal("Enchanted Book (").withStyle(ChatFormatting.WHITE) + .append(Component.literal("Ferocious Mana V").withStyle(ChatFormatting.LIGHT_PURPLE)) + .append(Component.literal(")").withStyle(ChatFormatting.WHITE)), + 118500000.0 + ), + Pair( + Component.literal("Crimson Essence").withStyle(ChatFormatting.LIGHT_PURPLE) + .append(Component.literal(" x2000").withStyle(ChatFormatting.DARK_GRAY)), + 2420000.0 + ), + Pair( + Component.literal("Kuudra Teeth").withStyle(ChatFormatting.DARK_PURPLE) + .append(Component.literal(" x4").withStyle(ChatFormatting.DARK_GRAY)), + 35480.0 + ), + Pair( + Component.literal("Kraken Shard").withStyle(ChatFormatting.GOLD) + .append(Component.literal(" x2").withStyle(ChatFormatting.DARK_GRAY)), + 821226.0 + ) + ) +} diff --git a/src/main/kotlin/com/odtheking/odin/features/impl/nether/Vesuvius.kt b/src/main/kotlin/com/odtheking/odin/features/impl/nether/Vesuvius.kt index 153d3e0a..172cbcae 100644 --- a/src/main/kotlin/com/odtheking/odin/features/impl/nether/Vesuvius.kt +++ b/src/main/kotlin/com/odtheking/odin/features/impl/nether/Vesuvius.kt @@ -41,13 +41,13 @@ object Vesuvius : Module( private val pearlRegex = Regex("^Heavy Pearl x(\\d+)$") private val chestRegex = Regex("^((Free|Paid) Chest)|(Kuudra - .+)$") private val uselessLinesRegex = Regex("^Contents|Cost|Click to open!|FREE|Already opened!|Can't open another chest!|Paid Chest|") - private val salvageItemsRegex = Regex("^Boots|Chestplate|Helmet|Cloak|Aurora Staff|Hollow Wand") + private val salvageItemsRegex = Regex("Boots|Leggings|Chestplate|Helmet|Cloak|Aurora Staff|Hollow Wand") private val ultimateEnchants = setOf( "Fatal Tempo", "Inferno" ) - private data class Key(val type: String, val coins: Int, val quantity: Int) + data class Key(val type: String, val coins: Int, val quantity: Int, val tier: Int) private data class ChestItem(val name: Component, val price: Double) private data class ChestData(val items: List, val cost: Double, val profit: Double) @@ -87,8 +87,7 @@ object Vesuvius : Module( } } - private fun parseItemValue(component: Component): Double? { - + fun parseItemValue(component: Component): Double? { var starCount = 0 val salvage = salvageItemsRegex.containsMatchIn(component.string) @@ -139,6 +138,7 @@ object Vesuvius : Module( } if (useSalvagePrices && salvage) { + modMessage("$component}") val price = cachedPrices["ESSENCE_CRIMSON"] ?: 0.0 val quantity = starCountToEssence[starCount] ?: 0.0 return price * quantity @@ -149,7 +149,7 @@ object Vesuvius : Module( return cachedPrices[item.uppercase().replace(" ", "_")] } - private fun getPriceOfKey(key: String): Double { + fun getPriceOfKey(key: String): Double { keys.find { it.type == key }?.let { val material = minOf(cachedPrices["ENCHANTED_RED_SAND"] ?: 0.0, cachedPrices["ENCHANTED_MYCELIUM"] ?: 0.0) val star = (cachedPrices["CORRUPTED_NETHER_STAR"] ?: 0.0) @@ -211,12 +211,12 @@ object Vesuvius : Module( return maxWidth to yOffset } - private val keys = listOf( - Key("Kuudra Key", 155200, 2), - Key("Hot Kuudra Key", 310400, 4), - Key("Burning Kuudra Key", 582000, 16), - Key("Fiery Kuudra Key", 1164000, 40), - Key("Infernal Kuudra Key", 2328000, 80) + val keys = listOf( + Key("Kuudra Key", 155200, 2, 1), + Key("Hot Kuudra Key", 310400, 4, 2), + Key("Burning Kuudra Key", 582000, 16, 3), + Key("Fiery Kuudra Key", 1164000, 40, 4), + Key("Infernal Kuudra Key", 2328000, 80, 5) ) private val itemReplacements = mapOf(