diff --git a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt index 170dfb524551..63236f2d7664 100644 --- a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt +++ b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt @@ -3,6 +3,7 @@ package at.hannibal2.skyhanni import at.hannibal2.skyhanni.api.CollectionAPI import at.hannibal2.skyhanni.api.DataWatcherAPI import at.hannibal2.skyhanni.api.GetFromSackAPI +import at.hannibal2.skyhanni.api.ReforgeAPI import at.hannibal2.skyhanni.api.SkillAPI import at.hannibal2.skyhanni.config.ConfigFileType import at.hannibal2.skyhanni.config.ConfigManager @@ -244,6 +245,7 @@ import at.hannibal2.skyhanni.features.inventory.ItemStars import at.hannibal2.skyhanni.features.inventory.MaxPurseItems import at.hannibal2.skyhanni.features.inventory.PowerStoneGuideFeatures import at.hannibal2.skyhanni.features.inventory.QuickCraftFeatures +import at.hannibal2.skyhanni.features.inventory.ReforgeHelper import at.hannibal2.skyhanni.features.inventory.RngMeterInventory import at.hannibal2.skyhanni.features.inventory.SackDisplay import at.hannibal2.skyhanni.features.inventory.ShiftClickBrewing @@ -412,6 +414,7 @@ import at.hannibal2.skyhanni.utils.LorenzUtils import at.hannibal2.skyhanni.utils.MinecraftConsoleFilter.Companion.initLogging import at.hannibal2.skyhanni.utils.NEUItems import at.hannibal2.skyhanni.utils.NEUVersionCheck.checkIfNeuIsLoaded +import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils import at.hannibal2.skyhanni.utils.TabListData import at.hannibal2.skyhanni.utils.UtilsPatterns import at.hannibal2.skyhanni.utils.repopatterns.RepoPatternManager @@ -501,6 +504,7 @@ class SkyHanniMod { loadModule(GardenBestCropTime()) loadModule(ActionBarData) loadModule(TrackerManager) + loadModule(SkyBlockItemModifierUtils) loadModule(ScoreboardPattern) loadModule(UtilsPatterns) loadModule(BossbarData) @@ -539,6 +543,7 @@ class SkyHanniMod { loadModule(LorenzUtils) loadModule(NEUItems) loadModule(PestAPI) + loadModule(ReforgeAPI) // features loadModule(BazaarOrderHelper()) @@ -568,6 +573,7 @@ class SkyHanniMod { loadModule(BazaarBestSellMethod()) loadModule(ShiftClickBrewing()) loadModule(BazaarOpenPriceWebsite()) + loadModule(ReforgeHelper()) loadModule(AuctionHouseCopyUnderbidPrice()) loadModule(AnvilCombineHelper()) loadModule(SeaCreatureMessageShortener()) 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 000000000000..c595788c9d95 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/api/ReforgeAPI.kt @@ -0,0 +1,149 @@ +package at.hannibal2.skyhanni.api + +import at.hannibal2.skyhanni.data.jsonobjects.repo.neu.NeuReforgeJson +import at.hannibal2.skyhanni.data.model.SkyblockStatList +import at.hannibal2.skyhanni.events.NeuRepositoryReloadEvent +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 net.minecraft.item.ItemStack +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +object ReforgeAPI { + var reforgeList: List = emptyList() + private set(value) { + field = value + nonePowerStoneReforge = value.filterNot { it.isReforgeStone } + onlyPowerStoneReforge = value.filter { it.isReforgeStone } + } + + var nonePowerStoneReforge: List = emptyList() + private set + + var onlyPowerStoneReforge: List = 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, + val reforgeStone: NEUInternalName? = null, + val specialItems: List? = null, + val extraProperty: Map = emptyMap(), + val costs: Map? = 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 { + return "Reforge $name" + } + + } + + @SubscribeEvent + fun onNeuRepoReload(event: NeuRepositoryReloadEvent) { + val reforgeStoneData = event.readConstant>("reforgestones").values + val reforgeData = event.readConstant>("reforges").values + reforgeList = (reforgeStoneData + reforgeData).map(::mapReforge) + } + + private fun mapReforge(it: NeuReforgeJson): Reforge { + val type = it.itemType + return Reforge( + name = it.reforgeName, + type = LorenzUtils.enumValueOf(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/ConfigManager.kt b/src/main/java/at/hannibal2/skyhanni/config/ConfigManager.kt index adc56c596bb8..4651c9a62247 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/ConfigManager.kt +++ b/src/main/java/at/hannibal2/skyhanni/config/ConfigManager.kt @@ -7,6 +7,9 @@ import at.hannibal2.skyhanni.data.jsonobjects.local.FriendsJson import at.hannibal2.skyhanni.data.jsonobjects.local.JacobContestsJson import at.hannibal2.skyhanni.data.jsonobjects.local.KnownFeaturesJson import at.hannibal2.skyhanni.data.jsonobjects.local.VisualWordsJson +import at.hannibal2.skyhanni.data.jsonobjects.other.HypixelApiTrophyFish +import at.hannibal2.skyhanni.data.model.SkyblockStat +import at.hannibal2.skyhanni.data.model.SkyblockStatList import at.hannibal2.skyhanni.events.LorenzEvent import at.hannibal2.skyhanni.features.fishing.trophy.TrophyRarity import at.hannibal2.skyhanni.features.misc.update.UpdateManager @@ -20,6 +23,7 @@ import at.hannibal2.skyhanni.utils.LorenzVec import at.hannibal2.skyhanni.utils.NEUInternalName import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName import at.hannibal2.skyhanni.utils.NEUItems +import at.hannibal2.skyhanni.utils.NumberUtil.isInt import at.hannibal2.skyhanni.utils.SimpleTimeMark import at.hannibal2.skyhanni.utils.SimpleTimeMark.Companion.asTimeMark import at.hannibal2.skyhanni.utils.tracker.SkyHanniTracker @@ -29,6 +33,7 @@ import com.google.gson.JsonObject import com.google.gson.TypeAdapter import com.google.gson.TypeAdapterFactory import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonToken import com.google.gson.stream.JsonWriter import io.github.notenoughupdates.moulconfig.annotations.ConfigLink import io.github.notenoughupdates.moulconfig.observer.PropertyTypeAdapterFactory @@ -148,6 +153,58 @@ class ConfigManager { return reader.nextString().toLong().asTimeMark() } }.nullSafe()) + .registerTypeAdapter(HypixelApiTrophyFish::class.java, object : TypeAdapter() { + override fun write(out: JsonWriter, value: HypixelApiTrophyFish) {} + + override fun read(reader: JsonReader): HypixelApiTrophyFish { + val trophyFish = mutableMapOf() + 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.endObject() + return HypixelApiTrophyFish(totalCaught, trophyFish) + } + }.nullSafe()) + .registerTypeAdapter(SkyblockStat::class.java, object : TypeAdapter() { + override fun write(out: JsonWriter, value: SkyblockStat) { + out.value(value.name.lowercase()) // F you guy who made the stats lowercase + } + + override fun read(reader: JsonReader): SkyblockStat { + return SkyblockStat.valueOf(reader.nextString().uppercase()) + } + }.nullSafe()) + .registerTypeAdapter({ out, value -> + out.beginObject() + value.forEach { + out.name(it.key.name.lowercase()).value(it.value) + } + out.endObject() + }, { reader -> + reader.beginObject() + val list = SkyblockStatList() + while (reader.hasNext()) { + val name = reader.nextName() + val value = reader.nextDouble() + list[SkyblockStat.valueOf(name.uppercase())] = value + } + reader.endObject() + list + }) .enableComplexMapKeySerialization() } @@ -156,6 +213,17 @@ class ConfigManager { .create() var configDirectory = File("config/skyhanni") + + private inline fun GsonBuilder.registerTypeAdapter( + crossinline write: (JsonWriter, T) -> Unit, + crossinline read: (JsonReader) -> T + ): GsonBuilder { + this.registerTypeAdapter(T::class.java, object : TypeAdapter() { + override fun write(out: JsonWriter, value: T) = write(out, value) + override fun read(reader: JsonReader) = read(reader) + }.nullSafe()) + return this + } } val features get() = jsonHolder[ConfigFileType.FEATURES] as Features 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 a8a38af68031..ca3e52fdfd26 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 @@ -45,4 +45,9 @@ public static class HarpConfig { @ConfigOption(name = "Tia Relay Abiphone Network Maintenance", desc = "") @Accordion public TiaRelayConfig tiaRelay = new TiaRelayConfig(); + + @Expose + @ConfigOption(name = "Reforge Helper", desc = "") + @Accordion + public ReforgeHelperConfig reforge = new ReforgeHelperConfig(); } 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 000000000000..9be1df419d64 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/inventory/helper/ReforgeHelperConfig.java @@ -0,0 +1,31 @@ +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.ConfigOption; + +public class ReforgeHelperConfig { + + @Expose + public Position posList = new Position(-200, 85, true, true); + @Expose + public Position posCurrent = new Position(280, 45, true, true); + + @Expose + @ConfigOption(name = "Enable", desc = "Enables the reforge helper") + @ConfigEditorBoolean + @FeatureToggle + public boolean enable = true; + + @Expose + @ConfigOption(name = "Reforge Stones Hex Only", desc = "Displays reforge stones only when in Hex") + @ConfigEditorBoolean + public boolean reforgeStonesOnlyHex = true; + + @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 similarity index 71% rename from src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/neu/NeuReforgeStoneJson.kt rename to src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/neu/NeuReforgeJson.kt index c450e391055e..01d35846b670 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 @SerializedName("internalName") val internalName: NEUInternalName?, @Expose val reforgeName: String, @Expose @SerializedName("itemTypes") val rawItemTypes: Any, @Expose val requiredRarities: List, - @Expose val reforgeCosts: Map, - @Expose val reforgeStats: Map>, + @Expose val reforgeCosts: Map?, + @Expose val reforgeStats: Map?, @Expose @SerializedName("reforgeAbility") val rawReforgeAbility: Any?, ) { private lateinit var reforgeAbilityField: Map + private lateinit var itemTypeField: Pair> val reforgeAbility get() = if (this::reforgeAbilityField.isInitialized) reforgeAbilityField @@ -36,28 +41,27 @@ data class NeuReforgeStoneJson( reforgeAbilityField } - /* used in ReforgeAPI which isn't in beta yet - val itemType: Pair> by lazy { + val itemType: Pair> + 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> ?: return@lazy type to emptyList() + val map = any as? Map> ?: 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 000000000000..4695ad3b7278 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/data/model/SkyblockStat.kt @@ -0,0 +1,85 @@ +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 fname = name.lowercase().allLettersFirstUppercase() + + val iconWithName = "$icon $fname" + + 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::class.java), Map { + operator fun minus(other: SkyblockStatList): SkyblockStatList { + return SkyblockStatList().apply { + for ((key, value) in this@SkyblockStatList) { + this[key] = value - (other[key] ?: 0.0) + } + for ((key, value) in other) { + if (this[key] == null) { + this[key] = (this@SkyblockStatList[key] ?: 0.0) - value + } + } + } + } + + companion object { + fun mapOf(vararg list: Pair) = SkyblockStatList().apply { + for ((key, value) in list) { + this[key] = value + } + } + } +} + 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 000000000000..5c87c07ea0cc --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/ReforgeHelper.kt @@ -0,0 +1,391 @@ +package at.hannibal2.skyhanni.features.inventory + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.api.ReforgeAPI +import at.hannibal2.skyhanni.data.ItemRenderBackground.Companion.background +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.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.RenderUtils +import at.hannibal2.skyhanni.utils.RenderUtils.renderRenderables +import at.hannibal2.skyhanni.utils.RenderUtils.renderStrings +import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.getReforgeName +import at.hannibal2.skyhanni.utils.StringUtils.matches +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.util.concurrent.atomic.AtomicBoolean +import at.hannibal2.skyhanni.utils.renderables.Renderable.Companion.string as rString + +class ReforgeHelper { + + private val config get() = SkyHanniMod.feature.inventory.helper.reforge + + private val reforgeMenu by RepoPattern.pattern( + "menu.reforge", + "Reforge Item" + ) + private val reforgeHexMenu by RepoPattern.pattern( + "menu.reforge.hex", + "The Hex ➜ Reforges" + ) + private val reforgeChatMessage by RepoPattern.pattern( + "chat.reforge.message", + "§aYou reforged your .* §r§ainto a .*!|§aYou applied a .* §r§ato your .*!" + ) + private val reforgeChatFail by RepoPattern.pattern( + "chat.reforge.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.enable && isInReforgeMenu + + private var itemToReforge: ItemStack? = null + private var inventoryContainer: Container? = null + + private var currentReforge: ReforgeAPI.Reforge? = null + set(value) { + field = value + formattedCurrentReforge = if (value == null) "" else "§7Now: §3${value.name}" + } + private var reforgeToSearch: ReforgeAPI.Reforge? = null + set(value) { + field = value + formattedReforgeToSearch = if (value == null) "" else "§7Goal: §9${value.name}" + } + + private var hoverdReforge: ReforgeAPI.Reforge? = null + + private var formattedCurrentReforge = "" + private var formattedReforgeToSearch = "" + + private val reforgeItem get() = if (isInHexReforgeMenu) 19 else 13 + private val reforgeButton get() = if (isInHexReforgeMenu) 48 else 22 + + private val hexReforgeNextDownButton = 35 + private val hexReforgeNextUpButton = 17 + + private val exitButton = 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 = generateDisplay() + + private val hoverColor = LorenzColor.GOLD.addOpacity(50).rgb + private val selectedColor = LorenzColor.BLUE.addOpacity(100).rgb + private val finishedColor = LorenzColor.GREEN.addOpacity(75).rgb + + 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 (currentReforge == reforgeToSearch) { + event.isCanceled = true + waitForChat.set(false) + } else if (waitForChat.get()) { + waitDelay = true + event.isCanceled = true + } else { + if (event.clickedButton == 2) return + if (waitDelay) { + waitDelay = false + } else { + waitForChat.set(true) + } + } + } + + DelayedRun.runNextTick { + itemUpdate() + } + } + + @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 (!isEnabled()) return + isInReforgeMenu = false + isInHexReforgeMenu = false + reforgeToSearch = null + currentReforge = null + hoverdReforge = null + sortAfter = null + itemToReforge = null + updateDisplay() + } + + private fun updateDisplay() { + display = generateDisplay() + } + + private fun generateDisplay() = buildList { + this.add(rString("§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.sortedBy(getSortSelector(itemRarity, sortAfter)).map(getReforgeView(itemRarity)) + this.addAll(list) + } + + private fun getReforgeView(itemRarity: LorenzRarity): (ReforgeAPI.Reforge) -> Renderable = { reforge -> + val text = (if (reforge.isReforgeStone) "§9" else "§7") + reforge.name + val tips = run { + val pre: List + val stats: List + val removedEffect: List + val addEffectText: String + if (currentReforge == reforge) { + pre = listOf(rString("§3Reforge is currently applied!")) + stats = (currentReforge?.stats?.get(itemRarity)?.print() ?: emptyList()) + removedEffect = emptyList() + addEffectText = "§aEffect:" + } else { + pre = listOf(rString("§6Reforge Stats")) + stats = (reforge.stats[itemRarity]?.print(currentReforge?.stats?.get(itemRarity)) ?: emptyList()) + removedEffect = getReforgeEffect( + currentReforge, + itemRarity + )?.let { listOf(rString("§cRemoves Effect:")) + it } + ?: emptyList() + addEffectText = "§aAdds Effect:" + } + + val addedEffect = + (getReforgeEffect(reforge, itemRarity)?.let { listOf(rString(addEffectText)) + it } + ?: emptyList()) + + return@run pre + stats + removedEffect + addedEffect + } + val onHover = if (!isInHexReforgeMenu) { + {} + } else { + { hoverdReforge = reforge } + } + + Renderable.clickAndHover( + text, tips, onClick = { reforgeToSearch = reforge }, onHover = onHover + ) + } + + 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? + ): (ReforgeAPI.Reforge) -> Comparable = + if (sorting != null) { + { -(it.stats[itemRarity]?.get(sorting) ?: 0.0) as Comparable } + } else { + { (it.isReforgeStone) as Comparable } + } + + 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 fieldColor = if (sortAfter == stat) LorenzColor.GRAY else LorenzColor.DARK_GRAY + + val sortField = + Renderable.drawInsideRoundedRect( + Renderable.hoverTips( + Renderable.fixedSizeLine( + rString(icon, horizontalAlign = RenderUtils.HorizontalAlignment.CENTER), + SkyblockStat.fontSizeOfLargestIcon + ), listOf("§6Sort after", tip) + ), fieldColor.toColor(), radius = 15, padding = 1 + ) + return if (sortAfter == stat) { + sortField + } else { + Renderable.clickable(sortField, { + sortAfter = stat + updateDisplay() + }) + } + } + + @SubscribeEvent + fun onRender(event: GuiRenderEvent.ChestGuiOverlayRenderEvent) { + if (!isEnabled()) return + config.posCurrent.renderStrings( + listOf(formattedReforgeToSearch, formattedCurrentReforge), + posLabel = "Reforge Notify" + ) + config.posList.renderRenderables(display, posLabel = "Reforge Overlay") + + if (hoverdReforge != null && isInHexReforgeMenu) { + if (hoverdReforge != currentReforge) { + colorReforgeStone(hoverColor, hoverdReforge?.rawReforgeStoneName ?: "Random Basic Reforge") + } else { + inventoryContainer?.inventory?.get(reforgeItem)?.background = hoverColor + } + hoverdReforge = null + } + + if (reforgeToSearch == null) return + if (reforgeToSearch != currentReforge) { + colorSelected() + } else { + inventoryContainer?.inventory?.get(reforgeItem)?.background = finishedColor + } + } + + private fun colorSelected() = if (reforgeToSearch?.isReforgeStone == true) { + if (isInHexReforgeMenu) { + colorReforgeStone(selectedColor, reforgeToSearch?.rawReforgeStoneName) + } else { + inventoryContainer?.inventory?.get(exitButton)?.background = selectedColor + } + } else { + inventoryContainer?.inventory?.get(reforgeButton)?.background = selectedColor + } + + private fun colorReforgeStone(color: Int, reforgeStone: String?) { + val inventory = inventoryContainer?.inventory ?: return + val itemStack = inventory.firstOrNull { it?.cleanName() == reforgeStone } + if (itemStack != null) { + itemStack.background = color + } else { + inventory[hexReforgeNextDownButton]?.takeIf { it.item == Items.skull }?.background = color + inventory[hexReforgeNextUpButton]?.takeIf { it.item == Items.skull }?.background = color + } + } + + private fun SkyblockStatList.print(appliedReforge: SkyblockStatList? = null): List { + val diff = appliedReforge?.let { this - it } + val main = ((diff ?: this).mapNotNull { + val key = it.key + val value = this[key] ?: return@mapNotNull null + buildList { + add(rString("§9${value.toStringWithPlus()}")) + diff?.get(key)?.let { add(rString((if (it < 0) "§c" else "§a") + it.toStringWithPlus())) } + add(rString(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 c79c1f8a4f2f..bc9f26f2adb2 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 @@ -3,7 +3,6 @@ package at.hannibal2.skyhanni.features.misc.items import at.hannibal2.skyhanni.SkyHanniMod 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 @@ -42,7 +41,6 @@ object EstimatedItemValue { private val cache = mutableMapOf>>() private var lastToolTipTime = 0L var gemstoneUnlockCosts = HashMap>>() - var reforges = mapOf() var bookBundleAmount = mapOf() private var currentlyShowing = false @@ -52,8 +50,6 @@ object EstimatedItemValue { fun onNeuRepoReload(event: NeuRepositoryReloadEvent) { gemstoneUnlockCosts = event.readConstant>>>("gemstonecosts") - reforges = - event.readConstant>("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 8602eece8227..ff7c80cbe6ea 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 @@ -164,15 +165,15 @@ object EstimatedItemValueCalculator { private fun addReforgeStone(stack: ItemStack, list: MutableList): 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" + NumberUtil.format(reforgeStonePrice) + "§7)") list.add(" §7Apply cost: (§6" + NumberUtil.format(applyCost) + "§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 3d6f0e74e439..7dafc635486c 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/NEUItems.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/NEUItems.kt @@ -37,6 +37,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 @@ -48,6 +49,7 @@ object NEUItems { private val multiplierCache = mutableMapOf>() private val recipesCache = mutableMapOf>() private val ingredientsCache = mutableMapOf>() + private val itemIdCache = mutableMapOf>() private val hypixelApiGson by lazy { ConfigManager.createBaseGsonBuilder() @@ -250,6 +252,17 @@ object NEUItems { fun allNeuRepoItems(): Map = NotEnoughUpdates.INSTANCE.manager.itemInformation + fun getInternalNamesForItemId(item: Item): List { + if (itemIdCache.contains(item)) { + return itemIdCache[item]!! + } + val result = allNeuRepoItems().filter { + Item.getByNameOrId(it.value.get("itemid").asString) == item + }.keys.map { it.asInternalName() } + itemIdCache[item] = result + return result + } + // TODO create extended function fun getMultiplier(internalName: NEUInternalName, tryCount: Int = 0): Pair { if (multiplierCache.contains(internalName)) { diff --git a/src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt b/src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt index e2df5349fbce..da8ca88a5a2a 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt @@ -164,6 +164,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 {