diff options
Diffstat (limited to 'src/main/java/de/hysky')
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 |