From e1b9ba327a4569e31728cbcbee217cbf72ec2e8d Mon Sep 17 00:00:00 2001 From: Thunderblade73 <85900443+Thunderblade73@users.noreply.github.com> Date: Mon, 23 Sep 2024 00:40:07 +0200 Subject: [PATCH 01/38] Fix: Stonks of stonks auction on auction (#2572) --- .../skyhanni/features/inventory/StockOfStonkFeature.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/StockOfStonkFeature.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/StockOfStonkFeature.kt index 4bc8af1e3e56..f905eb5a532c 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/inventory/StockOfStonkFeature.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/StockOfStonkFeature.kt @@ -6,6 +6,7 @@ import at.hannibal2.skyhanni.events.InventoryOpenEvent import at.hannibal2.skyhanni.events.LorenzToolTipEvent import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.CollectionUtils.transformAt +import at.hannibal2.skyhanni.utils.ConditionalUtils.transformIf import at.hannibal2.skyhanni.utils.LorenzUtils import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher @@ -20,7 +21,6 @@ object StockOfStonkFeature { private val repoGroup = RepoPattern.group("inventory.stockofstonks") - /** * REGEX-TEST: Stonks Auction */ @@ -42,7 +42,7 @@ object StockOfStonkFeature { */ private val topPattern by repoGroup.pattern( "top", - "§5§o§7§7▶ §c§lTOP (?[\\d,]+)§7 - §5Stock of Stonks §8x(?\\d+)", + "§5§o§7§.▶ §c§lTOP (?[\\d,]+)§7 - §5Stock of Stonks §8x(?\\d+)", ) /** @@ -85,7 +85,7 @@ object StockOfStonkFeature { } bidPattern.matchMatcher(line) { val cost = group("amount").replace(",", "").toLong() - val ratio = cost / stonksReward + val ratio = cost / stonksReward.transformIf({ this == 0 }, { 1 }) event.toolTip[index - 1] = line + " §7(§6§6${ratio.addSeparators()} §7per)" // double §6 for the replacement at the end if (ratio < bestRatio) { bestValueIndex = index - 1 From e60a7adf98a37ba13ce1caf372f3a6f9895c5da6 Mon Sep 17 00:00:00 2001 From: martimavocado <39881008+martimavocado@users.noreply.github.com> Date: Mon, 23 Sep 2024 05:15:58 +0100 Subject: [PATCH 02/38] Fix: Great Glacite Lake detection (#2565) --- src/main/java/at/hannibal2/skyhanni/data/MiningAPI.kt | 2 +- .../skyhanni/features/misc/discordrpc/DiscordLocationKey.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/at/hannibal2/skyhanni/data/MiningAPI.kt b/src/main/java/at/hannibal2/skyhanni/data/MiningAPI.kt index 35855fc612d5..91b7e4b79350 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/MiningAPI.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/MiningAPI.kt @@ -41,7 +41,7 @@ import kotlin.time.Duration.Companion.seconds object MiningAPI { private val group = RepoPattern.group("data.miningapi") - private val glaciteAreaPattern by group.pattern("area.glacite", "Glacite Tunnels|Glacite Lake") + private val glaciteAreaPattern by group.pattern("area.glacite", "Glacite Tunnels|Great Glacite Lake") private val dwarvenBaseCampPattern by group.pattern("area.basecamp", "Dwarven Base Camp") // TODO rename to include suffix "pattern", add regex test diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/discordrpc/DiscordLocationKey.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/discordrpc/DiscordLocationKey.kt index 96759bc7d2e5..895a04688f80 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/discordrpc/DiscordLocationKey.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/discordrpc/DiscordLocationKey.kt @@ -159,7 +159,7 @@ object DiscordLocationKey { "Dwarven Base Camp" to "glacite-tunnels", "Fossil Research Center" to "glacite-tunnels", - "Glacite Lake" to "glacite-tunnels", + "Great Glacite Lake" to "glacite-tunnels", "Glacite Mineshafts" to "glacite-tunnels", ) // maps sublocations to their broader image From 4b34113c86f824000bbe9eebb5cc3fcdfe56b9ba Mon Sep 17 00:00:00 2001 From: David Cole <40234707+DavidArthurCole@users.noreply.github.com> Date: Mon, 23 Sep 2024 00:17:59 -0400 Subject: [PATCH 03/38] Backend: Add Helper Text to `/shtestmessage` (#2568) --- .../hannibal2/skyhanni/test/command/TestChatCommand.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/at/hannibal2/skyhanni/test/command/TestChatCommand.kt b/src/main/java/at/hannibal2/skyhanni/test/command/TestChatCommand.kt index e9d8d61453cd..fbb28b2724b1 100644 --- a/src/main/java/at/hannibal2/skyhanni/test/command/TestChatCommand.kt +++ b/src/main/java/at/hannibal2/skyhanni/test/command/TestChatCommand.kt @@ -12,7 +12,14 @@ object TestChatCommand { fun command(args: Array) { if (args.isEmpty()) { - ChatUtils.userError("Specify a chat message to test!") + val syntaxStrings = listOf( + "§7Syntax: §e/shtestmessage §7<§e-chat message§7> §7[-lines] [-complex] [-clipboard] [-s]", + " §7[-lines]§e: §7Split the message into multiple by newlines", + " §7[-complex]§e: §7Parse the message as a JSON chat component", + " §7[-clipboard]§e: §7Read the message from the clipboard", + " §7[-s]§e: §7Hide the output message", + ) + ChatUtils.userError("Specify a chat message to test!\n${syntaxStrings.joinToString("\n")}") return } From 8f7b79a0b4953bb64f342ccc21d962c8aacbc169 Mon Sep 17 00:00:00 2001 From: ThatGravyBoat Date: Mon, 23 Sep 2024 02:51:37 -0230 Subject: [PATCH 04/38] Backend: Add blossom and make source checker show version again (#2558) --- build.gradle.kts | 7 +++++++ root.gradle.kts | 1 + settings.gradle.kts | 1 + src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt | 2 +- .../hannibal2/skyhanni/tweaker/DownloadSourceChecker.java | 5 ++--- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index e8464ada27e8..447387f86a72 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,6 +18,7 @@ plugins { kotlin("plugin.power-assert") `maven-publish` id("moe.nea.shot") version "1.0.0" + id("net.kyori.blossom") } val target = ProjectTarget.values().find { it.projectPath == project.path }!! @@ -312,12 +313,18 @@ if (!MultiVersionStage.activeState.shouldCompile(target)) { onlyIf { false } } } + preprocess { vars.put("MC", target.minecraftVersion.versionNumber) vars.put("FORGE", if (target.forgeDep != null) 1 else 0) vars.put("JAVA", target.minecraftVersion.javaVersion) patternAnnotation.set("at.hannibal2.skyhanni.utils.compat.Pattern") } + +blossom { + replaceToken("@MOD_VERSION@", version) +} + val sourcesJar by tasks.creating(Jar::class) { destinationDirectory.set(layout.buildDirectory.dir("badjars")) archiveClassifier.set("src") diff --git a/root.gradle.kts b/root.gradle.kts index 84a371098d90..627d155ee866 100644 --- a/root.gradle.kts +++ b/root.gradle.kts @@ -3,6 +3,7 @@ import com.replaymod.gradle.preprocess.Node plugins { id("dev.deftu.gradle.preprocess") version "0.6.1" + id("net.kyori.blossom") version "1.3.2" apply false id("gg.essential.loom") version "1.6.+" apply false kotlin("jvm") version "2.0.0" apply false kotlin("plugin.power-assert") version "2.0.0" apply false diff --git a/settings.gradle.kts b/settings.gradle.kts index aaf0f02cf6d6..adee2acba161 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,6 +14,7 @@ pluginManagement { maven("https://repo.nea.moe/releases") maven("https://repo.sk1er.club/repository/maven-releases/") maven("https://maven.deftu.dev/releases") + maven("https://maven.teamresourceful.com/repository/maven-private/") // Blossom maven("https://jitpack.io") { content { includeGroupByRegex("(com|io)\\.github\\..*") diff --git a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt index 35f083580184..45d02f5445f6 100644 --- a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt +++ b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt @@ -42,7 +42,7 @@ import org.apache.logging.log4j.Logger clientSideOnly = true, useMetadata = true, guiFactory = "at.hannibal2.skyhanni.config.ConfigGuiForgeInterop", - version = "0.27.Beta.11", + version = "@MOD_VERSION@", ) class SkyHanniMod { diff --git a/src/main/java/at/hannibal2/skyhanni/tweaker/DownloadSourceChecker.java b/src/main/java/at/hannibal2/skyhanni/tweaker/DownloadSourceChecker.java index 5672ee87cd7e..63a92849bc62 100644 --- a/src/main/java/at/hannibal2/skyhanni/tweaker/DownloadSourceChecker.java +++ b/src/main/java/at/hannibal2/skyhanni/tweaker/DownloadSourceChecker.java @@ -14,6 +14,7 @@ public class DownloadSourceChecker { + private static final String MOD_VERSION = "@MOD_VERSION@"; private static final String GITHUB_REPO = "511310721"; private static final String GITHUB_REPO_TEXT = "repo_id=" + GITHUB_REPO; private static final String MODRINTH_URL = "/data/byNkmv5G/"; @@ -95,12 +96,10 @@ private static void openMenu(URI host) { } )); - // TODO FIX THIS CAUSING CLASS LOADING AND CRASHING - //String version = SkyHanniMod.Companion.getVersion(); JOptionPane.showOptionDialog( frame, String.format(String.join("\n", SECURITY_POPUP), uriToSimpleString(host)), - "SkyHanni Security Error", + "SkyHanni " + MOD_VERSION + " Security Error", JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE, null, From b0735ffc3ed655838de6d0d0eb6376cde92483d1 Mon Sep 17 00:00:00 2001 From: CalMWolfs <94038482+CalMWolfs@users.noreply.github.com> Date: Mon, 23 Sep 2024 15:48:19 +1000 Subject: [PATCH 05/38] Backend: Create and use primitive recipe and ingredient (#2460) Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com> --- .../features/bingo/FirstMinionTier.kt | 13 +++-- .../features/bingo/MinionCraftHelper.kt | 18 +++---- .../garden/visitor/GardenVisitorFeatures.kt | 8 +-- .../garden/visitor/GardenVisitorSupercraft.kt | 10 ++-- .../inventory/craft/CraftableItemList.kt | 6 +-- .../items/EstimatedItemValueCalculator.kt | 9 ++-- .../skyhanni/test/SkyHanniDebugsAndTests.kt | 2 +- .../skyhanni/utils/ItemPriceUtils.kt | 7 ++- .../at/hannibal2/skyhanni/utils/ItemUtils.kt | 10 ++-- .../at/hannibal2/skyhanni/utils/NEUItems.kt | 25 ++++----- .../skyhanni/utils/PrimitiveIngredient.kt | 26 +++++++++ .../skyhanni/utils/PrimitiveRecipe.kt | 53 +++++++++++++++++++ 12 files changed, 126 insertions(+), 61 deletions(-) create mode 100644 src/main/java/at/hannibal2/skyhanni/utils/PrimitiveIngredient.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/utils/PrimitiveRecipe.kt diff --git a/src/main/java/at/hannibal2/skyhanni/features/bingo/FirstMinionTier.kt b/src/main/java/at/hannibal2/skyhanni/features/bingo/FirstMinionTier.kt index e4bae4f05f8d..e94fb8d840d0 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/bingo/FirstMinionTier.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/bingo/FirstMinionTier.kt @@ -1,13 +1,12 @@ package at.hannibal2.skyhanni.features.bingo import at.hannibal2.skyhanni.test.command.ErrorManager -import at.hannibal2.skyhanni.utils.ItemUtils.name 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.NEUItems.getCachedIngredients +import at.hannibal2.skyhanni.utils.NEUItems.getItemStackOrNull +import at.hannibal2.skyhanni.utils.PrimitiveRecipe import at.hannibal2.skyhanni.utils.StringUtils.removeColor -import io.github.moulberry.notenoughupdates.recipes.CraftingRecipe object FirstMinionTier { @@ -30,20 +29,20 @@ object FirstMinionTier { ) { for (minionId in tierOneMinionsFiltered) { for (recipe in NEUItems.getRecipes(minionId)) { - if (recipe !is CraftingRecipe) continue + if (!recipe.isCraftingRecipe()) continue checkOne(recipe, help, minions, minionId) } } } private fun checkOne( - recipe: CraftingRecipe, + recipe: PrimitiveRecipe, help: Map, minions: MutableMap, minionId: NEUInternalName, ) { - if (recipe.getCachedIngredients().any { help.contains(it.internalItemId.asInternalName()) }) { - val name = recipe.output.itemStack.name.removeColor() + if (recipe.getCachedIngredients().any { help.contains(it.internalName) }) { + val name = recipe.output?.internalName?.getItemStackOrNull()?.displayName?.removeColor() ?: return val abc = name.replace(" I", " 0") minions[abc] = minionId.replace("_1", "_0") } diff --git a/src/main/java/at/hannibal2/skyhanni/features/bingo/MinionCraftHelper.kt b/src/main/java/at/hannibal2/skyhanni/features/bingo/MinionCraftHelper.kt index c12bafc39ea3..dbec4ea5788c 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/bingo/MinionCraftHelper.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/bingo/MinionCraftHelper.kt @@ -24,7 +24,6 @@ import at.hannibal2.skyhanni.utils.StringUtils.removeColor import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern import com.google.gson.JsonArray import com.google.gson.JsonObject -import io.github.moulberry.notenoughupdates.recipes.CraftingRecipe import net.minecraft.client.Minecraft import net.minecraft.item.ItemStack import net.minecraftforge.fml.common.eventhandler.SubscribeEvent @@ -139,7 +138,7 @@ object MinionCraftHelper { for (recipe in recipes) { for (ingredient in recipe.getCachedIngredients()) { - val ingredientInternalName = ingredient.internalItemId.asInternalName() + val ingredientInternalName = ingredient.internalName if (ingredientInternalName == internalName) return true val ingredientPrimitive = NEUItems.getPrimitiveMultiplier(ingredientInternalName) @@ -169,10 +168,10 @@ object MinionCraftHelper { if (internalName.contains("_GENERATOR_")) { for (recipe in NEUItems.getRecipes(internalName)) { - if (recipe !is CraftingRecipe) continue + if (!recipe.isCraftingRecipe()) continue for (ingredient in recipe.getCachedIngredients()) { - val id = ingredient.internalItemId.asInternalName() + val id = ingredient.internalName if (!id.contains("_GENERATOR_") && !allIngredients.contains(id)) { allIngredients.add(id) } @@ -194,13 +193,12 @@ object MinionCraftHelper { newDisplay.add(minionName) val nextMinionId = minionId.addOneToId() for (recipe in NEUItems.getRecipes(nextMinionId)) { - if (recipe !is CraftingRecipe) continue - val output = recipe.output - val internalItemId = output.internalItemId.asInternalName() - if (!internalItemId.contains("_GENERATOR_")) continue + if (!recipe.isCraftingRecipe()) continue + val output = recipe.output ?: continue + if (!output.internalName.contains("_GENERATOR_")) continue val map = mutableMapOf() - for (input in recipe.inputs) { - val itemId = input.internalItemId.asInternalName() + for (input in recipe.ingredients) { + val itemId = input.internalName if (minionId != itemId) { val count = input.count.toInt() val old = map.getOrDefault(itemId, 0) diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/visitor/GardenVisitorFeatures.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/visitor/GardenVisitorFeatures.kt index aed4f7e7cffc..2e8d789660ea 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/visitor/GardenVisitorFeatures.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/visitor/GardenVisitorFeatures.kt @@ -46,7 +46,6 @@ import at.hannibal2.skyhanni.utils.LorenzUtils.isInIsland 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.NEUItems.allIngredients import at.hannibal2.skyhanni.utils.NEUItems.getItemStack import at.hannibal2.skyhanni.utils.NEUItems.getPrice import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators @@ -250,13 +249,14 @@ object GardenVisitorFeatures { val ingredients = NEUItems.getRecipes(internalName) // TODO describe what this line does - .firstOrNull() { !it.allIngredients().first().internalItemId.contains("PEST") } - ?.allIngredients() ?: emptySet() + .firstOrNull { !it.ingredients.first().internalName.contains("PEST") } + ?.ingredients ?: emptySet() if (ingredients.isEmpty()) return + // TODO change key to NEUInternalName val requiredIngredients = mutableMapOf() for (ingredient in ingredients) { - val key = ingredient.internalItemId + val key = ingredient.internalName.asString() requiredIngredients[key] = requiredIngredients.getOrDefault(key, 0) + ingredient.count.toInt() } diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/visitor/GardenVisitorSupercraft.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/visitor/GardenVisitorSupercraft.kt index f441d17ab6a1..26bfa7f26409 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/visitor/GardenVisitorSupercraft.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/visitor/GardenVisitorSupercraft.kt @@ -8,14 +8,11 @@ import at.hannibal2.skyhanni.events.render.gui.ReplaceItemEvent import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.test.command.ErrorManager import at.hannibal2.skyhanni.utils.HypixelCommands -import at.hannibal2.skyhanni.utils.ItemUtils.itemName 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.NEUItems.allIngredients import at.hannibal2.skyhanni.utils.NEUItems.getItemStack import at.hannibal2.skyhanni.utils.SimpleTimeMark -import at.hannibal2.skyhanni.utils.StringUtils.removeColor import io.github.moulberry.notenoughupdates.util.Utils import net.minecraft.entity.player.InventoryPlayer import net.minecraftforge.fml.common.eventhandler.EventPriority @@ -74,11 +71,12 @@ object GardenVisitorSupercraft { private fun getSupercraftForSacks(internalName: NEUInternalName, amount: Int) { val ingredients = NEUItems.getRecipes(internalName) // TODO describe what this line does - .firstOrNull { !it.allIngredients().first().internalItemId.contains("PEST") } - ?.allIngredients() ?: return + .firstOrNull { !it.ingredients.first().internalName.contains("PEST") } + ?.ingredients ?: return + // TODO change key to NEUInternalName val ingredientReqs = mutableMapOf() for (ingredient in ingredients) { - val key = ingredient.internalItemId + val key = ingredient.internalName.asString() ingredientReqs[key] = ingredientReqs.getOrDefault(key, 0) + ingredient.count.toInt() } hasIngredients = true diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/craft/CraftableItemList.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/craft/CraftableItemList.kt index 6b841f5a40d0..8612c9795cfc 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/inventory/craft/CraftableItemList.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/craft/CraftableItemList.kt @@ -20,12 +20,12 @@ import at.hannibal2.skyhanni.utils.NEUItems.isVanillaItem import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators import at.hannibal2.skyhanni.utils.NumberUtil.shortFormat import at.hannibal2.skyhanni.utils.PrimitiveItemStack.Companion.toPrimitiveStackOrNull +import at.hannibal2.skyhanni.utils.PrimitiveRecipe import at.hannibal2.skyhanni.utils.RegexUtils.matches import at.hannibal2.skyhanni.utils.RenderUtils.renderRenderables import at.hannibal2.skyhanni.utils.StringUtils import at.hannibal2.skyhanni.utils.renderables.Renderable import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern -import io.github.moulberry.notenoughupdates.recipes.CraftingRecipe import net.minecraftforge.fml.common.eventhandler.SubscribeEvent import kotlin.math.floor @@ -71,7 +71,7 @@ object CraftableItemList { val recipes = NEUItems.getRecipes(internalName) for (recipe in recipes) { - if (recipe !is CraftingRecipe) continue + if (!recipe.isCraftingRecipe()) continue val renderable = createItemRenderable(recipe, availableMaterial, pricePer, internalName) ?: continue lines[internalName] = renderable } @@ -79,7 +79,7 @@ object CraftableItemList { } private fun createItemRenderable( - recipe: CraftingRecipe, + recipe: PrimitiveRecipe, availableMaterial: Map, pricePer: MutableMap, internalName: NEUInternalName, 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 953809b963d9..81e12f91349a 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 @@ -20,13 +20,13 @@ import at.hannibal2.skyhanni.utils.ItemUtils.name 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 at.hannibal2.skyhanni.utils.NEUItems.getItemStackOrNull import at.hannibal2.skyhanni.utils.NEUItems.getNpcPriceOrNull import at.hannibal2.skyhanni.utils.NEUItems.getPriceOrNull import at.hannibal2.skyhanni.utils.NEUItems.getRawCraftCostOrNull import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators import at.hannibal2.skyhanni.utils.NumberUtil.shortFormat +import at.hannibal2.skyhanni.utils.PrimitiveIngredient import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.getAbilityScrolls import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.getArmorDye @@ -58,7 +58,6 @@ import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.hasJalapenoBook import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.hasWoodSingularity import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.isRecombobulated import at.hannibal2.skyhanni.utils.StringUtils.allLettersFirstUppercase -import io.github.moulberry.notenoughupdates.recipes.Ingredient import io.github.notenoughupdates.moulconfig.observer.Property import net.minecraft.item.ItemStack import java.util.Locale @@ -889,12 +888,12 @@ object EstimatedItemValueCalculator { val previousTotal = totalPrice for (ingredients in slot.value) { - val ingredient = Ingredient(NEUItems.manager, ingredients) + val ingredient = PrimitiveIngredient(ingredients) - totalPrice += if (ingredient.isCoins) { + totalPrice += if (ingredient.isCoin()) { ingredient.count } else { - ingredient.internalItemId.asInternalName().getPrice() * ingredient.count + ingredient.internalName.getPrice() * ingredient.count } } diff --git a/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt b/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt index 045ce5bce1c2..32b287cb96a2 100644 --- a/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt +++ b/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt @@ -449,7 +449,7 @@ object SkyHanniDebugsAndTests { } @SubscribeEvent - fun onSHowCraftPrice(event: LorenzToolTipEvent) { + fun onShowCraftPrice(event: LorenzToolTipEvent) { if (!LorenzUtils.inSkyBlock) return if (!debugConfig.showCraftPrice) return val price = event.itemStack.getInternalNameOrNull()?.getRawCraftCostOrNull() ?: return diff --git a/src/main/java/at/hannibal2/skyhanni/utils/ItemPriceUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/ItemPriceUtils.kt index b12f3640e07f..66775b2d63fb 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/ItemPriceUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/ItemPriceUtils.kt @@ -9,18 +9,17 @@ import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName import at.hannibal2.skyhanni.utils.NEUItems.getItemStackOrNull import at.hannibal2.skyhanni.utils.NEUItems.getRecipes import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators -import io.github.moulberry.notenoughupdates.recipes.NeuRecipe object ItemPriceUtils { fun NEUInternalName.getPrice( priceSource: ItemPriceSource = ItemPriceSource.BAZAAR_INSTANT_BUY, - pastRecipes: List = emptyList(), + pastRecipes: List = emptyList(), ) = getPriceOrNull(priceSource, pastRecipes) ?: 0.0 fun NEUInternalName.getPriceOrNull( priceSource: ItemPriceSource = ItemPriceSource.BAZAAR_INSTANT_BUY, - pastRecipes: List = emptyList(), + pastRecipes: List = emptyList(), ): Double? { when (this) { NEUInternalName.JASPER_CRYSTAL -> return 0.0 @@ -60,7 +59,7 @@ object ItemPriceUtils { // NEUItems.manager.auctionManager.getCraftCost(asString())?.craftCost fun NEUInternalName.getRawCraftCostOrNull( priceSource: ItemPriceSource = ItemPriceSource.BAZAAR_INSTANT_BUY, - pastRecipes: List = emptyList(), + pastRecipes: List = emptyList(), ): Double? = getRecipes(this).filter { it !in pastRecipes } .map { it.getRecipePrice(priceSource, pastRecipes + it) } .filter { it >= 0 } diff --git a/src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt index cc1a4362b070..ac35c07418ba 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt @@ -7,7 +7,6 @@ import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.test.command.ErrorManager import at.hannibal2.skyhanni.utils.CollectionUtils.addOrPut import at.hannibal2.skyhanni.utils.ItemPriceUtils.getPrice -import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName import at.hannibal2.skyhanni.utils.NEUItems.getItemStackOrNull import at.hannibal2.skyhanni.utils.NumberUtil.formatInt import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher @@ -20,7 +19,6 @@ import at.hannibal2.skyhanni.utils.StringUtils.removeColor import at.hannibal2.skyhanni.utils.StringUtils.removeResets import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern import com.google.common.collect.Lists -import io.github.moulberry.notenoughupdates.recipes.NeuRecipe import io.github.moulberry.notenoughupdates.util.NotificationHandler import net.minecraft.client.Minecraft import net.minecraft.init.Items @@ -463,19 +461,19 @@ object ItemUtils { return list } - fun neededItems(recipe: NeuRecipe): Map { + fun neededItems(recipe: PrimitiveRecipe): Map { val neededItems = mutableMapOf() for (ingredient in recipe.ingredients) { - val material = ingredient.internalItemId.asInternalName() + val material = ingredient.internalName val amount = ingredient.count.toInt() neededItems.addOrPut(material, amount) } return neededItems } - fun NeuRecipe.getRecipePrice( + fun PrimitiveRecipe.getRecipePrice( priceSource: ItemPriceSource = ItemPriceSource.BAZAAR_INSTANT_BUY, - pastRecipes: List = emptyList(), + pastRecipes: List = emptyList(), ): Double = neededItems(this).map { it.key.getPrice(priceSource, pastRecipes) * it.value }.sum() diff --git a/src/main/java/at/hannibal2/skyhanni/utils/NEUItems.kt b/src/main/java/at/hannibal2/skyhanni/utils/NEUItems.kt index 81eb2968cce3..34d491ceea81 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/NEUItems.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/NEUItems.kt @@ -30,9 +30,6 @@ import io.github.moulberry.notenoughupdates.NotEnoughUpdates import io.github.moulberry.notenoughupdates.events.ProfileDataLoadedEvent import io.github.moulberry.notenoughupdates.overlays.AuctionSearchOverlay import io.github.moulberry.notenoughupdates.overlays.BazaarSearchOverlay -import io.github.moulberry.notenoughupdates.recipes.CraftingRecipe -import io.github.moulberry.notenoughupdates.recipes.Ingredient -import io.github.moulberry.notenoughupdates.recipes.NeuRecipe import io.github.moulberry.notenoughupdates.util.ItemResolutionQuery import io.github.moulberry.notenoughupdates.util.Utils import net.minecraft.client.Minecraft @@ -57,8 +54,8 @@ object NEUItems { val manager: NEUManager get() = NotEnoughUpdates.INSTANCE.manager private val multiplierCache = mutableMapOf() - private val recipesCache = mutableMapOf>() - private val ingredientsCache = mutableMapOf>() + private val recipesCache = mutableMapOf>() + private val ingredientsCache = mutableMapOf>() private val itemIdCache = mutableMapOf>() private val hypixelApiGson by lazy { @@ -183,7 +180,7 @@ object NEUItems { @Deprecated("Moved to ItemPriceUtils", ReplaceWith("")) fun NEUInternalName.getPrice( priceSource: ItemPriceSource = ItemPriceSource.BAZAAR_INSTANT_BUY, - pastRecipes: List = emptyList(), + pastRecipes: List = emptyList(), ): Double = getPriceNew(priceSource, pastRecipes) @Deprecated("Moved to ItemPriceUtils", ReplaceWith("")) @@ -198,11 +195,11 @@ object NEUItems { @Deprecated("Moved to ItemPriceUtils", ReplaceWith("")) fun NEUInternalName.getPriceOrNull( priceSource: ItemPriceSource = ItemPriceSource.BAZAAR_INSTANT_BUY, - pastRecipes: List = emptyList(), + pastRecipes: List = emptyList(), ): Double? = this.getPriceOrNullNew(priceSource, pastRecipes) @Deprecated("Moved to ItemPriceUtils", ReplaceWith("")) - fun NEUInternalName.getRawCraftCostOrNull(pastRecipes: List = emptyList()): Double? = + fun NEUInternalName.getRawCraftCostOrNull(pastRecipes: List = emptyList()): Double? = getRawCraftCostOrNullNew(ItemPriceSource.BAZAAR_INSTANT_BUY, pastRecipes) fun NEUInternalName.getItemStackOrNull(): ItemStack? = ItemResolutionQuery(manager) @@ -309,12 +306,12 @@ object NEUItems { return internalName.makePrimitiveStack() } for (recipe in getRecipes(internalName)) { - if (recipe !is CraftingRecipe) continue + if (!recipe.isCraftingRecipe()) continue val map = mutableMapOf() for (ingredient in recipe.getCachedIngredients()) { val count = ingredient.count.toInt() - var internalItemId = ingredient.internalItemId.asInternalName() + var internalItemId = ingredient.internalName // ignore cactus green if (internalName == "ENCHANTED_CACTUS_GREEN".asInternalName() && internalItemId == "INK_SACK-2".asInternalName()) { internalItemId = "CACTUS".asInternalName() @@ -356,13 +353,13 @@ object NEUItems { return result } - fun getRecipes(internalName: NEUInternalName): Set { + fun getRecipes(internalName: NEUInternalName): Set { return recipesCache.getOrPut(internalName) { - manager.getRecipesFor(internalName.asString()) + PrimitiveRecipe.convertMultiple(manager.getRecipesFor(internalName.asString())).toSet() } } - fun NeuRecipe.getCachedIngredients() = ingredientsCache.getOrPut(this) { allIngredients() } + fun PrimitiveRecipe.getCachedIngredients() = ingredientsCache.getOrPut(this) { ingredients } fun neuHasFocus(): Boolean { if (AuctionSearchOverlay.shouldReplace()) return true @@ -391,6 +388,4 @@ object NEUItems { val jsonObject = ConfigManager.gson.fromJson(jsonString, JsonObject::class.java) return manager.jsonToStack(jsonObject, false) } - - fun NeuRecipe.allIngredients(): Set = ingredients } diff --git a/src/main/java/at/hannibal2/skyhanni/utils/PrimitiveIngredient.kt b/src/main/java/at/hannibal2/skyhanni/utils/PrimitiveIngredient.kt new file mode 100644 index 000000000000..e8d1edefcc44 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/PrimitiveIngredient.kt @@ -0,0 +1,26 @@ +package at.hannibal2.skyhanni.utils + +import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.SKYBLOCK_COIN +import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName +import io.github.moulberry.notenoughupdates.recipes.Ingredient + +class PrimitiveIngredient(val internalName: NEUInternalName, val count: Double = 1.0) { + + constructor(internalName: NEUInternalName, count: Int) : this(internalName, count.toDouble()) + + constructor(ingredientIdentifier: String) : this( + ingredientIdentifier.substringBefore(':').asInternalName(), + ingredientIdentifier.substringAfter(':').toDoubleOrNull() ?: 1.0, + ) + + companion object { + fun coinIngredient(count: Double = 1.0) = PrimitiveIngredient(SKYBLOCK_COIN, count) + + fun fromNeuIngredient(neuIngredient: Ingredient) = PrimitiveIngredient(neuIngredient.internalItemId.asInternalName(), neuIngredient.count) + } + + fun isCoin() = internalName == SKYBLOCK_COIN + + + override fun toString() = "$internalName x$count" +} diff --git a/src/main/java/at/hannibal2/skyhanni/utils/PrimitiveRecipe.kt b/src/main/java/at/hannibal2/skyhanni/utils/PrimitiveRecipe.kt new file mode 100644 index 000000000000..f064183ba372 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/PrimitiveRecipe.kt @@ -0,0 +1,53 @@ +package at.hannibal2.skyhanni.utils + +import at.hannibal2.skyhanni.utils.RecipeType.CRAFTING +import io.github.moulberry.notenoughupdates.recipes.EssenceUpgrades +import io.github.moulberry.notenoughupdates.recipes.ForgeRecipe +import io.github.moulberry.notenoughupdates.recipes.ItemShopRecipe +import io.github.moulberry.notenoughupdates.recipes.KatRecipe +import io.github.moulberry.notenoughupdates.recipes.MobLootRecipe +import io.github.moulberry.notenoughupdates.recipes.NeuRecipe +import io.github.moulberry.notenoughupdates.recipes.VillagerTradeRecipe + +data class PrimitiveRecipe( + val ingredients: Set, + val outputs: Set, + val recipeType: RecipeType, +) { + + val output by lazy { outputs.firstOrNull() } + + companion object { + fun fromNeuRecipe(neuRecipe: NeuRecipe): PrimitiveRecipe { + val ingredients = neuRecipe.ingredients.map { PrimitiveIngredient.fromNeuIngredient(it) }.toSet() + val outputs = neuRecipe.outputs.map { PrimitiveIngredient.fromNeuIngredient(it) }.toSet() + + val recipeType = when (neuRecipe::class.java) { + ForgeRecipe::class.java -> RecipeType.FORGE + VillagerTradeRecipe::class.java -> RecipeType.TRADE + EssenceUpgrades::class.java -> RecipeType.ESSENCE + MobLootRecipe::class.java -> RecipeType.MOB_DROP + ItemShopRecipe::class.java -> RecipeType.NPC_SHOP + KatRecipe::class.java -> RecipeType.KAT_UPGRADE + else -> CRAFTING + } + + return PrimitiveRecipe(ingredients, outputs, recipeType) + } + + fun convertMultiple(neuRecipes: Collection) = neuRecipes.map { fromNeuRecipe(it) } + } + + fun isCraftingRecipe() = this.recipeType == CRAFTING +} + +enum class RecipeType { + FORGE, + TRADE, + MOB_DROP, + NPC_SHOP, + KAT_UPGRADE, + ESSENCE, + CRAFTING, + ; +} From 143d1521bc3a5ff02e071e0c771aeb5f74e49db8 Mon Sep 17 00:00:00 2001 From: hannibal2 <24389977+hannibal00212@users.noreply.github.com> Date: Mon, 23 Sep 2024 08:26:10 +0200 Subject: [PATCH 06/38] added todo --- .../hannibal2/skyhanni/features/dungeon/DungeonFinderFeatures.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/at/hannibal2/skyhanni/features/dungeon/DungeonFinderFeatures.kt b/src/main/java/at/hannibal2/skyhanni/features/dungeon/DungeonFinderFeatures.kt index 508296d4974c..a710e4fd0e94 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/dungeon/DungeonFinderFeatures.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/dungeon/DungeonFinderFeatures.kt @@ -275,6 +275,7 @@ object DungeonFinderFeatures { if (!partyFinderTitlePattern.matches(inventoryName)) return map inInventory = true for ((slot, stack) in event.inventoryItems) { + // TODO use enum val classNames = mutableListOf("Healer", "Mage", "Berserk", "Archer", "Tank") val toolTip = stack.getLore().toMutableList() for ((index, line) in stack.getLore().withIndex()) { From 91dee900cc7bc40db5e936d2e9f2f18dcbbe870f Mon Sep 17 00:00:00 2001 From: Thunderblade73 <85900443+Thunderblade73@users.noreply.github.com> Date: Mon, 23 Sep 2024 08:26:32 +0200 Subject: [PATCH 07/38] Internal: Shared Live Templates for common structures (#2463) --- .gitignore | 1 + .idea/liveTemplates/SkyHanni.xml | 101 +++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 .idea/liveTemplates/SkyHanni.xml diff --git a/.gitignore b/.gitignore index d77f4bd8b6f0..7f4081e02e52 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ !.idea/icon.svg !.idea/dictionaries/default_user.xml !.idea/scopes/Mixins.xml +!.idea/liveTemplates/SkyHanni.xml .vscode/ run/ build/ diff --git a/.idea/liveTemplates/SkyHanni.xml b/.idea/liveTemplates/SkyHanni.xml new file mode 100644 index 000000000000..08c6737b5ce2 --- /dev/null +++ b/.idea/liveTemplates/SkyHanni.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + \ No newline at end of file From 478e5c6ac5f587dbf5f6f45f6489cbe37b05fd2f Mon Sep 17 00:00:00 2001 From: hannibal2 <24389977+hannibal002@users.noreply.github.com> Date: Mon, 23 Sep 2024 15:10:19 +0200 Subject: [PATCH 08/38] Improvement: Glacite Tunnels Area Navigation (#2544) Co-authored-by: nea Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com> --- .../skyhanni/api/event/SkyHanniEvent.kt | 3 + .../hannibal2/skyhanni/data/IslandGraphs.kt | 77 +++++++++++++++++-- .../hannibal2/skyhanni/events/LorenzEvent.kt | 5 ++ .../skyhanni/features/mining/TunnelsMaps.kt | 10 ++- .../skyhanni/features/misc/IslandAreas.kt | 4 +- 5 files changed, 89 insertions(+), 10 deletions(-) diff --git a/src/main/java/at/hannibal2/skyhanni/api/event/SkyHanniEvent.kt b/src/main/java/at/hannibal2/skyhanni/api/event/SkyHanniEvent.kt index 4f3685c8bdff..c05b438cde20 100644 --- a/src/main/java/at/hannibal2/skyhanni/api/event/SkyHanniEvent.kt +++ b/src/main/java/at/hannibal2/skyhanni/api/event/SkyHanniEvent.kt @@ -1,5 +1,8 @@ package at.hannibal2.skyhanni.api.event +/** + * Use @[HandleEvent] + */ abstract class SkyHanniEvent protected constructor() { var isCancelled: Boolean = false diff --git a/src/main/java/at/hannibal2/skyhanni/data/IslandGraphs.kt b/src/main/java/at/hannibal2/skyhanni/data/IslandGraphs.kt index ca3143b952ab..9e4639454361 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/IslandGraphs.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/IslandGraphs.kt @@ -1,6 +1,7 @@ package at.hannibal2.skyhanni.data import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.api.event.HandleEvent import at.hannibal2.skyhanni.data.model.Graph import at.hannibal2.skyhanni.data.model.GraphNode import at.hannibal2.skyhanni.data.model.findShortestPathAsGraphWithDistance @@ -10,23 +11,29 @@ import at.hannibal2.skyhanni.events.LorenzRenderWorldEvent import at.hannibal2.skyhanni.events.LorenzTickEvent import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent import at.hannibal2.skyhanni.events.RepositoryReloadEvent +import at.hannibal2.skyhanni.events.skyblock.ScoreboardAreaChangeEvent import at.hannibal2.skyhanni.features.misc.IslandAreas import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.test.SkyHanniDebugsAndTests +import at.hannibal2.skyhanni.utils.DelayedRun import at.hannibal2.skyhanni.utils.LocationUtils import at.hannibal2.skyhanni.utils.LocationUtils.canBeSeen import at.hannibal2.skyhanni.utils.LocationUtils.distanceSqToPlayer import at.hannibal2.skyhanni.utils.LocationUtils.distanceToPlayer import at.hannibal2.skyhanni.utils.LorenzColor import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.isInIsland import at.hannibal2.skyhanni.utils.LorenzVec +import at.hannibal2.skyhanni.utils.RegexUtils.matches import at.hannibal2.skyhanni.utils.RenderUtils.draw3DLine import at.hannibal2.skyhanni.utils.RenderUtils.draw3DPathWithWaypoint import at.hannibal2.skyhanni.utils.RenderUtils.drawWaypointFilled +import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern import net.minecraftforge.fml.common.eventhandler.SubscribeEvent import java.awt.Color import java.io.File import kotlin.math.abs +import kotlin.time.Duration.Companion.milliseconds /** * TODO @@ -101,26 +108,73 @@ object IslandGraphs { private var fastestPath: Graph? = null private var condition: () -> Boolean = { true } + private var inGlaciteTunnels: Boolean? = null + + private val patternGroup = RepoPattern.group("data.island.navigation") + + /** + * REGEX-TEST: Dwarven Base Camp + * REGEX-TEST: Forge + * REGEX-TEST: Fossil Research Center + */ + private val glaciteTunnelsPattern by patternGroup.pattern( + "glacitetunnels", + "(Glacite Tunnels|Dwarven Base Camp|Great Glacite Lake|Fossil Research Center)", + ) @SubscribeEvent fun onRepoReload(event: RepositoryReloadEvent) { if (!LorenzUtils.inSkyBlock) return - reloadFromJson(LorenzUtils.skyBlockIsland) + loadIsland(LorenzUtils.skyBlockIsland) } @SubscribeEvent fun onIslandChange(event: IslandChangeEvent) { - reloadFromJson(event.newIsland) + if (currentIslandGraph != null) return + if (event.newIsland == IslandType.NONE) return + loadIsland(event.newIsland) } @SubscribeEvent fun onWorldChange(event: LorenzWorldChangeEvent) { + currentIslandGraph = null reset() } - private fun reloadFromJson(newIsland: IslandType) { - val islandName = newIsland.name + fun isGlaciteTunnelsArea(area: String?): Boolean = glaciteTunnelsPattern.matches(area) + + @HandleEvent + fun onAreaChange(event: ScoreboardAreaChangeEvent) { + if (!IslandType.DWARVEN_MINES.isInIsland()) { + inGlaciteTunnels = null + return + } + + val now = isGlaciteTunnelsArea(LorenzUtils.skyBlockArea) + if (inGlaciteTunnels != now) { + inGlaciteTunnels = now + loadDwarvenMines() + } + } + + private fun loadDwarvenMines() { + if (isGlaciteTunnelsArea(LorenzUtils.skyBlockArea)) { + reloadFromJson("GLACITE_TUNNELS") + } else { + reloadFromJson("DWARVEN_MINES") + } + } + + private fun loadIsland(newIsland: IslandType) { + if (newIsland == IslandType.DWARVEN_MINES) { + loadDwarvenMines() + } else { + reloadFromJson(newIsland.name) + } + } + + private fun reloadFromJson(islandName: String) { val constant = "island_graphs/$islandName" val name = "constants/$constant.json" val jsonFile = File(SkyHanniMod.repo.repoLocation, name) @@ -136,6 +190,13 @@ object IslandGraphs { fun setNewGraph(graph: Graph) { reset() currentIslandGraph = graph + + // calling various update functions to make swtiching between deep caverns and glacite tunnels bareable + handleTick() + IslandAreas.noteMoved() + DelayedRun.runDelayed(150.milliseconds) { + IslandAreas.updatePosition() + } } private fun reset() { @@ -143,11 +204,16 @@ object IslandGraphs { currentTarget = null goal = null fastestPath = null + IslandAreas.display = null } @SubscribeEvent fun onTick(event: LorenzTickEvent) { if (!LorenzUtils.inSkyBlock) return + handleTick() + } + + private fun handleTick() { val prevClosed = closedNote val graph = currentIslandGraph ?: return @@ -294,8 +360,7 @@ object IslandGraphs { val nodes = graph.nodes val potentialSkip = - nodes.lastOrNull { it.position.canBeSeen(maxSkipDistance, -1.0) && abs(it.position.y - playerY) <= 2 } - ?: return null + nodes.lastOrNull { it.position.canBeSeen(maxSkipDistance, -1.0) && abs(it.position.y - playerY) <= 2 } ?: return null val angleSkip = if (potentialSkip == nodes.first()) { false diff --git a/src/main/java/at/hannibal2/skyhanni/events/LorenzEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/LorenzEvent.kt index c895b1058d7c..4a294a6779ca 100644 --- a/src/main/java/at/hannibal2/skyhanni/events/LorenzEvent.kt +++ b/src/main/java/at/hannibal2/skyhanni/events/LorenzEvent.kt @@ -12,6 +12,11 @@ import at.hannibal2.skyhanni.utils.system.PlatformUtils import net.minecraftforge.common.MinecraftForge import net.minecraftforge.fml.common.eventhandler.Event import net.minecraftforge.fml.common.eventhandler.IEventListener +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +/** + * Use @[SubscribeEvent] + */ @Deprecated("Use SkyHanniEvent instead") abstract class LorenzEvent : Event() { diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/TunnelsMaps.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/TunnelsMaps.kt index 847a3673f5ad..5214ff162eb5 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/mining/TunnelsMaps.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/TunnelsMaps.kt @@ -81,6 +81,7 @@ object TunnelsMaps { private var active: String = "" private lateinit var fairySouls: Map + // TODO what is this? why is there a difference? can this be replaced with GraphNodeTag.GRIND_ORES? private lateinit var newGemstones: Map> private lateinit var oldGemstones: Map> private lateinit var normalLocations: Map> @@ -221,7 +222,7 @@ object TunnelsMaps { @SubscribeEvent fun onRepoReload(event: RepositoryReloadEvent) { - graph = event.getConstant("TunnelsGraph", gson = Graph.gson) + graph = event.getConstant("island_graphs/GLACITE_TUNNELS", gson = Graph.gson) possibleLocations = graph.groupBy { it.name }.filterNotNullKeys().mapValues { (_, value) -> value } @@ -235,7 +236,12 @@ object TunnelsMaps { key.contains("Fairy") -> fairy[key] = value.first() newGemstonePattern.matches(key) -> newGemstone[key] = value oldGemstonePattern.matches(key) -> oldGemstone[key] = value - else -> other[key] = value + else -> { + // ignore node names without color codes + if (key.removeColor() != key) { + other[key] = value + } + } } } fairySouls = fairy diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/IslandAreas.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/IslandAreas.kt index 311af056eab0..d5e57d513380 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/IslandAreas.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/IslandAreas.kt @@ -41,7 +41,7 @@ object IslandAreas { private var nodes = mapOf() private var paths = mapOf() - private var display: Renderable? = null + var display: Renderable? = null private var targetNode: GraphNode? = null private var currentAreaName = "" private val textInput = TextInput() @@ -103,7 +103,7 @@ object IslandAreas { } } - private fun updatePosition() { + fun updatePosition() { display = buildDisplay().buildSearchBox(textInput) } From 4fe133f51a84d49ebc3d7444e010c7de474fecef Mon Sep 17 00:00:00 2001 From: hannibal2 <24389977+hannibal00212@users.noreply.github.com> Date: Mon, 23 Sep 2024 15:10:48 +0200 Subject: [PATCH 09/38] removed unnecessary debug --- .../features/misc/items/EstimatedItemValueCalculator.kt | 5 ----- .../java/at/hannibal2/skyhanni/utils/EssenceItemUtils.kt | 6 ------ 2 files changed, 11 deletions(-) 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 81e12f91349a..b0fbc2f31f83 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 @@ -498,11 +498,6 @@ object EstimatedItemValueCalculator { if (remainingStars <= 0) continue val price = getPriceFor(prices, remainingStars) ?: return null - println(" ") - println("price for $id ($remainingStars)") - println("price.itemPrice: ${price.itemPrice}") - println("essencePrice: ${price.essencePrice}") - println("itemPrice: ${price.itemPrice}") finalPrice = finalPrice?.let { it + price } ?: price remainingStars -= prices.size } diff --git a/src/main/java/at/hannibal2/skyhanni/utils/EssenceItemUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/EssenceItemUtils.kt index c63fb7177a14..b4b9f30b1f2f 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/EssenceItemUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/EssenceItemUtils.kt @@ -1,6 +1,5 @@ package at.hannibal2.skyhanni.utils -import at.hannibal2.skyhanni.config.ConfigManager import at.hannibal2.skyhanni.data.jsonobjects.repo.neu.NeuEssenceCostJson import at.hannibal2.skyhanni.events.NeuRepositoryReloadEvent import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule @@ -16,11 +15,6 @@ object EssenceItemUtils { @SubscribeEvent fun onNeuRepoReload(event: NeuRepositoryReloadEvent) { val unformattedData = event.getConstant>("essencecosts", NeuEssenceCostJson.TYPE) - - ConfigManager.gson.toJson(unformattedData).let { - ChatUtils.debug("saved to clipboard!") - OSUtils.copyToClipboard(it) - } this.itemPrices = reformatData(unformattedData) } From c24b433a0cfeb662d5ccaf63ff7e4f6a06f9be4a Mon Sep 17 00:00:00 2001 From: NopoTheGamer <40329022+NopoTheGamer@users.noreply.github.com> Date: Mon, 23 Sep 2024 23:30:53 +1000 Subject: [PATCH 10/38] Fix: Mining Commissions Blocks Color connected textures v2 (#2577) --- .../mixins/transformers/MixinOptifineConnectedTextures.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/at/hannibal2/skyhanni/mixins/transformers/MixinOptifineConnectedTextures.java b/src/main/java/at/hannibal2/skyhanni/mixins/transformers/MixinOptifineConnectedTextures.java index 9f3087e8e0ed..47ebe4ea63cf 100644 --- a/src/main/java/at/hannibal2/skyhanni/mixins/transformers/MixinOptifineConnectedTextures.java +++ b/src/main/java/at/hannibal2/skyhanni/mixins/transformers/MixinOptifineConnectedTextures.java @@ -16,8 +16,8 @@ private static IBlockState modifyGetConnectedTextureMultiPass(IBlockState state) return OptifineConnectedTexturesHookKt.modifyConnectedTexturesBlockState(state); } - @ModifyArg(method = "getConnectedTexture", at = @At(value = "INVOKE", target = "skipConnectedTexture")) - private static IBlockState modifySkipConnectedTexture(IBlockState state) { + @ModifyArg(method = "isNeighbour", at = @At(value = "INVOKE", target = "isNeighbour"), index = 4) + private static IBlockState modifyIsNeighbour(IBlockState state) { return OptifineConnectedTexturesHookKt.modifyConnectedTexturesBlockState(state); } } From 8edd0070a55d422e6470aaed09ab72d8bc952578 Mon Sep 17 00:00:00 2001 From: Empa <42304516+ItsEmpa@users.noreply.github.com> Date: Mon, 23 Sep 2024 15:45:21 +0200 Subject: [PATCH 11/38] Improvement: Add Pickobulus support to OreMineEvent (#2540) Co-authored-by: ItsEmpa Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com> --- .../at/hannibal2/skyhanni/data/MiningAPI.kt | 124 ++++++++++++++++-- .../skyhanni/events/mining/OreMinedEvent.kt | 2 +- .../features/mining/MineshaftPityDisplay.kt | 7 +- .../skyhanni/test/SkyHanniDebugsAndTests.kt | 4 +- 4 files changed, 118 insertions(+), 19 deletions(-) diff --git a/src/main/java/at/hannibal2/skyhanni/data/MiningAPI.kt b/src/main/java/at/hannibal2/skyhanni/data/MiningAPI.kt index 91b7e4b79350..9d635b1b9a22 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/MiningAPI.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/MiningAPI.kt @@ -24,7 +24,9 @@ import at.hannibal2.skyhanni.utils.LorenzUtils import at.hannibal2.skyhanni.utils.LorenzUtils.inAnyIsland import at.hannibal2.skyhanni.utils.LorenzUtils.isInIsland import at.hannibal2.skyhanni.utils.LorenzVec +import at.hannibal2.skyhanni.utils.NumberUtil.formatInt import at.hannibal2.skyhanni.utils.RegexUtils.firstMatcher +import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher import at.hannibal2.skyhanni.utils.RegexUtils.matches import at.hannibal2.skyhanni.utils.SimpleTimeMark import at.hannibal2.skyhanni.utils.TimeUtils.format @@ -44,13 +46,44 @@ object MiningAPI { private val glaciteAreaPattern by group.pattern("area.glacite", "Glacite Tunnels|Great Glacite Lake") private val dwarvenBaseCampPattern by group.pattern("area.basecamp", "Dwarven Base Camp") - // TODO rename to include suffix "pattern", add regex test - private val coldReset by group.pattern( + // TODO add regex test + private val coldResetPattern by group.pattern( "cold.reset", - "§6The warmth of the campfire reduced your §r§b❄ Cold §r§6to §r§a0§r§6!|§c ☠ §r§7You froze to death§r§7.", + "§6The warmth of the campfire reduced your §r§b❄ Cold §r§6to §r§a0§r§6!|§c ☠ §r§7You froze to death§r§7\\.", ) - private data class MinedBlock(val ore: OreBlock, var confirmed: Boolean, val time: SimpleTimeMark = SimpleTimeMark.now()) + private val pickbobulusGroup = group.group("pickobulus") + + /** + * REGEX-TEST: §aYou used your §r§6Pickobulus §r§aPickaxe Ability! + */ + + private val pickobulusUsePattern by pickbobulusGroup.pattern( + "use", + "§aYou used your §r§6Pickobulus §r§aPickaxe Ability!", + ) + + // TODO add regex test + private val pickobulusEndPattern by pickbobulusGroup.pattern( + "end", + "§7Your §r§aPickobulus §r§7destroyed §r§e(?[\\d,.]+) §r§7blocks!", + ) + + /** + * REGEX-TEST: §7Your §r§aPickobulus §r§7didn't destroy any blocks! + */ + private val pickobulusFailPattern by pickbobulusGroup.pattern( + "fail", + "§7Your §r§aPickobulus §r§7didn't destroy any blocks!" + ) + + private data class MinedBlock(val ore: OreBlock, var confirmed: Boolean) { + val time: SimpleTimeMark = SimpleTimeMark.now() + } + + // normal mining + private val recentClickedBlocks = ConcurrentSet>() + private val surroundingMinedBlocks = ConcurrentLinkedQueue>() private var lastInitSound = SimpleTimeMark.farPast() @@ -60,6 +93,18 @@ object MiningAPI { private var waitingForEffMinerSound = false private var waitingForEffMinerBlock = false + // pickobulus + private var lastPickobulusUse = SimpleTimeMark.farPast() + private var lastPickobulusExplosion = SimpleTimeMark.farPast() + private var pickobulusExplosionPos: LorenzVec? = null + private val pickobulusMinedBlocks = ConcurrentLinkedQueue>() + + private val pickobulusActive get() = lastPickobulusUse.passedSince() < 2.seconds + + private var pickobulusWaitingForSound = false + private var pickobulusWaitingForBlock = false + + // oreblock data var inGlacite = false var inTunnels = false var inMineshaft = false @@ -72,15 +117,15 @@ object MiningAPI { var currentAreaOreBlocks = setOf() private set - private val recentClickedBlocks = ConcurrentSet>() - private val surroundingMinedBlocks = ConcurrentLinkedQueue>() private val allowedSoundNames = setOf("dig.glass", "dig.stone", "dig.gravel", "dig.cloth", "random.orb") var cold: Int = 0 private set var lastColdUpdate = SimpleTimeMark.farPast() + private set var lastColdReset = SimpleTimeMark.farPast() + private set fun inGlaciteArea() = inGlacialTunnels() || IslandType.MINESHAFT.isInIsland() @@ -128,10 +173,29 @@ object MiningAPI { @SubscribeEvent fun onChat(event: LorenzChatEvent) { if (!inColdIsland()) return - if (coldReset.matches(event.message)) { + if (coldResetPattern.matches(event.message)) { updateCold(0) lastColdReset = SimpleTimeMark.now() + return } + if (pickobulusUsePattern.matches(event.message)) { + lastPickobulusUse = SimpleTimeMark.now() + return + } + if (pickobulusFailPattern.matches(event.message)) { + resetPickobulusEvent() + pickobulusMinedBlocks.clear() + return + } + pickobulusEndPattern.matchMatcher(event.message) { + val amount = group("amount").formatInt() + resetPickobulusEvent() + val blocks = pickobulusMinedBlocks.take(amount).countBy { it.second } + if (blocks.isNotEmpty()) OreMinedEvent(null, blocks).post() + pickobulusMinedBlocks.clear() + return + } + } @SubscribeEvent @@ -145,8 +209,18 @@ object MiningAPI { @SubscribeEvent fun onPlaySound(event: PlaySoundEvent) { if (!inCustomMiningIsland()) return + if (event.soundName == "random.explode" && lastPickobulusUse.passedSince() < 5.seconds) { + lastPickobulusExplosion = SimpleTimeMark.now() + pickobulusExplosionPos = event.location + pickobulusWaitingForSound = true + return + } if (event.soundName !in allowedSoundNames) return - //println("Sound: ${event.soundName} ${event.pitch} ${event.volume} ${event.location.toCleanString()}") + if (pickobulusActive && pickobulusWaitingForSound) { + pickobulusWaitingForSound = false + pickobulusWaitingForBlock = true + return + } if (waitingForInitSound) { if (event.soundName != "random.orb" && event.pitch == 0.7936508f) { val pos = event.location.roundLocationToBlock() @@ -180,10 +254,19 @@ object MiningAPI { if (newBlock != Blocks.air && newBlock != Blocks.bedrock && !isTitanium(newState)) return val pos = event.location - if (pos.distanceToPlayer() > 7) return - //println("Block change: $oldState -> $newState ${pos.toCleanString()}") + if (pickobulusActive && pickobulusWaitingForBlock) { + val explosionPos = pickobulusExplosionPos ?: return + if (explosionPos.distance(pos) > 15) return + val ore = OreBlock.getByStateOrNull(oldState) ?: return + if (pickobulusMinedBlocks.any { it.first == pos }) return + pickobulusMinedBlocks += pos to ore + pickobulusWaitingForBlock = false + pickobulusWaitingForSound = true + return + } if (lastInitSound.passedSince() > 100.milliseconds) return + if (pos.distanceToPlayer() > 7) return val ore = OreBlock.getByStateOrNull(oldState) ?: return @@ -210,10 +293,13 @@ object MiningAPI { recentClickedBlocks.removeIf { it.second.passedSince() >= 20.seconds } surroundingMinedBlocks.removeIf { it.first.time.passedSince() >= 20.seconds } - if (waitingForInitSound) return - if (lastInitSound.passedSince() < 200.milliseconds) return - // in case the init block is not found - resetOreEvent() + if (!waitingForInitSound && lastInitSound.passedSince() > 200.milliseconds) { + resetOreEvent() + } + if (!lastPickobulusUse.isFarPast() && lastPickobulusUse.passedSince() > 5.seconds) { + resetPickobulusEvent() + pickobulusMinedBlocks.clear() + } } @HandleEvent @@ -252,8 +338,10 @@ object MiningAPI { lastColdReset = SimpleTimeMark.now() recentClickedBlocks.clear() surroundingMinedBlocks.clear() + pickobulusMinedBlocks.clear() currentAreaOreBlocks = setOf() resetOreEvent() + resetPickobulusEvent() } private fun resetOreEvent() { @@ -264,6 +352,14 @@ object MiningAPI { waitingForEffMinerBlock = false } + private fun resetPickobulusEvent() { + lastPickobulusUse = SimpleTimeMark.farPast() + lastPickobulusExplosion = SimpleTimeMark.farPast() + pickobulusExplosionPos = null + pickobulusWaitingForSound = false + pickobulusWaitingForBlock = false + } + @SubscribeEvent fun onDebugDataCollect(event: DebugDataCollectEvent) { event.title("Mining API") diff --git a/src/main/java/at/hannibal2/skyhanni/events/mining/OreMinedEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/mining/OreMinedEvent.kt index a97402a4179a..d74e65eced2a 100644 --- a/src/main/java/at/hannibal2/skyhanni/events/mining/OreMinedEvent.kt +++ b/src/main/java/at/hannibal2/skyhanni/events/mining/OreMinedEvent.kt @@ -3,4 +3,4 @@ package at.hannibal2.skyhanni.events.mining import at.hannibal2.skyhanni.api.event.SkyHanniEvent import at.hannibal2.skyhanni.features.mining.OreBlock -class OreMinedEvent(val originalOre: OreBlock, val extraBlocks: Map) : SkyHanniEvent() +class OreMinedEvent(val originalOre: OreBlock?, val extraBlocks: Map) : SkyHanniEvent() diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/MineshaftPityDisplay.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/MineshaftPityDisplay.kt index 8497951febb1..ec9a390edff0 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/mining/MineshaftPityDisplay.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/MineshaftPityDisplay.kt @@ -85,9 +85,12 @@ object MineshaftPityDisplay { fun onOreMined(event: OreMinedEvent) { if (!MiningAPI.inGlacialTunnels()) return - event.originalOre.getPityBlock()?.let { it.blocksBroken++ } + val originalOre = event.originalOre + originalOre?.getPityBlock()?.let { it.blocksBroken++ } event.extraBlocks.toMutableMap() - .apply { addOrPut(event.originalOre, -1) } + .apply { + if (originalOre != null) addOrPut(originalOre, -1) + } .map { (block, amount) -> block.getPityBlock()?.let { it.efficientMiner += amount } } diff --git a/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt b/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt index 32b287cb96a2..c0c03b6771ab 100644 --- a/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt +++ b/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt @@ -597,9 +597,9 @@ object SkyHanniDebugsAndTests { @HandleEvent(onlyOnSkyblock = true) fun onOreMined(event: OreMinedEvent) { if (!debugConfig.oreEventMessages) return - val originalOre = event.originalOre + val originalOre = event.originalOre?.let { "$it " } ?: "" val extraBlocks = event.extraBlocks.map { "${it.key.name}: ${it.value}" } - ChatUtils.debug("Mined: $originalOre (${extraBlocks.joinToString()})") + ChatUtils.debug("Mined: $originalOre(${extraBlocks.joinToString()})") } @SubscribeEvent From 74490dcdb619e6eadf1be7665871cdaba4d58ee8 Mon Sep 17 00:00:00 2001 From: J10a1n15 <45315647+j10a1n15@users.noreply.github.com> Date: Mon, 23 Sep 2024 15:45:56 +0200 Subject: [PATCH 12/38] Backend: Carry Tracker Number format depending on Custom Scoreboard (#2571) --- .../at/hannibal2/skyhanni/features/misc/CarryTracker.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/CarryTracker.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/CarryTracker.kt index f70fa58b8517..bfecbc48f57f 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/CarryTracker.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/CarryTracker.kt @@ -7,7 +7,6 @@ import at.hannibal2.skyhanni.events.GuiRenderEvent import at.hannibal2.skyhanni.events.LorenzChatEvent import at.hannibal2.skyhanni.events.RepositoryReloadEvent import at.hannibal2.skyhanni.events.entity.slayer.SlayerDeathEvent -import at.hannibal2.skyhanni.features.gui.customscoreboard.CustomScoreboardUtils.formatNum import at.hannibal2.skyhanni.features.slayer.SlayerType import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.ChatUtils @@ -15,6 +14,7 @@ import at.hannibal2.skyhanni.utils.CollectionUtils.addString import at.hannibal2.skyhanni.utils.HypixelCommands import at.hannibal2.skyhanni.utils.KeyboardManager import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.NumberUtil.shortFormat import at.hannibal2.skyhanni.utils.NumberUtil.formatDouble import at.hannibal2.skyhanni.utils.NumberUtil.formatDoubleOrUserError import at.hannibal2.skyhanni.utils.NumberUtil.formatIntOrUserError @@ -181,7 +181,7 @@ object CarryTracker { val price = rawPrice.formatDoubleOrUserError() ?: return carryType.pricePer = price update() - ChatUtils.chat("Set carry price for $carryType §eto §6${price.formatNum()} coins.") + ChatUtils.chat("Set carry price for $carryType §eto §6${price.shortFormat()} coins.") } private fun getCustomer(customerName: String): Customer { @@ -260,7 +260,7 @@ object CarryTracker { val totalCost = customer.carries.sumOf { it.getCost() ?: 0.0 } val totalCostFormat = formatCost(totalCost) if (totalCostFormat != "") { - val paidFormat = "§6${customer.alreadyPaid.formatNum()}" + val paidFormat = "§6${customer.alreadyPaid.shortFormat()}" val missingFormat = formatCost(totalCost - customer.alreadyPaid) list.add( Renderable.clickAndHover( @@ -293,7 +293,7 @@ object CarryTracker { }?.takeIf { it != 0.0 } } - private fun formatCost(totalCost: Double?): String = if (totalCost == 0.0 || totalCost == null) "" else "§6${totalCost.formatNum()}" + private fun formatCost(totalCost: Double?): String = if (totalCost == 0.0 || totalCost == null) "" else "§6${totalCost.shortFormat()}" private fun createCarryType(input: String): CarryType? { if (input.length == 1) return null From d39f595b150aaa0eee32c7ebacb961ef48095924 Mon Sep 17 00:00:00 2001 From: CalMWolfs <94038482+CalMWolfs@users.noreply.github.com> Date: Mon, 23 Sep 2024 23:54:16 +1000 Subject: [PATCH 13/38] Backend: Remove more neu code from SkyHanni (#2419) --- .github/workflows/illegal-imports.txt | 3 +- .idea/dictionaries/default_user.xml | 3 +- build.gradle.kts | 6 +- .../events/NeuRepositoryReloadEvent.kt | 3 +- .../skyhanni/features/chat/ChatFilterGui.kt | 15 +- .../combat/ghostcounter/GhostCounter.kt | 3 +- .../features/combat/ghostcounter/GhostUtil.kt | 56 +++----- .../features/garden/GardenNextJacobContest.kt | 2 +- .../garden/inventory/plots/GardenPlotIcon.kt | 4 +- .../garden/visitor/GardenVisitorSupercraft.kt | 6 +- .../HighlightVisitorsOutsideOfGarden.kt | 5 +- .../AuctionHouseOpenPriceWebsite.kt | 4 +- .../bazaar/BazaarOpenPriceWebsite.kt | 6 +- .../skyhanni/features/mining/ColdOverlay.kt | 4 +- .../features/mining/DeepCavernsGuide.kt | 4 +- .../features/misc/UserLuckBreakdown.kt | 12 +- .../features/misc/limbo/LimboPlaytime.kt | 4 +- .../DefaultConfigOptionGui.kt | 21 +-- .../rift/everywhere/EnigmaSoulWaypoints.kt | 4 +- .../features/skillprogress/SkillType.kt | 4 +- .../test/SkyHanniConfigSearchResetCommand.kt | 2 +- .../skyhanni/utils/GuiRenderUtils.kt | 132 +++++++++++++----- .../at/hannibal2/skyhanni/utils/ItemUtils.kt | 56 +++++++- .../at/hannibal2/skyhanni/utils/NEUItems.kt | 3 +- .../hannibal2/skyhanni/utils/RenderUtils.kt | 28 ++-- .../utils/SkyBlockItemModifierUtils.kt | 3 +- .../skyhanni/utils/json/JsonUtils.kt | 18 +++ .../hannibal2/skyhanni/utils/json/Shimmy.kt | 64 +++++++++ .../skyhanni/utils/renderables/Renderable.kt | 27 +--- .../utils/renderables/RenderableTooltips.kt | 5 +- 30 files changed, 338 insertions(+), 169 deletions(-) create mode 100644 src/main/java/at/hannibal2/skyhanni/utils/json/Shimmy.kt diff --git a/.github/workflows/illegal-imports.txt b/.github/workflows/illegal-imports.txt index f53a5da78e25..83354f622960 100644 --- a/.github/workflows/illegal-imports.txt +++ b/.github/workflows/illegal-imports.txt @@ -5,7 +5,8 @@ at/hannibal2/skyhanni/ scala. at/hannibal2/skyhanni/ jline. -at/hannibal2/skyhanni/ io.github.moulberry.notenoughupdates.util.Constants at/hannibal2/skyhanni/ io.github.moulberry.notenoughupdates.events.SlotClickEvent at/hannibal2/skyhanni/ io.github.moulberry.notenoughupdates.events.ReplaceItemEvent +at/hannibal2/skyhanni/ io.github.moulberry.notenoughupdates.util.Constants +at/hannibal2/skyhanni/ io.github.moulberry.notenoughupdates.util.Utils at/hannibal2/skyhanni/ java.util.function.Supplier diff --git a/.idea/dictionaries/default_user.xml b/.idea/dictionaries/default_user.xml index cb91301718f0..a0017ca5e3e0 100644 --- a/.idea/dictionaries/default_user.xml +++ b/.idea/dictionaries/default_user.xml @@ -254,6 +254,7 @@ superpairs tablist terracottas + tessellator thaumaturgist thaumaturgy townsquare @@ -277,4 +278,4 @@ yolkar - + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 447387f86a72..add2df4e77e2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -160,9 +160,9 @@ dependencies { exclude(module = "unspecified") isTransitive = false } - // June 3, 2024, 9:30 PM AEST - // https://github.com/NotEnoughUpdates/NotEnoughUpdates/tree/2.3.0 - devenvMod("com.github.NotEnoughUpdates:NotEnoughUpdates:2.3.0:all") { + // August 27, 2024, 4:30 PM AEST + // https://github.com/NotEnoughUpdates/NotEnoughUpdates/tree/2.3.3 + devenvMod("com.github.NotEnoughUpdates:NotEnoughUpdates:2.3.3:all") { exclude(module = "unspecified") isTransitive = false } diff --git a/src/main/java/at/hannibal2/skyhanni/events/NeuRepositoryReloadEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/NeuRepositoryReloadEvent.kt index c2bd261ca0a1..97a3f580c555 100644 --- a/src/main/java/at/hannibal2/skyhanni/events/NeuRepositoryReloadEvent.kt +++ b/src/main/java/at/hannibal2/skyhanni/events/NeuRepositoryReloadEvent.kt @@ -7,6 +7,7 @@ import at.hannibal2.skyhanni.data.repo.RepoUtils import at.hannibal2.skyhanni.test.command.ErrorManager import at.hannibal2.skyhanni.utils.NEUItems.manager import at.hannibal2.skyhanni.utils.json.fromJson +import at.hannibal2.skyhanni.utils.json.getJson import com.google.gson.Gson import com.google.gson.JsonObject import com.google.gson.JsonSyntaxException @@ -15,7 +16,7 @@ import java.lang.reflect.Type class NeuRepositoryReloadEvent : LorenzEvent() { fun getConstant(file: String): JsonObject? { - return manager.getJsonFromFile(File(manager.repoLocation, "constants/$file.json")) + return File(manager.repoLocation, "constants/$file.json").getJson() } inline fun readConstant(file: String, gson: Gson = ConfigManager.gson): T { diff --git a/src/main/java/at/hannibal2/skyhanni/features/chat/ChatFilterGui.kt b/src/main/java/at/hannibal2/skyhanni/features/chat/ChatFilterGui.kt index 0fee71849b4f..6a5f22b6635a 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/chat/ChatFilterGui.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/chat/ChatFilterGui.kt @@ -5,7 +5,8 @@ import at.hannibal2.skyhanni.utils.ChatUtils import at.hannibal2.skyhanni.utils.KeyboardManager import at.hannibal2.skyhanni.utils.OSUtils import at.hannibal2.skyhanni.utils.StringUtils.stripHypixelMessage -import io.github.moulberry.notenoughupdates.util.Utils +import at.hannibal2.skyhanni.utils.renderables.Renderable +import at.hannibal2.skyhanni.utils.renderables.RenderableTooltips import io.github.notenoughupdates.moulconfig.internal.GlScissorStack import io.github.notenoughupdates.moulconfig.internal.RenderUtils import net.minecraft.client.Minecraft @@ -42,7 +43,7 @@ class ChatFilterGui(private val history: List + RenderableTooltips.setTooltipForRender(tooltip.map { Renderable.string(it) }) } GlStateManager.color(1f, 1f, 1f, 1f) } @@ -98,7 +99,7 @@ class ChatFilterGui(private val history: List${if (SkyHanniMod.feature.combat.ghostCounter.showMax) "25" else nextLevel}" - ) - ) + this.replace("%value%", t) + .replace( + "%display%", + "$level->${if (SkyHanniMod.feature.combat.ghostCounter.showMax) "25" else nextLevel}", + ) } - fun String.formatText(value: Double, session: Double) = Utils.chromaStringByColourCode( - this.replace("%value%", value.roundToPrecision(2).addSeparators()) - .replace("%session%", session.roundToPrecision(2).addSeparators()) - .replace("&", "§") - ) + fun String.formatText(value: Double, session: Double) = this.replace("%value%", value.roundToPrecision(2).addSeparators()) + .replace("%session%", session.roundToPrecision(2).addSeparators()) + .replace("&", "§") fun String.formatBestiary(currentKill: Int, killNeeded: Int): String { val bestiaryNextLevel = GhostCounter.storage?.bestiaryNextLevel @@ -127,17 +117,15 @@ object GhostUtil { val nextLevel = bestiaryNextLevel?.let { if (GhostCounter.config.showMax) "25" else "${it.toInt()}" } ?: "§cNo Bestiary Level data!" - return Utils.chromaStringByColourCode( - this.replace( - "%currentKill%", - if (GhostCounter.config.showMax) GhostCounter.bestiaryCurrentKill.addSeparators() else currentKill.addSeparators() - ) - .replace("%percentNumber%", percent(GhostCounter.bestiaryCurrentKill.toDouble())) - .replace("%killNeeded%", killNeeded.shortFormat()) - .replace("%currentLevel%", currentLevel) - .replace("%nextLevel%", nextLevel) - .replace("&", "§") + return this.replace( + "%currentKill%", + if (GhostCounter.config.showMax) GhostCounter.bestiaryCurrentKill.addSeparators() else currentKill.addSeparators(), ) + .replace("%percentNumber%", percent(GhostCounter.bestiaryCurrentKill.toDouble())) + .replace("%killNeeded%", killNeeded.shortFormat()) + .replace("%currentLevel%", currentLevel) + .replace("%nextLevel%", nextLevel) + .replace("&", "§") } private fun percent(number: Double) = diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/GardenNextJacobContest.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/GardenNextJacobContest.kt index 8c2dc9a95e08..ab8820b40fd6 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/GardenNextJacobContest.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/GardenNextJacobContest.kt @@ -32,10 +32,10 @@ import at.hannibal2.skyhanni.utils.SoundUtils import at.hannibal2.skyhanni.utils.StringUtils.removeColor import at.hannibal2.skyhanni.utils.TabListData import at.hannibal2.skyhanni.utils.TimeUtils.format +import at.hannibal2.skyhanni.utils.json.toJsonArray import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern import com.google.gson.Gson import com.google.gson.JsonPrimitive -import io.github.moulberry.notenoughupdates.util.toJsonArray import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/inventory/plots/GardenPlotIcon.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/inventory/plots/GardenPlotIcon.kt index 1fc378b7e890..01e9f87fb4df 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/inventory/plots/GardenPlotIcon.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/inventory/plots/GardenPlotIcon.kt @@ -8,10 +8,10 @@ import at.hannibal2.skyhanni.events.render.gui.ReplaceItemEvent import at.hannibal2.skyhanni.features.garden.GardenAPI import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.ChatUtils +import at.hannibal2.skyhanni.utils.ItemUtils.editItemInfo import at.hannibal2.skyhanni.utils.ItemUtils.getInternalName import at.hannibal2.skyhanni.utils.ItemUtils.getLore import at.hannibal2.skyhanni.utils.NEUItems.getItemStack -import io.github.moulberry.notenoughupdates.util.Utils import net.minecraft.client.player.inventory.ContainerLocalMenu import net.minecraft.init.Items import net.minecraft.item.ItemStack @@ -51,7 +51,7 @@ object GardenPlotIcon { for ((index, internalName) in plotList) { val old = originalStack[index]!! val new = internalName.getItemStack() - cachedStack[index] = Utils.editItemStackInfo(new, old.displayName, true, *old.getLore().toTypedArray()) + cachedStack[index] = new.editItemInfo(old.displayName, true, old.getLore()) } } diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/visitor/GardenVisitorSupercraft.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/visitor/GardenVisitorSupercraft.kt index 26bfa7f26409..f2e4f80fb8f1 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/visitor/GardenVisitorSupercraft.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/visitor/GardenVisitorSupercraft.kt @@ -8,12 +8,12 @@ import at.hannibal2.skyhanni.events.render.gui.ReplaceItemEvent import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.test.command.ErrorManager import at.hannibal2.skyhanni.utils.HypixelCommands +import at.hannibal2.skyhanni.utils.ItemUtils 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.NEUItems.getItemStack import at.hannibal2.skyhanni.utils.SimpleTimeMark -import io.github.moulberry.notenoughupdates.util.Utils import net.minecraft.entity.player.InventoryPlayer import net.minecraftforge.fml.common.eventhandler.EventPriority import net.minecraftforge.fml.common.eventhandler.SubscribeEvent @@ -30,11 +30,11 @@ object GardenVisitorSupercraft { private val superCraftItem by lazy { val neuItem = "GOLD_PICKAXE".asInternalName().getItemStack() - Utils.createItemStack( + ItemUtils.createItemStack( neuItem.item, "§bSuper Craft", "§7You have the items to craft", - "§7Click me to open the super crafter!" + "§7Click me to open the super crafter!", ) } diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/visitor/HighlightVisitorsOutsideOfGarden.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/visitor/HighlightVisitorsOutsideOfGarden.kt index 88ea75b572b9..8a8c11165564 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/visitor/HighlightVisitorsOutsideOfGarden.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/visitor/HighlightVisitorsOutsideOfGarden.kt @@ -2,6 +2,7 @@ package at.hannibal2.skyhanni.features.garden.visitor import at.hannibal2.skyhanni.api.event.HandleEvent import at.hannibal2.skyhanni.config.features.garden.visitor.VisitorConfig.VisitorBlockBehaviour +import at.hannibal2.skyhanni.data.HypixelData import at.hannibal2.skyhanni.data.jsonobjects.repo.GardenJson import at.hannibal2.skyhanni.data.jsonobjects.repo.GardenVisitor import at.hannibal2.skyhanni.events.RepositoryReloadEvent @@ -19,7 +20,6 @@ import at.hannibal2.skyhanni.utils.LorenzUtils import at.hannibal2.skyhanni.utils.LorenzVec import at.hannibal2.skyhanni.utils.getLorenzVec import at.hannibal2.skyhanni.utils.toLorenzVec -import io.github.moulberry.notenoughupdates.util.SBInfo import net.minecraft.client.Minecraft import net.minecraft.entity.Entity import net.minecraft.entity.EntityLivingBase @@ -57,7 +57,8 @@ object HighlightVisitorsOutsideOfGarden { } private fun isVisitor(entity: Entity): Boolean { - val mode = SBInfo.getInstance().getLocation() + // todo migrate to Skyhanni IslandType + val mode = HypixelData.mode val possibleJsons = visitorJson[mode] ?: return false val skinOrType = getSkinOrTypeFor(entity) return possibleJsons.any { diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/auctionhouse/AuctionHouseOpenPriceWebsite.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/auctionhouse/AuctionHouseOpenPriceWebsite.kt index b253e01911f2..7229283ca9c9 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/inventory/auctionhouse/AuctionHouseOpenPriceWebsite.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/auctionhouse/AuctionHouseOpenPriceWebsite.kt @@ -6,6 +6,7 @@ import at.hannibal2.skyhanni.events.InventoryCloseEvent import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent import at.hannibal2.skyhanni.events.render.gui.ReplaceItemEvent import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule +import at.hannibal2.skyhanni.utils.ItemUtils import at.hannibal2.skyhanni.utils.LorenzUtils import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName import at.hannibal2.skyhanni.utils.NEUItems.getItemStack @@ -13,7 +14,6 @@ import at.hannibal2.skyhanni.utils.OSUtils import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher import at.hannibal2.skyhanni.utils.SimpleTimeMark import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern -import io.github.moulberry.notenoughupdates.util.Utils import net.minecraft.entity.player.InventoryPlayer import net.minecraft.item.ItemStack import net.minecraftforge.fml.common.eventhandler.EventPriority @@ -48,7 +48,7 @@ object AuctionHouseOpenPriceWebsite { } } - private fun createDisplayItem() = Utils.createItemStack( + private fun createDisplayItem() = ItemUtils.createItemStack( "PAPER".asInternalName().getItemStack().item, "§bPrice History", "§7Click here to open", diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/bazaar/BazaarOpenPriceWebsite.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/bazaar/BazaarOpenPriceWebsite.kt index 61606346ad9e..216d3e10872a 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/inventory/bazaar/BazaarOpenPriceWebsite.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/bazaar/BazaarOpenPriceWebsite.kt @@ -4,12 +4,12 @@ import at.hannibal2.skyhanni.SkyHanniMod import at.hannibal2.skyhanni.events.GuiContainerEvent import at.hannibal2.skyhanni.events.render.gui.ReplaceItemEvent import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule +import at.hannibal2.skyhanni.utils.ItemUtils import at.hannibal2.skyhanni.utils.NEUInternalName import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName import at.hannibal2.skyhanni.utils.NEUItems.getItemStack import at.hannibal2.skyhanni.utils.OSUtils import at.hannibal2.skyhanni.utils.SimpleTimeMark -import io.github.moulberry.notenoughupdates.util.Utils import net.minecraft.entity.player.InventoryPlayer import net.minecraftforge.fml.common.eventhandler.EventPriority import net.minecraftforge.fml.common.eventhandler.SubscribeEvent @@ -23,12 +23,12 @@ object BazaarOpenPriceWebsite { private val item by lazy { val neuItem = "PAPER".asInternalName().getItemStack() - Utils.createItemStack( + ItemUtils.createItemStack( neuItem.item, "§bPrice History", "§7Click here to open", "§7the price history", - "§7on §cskyblock.bz" + "§7on §cskyblock.bz", ) } diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/ColdOverlay.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/ColdOverlay.kt index 8af757f6e074..58ef41ce7e8b 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/mining/ColdOverlay.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/ColdOverlay.kt @@ -6,8 +6,8 @@ import at.hannibal2.skyhanni.events.ColdUpdateEvent import at.hannibal2.skyhanni.events.GuiRenderEvent import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.DelayedRun +import at.hannibal2.skyhanni.utils.GuiRenderUtils import at.hannibal2.skyhanni.utils.NumberUtil -import at.hannibal2.skyhanni.utils.RenderUtils import at.hannibal2.skyhanni.utils.SimpleTimeMark import net.minecraft.client.Minecraft import net.minecraft.client.renderer.GlStateManager @@ -42,7 +42,7 @@ object ColdOverlay { GlStateManager.translate(0f, 0f, -500f) GlStateManager.color(1f, 1f, 1f, alpha) - RenderUtils.drawTexturedRect(0f, 0f) + GuiRenderUtils.drawTexturedRect(0f, 0f) GL11.glDepthMask(true) GlStateManager.popMatrix() diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/DeepCavernsGuide.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/DeepCavernsGuide.kt index 9600fbc20dd1..a1d8f43a8d5b 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/mining/DeepCavernsGuide.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/DeepCavernsGuide.kt @@ -16,12 +16,12 @@ import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.ChatUtils import at.hannibal2.skyhanni.utils.ColorUtils.toChromaColor import at.hannibal2.skyhanni.utils.ConditionalUtils +import at.hannibal2.skyhanni.utils.ItemUtils import at.hannibal2.skyhanni.utils.LorenzUtils import at.hannibal2.skyhanni.utils.LorenzUtils.isInIsland import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName import at.hannibal2.skyhanni.utils.NEUItems.getItemStack import at.hannibal2.skyhanni.utils.ParkourHelper -import io.github.moulberry.notenoughupdates.util.Utils import net.minecraft.client.player.inventory.ContainerLocalMenu import net.minecraftforge.fml.common.eventhandler.EventPriority import net.minecraftforge.fml.common.eventhandler.SubscribeEvent @@ -37,7 +37,7 @@ object DeepCavernsGuide { private val startIcon by lazy { val neuItem = "MAP".asInternalName().getItemStack() - Utils.createItemStack( + ItemUtils.createItemStack( neuItem.item, "§bDeep Caverns Guide", "§8(From SkyHanni)", diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/UserLuckBreakdown.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/UserLuckBreakdown.kt index d4532a77527a..7f041bc27f9a 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/UserLuckBreakdown.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/UserLuckBreakdown.kt @@ -10,6 +10,7 @@ import at.hannibal2.skyhanni.events.render.gui.ReplaceItemEvent import at.hannibal2.skyhanni.features.skillprogress.SkillType import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.CollectionUtils.addOrPut +import at.hannibal2.skyhanni.utils.ItemUtils import at.hannibal2.skyhanni.utils.ItemUtils.getLore import at.hannibal2.skyhanni.utils.ItemUtils.name import at.hannibal2.skyhanni.utils.LorenzUtils @@ -20,7 +21,6 @@ import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher import at.hannibal2.skyhanni.utils.SimpleTimeMark import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern -import io.github.moulberry.notenoughupdates.util.Utils import net.minecraft.client.player.inventory.ContainerLocalMenu import net.minecraft.item.ItemStack import net.minecraftforge.fml.common.eventhandler.SubscribeEvent @@ -237,9 +237,11 @@ object UserLuckBreakdown { } private fun createItems() { - fillerItem = Utils.createItemStack( + fillerItem = ItemUtils.createItemStack( fillerID.getItemStack().item, fillerName, + listOf(), + 1, 15, ) @@ -247,17 +249,17 @@ object UserLuckBreakdown { val skillLuck = skillOverflowLuck.values.sum() val totalLuck = skillLuck + limboLuck - mainLuckItem = Utils.createItemStack( + mainLuckItem = ItemUtils.createItemStack( mainLuckID.getItemStack().item, "$mainLuckName §f${tryTruncateFloat(totalLuck)}", *createItemLore("mainMenu", totalLuck), ) - limboItem = Utils.createItemStack( + limboItem = ItemUtils.createItemStack( limboID.getItemStack().item, limboName, *createItemLore("limbo", limboLuck), ) - skillsItem = Utils.createItemStack( + skillsItem = ItemUtils.createItemStack( skillsID.getItemStack().item, skillsName, *createItemLore("skills"), diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/limbo/LimboPlaytime.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/limbo/LimboPlaytime.kt index 0996701f23ca..666671e9ca3f 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/limbo/LimboPlaytime.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/limbo/LimboPlaytime.kt @@ -6,6 +6,7 @@ import at.hannibal2.skyhanni.events.InventoryOpenEvent import at.hannibal2.skyhanni.events.LorenzToolTipEvent import at.hannibal2.skyhanni.events.render.gui.ReplaceItemEvent import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule +import at.hannibal2.skyhanni.utils.ItemUtils import at.hannibal2.skyhanni.utils.LorenzUtils import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName @@ -14,7 +15,6 @@ import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators import at.hannibal2.skyhanni.utils.RegexUtils.matches import at.hannibal2.skyhanni.utils.SimpleTimeMark import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern -import io.github.moulberry.notenoughupdates.util.Utils import net.minecraft.client.player.inventory.ContainerLocalMenu import net.minecraft.item.ItemStack import net.minecraftforge.fml.common.eventhandler.SubscribeEvent @@ -58,7 +58,7 @@ object LimboPlaytime { if (lastCreateCooldown.passedSince() > 3.seconds) { lastCreateCooldown = SimpleTimeMark.now() - limboItem = Utils.createItemStack( + limboItem = ItemUtils.createItemStack( itemID.getItemStack().item, itemName, *createItemLore() diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/massconfiguration/DefaultConfigOptionGui.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/massconfiguration/DefaultConfigOptionGui.kt index a68978caec21..bbe40db5cd4a 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/massconfiguration/DefaultConfigOptionGui.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/massconfiguration/DefaultConfigOptionGui.kt @@ -1,6 +1,7 @@ package at.hannibal2.skyhanni.features.misc.massconfiguration -import io.github.moulberry.notenoughupdates.util.Utils +import at.hannibal2.skyhanni.utils.renderables.Renderable +import at.hannibal2.skyhanni.utils.renderables.RenderableTooltips import io.github.notenoughupdates.moulconfig.internal.GlScissorStack import io.github.notenoughupdates.moulconfig.internal.RenderUtils import io.github.notenoughupdates.moulconfig.internal.TextRenderUtils @@ -66,7 +67,7 @@ class DefaultConfigOptionGui( mc.fontRendererObj.FONT_HEIGHT.toFloat(), false, xSize / 2 - padding, - -1 + -1, ) GlStateManager.popMatrix() @@ -74,7 +75,7 @@ class DefaultConfigOptionGui( GlStateManager.translate( (width - xSize) / 2F + padding, (height + ySize) / 2F - mc.fontRendererObj.FONT_HEIGHT * 2, - 0F + 0F, ) var i = 0 fun button(title: String, tooltip: List, func: () -> Unit) { @@ -95,7 +96,7 @@ class DefaultConfigOptionGui( 2 + i.toFloat(), 0F, if (overMouse) 0xFF00FF00.toInt() else -1, - overMouse + overMouse, ) i += width + 12 } @@ -132,12 +133,12 @@ class DefaultConfigOptionGui( (height - ySize) / 2 + barSize, (width + xSize) / 2, (height + ySize) / 2 - barSize, - scaledResolution + scaledResolution, ) GlStateManager.translate( (width - xSize) / 2F + padding, (height - ySize) / 2F + barSize - currentScrollOffset, - 0F + 0F, ) for ((cat) in orderedOptions.entries) { @@ -157,13 +158,13 @@ class DefaultConfigOptionGui( "§7${cat.description}", "§7Current plan: ${suggestionState.label}", "§aClick to toggle!", - "§7Hold shift to show all options" + "§7Hold shift to show all options", ) if (isShiftKeyDown()) { hoveringTextToDraw = listOf( "§e${cat.name}", - "§7${cat.description}" + "§7${cat.description}", ) + orderedOptions[cat]!!.map { "§7 - §a" + it.name } } @@ -179,8 +180,8 @@ class DefaultConfigOptionGui( GlStateManager.popMatrix() GlScissorStack.pop(scaledResolution) - if (hoveringTextToDraw != null) { - Utils.drawHoveringText(hoveringTextToDraw, mouseX, mouseY, width, height, 100, mc.fontRendererObj) + hoveringTextToDraw?.let { tooltip -> + RenderableTooltips.setTooltipForRender(tooltip.map { Renderable.string(it) }) } } diff --git a/src/main/java/at/hannibal2/skyhanni/features/rift/everywhere/EnigmaSoulWaypoints.kt b/src/main/java/at/hannibal2/skyhanni/features/rift/everywhere/EnigmaSoulWaypoints.kt index d91b4008d912..2a4613f7a7bc 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/rift/everywhere/EnigmaSoulWaypoints.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/rift/everywhere/EnigmaSoulWaypoints.kt @@ -14,6 +14,7 @@ import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.ChatUtils import at.hannibal2.skyhanni.utils.ColorUtils.toChromaColor import at.hannibal2.skyhanni.utils.InventoryUtils.getAllItems +import at.hannibal2.skyhanni.utils.ItemUtils import at.hannibal2.skyhanni.utils.ItemUtils.getLore import at.hannibal2.skyhanni.utils.LocationUtils.distanceToPlayer import at.hannibal2.skyhanni.utils.LorenzColor @@ -24,7 +25,6 @@ import at.hannibal2.skyhanni.utils.RenderUtils.drawDynamicText import at.hannibal2.skyhanni.utils.RenderUtils.drawWaypointFilled import at.hannibal2.skyhanni.utils.RenderUtils.highlight import at.hannibal2.skyhanni.utils.StringUtils.removeColor -import io.github.moulberry.notenoughupdates.util.Utils import net.minecraft.client.gui.inventory.GuiChest import net.minecraft.client.player.inventory.ContainerLocalMenu import net.minecraft.inventory.ContainerChest @@ -43,7 +43,7 @@ object EnigmaSoulWaypoints { private val item by lazy { val neuItem = "SKYBLOCK_ENIGMA_SOUL".asInternalName().getItemStack() - Utils.createItemStack( + ItemUtils.createItemStack( neuItem.item, "§5Toggle Missing", "§7Click here to toggle", diff --git a/src/main/java/at/hannibal2/skyhanni/features/skillprogress/SkillType.kt b/src/main/java/at/hannibal2/skyhanni/features/skillprogress/SkillType.kt index bd44a63f39b7..b10bc8cb6c08 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/skillprogress/SkillType.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/skillprogress/SkillType.kt @@ -1,6 +1,6 @@ package at.hannibal2.skyhanni.features.skillprogress -import io.github.moulberry.notenoughupdates.util.Utils +import at.hannibal2.skyhanni.utils.ItemUtils import net.minecraft.block.Block import net.minecraft.init.Blocks import net.minecraft.init.Items @@ -21,7 +21,7 @@ enum class SkillType(val displayName: String, icon: Item) { constructor(displayName: String, block: Block) : this(displayName, Item.getItemFromBlock(block)) - val item: ItemStack by lazy { Utils.createItemStack(icon, displayName) } + val item: ItemStack by lazy { ItemUtils.createItemStack(icon, displayName) } val lowercaseName = displayName.lowercase() val uppercaseName = displayName.uppercase() diff --git a/src/main/java/at/hannibal2/skyhanni/test/SkyHanniConfigSearchResetCommand.kt b/src/main/java/at/hannibal2/skyhanni/test/SkyHanniConfigSearchResetCommand.kt index 8a74510828eb..34bf36b5fbbf 100644 --- a/src/main/java/at/hannibal2/skyhanni/test/SkyHanniConfigSearchResetCommand.kt +++ b/src/main/java/at/hannibal2/skyhanni/test/SkyHanniConfigSearchResetCommand.kt @@ -10,8 +10,8 @@ import at.hannibal2.skyhanni.utils.ChatUtils import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators import at.hannibal2.skyhanni.utils.OSUtils import at.hannibal2.skyhanni.utils.ReflectionUtils.makeAccessible +import at.hannibal2.skyhanni.utils.json.Shimmy import com.google.gson.JsonElement -import io.github.moulberry.notenoughupdates.util.Shimmy import kotlinx.coroutines.launch import java.lang.reflect.Field import java.lang.reflect.Modifier diff --git a/src/main/java/at/hannibal2/skyhanni/utils/GuiRenderUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/GuiRenderUtils.kt index 75eb5087ec7f..ba1081a5f3dd 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/GuiRenderUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/GuiRenderUtils.kt @@ -7,16 +7,17 @@ import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.NumberUtil.fractionOf import at.hannibal2.skyhanni.utils.RenderUtils.HorizontalAlignment import at.hannibal2.skyhanni.utils.renderables.Renderable -import io.github.moulberry.notenoughupdates.util.Utils import net.minecraft.client.Minecraft import net.minecraft.client.gui.FontRenderer import net.minecraft.client.gui.GuiScreen +import net.minecraft.client.gui.ScaledResolution import net.minecraft.client.renderer.GlStateManager import net.minecraft.client.renderer.RenderHelper import net.minecraft.client.renderer.Tessellator import net.minecraft.client.renderer.vertex.DefaultVertexFormats import net.minecraft.item.ItemStack import org.lwjgl.opengl.GL11 +import org.lwjgl.opengl.GL14 import java.awt.Color import java.text.DecimalFormat import kotlin.math.ceil @@ -75,7 +76,7 @@ object GuiRenderUtils { fun drawStringCentered(str: String?, x: Int, y: Int) { drawStringCentered( - str, Minecraft.getMinecraft().fontRendererObj, x.toFloat(), y.toFloat(), true, 0xffffff + str, Minecraft.getMinecraft().fontRendererObj, x.toFloat(), y.toFloat(), true, 0xffffff, ) } @@ -125,25 +126,25 @@ object GuiRenderUtils { if (tooltipY + tooltipHeight + 6 > screenHeight) tooltipY = screenHeight - tooltipHeight - 6 // main background GuiScreen.drawRect( - tooltipX - 3, tooltipY - 3, tooltipX + tooltipTextWidth + 3, tooltipY + tooltipHeight + 3, -0xfeffff0 + tooltipX - 3, tooltipY - 3, tooltipX + tooltipTextWidth + 3, tooltipY + tooltipHeight + 3, -0xfeffff0, ) // borders GuiScreen.drawRect( - tooltipX - 3, tooltipY - 3 + 1, tooltipX - 3 + 1, tooltipY + tooltipHeight + 3 - 1, borderColor + tooltipX - 3, tooltipY - 3 + 1, tooltipX - 3 + 1, tooltipY + tooltipHeight + 3 - 1, borderColor, - ) + ) GuiScreen.drawRect( tooltipX + tooltipTextWidth + 2, tooltipY - 3 + 1, tooltipX + tooltipTextWidth + 3, tooltipY + tooltipHeight + 3 - 1, - borderColor + borderColor, ) GuiScreen.drawRect( - tooltipX - 3, tooltipY - 3, tooltipX + tooltipTextWidth + 3, tooltipY - 3 + 1, borderColor + tooltipX - 3, tooltipY - 3, tooltipX + tooltipTextWidth + 3, tooltipY - 3 + 1, borderColor, ) GuiScreen.drawRect( @@ -151,7 +152,7 @@ object GuiRenderUtils { tooltipY + tooltipHeight + 2, tooltipX + tooltipTextWidth + 3, tooltipY + tooltipHeight + 3, - borderColor + borderColor, ) GlStateManager.translate(0f, 0f, -100f) GlStateManager.disableDepth() @@ -188,27 +189,33 @@ object GuiRenderUtils { val current = currentValue.toDouble().coerceAtLeast(0.0) val percent = current.fractionOf(maxValue) val scale = textScale.toDouble() - return Renderable.hoverTips(Renderable.verticalContainer( - listOf( - Renderable.string(label, scale = scale), - Renderable.fixedSizeLine( - listOf( - Renderable.string( - "§2${DecimalFormat("0.##").format(current)} / ${ - DecimalFormat( - "0.##" - ).format(maxValue) - }☘", scale = scale, horizontalAlign = HorizontalAlignment.LEFT + return Renderable.hoverTips( + Renderable.verticalContainer( + listOf( + Renderable.string(label, scale = scale), + Renderable.fixedSizeLine( + listOf( + Renderable.string( + "§2${DecimalFormat("0.##").format(current)} / ${ + DecimalFormat( + "0.##", + ).format(maxValue) + }☘", + scale = scale, horizontalAlign = HorizontalAlignment.LEFT, + ), + Renderable.string( + "§2${(percent * 100).round(1)}%", + scale = scale, + horizontalAlign = HorizontalAlignment.RIGHT, + ), ), - Renderable.string( - "§2${(percent * 100).round(1)}%", - scale = scale, - horizontalAlign = HorizontalAlignment.RIGHT - ), - ), width - ), Renderable.progressBar(percent, width = width) - ) - ), tooltip.split('\n').map { Renderable.string(it) }) + width, + ), + Renderable.progressBar(percent, width = width), + ), + ), + tooltip.split('\n').map { Renderable.string(it) }, + ) } private fun barColorGradient(double: Double): Int { @@ -265,16 +272,14 @@ object GuiRenderUtils { Color.LIGHT_GRAY.darker().red / 255f, Color.LIGHT_GRAY.darker().green / 255f, Color.LIGHT_GRAY.darker().blue / 255f, - 1f + 1f, ) } else { GlStateManager.color(color.darker().red / 255f, color.darker().green / 255f, color.darker().blue / 255f, 1f) } - Utils.drawTexturedRect(x, y, w_2.toFloat(), height, 0f, w_2 / xSize, vMinEmpty, vMaxEmpty, GL11.GL_NEAREST) - Utils.drawTexturedRect( - x + w_2, y, w_2.toFloat(), height, 1 - w_2 / xSize, 1f, vMinEmpty, vMaxEmpty, GL11.GL_NEAREST - ) + drawTexturedRect(x, y, w_2.toFloat(), height, 0f, w_2 / xSize, vMinEmpty, vMaxEmpty, GL11.GL_NEAREST) + drawTexturedRect(x + w_2, y, w_2.toFloat(), height, 1 - w_2 / xSize, 1f, vMinEmpty, vMaxEmpty, GL11.GL_NEAREST) if (useChroma) { GlStateManager.color(Color.WHITE.red / 255f, Color.WHITE.green / 255f, Color.WHITE.blue / 255f, 1f) @@ -285,9 +290,9 @@ object GuiRenderUtils { if (k > 0) { val uMax = w_2.toDouble().coerceAtMost(k.toDouble() / xSize).toFloat() val width = w_2.coerceAtMost(k).toFloat() - Utils.drawTexturedRect(x, y, width, height, 0f, uMax, vMinFilled, vMaxFilled, GL11.GL_NEAREST) + drawTexturedRect(x, y, width, height, 0f, uMax, vMinFilled, vMaxFilled, GL11.GL_NEAREST) if (completed > 0.5f) { - Utils.drawTexturedRect( + drawTexturedRect( x + w_2, y, (k - w_2).toFloat(), @@ -296,7 +301,7 @@ object GuiRenderUtils { 1 + (k - w) / xSize, vMinFilled, vMaxFilled, - GL11.GL_NEAREST + GL11.GL_NEAREST, ) } } @@ -342,4 +347,59 @@ object GuiRenderUtils { GlStateManager.enableAlpha() GlStateManager.enableTexture2D() } + + fun drawTexturedRect(x: Float, y: Float) { + with(ScaledResolution(Minecraft.getMinecraft())) { + drawTexturedRect(x, y, scaledWidth.toFloat(), scaledHeight.toFloat(), filter = GL11.GL_NEAREST) + } + } + + fun drawTexturedRect( + x: Int, + y: Int, + width: Int, + height: Int, + uMin: Float = 0f, + uMax: Float = 1f, + vMin: Float = 0f, + vMax: Float = 1f, + filter: Int = GL11.GL_NEAREST, + ) { + drawTexturedRect(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), uMin, uMax, vMin, vMax, filter) + } + + // Taken from NEU + fun drawTexturedRect( + x: Float, + y: Float, + width: Float, + height: Float, + uMin: Float = 0f, + uMax: Float = 1f, + vMin: Float = 0f, + vMax: Float = 1f, + filter: Int = GL11.GL_NEAREST, + ) { + GlStateManager.enableTexture2D() + GlStateManager.enableBlend() + GlStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA) + GL14.glBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA) + + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, filter) + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, filter) + + val tessellator = Tessellator.getInstance() + val worldRenderer = tessellator.worldRenderer + worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX) + worldRenderer.pos(x.toDouble(), (y + height).toDouble(), 0.0).tex(uMin.toDouble(), vMax.toDouble()).endVertex() + worldRenderer.pos((x + width).toDouble(), (y + height).toDouble(), 0.0).tex(uMax.toDouble(), vMax.toDouble()).endVertex() + worldRenderer.pos((x + width).toDouble(), y.toDouble(), 0.0).tex(uMax.toDouble(), vMin.toDouble()).endVertex() + worldRenderer.pos(x.toDouble(), y.toDouble(), 0.0).tex(uMin.toDouble(), vMin.toDouble()).endVertex() + tessellator.draw() + + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST) + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST) + + GlStateManager.disableBlend() + } } diff --git a/src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt index ac35c07418ba..add66cc0adcf 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt @@ -22,6 +22,7 @@ import com.google.common.collect.Lists import io.github.moulberry.notenoughupdates.util.NotificationHandler import net.minecraft.client.Minecraft import net.minecraft.init.Items +import net.minecraft.item.Item import net.minecraft.item.ItemStack import net.minecraft.nbt.NBTTagCompound import net.minecraft.nbt.NBTTagList @@ -43,9 +44,11 @@ object ItemUtils { fun isSack(stack: ItemStack) = stack.getInternalName().endsWith("_SACK") && stack.cleanName().endsWith(" Sack") - fun ItemStack.getLore(): List { - val tagCompound = this.tagCompound ?: return emptyList() - val tagList = tagCompound.getCompoundTag("display").getTagList("Lore", 8) + fun ItemStack.getLore(): List = this.tagCompound.getLore() + + fun NBTTagCompound?.getLore(): List { + this ?: return emptyList() + val tagList = this.getCompoundTag("display").getTagList("Lore", 8) val list: MutableList = ArrayList() for (i in 0 until tagList.tagCount()) { list.add(tagList.getStringTagAt(i)) @@ -53,6 +56,13 @@ object ItemUtils { return list } + fun getDisplayName(compound: NBTTagCompound?): String? { + compound ?: return null + val name = compound.getCompoundTag("display").getString("Name") + if (name == null || name.isEmpty()) return null + return name + } + fun ItemStack.setLore(lore: List): ItemStack { val tagCompound = this.tagCompound ?: NBTTagCompound() val display = tagCompound.getCompoundTag("display") @@ -67,12 +77,14 @@ object ItemUtils { } var ItemStack.extraAttributes: NBTTagCompound - get() = this.tagCompound?.getCompoundTag("ExtraAttributes") ?: NBTTagCompound() + get() = this.tagCompound?.extraAttributes ?: NBTTagCompound() set(value) { val tag = this.tagCompound ?: NBTTagCompound().also { tagCompound = it } tag.setTag("ExtraAttributes", value) } + val NBTTagCompound.extraAttributes: NBTTagCompound get() = this.getCompoundTag("ExtraAttributes") + fun ItemStack.overrideId(id: String): ItemStack { extraAttributes = extraAttributes.apply { setString("id", id) } return this @@ -183,6 +195,20 @@ object ItemUtils { return render } + fun createItemStack(item: Item, displayName: String, vararg lore: String): ItemStack { + return createItemStack(item, displayName, lore.toList()) + } + + // Taken from NEU + fun createItemStack(item: Item, displayName: String, lore: List, amount: Int = 1, damage: Int = 0): ItemStack { + val stack = ItemStack(item, amount, damage) + val tag = NBTTagCompound() + addNameAndLore(tag, displayName, *lore.toTypedArray()) + tag.setInteger("HideFlags", 254) + stack.tagCompound = tag + return stack + } + // Taken from NEU private fun addNameAndLore(tag: NBTTagCompound, displayName: String, vararg lore: String) { val display = NBTTagCompound() @@ -315,6 +341,28 @@ object ItemUtils { setStackDisplayName(value) } + // Taken from NEU + fun ItemStack.editItemInfo(displayName: String, disableNeuTooltips: Boolean, lore: List): ItemStack { + val tag = this.tagCompound ?: NBTTagCompound() + val display = tag.getCompoundTag("display") + val loreList = NBTTagList() + for (line in lore) { + loreList.appendTag(NBTTagString(line)) + } + + display.setString("Name", displayName) + display.setTag("Lore", loreList) + + tag.setTag("display", display) + tag.setInteger("HideFlags", 254) + if (disableNeuTooltips) { + tag.setBoolean("disableNeuTooltip", true) + } + + this.tagCompound = tag + return this + } + fun isSkyBlockMenuItem(stack: ItemStack?): Boolean = stack?.getInternalName()?.equals("SKYBLOCK_MENU") ?: false private val itemAmountCache = mutableMapOf>() diff --git a/src/main/java/at/hannibal2/skyhanni/utils/NEUItems.kt b/src/main/java/at/hannibal2/skyhanni/utils/NEUItems.kt index 34d491ceea81..198b43a856a6 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/NEUItems.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/NEUItems.kt @@ -31,7 +31,6 @@ import io.github.moulberry.notenoughupdates.events.ProfileDataLoadedEvent import io.github.moulberry.notenoughupdates.overlays.AuctionSearchOverlay import io.github.moulberry.notenoughupdates.overlays.BazaarSearchOverlay import io.github.moulberry.notenoughupdates.util.ItemResolutionQuery -import io.github.moulberry.notenoughupdates.util.Utils import net.minecraft.client.Minecraft import net.minecraft.client.renderer.GLAllocation import net.minecraft.client.renderer.GlStateManager @@ -97,7 +96,7 @@ object NEUItems { val ignoreItemsFilter = MultiFilter() private val fallbackItem by lazy { - Utils.createItemStack( + ItemUtils.createItemStack( ItemStack(Blocks.barrier).item, "§cMissing Repo Item", "§cYour NEU repo seems to be out of date", diff --git a/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt index 08532e0c4eec..609ccebae24b 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt @@ -19,11 +19,11 @@ import at.hannibal2.skyhanni.utils.CollectionUtils.zipWithNext3 import at.hannibal2.skyhanni.utils.ColorUtils.getFirstColorCode import at.hannibal2.skyhanni.utils.LorenzColor.Companion.toLorenzColor import at.hannibal2.skyhanni.utils.LorenzUtils.getCorners +import at.hannibal2.skyhanni.utils.compat.GuiScreenUtils import at.hannibal2.skyhanni.utils.renderables.Renderable import at.hannibal2.skyhanni.utils.renderables.RenderableUtils.renderXAligned import at.hannibal2.skyhanni.utils.renderables.RenderableUtils.renderYAligned import at.hannibal2.skyhanni.utils.shader.ShaderManager -import io.github.moulberry.notenoughupdates.util.Utils import io.github.notenoughupdates.moulconfig.internal.TextRenderUtils import net.minecraft.client.Minecraft import net.minecraft.client.gui.FontRenderer @@ -41,6 +41,7 @@ import net.minecraft.item.ItemStack import net.minecraft.util.AxisAlignedBB import net.minecraft.util.MathHelper import net.minecraft.util.ResourceLocation +import org.lwjgl.input.Mouse import org.lwjgl.opengl.GL11 import java.awt.Color import java.nio.FloatBuffer @@ -95,6 +96,15 @@ object RenderUtils { highlight(color, x, y) } + fun getMouseX(): Int { + return Mouse.getX() * GuiScreenUtils.scaledWindowWidth / Minecraft.getMinecraft().displayWidth + } + + fun getMouseY(): Int { + val height = GuiScreenUtils.scaledWindowHeight + return height - Mouse.getY() * height / Minecraft.getMinecraft().displayHeight - 1 + } + fun highlight(color: Color, x: Int, y: Int) { GlStateManager.disableLighting() GlStateManager.disableDepth() @@ -504,8 +514,8 @@ object RenderUtils { fun Position.transform(): Pair { GlStateManager.translate(getAbsX().toFloat(), getAbsY().toFloat(), 0F) GlStateManager.scale(effectiveScale, effectiveScale, 1F) - val x = ((Utils.getMouseX() - getAbsX()) / effectiveScale).toInt() - val y = ((Utils.getMouseY() - getAbsY()) / effectiveScale).toInt() + val x = ((getMouseX() - getAbsX()) / effectiveScale).toInt() + val y = ((getMouseY() - getAbsY()) / effectiveScale).toInt() return x to y } @@ -1712,7 +1722,7 @@ object RenderUtils { fun drawRoundTexturedRect(x: Int, y: Int, width: Int, height: Int, filter: Int, radius: Int = 10, smoothness: Int = 1) { // if radius is 0 then just draw a normal textured rect if (radius <= 0) { - Utils.drawTexturedRect(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), filter) + GuiRenderUtils.drawTexturedRect(x, y, width, height, filter = filter) return } @@ -1731,7 +1741,7 @@ object RenderUtils { GlStateManager.pushMatrix() ShaderManager.enableShader(ShaderManager.Shaders.ROUNDED_TEXTURE) - Utils.drawTexturedRect(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), filter) + GuiRenderUtils.drawTexturedRect(x, y, width, height, filter = filter) ShaderManager.disableShader() GlStateManager.popMatrix() @@ -1832,6 +1842,7 @@ object RenderUtils { GlStateManager.popMatrix() } + // todo merge with the one in GuiRenderUtils fun drawGradientRect( left: Int, top: Int, @@ -1871,13 +1882,6 @@ object RenderUtils { GlStateManager.enableTexture2D() } - // TODO move off of neu function - fun drawTexturedRect(x: Float, y: Float) { - with(ScaledResolution(Minecraft.getMinecraft())) { - Utils.drawTexturedRect(x, y, scaledWidth.toFloat(), scaledHeight.toFloat(), GL11.GL_NEAREST) - } - } - fun getAlpha(): Float { colorBuffer.clear() GlStateManager.getFloat(GL11.GL_CURRENT_COLOR, colorBuffer) diff --git a/src/main/java/at/hannibal2/skyhanni/utils/SkyBlockItemModifierUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/SkyBlockItemModifierUtils.kt index 4d0cf446856f..128b5d11e877 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/SkyBlockItemModifierUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/SkyBlockItemModifierUtils.kt @@ -3,6 +3,7 @@ package at.hannibal2.skyhanni.utils import at.hannibal2.skyhanni.config.ConfigManager import at.hannibal2.skyhanni.data.PetAPI import at.hannibal2.skyhanni.mixins.hooks.ItemStackCachedData +import at.hannibal2.skyhanni.utils.ItemUtils.extraAttributes import at.hannibal2.skyhanni.utils.ItemUtils.getInternalName import at.hannibal2.skyhanni.utils.ItemUtils.getLore import at.hannibal2.skyhanni.utils.ItemUtils.name @@ -272,7 +273,7 @@ object SkyBlockItemModifierUtils { private fun ItemStack.getAttributeByte(label: String) = getExtraAttributes()?.getByte(label) ?: 0 - fun ItemStack.getExtraAttributes() = tagCompound?.getCompoundTag("ExtraAttributes") + fun ItemStack.getExtraAttributes() = tagCompound?.extraAttributes class GemstoneSlot(val type: GemstoneType, val quality: GemstoneQuality) { diff --git a/src/main/java/at/hannibal2/skyhanni/utils/json/JsonUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/json/JsonUtils.kt index 750bd96aa662..ed4a5fbe6709 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/json/JsonUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/json/JsonUtils.kt @@ -1,9 +1,11 @@ package at.hannibal2.skyhanni.utils.json +import at.hannibal2.skyhanni.config.ConfigManager import com.google.gson.Gson import com.google.gson.JsonArray import com.google.gson.JsonElement import com.google.gson.JsonObject +import java.io.File import java.io.Reader import kotlin.reflect.jvm.javaType import kotlin.reflect.typeOf @@ -15,6 +17,16 @@ inline fun Gson.fromJson(jsonElement: JsonElement): T = inline fun Gson.fromJson(reader: Reader): T = this.fromJson(reader, typeOf().javaType) +fun File.getJson(): JsonObject? { + return try { + this.inputStream().use { + ConfigManager.gson.fromJson(it.reader(), JsonObject::class.java) + } + } catch (e: Exception) { + null + } +} + /** * Straight forward deep copy. This is included in gson as well, but different versions have it exposed privately instead of publicly, * so this reimplementation is here as an always public alternative. @@ -35,3 +47,9 @@ fun JsonElement.shDeepCopy(): JsonElement { else -> this } } + +fun Iterable.toJsonArray(): JsonArray = JsonArray().also { + for (jsonElement in this) { + it.add(jsonElement) + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/utils/json/Shimmy.kt b/src/main/java/at/hannibal2/skyhanni/utils/json/Shimmy.kt new file mode 100644 index 000000000000..31022da58d19 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/json/Shimmy.kt @@ -0,0 +1,64 @@ +package at.hannibal2.skyhanni.utils.json + +import at.hannibal2.skyhanni.config.ConfigManager +import com.google.gson.JsonElement +import java.lang.reflect.Field + +// Copied from NEU +class Shimmy private constructor( + val source: Any, + val field: Field, +) { + companion object { + private fun shimmy(source: Any?, fieldName: String): Any? { + if (source == null) return null + return try { + val declaredField = source.javaClass.getDeclaredField(fieldName) + declaredField.isAccessible = true + declaredField.get(source) + } catch (e: NoSuchFieldException) { + null + } + } + + @JvmStatic + fun makeShimmy(source: Any?, path: List): Shimmy? { + if (path.isEmpty()) + return null + var source = source + for (part in path.dropLast(1)) { + source = shimmy(source, part) + } + if (source == null) return null + val lastName = path.last() + return try { + val field = source.javaClass.getDeclaredField(lastName) + field.isAccessible = true + Shimmy( + source, + field, + ) + } catch (e: NoSuchFieldException) { + null + } + } + + } + + val clazz: Class<*> = field.type + fun get(): Any? { + return field.get(source) + } + + fun set(value: Any?) { + field.set(source, value) + } + + fun getJson(): JsonElement { + return ConfigManager.gson.toJsonTree(get()) + } + + fun setJson(element: JsonElement) { + set(ConfigManager.gson.fromJson(element, clazz)) + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/utils/renderables/Renderable.kt b/src/main/java/at/hannibal2/skyhanni/utils/renderables/Renderable.kt index d161cf2b0eeb..f3f704f374a2 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/renderables/Renderable.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/renderables/Renderable.kt @@ -14,6 +14,7 @@ import at.hannibal2.skyhanni.utils.CollectionUtils.contains import at.hannibal2.skyhanni.utils.ColorUtils import at.hannibal2.skyhanni.utils.ColorUtils.addAlpha import at.hannibal2.skyhanni.utils.ColorUtils.darker +import at.hannibal2.skyhanni.utils.GuiRenderUtils import at.hannibal2.skyhanni.utils.KeyboardManager.isKeyClicked import at.hannibal2.skyhanni.utils.LorenzColor import at.hannibal2.skyhanni.utils.LorenzLogger @@ -29,7 +30,6 @@ import at.hannibal2.skyhanni.utils.renderables.RenderableUtils.renderXAligned import at.hannibal2.skyhanni.utils.renderables.RenderableUtils.renderXYAligned import at.hannibal2.skyhanni.utils.renderables.RenderableUtils.renderYAligned import at.hannibal2.skyhanni.utils.shader.ShaderManager -import io.github.moulberry.notenoughupdates.util.Utils import io.github.notenoughupdates.moulconfig.gui.GuiScreenElementWrapper import net.minecraft.client.Minecraft import net.minecraft.client.gui.Gui @@ -1272,18 +1272,9 @@ interface Renderable { override fun render(posX: Int, posY: Int) { Minecraft.getMinecraft().textureManager.bindTexture(texture) + GlStateManager.color(1f, 1f, 1f, alpha / 255f) - Utils.drawTexturedRect( - 0f, - 0f, - width.toFloat(), - height.toFloat(), - uMin, - uMax, - vMin, - vMax, - GL11.GL_NEAREST, - ) + GuiRenderUtils.drawTexturedRect(0, 0, width, height, uMin, uMax, vMin, vMax) GlStateManager.color(1f, 1f, 1f, 1f) GlStateManager.translate(padding.toFloat(), padding.toFloat(), 0f) @@ -1312,17 +1303,7 @@ interface Renderable { override fun render(posX: Int, posY: Int) { Minecraft.getMinecraft().textureManager.bindTexture(texture) GlStateManager.color(1f, 1f, 1f, alpha / 255f) - Utils.drawTexturedRect( - 0f, - 0f, - width.toFloat(), - height.toFloat(), - uMin, - uMax, - vMin, - vMax, - GL11.GL_NEAREST, - ) + GuiRenderUtils.drawTexturedRect(0, 0, width, height, uMin, uMax, vMin, vMax) GlStateManager.color(1f, 1f, 1f, 1f) } } diff --git a/src/main/java/at/hannibal2/skyhanni/utils/renderables/RenderableTooltips.kt b/src/main/java/at/hannibal2/skyhanni/utils/renderables/RenderableTooltips.kt index b37c89bb552d..b6c4225ba275 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/renderables/RenderableTooltips.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/renderables/RenderableTooltips.kt @@ -5,7 +5,6 @@ import at.hannibal2.skyhanni.utils.ItemUtils.getLore import at.hannibal2.skyhanni.utils.LorenzColor import at.hannibal2.skyhanni.utils.RenderUtils import at.hannibal2.skyhanni.utils.renderables.RenderableUtils.renderXAligned -import io.github.moulberry.notenoughupdates.util.Utils import net.minecraft.client.Minecraft import net.minecraft.client.gui.ScaledResolution import net.minecraft.client.renderer.GlStateManager @@ -44,8 +43,8 @@ object RenderableTooltips { val tips = tooltip.tips if (tips.isEmpty()) return - val x = Utils.getMouseX() + 12 - val y = Utils.getMouseY() - if (tips.size > 1) 1 else -7 + val x = RenderUtils.getMouseX() + 12 + val y = RenderUtils.getMouseY() - if (tips.size > 1) 1 else -7 val borderColorStart = tooltip.getBorderColor() val scaled = ScaledResolution(Minecraft.getMinecraft()) val isSpacedTitle = tooltip.isSpacedTitle() From f78d580531941d52bbbeca625c3f078beae8996a Mon Sep 17 00:00:00 2001 From: hannibal2 <24389977+hannibal00212@users.noreply.github.com> Date: Mon, 23 Sep 2024 15:55:25 +0200 Subject: [PATCH 14/38] code cleanup --- .../at/hannibal2/skyhanni/events/NeuRepositoryReloadEvent.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/at/hannibal2/skyhanni/events/NeuRepositoryReloadEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/NeuRepositoryReloadEvent.kt index 97a3f580c555..9feb170c1f08 100644 --- a/src/main/java/at/hannibal2/skyhanni/events/NeuRepositoryReloadEvent.kt +++ b/src/main/java/at/hannibal2/skyhanni/events/NeuRepositoryReloadEvent.kt @@ -15,9 +15,7 @@ import java.io.File import java.lang.reflect.Type class NeuRepositoryReloadEvent : LorenzEvent() { - fun getConstant(file: String): JsonObject? { - return File(manager.repoLocation, "constants/$file.json").getJson() - } + fun getConstant(file: String): JsonObject? = File(manager.repoLocation, "constants/$file.json").getJson() inline fun readConstant(file: String, gson: Gson = ConfigManager.gson): T { val data = getConstant(file) ?: ErrorManager.skyHanniError("$file failed to load from neu repo!") From 7c9f889efd9c5d889394f8bf00288ef1c38f8ae4 Mon Sep 17 00:00:00 2001 From: CalMWolfs <94038482+CalMWolfs@users.noreply.github.com> Date: Tue, 24 Sep 2024 00:11:18 +1000 Subject: [PATCH 15/38] Backend: Add Http Request Patching (#2578) --- .../skyhanni/config/commands/Commands.kt | 4 +- .../at/hannibal2/skyhanni/data/MayorAPI.kt | 4 +- .../data/bazaar/HypixelBazaarFetcher.kt | 4 +- .../features/chat/translation/Translator.kt | 4 +- .../features/garden/GardenNextJacobContest.kt | 6 +- .../garden/farming/FarmingWeightDisplay.kt | 8 +-- .../inventory/bazaar/BazaarDataHolder.kt | 4 +- .../mining/eventtracker/MiningEventTracker.kt | 6 +- .../features/misc/update/UpdateManager.kt | 4 +- .../utils/{APIUtil.kt => APIUtils.kt} | 54 ++++++++++++------ src/main/resources/mykeystore.jks | Bin 0 -> 124141 bytes 11 files changed, 59 insertions(+), 39 deletions(-) rename src/main/java/at/hannibal2/skyhanni/utils/{APIUtil.kt => APIUtils.kt} (82%) create mode 100644 src/main/resources/mykeystore.jks diff --git a/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt b/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt index cf4679e396d6..a52852a8193f 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt +++ b/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt @@ -94,7 +94,7 @@ import at.hannibal2.skyhanni.test.command.TestChatCommand import at.hannibal2.skyhanni.test.command.TrackParticlesCommand import at.hannibal2.skyhanni.test.command.TrackSoundsCommand import at.hannibal2.skyhanni.test.graph.GraphEditor -import at.hannibal2.skyhanni.utils.APIUtil +import at.hannibal2.skyhanni.utils.APIUtils import at.hannibal2.skyhanni.utils.ChatUtils import at.hannibal2.skyhanni.utils.ExtendedChatColor import at.hannibal2.skyhanni.utils.ItemPriceUtils @@ -382,7 +382,7 @@ object Commands { registerCommand( "shtogglehypixelapierrors", "Show/hide hypixel api error messages in chat", - ) { APIUtil.toggleApiErrorMessages() } + ) { APIUtils.toggleApiErrorMessages() } registerCommand( "shclearcropspeed", "Reset garden crop speed data and best crop time data", diff --git a/src/main/java/at/hannibal2/skyhanni/data/MayorAPI.kt b/src/main/java/at/hannibal2/skyhanni/data/MayorAPI.kt index b4fb7eaa2dc8..469c48489245 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/MayorAPI.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/MayorAPI.kt @@ -16,7 +16,7 @@ import at.hannibal2.skyhanni.events.LorenzChatEvent import at.hannibal2.skyhanni.events.SecondPassedEvent import at.hannibal2.skyhanni.features.fame.ReminderUtils import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule -import at.hannibal2.skyhanni.utils.APIUtil +import at.hannibal2.skyhanni.utils.APIUtils import at.hannibal2.skyhanni.utils.ChatUtils import at.hannibal2.skyhanni.utils.CollectionUtils.nextAfter import at.hannibal2.skyhanni.utils.CollectionUtils.put @@ -216,7 +216,7 @@ object MayorAPI { SkyHanniMod.coroutineScope.launch { val url = "https://api.hypixel.net/v2/resources/skyblock/election" - val jsonObject = withContext(dispatcher) { APIUtil.getJSONResponse(url) } + val jsonObject = withContext(dispatcher) { APIUtils.getJSONResponse(url) } rawMayorData = ConfigManager.gson.fromJson(jsonObject) val data = rawMayorData ?: return@launch val map = mutableMapOf() diff --git a/src/main/java/at/hannibal2/skyhanni/data/bazaar/HypixelBazaarFetcher.kt b/src/main/java/at/hannibal2/skyhanni/data/bazaar/HypixelBazaarFetcher.kt index 29a16944c3d3..13044b7c3151 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/bazaar/HypixelBazaarFetcher.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/bazaar/HypixelBazaarFetcher.kt @@ -6,7 +6,7 @@ import at.hannibal2.skyhanni.events.LorenzTickEvent import at.hannibal2.skyhanni.features.inventory.bazaar.BazaarData import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.test.command.ErrorManager -import at.hannibal2.skyhanni.utils.APIUtil +import at.hannibal2.skyhanni.utils.APIUtils import at.hannibal2.skyhanni.utils.ChatUtils import at.hannibal2.skyhanni.utils.ItemUtils.itemName import at.hannibal2.skyhanni.utils.LorenzUtils @@ -46,7 +46,7 @@ object HypixelBazaarFetcher { val fetchType = if (nextFetchIsManual) "manual" else "automatic" nextFetchIsManual = false try { - val jsonResponse = withContext(Dispatchers.IO) { APIUtil.getJSONResponse(URL) }.asJsonObject + val jsonResponse = withContext(Dispatchers.IO) { APIUtils.getJSONResponse(URL) }.asJsonObject val response = ConfigManager.gson.fromJson(jsonResponse) if (response.success) { latestProductInformation = process(response.products) diff --git a/src/main/java/at/hannibal2/skyhanni/features/chat/translation/Translator.kt b/src/main/java/at/hannibal2/skyhanni/features/chat/translation/Translator.kt index 389b57fb0fc7..24809faac798 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/chat/translation/Translator.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/chat/translation/Translator.kt @@ -6,7 +6,7 @@ import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator import at.hannibal2.skyhanni.events.ConfigLoadEvent import at.hannibal2.skyhanni.events.LorenzChatEvent import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule -import at.hannibal2.skyhanni.utils.APIUtil +import at.hannibal2.skyhanni.utils.APIUtils import at.hannibal2.skyhanni.utils.ChatUtils import at.hannibal2.skyhanni.utils.ConditionalUtils.onToggle import at.hannibal2.skyhanni.utils.ConditionalUtils.transformIf @@ -112,7 +112,7 @@ object Translator { */ private fun getJSONResponse(urlString: String) = - APIUtil.getJSONResponseAsElement(urlString, false, "Google Translate API") + APIUtils.getJSONResponseAsElement(urlString, false, "Google Translate API") private fun getTranslationToEnglish(message: String): String { val url = diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/GardenNextJacobContest.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/GardenNextJacobContest.kt index ab8820b40fd6..4285a24af969 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/GardenNextJacobContest.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/GardenNextJacobContest.kt @@ -15,7 +15,7 @@ import at.hannibal2.skyhanni.events.TabListUpdateEvent import at.hannibal2.skyhanni.features.garden.GardenAPI.addCropIcon import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.test.command.ErrorManager -import at.hannibal2.skyhanni.utils.APIUtil +import at.hannibal2.skyhanni.utils.APIUtils import at.hannibal2.skyhanni.utils.ChatUtils import at.hannibal2.skyhanni.utils.ConfigUtils import at.hannibal2.skyhanni.utils.HypixelCommands @@ -543,7 +543,7 @@ object GardenNextJacobContest { suspend fun fetchUpcomingContests() { try { val url = "https://api.elitebot.dev/contests/at/now" - val result = withContext(dispatcher) { APIUtil.getJSONResponse(url) }.asJsonObject + val result = withContext(dispatcher) { APIUtils.getJSONResponse(url) }.asJsonObject val newContests = mutableMapOf() @@ -613,7 +613,7 @@ object GardenNextJacobContest { val url = "https://api.elitebot.dev/contests/at/now" val body = Gson().toJson(formatted) - val result = withContext(dispatcher) { APIUtil.postJSONIsSuccessful(url, body) } + val result = withContext(dispatcher) { APIUtils.postJSONIsSuccessful(url, body) } if (result) { ChatUtils.chat("Successfully submitted this years upcoming contests, thank you for helping everyone out!") diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/farming/FarmingWeightDisplay.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/farming/FarmingWeightDisplay.kt index 222e75f4f296..1fa4b57e6a56 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/farming/FarmingWeightDisplay.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/farming/FarmingWeightDisplay.kt @@ -20,7 +20,7 @@ import at.hannibal2.skyhanni.features.garden.farming.GardenCropSpeed.getSpeed import at.hannibal2.skyhanni.features.garden.pests.PestType import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.test.command.ErrorManager -import at.hannibal2.skyhanni.utils.APIUtil +import at.hannibal2.skyhanni.utils.APIUtils import at.hannibal2.skyhanni.utils.ChatUtils import at.hannibal2.skyhanni.utils.LorenzUtils import at.hannibal2.skyhanni.utils.LorenzUtils.round @@ -447,7 +447,7 @@ object FarmingWeightDisplay { val atRank = if (isEtaEnabled() && goalRank != 10001) "&atRank=$goalRank" else "" val url = "https://api.elitebot.dev/leaderboard/rank/farmingweight/$uuid/$profileId$includeUpcoming$atRank" - val apiResponse = APIUtil.getJSONResponse(url) + val apiResponse = APIUtils.getJSONResponse(url) try { val apiData = toEliteLeaderboardJson(apiResponse).data @@ -477,7 +477,7 @@ object FarmingWeightDisplay { private fun loadWeight(localProfile: String) { val uuid = LorenzUtils.getPlayerUuid() val url = "https://api.elitebot.dev/weight/$uuid" - val apiResponse = APIUtil.getJSONResponse(url) + val apiResponse = APIUtils.getJSONResponse(url) var error: Throwable? = null @@ -573,7 +573,7 @@ object FarmingWeightDisplay { if (attemptingCropWeightFetch || hasFetchedCropWeights) return attemptingCropWeightFetch = true val url = "https://api.elitebot.dev/weights/all" - val apiResponse = APIUtil.getJSONResponse(url) + val apiResponse = APIUtils.getJSONResponse(url) try { val apiData = eliteWeightApiGson.fromJson(apiResponse) diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/bazaar/BazaarDataHolder.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/bazaar/BazaarDataHolder.kt index 3d7867ad9540..cf9cb6d2ec06 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/inventory/bazaar/BazaarDataHolder.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/bazaar/BazaarDataHolder.kt @@ -5,7 +5,7 @@ import at.hannibal2.skyhanni.config.ConfigManager import at.hannibal2.skyhanni.data.jsonobjects.other.SkyblockItemsDataJson import at.hannibal2.skyhanni.features.rift.RiftAPI import at.hannibal2.skyhanni.test.command.ErrorManager -import at.hannibal2.skyhanni.utils.APIUtil +import at.hannibal2.skyhanni.utils.APIUtils import at.hannibal2.skyhanni.utils.NEUInternalName import at.hannibal2.skyhanni.utils.NEUItems import at.hannibal2.skyhanni.utils.json.fromJson @@ -22,7 +22,7 @@ class BazaarDataHolder { private fun loadNpcPrices(): MutableMap { val list = mutableMapOf() - val apiResponse = APIUtil.getJSONResponse("https://api.hypixel.net/v2/resources/skyblock/items") + val apiResponse = APIUtils.getJSONResponse("https://api.hypixel.net/v2/resources/skyblock/items") try { val itemsData = ConfigManager.gson.fromJson(apiResponse) diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/eventtracker/MiningEventTracker.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/eventtracker/MiningEventTracker.kt index 97806b6fb0ad..a076ba68f0c3 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/mining/eventtracker/MiningEventTracker.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/eventtracker/MiningEventTracker.kt @@ -13,7 +13,7 @@ import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent import at.hannibal2.skyhanni.events.SecondPassedEvent import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.test.command.ErrorManager -import at.hannibal2.skyhanni.utils.APIUtil +import at.hannibal2.skyhanni.utils.APIUtils import at.hannibal2.skyhanni.utils.ChatUtils import at.hannibal2.skyhanni.utils.LorenzUtils import at.hannibal2.skyhanni.utils.LorenzUtils.isInIsland @@ -160,7 +160,7 @@ object MiningEventTracker { private fun sendData(json: String) { val response = try { - APIUtil.postJSON("https://api.soopy.dev/skyblock/chevents/set", json) + APIUtils.postJSON("https://api.soopy.dev/skyblock/chevents/set", json) } catch (e: IOException) { if (LorenzUtils.debug) { ErrorManager.logErrorWithData( @@ -194,7 +194,7 @@ object MiningEventTracker { canRequestAt = SimpleTimeMark.now() + defaultCooldown SkyHanniMod.coroutineScope.launch { val data = try { - APIUtil.getJSONResponse("https://api.soopy.dev/skyblock/chevents/get") + APIUtils.getJSONResponse("https://api.soopy.dev/skyblock/chevents/get") } catch (e: Exception) { apiErrorCount++ canRequestAt = SimpleTimeMark.now() + 20.minutes diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/update/UpdateManager.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/update/UpdateManager.kt index 040b6741a847..9948d5eeb973 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/update/UpdateManager.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/update/UpdateManager.kt @@ -5,12 +5,12 @@ import at.hannibal2.skyhanni.config.features.About.UpdateStream import at.hannibal2.skyhanni.events.ConfigLoadEvent import at.hannibal2.skyhanni.events.LorenzTickEvent import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule +import at.hannibal2.skyhanni.utils.APIUtils import at.hannibal2.skyhanni.utils.ChatUtils import at.hannibal2.skyhanni.utils.ConditionalUtils.onToggle import at.hannibal2.skyhanni.utils.DelayedRun import at.hannibal2.skyhanni.utils.LorenzLogger import com.google.gson.JsonElement -import io.github.moulberry.notenoughupdates.util.ApiUtil import io.github.notenoughupdates.moulconfig.observer.Property import io.github.notenoughupdates.moulconfig.processor.MoulConfigProcessor import moe.nea.libautoupdate.CurrentVersion @@ -161,7 +161,7 @@ object UpdateManager { context.cleanup() UpdateUtils.patchConnection { if (it is HttpsURLConnection) { - ApiUtil.patchHttpsRequest(it) + APIUtils.patchHttpsRequest(it) } } } diff --git a/src/main/java/at/hannibal2/skyhanni/utils/APIUtil.kt b/src/main/java/at/hannibal2/skyhanni/utils/APIUtils.kt similarity index 82% rename from src/main/java/at/hannibal2/skyhanni/utils/APIUtil.kt rename to src/main/java/at/hannibal2/skyhanni/utils/APIUtils.kt index 483bb6d047ab..d28263af6c7d 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/APIUtil.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/APIUtils.kt @@ -16,17 +16,41 @@ import org.apache.http.impl.client.HttpClientBuilder import org.apache.http.impl.client.HttpClients import org.apache.http.message.BasicHeader import org.apache.http.util.EntityUtils -import java.io.BufferedReader -import java.io.File -import java.io.FileInputStream -import java.io.InputStreamReader -import java.nio.charset.StandardCharsets +import java.security.KeyStore +import javax.net.ssl.HttpsURLConnection +import javax.net.ssl.KeyManagerFactory +import javax.net.ssl.SSLContext +import javax.net.ssl.TrustManagerFactory -object APIUtil { +object APIUtils { private val parser = JsonParser() private var showApiErrors = false + private val ctx: SSLContext? = run { + try { + val myKeyStore = KeyStore.getInstance("JKS") + myKeyStore.load(APIUtils.javaClass.getResourceAsStream("/keystore.jks"), "changeit".toCharArray()) + val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) + val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) + kmf.init(myKeyStore, null) + tmf.init(myKeyStore) + SSLContext.getInstance("TLS").apply { + init(kmf.keyManagers, tmf.trustManagers, null) + } + } catch (e: Exception) { + println("Failed to load keystore. A lot of API requests won't work") + e.printStackTrace() + null + } + } + + fun patchHttpsRequest(connection: HttpsURLConnection) { + ctx?.let { + connection.sslSocketFactory = it.socketFactory + } + } + data class ApiResponse(val success: Boolean, val message: String?, val data: JsonObject) private val builder: HttpClientBuilder = @@ -34,19 +58,19 @@ object APIUtil { .setDefaultHeaders( mutableListOf( BasicHeader("Pragma", "no-cache"), - BasicHeader("Cache-Control", "no-cache") - ) + BasicHeader("Cache-Control", "no-cache"), + ), ) .setDefaultRequestConfig( RequestConfig.custom() - .build() + .build(), ) .useSystemProperties() /** * TODO * make suspend - * use withContext(Dispatchers.IO) { APIUtil.getJSONResponse(url) }.asJsonObject + * use withContext(Dispatchers.IO) { APIUtils.getJSONResponse(url) }.asJsonObject */ fun getJSONResponse(urlString: String, silentError: Boolean = false) = getJSONResponseAsElement(urlString, silentError) as JsonObject @@ -76,7 +100,7 @@ object APIUtil { ChatUtils.clickableChat( "Problems with detecting the Hypixel API. §eClick here to hide this message for now.", onClick = { toggleApiErrorMessages() }, - "§eClick to run /shtogglehypixelapierrors!" + "§eClick to run /shtogglehypixelapierrors!", ) } ErrorManager.skyHanniError( @@ -84,7 +108,7 @@ object APIUtil { "error message" to "$message(502 Bad Gateway)", "apiName" to apiName, "urlString" to urlString, - "returnedData" to retSrc + "returnedData" to retSrc, ) } else { ErrorManager.skyHanniError( @@ -92,7 +116,7 @@ object APIUtil { "error message" to message, "apiName" to apiName, "urlString" to urlString, - "returnedData" to retSrc + "returnedData" to retSrc, ) } } @@ -181,10 +205,6 @@ object APIUtil { return false } - fun readFile(file: File): BufferedReader { - return BufferedReader(InputStreamReader(FileInputStream(file), StandardCharsets.UTF_8)) - } - // TODO remove command, use clickable chat message instead fun toggleApiErrorMessages() { showApiErrors = !showApiErrors diff --git a/src/main/resources/mykeystore.jks b/src/main/resources/mykeystore.jks new file mode 100644 index 0000000000000000000000000000000000000000..75039b79a01089ca19b91d4cb2bc4538236f8157 GIT binary patch literal 124141 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

-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 Date: Mon, 23 Sep 2024 16:47:07 +0200 Subject: [PATCH 16/38] Feature: Reindrake Warp Helper (#2569) --- .../features/event/winter/WinterConfig.java | 6 +++ .../event/winter/ReindrakeWarpHelper.kt | 42 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 src/main/java/at/hannibal2/skyhanni/features/event/winter/ReindrakeWarpHelper.kt diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/event/winter/WinterConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/event/winter/WinterConfig.java index 837f4c7ea811..550ceaeee0ec 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/event/winter/WinterConfig.java +++ b/src/main/java/at/hannibal2/skyhanni/config/features/event/winter/WinterConfig.java @@ -46,4 +46,10 @@ public class WinterConfig { @FeatureToggle public boolean newYearCakeReminder = true; + @Expose + @ConfigOption(name = "Reindrake Warp Helper", desc = "Sends a clickable message in chat to warp to the Winter Island spawn when a Reindrake spawns.") + @ConfigEditorBoolean + @FeatureToggle + public boolean reindrakeWarpHelper = true; + } diff --git a/src/main/java/at/hannibal2/skyhanni/features/event/winter/ReindrakeWarpHelper.kt b/src/main/java/at/hannibal2/skyhanni/features/event/winter/ReindrakeWarpHelper.kt new file mode 100644 index 000000000000..7131426d42b3 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/event/winter/ReindrakeWarpHelper.kt @@ -0,0 +1,42 @@ +package at.hannibal2.skyhanni.features.event.winter + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.data.IslandType +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule +import at.hannibal2.skyhanni.utils.ChatUtils +import at.hannibal2.skyhanni.utils.HypixelCommands +import at.hannibal2.skyhanni.utils.LorenzUtils.isInIsland +import at.hannibal2.skyhanni.utils.RegexUtils.matches +import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +@SkyHanniModule +object ReindrakeWarpHelper { + + private val config get() = SkyHanniMod.feature.event.winter + + private val patternGroup = RepoPattern.group("event.winter.reindrakewarphelper") + + /** + * REGEX-TEST: §c§lWOAH! §cA §4Reindrake §cwas summoned from the depths! + */ + private val spawnPattern by patternGroup.pattern( + "spawn.message", + "§c§lWOAH! §cA §4Reindrake §cwas summoned from the depths!", + ) + + @SubscribeEvent + fun onMessage(event: LorenzChatEvent) { + if (!isEnabled()) return + if (!spawnPattern.matches(event.message)) return + ChatUtils.clickToActionOrDisable( + "A Reindrake was detected. Click to warp to the Winter Island spawn!", + config::reindrakeWarpHelper, + actionName = "warp to winter island spawn", + action = { HypixelCommands.warp("winter") } + ) + } + + fun isEnabled() = IslandType.WINTER.isInIsland() && config.reindrakeWarpHelper +} From 2eed7efac2dab7ec54043d188d814b17052cebdd Mon Sep 17 00:00:00 2001 From: CalMWolfs <94038482+CalMWolfs@users.noreply.github.com> Date: Tue, 24 Sep 2024 01:16:31 +1000 Subject: [PATCH 17/38] Backend: Replace round and roundToPrecision with roundTo (#1931) Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com> --- .../core/config/gui/GuiPositionEditor.kt | 5 ++-- .../at/hannibal2/skyhanni/data/QuiverAPI.kt | 4 +-- .../at/hannibal2/skyhanni/data/model/Graph.kt | 4 +-- .../skyhanni/events/PlaySoundEvent.kt | 6 ++--- .../skyhanni/events/ReceiveParticleEvent.kt | 4 +-- .../skyhanni/features/combat/BestiaryData.kt | 4 +-- .../damageindicator/DamageIndicatorManager.kt | 4 +-- .../combat/ghostcounter/GhostCounter.kt | 4 +-- .../features/combat/ghostcounter/GhostUtil.kt | 8 +++--- .../features/event/diana/BurrowWarpHelper.kt | 8 +++--- .../event/hoppity/HoppityCollectionStats.kt | 4 +-- .../event/hoppity/HoppityEggLocator.kt | 4 +-- .../features/garden/FarmingFortuneDisplay.kt | 4 +-- .../features/garden/GardenYawAndPitch.kt | 6 ++--- .../garden/composter/ComposterOverlay.kt | 6 ++--- .../garden/contest/FarmingPersonalBestGain.kt | 4 +-- .../contest/JacobContestFFNeededDisplay.kt | 4 +-- .../contest/JacobContestStatsSummary.kt | 4 +-- .../garden/contest/JacobContestTimeNeeded.kt | 6 ++--- .../features/garden/farming/CropSpeedMeter.kt | 6 ++--- .../garden/farming/FarmingWeightDisplay.kt | 6 ++--- .../farming/GardenCropMilestoneDisplay.kt | 6 ++--- .../farming/lane/FarmingLaneFeatures.kt | 6 ++--- .../inventory/GardenCropMilestoneInventory.kt | 4 +-- .../garden/inventory/SkyMartCopperPrice.kt | 4 +-- .../ChocolateFactoryDataLoader.kt | 8 +++--- .../ChocolateFactoryTooltip.kt | 4 +-- .../abilitycooldown/ItemAbility.kt | 4 +-- .../abilitycooldown/ItemAbilityCooldown.kt | 4 +-- .../features/mining/MineshaftPityDisplay.kt | 6 ++--- .../features/mining/PowderPerHotmPerk.kt | 4 +-- .../solver/FossilSolverDisplay.kt | 4 +-- .../skyhanni/features/misc/IslandAreas.kt | 4 +-- .../features/misc/MovementSpeedDisplay.kt | 4 +-- .../skyhanni/features/misc/TpsCounter.kt | 4 +-- .../features/misc/UserLuckBreakdown.kt | 8 +++--- .../features/misc/limbo/LimboPlaytime.kt | 4 +-- .../features/misc/limbo/LimboTimeTracker.kt | 10 +++---- .../features/misc/pets/PetExpTooltip.kt | 6 ++--- .../livingcave/LivingMetalSuitProgress.kt | 4 +-- .../rift/everywhere/CruxTalismanDisplay.kt | 4 +-- .../rift/everywhere/motes/RiftMotesOrb.kt | 4 +-- .../features/skillprogress/SkillProgress.kt | 4 +-- .../features/skillprogress/SkillTooltip.kt | 6 ++--- .../at/hannibal2/skyhanni/test/PacketTest.kt | 4 +-- .../skyhanni/test/SkyHanniDebugsAndTests.kt | 11 ++++---- .../test/command/TrackParticlesCommand.kt | 4 +-- .../test/command/TrackSoundsCommand.kt | 4 +-- .../skyhanni/utils/GuiRenderUtils.kt | 4 +-- .../hannibal2/skyhanni/utils/LorenzUtils.kt | 21 ++++----------- .../at/hannibal2/skyhanni/utils/LorenzVec.kt | 10 +++---- .../at/hannibal2/skyhanni/utils/NumberUtil.kt | 26 +++++++++---------- 52 files changed, 145 insertions(+), 160 deletions(-) diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiPositionEditor.kt b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiPositionEditor.kt index 3e64d5f5abff..ba3a576754dc 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiPositionEditor.kt +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiPositionEditor.kt @@ -27,11 +27,10 @@ import at.hannibal2.skyhanni.data.OtherInventoryData import at.hannibal2.skyhanni.mixins.transformers.gui.AccessorGuiContainer import at.hannibal2.skyhanni.utils.GuiRenderUtils import at.hannibal2.skyhanni.utils.KeyboardManager -import at.hannibal2.skyhanni.utils.LorenzUtils.round +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.compat.GuiScreenUtils import at.hannibal2.skyhanni.utils.compat.SkyhanniBaseScreen import net.minecraft.client.Minecraft -import net.minecraft.client.gui.ScaledResolution import net.minecraft.client.gui.inventory.GuiContainer import net.minecraft.client.renderer.GlStateManager import org.lwjgl.input.Keyboard @@ -100,7 +99,7 @@ class GuiPositionEditor( } val pos = positions[displayPos] - val location = "§7x: §e${pos.rawX}§7, y: §e${pos.rawY}§7, scale: §e${pos.scale.round(2)}" + val location = "§7x: §e${pos.rawX}§7, y: §e${pos.rawY}§7, scale: §e${pos.scale.roundTo(2)}" GuiRenderUtils.drawStringCentered("§b" + pos.internalName, getScaledWidth() / 2, 18) GuiRenderUtils.drawStringCentered(location, getScaledWidth() / 2, 28) if (pos.canJumpToConfigOptions()) diff --git a/src/main/java/at/hannibal2/skyhanni/data/QuiverAPI.kt b/src/main/java/at/hannibal2/skyhanni/data/QuiverAPI.kt index 88daf40b9520..2f516610ca51 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/QuiverAPI.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/QuiverAPI.kt @@ -16,10 +16,10 @@ import at.hannibal2.skyhanni.utils.ItemUtils.getInternalNameOrNull import at.hannibal2.skyhanni.utils.ItemUtils.getItemCategoryOrNull import at.hannibal2.skyhanni.utils.ItemUtils.getLore import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.NEUInternalName import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName import at.hannibal2.skyhanni.utils.NumberUtil.formatInt +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher import at.hannibal2.skyhanni.utils.RegexUtils.matches import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.getExtraAttributes @@ -236,7 +236,7 @@ object QuiverAPI { } } - fun Int.asArrowPercentage() = ((this.toFloat() / MAX_ARROW_AMOUNT) * 100).round(1) + fun Int.asArrowPercentage() = ((this.toFloat() / MAX_ARROW_AMOUNT) * 100).roundTo(1) fun hasBowInInventory() = hasBow diff --git a/src/main/java/at/hannibal2/skyhanni/data/model/Graph.kt b/src/main/java/at/hannibal2/skyhanni/data/model/Graph.kt index b9ad1251ca8c..bc4be57a16d4 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/model/Graph.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/model/Graph.kt @@ -1,7 +1,7 @@ package at.hannibal2.skyhanni.data.model -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.LorenzVec +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.json.SkyHanniTypeAdapters.registerTypeAdapter import at.hannibal2.skyhanni.utils.json.fromJson import com.google.gson.GsonBuilder @@ -63,7 +63,7 @@ value class Graph( out.beginObject() for ((node, weight) in it.neighbours) { val id = node.id.toString() - out.name(id).value(weight.round(2)) + out.name(id).value(weight.roundTo(2)) } out.endObject() diff --git a/src/main/java/at/hannibal2/skyhanni/events/PlaySoundEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/PlaySoundEvent.kt index 2f793ff28860..386167123768 100644 --- a/src/main/java/at/hannibal2/skyhanni/events/PlaySoundEvent.kt +++ b/src/main/java/at/hannibal2/skyhanni/events/PlaySoundEvent.kt @@ -1,8 +1,8 @@ package at.hannibal2.skyhanni.events import at.hannibal2.skyhanni.utils.LocationUtils.distanceToPlayer -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.LorenzVec +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import net.minecraftforge.fml.common.eventhandler.Cancelable @Cancelable @@ -12,9 +12,7 @@ class PlaySoundEvent(val soundName: String, val location: LorenzVec, val pitch: val distanceToPlayer by lazy { location.distanceToPlayer() } override fun toString(): String { return "PlaySoundEvent(soundName='$soundName', pitch=$pitch, volume=$volume, location=${location.round(1)}, distanceToPlayer=${ - distanceToPlayer.round( - 1 - ) + distanceToPlayer.roundTo(1) })" } } diff --git a/src/main/java/at/hannibal2/skyhanni/events/ReceiveParticleEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/ReceiveParticleEvent.kt index ef296326da84..7c695c7eec2f 100644 --- a/src/main/java/at/hannibal2/skyhanni/events/ReceiveParticleEvent.kt +++ b/src/main/java/at/hannibal2/skyhanni/events/ReceiveParticleEvent.kt @@ -1,8 +1,8 @@ package at.hannibal2.skyhanni.events import at.hannibal2.skyhanni.utils.LocationUtils.distanceToPlayer -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.LorenzVec +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import net.minecraft.util.EnumParticleTypes import net.minecraftforge.fml.common.eventhandler.Cancelable @@ -26,7 +26,7 @@ class ReceiveParticleEvent( 1 ) }, longDistance=$longDistance, particleArgs=${particleArgs.contentToString()}, distanceToPlayer=${ - distanceToPlayer.round( + distanceToPlayer.roundTo( 1 ) })" diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/BestiaryData.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/BestiaryData.kt index 5d056f456818..957f1ecd0b29 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/combat/BestiaryData.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/combat/BestiaryData.kt @@ -20,7 +20,7 @@ import at.hannibal2.skyhanni.utils.LorenzUtils.addButton import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators import at.hannibal2.skyhanni.utils.NumberUtil.formatLong import at.hannibal2.skyhanni.utils.NumberUtil.romanToDecimalIfNecessary -import at.hannibal2.skyhanni.utils.NumberUtil.roundToPrecision +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.NumberUtil.shortFormat import at.hannibal2.skyhanni.utils.NumberUtil.toRoman import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher @@ -327,7 +327,7 @@ object BestiaryData { DisplayTypeEntry.GLOBAL_NEXT -> mob.killNeededForNextLevel else -> 0 } - val percentage = ((currentKill.toDouble() / killNeeded) * 100).roundToPrecision(2) + val percentage = ((currentKill.toDouble() / killNeeded) * 100).roundTo(2) val suffix = if (type == DisplayTypeEntry.GLOBAL_NEXT) "§ato level ${mob.getNextLevel()}" else "" "§7(§b${currentKill.formatNumber()}§7/§b${killNeeded.formatNumber()}§7) §a$percentage§6% $suffix" } diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/DamageIndicatorManager.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/DamageIndicatorManager.kt index b7af2d72b2d4..0391e40a3697 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/DamageIndicatorManager.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/DamageIndicatorManager.kt @@ -34,10 +34,10 @@ import at.hannibal2.skyhanni.utils.LocationUtils.distanceToPlayer import at.hannibal2.skyhanni.utils.LorenzColor import at.hannibal2.skyhanni.utils.LorenzUtils import at.hannibal2.skyhanni.utils.LorenzUtils.baseMaxHealth -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.LorenzVec import at.hannibal2.skyhanni.utils.NumberUtil import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.NumberUtil.shortFormat import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher import at.hannibal2.skyhanni.utils.RenderUtils.drawDynamicText @@ -718,7 +718,7 @@ object DamageIndicatorManager { if (existed > 40) { val end = (20 * 26) - existed val time = end.toDouble() / 20 - entityData.nameAbove = "Mania Circles: §b${time.round(1)}s" + entityData.nameAbove = "Mania Circles: §b${time.roundTo(1)}s" return "" } } diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostCounter.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostCounter.kt index 85d5778fe25b..b337d58b1487 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostCounter.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostCounter.kt @@ -48,7 +48,7 @@ import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators import at.hannibal2.skyhanni.utils.NumberUtil.formatDouble import at.hannibal2.skyhanni.utils.NumberUtil.formatLong import at.hannibal2.skyhanni.utils.NumberUtil.romanToDecimal -import at.hannibal2.skyhanni.utils.NumberUtil.roundToPrecision +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.OSUtils import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher import at.hannibal2.skyhanni.utils.RenderUtils.renderStringsAndItems @@ -154,7 +154,7 @@ object GhostCounter { 0.0 -> "0" else -> { val mf = (((storage?.totalMF!! / Option.TOTALDROPS.get()) + Math.ulp(1.0)) * 100) / 100 - mf.roundToPrecision(2).toString() + mf.roundTo(2).toString() } } diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostUtil.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostUtil.kt index d568fc49288f..36a4edfb3cd4 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostUtil.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/combat/ghostcounter/GhostUtil.kt @@ -6,7 +6,7 @@ import at.hannibal2.skyhanni.data.ProfileStorageData import at.hannibal2.skyhanni.test.command.ErrorManager import at.hannibal2.skyhanni.utils.ChatUtils import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators -import at.hannibal2.skyhanni.utils.NumberUtil.roundToPrecision +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.NumberUtil.shortFormat import java.io.FileReader @@ -106,8 +106,8 @@ object GhostUtil { ) } - fun String.formatText(value: Double, session: Double) = this.replace("%value%", value.roundToPrecision(2).addSeparators()) - .replace("%session%", session.roundToPrecision(2).addSeparators()) + fun String.formatText(value: Double, session: Double) = this.replace("%value%", value.roundTo(2).addSeparators()) + .replace("%session%", session.roundTo(2).addSeparators()) .replace("&", "§") fun String.formatBestiary(currentKill: Int, killNeeded: Int): String { @@ -129,5 +129,5 @@ object GhostUtil { } private fun percent(number: Double) = - 100.0.coerceAtMost(((number / 100_000) * 100).roundToPrecision(4)).toString() + 100.0.coerceAtMost(((number / 100_000) * 100).roundTo(4)).toString() } diff --git a/src/main/java/at/hannibal2/skyhanni/features/event/diana/BurrowWarpHelper.kt b/src/main/java/at/hannibal2/skyhanni/features/event/diana/BurrowWarpHelper.kt index 34dab01e6ce1..4074b465de33 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/event/diana/BurrowWarpHelper.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/event/diana/BurrowWarpHelper.kt @@ -12,8 +12,8 @@ import at.hannibal2.skyhanni.utils.CollectionUtils.sorted import at.hannibal2.skyhanni.utils.HypixelCommands import at.hannibal2.skyhanni.utils.LocationUtils import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.LorenzVec +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.SimpleTimeMark import net.minecraft.client.Minecraft import net.minecraftforge.fml.common.eventhandler.SubscribeEvent @@ -104,11 +104,11 @@ object BurrowWarpHelper { debug?.add("warpPoint: ${warpPoint.displayName}") val playerDistance = playerLocation.distance(target) - debug?.add("playerDistance: ${playerDistance.round(1)}") + debug?.add("playerDistance: ${playerDistance.roundTo(1)}") val warpDistance = warpPoint.distance(target) - debug?.add("warpDistance: ${warpDistance.round(1)}") + debug?.add("warpDistance: ${warpDistance.roundTo(1)}") val difference = playerDistance - warpDistance - debug?.add("difference: ${difference.round(1)}") + debug?.add("difference: ${difference.roundTo(1)}") val setWarpPoint = difference > 10 debug?.add("setWarpPoint: $setWarpPoint") currentWarp = if (setWarpPoint) warpPoint else null diff --git a/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityCollectionStats.kt b/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityCollectionStats.kt index 7be0913e9fb2..e84d93dfa6a3 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityCollectionStats.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityCollectionStats.kt @@ -22,11 +22,11 @@ import at.hannibal2.skyhanni.utils.KSerializable import at.hannibal2.skyhanni.utils.LorenzColor import at.hannibal2.skyhanni.utils.LorenzRarity import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.NEUInternalName import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators import at.hannibal2.skyhanni.utils.NumberUtil.formatInt +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.NumberUtil.shortFormat import at.hannibal2.skyhanni.utils.RegexUtils.anyMatches import at.hannibal2.skyhanni.utils.RegexUtils.findMatcher @@ -435,7 +435,7 @@ object HoppityCollectionStats { add("§7Total Rabbits Found: §a${displayFound + displayDuplicates}") add("") add("§7Chocolate Per Second: §a${displayChocolatePerSecond.addSeparators()}") - add("§7Chocolate Multiplier: §a${displayChocolateMultiplier.round(3)}") + add("§7Chocolate Multiplier: §a${displayChocolateMultiplier.roundTo(3)}") } table.add( DisplayTableEntry( diff --git a/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggLocator.kt b/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggLocator.kt index 8e9e68e16731..468fecbcb480 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggLocator.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggLocator.kt @@ -19,10 +19,10 @@ import at.hannibal2.skyhanni.utils.ItemUtils.getInternalName import at.hannibal2.skyhanni.utils.LocationUtils.distanceToPlayer import at.hannibal2.skyhanni.utils.LorenzColor import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.LorenzVec import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName import at.hannibal2.skyhanni.utils.NumberUtil.formatInt +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.RecalculatingValue import at.hannibal2.skyhanni.utils.RenderUtils.draw3DLine import at.hannibal2.skyhanni.utils.RenderUtils.drawColor @@ -249,7 +249,7 @@ object HoppityEggLocator { }.sortedBy { it.second } eggLocationWeights = sortedEggs.map { - it.second.round(3) + it.second.roundTo(3) }.take(5) val filteredEggs = sortedEggs.filter { diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/FarmingFortuneDisplay.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/FarmingFortuneDisplay.kt index 29b92f24afd8..55898266e440 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/FarmingFortuneDisplay.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/FarmingFortuneDisplay.kt @@ -17,9 +17,9 @@ import at.hannibal2.skyhanni.utils.CollectionUtils.nextAfter import at.hannibal2.skyhanni.utils.HypixelCommands import at.hannibal2.skyhanni.utils.ItemUtils.getInternalName import at.hannibal2.skyhanni.utils.ItemUtils.getLore -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.NEUInternalName import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.RegexUtils.groupOrNull import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher import at.hannibal2.skyhanni.utils.RenderUtils.renderRenderables @@ -167,7 +167,7 @@ object FarmingFortuneDisplay { list.add( Renderable.string( "§6Farming Fortune§7: §e" + if (!recentlySwitchedTool && farmingFortune != -1.0) { - farmingFortune.round(0).addSeparators() + farmingFortune.roundTo(0).addSeparators() } else "§7" + (displayCrop.getLatestTrueFarmingFortune()?.addSeparators() ?: "?"), ), ) diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/GardenYawAndPitch.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/GardenYawAndPitch.kt index cdbb07963d8d..ad03a7784cf3 100755 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/GardenYawAndPitch.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/GardenYawAndPitch.kt @@ -7,7 +7,7 @@ import at.hannibal2.skyhanni.events.GuiRenderEvent import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.LocationUtils import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.round +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.RenderUtils.renderStrings import at.hannibal2.skyhanni.utils.SimpleTimeMark import net.minecraft.client.Minecraft @@ -41,8 +41,8 @@ object GardenYawAndPitch { if (!config.showAlways && lastChange.passedSince() > config.timeout.seconds) return - val yawText = yaw.round(config.yawPrecision).toBigDecimal().toPlainString() - val pitchText = pitch.round(config.pitchPrecision).toBigDecimal().toPlainString() + val yawText = yaw.roundTo(config.yawPrecision).toBigDecimal().toPlainString() + val pitchText = pitch.roundTo(config.pitchPrecision).toBigDecimal().toPlainString() val displayList = listOf( "§aYaw: §f$yawText", "§aPitch: §f$pitchText", diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/composter/ComposterOverlay.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/composter/ComposterOverlay.kt index aee2a93f428f..fa45e579dfa3 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/composter/ComposterOverlay.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/composter/ComposterOverlay.kt @@ -31,7 +31,6 @@ import at.hannibal2.skyhanni.utils.ItemUtils.name import at.hannibal2.skyhanni.utils.KeyboardManager import at.hannibal2.skyhanni.utils.LorenzUtils import at.hannibal2.skyhanni.utils.LorenzUtils.addSelector -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.NEUInternalName import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.NONE import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName @@ -41,6 +40,7 @@ import at.hannibal2.skyhanni.utils.NEUItems.getPrice import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators import at.hannibal2.skyhanni.utils.NumberUtil.romanToDecimalIfNecessary import at.hannibal2.skyhanni.utils.NumberUtil.shortFormat +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher import at.hannibal2.skyhanni.utils.RenderUtils.renderStringsAndItems import at.hannibal2.skyhanni.utils.SimpleTimeMark @@ -338,10 +338,10 @@ object ComposterOverlay { val multiplier = multiDropFactor * timeMultiplier val multiplierPreview = multiDropFactorPreview * timeMultiplierPreview val compostPerTitlePreview = - if (multiplier != multiplierPreview) " §c➜ §e" + multiplierPreview.round(2) else "" + if (multiplier != multiplierPreview) " §c➜ §e" + multiplierPreview.roundTo(2) else "" val compostPerTitle = if (currentTimeType == TimeType.COMPOST) "Compost multiplier" else "Composts per $timeText" - newList.addAsSingletonList(" §7$compostPerTitle: §e${multiplier.round(2)}$compostPerTitlePreview") + newList.addAsSingletonList(" §7$compostPerTitle: §e${multiplier.roundTo(2)}$compostPerTitlePreview") val organicMatterPrice = getPrice(organicMatterItem) val organicMatterFactor = organicMatterFactors[organicMatterItem]!! diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/contest/FarmingPersonalBestGain.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/contest/FarmingPersonalBestGain.kt index 7d009629b7c9..a2e8a859f703 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/contest/FarmingPersonalBestGain.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/contest/FarmingPersonalBestGain.kt @@ -6,8 +6,8 @@ import at.hannibal2.skyhanni.features.garden.GardenAPI import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.ChatUtils import at.hannibal2.skyhanni.utils.DelayedRun -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.NumberUtil.formatDouble +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern import net.minecraftforge.fml.common.eventhandler.SubscribeEvent @@ -88,7 +88,7 @@ object FarmingPersonalBestGain { val oldFF = oldCollected / collectionPerFF val ffDiff = newFF - oldFF - ChatUtils.chat("This is §6${ffDiff.round(2)}☘ $crop Fortune §emore than previously!") + ChatUtils.chat("This is §6${ffDiff.roundTo(2)}☘ $crop Fortune §emore than previously!") } fun isEnabled() = GardenAPI.inGarden() && config.contestPersonalBestIncreaseFF diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/contest/JacobContestFFNeededDisplay.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/contest/JacobContestFFNeededDisplay.kt index 963193c4ba74..5d9b7f92f34e 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/contest/JacobContestFFNeededDisplay.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/contest/JacobContestFFNeededDisplay.kt @@ -12,8 +12,8 @@ import at.hannibal2.skyhanni.utils.CollectionUtils.addAsSingletonList import at.hannibal2.skyhanni.utils.InventoryUtils import at.hannibal2.skyhanni.utils.ItemUtils.name import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.RenderUtils.renderStringsAndItems import at.hannibal2.skyhanni.utils.SimpleTimeMark import net.minecraft.item.ItemStack @@ -81,7 +81,7 @@ object JacobContestFFNeededDisplay { addAsSingletonList("§cassuming 19.9 instead.") } else { if (blocksPerSecond < 15.0) { - add(listOf("§7Your latest ", crop.icon, "§7blocks/second: §e${blocksPerSecond.round(2)}")) + add(listOf("§7Your latest ", crop.icon, "§7blocks/second: §e${blocksPerSecond.roundTo(2)}")) add(listOf("§cThis is too low, showing 19.9 Blocks/second instead!")) blocksPerSecond = 19.9 } diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/contest/JacobContestStatsSummary.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/contest/JacobContestStatsSummary.kt index 3d4fef9a3c66..144abc0c2486 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/contest/JacobContestStatsSummary.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/contest/JacobContestStatsSummary.kt @@ -6,8 +6,8 @@ import at.hannibal2.skyhanni.events.FarmingContestEvent import at.hannibal2.skyhanni.features.garden.GardenAPI import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.ChatUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.SimpleTimeMark import at.hannibal2.skyhanni.utils.TimeUtils.format import net.minecraftforge.fml.common.eventhandler.SubscribeEvent @@ -41,7 +41,7 @@ object JacobContestStatsSummary { FarmingContestPhase.STOP -> { val duration = startTime.passedSince() - val blocksPerSecond = (blocksBroken.toDouble() / duration.inWholeSeconds).round(2) + val blocksPerSecond = (blocksBroken.toDouble() / duration.inWholeSeconds).roundTo(2) val cropName = event.crop.cropName ChatUtils.chat("Stats for $cropName Contest:") val time = duration.format() diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/contest/JacobContestTimeNeeded.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/contest/JacobContestTimeNeeded.kt index 3e99c2683fd2..bb9f9ea21773 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/contest/JacobContestTimeNeeded.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/contest/JacobContestTimeNeeded.kt @@ -11,8 +11,8 @@ import at.hannibal2.skyhanni.utils.CollectionUtils.addAsSingletonList import at.hannibal2.skyhanni.utils.CollectionUtils.sorted import at.hannibal2.skyhanni.utils.LorenzUtils import at.hannibal2.skyhanni.utils.LorenzUtils.addSelector -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.RenderUtils.renderStringsAndItems import at.hannibal2.skyhanni.utils.TimeUtils.format import at.hannibal2.skyhanni.utils.renderables.Renderable @@ -102,7 +102,7 @@ object JacobContestTimeNeeded { return } - val speed = (ff * crop.baseDrops * bps / 100).round(1).toInt() + val speed = (ff * crop.baseDrops * bps / 100).roundTo(1).toInt() renderCrop(speed, crop, averages, sorted, map) } @@ -174,7 +174,7 @@ object JacobContestTimeNeeded { add("") val latestFF = crop.getLatestTrueFarmingFortune() ?: 0.0 add("§7Latest FF: §e${(latestFF).addSeparators()}") - val bps = crop.getBps()?.round(1) ?: 0 + val bps = crop.getBps()?.roundTo(1) ?: 0 add("§7${addBpsTitle()}§e${bps.addSeparators()}") addAll(lowBPSWarning) }) diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/farming/CropSpeedMeter.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/farming/CropSpeedMeter.kt index aa977b65a575..74c59674f580 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/farming/CropSpeedMeter.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/farming/CropSpeedMeter.kt @@ -9,8 +9,8 @@ import at.hannibal2.skyhanni.features.garden.CropType import at.hannibal2.skyhanni.features.garden.GardenAPI import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.ChatUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.RenderUtils.renderStrings import net.minecraftforge.fml.common.eventhandler.SubscribeEvent @@ -91,7 +91,7 @@ object CropSpeedMeter { currentCrop?.let { val crops = it.getCounter() - startCrops[it]!! val blocks = currentBlocks - val cropsPerBlocks = (crops.toDouble() / blocks.toDouble()).round(3) + val cropsPerBlocks = (crops.toDouble() / blocks.toDouble()).roundTo(3) val list = mutableListOf() list.add("") @@ -101,7 +101,7 @@ object CropSpeedMeter { list.add(" §7Crops per Block: " + cropsPerBlocks.addSeparators()) val baseDrops = it.baseDrops - val farmingFortune = (cropsPerBlocks * 100 / baseDrops).round(3) + val farmingFortune = (cropsPerBlocks * 100 / baseDrops).roundTo(3) list.add(" §7Calculated farming Fortune: §e" + farmingFortune.addSeparators()) list.add("§cOpen /cropmilestones again to recalculate!") diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/farming/FarmingWeightDisplay.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/farming/FarmingWeightDisplay.kt index 1fa4b57e6a56..da461d749604 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/farming/FarmingWeightDisplay.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/farming/FarmingWeightDisplay.kt @@ -23,8 +23,8 @@ import at.hannibal2.skyhanni.test.command.ErrorManager import at.hannibal2.skyhanni.utils.APIUtils import at.hannibal2.skyhanni.utils.ChatUtils import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.OSUtils import at.hannibal2.skyhanni.utils.RenderUtils.renderRenderables import at.hannibal2.skyhanni.utils.SimpleTimeMark @@ -234,7 +234,7 @@ object FarmingWeightDisplay { } val totalWeight = (localWeight + weight) - return "§e" + totalWeight.round(2).addSeparators() + return "§e" + totalWeight.roundTo(2).addSeparators() } private fun getRankGoal(): Int { @@ -326,7 +326,7 @@ object FarmingWeightDisplay { " §7(§b$format§7)" } else "" - val weightFormat = weightUntilOvertake.round(2).addSeparators() + val weightFormat = weightUntilOvertake.roundTo(2).addSeparators() val text = "§e$weightFormat$timeFormat §7behind §b$nextName" return if (showRankGoal) { Renderable.string(text) diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/farming/GardenCropMilestoneDisplay.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/farming/GardenCropMilestoneDisplay.kt index c37eb41b7cdf..7189199a9987 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/farming/GardenCropMilestoneDisplay.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/farming/GardenCropMilestoneDisplay.kt @@ -26,8 +26,8 @@ import at.hannibal2.skyhanni.utils.CollectionUtils.addString import at.hannibal2.skyhanni.utils.ConditionalUtils import at.hannibal2.skyhanni.utils.ConfigUtils import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.RenderUtils.renderRenderables import at.hannibal2.skyhanni.utils.RenderUtils.renderStringsAndItems import at.hannibal2.skyhanni.utils.SimpleTimeMark @@ -187,7 +187,7 @@ object GardenCropMilestoneDisplay { val farmingFortune = FarmingFortuneDisplay.getCurrentFarmingFortune() val speed = GardenCropSpeed.averageBlocksPerSecond - val farmingFortuneSpeed = ((100.0 + farmingFortune) * crop.baseDrops * speed / 100).round(1).toInt() + val farmingFortuneSpeed = ((100.0 + farmingFortune) * crop.baseDrops * speed / 100).roundTo(1).toInt() if (farmingFortuneSpeed > 0) { crop.setSpeed(farmingFortuneSpeed) @@ -219,7 +219,7 @@ object GardenCropMilestoneDisplay { val hourFormat = (farmingFortuneSpeed * 60 * 60).addSeparators() lineMap[MilestoneTextEntry.CROPS_PER_HOUR] = Renderable.string("§7Crops/Hour§8: §e$hourFormat") - val formatBps = speed.round(config.blocksBrokenPrecision).addSeparators() + val formatBps = speed.roundTo(config.blocksBrokenPrecision).addSeparators() lineMap[MilestoneTextEntry.BLOCKS_PER_SECOND] = Renderable.string("§7Blocks/Second§8: §e$formatBps") } diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/farming/lane/FarmingLaneFeatures.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/farming/lane/FarmingLaneFeatures.kt index cb63d40de1d8..e407d0ad4deb 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/farming/lane/FarmingLaneFeatures.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/farming/lane/FarmingLaneFeatures.kt @@ -14,8 +14,8 @@ import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.LocationUtils import at.hannibal2.skyhanni.utils.LorenzColor import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.LorenzVec +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.RenderUtils.drawDynamicText import at.hannibal2.skyhanni.utils.RenderUtils.drawWaypointFilled import at.hannibal2.skyhanni.utils.RenderUtils.renderStrings @@ -76,7 +76,7 @@ object FarmingLaneFeatures { if (config.distanceDisplay) { display = buildList { - add("§7Distance until switch: §e${currentDistance.round(1)}") + add("§7Distance until switch: §e${currentDistance.roundTo(1)}") val normal = movementState == MovementState.NORMAL val color = if (normal) "§b" else "§8" @@ -152,7 +152,7 @@ object FarmingLaneFeatures { private var sameSpeedCounter = 0 private fun calculateSpeed(): Boolean { - val speed = MovementSpeedDisplay.speed.round(2) + val speed = MovementSpeedDisplay.speed.roundTo(2) movementState = calculateMovementState(speed) if (movementState != MovementState.NORMAL) return false diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/inventory/GardenCropMilestoneInventory.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/inventory/GardenCropMilestoneInventory.kt index 7a2f8d415584..e0cb070739b9 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/inventory/GardenCropMilestoneInventory.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/inventory/GardenCropMilestoneInventory.kt @@ -12,8 +12,8 @@ import at.hannibal2.skyhanni.features.garden.GardenAPI import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.CollectionUtils.indexOfFirst import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.NumberUtil.shortFormat import at.hannibal2.skyhanni.utils.StringUtils import net.minecraftforge.fml.common.eventhandler.SubscribeEvent @@ -35,7 +35,7 @@ object GardenCropMilestoneInventory { val tier = GardenCropMilestones.getTierForCropCount(counter, cropType, allowOverflow) tiers.add(tier.toDouble()) } - average = (tiers.sum() / CropType.entries.size).round(2) + average = (tiers.sum() / CropType.entries.size).roundTo(2) } @SubscribeEvent diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/inventory/SkyMartCopperPrice.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/inventory/SkyMartCopperPrice.kt index 5ef15090739a..078ef16724d2 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/inventory/SkyMartCopperPrice.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/inventory/SkyMartCopperPrice.kt @@ -12,12 +12,12 @@ import at.hannibal2.skyhanni.utils.ItemUtils.getLore import at.hannibal2.skyhanni.utils.ItemUtils.itemName import at.hannibal2.skyhanni.utils.ItemUtils.loreCosts import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.NEUItems.getPrice import at.hannibal2.skyhanni.utils.NEUItems.getPriceOrNull import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators import at.hannibal2.skyhanni.utils.NumberUtil.formatInt import at.hannibal2.skyhanni.utils.NumberUtil.shortFormat +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher import at.hannibal2.skyhanni.utils.RenderUtils.renderRenderables import at.hannibal2.skyhanni.utils.renderables.Renderable @@ -115,7 +115,7 @@ object SkyMartCopperPrice { event.move(3, "garden.skyMartCopperPriceAdvancedStats", "garden.skyMart.copperPriceAdvancedStats") event.move(3, "garden.skyMartCopperPricePos", "garden.skyMart.copperPricePos") event.transform(32, "garden.skyMart.itemScale") { - JsonPrimitive((it.asDouble / 1.851).round(1)) + JsonPrimitive((it.asDouble / 1.851).roundTo(1)) } } diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/chocolatefactory/ChocolateFactoryDataLoader.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/chocolatefactory/ChocolateFactoryDataLoader.kt index 3724dbc53576..eebbb19e93a7 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/inventory/chocolatefactory/ChocolateFactoryDataLoader.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/chocolatefactory/ChocolateFactoryDataLoader.kt @@ -14,11 +14,11 @@ import at.hannibal2.skyhanni.utils.InventoryUtils import at.hannibal2.skyhanni.utils.ItemUtils.getLore import at.hannibal2.skyhanni.utils.ItemUtils.getSkullTexture import at.hannibal2.skyhanni.utils.ItemUtils.name -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.NumberUtil.formatDouble import at.hannibal2.skyhanni.utils.NumberUtil.formatInt import at.hannibal2.skyhanni.utils.NumberUtil.formatLong import at.hannibal2.skyhanni.utils.NumberUtil.romanToDecimal +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.RegexUtils.matchFirst import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher import at.hannibal2.skyhanni.utils.RegexUtils.matches @@ -334,7 +334,7 @@ object ChocolateFactoryDataLoader { val itemName = item.name.removeColor() val lore = item.getLore() val upgradeCost = ChocolateFactoryAPI.getChocolateBuyCost(lore) - val averageChocolate = ChocolateAmount.averageChocPerSecond().round(2) + val averageChocolate = ChocolateAmount.averageChocPerSecond().roundTo(2) val isMaxed = upgradeCost == null if (slotIndex in ChocolateFactoryAPI.rabbitSlots) { @@ -415,8 +415,8 @@ object ChocolateFactoryDataLoader { newAverageChocolate: Double, isRabbit: Boolean, ) { - val extra = (newAverageChocolate - averageChocolate).round(2) - val effectiveCost = (upgradeCost!! / extra).round(2) + val extra = (newAverageChocolate - averageChocolate).roundTo(2) + val effectiveCost = (upgradeCost!! / extra).roundTo(2) val upgrade = ChocolateFactoryUpgrade(slotIndex, level, upgradeCost, extra, effectiveCost, isRabbit = isRabbit) list.add(upgrade) } diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/chocolatefactory/ChocolateFactoryTooltip.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/chocolatefactory/ChocolateFactoryTooltip.kt index 8973bc1e4fbe..88ac9d4747ae 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/inventory/chocolatefactory/ChocolateFactoryTooltip.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/chocolatefactory/ChocolateFactoryTooltip.kt @@ -3,8 +3,8 @@ package at.hannibal2.skyhanni.features.inventory.chocolatefactory import at.hannibal2.skyhanni.events.LorenzToolTipEvent import at.hannibal2.skyhanni.features.inventory.chocolatefactory.ChocolateFactoryAPI.profileStorage import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import net.minecraftforge.fml.common.eventhandler.EventPriority import net.minecraftforge.fml.common.eventhandler.SubscribeEvent @@ -35,7 +35,7 @@ object ChocolateFactoryTooltip { if (upgradeInfo.effectiveCost == null) return - event.toolTip.add("§7Extra: §6${upgradeInfo.extraPerSecond?.round(2) ?: "N/A"} §7choc/s") + event.toolTip.add("§7Extra: §6${upgradeInfo.extraPerSecond?.roundTo(2) ?: "N/A"} §7choc/s") event.toolTip.add("§7Effective Cost: §6${upgradeInfo.effectiveCost.addSeparators() ?: "N/A"}") if (slotIndex == ChocolateFactoryAPI.timeTowerIndex) { diff --git a/src/main/java/at/hannibal2/skyhanni/features/itemabilities/abilitycooldown/ItemAbility.kt b/src/main/java/at/hannibal2/skyhanni/features/itemabilities/abilitycooldown/ItemAbility.kt index de7bc8bdf77f..020feba76976 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/itemabilities/abilitycooldown/ItemAbility.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/itemabilities/abilitycooldown/ItemAbility.kt @@ -2,10 +2,10 @@ package at.hannibal2.skyhanni.features.itemabilities.abilitycooldown import at.hannibal2.skyhanni.features.dungeon.DungeonAPI import at.hannibal2.skyhanni.utils.LorenzColor -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.NEUInternalName import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import kotlin.math.floor enum class ItemAbility( @@ -106,7 +106,7 @@ enum class ItemAbility( duration /= 100 var d = duration.toDouble() d /= 10.0 - d.round(1).addSeparators() + d.roundTo(1).addSeparators() } else { duration /= 1000 duration++ diff --git a/src/main/java/at/hannibal2/skyhanni/features/itemabilities/abilitycooldown/ItemAbilityCooldown.kt b/src/main/java/at/hannibal2/skyhanni/features/itemabilities/abilitycooldown/ItemAbilityCooldown.kt index 567adb251af5..5748220c5e38 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/itemabilities/abilitycooldown/ItemAbilityCooldown.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/itemabilities/abilitycooldown/ItemAbilityCooldown.kt @@ -23,8 +23,8 @@ import at.hannibal2.skyhanni.utils.ItemUtils.getInternalName import at.hannibal2.skyhanni.utils.LorenzColor import at.hannibal2.skyhanni.utils.LorenzUtils import at.hannibal2.skyhanni.utils.LorenzUtils.between -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher import at.hannibal2.skyhanni.utils.RenderUtils.highlight import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.getAbilityScrolls @@ -168,7 +168,7 @@ object ItemAbilityCooldown { ItemAbility.STAFF_OF_THE_VOLCANO.sound() } // Holy Ice - event.soundName == "random.drink" && event.pitch.round(1) == 1.8f && event.volume == 1.0f -> { + event.soundName == "random.drink" && event.pitch.roundTo(1) == 1.8f && event.volume == 1.0f -> { ItemAbility.HOLY_ICE.sound() } // Royal Pigeon diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/MineshaftPityDisplay.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/MineshaftPityDisplay.kt index ec9a390edff0..4415b59f5872 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/mining/MineshaftPityDisplay.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/MineshaftPityDisplay.kt @@ -17,8 +17,8 @@ import at.hannibal2.skyhanni.features.mining.MineshaftPityDisplay.PityBlock.Comp import at.hannibal2.skyhanni.features.mining.OreType.Companion.getOreType import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.CollectionUtils.addOrPut -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.RegexUtils.matches import at.hannibal2.skyhanni.utils.RenderUtils.renderRenderables import at.hannibal2.skyhanni.utils.SimpleTimeMark @@ -118,7 +118,7 @@ object MineshaftPityDisplay { add("§7Pity Counter: §e$pityCounter") add( "§7Chance: " + - "§e1§6/§e${chance.round(1)} " + + "§e1§6/§e${chance.roundTo(1)} " + "§7(§b${((1.0 / chance) * 100).addSeparators()}%§7)", ) minedBlocks.forEach { @@ -214,7 +214,7 @@ object MineshaftPityDisplay { MineshaftPityLine.COUNTER to Renderable.string("§3Pity Counter: §e$counterUntilPity§6/§e$MAX_COUNTER"), MineshaftPityLine.CHANCE to Renderable.string( "§3Chance: §e1§6/§e${ - chance.round(1).addSeparators() + chance.roundTo(1).addSeparators() } §7(§b${((1.0 / chance) * 100).addSeparators()}%§7)", ), MineshaftPityLine.NEEDED_TO_PITY to neededToPityRenderable, diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/PowderPerHotmPerk.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/PowderPerHotmPerk.kt index 58fa83753fe9..e268beb16f94 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/mining/PowderPerHotmPerk.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/PowderPerHotmPerk.kt @@ -6,9 +6,9 @@ import at.hannibal2.skyhanni.events.LorenzToolTipEvent import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.KeyboardManager.isKeyHeld import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators import at.hannibal2.skyhanni.utils.NumberUtil.fractionOf +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.RegexUtils.matches import at.hannibal2.skyhanni.utils.StringUtils.removeColor import net.minecraftforge.fml.common.eventhandler.SubscribeEvent @@ -48,7 +48,7 @@ object PowderPerHotmPerk { private fun handlePowderSpend(perk: HotmData): String { val currentPowderSpend = perk.calculateTotalCost(perk.rawLevel) val maxPowderNeeded = perk.totalCostMaxLevel - val percentage = (currentPowderSpend.fractionOf(maxPowderNeeded) * 100).round(2) + val percentage = (currentPowderSpend.fractionOf(maxPowderNeeded) * 100).roundTo(2) return when (config.powderSpentDesign) { PowderSpentDesign.NUMBER -> { diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilSolverDisplay.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilSolverDisplay.kt index 82fc73030a45..cb50e3c85459 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilSolverDisplay.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/solver/FossilSolverDisplay.kt @@ -17,7 +17,7 @@ import at.hannibal2.skyhanni.utils.InventoryUtils import at.hannibal2.skyhanni.utils.ItemUtils.getLore import at.hannibal2.skyhanni.utils.LorenzColor import at.hannibal2.skyhanni.utils.LorenzUtils.isInIsland -import at.hannibal2.skyhanni.utils.LorenzUtils.round +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher import at.hannibal2.skyhanni.utils.RenderUtils.highlight import at.hannibal2.skyhanni.utils.RenderUtils.renderString @@ -218,7 +218,7 @@ object FossilSolverDisplay { } fun nextData(slotToClick: FossilTile, correctPercentage: Double, fossilsRemaining: Int) { - val formattedPercentage = (correctPercentage * 100).round(1) + val formattedPercentage = (correctPercentage * 100).roundTo(1) possibleFossilsRemaining = fossilsRemaining FossilSolverDisplay.slotToClick = slotToClick.toSlotIndex() diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/IslandAreas.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/IslandAreas.kt index d5e57d513380..3c1e844a628a 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/IslandAreas.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/IslandAreas.kt @@ -23,7 +23,7 @@ import at.hannibal2.skyhanni.utils.LocationUtils.canBeSeen import at.hannibal2.skyhanni.utils.LocationUtils.distanceToPlayer import at.hannibal2.skyhanni.utils.LorenzColor import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.round +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.RenderUtils.drawDynamicText import at.hannibal2.skyhanni.utils.RenderUtils.renderRenderable import at.hannibal2.skyhanni.utils.renderables.Renderable @@ -164,7 +164,7 @@ object IslandAreas { } } - val distance = difference.round(1) + val distance = difference.roundTo(1) val text = "${coloredName}§7: §e$distance$suffix" if (!foundCurrentArea) { diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/MovementSpeedDisplay.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/MovementSpeedDisplay.kt index 50148f031878..685ffa3cdc25 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/MovementSpeedDisplay.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/MovementSpeedDisplay.kt @@ -7,8 +7,8 @@ import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.BlockUtils.getBlockAt import at.hannibal2.skyhanni.utils.LocationUtils import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.LorenzVec +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.RenderUtils.renderString import net.minecraft.client.Minecraft import net.minecraft.init.Blocks @@ -59,7 +59,7 @@ object MovementSpeedDisplay { } usingSoulsandSpeed = movingOnSoulsand && soulsandSpeeds.size == 6 if (isEnabled()) { - display = "Movement Speed: ${speed.round(2)}" + display = "Movement Speed: ${speed.roundTo(2)}" } } diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/TpsCounter.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/TpsCounter.kt index 0a3a60295371..01d84daa3fdc 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/TpsCounter.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/TpsCounter.kt @@ -10,7 +10,7 @@ import at.hannibal2.skyhanni.events.minecraft.packet.PacketReceivedEvent import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.ChatUtils import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.round +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.RenderUtils.renderString import net.minecraftforge.fml.common.eventhandler.SubscribeEvent import kotlin.concurrent.fixedRateTimer @@ -55,7 +55,7 @@ object TpsCounter { "§eTPS: §f(${current}s)" } else { val sum = tpsList.sum().toDouble() - var tps = (sum / tpsList.size).round(1) + var tps = (sum / tpsList.size).roundTo(1) if (tps > 20) tps = 20.0 val color = getColor(tps) "§eTPS: $color$tps" diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/UserLuckBreakdown.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/UserLuckBreakdown.kt index 7f041bc27f9a..f1fad4e63aca 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/UserLuckBreakdown.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/UserLuckBreakdown.kt @@ -14,10 +14,10 @@ import at.hannibal2.skyhanni.utils.ItemUtils import at.hannibal2.skyhanni.utils.ItemUtils.getLore import at.hannibal2.skyhanni.utils.ItemUtils.name import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName import at.hannibal2.skyhanni.utils.NEUItems.getItemStack import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher import at.hannibal2.skyhanni.utils.SimpleTimeMark import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern @@ -160,7 +160,7 @@ object UserLuckBreakdown { skillCalcCoolDown = SimpleTimeMark.now() calcSkillLuck() } - val limboLuck = storage?.limbo?.userLuck?.round(1) ?: 0.0f + val limboLuck = storage?.limbo?.userLuck?.roundTo(1) ?: 0.0f when (event.slot.inventory.name) { "Your Equipment and Stats" -> equipmentMenuTooltip(event, limboLuck) "Your Stats Breakdown" -> statsBreakdownLoreTooltip(event, limboLuck) @@ -270,7 +270,7 @@ object UserLuckBreakdown { calcSkillLuck() return when (type) { "mainMenu" -> { - val luckString = tryTruncateFloat(luckInput.round(2)) + val luckString = tryTruncateFloat(luckInput.roundTo(2)) if (luckInput == 0.0f) { arrayOf( "§7SkyHanni User Luck is the best stat.", @@ -293,7 +293,7 @@ object UserLuckBreakdown { } "limbo" -> { - val luckString = tryTruncateFloat(luckInput.round(2)) + val luckString = tryTruncateFloat(luckInput.roundTo(2)) arrayOf( "§8Action", "", diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/limbo/LimboPlaytime.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/limbo/LimboPlaytime.kt index 666671e9ca3f..1c46b40d6529 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/limbo/LimboPlaytime.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/limbo/LimboPlaytime.kt @@ -8,10 +8,10 @@ import at.hannibal2.skyhanni.events.render.gui.ReplaceItemEvent import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.ItemUtils import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName import at.hannibal2.skyhanni.utils.NEUItems.getItemStack import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.RegexUtils.matches import at.hannibal2.skyhanni.utils.SimpleTimeMark import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern @@ -105,7 +105,7 @@ object LimboPlaytime { if ((wholeMinutes % 60) == 0) { hoursString = "$wholeHours" } else { - val minutes: Float = ((wholeMinutes - wholeHours * 60).toFloat() / 60).round(1) + val minutes: Float = ((wholeMinutes - wholeHours * 60).toFloat() / 60).roundTo(1) hoursString = wholeHours.addSeparators() if (findFloatDecimalPlace(minutes) != 0) { val minutesString = minutes.toString() diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/limbo/LimboTimeTracker.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/limbo/LimboTimeTracker.kt index 62f4c46561c6..bfb8ce715d2f 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/limbo/LimboTimeTracker.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/limbo/LimboTimeTracker.kt @@ -15,7 +15,7 @@ import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.ChatUtils import at.hannibal2.skyhanni.utils.LocationUtils.isPlayerInside import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.round +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.RenderUtils.renderString import at.hannibal2.skyhanni.utils.SimpleTimeMark import at.hannibal2.skyhanni.utils.TimeUtils.format @@ -116,7 +116,7 @@ object LimboTimeTracker { if (passedSince > currentPB) { oldPB = currentPB storage?.personalBest = passedSince.toInt(DurationUnit.SECONDS) - userLuck = ((storage?.personalBest ?: 0) * USER_LUCK_MULTIPLIER).round(2) + userLuck = ((storage?.personalBest ?: 0) * USER_LUCK_MULTIPLIER).roundTo(2) if (onFire) userLuck *= FIRE_MULTIPLIER ChatUtils.chat("§fYou were in Limbo for §e$duration§f! §d§lPERSONAL BEST§r§f!") if (oldPB != 0.seconds) { @@ -126,9 +126,9 @@ object LimboTimeTracker { } else ChatUtils.chat("§fYou were in Limbo for §e$duration§f.") if (userLuck > oldLuck) { if (onFire) { - ChatUtils.chat("§fYour §aPersonal Bests§f perk is now granting you §a+${userLuck.round(2)}§c✴ §aSkyHanni User Luck§f! ") + ChatUtils.chat("§fYour §aPersonal Bests§f perk is now granting you §a+${userLuck.roundTo(2)}§c✴ §aSkyHanni User Luck§f! ") } else { - ChatUtils.chat("§fYour §aPersonal Bests§f perk is now granting you §a+${userLuck.round(2)}✴ SkyHanni User Luck§f!") + ChatUtils.chat("§fYour §aPersonal Bests§f perk is now granting you §a+${userLuck.roundTo(2)}✴ SkyHanni User Luck§f!") } storage?.userLuck = userLuck } @@ -152,7 +152,7 @@ object LimboTimeTracker { val currentPB = storage?.personalBest ?: 0 val userLuck = storage?.userLuck ?: 0f val limboPB: Int = if (currentPB < timeInLimbo) timeInLimbo else currentPB - var luckString = tryTruncateFloat(userLuck.round(2)) + var luckString = tryTruncateFloat(userLuck.roundTo(2)) if (userLuck > 0) luckString = "+$luckString" var firstMessage = "§fYour current PB is §e${limboPB.seconds}§f, granting you §a$luckString✴ SkyHanni User Luck§f!" diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/pets/PetExpTooltip.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/pets/PetExpTooltip.kt index 8e544a3dfefc..e5c3179cc4a9 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/pets/PetExpTooltip.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/pets/PetExpTooltip.kt @@ -11,8 +11,8 @@ import at.hannibal2.skyhanni.utils.ItemUtils.name import at.hannibal2.skyhanni.utils.KeyboardManager import at.hannibal2.skyhanni.utils.LorenzRarity import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.NumberUtil.shortFormat import at.hannibal2.skyhanni.utils.ReflectionUtils.makeAccessible import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.getPetExp @@ -36,7 +36,7 @@ object PetExpTooltip { if (!KeyboardManager.isShiftKeyDown() && !config.showAlways) return val itemStack = event.itemStack - val petExperience = itemStack.getPetExp()?.round(1) ?: return + val petExperience = itemStack.getPetExp()?.roundTo(1) ?: return val name = itemStack.name try { @@ -105,7 +105,7 @@ object PetExpTooltip { val maxLevel = if (useGoldenDragonLevels) 200 else 100 val maxXp = when { - useGoldenDragonLevels -> LEVEL_200_LEGENDARY // lvl 200 legendary + useGoldenDragonLevels -> LEVEL_200_LEGENDARY petName.contains("Bingo") -> LEVEL_100_COMMON else -> LEVEL_100_LEGENDARY diff --git a/src/main/java/at/hannibal2/skyhanni/features/rift/area/livingcave/LivingMetalSuitProgress.kt b/src/main/java/at/hannibal2/skyhanni/features/rift/area/livingcave/LivingMetalSuitProgress.kt index b0d4676f2e31..9c65a273e867 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/rift/area/livingcave/LivingMetalSuitProgress.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/rift/area/livingcave/LivingMetalSuitProgress.kt @@ -7,7 +7,7 @@ import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.CollectionUtils.addAsSingletonList import at.hannibal2.skyhanni.utils.InventoryUtils import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.NumberUtil.roundToPrecision +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.RenderUtils.renderStringsAndItems import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.getLivingMetalProgress import net.minecraft.item.ItemStack @@ -39,7 +39,7 @@ object LivingMetalSuitProgress { if (progressMap.isEmpty()) return@buildList - val totalProgress = progressMap.values.map { it ?: 1.0 }.average().roundToPrecision(1) + val totalProgress = progressMap.values.map { it ?: 1.0 }.average().roundTo(1) val formatPercentage = LorenzUtils.formatPercentage(totalProgress) addAsSingletonList("§7Living Metal Suit Progress: ${if (isMaxed) "§a§lMAXED!" else "§a$formatPercentage"}") diff --git a/src/main/java/at/hannibal2/skyhanni/features/rift/everywhere/CruxTalismanDisplay.kt b/src/main/java/at/hannibal2/skyhanni/features/rift/everywhere/CruxTalismanDisplay.kt index ccdb19fc5094..c333550f6349 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/rift/everywhere/CruxTalismanDisplay.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/rift/everywhere/CruxTalismanDisplay.kt @@ -11,7 +11,7 @@ import at.hannibal2.skyhanni.utils.InventoryUtils import at.hannibal2.skyhanni.utils.ItemUtils.getInternalName import at.hannibal2.skyhanni.utils.ItemUtils.getLore import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.NumberUtil.roundToPrecision +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher import at.hannibal2.skyhanni.utils.RenderUtils.renderStringsAndItems import at.hannibal2.skyhanni.utils.StringUtils.removeColor @@ -80,7 +80,7 @@ object CruxTalismanDisplay { } } } - percentValue = ((percent.toDouble() / 600) * 100).roundToPrecision(1) + percentValue = ((percent.toDouble() / 600) * 100).roundTo(1) if (bonusesLine.isNotEmpty() && config.showBonuses.get()) { addAsSingletonList("§7Bonuses:") bonusesLine.forEach { addAsSingletonList(" $it") } diff --git a/src/main/java/at/hannibal2/skyhanni/features/rift/everywhere/motes/RiftMotesOrb.kt b/src/main/java/at/hannibal2/skyhanni/features/rift/everywhere/motes/RiftMotesOrb.kt index d1ada6a38e98..4fc0ccbf5fce 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/rift/everywhere/motes/RiftMotesOrb.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/rift/everywhere/motes/RiftMotesOrb.kt @@ -9,8 +9,8 @@ import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.CollectionUtils.editCopy import at.hannibal2.skyhanni.utils.LocationUtils.distanceToPlayer import at.hannibal2.skyhanni.utils.LorenzColor -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.LorenzVec +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher import at.hannibal2.skyhanni.utils.RenderUtils.drawDynamicText import at.hannibal2.skyhanni.utils.RenderUtils.drawWaypointFilled @@ -79,7 +79,7 @@ object RiftMotesOrb { val ageInSeconds = (System.currentTimeMillis() - orb.startTime).toDouble() / 1000 if (ageInSeconds < 0.5) continue - val particlesPerSecond = (orb.counter.toDouble() / ageInSeconds).round(1) + val particlesPerSecond = (orb.counter.toDouble() / ageInSeconds).roundTo(1) if (particlesPerSecond < 60 || particlesPerSecond > 90) continue orb.isOrb = true diff --git a/src/main/java/at/hannibal2/skyhanni/features/skillprogress/SkillProgress.kt b/src/main/java/at/hannibal2/skyhanni/features/skillprogress/SkillProgress.kt index 87228447fadc..a16f78b52e45 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/skillprogress/SkillProgress.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/skillprogress/SkillProgress.kt @@ -23,7 +23,7 @@ import at.hannibal2.skyhanni.utils.LorenzUtils import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators import at.hannibal2.skyhanni.utils.NumberUtil.formatDouble import at.hannibal2.skyhanni.utils.NumberUtil.interpolate -import at.hannibal2.skyhanni.utils.NumberUtil.roundToPrecision +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.Quad import at.hannibal2.skyhanni.utils.RenderUtils.renderRenderables import at.hannibal2.skyhanni.utils.RenderUtils.renderStringsAndItems @@ -441,7 +441,7 @@ object SkillProgress { val percent = if (currentXpMax == 0L) 100F else 100F * currentXp / currentXpMax if (config.usePercentage.get()) - append("§7(§6${percent.roundToPrecision(2)}%§7)") + append("§7(§6${percent.roundTo(2)}%§7)") else { if (currentXpMax == 0L) append("§7(§6${currentXp.addSeparators()}§7)") diff --git a/src/main/java/at/hannibal2/skyhanni/features/skillprogress/SkillTooltip.kt b/src/main/java/at/hannibal2/skyhanni/features/skillprogress/SkillTooltip.kt index 0a94f9ee8b28..8c1d3f618c29 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/skillprogress/SkillTooltip.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/skillprogress/SkillTooltip.kt @@ -10,7 +10,7 @@ import at.hannibal2.skyhanni.utils.ItemUtils.getLore import at.hannibal2.skyhanni.utils.ItemUtils.name import at.hannibal2.skyhanni.utils.LorenzUtils import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators -import at.hannibal2.skyhanni.utils.NumberUtil.roundToPrecision +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.NumberUtil.toRoman import at.hannibal2.skyhanni.utils.StringUtils import at.hannibal2.skyhanni.utils.StringUtils.isRoman @@ -40,7 +40,7 @@ object SkillTooltip { val maxReached = "§7§8Max Skill level reached!" if (line.contains(maxReached) && overflowConfig.enableInSkillMenuTooltip) { val progress = (skillInfo.overflowCurrentXp.toDouble() / skillInfo.overflowCurrentXpMax) * 100 - val percent = "§e${progress.roundToPrecision(1)}%" + val percent = "§e${progress.roundTo(1)}%" val currentLevel = skillInfo.overflowLevel val level = if (useRoman) currentLevel.toRoman() else currentLevel @@ -74,7 +74,7 @@ object SkillTooltip { val progress = have.toDouble() / need val progressBar = StringUtils.progressBar(progress) val nextLevel = if (useRoman) targetLevel.toRoman() else targetLevel - val percent = "§e${(progress * 100).roundToPrecision(1)}%" + val percent = "§e${(progress * 100).roundTo(1)}%" iterator.add("") iterator.add("§7Progress to Level $nextLevel: $percent") iterator.add("$progressBar §e${have.addSeparators()}§6/§e${need.addSeparators()}") diff --git a/src/main/java/at/hannibal2/skyhanni/test/PacketTest.kt b/src/main/java/at/hannibal2/skyhanni/test/PacketTest.kt index 99dd9fcbfc76..a0ab34edee3a 100644 --- a/src/main/java/at/hannibal2/skyhanni/test/PacketTest.kt +++ b/src/main/java/at/hannibal2/skyhanni/test/PacketTest.kt @@ -7,9 +7,9 @@ import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.ChatUtils import at.hannibal2.skyhanni.utils.EntityUtils import at.hannibal2.skyhanni.utils.LocationUtils.distanceToPlayer -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.LorenzVec import at.hannibal2.skyhanni.utils.NumberUtil.isInt +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.ReflectionUtils.makeAccessible import at.hannibal2.skyhanni.utils.getLorenzVec import at.hannibal2.skyhanni.utils.toLorenzVec @@ -187,7 +187,7 @@ object PacketTest { } private fun getDistance(location: LorenzVec?): Double { - return location?.distanceToPlayer()?.round(1) ?: 0.0 + return location?.distanceToPlayer()?.roundTo(1) ?: 0.0 } private fun getLocation(packet: Packet<*>, entity: Entity?): LorenzVec? { diff --git a/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt b/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt index c0c03b6771ab..156ce82a31c3 100644 --- a/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt +++ b/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt @@ -38,7 +38,6 @@ import at.hannibal2.skyhanni.utils.LorenzColor import at.hannibal2.skyhanni.utils.LorenzDebug import at.hannibal2.skyhanni.utils.LorenzLogger import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.LorenzVec import at.hannibal2.skyhanni.utils.NEUInternalName import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName @@ -47,6 +46,7 @@ import at.hannibal2.skyhanni.utils.NEUItems.getItemStackOrNull import at.hannibal2.skyhanni.utils.NEUItems.getNpcPriceOrNull import at.hannibal2.skyhanni.utils.NEUItems.getPriceOrNull import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.OSUtils import at.hannibal2.skyhanni.utils.ReflectionUtils.makeAccessible import at.hannibal2.skyhanni.utils.RenderUtils.drawDynamicText @@ -324,9 +324,9 @@ object SkyHanniDebugsAndTests { fun copyLocation(args: Array) { val location = LocationUtils.playerLocation() - val x = (location.x + 0.001).round(1) - val y = (location.y + 0.001).round(1) - val z = (location.z + 0.001).round(1) + val x = (location.x + 0.001).roundTo(1) + val y = (location.y + 0.001).roundTo(1) + val z = (location.z + 0.001).roundTo(1) if (args.size == 1 && args[0].equals("json", false)) { OSUtils.copyToClipboard("\"$x:$y:$z\"") return @@ -576,7 +576,8 @@ object SkyHanniDebugsAndTests { }.editCopy { this.add( 0, - generateSequence(scale) { it + 0.1 }.take(25).map { Renderable.string(it.round(1).toString()) }.toList(), + generateSequence(scale) { it + 0.1 }.take(25).map { Renderable.string(it.roundTo(1).toString()) } + .toList(), ) } config.debugItemPos.renderRenderables( diff --git a/src/main/java/at/hannibal2/skyhanni/test/command/TrackParticlesCommand.kt b/src/main/java/at/hannibal2/skyhanni/test/command/TrackParticlesCommand.kt index 0892d11252a1..69c16daeb414 100644 --- a/src/main/java/at/hannibal2/skyhanni/test/command/TrackParticlesCommand.kt +++ b/src/main/java/at/hannibal2/skyhanni/test/command/TrackParticlesCommand.kt @@ -7,8 +7,8 @@ import at.hannibal2.skyhanni.events.LorenzTickEvent import at.hannibal2.skyhanni.events.ReceiveParticleEvent import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.ChatUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.LorenzVec +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.OSUtils import at.hannibal2.skyhanni.utils.RenderUtils.drawDynamicText import at.hannibal2.skyhanni.utils.RenderUtils.renderRenderables @@ -116,7 +116,7 @@ object TrackParticlesCommand { event.drawDynamicText(key, "§7§l${particle.type}", 0.8) event.drawDynamicText( key.up(-0.2), - "§7C: §e${particle.count} §7S: §a${particle.speed.round(2)}", + "§7C: §e${particle.count} §7S: §a${particle.speed.roundTo(2)}", scaleMultiplier = 0.8 ) } diff --git a/src/main/java/at/hannibal2/skyhanni/test/command/TrackSoundsCommand.kt b/src/main/java/at/hannibal2/skyhanni/test/command/TrackSoundsCommand.kt index 50b0c417dc85..1fadab62da24 100644 --- a/src/main/java/at/hannibal2/skyhanni/test/command/TrackSoundsCommand.kt +++ b/src/main/java/at/hannibal2/skyhanni/test/command/TrackSoundsCommand.kt @@ -7,8 +7,8 @@ import at.hannibal2.skyhanni.events.LorenzTickEvent import at.hannibal2.skyhanni.events.PlaySoundEvent import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.ChatUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.LorenzVec +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.OSUtils import at.hannibal2.skyhanni.utils.RenderUtils.drawDynamicText import at.hannibal2.skyhanni.utils.RenderUtils.renderRenderables @@ -123,7 +123,7 @@ object TrackSoundsCommand { event.drawDynamicText(key, "§7§l${sound.soundName}", 0.8) event.drawDynamicText( key.up(-0.2), - "§7P: §e${sound.pitch.round(2)} §7V: $volumeColor${sound.volume.round(2)}", + "§7P: §e${sound.pitch.roundTo(2)} §7V: $volumeColor${sound.volume.roundTo(2)}", scaleMultiplier = 0.8, ) } diff --git a/src/main/java/at/hannibal2/skyhanni/utils/GuiRenderUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/GuiRenderUtils.kt index ba1081a5f3dd..121b9cce337a 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/GuiRenderUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/GuiRenderUtils.kt @@ -3,8 +3,8 @@ package at.hannibal2.skyhanni.utils import at.hannibal2.skyhanni.config.features.skillprogress.SkillProgressBarConfig import at.hannibal2.skyhanni.features.chroma.ChromaShaderManager import at.hannibal2.skyhanni.features.chroma.ChromaType -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.NumberUtil.fractionOf +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.RenderUtils.HorizontalAlignment import at.hannibal2.skyhanni.utils.renderables.Renderable import net.minecraft.client.Minecraft @@ -204,7 +204,7 @@ object GuiRenderUtils { scale = scale, horizontalAlign = HorizontalAlignment.LEFT, ), Renderable.string( - "§2${(percent * 100).round(1)}%", + "§2${(percent * 100).roundTo(1)}%", scale = scale, horizontalAlign = HorizontalAlignment.RIGHT, ), diff --git a/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt index 1fd5419d9622..cd96121c426d 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt @@ -16,6 +16,7 @@ import at.hannibal2.skyhanni.test.TestBingo import at.hannibal2.skyhanni.utils.ChatUtils.lastButtonClicked import at.hannibal2.skyhanni.utils.ItemUtils.getItemCategoryOrNull import at.hannibal2.skyhanni.utils.NEUItems.getItemStackOrNull +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.SimpleTimeMark.Companion.fromNow import at.hannibal2.skyhanni.utils.StringUtils.capAtMinecraftLength import at.hannibal2.skyhanni.utils.StringUtils.removeColor @@ -100,23 +101,11 @@ object LorenzUtils { return originalMessage.stripHypixelMessage() } - fun Double.round(decimals: Int): Double { - var multiplier = 1.0 - repeat(decimals) { multiplier *= 10 } - val result = kotlin.math.round(this * multiplier) / multiplier - val a = result.toString() - val b = toString() - return if (a.length > b.length) this else result - } + @Deprecated("Use roundTo instead", ReplaceWith("this.roundTo(precision)")) + fun Double.round(decimals: Int) = this.roundTo(decimals) - fun Float.round(decimals: Int): Float { - var multiplier = 1.0 - repeat(decimals) { multiplier *= 10 } - val result = kotlin.math.round(this * multiplier) / multiplier - val a = result.toString().length - val b = toString().length - return if (a > b) this else result.toFloat() - } + @Deprecated("Use roundTo instead", ReplaceWith("this.roundTo(precision)")) + fun Float.round(decimals: Int) = this.roundTo(decimals) // TODO replace all calls with regex @Deprecated("Do not use complicated string operations", ReplaceWith("Regex")) diff --git a/src/main/java/at/hannibal2/skyhanni/utils/LorenzVec.kt b/src/main/java/at/hannibal2/skyhanni/utils/LorenzVec.kt index a573ed78daeb..2e5748c6c590 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/LorenzVec.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/LorenzVec.kt @@ -1,6 +1,6 @@ package at.hannibal2.skyhanni.utils -import at.hannibal2.skyhanni.utils.LorenzUtils.round +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import net.minecraft.client.renderer.GlStateManager import net.minecraft.entity.Entity import net.minecraft.network.play.server.S2APacketParticles @@ -153,12 +153,12 @@ data class LorenzVec( return result } - fun round(decimals: Int) = LorenzVec(x.round(decimals), y.round(decimals), z.round(decimals)) + fun round(decimals: Int) = LorenzVec(x.roundTo(decimals), y.roundTo(decimals), z.roundTo(decimals)) fun roundLocationToBlock(): LorenzVec { - val x = (x - .499999).round(0) - val y = (y - .499999).round(0) - val z = (z - .499999).round(0) + val x = (x - .499999).roundTo(0) + val y = (y - .499999).roundTo(0) + val z = (z - .499999).roundTo(0) return LorenzVec(x, y, z) } diff --git a/src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt b/src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt index 4d5fe0a1dd6f..58441ecabc4b 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt @@ -1,13 +1,11 @@ package at.hannibal2.skyhanni.utils import at.hannibal2.skyhanni.SkyHanniMod -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.RegexUtils.matches import java.text.NumberFormat import java.util.Locale import java.util.TreeMap import kotlin.math.pow -import kotlin.math.roundToInt object NumberUtil { @@ -83,21 +81,21 @@ object NumberUtil { * @link https://stackoverflow.com/a/22186845 * @author jpdymond */ - fun Double.roundToPrecision(precision: Int): Double { // TODO is this the same as LorenzUtils.round() ? - val scale = 10.0.pow(precision).toInt() - return (this * scale).roundToInt().toDouble() / scale + fun Double.roundTo(precision: Int): Double { + val scale = 10.0.pow(precision) + return kotlin.math.round(scale) / scale } - /** - * This code was unmodified and taken under CC BY-SA 3.0 license - * @link https://stackoverflow.com/a/22186845 - * @author jpdymond - */ - fun Float.roundToPrecision(precision: Int): Float { - val scale = 10.0.pow(precision).toInt() - return (this * scale).roundToInt().toFloat() / scale + fun Float.roundTo(precision: Int): Float { + return toDouble().roundTo(precision).toFloat() } + @Deprecated("Use roundTo instead", ReplaceWith("this.roundTo(precision)")) + fun Double.roundToPrecision(precision: Int) = this.roundTo(precision) + + @Deprecated("Use roundTo instead", ReplaceWith("this.roundTo(precision)")) + fun Float.roundToPrecision(precision: Int) = this.roundTo(precision) + fun Number.ordinal(): String { val long = this.toLong() if (long % 100 in 11..13) return "th" @@ -209,7 +207,7 @@ object NumberUtil { fun Number.percentWithColorCode(max: Number, round: Int = 1): String { val fraction = this.fractionOf(max) val color = percentageColor(fraction) - val amount = (fraction * 100.0).round(round) + val amount = (fraction * 100.0).roundTo(round) return "${color.getChatColor()}$amount%" } From a8be86761f6302894346498df121a499be3f93ad Mon Sep 17 00:00:00 2001 From: hannibal2 <24389977+hannibal00212@users.noreply.github.com> Date: Mon, 23 Sep 2024 17:19:44 +0200 Subject: [PATCH 18/38] fix rounding --- .../experimentationtable/ExperimentsProfitTracker.kt | 4 ++-- src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt | 4 ++-- src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt | 6 ++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/experimentationtable/ExperimentsProfitTracker.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/experimentationtable/ExperimentsProfitTracker.kt index 7b6fa9bc5f1e..6e755d874018 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/inventory/experimentationtable/ExperimentsProfitTracker.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/experimentationtable/ExperimentsProfitTracker.kt @@ -25,10 +25,10 @@ import at.hannibal2.skyhanni.utils.ItemPriceUtils.getPrice import at.hannibal2.skyhanni.utils.ItemUtils.getInternalName import at.hannibal2.skyhanni.utils.ItemUtils.getInternalNameOrNull import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.round import at.hannibal2.skyhanni.utils.LorenzVec import at.hannibal2.skyhanni.utils.NEUInternalName import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.roundTo import at.hannibal2.skyhanni.utils.NumberUtil.shortFormat import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher import at.hannibal2.skyhanni.utils.RegexUtils.matches @@ -162,7 +162,7 @@ object ExperimentsProfitTracker { val price = internalName.getPrice() val npcPrice = internalName.getNpcPriceOrNull() ?: 0.0 val maxPrice = npcPrice.coerceAtLeast(price) - startCostTemp += maxPrice.round(0).toInt() + startCostTemp += maxPrice.roundTo(0).toInt() iterator.remove() } tracker.modify { diff --git a/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt index cd96121c426d..e4e74b0d213f 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt @@ -101,10 +101,10 @@ object LorenzUtils { return originalMessage.stripHypixelMessage() } - @Deprecated("Use roundTo instead", ReplaceWith("this.roundTo(precision)")) + @Deprecated("Use roundTo instead", ReplaceWith("this.roundTo(decimals)")) fun Double.round(decimals: Int) = this.roundTo(decimals) - @Deprecated("Use roundTo instead", ReplaceWith("this.roundTo(precision)")) + @Deprecated("Use roundTo instead", ReplaceWith("this.roundTo(decimals)")) fun Float.round(decimals: Int) = this.roundTo(decimals) // TODO replace all calls with regex diff --git a/src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt b/src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt index 58441ecabc4b..dc7c4828c431 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt @@ -83,12 +83,10 @@ object NumberUtil { */ fun Double.roundTo(precision: Int): Double { val scale = 10.0.pow(precision) - return kotlin.math.round(scale) / scale + return kotlin.math.round(this * scale) / scale } - fun Float.roundTo(precision: Int): Float { - return toDouble().roundTo(precision).toFloat() - } + fun Float.roundTo(precision: Int): Float = toDouble().roundTo(precision).toFloat() @Deprecated("Use roundTo instead", ReplaceWith("this.roundTo(precision)")) fun Double.roundToPrecision(precision: Int) = this.roundTo(precision) From 9a3620ea19f08104cc97560bb16feb2092b85847 Mon Sep 17 00:00:00 2001 From: hannibal2 <24389977+hannibal00212@users.noreply.github.com> Date: Mon, 23 Sep 2024 17:20:03 +0200 Subject: [PATCH 19/38] code cleanup --- .../at/hannibal2/skyhanni/features/garden/GardenYawAndPitch.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/GardenYawAndPitch.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/GardenYawAndPitch.kt index ad03a7784cf3..171df87e70ab 100755 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/GardenYawAndPitch.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/GardenYawAndPitch.kt @@ -30,7 +30,7 @@ object GardenYawAndPitch { if (GardenAPI.toolInHand == null && !config.showWithoutTool) return val player = Minecraft.getMinecraft().thePlayer - var yaw = LocationUtils.calculatePlayerYaw() + val yaw = LocationUtils.calculatePlayerYaw() val pitch = player.rotationPitch if (yaw != lastYaw || pitch != lastPitch) { From e4821befd023da38f8a054773e38b3f9a6f28210 Mon Sep 17 00:00:00 2001 From: hannibal2 <24389977+hannibal00212@users.noreply.github.com> Date: Mon, 23 Sep 2024 17:20:33 +0200 Subject: [PATCH 20/38] replacing round to roundTo in LorenzVec --- .../java/at/hannibal2/skyhanni/events/PlaySoundEvent.kt | 2 +- .../at/hannibal2/skyhanni/events/ReceiveParticleEvent.kt | 4 ++-- .../skyhanni/features/combat/mobs/ArachneSpawnTimer.kt | 2 +- .../skyhanni/features/garden/pests/PestParticleWaypoint.kt | 4 ++-- .../features/mining/glacitemineshaft/MineshaftWaypoints.kt | 2 +- .../hannibal2/skyhanni/features/misc/trevor/TrevorSolver.kt | 2 +- .../at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt | 2 +- src/main/java/at/hannibal2/skyhanni/utils/LorenzVec.kt | 6 +++++- 8 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/main/java/at/hannibal2/skyhanni/events/PlaySoundEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/PlaySoundEvent.kt index 386167123768..96f228a6ba85 100644 --- a/src/main/java/at/hannibal2/skyhanni/events/PlaySoundEvent.kt +++ b/src/main/java/at/hannibal2/skyhanni/events/PlaySoundEvent.kt @@ -11,7 +11,7 @@ class PlaySoundEvent(val soundName: String, val location: LorenzVec, val pitch: val distanceToPlayer by lazy { location.distanceToPlayer() } override fun toString(): String { - return "PlaySoundEvent(soundName='$soundName', pitch=$pitch, volume=$volume, location=${location.round(1)}, distanceToPlayer=${ + return "PlaySoundEvent(soundName='$soundName', pitch=$pitch, volume=$volume, location=${location.roundTo(1)}, distanceToPlayer=${ distanceToPlayer.roundTo(1) })" } diff --git a/src/main/java/at/hannibal2/skyhanni/events/ReceiveParticleEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/ReceiveParticleEvent.kt index 7c695c7eec2f..75fe8af8a3c2 100644 --- a/src/main/java/at/hannibal2/skyhanni/events/ReceiveParticleEvent.kt +++ b/src/main/java/at/hannibal2/skyhanni/events/ReceiveParticleEvent.kt @@ -21,8 +21,8 @@ class ReceiveParticleEvent( val distanceToPlayer by lazy { location.distanceToPlayer() } override fun toString(): String { - return "ReceiveParticleEvent(type='$type', location=${location.round(1)}, count=$count, speed=$speed, offset=${ - offset.round( + return "ReceiveParticleEvent(type='$type', location=${location.roundTo(1)}, count=$count, speed=$speed, offset=${ + offset.roundTo( 1 ) }, longDistance=$longDistance, particleArgs=${particleArgs.contentToString()}, distanceToPlayer=${ diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/mobs/ArachneSpawnTimer.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/mobs/ArachneSpawnTimer.kt index 2fee0577e8f6..d2e6f7ed67e5 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/combat/mobs/ArachneSpawnTimer.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/combat/mobs/ArachneSpawnTimer.kt @@ -99,7 +99,7 @@ object ArachneSpawnTimer { val packet = event.packet if (packet is S2APacketParticles) { - val location = packet.toLorenzVec().round(2) + val location = packet.toLorenzVec().roundTo(2) if (arachneAltarLocation.distance(location) > 30) return if (packet.particleType == EnumParticleTypes.REDSTONE && packet.particleSpeed == 1.0f) { particleCounter += 1 diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/pests/PestParticleWaypoint.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/pests/PestParticleWaypoint.kt index 23ead6f911d0..d07e55816732 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/pests/PestParticleWaypoint.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/pests/PestParticleWaypoint.kt @@ -86,7 +86,7 @@ object PestParticleWaypoint { val yellow = LorenzVec(0.8, 0.8, 0.0) val redPest = LorenzVec(0.8, 0.4, 0.0) val redPlot = LorenzVec(0.8, 0.0, 0.0) - isPointingToPest = when (event.offset.round(5)) { + isPointingToPest = when (event.offset.roundTo(5)) { redPlot -> false redPest, yellow, darkYellow -> true else -> return @@ -150,7 +150,7 @@ object PestParticleWaypoint { waypoint, color, 3, - false + false, ) } diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/glacitemineshaft/MineshaftWaypoints.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/glacitemineshaft/MineshaftWaypoints.kt index 7476e212b455..ae17e783bb16 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/mining/glacitemineshaft/MineshaftWaypoints.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/glacitemineshaft/MineshaftWaypoints.kt @@ -38,7 +38,7 @@ object MineshaftWaypoints { fun onIslandChange(event: IslandChangeEvent) { if (event.newIsland != IslandType.MINESHAFT) return - val playerLocation = LocationUtils.playerLocation().round(0).add(y = -1) + val playerLocation = LocationUtils.playerLocation().roundTo(0).add(y = -1) if (config.mineshaftWaypoints.entranceLocation) { waypoints.add(MineshaftWaypoint(waypointType = MineshaftWaypointType.ENTRANCE, location = playerLocation)) diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/trevor/TrevorSolver.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/trevor/TrevorSolver.kt index 2188cb1744f0..94fefbdf8042 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/trevor/TrevorSolver.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/trevor/TrevorSolver.kt @@ -29,7 +29,7 @@ object TrevorSolver { var averageHeight = (minHeight + maxHeight) / 2 fun findMobHeight(height: Int, above: Boolean) { - val playerPosition = LocationUtils.playerLocation().round(2) + val playerPosition = LocationUtils.playerLocation().roundTo(2) val mobHeight = if (above) playerPosition.y + height else playerPosition.y - height if (maxHeight == 0.0) { diff --git a/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt b/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt index 156ce82a31c3..cac3349e11bd 100644 --- a/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt +++ b/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt @@ -606,7 +606,7 @@ object SkyHanniDebugsAndTests { @SubscribeEvent fun onReceiveParticle(event: ReceiveParticleEvent) { // val particleType = event.type -// val distance = LocationUtils.playerLocation().distance(event.location).round(2) +// val distance = LocationUtils.playerLocation().distance(event.location).roundTo(2) // // println("") // println("particleType: $particleType") diff --git a/src/main/java/at/hannibal2/skyhanni/utils/LorenzVec.kt b/src/main/java/at/hannibal2/skyhanni/utils/LorenzVec.kt index 2e5748c6c590..6769210ef26b 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/LorenzVec.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/LorenzVec.kt @@ -153,7 +153,10 @@ data class LorenzVec( return result } - fun round(decimals: Int) = LorenzVec(x.roundTo(decimals), y.roundTo(decimals), z.roundTo(decimals)) + @Deprecated("Use roundTo instead", ReplaceWith("this.roundTo(precision)")) + fun round(precision: Int) = roundTo(precision) + + fun roundTo(precision: Int) = LorenzVec(x.roundTo(precision), y.roundTo(precision), z.roundTo(precision)) fun roundLocationToBlock(): LorenzVec { val x = (x - .499999).roundTo(0) @@ -164,6 +167,7 @@ data class LorenzVec( fun slope(other: LorenzVec, factor: Double) = this + (other - this).scale(factor) + // TODO better name. dont confuse with roundTo() fun roundLocation(): LorenzVec { val x = if (this.x < 0) x.toInt() - 1 else x.toInt() val y = y.toInt() - 1 From cf039c7e215b26f78eeb468cd911c5d8f8586115 Mon Sep 17 00:00:00 2001 From: Clicks <58398364+CuzImClicks@users.noreply.github.com> Date: Tue, 24 Sep 2024 10:26:42 +0200 Subject: [PATCH 21/38] Improvement: Option to hide all Tooltips inside of Excavator (#2579) Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com> --- .../mining/ExcavatorTooltipHiderConfig.java | 21 ++++++++++++++ .../mining/FossilExcavatorConfig.java | 13 ++++----- .../fossilexcavator/ExcavatorTooltipHider.kt | 28 +++++++++++++++++-- 3 files changed, 51 insertions(+), 11 deletions(-) create mode 100644 src/main/java/at/hannibal2/skyhanni/config/features/mining/ExcavatorTooltipHiderConfig.java diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/mining/ExcavatorTooltipHiderConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/mining/ExcavatorTooltipHiderConfig.java new file mode 100644 index 000000000000..b1489ace876a --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/mining/ExcavatorTooltipHiderConfig.java @@ -0,0 +1,21 @@ +package at.hannibal2.skyhanni.config.features.mining; + +import at.hannibal2.skyhanni.config.FeatureToggle; +import com.google.gson.annotations.Expose; +import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorBoolean; +import io.github.notenoughupdates.moulconfig.annotations.ConfigOption; + +public class ExcavatorTooltipHiderConfig { + + @Expose + @ConfigOption(name = "Hide Dirt", desc = "Hides tooltips of the Dirt inside of the Fossil Excavator.") + @ConfigEditorBoolean + @FeatureToggle + public boolean hideDirt = true; + + @Expose + @ConfigOption(name = "Hide Everything", desc = "Hide all tooltips inside of the Fossil Excavator.") + @ConfigEditorBoolean + @FeatureToggle + public boolean hideEverything = false; +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/mining/FossilExcavatorConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/mining/FossilExcavatorConfig.java index 8dfb12854232..8445afe5336a 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/mining/FossilExcavatorConfig.java +++ b/src/main/java/at/hannibal2/skyhanni/config/features/mining/FossilExcavatorConfig.java @@ -18,6 +18,11 @@ public class FossilExcavatorConfig { @Accordion public ExcavatorProfitTrackerConfig profitTracker = new ExcavatorProfitTrackerConfig(); + @Expose + @ConfigOption(name = "Excavator Tooltip Hider", desc = "") + @Accordion + public ExcavatorTooltipHiderConfig tooltipHider = new ExcavatorTooltipHiderConfig(); + @Expose @ConfigOption( name = "Profit per Excavation", @@ -36,12 +41,4 @@ public class FossilExcavatorConfig { @FeatureToggle public boolean glacitePowderStack = false; - @Expose - @ConfigOption( - name = "Hide Excavator Tooltips", - desc = "Hides tooltips of items inside of the Fossil Excavator." - ) - @ConfigEditorBoolean - @FeatureToggle - public boolean hideExcavatorTooltips = true; } diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/ExcavatorTooltipHider.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/ExcavatorTooltipHider.kt index 8b58402cdd63..0b0469b91675 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/ExcavatorTooltipHider.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/fossilexcavator/ExcavatorTooltipHider.kt @@ -3,20 +3,42 @@ package at.hannibal2.skyhanni.features.mining.fossilexcavator import at.hannibal2.skyhanni.SkyHanniMod import at.hannibal2.skyhanni.events.LorenzToolTipEvent import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule +import at.hannibal2.skyhanni.utils.ItemUtils.name +import at.hannibal2.skyhanni.utils.RegexUtils.matches +import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern import net.minecraft.client.player.inventory.ContainerLocalMenu import net.minecraftforge.fml.common.eventhandler.SubscribeEvent @SkyHanniModule object ExcavatorTooltipHider { - private val config get() = SkyHanniMod.feature.mining.fossilExcavator + private val config get() = SkyHanniMod.feature.mining.fossilExcavator.tooltipHider + + /** + * REGEX-TEST: §6Dirt + */ + private val dirtPattern by RepoPattern.pattern( + "excavator.dirt.name", + "§6Dirt", + ) @SubscribeEvent fun onTooltip(event: LorenzToolTipEvent) { if (!isEnabled()) return + if (event.slot.inventory !is ContainerLocalMenu) return - event.cancel() + if (config.hideEverything) { + event.cancel() + return + } + + if (config.hideDirt) { + val isDirt = dirtPattern.matches(event.itemStack.name) + if (isDirt) { + event.cancel() + } + } } - fun isEnabled() = FossilExcavatorAPI.inInventory && !FossilExcavatorAPI.inExcavatorMenu && config.hideExcavatorTooltips + fun isEnabled() = FossilExcavatorAPI.inInventory && !FossilExcavatorAPI.inExcavatorMenu } From 04d2530a67ef9ae2b877a448e15ce16fadd4c25b Mon Sep 17 00:00:00 2001 From: Luna Date: Tue, 24 Sep 2024 10:27:37 +0200 Subject: [PATCH 22/38] Fix: Hard Stone in Crystal Hollows misdetected as Mithril (#2580) --- src/main/java/at/hannibal2/skyhanni/features/mining/OreBlock.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/OreBlock.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/OreBlock.kt index 8e7cbf0821da..4275a809ca92 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/mining/OreBlock.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/OreBlock.kt @@ -30,7 +30,7 @@ enum class OreBlock( // MITHRIL LOW_TIER_MITHRIL( checkBlock = ::isLowTierMithril, - checkArea = { inDwarvenMines || inCrystalHollows || inGlacite }, + checkArea = { inDwarvenMines || inGlacite }, ), MID_TIER_MITHRIL( checkBlock = { it.block == Blocks.prismarine }, From 1e9764d1d1209177d4a939a9b812931a7d49a926 Mon Sep 17 00:00:00 2001 From: hannibal2 <24389977+hannibal002@users.noreply.github.com> Date: Tue, 24 Sep 2024 10:27:50 +0200 Subject: [PATCH 23/38] Backend: PrimitiveIngredient changes and code cleanup (#2576) Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com> Co-authored-by: Cal --- .../skyhanni/features/bingo/MinionCraftHelper.kt | 9 ++++----- .../garden/visitor/GardenVisitorFeatures.kt | 13 ++++++------- .../garden/visitor/GardenVisitorSupercraft.kt | 14 +++++++------- .../at/hannibal2/skyhanni/utils/ItemUtils.kt | 5 ++--- .../java/at/hannibal2/skyhanni/utils/NEUItems.kt | 9 +++++---- .../skyhanni/utils/PrimitiveIngredient.kt | 16 +++++++++++++--- .../skyhanni/utils/PrimitiveItemStack.kt | 2 ++ 7 files changed, 39 insertions(+), 29 deletions(-) diff --git a/src/main/java/at/hannibal2/skyhanni/features/bingo/MinionCraftHelper.kt b/src/main/java/at/hannibal2/skyhanni/features/bingo/MinionCraftHelper.kt index dbec4ea5788c..6f7a062f85e5 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/bingo/MinionCraftHelper.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/bingo/MinionCraftHelper.kt @@ -7,6 +7,7 @@ import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent import at.hannibal2.skyhanni.events.LorenzTickEvent import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule +import at.hannibal2.skyhanni.utils.CollectionUtils.addOrPut import at.hannibal2.skyhanni.utils.ItemUtils.getInternalName import at.hannibal2.skyhanni.utils.ItemUtils.hasEnchantments import at.hannibal2.skyhanni.utils.ItemUtils.itemName @@ -18,6 +19,7 @@ import at.hannibal2.skyhanni.utils.NEUItems import at.hannibal2.skyhanni.utils.NEUItems.getCachedIngredients import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators import at.hannibal2.skyhanni.utils.NumberUtil.romanToDecimalIfNecessary +import at.hannibal2.skyhanni.utils.PrimitiveIngredient.Companion.toPrimitiveItemStacks import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher import at.hannibal2.skyhanni.utils.RenderUtils.renderStrings import at.hannibal2.skyhanni.utils.StringUtils.removeColor @@ -197,12 +199,9 @@ object MinionCraftHelper { val output = recipe.output ?: continue if (!output.internalName.contains("_GENERATOR_")) continue val map = mutableMapOf() - for (input in recipe.ingredients) { - val itemId = input.internalName + for ((itemId, count) in recipe.ingredients.toPrimitiveItemStacks()) { if (minionId != itemId) { - val count = input.count.toInt() - val old = map.getOrDefault(itemId, 0) - map[itemId] = old + count + map.addOrPut(itemId, count) } } var allDone = true diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/visitor/GardenVisitorFeatures.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/visitor/GardenVisitorFeatures.kt index 2e8d789660ea..70392bb46d78 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/visitor/GardenVisitorFeatures.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/visitor/GardenVisitorFeatures.kt @@ -29,6 +29,7 @@ import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.test.command.ErrorManager import at.hannibal2.skyhanni.utils.ChatUtils import at.hannibal2.skyhanni.utils.CollectionUtils.addAsSingletonList +import at.hannibal2.skyhanni.utils.CollectionUtils.addOrPut import at.hannibal2.skyhanni.utils.ConfigUtils import at.hannibal2.skyhanni.utils.EntityUtils import at.hannibal2.skyhanni.utils.HypixelCommands @@ -51,6 +52,7 @@ import at.hannibal2.skyhanni.utils.NEUItems.getPrice import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators import at.hannibal2.skyhanni.utils.NumberUtil.formatInt import at.hannibal2.skyhanni.utils.NumberUtil.shortFormat +import at.hannibal2.skyhanni.utils.PrimitiveIngredient.Companion.toPrimitiveItemStacks import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher import at.hannibal2.skyhanni.utils.RenderUtils.drawString import at.hannibal2.skyhanni.utils.RenderUtils.renderStringsAndItems @@ -253,16 +255,13 @@ object GardenVisitorFeatures { ?.ingredients ?: emptySet() if (ingredients.isEmpty()) return - // TODO change key to NEUInternalName - val requiredIngredients = mutableMapOf() - for (ingredient in ingredients) { - val key = ingredient.internalName.asString() - requiredIngredients[key] = - requiredIngredients.getOrDefault(key, 0) + ingredient.count.toInt() + val requiredIngredients = mutableMapOf() + for ((key, count) in ingredients.toPrimitiveItemStacks()) { + requiredIngredients.addOrPut(key, count) } var hasIngredients = true for ((key, value) in requiredIngredients) { - val sackItem = key.asInternalName().getAmountInSacks() + val sackItem = key.getAmountInSacks() if (sackItem < value * (amount - amountInSacks)) { hasIngredients = false break diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/visitor/GardenVisitorSupercraft.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/visitor/GardenVisitorSupercraft.kt index f2e4f80fb8f1..9b14483dc476 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/visitor/GardenVisitorSupercraft.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/visitor/GardenVisitorSupercraft.kt @@ -7,12 +7,14 @@ import at.hannibal2.skyhanni.events.garden.visitor.VisitorOpenEvent import at.hannibal2.skyhanni.events.render.gui.ReplaceItemEvent import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.test.command.ErrorManager +import at.hannibal2.skyhanni.utils.CollectionUtils.addOrPut import at.hannibal2.skyhanni.utils.HypixelCommands import at.hannibal2.skyhanni.utils.ItemUtils 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.NEUItems.getItemStack +import at.hannibal2.skyhanni.utils.PrimitiveIngredient.Companion.toPrimitiveItemStacks import at.hannibal2.skyhanni.utils.SimpleTimeMark import net.minecraft.entity.player.InventoryPlayer import net.minecraftforge.fml.common.eventhandler.EventPriority @@ -73,15 +75,13 @@ object GardenVisitorSupercraft { // TODO describe what this line does .firstOrNull { !it.ingredients.first().internalName.contains("PEST") } ?.ingredients ?: return - // TODO change key to NEUInternalName - val ingredientReqs = mutableMapOf() - for (ingredient in ingredients) { - val key = ingredient.internalName.asString() - ingredientReqs[key] = ingredientReqs.getOrDefault(key, 0) + ingredient.count.toInt() + val requiredIngredients = mutableMapOf() + for ((key, count) in ingredients.toPrimitiveItemStacks()) { + requiredIngredients.addOrPut(key, count) } hasIngredients = true - for ((key, value) in ingredientReqs) { - val sackItem = key.asInternalName().getAmountInSacks() + for ((key, value) in requiredIngredients) { + val sackItem = key.getAmountInSacks() lastSuperCraftMaterial = internalName.asString() if (sackItem < value * amount) { hasIngredients = false diff --git a/src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt index add66cc0adcf..d39b37c8abb1 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt @@ -9,6 +9,7 @@ import at.hannibal2.skyhanni.utils.CollectionUtils.addOrPut import at.hannibal2.skyhanni.utils.ItemPriceUtils.getPrice import at.hannibal2.skyhanni.utils.NEUItems.getItemStackOrNull import at.hannibal2.skyhanni.utils.NumberUtil.formatInt +import at.hannibal2.skyhanni.utils.PrimitiveIngredient.Companion.toPrimitiveItemStacks import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher import at.hannibal2.skyhanni.utils.RegexUtils.matches import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.cachedData @@ -511,9 +512,7 @@ object ItemUtils { fun neededItems(recipe: PrimitiveRecipe): Map { val neededItems = mutableMapOf() - for (ingredient in recipe.ingredients) { - val material = ingredient.internalName - val amount = ingredient.count.toInt() + for ((material, amount) in recipe.ingredients.toPrimitiveItemStacks()) { neededItems.addOrPut(material, amount) } return neededItems diff --git a/src/main/java/at/hannibal2/skyhanni/utils/NEUItems.kt b/src/main/java/at/hannibal2/skyhanni/utils/NEUItems.kt index 198b43a856a6..2f17ea4d1d31 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/NEUItems.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/NEUItems.kt @@ -9,10 +9,12 @@ import at.hannibal2.skyhanni.events.NeuRepositoryReloadEvent import at.hannibal2.skyhanni.events.RepositoryReloadEvent import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.test.command.ErrorManager +import at.hannibal2.skyhanni.utils.CollectionUtils.addOrPut import at.hannibal2.skyhanni.utils.ItemBlink.checkBlinkItem import at.hannibal2.skyhanni.utils.ItemUtils.getInternalName import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName import at.hannibal2.skyhanni.utils.NumberUtil.isInt +import at.hannibal2.skyhanni.utils.PrimitiveIngredient.Companion.toPrimitiveItemStacks import at.hannibal2.skyhanni.utils.PrimitiveItemStack.Companion.makePrimitiveStack import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.getItemId import at.hannibal2.skyhanni.utils.json.BaseGsonBuilder @@ -308,8 +310,8 @@ object NEUItems { if (!recipe.isCraftingRecipe()) continue val map = mutableMapOf() - for (ingredient in recipe.getCachedIngredients()) { - val count = ingredient.count.toInt() + for (ingredient in recipe.getCachedIngredients().toPrimitiveItemStacks()) { + val amount = ingredient.amount var internalItemId = ingredient.internalName // ignore cactus green if (internalName == "ENCHANTED_CACTUS_GREEN".asInternalName() && internalItemId == "INK_SACK-2".asInternalName()) { @@ -331,8 +333,7 @@ object NEUItems { continue } - val old = map.getOrDefault(internalItemId, 0) - map[internalItemId] = old + count + map.addOrPut(internalItemId, amount) } if (map.size != 1) continue val current = map.iterator().next().toPair() diff --git a/src/main/java/at/hannibal2/skyhanni/utils/PrimitiveIngredient.kt b/src/main/java/at/hannibal2/skyhanni/utils/PrimitiveIngredient.kt index e8d1edefcc44..b1692a119b62 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/PrimitiveIngredient.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/PrimitiveIngredient.kt @@ -2,6 +2,7 @@ package at.hannibal2.skyhanni.utils import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.SKYBLOCK_COIN import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName +import at.hannibal2.skyhanni.utils.NumberUtil.formatDouble import io.github.moulberry.notenoughupdates.recipes.Ingredient class PrimitiveIngredient(val internalName: NEUInternalName, val count: Double = 1.0) { @@ -10,17 +11,26 @@ class PrimitiveIngredient(val internalName: NEUInternalName, val count: Double = constructor(ingredientIdentifier: String) : this( ingredientIdentifier.substringBefore(':').asInternalName(), - ingredientIdentifier.substringAfter(':').toDoubleOrNull() ?: 1.0, + // if second part is blank, the count is assumed to be 1 + ingredientIdentifier.substringAfter(':', "").let { if (it.isBlank()) 1.0 else it.formatDouble() }, ) companion object { fun coinIngredient(count: Double = 1.0) = PrimitiveIngredient(SKYBLOCK_COIN, count) - fun fromNeuIngredient(neuIngredient: Ingredient) = PrimitiveIngredient(neuIngredient.internalItemId.asInternalName(), neuIngredient.count) + fun fromNeuIngredient(neuIngredient: Ingredient) = + PrimitiveIngredient(neuIngredient.internalItemId.asInternalName(), neuIngredient.count) + + fun Set.toPrimitiveItemStacks(): List = + map { it.toPrimitiveItemStack() } } fun isCoin() = internalName == SKYBLOCK_COIN - override fun toString() = "$internalName x$count" + + fun toPair() = Pair(internalName, count) + + // TODO should maybe throw an error when trying to use with internalName == SKYBLOCK_COIN + fun toPrimitiveItemStack() = PrimitiveItemStack(internalName, count.toInt()) } diff --git a/src/main/java/at/hannibal2/skyhanni/utils/PrimitiveItemStack.kt b/src/main/java/at/hannibal2/skyhanni/utils/PrimitiveItemStack.kt index 52faaba30066..f9d2b89f7b00 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/PrimitiveItemStack.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/PrimitiveItemStack.kt @@ -15,6 +15,8 @@ data class PrimitiveItemStack(val internalName: NEUInternalName, val amount: Int val itemName by lazy { internalName.itemName } + fun toPair() = Pair(internalName, amount) + companion object { fun NEUInternalName.makePrimitiveStack(amount: Int = 1) = PrimitiveItemStack(this, amount) From cc8a3dba69e8f1474a33a7e47b32d452f11735fd Mon Sep 17 00:00:00 2001 From: hannibal2 <24389977+hannibal002@users.noreply.github.com> Date: Tue, 24 Sep 2024 10:28:06 +0200 Subject: [PATCH 24/38] Feature: /shnavigate (#2575) Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com> --- .../skyhanni/config/commands/Commands.kt | 2 + .../skyhanni/data/model/GraphNodeTag.kt | 28 ++++- .../skyhanni/features/commands/HelpCommand.kt | 14 +-- .../misc/pathfind/NavigationHelper.kt | 110 ++++++++++++++++++ .../misc/reminders/ReminderManager.kt | 16 +-- .../skyhanni/test/SkyHanniDebugsAndTests.kt | 4 +- .../test/graph/GraphEditorBugFinder.kt | 11 ++ .../skyhanni/test/graph/GraphNodeEditor.kt | 6 + .../at/hannibal2/skyhanni/utils/chat/Text.kt | 7 ++ 9 files changed, 168 insertions(+), 30 deletions(-) create mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/pathfind/NavigationHelper.kt diff --git a/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt b/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt index a52852a8193f..9f2e3cc3fd71 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt +++ b/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt @@ -72,6 +72,7 @@ import at.hannibal2.skyhanni.features.misc.TpsCounter import at.hannibal2.skyhanni.features.misc.discordrpc.DiscordRPCManager import at.hannibal2.skyhanni.features.misc.limbo.LimboTimeTracker import at.hannibal2.skyhanni.features.misc.massconfiguration.DefaultConfigFeatures +import at.hannibal2.skyhanni.features.misc.pathfind.NavigationHelper import at.hannibal2.skyhanni.features.misc.reminders.ReminderManager import at.hannibal2.skyhanni.features.misc.update.UpdateManager import at.hannibal2.skyhanni.features.misc.visualwords.VisualWordGui @@ -182,6 +183,7 @@ object Commands { ) registerCommand("shremind", "Set a reminder for yourself") { ReminderManager.command(it) } registerCommand("shwords", "Opens the config list for modifying visual words") { openVisualWords() } + registerCommand("shnavigate", "Using path finder to go to locatons") { NavigationHelper.onCommand(it) } } private fun usersNormal() { diff --git a/src/main/java/at/hannibal2/skyhanni/data/model/GraphNodeTag.kt b/src/main/java/at/hannibal2/skyhanni/data/model/GraphNodeTag.kt index 2cdc9e7e0d1d..6bf00edafcf6 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/model/GraphNodeTag.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/model/GraphNodeTag.kt @@ -6,7 +6,7 @@ import at.hannibal2.skyhanni.utils.LorenzColor enum class GraphNodeTag( val internalName: String, val color: LorenzColor, - cleanName: String, + val cleanName: String, val description: String, val onlyIsland: IslandType? = null, ) { @@ -17,7 +17,7 @@ enum class GraphNodeTag( AREA("area", LorenzColor.DARK_GREEN, "Area", "A big SkyBlock area."), SMALL_AREA("small_area", LorenzColor.GREEN, "Small Area", "A small SkyBlock area, e.g. a house."), POI("poi", LorenzColor.WHITE, "Point of Interest", "A relevant spot or a landmark on the map."), -// LAUNCH_PAD("launch", LorenzColor.WHITE, "Launch Pad", "Slime blocks sending you to another server."), + // LAUNCH_PAD("launch", LorenzColor.WHITE, "Launch Pad", "Slime blocks sending you to another server."), TELEPORT("teleport", LorenzColor.BLUE, "Teleport", "A spot from/to teleport."), // on multiple islands @@ -43,18 +43,36 @@ enum class GraphNodeTag( // Rift RIFT_ENIGMA("rift_enigma", LorenzColor.DARK_PURPLE, "Enigma Soul", "Enigma Souls in the Rift.", onlyIsland = IslandType.THE_RIFT), RIFT_EYE("rift_eye", LorenzColor.DARK_RED, "Rift Eye", "An Eye in the Rift to teleport to.", onlyIsland = IslandType.THE_RIFT), - RIFT_MONTEZUMA("rift_montezuma", LorenzColor.GRAY, "Montezuma Soul Piece", "A piece of the Montezuma Soul.", onlyIsland = IslandType.THE_RIFT), + RIFT_MONTEZUMA( + "rift_montezuma", + LorenzColor.GRAY, + "Montezuma Soul Piece", + "A piece of the Montezuma Soul.", + onlyIsland = IslandType.THE_RIFT, + ), RIFT_EFFIGY("rift_effigy", LorenzColor.RED, "Blood Effigies", "Locations of the Blood Effigies.", onlyIsland = IslandType.THE_RIFT), // Spider's Den - SPIDER_RELIC("SPIDER_RELIC", LorenzColor.DARK_PURPLE, "Spider's Relic", "An relic in the Spider's Den.", onlyIsland = IslandType.SPIDER_DEN), + SPIDER_RELIC( + "SPIDER_RELIC", + LorenzColor.DARK_PURPLE, + "Spider's Relic", + "An relic in the Spider's Den.", + onlyIsland = IslandType.SPIDER_DEN, + ), // Dwarven Mines MINES_EMISSARY("mines_emissary", LorenzColor.GOLD, "Mines Emissary", "An Emissary to the king.", onlyIsland = IslandType.DWARVEN_MINES), // commission areas // Crimson Isles - CRIMSON_MINIBOSS("crimson_miniboss", LorenzColor.RED, "Crimson Miniboss", "A Miniboss in the Crimson Isle.", onlyIsland = IslandType.CRIMSON_ISLE), + CRIMSON_MINIBOSS( + "crimson_miniboss", + LorenzColor.RED, + "Crimson Miniboss", + "A Miniboss in the Crimson Isle.", + onlyIsland = IslandType.CRIMSON_ISLE, + ), // The End END_GOLEM("end_golem", LorenzColor.RED, "Golem Spawn", "A spot where the golem can spawn in the End.", onlyIsland = IslandType.THE_END), diff --git a/src/main/java/at/hannibal2/skyhanni/features/commands/HelpCommand.kt b/src/main/java/at/hannibal2/skyhanni/features/commands/HelpCommand.kt index 1890c62930d1..0f07c47f8d14 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/commands/HelpCommand.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/commands/HelpCommand.kt @@ -5,13 +5,10 @@ import at.hannibal2.skyhanni.utils.StringUtils.splitLines import at.hannibal2.skyhanni.utils.chat.Text import at.hannibal2.skyhanni.utils.chat.Text.asComponent import at.hannibal2.skyhanni.utils.chat.Text.center -import at.hannibal2.skyhanni.utils.chat.Text.fitToChat import at.hannibal2.skyhanni.utils.chat.Text.hover import at.hannibal2.skyhanni.utils.chat.Text.onClick import at.hannibal2.skyhanni.utils.chat.Text.send -import at.hannibal2.skyhanni.utils.chat.Text.style import at.hannibal2.skyhanni.utils.chat.Text.suggest -import net.minecraft.util.EnumChatFormatting import net.minecraft.util.IChatComponent import kotlin.math.ceil @@ -20,11 +17,6 @@ object HelpCommand { private const val COMMANDS_PER_PAGE = 15 private const val HELP_ID = -6457563 - private fun createDivider() = Text.HYPHEN.fitToChat().style { - strikethrough = true - color = EnumChatFormatting.BLUE - } - private fun createCommandEntry(command: Commands.CommandInfo): IChatComponent { val category = command.category val color = category.color @@ -60,7 +52,7 @@ object HelpCommand { val text = mutableListOf() - text.add(createDivider()) + text.add(Text.createDivider()) text.add(title.asComponent().center()) text.add( Text.join( @@ -77,7 +69,7 @@ object HelpCommand { } else null, ).center(), ) - text.add(createDivider()) + text.add(Text.createDivider()) if (filtered.isEmpty()) { text.add(Text.EMPTY) @@ -91,7 +83,7 @@ object HelpCommand { } } - text.add(createDivider()) + text.add(Text.createDivider()) Text.multiline(text).send(HELP_ID) } diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/pathfind/NavigationHelper.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/pathfind/NavigationHelper.kt new file mode 100644 index 000000000000..b27292559d34 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/pathfind/NavigationHelper.kt @@ -0,0 +1,110 @@ +package at.hannibal2.skyhanni.features.misc.pathfind + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.data.IslandGraphs +import at.hannibal2.skyhanni.data.model.GraphNode +import at.hannibal2.skyhanni.data.model.GraphNodeTag +import at.hannibal2.skyhanni.data.model.findShortestDistance +import at.hannibal2.skyhanni.utils.CollectionUtils.sorted +import at.hannibal2.skyhanni.utils.LorenzUtils.round +import at.hannibal2.skyhanni.utils.chat.Text +import at.hannibal2.skyhanni.utils.chat.Text.asComponent +import at.hannibal2.skyhanni.utils.chat.Text.center +import at.hannibal2.skyhanni.utils.chat.Text.hover +import at.hannibal2.skyhanni.utils.chat.Text.onClick +import at.hannibal2.skyhanni.utils.chat.Text.send +import kotlinx.coroutines.launch +import net.minecraft.util.IChatComponent + +object NavigationHelper { + private val NAVIGATION_CHAT_ID = -6457562 + + val allowedTags = listOf( + GraphNodeTag.NPC, + GraphNodeTag.AREA, + GraphNodeTag.SMALL_AREA, + GraphNodeTag.POI, + GraphNodeTag.SLAYER, + GraphNodeTag.GRIND_MOBS, + GraphNodeTag.GRIND_ORES, + GraphNodeTag.GRIND_CROPS, + GraphNodeTag.MINES_EMISSARY, + GraphNodeTag.CRIMSON_MINIBOSS, + ) + + fun onCommand(args: Array) { + SkyHanniMod.coroutineScope.launch { + doCommandAsync(args) + } + } + + private fun doCommandAsync(args: Array) { + val searchTerm = args.joinToString(" ").lowercase() + val distances = calculateDistances(searchTerm) + val names = calculateNames(distances) + + val text = mutableListOf() + text.add(Text.createDivider()) + text.add("§7Found ${names.size} locations ($searchTerm)".asComponent().center()) + val goBack = { + onCommand(searchTerm.split(" ").toTypedArray()) + IslandGraphs.stop() + } + // TODO dont show a too long list, add pages + for ((name, node) in names) { + val distance = distances[node]!!.round(1) + val component = "$name §e$distance".asComponent() + component.onClick { + IslandGraphs.pathFind(node.position) + sendNavigateMessage(name, goBack) + } + val tag = node.tags.first { it in allowedTags } + // TODO include most closest area, if this is no area (type in area = forger in forge) + component.hover = + ("§eClick to start navigating to\n" + "§7Type: §r${tag.displayName}\n" + "§7Distance: §e$distance blocks").asComponent() + text.add(component) + } + text.add(Text.createDivider()) + Text.multiline(text).send(NAVIGATION_CHAT_ID) + } + + private fun sendNavigateMessage(name: String, goBack: () -> Unit) { + val componentText = "§7Navigating to §r$name".asComponent() + componentText.onClick(onClick = goBack) + componentText.send(NAVIGATION_CHAT_ID) + } + + private fun calculateNames(distances: Map): MutableMap { + val names = mutableMapOf() + for (node in distances.sorted().keys) { + val tag = node.tags.first { it in allowedTags } + val name = "${node.name} §7(${tag.displayName}§7)" + if (name in names) continue + names[name] = node + } + return names + } + + private fun calculateDistances( + searchTerm: String, + ): Map { + val grapth = IslandGraphs.currentIslandGraph ?: return emptyMap() + val closedNote = IslandGraphs.closedNote ?: return emptyMap() + + val nodes = grapth.nodes + val distances = mutableMapOf() + for (node in nodes) { + val name = node.name ?: continue + val remainingTags = node.tags.filter { it in allowedTags } + if (remainingTags.isEmpty()) continue + if (name.lowercase().contains(searchTerm)) { + distances[node] = grapth.findShortestDistance(closedNote, node) + } + if (remainingTags.size != 1) { + println("found node with invalid amount of tags: ${node.name} (${remainingTags.map { it.cleanName }}") + } + } + return distances + } + +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/reminders/ReminderManager.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/reminders/ReminderManager.kt index 472aac2de6be..1df878ac9a2f 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/reminders/ReminderManager.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/reminders/ReminderManager.kt @@ -13,13 +13,10 @@ import at.hannibal2.skyhanni.utils.chat.Text import at.hannibal2.skyhanni.utils.chat.Text.asComponent import at.hannibal2.skyhanni.utils.chat.Text.center import at.hannibal2.skyhanni.utils.chat.Text.command -import at.hannibal2.skyhanni.utils.chat.Text.fitToChat import at.hannibal2.skyhanni.utils.chat.Text.hover import at.hannibal2.skyhanni.utils.chat.Text.send -import at.hannibal2.skyhanni.utils.chat.Text.style import at.hannibal2.skyhanni.utils.chat.Text.suggest import at.hannibal2.skyhanni.utils.chat.Text.wrap -import net.minecraft.util.EnumChatFormatting import net.minecraft.util.IChatComponent import net.minecraftforge.fml.common.eventhandler.SubscribeEvent import kotlin.time.Duration @@ -44,11 +41,6 @@ object ReminderManager { private fun sendMessage(message: String) = Text.join("§e[Reminder]", " ", message).send(REMINDERS_ACTION_ID) - private fun createDivider() = Text.HYPHEN.fitToChat().style { - strikethrough = true - color = EnumChatFormatting.BLUE - } - private fun parseDuration(text: String): Duration? = try { val duration = TimeUtils.getDuration(text) if (duration <= 1.seconds) null else duration @@ -64,7 +56,7 @@ object ReminderManager { val text: MutableList = mutableListOf() - text.add(createDivider()) + text.add(Text.createDivider()) text.add( Text.join( @@ -113,7 +105,7 @@ object ReminderManager { text.add(Text.EMPTY) } - text.add(createDivider()) + text.add(Text.createDivider()) Text.join(*text.toTypedArray(), separator = Text.NEWLINE).send(REMINDERS_LIST_ID) } @@ -183,7 +175,7 @@ object ReminderManager { } private fun help() { - createDivider().send() + Text.createDivider().send() "§6SkyHanni Reminder Commands:".asComponent().send() "§e/shremind