aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/de/hysky
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/de/hysky')
-rw-r--r--src/main/java/de/hysky/skyblocker/SkyblockerMod.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/config/ConfigUtils.java21
-rw-r--r--src/main/java/de/hysky/skyblocker/config/ImageRepoLoader.java144
-rw-r--r--src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java5
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java8
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/CroesusHelper.java8
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/CroesusProfit.java267
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/CreeperBeams.java5
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java22
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java17
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/FileUtils.java36
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/Http.java24
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java20
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java2
15 files changed, 541 insertions, 45 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
index 39e14e1b..ebb85b36 100644
--- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
+++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
@@ -2,6 +2,8 @@ package de.hysky.skyblocker;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
+
+import de.hysky.skyblocker.config.ImageRepoLoader;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.debug.Debug;
import de.hysky.skyblocker.skyblock.*;
@@ -95,6 +97,7 @@ public class SkyblockerMod implements ClientModInitializer {
SkyblockerConfigManager.init();
Tips.init();
NEURepoManager.init();
+ ImageRepoLoader.init();
ItemRepository.init();
PlayerHeadHashCache.init();
HotbarSlotLock.init();
diff --git a/src/main/java/de/hysky/skyblocker/config/ConfigUtils.java b/src/main/java/de/hysky/skyblocker/config/ConfigUtils.java
index 8b0f27a7..781f7f15 100644
--- a/src/main/java/de/hysky/skyblocker/config/ConfigUtils.java
+++ b/src/main/java/de/hysky/skyblocker/config/ConfigUtils.java
@@ -1,16 +1,26 @@
package de.hysky.skyblocker.config;
import dev.isxander.yacl3.api.Option;
+import dev.isxander.yacl3.api.OptionDescription;
import dev.isxander.yacl3.api.controller.*;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
+import net.minecraft.util.Identifier;
+
+import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.Nullable;
+
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.utils.FileUtils;
+import java.nio.file.Path;
import java.util.function.Function;
public class ConfigUtils {
public static final ValueFormatter<Formatting> FORMATTING_FORMATTER = formatting -> Text.literal(StringUtils.capitalize(formatting.getName().replaceAll("_", " ")));
public static final ValueFormatter<Float> FLOAT_TWO_FORMATTER = value -> Text.literal(String.format("%,.2f", value).replaceAll("[\u00a0\u202F]", " "));
+ private static final Path IMAGE_DIRECTORY = ImageRepoLoader.REPO_DIRECTORY.resolve("Skyblocker-Assets-images");
public static BooleanControllerBuilder createBooleanController(Option<Boolean> opt) {
return BooleanControllerBuilder.create(opt).yesNoFormatter().coloured(true);
@@ -34,4 +44,15 @@ public class ConfigUtils {
public static <E extends Enum<E>> Function<Option<E>, ControllerBuilder<E>> getEnumDropdownControllerFactory(ValueFormatter<E> formatter) {
return opt -> EnumDropdownControllerBuilder.create(opt).formatValue(formatter);
}
+
+ /**
+ * Creates an {@link OptionDescription} with an image and text.
+ */
+ @SafeVarargs
+ public static OptionDescription withImage(Path imagePath, @Nullable Text... texts) {
+ return OptionDescription.createBuilder()
+ .text(ArrayUtils.isNotEmpty(texts) ? texts : new Text[] {})
+ .image(IMAGE_DIRECTORY.resolve(imagePath), new Identifier(SkyblockerMod.NAMESPACE, "config_image_" + FileUtils.normalizePath(imagePath)))
+ .build();
+ }
}
diff --git a/src/main/java/de/hysky/skyblocker/config/ImageRepoLoader.java b/src/main/java/de/hysky/skyblocker/config/ImageRepoLoader.java
new file mode 100644
index 00000000..584b79e7
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/config/ImageRepoLoader.java
@@ -0,0 +1,144 @@
+package de.hysky.skyblocker.config;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.concurrent.CompletableFuture;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import org.slf4j.Logger;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.mojang.logging.LogUtils;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.JsonOps;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.utils.FileUtils;
+import de.hysky.skyblocker.utils.Http;
+
+public class ImageRepoLoader {
+ private static final Logger LOGGER = LogUtils.getLogger();
+ static final Path REPO_DIRECTORY = SkyblockerMod.CONFIG_DIR.resolve("image-repo");
+ private static final String BRANCH_INFO = "https://api.github.com/repos/SkyblockerMod/Skyblocker-Assets/branches/images";
+ private static final String REPO_DOWNLOAD = "https://github.com/SkyblockerMod/Skyblocker-Assets/archive/refs/heads/images.zip";
+ private static final String PLACEHOLDER_HASH = "None!";
+
+ public static void init() {
+ update(0);
+ }
+
+ /**
+ * Attempts to update/load the image repository, if any errors are encountered it will try 3 times.
+ */
+ private static void update(int retries) {
+ CompletableFuture.runAsync(() -> {
+ if (retries < 3) {
+ try {
+ long start = System.currentTimeMillis();
+ //Retrieve the saved commit hash
+ String savedCommitHash = checkSavedCommitData();
+
+ //Fetch the latest commit data
+ JsonObject response = JsonParser.parseString(Http.sendGetRequest(BRANCH_INFO)).getAsJsonObject();
+ String latestCommitHash = response.getAsJsonObject("commit").get("sha").getAsString();
+
+ //Download the repository if there was a new commit
+ if (!savedCommitHash.equals(latestCommitHash)) {
+ InputStream in = Http.downloadContent(REPO_DOWNLOAD);
+
+ //Delete all directories to clear potentially now unused/old files
+ //TODO change this to only delete periodically?
+ if (Files.exists(REPO_DIRECTORY)) deleteDirectories();
+
+ try (ZipInputStream zis = new ZipInputStream(in)) {
+ ZipEntry entry;
+
+ while ((entry = zis.getNextEntry()) != null) {
+ Path outputFile = REPO_DIRECTORY.resolve(entry.getName());
+
+ if (entry.isDirectory()) {
+ Files.createDirectories(outputFile);
+ } else {
+ Files.createDirectories(outputFile.getParent());
+ Files.copy(zis, outputFile, StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+ }
+
+ writeCommitData(latestCommitHash);
+
+ long end = System.currentTimeMillis();
+ LOGGER.info("[Skyblocker] Successfully updated the Image Respository in {} ms! {} → {}", end - start, savedCommitHash, latestCommitHash);
+ } else {
+ LOGGER.info("[Skyblocker] The Image Respository is up to date!");
+ }
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker] Error while downloading image repo on attempt {}!", retries, e);
+ update(retries + 1);
+ }
+ }
+ });
+ }
+
+ /**
+ * @return The stored hash or the {@link #PLACEHOLDER_HASH}.
+ */
+ private static String checkSavedCommitData() throws IOException {
+ Path file = REPO_DIRECTORY.resolve("image_repo.json");
+
+ if (Files.exists(file)) {
+ try (BufferedReader reader = Files.newBufferedReader(file)) {
+ CommitData commitData = CommitData.CODEC.parse(JsonOps.INSTANCE, JsonParser.parseReader(reader)).result().orElseThrow();
+
+ return commitData.commit();
+ }
+ }
+
+ return PLACEHOLDER_HASH;
+ }
+
+ /**
+ * Writes the {@code newHash} into a file to be used to check for repo updates.
+ *
+ * @implNote Checking whether the directory exists or not isn't needed as this is called after all files are written successfully.
+ */
+ private static void writeCommitData(String newHash) throws IOException {
+ Path file = REPO_DIRECTORY.resolve("image_repo.json");
+ CommitData commitData = new CommitData(newHash, System.currentTimeMillis());
+
+ try (BufferedWriter writer = Files.newBufferedWriter(file)) {
+ SkyblockerMod.GSON.toJson(CommitData.CODEC.encodeStart(JsonOps.INSTANCE, commitData).result().orElseThrow(), writer);
+ }
+ }
+
+ /**
+ * Deletes all directories (not files) inside of the {@link #REPO_DIRECTORY}
+ * @throws IOException
+ */
+ private static void deleteDirectories() throws IOException {
+ Files.list(REPO_DIRECTORY)
+ .filter(Files::isDirectory)
+ .forEach(dir -> {
+ try {
+ FileUtils.recursiveDelete(dir);
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker] Encountered an exception while deleting a directory! Path: {}", dir.toAbsolutePath(), e);
+ }
+ });
+ }
+
+ record CommitData(String commit, long lastUpdated) {
+ static final Codec<CommitData> CODEC = RecordCodecBuilder.create(instance -> instance.group(
+ Codec.STRING.fieldOf("commit").forGetter(CommitData::commit),
+ Codec.LONG.fieldOf("lastUpdated").forGetter(CommitData::lastUpdated))
+ .apply(instance, CommitData::new));
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
index e60aa03b..e006886c 100644
--- a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
+++ b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
@@ -866,6 +866,9 @@ public class SkyblockerConfig {
public boolean includeEssence = true;
@SerialEntry
+ public boolean croesusProfit = true;
+
+ @SerialEntry
public int neutralThreshold = 1000;
@SerialEntry
@@ -1034,7 +1037,7 @@ public class SkyblockerConfig {
public boolean noArrowPoisonWarning = true;
@SerialEntry
- public int arrowPoisonThreshold = 16;
+ public int arrowPoisonThreshold = 32;
}
public static class Rift {
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java
index 3ebd5d76..9d6e1beb 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java
@@ -279,6 +279,14 @@ public class DungeonsCategory {
newValue -> config.locations.dungeons.dungeonChestProfit.includeEssence = newValue)
.controller(ConfigUtils::createBooleanController)
.build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonChestProfit.croesusProfit"))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonChestProfit.croesusProfit.@Tooltip")))
+ .binding(defaults.locations.dungeons.dungeonChestProfit.croesusProfit,
+ () -> config.locations.dungeons.dungeonChestProfit.croesusProfit,
+ newValue -> config.locations.dungeons.dungeonChestProfit.croesusProfit = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
.option(Option.<Integer>createBuilder()
.name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonChestProfit.neutralThreshold"))
.description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonChestProfit.neutralThreshold.@Tooltip")))
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/CroesusHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/CroesusHelper.java
index e95b47c9..01422770 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/CroesusHelper.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/CroesusHelper.java
@@ -25,8 +25,12 @@ public class CroesusHelper extends ContainerSolver {
List<ColorHighlight> highlights = new ArrayList<>();
for (Map.Entry<Integer, ItemStack> entry : slots.entrySet()) {
ItemStack stack = entry.getValue();
- if (stack != null && stack.getNbt() != null && (stack.getNbt().toString().contains("No more Chests to open!") || stack.getNbt().toString().contains("Opened Chest:"))) {
- highlights.add(ColorHighlight.gray(entry.getKey()));
+ if (stack != null && stack.getNbt() != null) {
+ if (stack.getNbt().toString().contains("Opened Chest:")) {
+ highlights.add(ColorHighlight.gray(entry.getKey()));
+ } else if (stack.getNbt().toString().contains("No more Chests to open!")) {
+ highlights.add(ColorHighlight.red(entry.getKey()));
+ }
}
}
return highlights;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/CroesusProfit.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/CroesusProfit.java
new file mode 100644
index 00000000..ca166915
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/CroesusProfit.java
@@ -0,0 +1,267 @@
+package de.hysky.skyblocker.skyblock.dungeon;
+
+import com.google.gson.JsonObject;
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType;
+import de.hysky.skyblocker.utils.ItemUtils;
+import de.hysky.skyblocker.utils.render.gui.ColorHighlight;
+import de.hysky.skyblocker.utils.render.gui.ContainerSolver;
+import net.minecraft.item.ItemStack;
+import net.minecraft.text.Text;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class CroesusProfit extends ContainerSolver {
+ private static final Pattern ESSENCE_PATTERN = Pattern.compile("(?<type>[A-Za-z]+) Essence x(?<amount>[0-9]+)");
+ public CroesusProfit() {
+ super(".*Catacombs - Floor.*");
+ }
+
+ @Override
+ protected boolean isEnabled() {
+ return SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.croesusProfit;
+ }
+
+ @Override
+ protected List<ColorHighlight> getColors(String[] groups, Map<Integer, ItemStack> slots) {
+ List<ColorHighlight> highlights = new ArrayList<>();
+ ItemStack bestChest = null, secondBestChest = null;
+ long bestValue = 0, secondBestValue = 0; // If negative value of chest - it is out of the question
+ long dungeonKeyPriceData = getItemPrice("DUNGEON_CHEST_KEY") * 2; // lesser ones don't worth the hassle
+
+ for (Map.Entry<Integer, ItemStack> entry : slots.entrySet()) {
+ ItemStack stack = entry.getValue();
+ if (stack != null && stack.getNbt() != null && stack.getName().toString().contains("Chest")) {
+ long value = valueChest(stack);
+ if (value > bestValue) {
+ secondBestChest = bestChest;
+ secondBestValue = bestValue;
+ bestChest = stack;
+ bestValue = value;
+ } else if (value > secondBestValue) {
+ secondBestChest = stack;
+ secondBestValue = value;
+ }
+ }
+ }
+
+ for (Map.Entry<Integer, ItemStack> entry : slots.entrySet()) {
+ ItemStack stack = entry.getValue();
+ if (stack != null && stack.getNbt() != null) {
+ if (stack.equals(bestChest)) {
+ highlights.add(ColorHighlight.green(entry.getKey()));
+ } else if (stack.equals(secondBestChest) && secondBestValue > dungeonKeyPriceData) {
+ highlights.add(ColorHighlight.yellow(entry.getKey()));
+ }
+ }
+ }
+ return highlights;
+ }
+
+
+ private long valueChest(@NotNull ItemStack chest) {
+ long chestValue = 0;
+ int chestPrice = 0;
+ List<String> chestItems = new ArrayList<>();
+
+ boolean processingContents = false;
+ for (Text line : ItemUtils.getNbtTooltips(chest)) {
+ String lineString = line.getString();
+ if (lineString.contains("Contents")) {
+ processingContents = true;
+ continue;
+ } else if (lineString.isEmpty()) {
+ processingContents = false;
+ } else if (lineString.contains("Coins") && !processingContents) {
+ chestPrice = Integer.parseInt(lineString.replaceAll(",", "").replaceAll("\\D", ""));
+ }
+
+ if (processingContents) {
+ if (lineString.contains("Essence")) {
+ Matcher matcher = ESSENCE_PATTERN.matcher(lineString);
+ if (matcher.matches()) { // add to chest value result of multiplying price of essence on it's amount
+ chestValue += getItemPrice(("ESSENCE_" + matcher.group("type")).toUpperCase()) * Integer.parseInt(matcher.group("amount"));
+ }
+ } else {
+ if (lineString.contains("Spirit")) { // TODO: make code like this to detect recombed gear (it can drop with 1% chance, according to wiki, tho I never saw any?)
+ chestValue += line.getStyle().toString().contains("color=dark_purple") ? getItemPrice("Spirit Epic") : getItemPrice(lineString);
+ } else {
+ chestItems.add(lineString);
+ }
+ }
+ }
+ }
+ for (String item : chestItems){
+ chestValue += getItemPrice(item);
+ }
+ return chestValue-chestPrice;
+ }
+
+
+ private long getItemPrice(String itemDisplayName) {
+ JsonObject bazaarPrices = TooltipInfoType.BAZAAR.getData();
+ JsonObject lbinPrices = TooltipInfoType.LOWEST_BINS.getData();
+ long itemValue = 0;
+ String id = dungeonDropsNameToId.get(itemDisplayName);
+
+ if (bazaarPrices == null || lbinPrices == null) return 0;
+
+ if (bazaarPrices.has(id)) {
+ JsonObject item = bazaarPrices.get(id).getAsJsonObject();
+ boolean isPriceNull = item.get("sellPrice").isJsonNull();
+ return (isPriceNull ? 0L : item.get("sellPrice").getAsLong());
+ } else if (lbinPrices.has(id)) {
+ return lbinPrices.get(id).getAsLong();
+ }
+ return itemValue;
+ }
+
+
+ // I did a thing :(
+ final Map<String, String> dungeonDropsNameToId = new HashMap<>() {{
+ put("Enchanted Book (Ultimate Jerry I)", "ENCHANTMENT_ULTIMATE_JERRY_1"); // ultimate books start
+ put("Enchanted Book (Ultimate Jerry II)", "ENCHANTMENT_ULTIMATE_JERRY_2");
+ put("Enchanted Book (Ultimate Jerry III)", "ENCHANTMENT_ULTIMATE_JERRY_3");
+ put("Enchanted Book (Bank I)", "ENCHANTMENT_ULTIMATE_BANK_1");
+ put("Enchanted Book (Bank II)", "ENCHANTMENT_ULTIMATE_BANK_2");
+ put("Enchanted Book (Bank III)", "ENCHANTMENT_ULTIMATE_BANK_3");
+ put("Enchanted Book (Combo I)", "ENCHANTMENT_ULTIMATE_COMBO_1");
+ put("Enchanted Book (Combo II)", "ENCHANTMENT_ULTIMATE_COMBO_2");
+ put("Enchanted Book (No Pain No Gain I)", "ENCHANTMENT_ULTIMATE_NO_PAIN_NO_GAIN_1");
+ put("Enchanted Book (No Pain No Gain II)", "ENCHANTMENT_ULTIMATE_NO_PAIN_NO_GAIN_2");
+ put("Enchanted Book (Ultimate Wise I)", "ENCHANTMENT_ULTIMATE_WISE_1");
+ put("Enchanted Book (Ultimate Wise II)", "ENCHANTMENT_ULTIMATE_WISE_2");
+ put("Enchanted Book (Wisdom I)", "ENCHANTMENT_ULTIMATE_WISDOM_1");
+ put("Enchanted Book (Wisdom II)", "ENCHANTMENT_ULTIMATE_WISDOM_2");
+ put("Enchanted Book (Last Stand I)", "ENCHANTMENT_ULTIMATE_LAST_STAND_1");
+ put("Enchanted Book (Last Stand II)", "ENCHANTMENT_ULTIMATE_LAST_STAND_2");
+ put("Enchanted Book (Rend I)", "ENCHANTMENT_ULTIMATE_REND_1");
+ put("Enchanted Book (Rend II)", "ENCHANTMENT_ULTIMATE_REND_2");
+ put("Enchanted Book (Legion I)", "ENCHANTMENT_ULTIMATE_LEGION_1");
+ put("Enchanted Book (Swarm I)", "ENCHANTMENT_ULTIMATE_SWARM_1");
+ put("Enchanted Book (One For All I)", "ENCHANTMENT_ULTIMATE_ONE_FOR_ALL_1");
+ put("Enchanted Book (Soul Eater I)", "ENCHANTMENT_ULTIMATE_SOUL_EATER_1"); // ultimate books end
+ put("Enchanted Book (Infinite Quiver VI)", "ENCHANTMENT_INFINITE_QUIVER_6"); // enchanted books start
+ put("Enchanted Book (Infinite Quiver VII)", "ENCHANTMENT_INFINITE_QUIVER_7");
+ put("Enchanted Book (Feather Falling VI)", "ENCHANTMENT_FEATHER_FALLING_6");
+ put("Enchanted Book (Feather Falling VII)", "ENCHANTMENT_FEATHER_FALLING_7");
+ put("Enchanted Book (Rejuvenate I)", "ENCHANTMENT_REJUVENATE_1");
+ put("Enchanted Book (Rejuvenate II)", "ENCHANTMENT_REJUVENATE_2");
+ put("Enchanted Book (Rejuvenate III)", "ENCHANTMENT_REJUVENATE_3");
+ put("Enchanted Book (Overload)", "ENCHANTMENT_OVERLOAD_1");
+ put("Enchanted Book (Lethality VI)", "ENCHANTMENT_LETHALITY_6");
+ put("Enchanted Book (Thunderlord VII)", "ENCHANTMENT_THUNDERLORD_7"); // enchanted books end
+
+ put("Hot Potato Book", "HOT_POTATO_BOOK"); // HPB, FPB, Recomb (universal drops)
+ put("Fuming Potato Book", "FUMING_POTATO_BOOK");
+ put("Recombobulator 3000", "RECOMBOBULATOR_3000");
+ put("Necromancer's Brooch", "NECROMANCER_BROOCH");
+ put("ESSENCE_WITHER","ESSENCE_WITHER"); // Essences. Really stupid way of doing this
+ put("ESSENCE_UNDEAD", "ESSENCE_UNDEAD");
+ put("ESSENCE_DRAGON", "ESSENCE_DRAGON");
+ put("ESSENCE_SPIDER", "ESSENCE_SPIDER");
+ put("ESSENCE_ICE", "ESSENCE_ICE");
+ put("ESSENCE_DIAMOND", "ESSENCE_DIAMOND");
+ put("ESSENCE_GOLD", "ESSENCE_GOLD");
+ put("ESSENCE_CRIMSON", "ESSENCE_CRIMSON");
+ put("DUNGEON_CHEST_KEY", "DUNGEON_CHEST_KEY");
+
+ put("Bonzo's Staff", "BONZO_STAFF"); // F1 M1
+ put("Master Skull - Tier 1", "MASTER_SKULL_TIER_1");
+ put("Bonzo's Mask", "BONZO_MASK");
+ put("Balloon Snake", "BALLOON_SNAKE");
+ put("Red Nose", "RED_NOSE");
+
+ put("Red Scarf", "RED_SCARF"); // F2 M2
+ put("Adaptive Blade", "STONE_BLADE");
+ put("Master Skull - Tier 2", "MASTER_SKULL_TIER_2");
+ put("Adaptive Belt", "ADAPTIVE_BELT");
+ put("Scarf's Studies", "SCARF_STUDIES");
+
+ put("First Master Star", "FIRST_MASTER_STAR"); // F3 M3
+ put("Adaptive Helmet", "ADAPTIVE_HELMET");
+ put("Adaptive Chestplate", "ADAPTIVE_CHESTPLATE");
+ put("Adaptive Leggings", "ADAPTIVE_LEGGINGS");
+ put("Adaptive Boots", "ADAPTIVE_BOOTS");
+ put("Master Skull - Tier 3", "MASTER_SKULL_TIER_3");
+ put("Suspicious Vial", "SUSPICIOUS_VIAL");
+
+ put("Spirit Sword", "SPIRIT_SWORD"); // F4 M4
+ put("Spirit Shortbow", "ITEM_SPIRIT_BOW");
+ put("Spirit Boots", "THORNS_BOOTS");
+ put("Spirit", "LVL_1_LEGENDARY_SPIRIT"); // Spirit pet (Legendary)
+ put("Spirit Epic", "LVL_1_EPIC_SPIRIT");
+
+ put("Second Master Star", "SECOND_MASTER_STAR");
+ put("Spirit Wing", "SPIRIT_WING");
+ put("Spirit Bone", "SPIRIT_BONE");
+ put("Spirit Stone", "SPIRIT_DECOY");
+
+ put("Shadow Fury", "SHADOW_FURY"); // F5 M5
+ put("Last Breath", "LAST_BREATH");
+ put("Third Master Star", "THIRD_MASTER_STAR");
+ put("Warped Stone", "AOTE_STONE");
+ put("Livid Dagger", "LIVID_DAGGER");
+ put("Shadow Assassin Helmet", "SHADOW_ASSASSIN_HELMET");
+ put("Shadow Assassin Chestplate", "SHADOW_ASSASSIN_CHESTPLATE");
+ put("Shadow Assassin Leggings", "SHADOW_ASSASSIN_LEGGINGS");
+ put("Shadow Assassin Boots", "SHADOW_ASSASSIN_BOOTS");
+ put("Shadow Assassin Cloak", "SHADOW_ASSASSIN_CLOAK");
+ put("Master Skull - Tier 4", "MASTER_SKULL_TIER_4");
+ put("Dark Orb", "DARK_ORB");
+
+ put("Precursor Eye", "PRECURSOR_EYE"); // F6 M6
+ put("Giant's Sword", "GIANTS_SWORD");
+ put("Necromancer Lord Helmet", "NECROMANCER_LORD_HELMET");
+ put("Necromancer Lord Chestplate", "NECROMANCER_LORD_CHESTPLATE");
+ put("Necromancer Lord Leggings", "NECROMANCER_LORD_LEGGINGS");
+ put("Necromancer Lord Boots", "NECROMANCER_LORD_BOOTS");
+ put("Fourth Master Star", "FOURTH_MASTER_STAR");
+ put("Summoning Ring", "SUMMONING_RING");
+ put("Fel Skull", "FEL_SKULL");
+ put("Necromancer Sword", "NECROMANCER_SWORD");
+ put("Soulweaver Gloves", "SOULWEAVER_GLOVES");
+ put("Sadan's Brooch", "SADAN_BROOCH");
+ put("Giant Tooth", "GIANT_TOOTH");
+
+ put("Precursor Gear", "PRECURSOR_GEAR"); // F7 M7
+ put("Necron Dye", "DYE_NECRON");
+ put("Storm the Fish", "STORM_THE_FISH");
+ put("Maxor the Fish", "MAXOR_THE_FISH");
+ put("Goldor the Fish", "GOLDOR_THE_FISH");
+ put("Dark Claymore", "DARK_CLAYMORE");
+ put("Necron's Handle", "NECRON_HANDLE");
+ put("Master Skull - Tier 5", "MASTER_SKULL_TIER_5");
+ put("Shadow Warp", "SHADOW_WARP_SCROLL");
+ put("Wither Shield", "WITHER_SHIELD_SCROLL");
+ put("Implosion", "IMPLOSION_SCROLL");
+ put("Fifth Master Star", "FIFTH_MASTER_STAR");
+ put("Auto Recombobulator", "AUTO_RECOMBOBULATOR");
+ put("Wither Helmet", "WITHER_HELMET");
+ put("Wither Chestplate", "WITHER_CHESTPLATE");
+ put("Wither Leggings", "WITHER_LEGGINGS");
+ put("Wither Boots", "WITHER_BOOTS");
+ put("Wither Catalyst", "WITHER_CATALYST");
+ put("Wither Cloak Sword", "WITHER_CLOAK");
+ put("Wither Blood", "WITHER_BLOOD");
+
+ put("Shiny Wither Helmet", "SHINY_WITHER_HELMET"); // M7 shiny drops
+ put("Shiny Wither Chestplate", "SHINY_WITHER_CHESTPLATE");
+ put("Shiny Wither Leggings", "SHINY_WITHER_LEGGINGS");
+ put("Shiny Wither Boots", "SHINY_WITHER_BOOTS");
+ put("Shiny Necron's Handle", "SHINY_NECRON_HANDLE"); // cool thing
+
+ put("Dungeon Disc", "DUNGEON_DISC_1");
+ put("Clown Disc", "DUNGEON_DISC_2");
+ put("Watcher Disc", "DUNGEON_DISC_3");
+ put("Old Disc", "DUNGEON_DISC_4");
+ put("Necron Disc", "DUNGEON_DISC_5");
+ }};
+}
+
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/CreeperBeams.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/CreeperBeams.java
index 73cd99c8..419ce85c 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/CreeperBeams.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/CreeperBeams.java
@@ -32,6 +32,7 @@ public class CreeperBeams extends DungeonPuzzle {
DyeColor.LIME.getColorComponents(),
DyeColor.YELLOW.getColorComponents(),
DyeColor.MAGENTA.getColorComponents(),
+ DyeColor.PINK.getColorComponents(),
};
private static final float[] GREEN_COLOR_COMPONENTS = DyeColor.GREEN.getColorComponents();
@@ -155,7 +156,7 @@ public class CreeperBeams extends DungeonPuzzle {
ArrayList<Beam> result = new ArrayList<>();
allLines.sort(Comparator.comparingDouble(ObjectDoublePair::rightDouble));
- while (result.size() < 4 && !allLines.isEmpty()) {
+ while (result.size() < 5 && !allLines.isEmpty()) {
Beam solution = allLines.get(0).left();
result.add(solution);
@@ -165,7 +166,7 @@ public class CreeperBeams extends DungeonPuzzle {
allLines.removeIf(beam -> solution.containsComponentOf(beam.left()));
}
- if (result.size() != 4) {
+ if (result.size() < 5) {
LOGGER.error("Not enough solutions found. This is bad...");
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java
index df8d1e70..bde8b3ea 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java
@@ -21,6 +21,7 @@ import net.minecraft.screen.slot.Slot;
import net.minecraft.text.Text;
import net.minecraft.text.Text.Serialization;
import net.minecraft.util.Formatting;
+import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -140,9 +141,7 @@ public class VisitorHelper {
String itemName = itemEntry.getKey();
int amount = itemEntry.getValue();
ItemStack stack = getCachedItem(itemName);
- if (stack != null) {
- drawItemEntryWithHover(context, textRenderer, stack, amount, index, mouseX, mouseY);
- }
+ drawItemEntryWithHover(context, textRenderer, stack, itemName, amount, index, mouseX, mouseY);
return index + 1;
}
@@ -150,21 +149,26 @@ public class VisitorHelper {
String strippedName = Formatting.strip(displayName);
ItemStack cachedStack = itemCache.get(strippedName);
if (cachedStack != null) return cachedStack;
- NEUItem neuItem = NEURepoManager.NEU_REPO.getItems().getItems().values().stream()
+ Map<String, NEUItem> items = NEURepoManager.NEU_REPO.getItems().getItems();
+ if (items == null) return null;
+ ItemStack stack = items.values().stream()
.filter(i -> Formatting.strip(i.getDisplayName()).equals(strippedName))
.findFirst()
+ .map(NEUItem::getSkyblockItemId)
+ .map(ItemRepository::getItemStack)
.orElse(null);
- if (neuItem == null) return null;
- ItemStack stack = ItemRepository.getItemStack(neuItem.getSkyblockItemId());
+ if (stack == null) return null;
itemCache.put(strippedName, stack);
return stack;
}
- private static void drawItemEntryWithHover(DrawContext context, TextRenderer textRenderer, ItemStack stack, int amount, int index, int mouseX, int mousseY) {
- Text text = Serialization.fromJson(stack.getSubNbt("display").getString("Name")).append(" x" + amount);
+ private static void drawItemEntryWithHover(DrawContext context, TextRenderer textRenderer, @Nullable ItemStack stack, String itemName, int amount, int index, int mouseX, int mousseY) {
+ Text text = stack != null ? Serialization.fromJson(stack.getSubNbt("display").getString("Name")).append(" x" + amount) : Text.literal(itemName + " x" + amount);
drawTextWithOptionalUnderline(context, textRenderer, text, TEXT_START_X + TEXT_INDENT, TEXT_START_Y + (index * (LINE_SPACING + textRenderer.fontHeight)), mouseX, mousseY);
// drawItem adds 150 to the z, which puts our z at 350, above the item in the slot (250) and their text (300) and below the cursor stack (382) and their text (432)
- context.drawItem(stack, TEXT_START_X + TEXT_INDENT + 2 + textRenderer.getWidth(text), TEXT_START_Y + (index * (LINE_SPACING + textRenderer.fontHeight)) - textRenderer.fontHeight + 5);
+ if (stack != null) {
+ context.drawItem(stack, TEXT_START_X + TEXT_INDENT + 2 + textRenderer.getWidth(text), TEXT_START_Y + (index * (LINE_SPACING + textRenderer.fontHeight)) - textRenderer.fontHeight + 5);
+ }
}
private static void drawTextWithOptionalUnderline(DrawContext context, TextRenderer textRenderer, Text text, int x, int y, int mouseX, int mouseY) {
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java
index fbef1bcb..1b3f402d 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java
@@ -249,7 +249,7 @@ public class ItemTooltip {
public static void nullWarning() {
if (!sentNullWarning && client.player != null) {
- client.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.itemTooltip.nullMessage")), false);
+ LOGGER.warn(Constants.PREFIX.get().append(Text.translatable("skyblocker.itemTooltip.nullMessage")).getString());
sentNullWarning = true;
}
}
@@ -355,15 +355,19 @@ public class ItemTooltip {
// If these options is true beforehand, the client will get first data of these options while loading.
// After then, it will only fetch the data if it is on Skyblock.
- public static int minute = -1;
+ public static int minute = 0;
public static void init() {
Scheduler.INSTANCE.scheduleCyclic(() -> {
- if (!Utils.isOnSkyblock() && 0 < minute++) {
+ if (!Utils.isOnSkyblock() && 0 < minute) {
sentNullWarning = false;
return;
}
+ if (++minute % 60 == 0) {
+ sentNullWarning = false;
+ }
+
List<CompletableFuture<Void>> futureList = new ArrayList<>();
TooltipInfoType.NPC.downloadIfEnabled(futureList);
@@ -387,9 +391,10 @@ public class ItemTooltip {
TooltipInfoType.MUSEUM.downloadIfEnabled(futureList);
TooltipInfoType.COLOR.downloadIfEnabled(futureList);
- minute++;
- CompletableFuture.allOf(futureList.toArray(CompletableFuture[]::new))
- .whenComplete((unused, throwable) -> sentNullWarning = false);
+ CompletableFuture.allOf(futureList.toArray(CompletableFuture[]::new)).exceptionally(e -> {
+ LOGGER.error("Encountered unknown error while downloading tooltip data", e);
+ return null;
+ });
}, 1200, true);
}
} \ No newline at end of file
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java
index fc5c7087..d798451e 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java
@@ -15,8 +15,8 @@ import java.util.function.Predicate;
public enum TooltipInfoType implements Runnable {
NPC("https://hysky.de/api/npcprice", itemTooltip -> itemTooltip.enableNPCPrice, true),
- BAZAAR("https://hysky.de/api/bazaar", itemTooltip -> itemTooltip.enableBazaarPrice || SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.enableProfitCalculator || SkyblockerConfigManager.get().general.chestValue.enableChestValue, itemTooltip -> itemTooltip.enableBazaarPrice, false),
- LOWEST_BINS("https://hysky.de/api/auctions/lowestbins", itemTooltip -> itemTooltip.enableLowestBIN || SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.enableProfitCalculator || SkyblockerConfigManager.get().general.chestValue.enableChestValue, itemTooltip -> itemTooltip.enableLowestBIN, false),
+ BAZAAR("https://hysky.de/api/bazaar", itemTooltip -> itemTooltip.enableBazaarPrice || SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.enableProfitCalculator || SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.croesusProfit || SkyblockerConfigManager.get().general.chestValue.enableChestValue, itemTooltip -> itemTooltip.enableBazaarPrice, false),
+ LOWEST_BINS("https://hysky.de/api/auctions/lowestbins", itemTooltip -> itemTooltip.enableLowestBIN || SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.enableProfitCalculator || SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.croesusProfit || SkyblockerConfigManager.get().general.chestValue.enableChestValue, itemTooltip -> itemTooltip.enableLowestBIN, false),
ONE_DAY_AVERAGE("https://moulberry.codes/auction_averages_lbin/1day.json", itemTooltip -> itemTooltip.enableAvgBIN, false),
THREE_DAY_AVERAGE("https://moulberry.codes/auction_averages_lbin/3day.json", itemTooltip -> itemTooltip.enableAvgBIN || SkyblockerConfigManager.get().general.searchOverlay.enableAuctionHouse, itemTooltip -> itemTooltip.enableAvgBIN, false),
MOTES("https://hysky.de/api/motesprice", itemTooltip -> itemTooltip.enableMotesPrice, itemTooltip -> itemTooltip.enableMotesPrice && Utils.isInTheRift(), true),
diff --git a/src/main/java/de/hysky/skyblocker/utils/FileUtils.java b/src/main/java/de/hysky/skyblocker/utils/FileUtils.java
new file mode 100644
index 00000000..22611441
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/utils/FileUtils.java
@@ -0,0 +1,36 @@
+package de.hysky.skyblocker.utils;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.slf4j.Logger;
+
+import com.mojang.logging.LogUtils;
+
+public class FileUtils {
+ private static final Logger LOGGER = LogUtils.getLogger();
+
+ public static void recursiveDelete(Path dir) throws IOException {
+ if (Files.isDirectory(dir) && !Files.isSymbolicLink(dir)) {
+ Files.list(dir).forEach(child -> {
+ try {
+ recursiveDelete(child);
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker] Encountered an exception while deleting a file! Path: {}", child.toAbsolutePath(), e);
+ }
+ });
+ }
+
+ Files.delete(dir);
+ }
+
+ /**
+ * Replaces any characters that do not match the regex: [^a-z0-9_.-]
+ *
+ * @implNote Designed to convert a file path to an {@link net.minecraft.util.Identifier}
+ */
+ public static String normalizePath(Path path) {
+ return path.toString().toLowerCase().replaceAll("[^a-z0-9_.-]", "");
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/utils/Http.java b/src/main/java/de/hysky/skyblocker/utils/Http.java
index 58deced2..871eac78 100644
--- a/src/main/java/de/hysky/skyblocker/utils/Http.java
+++ b/src/main/java/de/hysky/skyblocker/utils/Http.java
@@ -33,10 +33,6 @@ public class Http {
.followRedirects(Redirect.NORMAL)
.build();
- public static String sendGetRequest(String url) throws IOException, InterruptedException {
- return sendCacheableGetRequest(url).content();
- }
-
private static ApiResponse sendCacheableGetRequest(String url) throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder()
.GET()
@@ -55,6 +51,26 @@ public class Http {
return new ApiResponse(body, response.statusCode(), getCacheStatuses(headers), getAge(headers));
}
+
+ public static InputStream downloadContent(String url) throws IOException, InterruptedException {
+ HttpRequest request = HttpRequest.newBuilder()
+ .GET()
+ .header("Accept", "*/*")
+ .header("Accept-Encoding", "gzip, deflate")
+ .header("User-Agent", USER_AGENT)
+ .version(Version.HTTP_2)
+ .uri(URI.create(url))
+ .build();
+
+ HttpResponse<InputStream> response = HTTP_CLIENT.send(request, BodyHandlers.ofInputStream());
+ InputStream decodedInputStream = getDecodedInputStream(response);
+
+ return decodedInputStream;
+ }
+
+ public static String sendGetRequest(String url) throws IOException, InterruptedException {
+ return sendCacheableGetRequest(url).content();
+ }
public static HttpHeaders sendHeadRequest(String url) throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder()
diff --git a/src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java b/src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java
index 870e94da..c779d666 100644
--- a/src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java
+++ b/src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java
@@ -13,13 +13,10 @@ import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.File;
-import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.CompletableFuture;
-import java.util.stream.Stream;
/**
* Initializes the NEU repo, which contains item metadata and fairy souls location data. Clones the repo if it does not exist and checks for updates. Use {@link #runAsyncAfterLoad(Runnable)} to run code after the repo is initialized.
@@ -76,7 +73,7 @@ public class NEURepoManager {
CompletableFuture.runAsync(() -> {
try {
ItemRepository.setFilesImported(false);
- recursiveDelete(NEURepoManager.LOCAL_REPO_DIR);
+ FileUtils.recursiveDelete(NEURepoManager.LOCAL_REPO_DIR);
} catch (Exception ex) {
if (MinecraftClient.getInstance().player != null)
MinecraftClient.getInstance().player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.updaterepository.failed")), false);
@@ -86,21 +83,6 @@ public class NEURepoManager {
});
}
- @SuppressWarnings("ResultOfMethodCallIgnored")
- private static void recursiveDelete(Path dir) throws IOException {
- if (Files.isDirectory(dir) && !Files.isSymbolicLink(dir)) {
- Files.list(dir).forEach(child -> {
- try {
- recursiveDelete(child);
- } catch (Exception e) {
- LOGGER.error("[Skyblocker] Encountered an exception while deleting a file! Path: {}", child.toAbsolutePath(), e);
- }
- });
- }
-
- Files.delete(dir);
- }
-
/**
* Runs the given runnable after the NEU repo is initialized.
* @param runnable the runnable to run
diff --git a/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java b/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java
index f78e5184..460f34dd 100644
--- a/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java
+++ b/src/main/java/de/hysky/skyblocker/utils/render/gui/ContainerSolverManager.java
@@ -3,6 +3,7 @@ package de.hysky.skyblocker.utils.render.gui;
import com.mojang.blaze3d.systems.RenderSystem;
import de.hysky.skyblocker.mixin.accessor.HandledScreenAccessor;
import de.hysky.skyblocker.skyblock.dungeon.CroesusHelper;
+import de.hysky.skyblocker.skyblock.dungeon.CroesusProfit;
import de.hysky.skyblocker.skyblock.dungeon.terminal.ColorTerminal;
import de.hysky.skyblocker.skyblock.dungeon.terminal.OrderTerminal;
import de.hysky.skyblocker.skyblock.dungeon.terminal.StartsWithTerminal;
@@ -40,6 +41,7 @@ public class ContainerSolverManager {
new OrderTerminal(),
new StartsWithTerminal(),
new CroesusHelper(),
+ new CroesusProfit(),
new ChronomatronSolver(),
new SuperpairsSolver(),
UltrasequencerSolver.INSTANCE