diff options
12 files changed, 843 insertions, 63 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/api/ReforgeAPI.kt b/src/main/java/at/hannibal2/skyhanni/api/ReforgeAPI.kt new file mode 100644 index 000000000..70f6910c9 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/api/ReforgeAPI.kt @@ -0,0 +1,198 @@ +package at.hannibal2.skyhanni.api + +import at.hannibal2.skyhanni.data.jsonobjects.repo.neu.NeuReforgeJson +import at.hannibal2.skyhanni.data.model.SkyblockStat +import at.hannibal2.skyhanni.data.model.SkyblockStatList +import at.hannibal2.skyhanni.events.NeuRepositoryReloadEvent +import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule +import at.hannibal2.skyhanni.utils.ItemCategory +import at.hannibal2.skyhanni.utils.ItemUtils.getInternalName +import at.hannibal2.skyhanni.utils.ItemUtils.getItemCategoryOrNull +import at.hannibal2.skyhanni.utils.ItemUtils.itemNameWithoutColor +import at.hannibal2.skyhanni.utils.LorenzRarity +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.NEUInternalName +import at.hannibal2.skyhanni.utils.json.BaseGsonBuilder +import at.hannibal2.skyhanni.utils.json.SkyHanniTypeAdapters +import com.google.gson.Gson +import com.google.gson.TypeAdapter +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonWriter +import net.minecraft.item.ItemStack +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +@SkyHanniModule +object ReforgeAPI { + var reforgeList: List<Reforge> = emptyList() + private set(value) { + field = value + nonePowerStoneReforge = value.filterNot { it.isReforgeStone } + onlyPowerStoneReforge = value.filter { it.isReforgeStone } + } + + var nonePowerStoneReforge: List<Reforge> = emptyList() + private set + + var onlyPowerStoneReforge: List<Reforge> = emptyList() + private set + + enum class ReforgeType { + SWORD, + BOW, + ARMOR, + CHESTPLATE, + HELMET, + CLOAK, + AXE, + HOE, + AXE_AND_HOE, + PICKAXE, + EQUIPMENT, + ROD, + SWORD_AND_ROD, + SPECIAL_ITEMS, + VACUUM + } + + class Reforge( + val name: String, + val type: ReforgeType, + val stats: Map<LorenzRarity, SkyblockStatList>, + val reforgeStone: NEUInternalName? = null, + val specialItems: List<NEUInternalName>? = null, + val extraProperty: Map<LorenzRarity, String> = emptyMap(), + val costs: Map<LorenzRarity, Long>? = null, + ) { + + val isReforgeStone = reforgeStone != null + + val rawReforgeStoneName = reforgeStone?.itemNameWithoutColor + + val lowercaseName = name.lowercase() + + fun isValid(itemStack: ItemStack) = isValid(itemStack.getItemCategoryOrNull(), itemStack.getInternalName()) + + fun isValid(itemCategory: ItemCategory?, internalName: NEUInternalName) = when (type) { + ReforgeType.SWORD -> setOf( + ItemCategory.SWORD, + ItemCategory.GAUNTLET, + ItemCategory.LONGSWORD, + ItemCategory.FISHING_WEAPON, + ).contains(itemCategory) + + ReforgeType.BOW -> itemCategory == ItemCategory.BOW || itemCategory == ItemCategory.SHORT_BOW + ReforgeType.ARMOR -> setOf( + ItemCategory.HELMET, + ItemCategory.CHESTPLATE, + ItemCategory.LEGGINGS, + ItemCategory.BOOTS, + ).contains(itemCategory) + + ReforgeType.CHESTPLATE -> itemCategory == ItemCategory.CHESTPLATE + ReforgeType.HELMET -> itemCategory == ItemCategory.HELMET + ReforgeType.CLOAK -> itemCategory == ItemCategory.CLOAK + ReforgeType.AXE -> itemCategory == ItemCategory.AXE + ReforgeType.HOE -> itemCategory == ItemCategory.HOE + ReforgeType.AXE_AND_HOE -> itemCategory == ItemCategory.HOE || itemCategory == ItemCategory.AXE + ReforgeType.PICKAXE -> + itemCategory == ItemCategory.PICKAXE || + itemCategory == ItemCategory.DRILL || + itemCategory == ItemCategory.GAUNTLET + + ReforgeType.EQUIPMENT -> setOf( + ItemCategory.CLOAK, + ItemCategory.BELT, + ItemCategory.NECKLACE, + ItemCategory.BRACELET, + ItemCategory.GLOVES, + ).contains(itemCategory) + + ReforgeType.ROD -> itemCategory == ItemCategory.FISHING_ROD || itemCategory == ItemCategory.FISHING_WEAPON + ReforgeType.SWORD_AND_ROD -> setOf( + ItemCategory.SWORD, + ItemCategory.GAUNTLET, + ItemCategory.LONGSWORD, + ItemCategory.FISHING_ROD, + ItemCategory.FISHING_WEAPON, + ).contains(itemCategory) + + ReforgeType.VACUUM -> itemCategory == ItemCategory.VACUUM + ReforgeType.SPECIAL_ITEMS -> specialItems?.contains(internalName) ?: false + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Reforge + + if (name != other.name) return false + if (type != other.type) return false + if (stats != other.stats) return false + if (reforgeStone != other.reforgeStone) return false + if (specialItems != other.specialItems) return false + if (extraProperty != other.extraProperty) return false + + return true + } + + override fun hashCode(): Int { + var result = name.hashCode() + result = 31 * result + type.hashCode() + result = 31 * result + stats.hashCode() + result = 31 * result + (reforgeStone?.hashCode() ?: 0) + result = 31 * result + (specialItems?.hashCode() ?: 0) + result = 31 * result + extraProperty.hashCode() + return result + } + + override fun toString(): String = "Reforge $name" + } + + @SubscribeEvent + fun onNeuRepoReload(event: NeuRepositoryReloadEvent) { + val reforgeStoneData = event.readConstant<Map<String, NeuReforgeJson>>("reforgestones", reforgeGson).values + val reforgeData = event.readConstant<Map<String, NeuReforgeJson>>("reforges", reforgeGson).values + reforgeList = (reforgeStoneData + reforgeData).map(::mapReforge) + } + + private val reforgeGson: Gson = BaseGsonBuilder.gson() + .registerTypeAdapter(SkyblockStat::class.java, SkyHanniTypeAdapters.SKYBLOCK_STAT.nullSafe()) + .registerTypeAdapter( + SkyblockStatList::class.java, + object : TypeAdapter<SkyblockStatList>() { + override fun write(out: JsonWriter, value: SkyblockStatList) { + out.beginObject() + value.entries.forEach { + out.name(it.key.name.lowercase()).value(it.value) + } + out.endObject() + } + + override fun read(reader: JsonReader): SkyblockStatList { + reader.beginObject() + val list = SkyblockStatList() + while (reader.hasNext()) { + val name = reader.nextName() + val value = reader.nextDouble() + list[SkyblockStat.valueOf(name.uppercase())] = value + } + reader.endObject() + return list + } + }, + ).create() + + private fun mapReforge(it: NeuReforgeJson): Reforge { + val type = it.itemType + return Reforge( + name = it.reforgeName, + type = LorenzUtils.enumValueOf<ReforgeType>(type.first), + stats = it.reforgeStats ?: emptyMap(), + reforgeStone = it.internalName, + specialItems = type.second.takeIf { it.isNotEmpty() }, + extraProperty = it.reforgeAbility, + costs = it.reforgeCosts, + ) + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/inventory/helper/HelperConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/inventory/helper/HelperConfig.java index 990399d62..f6818d2c8 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/inventory/helper/HelperConfig.java +++ b/src/main/java/at/hannibal2/skyhanni/config/features/inventory/helper/HelperConfig.java @@ -53,6 +53,11 @@ public class HelperConfig { public TiaRelayConfig tiaRelay = new TiaRelayConfig(); @Expose + @ConfigOption(name = "Reforge Helper", desc = "") + @Accordion + public ReforgeHelperConfig reforge = new ReforgeHelperConfig(); + + @Expose @ConfigOption(name = "Enchanting", desc = "") @Accordion public EnchantingConfig enchanting = new EnchantingConfig(); diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/inventory/helper/ReforgeHelperConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/inventory/helper/ReforgeHelperConfig.java new file mode 100644 index 000000000..43bba7f8c --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/inventory/helper/ReforgeHelperConfig.java @@ -0,0 +1,36 @@ +package at.hannibal2.skyhanni.config.features.inventory.helper; + +import at.hannibal2.skyhanni.config.FeatureToggle; +import at.hannibal2.skyhanni.config.core.config.Position; +import com.google.gson.annotations.Expose; +import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorBoolean; +import io.github.notenoughupdates.moulconfig.annotations.ConfigLink; +import io.github.notenoughupdates.moulconfig.annotations.ConfigOption; + +public class ReforgeHelperConfig { + + @Expose + @ConfigLink(owner = ReforgeHelperConfig.class, field = "enabled") + public Position position = new Position(80, 85, true, true); + + @Expose + @ConfigOption(name = "Enable", desc = "Enables the reforge helper.") + @ConfigEditorBoolean + @FeatureToggle + public boolean enabled = true; + + @Expose + @ConfigOption(name = "Stones Hex Only", desc = "Displays reforge stones only when in Hex.") + @ConfigEditorBoolean + public boolean reforgeStonesOnlyHex = true; + + @Expose + @ConfigOption(name = "Show Diff", desc = "Shows the difference of the new reforge to the current one in the slecetion list.") + @ConfigEditorBoolean + public boolean showDiff = false; + + @Expose + @ConfigOption(name = "Hide chat", desc = "Hides the vanilla chat messages from reforging.") + @ConfigEditorBoolean + public boolean hideChat = false; +} diff --git a/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/neu/NeuReforgeStoneJson.kt b/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/neu/NeuReforgeJson.kt index 8ebe2dea6..a8c8f3fe4 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/neu/NeuReforgeStoneJson.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/neu/NeuReforgeJson.kt @@ -1,21 +1,26 @@ package at.hannibal2.skyhanni.data.jsonobjects.repo.neu +import at.hannibal2.skyhanni.data.model.SkyblockStatList import at.hannibal2.skyhanni.utils.LorenzRarity import at.hannibal2.skyhanni.utils.NEUInternalName +import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName +import at.hannibal2.skyhanni.utils.NEUItems import com.google.gson.annotations.Expose import com.google.gson.annotations.SerializedName +import net.minecraft.item.Item -data class NeuReforgeStoneJson( - @Expose val internalName: NEUInternalName, +data class NeuReforgeJson( + @Expose val internalName: NEUInternalName?, @Expose val reforgeName: String, @Expose @SerializedName("itemTypes") val rawItemTypes: Any, @Expose val requiredRarities: List<LorenzRarity>, - @Expose val reforgeCosts: Map<LorenzRarity, Long>, - @Expose val reforgeStats: Map<LorenzRarity, Map<String, Double>>, + @Expose val reforgeCosts: Map<LorenzRarity, Long>?, + @Expose val reforgeStats: Map<LorenzRarity, SkyblockStatList>?, @Expose @SerializedName("reforgeAbility") val rawReforgeAbility: Any?, ) { private lateinit var reforgeAbilityField: Map<LorenzRarity, String> + private lateinit var itemTypeField: Pair<String, List<NEUInternalName>> val reforgeAbility get() = if (this::reforgeAbilityField.isInitialized) reforgeAbilityField @@ -27,7 +32,7 @@ data class NeuReforgeStoneJson( is Map<*, *> -> (this.rawReforgeAbility as? Map<String, String>)?.mapKeys { LorenzRarity.valueOf( - it.key.uppercase().replace(" ", "_") + it.key.uppercase().replace(" ", "_"), ) } ?: emptyMap() @@ -36,28 +41,27 @@ data class NeuReforgeStoneJson( reforgeAbilityField } - /* used in ReforgeAPI which isn't in beta yet - val itemType: Pair<String, List<NEUInternalName>> by lazy { + val itemType: Pair<String, List<NEUInternalName>> + get() = if (this::itemTypeField.isInitialized) itemTypeField + else run { val any = this.rawItemTypes - return@lazy when (any) { + return when (any) { is String -> { any.replace("/", "_AND_").uppercase() to emptyList() } is Map<*, *> -> { val type = "SPECIAL_ITEMS" - val map = any as? Map<String, List<String>> ?: return@lazy type to emptyList() + val map = any as? Map<String, List<String>> ?: return type to emptyList() val internalNames = map["internalName"]?.map { it.asInternalName() } ?: emptyList() val itemType = map["itemid"]?.map { NEUItems.getInternalNamesForItemId(Item.getByNameOrId(it)) - }?.flatten() - ?: emptyList() + }?.flatten() ?: emptyList() type to (internalNames + itemType) } else -> throw IllegalStateException() } } -*/ } diff --git a/src/main/java/at/hannibal2/skyhanni/data/model/SkyblockStat.kt b/src/main/java/at/hannibal2/skyhanni/data/model/SkyblockStat.kt new file mode 100644 index 000000000..161ec76f5 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/data/model/SkyblockStat.kt @@ -0,0 +1,80 @@ +package at.hannibal2.skyhanni.data.model + +import at.hannibal2.skyhanni.utils.StringUtils.allLettersFirstUppercase +import net.minecraft.client.Minecraft +import java.util.EnumMap + +enum class SkyblockStat(val icon: String) { + DAMAGE("§c❁"), + HEALTH("§c❤"), + DEFENSE("§a❈"), + STRENGTH("§c❁"), + INTELLIGENCE("§b✎"), + CRIT_DAMAGE("§9☠"), + CRIT_CHANCE("§9☣"), + FEROCITY("§c⫽"), + BONUS_ATTACK_SPEED("§e⚔"), + ABILITY_DAMAGE("§c๑"), + HEALTH_REGEN("§c❣"), + VITALITY("§4♨"), + MENDING("§a☄"), + TRUE_DEFENCE("§7❂"), + SWING_RANGE("§eⓈ"), + SPEED("§f✦"), + SEA_CREATURE_CHANCE("§3α"), + MAGIC_FIND("§b✯"), + PET_LUCK("§d♣"), + FISHING_SPEED("§b☂"), + BONUS_PEST_CHANCE("§2ൠ"), + COMBAT_WISDOM("§3☯"), + MINING_WISDOM("§3☯"), + FARMING_WISDOM("§3☯"), + FORAGING_WISDOM("§3☯"), + FISHING_WISDOM("§3☯"), + ENCHANTING_WISDOM("§3☯"), + ALCHEMY_WISDOM("§3☯"), + CARPENTRY_WISDOM("§3☯"), + RUNECRAFTING_WISDOM("§3☯"), + SOCIAL_WISDOM("§3☯"), + TAMING_WISDOM("§3☯"), + MINING_SPEED("§6⸕"), + BREAKING_POWER("§2Ⓟ"), + PRISTINE("§5✧"), + FORAGING_FORTUNE("§☘"), + FARMING_FORTUNE("§6☘"), + MINING_FORTUNE("§6☘"), + FEAR("§a☠") + ; + + val capitalizedName = name.lowercase().allLettersFirstUppercase() + + val iconWithName = "$icon $capitalizedName" + + fun asString(value: Int) = (if (value > 0) "+" else "") + value.toString() + " " + this.icon + + companion object { + val fontSizeOfLargestIcon by lazy { + entries.maxOf { Minecraft.getMinecraft().fontRendererObj.getStringWidth(it.icon) } + 1 + } + } +} + +class SkyblockStatList : EnumMap<SkyblockStat, Double>(SkyblockStat::class.java), Map<SkyblockStat, Double> { + operator fun minus(other: SkyblockStatList): SkyblockStatList { + return SkyblockStatList().apply { + val keys = this.keys + other.keys + for (key in keys) { + this[key] = (this@SkyblockStatList[key] ?: 0.0) - (other[key] ?: 0.0) + } + } + } + + companion object { + fun mapOf(vararg list: Pair<SkyblockStat, Double>) = SkyblockStatList().apply { + for ((key, value) in list) { + this[key] = value + } + } + } +} + diff --git a/src/main/java/at/hannibal2/skyhanni/events/NeuRepositoryReloadEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/NeuRepositoryReloadEvent.kt index a02cb308f..6f7df5964 100644 --- a/src/main/java/at/hannibal2/skyhanni/events/NeuRepositoryReloadEvent.kt +++ b/src/main/java/at/hannibal2/skyhanni/events/NeuRepositoryReloadEvent.kt @@ -4,6 +4,7 @@ import at.hannibal2.skyhanni.config.ConfigManager import at.hannibal2.skyhanni.test.command.ErrorManager import at.hannibal2.skyhanni.utils.NEUItems.manager import at.hannibal2.skyhanni.utils.json.fromJson +import com.google.gson.Gson import com.google.gson.JsonObject import com.google.gson.JsonSyntaxException import java.io.File @@ -13,14 +14,14 @@ class NeuRepositoryReloadEvent : LorenzEvent() { return manager.getJsonFromFile(File(manager.repoLocation, "constants/$file.json")) } - inline fun <reified T : Any> readConstant(file: String): T { + inline fun <reified T : Any> readConstant(file: String, gson: Gson = ConfigManager.gson): T { val data = getConstant(file) ?: ErrorManager.skyHanniError("$file failed to load from neu repo!") return try { - ConfigManager.gson.fromJson<T>(data) + gson.fromJson<T>(data) } catch (e: JsonSyntaxException) { ErrorManager.logErrorWithData( e, "$file failed to read from neu repo!", - "data" to data + "data" to data, ) throw e } diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/ReforgeHelper.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/ReforgeHelper.kt new file mode 100644 index 000000000..d1826b481 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/ReforgeHelper.kt @@ -0,0 +1,432 @@ +package at.hannibal2.skyhanni.features.inventory + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.api.ReforgeAPI +import at.hannibal2.skyhanni.data.model.SkyblockStat +import at.hannibal2.skyhanni.data.model.SkyblockStatList +import at.hannibal2.skyhanni.events.GuiContainerEvent +import at.hannibal2.skyhanni.events.GuiRenderEvent +import at.hannibal2.skyhanni.events.InventoryCloseEvent +import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule +import at.hannibal2.skyhanni.utils.DelayedRun +import at.hannibal2.skyhanni.utils.ItemUtils.cleanName +import at.hannibal2.skyhanni.utils.ItemUtils.getInternalName +import at.hannibal2.skyhanni.utils.ItemUtils.getItemCategoryOrNull +import at.hannibal2.skyhanni.utils.ItemUtils.getItemRarityOrNull +import at.hannibal2.skyhanni.utils.ItemUtils.name +import at.hannibal2.skyhanni.utils.LorenzColor +import at.hannibal2.skyhanni.utils.LorenzRarity +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.NumberUtil.toStringWithPlus +import at.hannibal2.skyhanni.utils.RegexUtils.matches +import at.hannibal2.skyhanni.utils.RenderUtils +import at.hannibal2.skyhanni.utils.RenderUtils.drawSlotText +import at.hannibal2.skyhanni.utils.RenderUtils.highlight +import at.hannibal2.skyhanni.utils.RenderUtils.renderRenderables +import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.getReforgeName +import at.hannibal2.skyhanni.utils.SoundUtils +import at.hannibal2.skyhanni.utils.TimeUtils.ticks +import at.hannibal2.skyhanni.utils.renderables.Renderable +import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern +import net.minecraft.client.Minecraft +import net.minecraft.init.Items +import net.minecraft.inventory.Container +import net.minecraft.item.ItemStack +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import java.awt.Color +import java.util.concurrent.atomic.AtomicBoolean +import at.hannibal2.skyhanni.utils.renderables.Renderable.Companion.string as renderableString + +@SkyHanniModule +object ReforgeHelper { + + private val config get() = SkyHanniMod.feature.inventory.helper.reforge + + private val repoGroup = RepoPattern.group("reforge") + + private val reforgeMenu by repoGroup.pattern( + "menu.blacksmith", + "Reforge Item", + ) + private val reforgeHexMenu by repoGroup.pattern( + "menu.hex", + "The Hex ➜ Reforges", + ) + private val reforgeChatMessage by repoGroup.pattern( + "chat.success", + "§aYou reforged your .* §r§ainto a .*!|§aYou applied a .* §r§ato your .*!", + ) + private val reforgeChatFail by repoGroup.pattern( + "chat.fail", + "§cWait a moment before reforging again!|§cWhoa! Slow down there!", + ) + + private var isInReforgeMenu = false + private var isInHexReforgeMenu = false + + private fun isReforgeMenu(chestName: String) = reforgeMenu.matches(chestName) + private fun isHexReforgeMenu(chestName: String) = reforgeHexMenu.matches(chestName) + + private fun isEnabled() = LorenzUtils.inSkyBlock && config.enabled && isInReforgeMenu + + private var itemToReforge: ItemStack? = null + private var inventoryContainer: Container? = null + + private var currentReforge: ReforgeAPI.Reforge? = null + private var reforgeToSearch: ReforgeAPI.Reforge? = null + + private var hoveredReforge: ReforgeAPI.Reforge? = null + + private val reforgeItem get() = if (isInHexReforgeMenu) 19 else 13 + private val reforgeButton get() = if (isInHexReforgeMenu) 48 else 22 + + private const val HEX_REFORGE_NEXT_DOWN_BUTTON = 35 + private const val HEX_REFORGE_NEXT_UP_BUTTON = 17 + + private const val EXIT_BUTTON = 40 + + private var waitForChat = AtomicBoolean(false) + + /** Gatekeeps instant double switches of the state */ + private var waitDelay = false + + private var sortAfter: SkyblockStat? = null + + private var display: List<Renderable> = generateDisplay() + + private val hoverColor = LorenzColor.GOLD.addOpacity(50) + private val selectedColor = LorenzColor.BLUE.addOpacity(100) + private val finishedColor = LorenzColor.GREEN.addOpacity(75) + + private fun itemUpdate() { + val newItem = inventoryContainer?.getSlot(reforgeItem)?.stack + if (newItem?.getInternalName() != itemToReforge?.getInternalName()) { + reforgeToSearch = null + } + itemToReforge = newItem + val newReforgeName = itemToReforge?.getReforgeName() ?: "" + if (newReforgeName == currentReforge?.lowercaseName) return + currentReforge = ReforgeAPI.reforgeList.firstOrNull { it.lowercaseName == newReforgeName } + updateDisplay() + } + + @SubscribeEvent + fun onClick(event: GuiContainerEvent.SlotClickEvent) { + if (!isEnabled()) return + if (event.slot?.slotNumber == reforgeButton) { + if (event.slot.stack?.name == "§eReforge Item" || event.slot.stack?.name == "§cError!") return + if (handleReforgeButtonClick(event)) return + } + + DelayedRun.runNextTick { + itemUpdate() + } + } + + private fun handleReforgeButtonClick(event: GuiContainerEvent.SlotClickEvent): Boolean { + if (currentReforge == reforgeToSearch) { + event.cancel() + waitForChat.set(false) + SoundUtils.playBeepSound() + } else if (waitForChat.get()) { + waitDelay = true + event.cancel() + } else { + if (event.clickedButton == 2) return true + if (waitDelay) { + waitDelay = false + } else { + waitForChat.set(true) + } + } + return false + } + + @SubscribeEvent + fun onChat(event: LorenzChatEvent) { + if (!isEnabled()) return + when { + reforgeChatMessage.matches(event.message) -> { + DelayedRun.runDelayed(2.ticks) { + itemUpdate() + waitForChat.set(false) + } + if (config?.hideChat == true) { + event.blockedReason = "reforge_hide" + } + } + + reforgeChatFail.matches(event.message) -> { + DelayedRun.runDelayed(2.ticks) { + waitForChat.set(false) + } + if (config?.hideChat == true) { + event.blockedReason = "reforge_hide" + } + } + } + } + + @SubscribeEvent + fun onOpen(event: InventoryFullyOpenedEvent) { + if (!LorenzUtils.inSkyBlock) return + when { + isHexReforgeMenu(event.inventoryName) -> { + isInHexReforgeMenu = true + DelayedRun.runDelayed(2.ticks) { + itemUpdate() // update since an item must already be in place + } + } + + isReforgeMenu(event.inventoryName) -> { + itemToReforge = null + currentReforge = null + } + + else -> return + } + isInReforgeMenu = true + waitForChat.set(false) + DelayedRun.runNextTick { + inventoryContainer = Minecraft.getMinecraft().thePlayer.openContainer + } + } + + @SubscribeEvent + fun onClose(event: InventoryCloseEvent) { + if (!isInReforgeMenu) return + isInReforgeMenu = false + isInHexReforgeMenu = false + reforgeToSearch = null + currentReforge = null + hoveredReforge = null + sortAfter = null + itemToReforge = null + display = emptyList() + } + + private fun updateDisplay() { + display = generateDisplay() + } + + private fun generateDisplay() = buildList<Renderable> { + this.add(renderableString("§6Reforge Overlay")) + + val item = itemToReforge ?: run { + reforgeToSearch = null + return@buildList + } + + val internalName = item.getInternalName() + val itemType = item.getItemCategoryOrNull() + val itemRarity = item.getItemRarityOrNull() ?: return@buildList + + val rawReforgeList = + if (!isInHexReforgeMenu && config.reforgeStonesOnlyHex) ReforgeAPI.nonePowerStoneReforge else ReforgeAPI.reforgeList + val reforgeList = rawReforgeList.filter { it.isValid(itemType, internalName) } + + val statTypes = reforgeList.mapNotNull { it.stats[itemRarity]?.keys }.flatten().toSet() + + val statTypeButtons = (listOf(getStatButton(null)) + statTypes.map { getStatButton(it) }).chunked(9) + this.add(Renderable.table(statTypeButtons, xPadding = 3, yPadding = 2)) + + val list = reforgeList.sortedWith(getSortSelector(itemRarity, sortAfter)).map(getReforgeView(itemRarity)) + this.addAll(list) + } + + private fun getReforgeColour(reforge: ReforgeAPI.Reforge) = when { + currentReforge == reforge -> "§6" + reforgeToSearch == reforge -> "§3" + reforge.isReforgeStone -> "§9" + else -> "§7" + } + + private fun getReforgeView(itemRarity: LorenzRarity): (ReforgeAPI.Reforge) -> Renderable = { reforge -> + val text = getReforgeColour(reforge) + reforge.name + val tips = getReforgeTips(reforge, itemRarity) + val onHover = if (!isInHexReforgeMenu) { + {} + } else { + { hoveredReforge = reforge } + } + + Renderable.clickAndHover( + text, tips, + onClick = { + SoundUtils.playClickSound() + reforgeToSearch = reforge + updateDisplay() + }, + onHover = onHover, + ) + } + + private fun getReforgeTips( + reforge: ReforgeAPI.Reforge, + itemRarity: LorenzRarity, + ): List<Renderable> { + val stats: List<Renderable> + val removedEffect: List<Renderable> + val addEffectText: String + val click: List<Renderable> + if (currentReforge == reforge) { + stats = currentReforge?.stats?.get(itemRarity)?.print() ?: emptyList() + removedEffect = emptyList() + addEffectText = "§aEffect:" + click = listOf(renderableString(""), renderableString("§3Reforge is currently applied!")) + } else { + stats = reforge.stats[itemRarity]?.print(currentReforge?.stats?.get(itemRarity)) ?: emptyList() + removedEffect = getReforgeEffect( + currentReforge, + itemRarity, + )?.let { listOf(renderableString("§cRemoves Effect:")) + it }?.takeIf { config.showDiff } ?: emptyList() + addEffectText = "§aAdds Effect:" + click = if (reforgeToSearch != reforge) { + listOf(renderableString(""), renderableString("§eClick to select!")) + } else emptyList() + } + + val addedEffect = getReforgeEffect(reforge, itemRarity)?.let { listOf(renderableString(addEffectText)) + it } ?: emptyList() + + return listOf(renderableString("§6Reforge Stats")) + stats + removedEffect + addedEffect + click + } + + private fun getReforgeEffect(reforge: ReforgeAPI.Reforge?, rarity: LorenzRarity) = + reforge?.extraProperty?.get(rarity)?.let { + Renderable.wrappedString( + it, + 190, + color = LorenzColor.GRAY.toColor(), + ) + } + + private fun getSortSelector( + itemRarity: LorenzRarity, + sorting: SkyblockStat?, + ): Comparator<ReforgeAPI.Reforge> = + if (sorting != null) { + Comparator.comparing<ReforgeAPI.Reforge, Double> { it.stats[itemRarity]?.get(sorting) ?: 0.0 }.reversed() + } else { + Comparator.comparing { it.isReforgeStone } + } + + private fun getStatButton(stat: SkyblockStat?): Renderable { + val icon: String + val tip: String + if (stat == null) { + icon = "§7D" + tip = "§7Default" + } else { + icon = stat.icon + tip = stat.iconWithName + } + + val alreadySelected = sortAfter == stat + val fieldColor = if (alreadySelected) LorenzColor.GRAY else LorenzColor.DARK_GRAY + + + val tips = if (alreadySelected) { + listOf("§6Sort by", tip) + } else { + listOf("§6Sort by", tip, "", "§eClick to apply sorting!") + } + val sortField = + Renderable.drawInsideRoundedRect( + Renderable.hoverTips( + Renderable.fixedSizeLine( + renderableString(icon, horizontalAlign = RenderUtils.HorizontalAlignment.CENTER), + SkyblockStat.fontSizeOfLargestIcon, + ), + tips, + ), + fieldColor.toColor(), radius = 15, padding = 1, + ) + return if (alreadySelected) { + sortField + } else { + Renderable.clickable( + sortField, + { + sortAfter = stat + updateDisplay() + }, + ) + } + } + + @SubscribeEvent + fun onRender(event: GuiRenderEvent.ChestGuiOverlayRenderEvent) { + if (!isEnabled()) return + config.position.renderRenderables(display, posLabel = "Reforge Overlay") + } + + @SubscribeEvent + fun onForegroundDrawn(event: GuiContainerEvent.ForegroundDrawnEvent) { + if (!isEnabled()) return + if (currentReforge == null) return + + inventoryContainer?.getSlot(reforgeItem)?.let { + event.drawSlotText(it.xDisplayPosition - 5, it.yDisplayPosition, "§e${currentReforge?.name}", 1f) + } + } + + @SubscribeEvent + fun onGuiContainerBackgroundDrawn(event: GuiContainerEvent.BackgroundDrawnEvent) { + if (hoveredReforge != null && isInHexReforgeMenu) { + if (hoveredReforge != currentReforge) { + colorReforgeStone(hoverColor, hoveredReforge?.rawReforgeStoneName ?: "Random Basic Reforge") + } else { + inventoryContainer?.getSlot(reforgeItem)?.highlight(hoverColor) + + //?.get(reforgeItem)?. = hoverColor + } + hoveredReforge = null + } + + if (reforgeToSearch == null) return + if (reforgeToSearch != currentReforge) { + colorSelected() + } else { + inventoryContainer?.getSlot(reforgeItem)?.highlight(finishedColor) + } + } + + private fun colorSelected() = if (reforgeToSearch?.isReforgeStone == true) { + if (isInHexReforgeMenu) { + colorReforgeStone(selectedColor, reforgeToSearch?.rawReforgeStoneName) + } else { + inventoryContainer?.getSlot(EXIT_BUTTON)?.highlight(selectedColor) + } + } else { + inventoryContainer?.getSlot(reforgeButton)?.highlight(selectedColor) + } + + private fun colorReforgeStone(color: Color, reforgeStone: String?) { + val inventory = inventoryContainer?.inventorySlots ?: return + val slot = inventory.firstOrNull { it?.stack?.cleanName() == reforgeStone } + if (slot != null) { + slot highlight color + } else { + inventory[HEX_REFORGE_NEXT_DOWN_BUTTON]?.takeIf { it.stack.item == Items.skull }?.highlight(color) + inventory[HEX_REFORGE_NEXT_UP_BUTTON]?.takeIf { it.stack.item == Items.skull }?.highlight(color) + } + } + + private fun SkyblockStatList.print(appliedReforge: SkyblockStatList? = null): List<Renderable> { + val diff = appliedReforge?.takeIf { config.showDiff }?.let { this - it } + val main = ((diff ?: this).mapNotNull { + val key = it.key + val value = this[key] ?: 0.0 + buildList<Renderable> { + add(renderableString("§9${value.toStringWithPlus().removeSuffix(".0")}")) + diff?.get(key)?.let { add(renderableString((if (it < 0) "§c" else "§a") + it.toStringWithPlus().removeSuffix(".0"))) } + add(renderableString(key.iconWithName)) + } + }) + val table = Renderable.table(main, 5) + return listOf(table) + } +} + diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/items/EstimatedItemValue.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/items/EstimatedItemValue.kt index 724583e20..dee0a6749 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/items/EstimatedItemValue.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/items/EstimatedItemValue.kt @@ -4,7 +4,6 @@ import at.hannibal2.skyhanni.SkyHanniMod import at.hannibal2.skyhanni.api.event.HandleEvent import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator import at.hannibal2.skyhanni.data.jsonobjects.repo.ItemsJson -import at.hannibal2.skyhanni.data.jsonobjects.repo.neu.NeuReforgeStoneJson import at.hannibal2.skyhanni.events.ConfigLoadEvent import at.hannibal2.skyhanni.events.GuiRenderEvent import at.hannibal2.skyhanni.events.InventoryCloseEvent @@ -45,7 +44,6 @@ object EstimatedItemValue { private val cache = mutableMapOf<ItemStack, List<List<Any>>>() private var lastToolTipTime = 0L var gemstoneUnlockCosts = HashMap<NEUInternalName, HashMap<String, List<String>>>() - var reforges = mapOf<NEUInternalName, NeuReforgeStoneJson>() var bookBundleAmount = mapOf<String, Int>() private var currentlyShowing = false @@ -55,8 +53,6 @@ object EstimatedItemValue { fun onNeuRepoReload(event: NeuRepositoryReloadEvent) { gemstoneUnlockCosts = event.readConstant<HashMap<NEUInternalName, HashMap<String, List<String>>>>("gemstonecosts") - reforges = - event.readConstant<Map<NEUInternalName, NeuReforgeStoneJson>>("reforgestones") } @SubscribeEvent diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/items/EstimatedItemValueCalculator.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/items/EstimatedItemValueCalculator.kt index e1619ad92..05d53fa65 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/items/EstimatedItemValueCalculator.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/items/EstimatedItemValueCalculator.kt @@ -1,6 +1,7 @@ package at.hannibal2.skyhanni.features.misc.items import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.api.ReforgeAPI import at.hannibal2.skyhanni.test.command.ErrorManager import at.hannibal2.skyhanni.utils.CollectionUtils.sortedDesc import at.hannibal2.skyhanni.utils.ItemUtils.getInternalName @@ -130,7 +131,7 @@ object EstimatedItemValueCalculator { ) if (price != null) { val name = attributes[0].first.fixMending().allLettersFirstUppercase() - list.add("§7Attribute §9$name ${attributes[0].second}§7: (§6${price.shortFormat()}§7)",) + list.add("§7Attribute §9$name ${attributes[0].second}§7: (§6${price.shortFormat()}§7)") return price } } @@ -214,15 +215,15 @@ object EstimatedItemValueCalculator { private fun addReforgeStone(stack: ItemStack, list: MutableList<String>): Double { val rawReforgeName = stack.getReforgeName() ?: return 0.0 - val reforge = EstimatedItemValue.reforges.values.firstOrNull { - rawReforgeName == it.reforgeName.lowercase() || rawReforgeName == it.internalName.asString().lowercase() + val reforge = ReforgeAPI.onlyPowerStoneReforge.firstOrNull { + rawReforgeName == it.lowercaseName || rawReforgeName == it.reforgeStone?.asString()?.lowercase() } ?: return 0.0 - val internalName = reforge.internalName.asString().asInternalName() + val internalName = reforge.reforgeStone ?: return 0.0 val reforgeStonePrice = internalName.getPrice() val reforgeStoneName = internalName.itemName - val applyCost = getReforgeStoneApplyCost(stack, reforge.reforgeCosts, internalName) ?: return 0.0 + val applyCost = reforge.costs?.let { getReforgeStoneApplyCost(stack, it, internalName) } ?: return 0.0 - list.add("§7Reforge: §9${reforge.reforgeName}") + list.add("§7Reforge: §9${reforge.name}") list.add(" §7Stone $reforgeStoneName §7(§6" + reforgeStonePrice.shortFormat() + "§7)") list.add(" §7Apply cost: (§6" + applyCost.shortFormat() + "§7)") return reforgeStonePrice + applyCost diff --git a/src/main/java/at/hannibal2/skyhanni/utils/NEUItems.kt b/src/main/java/at/hannibal2/skyhanni/utils/NEUItems.kt index bb2d3fd3e..4b334d027 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/NEUItems.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/NEUItems.kt @@ -42,6 +42,7 @@ import net.minecraft.client.renderer.GlStateManager import net.minecraft.client.renderer.RenderHelper import net.minecraft.init.Blocks import net.minecraft.init.Items +import net.minecraft.item.Item import net.minecraft.item.ItemStack import net.minecraft.nbt.NBTTagCompound import net.minecraftforge.fml.common.eventhandler.SubscribeEvent @@ -54,35 +55,39 @@ object NEUItems { private val multiplierCache = mutableMapOf<NEUInternalName, PrimitiveItemStack>() private val recipesCache = mutableMapOf<NEUInternalName, Set<NeuRecipe>>() private val ingredientsCache = mutableMapOf<NeuRecipe, Set<Ingredient>>() + private val itemIdCache = mutableMapOf<Item, List<NEUInternalName>>() private val hypixelApiGson by lazy { BaseGsonBuilder.gson() - .registerTypeAdapter(HypixelApiTrophyFish::class.java, object : TypeAdapter<HypixelApiTrophyFish>() { - override fun write(out: JsonWriter, value: HypixelApiTrophyFish) {} - - override fun read(reader: JsonReader): HypixelApiTrophyFish { - val trophyFish = mutableMapOf<String, Int>() - var totalCaught = 0 - reader.beginObject() - while (reader.hasNext()) { - val key = reader.nextName() - if (key == "total_caught") { - totalCaught = reader.nextInt() - continue - } - if (reader.peek() == JsonToken.NUMBER) { - val valueAsString = reader.nextString() - if (valueAsString.isInt()) { - trophyFish[key] = valueAsString.toInt() + .registerTypeAdapter( + HypixelApiTrophyFish::class.java, + object : TypeAdapter<HypixelApiTrophyFish>() { + override fun write(out: JsonWriter, value: HypixelApiTrophyFish) {} + + override fun read(reader: JsonReader): HypixelApiTrophyFish { + val trophyFish = mutableMapOf<String, Int>() + var totalCaught = 0 + reader.beginObject() + while (reader.hasNext()) { + val key = reader.nextName() + if (key == "total_caught") { + totalCaught = reader.nextInt() continue } + if (reader.peek() == JsonToken.NUMBER) { + val valueAsString = reader.nextString() + if (valueAsString.isInt()) { + trophyFish[key] = valueAsString.toInt() + continue + } + } + reader.skipValue() } - reader.skipValue() + reader.endObject() + return HypixelApiTrophyFish(totalCaught, trophyFish) } - reader.endObject() - return HypixelApiTrophyFish(totalCaught, trophyFish) - } - }.nullSafe()) + }.nullSafe(), + ) .create() } @@ -94,7 +99,7 @@ object NEUItems { Utils.createItemStack( ItemStack(Blocks.barrier).item, "§cMissing Repo Item", - "§cYour NEU repo seems to be out of date" + "§cYour NEU repo seems to be out of date", ) } @@ -119,7 +124,7 @@ object NEUItems { } catch (e: Exception) { ErrorManager.logErrorWithData( e, "Error reading hypixel player api data", - "data" to apiData + "data" to apiData, ) } } @@ -210,7 +215,7 @@ object NEUItems { "This may be because your NEU repo is outdated. Please ask in the SkyHanni " + "Discord if this is the case.", "Item name" to this.asString(), - "repo commit" to manager.latestRepoCommit + "repo commit" to manager.latestRepoCommit, ) fallbackItem } @@ -225,7 +230,7 @@ object NEUItems { x: Float, y: Float, scaleMultiplier: Double = itemFontSize, - rescaleSkulls: Boolean = true + rescaleSkulls: Boolean = true, ) { val item = checkBlinkItem() val isSkull = rescaleSkulls && item.item === Items.skull @@ -281,13 +286,24 @@ object NEUItems { fun allNeuRepoItems(): Map<String, JsonObject> = NotEnoughUpdates.INSTANCE.manager.itemInformation + fun getInternalNamesForItemId(item: Item): List<NEUInternalName> { + itemIdCache[item]?.let { + return it + } + val result = allNeuRepoItems() + .filter { Item.getByNameOrId(it.value.get("itemid").asString) == item } + .keys.map { it.asInternalName() } + itemIdCache[item] = result + return result + } + fun getPrimitiveMultiplier(internalName: NEUInternalName, tryCount: Int = 0): PrimitiveItemStack { multiplierCache[internalName]?.let { return it } if (tryCount == 10) { ErrorManager.logErrorStateWithData( "Could not load recipe data.", "Failed to find item multiplier", - "internalName" to internalName + "internalName" to internalName, ) return internalName.makePrimitiveStack() } diff --git a/src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt b/src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt index 1ee34699c..b97298ce4 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt @@ -33,7 +33,7 @@ object NumberUtil { 5 to "V", 4 to "IV", 1 to "I", - ) + ), ) @Deprecated("outdated", ReplaceWith("value.shortFormat(preciseBillions)")) @@ -170,6 +170,8 @@ object NumberUtil { } else romanSymbols[l] + (this - l).toRoman() } + fun Number.toStringWithPlus() = (if (this.toDouble() >= 0.0) "+" else "") + this.toString() + private fun processDecimal(decimal: Int, lastNumber: Int, lastDecimal: Int) = if (lastNumber > decimal) { lastDecimal - decimal } else { diff --git a/src/main/java/at/hannibal2/skyhanni/utils/json/SkyHanniTypeAdapters.kt b/src/main/java/at/hannibal2/skyhanni/utils/json/SkyHanniTypeAdapters.kt index dc50eef74..7859853ca 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/json/SkyHanniTypeAdapters.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/json/SkyHanniTypeAdapters.kt @@ -1,6 +1,7 @@ package at.hannibal2.skyhanni.utils.json import at.hannibal2.skyhanni.data.IslandType +import at.hannibal2.skyhanni.data.model.SkyblockStat import at.hannibal2.skyhanni.features.fishing.trophy.TrophyRarity import at.hannibal2.skyhanni.features.garden.CropType import at.hannibal2.skyhanni.features.garden.pests.PestType @@ -24,22 +25,22 @@ object SkyHanniTypeAdapters { val UUID: TypeAdapter<UUID> = SimpleStringTypeAdapter( { this.toString() }, - { java.util.UUID.fromString(this) } + { java.util.UUID.fromString(this) }, ) val INTERNAL_NAME: TypeAdapter<NEUInternalName> = SimpleStringTypeAdapter( { this.asString() }, - { this.asInternalName() } + { this.asInternalName() }, ) val VEC_STRING: TypeAdapter<LorenzVec> = SimpleStringTypeAdapter( { "$x:$y:$z" }, - { LorenzVec.decodeFromString(this) } + { LorenzVec.decodeFromString(this) }, ) val TROPHY_RARITY: TypeAdapter<TrophyRarity> = SimpleStringTypeAdapter( { name }, - { TrophyRarity.getByName(this) ?: error("Could not parse TrophyRarity from '$this'") } + { TrophyRarity.getByName(this) ?: error("Could not parse TrophyRarity from '$this'") }, ) val TIME_MARK: TypeAdapter<SimpleTimeMark> = object : TypeAdapter<SimpleTimeMark>() { @@ -54,12 +55,17 @@ object SkyHanniTypeAdapters { val CROP_TYPE: TypeAdapter<CropType> = SimpleStringTypeAdapter( { name }, - { CropType.getByName(this) } + { CropType.getByName(this) }, ) val PEST_TYPE: TypeAdapter<PestType> = SimpleStringTypeAdapter( { name }, - { PestType.getByName(this) } + { PestType.getByName(this) }, + ) + + val SKYBLOCK_STAT: TypeAdapter<SkyblockStat> = SimpleStringTypeAdapter( + { name.lowercase() }, + { SkyblockStat.valueOf(this.uppercase()) }, ) val TRACKER_DISPLAY_MODE = SimpleStringTypeAdapter.forEnum<SkyHanniTracker.DefaultDisplayMode>() @@ -70,10 +76,13 @@ object SkyHanniTypeAdapters { crossinline write: (JsonWriter, T) -> Unit, crossinline read: (JsonReader) -> T, ): GsonBuilder { - this.registerTypeAdapter(T::class.java, object : TypeAdapter<T>() { - override fun write(out: JsonWriter, value: T) = write(out, value) - override fun read(reader: JsonReader) = read(reader) - }.nullSafe()) + this.registerTypeAdapter( + T::class.java, + object : TypeAdapter<T>() { + override fun write(out: JsonWriter, value: T) = write(out, value) + override fun read(reader: JsonReader) = read(reader) + }.nullSafe(), + ) return this } } |