diff options
44 files changed, 1423 insertions, 449 deletions
@@ -4,7 +4,7 @@ <div align="center"> <!-- release --> <a href="https://github.com/Moulberry/NotEnoughUpdates/releases/latest" target="_blank"> - <img src="https://img.shields.io/github/v/release/Moulberry/NotEnoughUpdates-REPO?color=informational&include_prereleases&label=release&logo=github&logoColor=white" alt="release"> + <img src="https://img.shields.io/github/v/release/Moulberry/NotEnoughUpdates?color=informational&include_prereleases&label=release&logo=github&logoColor=white" alt="release"> </a> <!-- lines --> <a href="https://github.com/NotEnoughUpdates/NotEnoughUpdates/graphs/code-frequency" target="_blank"> diff --git a/src/main/java/io/github/moulberry/notenoughupdates/NEUOverlay.java b/src/main/java/io/github/moulberry/notenoughupdates/NEUOverlay.java index 7159ef89..927b5e76 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/NEUOverlay.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/NEUOverlay.java @@ -1500,7 +1500,14 @@ public class NEUOverlay extends Gui { int actualIndex = index + getSlotsXSize() * getSlotsYSize() * page; List<JsonObject> searchedItems = getSearchedItems(); if (0 <= actualIndex && actualIndex < searchedItems.size()) { - return searchedItems.get(actualIndex); + try { + return searchedItems.get(actualIndex); + } catch (IndexOutOfBoundsException e) { + System.out.println("searchedItems size: " + searchedItems.size()); + System.out.println("actualIndex: " + actualIndex); + e.printStackTrace(); + return null; + } } else { return null; } diff --git a/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java b/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java index 94c7cc2b..ac1121ee 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java @@ -97,7 +97,7 @@ import java.util.Set; guiFactory = "io.github.moulberry.notenoughupdates.core.config.MoulConfigGuiForgeInterop") public class NotEnoughUpdates { public static final String MODID = "notenoughupdates"; - public static final String VERSION = "2.1.0-REL"; + public static final String VERSION = "2.1.1-PRE"; public static final int VERSION_ID = 20101; //2.1.1 only so update notif works public static final int PRE_VERSION_ID = 0; public static final int HOTFIX_VERSION_ID = 0; diff --git a/src/main/java/io/github/moulberry/notenoughupdates/auction/APIManager.java b/src/main/java/io/github/moulberry/notenoughupdates/auction/APIManager.java index 5ec3724a..ac60ffd9 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/auction/APIManager.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/auction/APIManager.java @@ -292,7 +292,7 @@ public class APIManager { .newMoulberryRequest("lowestbin.json.gz") .gunzip() .requestJson() - .thenAccept(jsonObject -> { + .thenAcceptAsync(jsonObject -> { if (lowestBins == null) { lowestBins = new JsonObject(); } @@ -465,12 +465,12 @@ public class APIManager { }; manager.apiUtils.newMoulberryRequest("auctionLast.json.gz") - .gunzip().requestJson().thenAccept(process); + .gunzip().requestJson().thenAcceptAsync(process); manager.apiUtils .newMoulberryRequest("auction.json.gz") .gunzip().requestJson() - .thenAccept(jsonObject -> { + .thenAcceptAsync(jsonObject -> { if (jsonObject.get("success").getAsBoolean()) { long apiUpdate = (long) jsonObject.get("time").getAsFloat(); if (lastApiUpdate == apiUpdate) { @@ -683,7 +683,7 @@ public class APIManager { manager.apiUtils .newAnonymousHypixelApiRequest("skyblock/auctions") .requestJson() - .thenAccept(jsonObject -> { + .thenAcceptAsync(jsonObject -> { if (jsonObject == null) return; if (jsonObject.get("success").getAsBoolean()) { @@ -733,7 +733,7 @@ public class APIManager { manager.apiUtils .newAnonymousHypixelApiRequest("skyblock/bazaar") .requestJson() - .thenAccept(jsonObject -> { + .thenAcceptAsync(jsonObject -> { if (!jsonObject.get("success").getAsBoolean()) return; craftCost.clear(); @@ -789,7 +789,7 @@ public class APIManager { public void updateAvgPrices() { manager.apiUtils .newMoulberryRequest("auction_averages/3day.json.gz") - .gunzip().requestJson().thenAccept((jsonObject) -> { + .gunzip().requestJson().thenAcceptAsync((jsonObject) -> { craftCost.clear(); auctionPricesJson = jsonObject; lastAuctionAvgUpdate = System.currentTimeMillis(); @@ -797,7 +797,7 @@ public class APIManager { manager.apiUtils .newMoulberryRequest("auction_averages_lbin/1day.json.gz") .gunzip().requestJson() - .thenAccept((jsonObject) -> { + .thenAcceptAsync((jsonObject) -> { auctionPricesAvgLowestBinJson = jsonObject; }); } diff --git a/src/main/java/io/github/moulberry/notenoughupdates/commands/dev/DevTestCommand.java b/src/main/java/io/github/moulberry/notenoughupdates/commands/dev/DevTestCommand.java index 0d882358..1a1400ab 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/commands/dev/DevTestCommand.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/commands/dev/DevTestCommand.java @@ -33,11 +33,13 @@ import io.github.moulberry.notenoughupdates.miscfeatures.customblockzones.Locati import io.github.moulberry.notenoughupdates.miscfeatures.customblockzones.SpecialBlockZone; import io.github.moulberry.notenoughupdates.miscgui.GuiPriceGraph; import io.github.moulberry.notenoughupdates.miscgui.minionhelper.MinionHelperManager; +import io.github.moulberry.notenoughupdates.util.ApiCache; import io.github.moulberry.notenoughupdates.util.PronounDB; import io.github.moulberry.notenoughupdates.util.SBInfo; import io.github.moulberry.notenoughupdates.util.TabListUtils; import io.github.moulberry.notenoughupdates.util.Utils; import io.github.moulberry.notenoughupdates.util.hypixelapi.ProfileCollectionInfo; +import lombok.var; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiScreen; import net.minecraft.command.CommandException; @@ -94,6 +96,8 @@ public class DevTestCommand extends ClientCommandBase { }; private int devFailIndex = 0; + public static int testValue = 0; + public DevTestCommand() { super("neudevtest"); } @@ -126,6 +130,24 @@ public class DevTestCommand extends ClientCommandBase { Utils.addChatMessage(EnumChatFormatting.RED + DEV_FAIL_STRINGS[devFailIndex++]); return; } + if (args.length == 1 && args[0].equalsIgnoreCase("dumpapihistogram")) { + synchronized (ApiCache.INSTANCE) { + Utils.addChatMessage("§e[NEU] API Request Histogram"); + Utils.addChatMessage("§e[NEU] §bClass Name§e: §aCached§e/§cNonCached§e/§dTotal"); + ApiCache.INSTANCE.getHistogramTotalRequests().forEach((className, totalRequests) -> { + var nonCachedRequests = ApiCache.INSTANCE.getHistogramNonCachedRequests().getOrDefault(className, 0); + var cachedRequests = totalRequests - nonCachedRequests; + Utils.addChatMessage( + String.format( + "§e[NEU] §b%s §a%d§e/§c%d§e/§d%d", + className, + cachedRequests, + nonCachedRequests, + totalRequests + )); + }); + } + } if (args.length == 1 && args[0].equalsIgnoreCase("testprofile")) { NotEnoughUpdates.INSTANCE.manager.apiUtils.newHypixelApiRequest("skyblock/profiles") .queryArgument( @@ -254,5 +276,17 @@ public class DevTestCommand extends ClientCommandBase { MiscUtils.copyToClipboard(builder.toString()); Utils.addChatMessage("§e[NEU] Copied tablist to clipboard!"); } + if (args.length >= 1 && args[0].equalsIgnoreCase("useragent")) { + String newUserAgent = args.length == 1 ? null : String.join(" ", Arrays.copyOfRange(args, 1, args.length)); + Utils.addChatMessage("§e[NEU] Changed user agent override to: " + newUserAgent); + NotEnoughUpdates.INSTANCE.config.hidden.customUserAgent = newUserAgent; + } + if (args.length == 2 && args[0].equalsIgnoreCase("value")) { + try { + testValue = Integer.parseInt(args[1]); + } catch (NumberFormatException e) { + Utils.addChatMessage("NumberFormatException!"); + } + } } } diff --git a/src/main/java/io/github/moulberry/notenoughupdates/commands/misc/PronounsCommand.java b/src/main/java/io/github/moulberry/notenoughupdates/commands/misc/PronounsCommand.java index 5a4f1400..cf0d0c56 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/commands/misc/PronounsCommand.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/commands/misc/PronounsCommand.java @@ -88,7 +88,7 @@ public class PronounsCommand extends ClientCommandBase { "§e[NEU] Pronouns for §b" + user + " §eon §b" + platform + "§e:"), id); betterPronounChoice.render().forEach(it -> nc.printChatMessage(new ChatComponentText("§e[NEU] §a" + it))); return null; - }, MinecraftExecutor.INSTANCE); + }, MinecraftExecutor.OnThread); } } diff --git a/src/main/java/io/github/moulberry/notenoughupdates/core/config/annotations/ConfigOption.java b/src/main/java/io/github/moulberry/notenoughupdates/core/config/annotations/ConfigOption.java index 920cb326..ddd1e71f 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/core/config/annotations/ConfigOption.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/core/config/annotations/ConfigOption.java @@ -30,6 +30,7 @@ public @interface ConfigOption { String name(); String desc(); + String[] searchTags() default ""; int subcategoryId() default -1; diff --git a/src/main/java/io/github/moulberry/notenoughupdates/core/util/ArrowPagesUtils.java b/src/main/java/io/github/moulberry/notenoughupdates/core/util/ArrowPagesUtils.java index 86430804..1e70db7f 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/core/util/ArrowPagesUtils.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/core/util/ArrowPagesUtils.java @@ -173,7 +173,7 @@ public class ArrowPagesUtils { BUTTON_WIDTH, BUTTON_HEIGHT ) && - currentPage < totalPages) { + currentPage < totalPages - 1) { int newPage = currentPage + 1; pageChange.accept(MathHelper.clamp_int(newPage, 0, totalPages - 1)); Utils.playPressSound(); diff --git a/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/CapeManager.java b/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/CapeManager.java index 4a7c1939..984a7931 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/CapeManager.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/CapeManager.java @@ -150,7 +150,7 @@ public class CapeManager { NotEnoughUpdates.INSTANCE.manager.apiUtils .newMoulberryRequest("activecapes.json") .requestJson() - .thenAccept(jsonObject -> { + .thenAcceptAsync(jsonObject -> { if (jsonObject.get("success").getAsBoolean()) { lastJsonSync = jsonObject; @@ -171,7 +171,7 @@ public class CapeManager { NotEnoughUpdates.INSTANCE.manager.apiUtils .newMoulberryRequest("permscapes.json") .requestJson() - .thenAccept(jsonObject -> { + .thenAcceptAsync(jsonObject -> { if (!jsonObject.get("success").getAsBoolean()) return; permSyncTries = 0; diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CookieWarning.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CookieWarning.java index 80751371..f130a993 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CookieWarning.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CookieWarning.java @@ -31,9 +31,13 @@ public class CookieWarning { private static boolean hasNotified; private static boolean hasErrorMessage; + private static long cookieEndTime = 0; + private static boolean hasCookie = true; + private static long lastChecked = 0; public static void resetNotification() { hasNotified = false; + hasCookie = true; NotificationHandler.cancelNotification(); } @@ -41,96 +45,135 @@ public class CookieWarning { * Checks the tab list for a cookie timer, and sends a notification if the timer is within the tolerance */ public static void checkCookie() { - if (NotEnoughUpdates.INSTANCE.config.notifications.doBoosterNotif && - NotEnoughUpdates.INSTANCE.hasSkyblockScoreboard()) { - String[] lines; - try { - lines = ((AccessorGuiPlayerTabOverlay) Minecraft.getMinecraft().ingameGUI.getTabList()) - .getFooter() - .getUnformattedText() - .split("\n"); - } catch (NullPointerException e) { - return; // if the footer is null or somehow doesn't exist, stop + if (!NotEnoughUpdates.INSTANCE.config.notifications.doBoosterNotif || + !NotEnoughUpdates.INSTANCE.hasSkyblockScoreboard()) { + return; + } + String timeLine = getTimeLine(); + if (!hasCookie) { + if (!hasNotified) { + NotificationHandler.displayNotification(Lists.newArrayList( + "§cBooster Cookie Ran Out!", + "§7Your Booster Cookie expired!", + "§7", + "§7Press X on your keyboard to close this notification" + ), true, true); + hasNotified = true; } - boolean hasCookie = true; - String timeLine = null; // the line that contains the cookie timer - for (int i = 0; i < lines.length; i++) { - if (lines[i].startsWith("Cookie Buff")) { - timeLine = lines[i + 1]; // the line after the "Cookie Buff" line - } - if (lines[i].startsWith("Not active! Obtain booster cookies from the")) { - hasCookie = false; - } + return; + } + if (timeLine == null) return; + + int minutes = getMinutesRemaining(timeLine); + if (minutes < NotEnoughUpdates.INSTANCE.config.notifications.boosterCookieWarningMins && !hasNotified) { + NotificationHandler.displayNotification(Lists.newArrayList( + "§cBooster Cookie Running Low!", + "§7Your Booster Cookie will expire in " + timeLine, + "§7", + "§7Press X on your keyboard to close this notification" + ), true, true); + hasNotified = true; + } + } + + private static int getMinutesRemaining(String timeLine) { + String clean = timeLine.replaceAll("(§.)", ""); + clean = clean.replaceAll("(\\d)([smhdy])", "$1 $2"); + String[] digits = clean.split(" "); + int minutes = 0; + try { + for (int i = 0; i < digits.length; i++) { + if (i % 2 == 1) continue; + + String number = digits[i]; + String unit = digits[i + 1]; + long val = Integer.parseInt(number); + switch (unit) { + case "Years": + case "Year": + minutes += val * 525600; + break; + case "Months": + case "Month": + minutes += val * 43200; + break; + case "Days": + case "Day": + minutes += val * 1440; + break; + case "Hours": + case "Hour": + case "h": + minutes += val * 60; + break; + case "Minutes": + case "Minute": + case "m": + minutes += val; + break; + } // ignore seconds } - if (!hasCookie) { - if (!hasNotified) { - NotificationHandler.displayNotification(Lists.newArrayList( - "\u00a7cBooster Cookie Ran Out!", - "\u00a77Your Booster Cookie expired!", - "\u00a77", - "\u00a77Press X on your keyboard to close this notification" - ), true, true); - hasNotified = true; - } - return; + } catch (NumberFormatException e) { + if (!hasErrorMessage) { + e.printStackTrace(); + Utils.addChatMessage(EnumChatFormatting.RED + + "NEU ran into an issue when retrieving the Booster Cookie Timer. Check the logs for details."); + hasErrorMessage = true; } - if (timeLine != null) { - String clean = timeLine.replaceAll("(\u00a7.)", ""); - clean = clean.replaceAll("(\\d)([smhdy])", "$1 $2"); - String[] digits = clean.split(" "); - int minutes = 0; - try { - for (int i = 0; i < digits.length; i++) { - if (i % 2 == 1) continue; + hasNotified = true; + } + return minutes; + } - String number = digits[i]; - String unit = digits[i + 1]; - long val = Integer.parseInt(number); - switch (unit) { - case "Years": - case "Year": - minutes += val * 525600; - break; - case "Months": - case "Month": - minutes += val * 43200; - break; - case "Days": - case "Day": - minutes += val * 1440; - break; - case "Hours": - case "Hour": - case "h": - minutes += val * 60; - break; - case "Minutes": - case "Minute": - case "m": - minutes += val; - break; - } // ignore seconds - } - } catch (NumberFormatException e) { - if (!hasErrorMessage) { - e.printStackTrace(); - Utils.addChatMessage(EnumChatFormatting.RED + - "NEU ran into an issue when retrieving the Booster Cookie Timer. Check the logs for details."); - hasErrorMessage = true; - } - hasNotified = true; - } - if (minutes < NotEnoughUpdates.INSTANCE.config.notifications.boosterCookieWarningMins && !hasNotified) { - NotificationHandler.displayNotification(Lists.newArrayList( - "\u00a7cBooster Cookie Running Low!", - "\u00a77Your Booster Cookie will expire in " + timeLine, - "\u00a77", - "\u00a77Press X on your keyboard to close this notification" - ), true, true); - hasNotified = true; - } + private static String getTimeLine() { + String[] lines; + try { + lines = ((AccessorGuiPlayerTabOverlay) Minecraft.getMinecraft().ingameGUI.getTabList()) + .getFooter() + .getUnformattedText() + .split("\n"); + } catch (NullPointerException ignored) { + return null; + } + String timeLine = null; // the line that contains the cookie timer + for (int i = 0; i < lines.length; i++) { + if (lines[i].startsWith("Cookie Buff")) { + timeLine = lines[i + 1]; // the line after the "Cookie Buff" line + } + if (lines[i].startsWith("Not active! Obtain booster cookies from the")) { + hasCookie = false; } } + return timeLine; + } + + public static boolean hasActiveBoosterCookie() { + long cookieEndTime = getCookieEndTime(); + return cookieEndTime > System.currentTimeMillis(); + } + + private static long getCookieEndTime() { + // Only updating every 10 seconds +// if (System.currentTimeMillis() > lastChecked + 10_000) return cookieEndTime; + if (lastChecked + 3_000 > System.currentTimeMillis()) return cookieEndTime; + + String timeLine = getTimeLine(); + if (hasCookie && timeLine != null) { + int minutes = getMinutesRemaining(timeLine); + cookieEndTime = System.currentTimeMillis() + (long) minutes * 60 * 1000; + } else { + cookieEndTime = 0; + } + + lastChecked = System.currentTimeMillis(); + return cookieEndTime; + } + public static void onProfileSwitch() { + resetNotification(); + hasErrorMessage = false; + cookieEndTime = 0; + hasCookie = true; + lastChecked = 0; } } diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/ItemCooldowns.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/ItemCooldowns.java index 5651a6ed..2253ca4a 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/ItemCooldowns.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/ItemCooldowns.java @@ -182,8 +182,7 @@ public class ItemCooldowns { @SubscribeEvent public void onChatMessage(ClientChatReceivedEvent event) { - if (PICKAXE_ABILITY_ACTIVATION.matcher(event.message.getFormattedText()).matches() && - NotEnoughUpdates.INSTANCE.config.itemOverlays.pickaxeAbility && pickaxeCooldown != 0) { + if (PICKAXE_ABILITY_ACTIVATION.matcher(event.message.getFormattedText()).matches() && pickaxeCooldown != 0) { findCooldownInTooltip(Item.PICKAXES); pickaxeUseCooldownMillisRemaining = pickaxeCooldown * 1000; } @@ -260,10 +259,12 @@ public class ItemCooldowns { } // Pickaxes - if (isPickaxe(internalname) && NotEnoughUpdates.INSTANCE.config.itemOverlays.pickaxeAbility) { + if (isPickaxe(internalname)) { findCooldownInTooltip(Item.PICKAXES); - return durabilityOverride(pickaxeUseCooldownMillisRemaining, pickaxeCooldown, stack); + if (NotEnoughUpdates.INSTANCE.config.itemOverlays.pickaxeAbility) { + return durabilityOverride(pickaxeUseCooldownMillisRemaining, pickaxeCooldown, stack); + } else return -1; } // Treecapitator / Jungle Axe if (internalname.equals("TREECAPITATOR_AXE") || internalname.equals("JUNGLE_AXE")) { diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/PetInfoOverlay.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/PetInfoOverlay.java index 716fb37d..ceae782b 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/PetInfoOverlay.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/PetInfoOverlay.java @@ -473,29 +473,44 @@ public class PetInfoOverlay extends TextOverlay { } } JsonObject pets = Constants.PETS; - if (pets != null && pets.has("custom_pet_leveling") && pets.get("custom_pet_leveling").getAsJsonObject().has(pet.petType.toUpperCase()) && - pets.get("custom_pet_leveling").getAsJsonObject().get(pet.petType.toUpperCase()).getAsJsonObject().has("xp_multiplier")) { - xp *= pets.get("custom_pet_leveling").getAsJsonObject().get(pet.petType.toUpperCase()).getAsJsonObject().get("xp_multiplier").getAsFloat(); + if (pets != null && pets.has("custom_pet_leveling") && + pets.get("custom_pet_leveling").getAsJsonObject().has(pet.petType.toUpperCase()) && + pets.get("custom_pet_leveling").getAsJsonObject().get(pet.petType.toUpperCase()).getAsJsonObject().has( + "xp_multiplier")) { + xp *= pets.get("custom_pet_leveling").getAsJsonObject().get(pet.petType.toUpperCase()).getAsJsonObject().get( + "xp_multiplier").getAsFloat(); } return xp; } + private int firstPetLines = 0; + private int secondPetLines = 0; + @Override public void updateFrequent() { Pet currentPet = getCurrentPet(); if (!NotEnoughUpdates.INSTANCE.config.petOverlay.enablePetInfo || currentPet == null) { overlayStrings = null; } else { + firstPetLines = 0; + secondPetLines = 0; overlayStrings = new ArrayList<>(); overlayStrings.addAll(createStringsForPet(currentPet, false)); + firstPetLines = overlayStrings.size(); Pet currentPet2 = getCurrentPet2(); if (currentPet2 != null) { overlayStrings.add(""); + if (firstPetLines == 1) { + overlayStrings.add(""); + } overlayStrings.addAll(createStringsForPet(currentPet2, true)); + secondPetLines = overlayStrings.size() - firstPetLines - 1; + if (firstPetLines == 1) { + secondPetLines--; + } } - } } @@ -519,14 +534,20 @@ public class PetInfoOverlay extends TextOverlay { currentPet.rarity.chatFormatting + WordUtils.capitalizeFully(currentPet.petType.replace("_", " ")); - String lvlStringShort = EnumChatFormatting.AQUA + "" + roundFloat(levelXp) + "/" + - roundFloat(currentPet.petLevel.getExpRequiredForNextLevel()) - + EnumChatFormatting.YELLOW + " (" + getLevelPercent(currentPet) + "%)"; + float levelPercent = getLevelPercent(currentPet); + String lvlStringShort = null; + String lvlString = null; + + if (levelPercent != 100 || !NotEnoughUpdates.INSTANCE.config.petOverlay.hidePetLevelProgress) { + lvlStringShort = EnumChatFormatting.AQUA + "" + roundFloat(levelXp) + "/" + + roundFloat(currentPet.petLevel.getExpRequiredForNextLevel()) + + EnumChatFormatting.YELLOW + " (" + levelPercent + "%)"; - String lvlString = EnumChatFormatting.AQUA + "" + - Utils.shortNumberFormat(Math.min(levelXp, currentPet.petLevel.getExpRequiredForNextLevel()), 0) + "/" + - Utils.shortNumberFormat(currentPet.petLevel.getExpRequiredForNextLevel(), 0) - + EnumChatFormatting.YELLOW + " (" + getLevelPercent(currentPet) + "%)"; + lvlString = EnumChatFormatting.AQUA + "" + + Utils.shortNumberFormat(Math.min(levelXp, currentPet.petLevel.getExpRequiredForNextLevel()), 0) + "/" + + Utils.shortNumberFormat(currentPet.petLevel.getExpRequiredForNextLevel(), 0) + + EnumChatFormatting.YELLOW + " (" + levelPercent + "%)"; + } float xpGain; if (!secondPet) { @@ -593,6 +614,8 @@ public class PetInfoOverlay extends TextOverlay { String finalEtaMaxStr = etaMaxStr; String finalXpGainString = xpGainString; String finalPetItemStr = petItemStr; + String finalLvlString = lvlString; + String finalLvlStringShort = lvlStringShort; return new ArrayList<String>() {{ for (int index : NotEnoughUpdates.INSTANCE.config.petOverlay.petOverlayText) { switch (index) { @@ -600,10 +623,10 @@ public class PetInfoOverlay extends TextOverlay { add(petName); break; case 1: - add(lvlStringShort); + if (finalLvlStringShort != null) add(finalLvlStringShort); break; case 2: - add(lvlString); + if (finalLvlString != null) add(finalLvlString); break; case 3: add(finalXpGainString); @@ -716,6 +739,10 @@ public class PetInfoOverlay extends TextOverlay { GlStateManager.enableDepth(); GlStateManager.pushMatrix(); Utils.pushGuiScale(NotEnoughUpdates.INSTANCE.config.locationedit.guiScale); + + if (firstPetLines == 1) y -= 9; + if (firstPetLines == 2) y -= 3; + GlStateManager.translate(x - 2, y - 2, 0); GlStateManager.scale(2, 2, 1); Utils.drawItemStack(stack, 0, 0); @@ -730,12 +757,16 @@ public class PetInfoOverlay extends TextOverlay { if (petItem2 != null) { Vector2f position = getPosition(overlayWidth, overlayHeight, true); int x = (int) position.x; - int y = (int) position.y + NotEnoughUpdates.INSTANCE.config.petOverlay.petOverlayText.size() * 10; + int y = (int) position.y + (overlayStrings.size() - secondPetLines) * 10; ItemStack stack = NotEnoughUpdates.INSTANCE.manager.jsonToStack(petItem2); GlStateManager.enableDepth(); GlStateManager.pushMatrix(); Utils.pushGuiScale(NotEnoughUpdates.INSTANCE.config.locationedit.guiScale); + + if (secondPetLines == 1) y -= 9; + if (secondPetLines == 2) y -= 3; + GlStateManager.translate(x - 2, y - 2, 0); GlStateManager.scale(2, 2, 1); Utils.drawItemStack(stack, 0, 0); @@ -1114,7 +1145,7 @@ public class PetInfoOverlay extends TextOverlay { PetInfoOverlay.config.selectedPet = -1; Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText( EnumChatFormatting.RED + "[NEU] Can't find pet \u00a7" + petStringMatch + - EnumChatFormatting.RED + " try revisiting all pages of /pets.")); + EnumChatFormatting.RED + " try revisiting all pages of /pets.")); } } } else if ((chatMessage.toLowerCase().startsWith("you despawned your")) || (chatMessage.toLowerCase().contains( diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/PowerStoneStatsDisplay.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/PowerStoneStatsDisplay.java index 31dd71eb..907f0f0d 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/PowerStoneStatsDisplay.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/PowerStoneStatsDisplay.java @@ -19,10 +19,13 @@ package io.github.moulberry.notenoughupdates.miscfeatures; +import com.google.gson.JsonObject; import io.github.moulberry.notenoughupdates.NotEnoughUpdates; import io.github.moulberry.notenoughupdates.autosubscribe.NEUAutoSubscribe; import io.github.moulberry.notenoughupdates.core.util.StringUtils; +import io.github.moulberry.notenoughupdates.events.ProfileDataLoadedEvent; import io.github.moulberry.notenoughupdates.options.NEUConfig; +import io.github.moulberry.notenoughupdates.profileviewer.ProfileViewerUtils; import io.github.moulberry.notenoughupdates.util.ItemUtils; import io.github.moulberry.notenoughupdates.util.Utils; import net.minecraft.client.Minecraft; @@ -54,13 +57,25 @@ public class PowerStoneStatsDisplay { } @SubscribeEvent + public void onProfileDataLoaded(ProfileDataLoadedEvent event) { + JsonObject profileInfo = event.getProfileInfo(); + + if (profileInfo == null) return; + + JsonObject inventoryInfo = ProfileViewerUtils.readInventoryInfo(profileInfo, "talisman_bag"); + if (inventoryInfo == null) return; + + NEUConfig.HiddenProfileSpecific configProfileSpecific = NotEnoughUpdates.INSTANCE.config.getProfileSpecific(); + if (configProfileSpecific == null) return; + int powerAmount = ProfileViewerUtils.getMagicalPower(inventoryInfo, profileInfo); + configProfileSpecific.magicalPower = powerAmount; + } + + @SubscribeEvent public void onTick(TickEvent event) { + if (!NotEnoughUpdates.INSTANCE.config.tooltipTweaks.powerStoneStats) return; if (!dirty) return; - - if (!Utils.getOpenChestName().equals("SkyBlock Menu")) { - dirty = false; - return; - } + if (!Utils.getOpenChestName().equals("Your Bags")) return; EntityPlayerSP p = Minecraft.getMinecraft().thePlayer; Container openContainer = p.openContainer; @@ -70,14 +85,15 @@ public class PowerStoneStatsDisplay { String displayName = stack.getDisplayName(); if (!"§aAccessory Bag".equals(displayName)) continue; + dirty = false; for (String line : ItemUtils.getLore(stack)) { if (line.startsWith("§7Magical Power: ")) { String rawNumber = line.split("§6")[1].replace(",", ""); NEUConfig.HiddenProfileSpecific configProfileSpecific = NotEnoughUpdates.INSTANCE.config.getProfileSpecific(); if (configProfileSpecific == null) return; - configProfileSpecific.magicalPower = Integer.parseInt(rawNumber); - dirty = false; + int magicalPower = Integer.parseInt(rawNumber); + configProfileSpecific.magicalPower = magicalPower; } } } diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/updater/AutoUpdater.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/updater/AutoUpdater.java index 54fcc204..f41654e7 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/updater/AutoUpdater.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/updater/AutoUpdater.java @@ -186,7 +186,7 @@ public class AutoUpdater { int fullReleaseVersion = o.has("version_id") && o.get("version_id").isJsonPrimitive() ? o.get("version_id").getAsInt() : -1; int preReleaseVersion = - o.has("version_id") && o.get("version_id").isJsonPrimitive() ? o.get("version_id").getAsInt() : -1; + o.has("pre_version_id") && o.get("pre_version_id").isJsonPrimitive() ? o.get("pre_version_id").getAsInt() : -1; int hotfixVersion = o.has("hotfix_id") && o.get("hotfix_id").isJsonPrimitive() ? o.get("hotfix_id").getAsInt() : -1; diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/world/FrozenTreasuresHighlighter.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/world/FrozenTreasuresHighlighter.java index 2f8071a0..1a541433 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/world/FrozenTreasuresHighlighter.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/world/FrozenTreasuresHighlighter.java @@ -28,11 +28,14 @@ import net.minecraft.client.Minecraft; import net.minecraft.entity.Entity; import net.minecraft.entity.item.EntityArmorStand; import net.minecraft.init.Blocks; +import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.BlockPos; import net.minecraft.world.World; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.gameevent.TickEvent; +import java.util.ArrayList; +import java.util.Base64; import java.util.List; @NEUAutoSubscribe @@ -40,6 +43,15 @@ public class FrozenTreasuresHighlighter extends GenericBlockHighlighter { private static final FrozenTreasuresHighlighter INSTANCE = new FrozenTreasuresHighlighter(); + private static final List<String> rideablePetTextureUrls = new ArrayList<String>() {{ + // Armadillo + add("http://textures.minecraft.net/texture/c1eb6df4736ae24dd12a3d00f91e6e3aa7ade6bbefb0978afef2f0f92461018f"); + // Rock + add("http://textures.minecraft.net/texture/cb2b5d48e57577563aca31735519cb622219bc058b1f34648b67b8e71bc0fa"); + // Rat + add("http://textures.minecraft.net/texture/a8abb471db0ab78703011979dc8b40798a941f3a4dec3ec61cbeec2af8cffe8"); + }}; + public static FrozenTreasuresHighlighter getInstance() {return INSTANCE;} @Override @@ -62,10 +74,46 @@ public class FrozenTreasuresHighlighter extends GenericBlockHighlighter { World w = Minecraft.getMinecraft().theWorld; if (w == null) return; List<Entity> entities = w.getLoadedEntityList(); - for (Entity e : entities) { - if ((e instanceof EntityArmorStand) && ((EntityArmorStand) e).getCurrentArmor(3) != null) highlightedBlocks.add(e - .getPosition() - .add(0, 1, 0)); + for (Entity entity : entities) { + if ((entity instanceof EntityArmorStand) && + ((EntityArmorStand) entity).getCurrentArmor(3) != null) { + + // If an armor stand has a 'hat' with a NBTTagCompound check if it has a pet texture url + if (((EntityArmorStand) entity).getCurrentArmor(3).hasTagCompound()) { + NBTTagCompound nbtTagCompound = ((EntityArmorStand) entity).getCurrentArmor(3).getTagCompound(); + + // Get Base64 texture value from the tag compound + String textureValue = nbtTagCompound + .getCompoundTag("SkullOwner") + .getCompoundTag("Properties") + .getTagList("textures", 10) + .getCompoundTagAt(0) + .getString("Value"); + + // Decode and find texture url from the texture value + String trimmedJson = new String(Base64.getDecoder().decode(textureValue)).replace(" ", ""); + + + String textureUrl = ""; + if (trimmedJson.contains("url")) { + textureUrl = trimmedJson.substring( + trimmedJson.indexOf("url")+6, // Start of url + trimmedJson.substring( // Get the substring from the start of the url to the end of string + trimmedJson.indexOf("url")+6).indexOf("\"") // Get index of first " after start of url + + trimmedJson.indexOf("url")+6); // Add on the length of numbers up until the start of url to get correct index from overall string + } + + + // If the list of rideable pet texture urls doesn't include the found texture then it is a frozen treasure + if (!rideablePetTextureUrls.contains(textureUrl)) { + highlightedBlocks.add(entity.getPosition().add(0, 1, 0)); + } + } else { + // This is for frozen treasures which are just blocks i.e. Packed Ice, Enchanted Packed Ice etc. + // (Since I don't believe the blocks have NBTTagCompound data) + highlightedBlocks.add(entity.getPosition().add(0, 1, 0)); + } + } } } diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscgui/CalendarOverlay.java b/src/main/java/io/github/moulberry/notenoughupdates/miscgui/CalendarOverlay.java index 48d37f2f..87691631 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/miscgui/CalendarOverlay.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/miscgui/CalendarOverlay.java @@ -298,6 +298,7 @@ public class CalendarOverlay { boolean changed = false; for (int i = 0; i < 31; i++) { ItemStack item = cc.getLowerChestInventory().getStackInSlot(1 + (i % 7) + (i / 7) * 9); + if (item == null) continue; JsonArray array = new JsonArray(); if (item.getTagCompound() != null) { diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscgui/minionhelper/loaders/MinionHelperApiLoader.java b/src/main/java/io/github/moulberry/notenoughupdates/miscgui/minionhelper/loaders/MinionHelperApiLoader.java index aaa398f4..ecf02236 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/miscgui/minionhelper/loaders/MinionHelperApiLoader.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/miscgui/minionhelper/loaders/MinionHelperApiLoader.java @@ -47,7 +47,6 @@ import java.util.Map; public class MinionHelperApiLoader { private final MinionHelperManager manager; private boolean dirty = true; - private int ticks = 0; private boolean collectionApiEnabled = true; private boolean ignoreWorldSwitches = false; private boolean readyToUse = false; @@ -72,11 +71,7 @@ public class MinionHelperApiLoader { if (Minecraft.getMinecraft().thePlayer == null) return; if (!NotEnoughUpdates.INSTANCE.hasSkyblockScoreboard()) return; if (!NotEnoughUpdates.INSTANCE.config.minionHelper.gui) return; - ticks++; - - if (ticks % 20 != 0) return; - - if (dirty) { + if (dirty && "Crafted Minions".equals(Utils.getOpenChestName())) { load(); } else { if (System.currentTimeMillis() > lastLoaded + 60_000 * 3) { diff --git a/src/main/java/io/github/moulberry/notenoughupdates/options/NEUConfig.java b/src/main/java/io/github/moulberry/notenoughupdates/options/NEUConfig.java index 302b8927..4020a82a 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/options/NEUConfig.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/options/NEUConfig.java @@ -446,6 +446,8 @@ public class NEUConfig extends Config { @Expose public int compareMode = 0; @Expose + public String customUserAgent = null; + @Expose public int sortMode = 0; @Expose public ArrayList<Boolean> compareAscending = Lists.newArrayList(true, true, true); diff --git a/src/main/java/io/github/moulberry/notenoughupdates/options/customtypes/NEUDebugFlag.java b/src/main/java/io/github/moulberry/notenoughupdates/options/customtypes/NEUDebugFlag.java index 50f459c0..90ef93bb 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/options/customtypes/NEUDebugFlag.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/options/customtypes/NEUDebugFlag.java @@ -31,6 +31,7 @@ public enum NEUDebugFlag { WISHING("Wishing Compass Solver"), MAP("Dungeon Map Player Information"), SEARCH("SearchString Matches"), + API_CACHE("Api Cache"), ; private final String description; @@ -43,6 +44,10 @@ public enum NEUDebugFlag { return description; } + public void log(String message) { + NEUDebugLogger.log(this, message); + } + public boolean isSet() { return NEUDebugLogger.isFlagEnabled(this); } diff --git a/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/Misc.java b/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/Misc.java index 47e51eec..affaa68a 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/Misc.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/Misc.java @@ -311,4 +311,13 @@ public class Misc { @ConfigEditorBoolean public boolean dungeonGroupsPV = true; + @Expose + @ConfigOption( + name = "Old SkyBlock Menu", + desc = "Show old buttons in the SkyBlock Menu: Trade, Accessories, Potions, Quiver, Fishing and Sacks. " + + "§cOnly works with the booster cookie effect active." + ) + @ConfigEditorBoolean + public boolean oldSkyBlockMenu = false; + } diff --git a/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/MiscOverlays.java b/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/MiscOverlays.java index 064a9304..53361a26 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/MiscOverlays.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/MiscOverlays.java @@ -24,7 +24,6 @@ import io.github.moulberry.notenoughupdates.core.config.Position; import io.github.moulberry.notenoughupdates.core.config.annotations.ConfigAccordionId; import io.github.moulberry.notenoughupdates.core.config.annotations.ConfigEditorAccordion; import io.github.moulberry.notenoughupdates.core.config.annotations.ConfigEditorBoolean; -import io.github.moulberry.notenoughupdates.core.config.annotations.ConfigEditorColour; import io.github.moulberry.notenoughupdates.core.config.annotations.ConfigEditorDraggableList; import io.github.moulberry.notenoughupdates.core.config.annotations.ConfigEditorDropdown; import io.github.moulberry.notenoughupdates.core.config.annotations.ConfigOption; @@ -61,6 +60,15 @@ public class MiscOverlays { @Expose @ConfigOption( + name = "Todo Overlay Hide Bingo", + desc = "Hide some tasks from the todo overlay while on a bingo profile: Cookie Buff, Godpot, Heavy Pearls, Crimson Isle Quests" + ) + @ConfigEditorBoolean + @ConfigAccordionId(id = 0) + public boolean todoOverlayHideAtBingo = true; + + @Expose + @ConfigOption( name = "Todo Text", desc = "\u00a7eDrag text to change the appearance of the overlay\n" + "\u00a7rIf you want to see the time until something is available, click \"Add\" and then the respective timer" diff --git a/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/PetOverlay.java b/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/PetOverlay.java index fce1f9af..9d88e8f1 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/PetOverlay.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/PetOverlay.java @@ -22,7 +22,6 @@ package io.github.moulberry.notenoughupdates.options.seperateSections; import com.google.gson.annotations.Expose; import io.github.moulberry.notenoughupdates.core.config.Position; import io.github.moulberry.notenoughupdates.core.config.annotations.ConfigEditorBoolean; -import io.github.moulberry.notenoughupdates.core.config.annotations.ConfigEditorButton; import io.github.moulberry.notenoughupdates.core.config.annotations.ConfigEditorDraggableList; import io.github.moulberry.notenoughupdates.core.config.annotations.ConfigEditorDropdown; import io.github.moulberry.notenoughupdates.core.config.annotations.ConfigOption; @@ -131,4 +130,12 @@ public class PetOverlay { ) @ConfigEditorBoolean public boolean showKatSitting = true; + + @Expose + @ConfigOption( + name = "Hide Pet Level Progress", + desc = "Hide the pet level progress information for maxed out pets." + ) + @ConfigEditorBoolean + public boolean hidePetLevelProgress = false; } diff --git a/src/main/java/io/github/moulberry/notenoughupdates/overlays/TimersOverlay.java b/src/main/java/io/github/moulberry/notenoughupdates/overlays/TimersOverlay.java index 419c9785..90aa2984 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/overlays/TimersOverlay.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/overlays/TimersOverlay.java @@ -898,12 +898,26 @@ public class TimersOverlay extends TextTabOverlay { overlayStrings = new ArrayList<>(); for (int index : NotEnoughUpdates.INSTANCE.config.miscOverlays.todoText2) { if (map.containsKey(index)) { - overlayStrings.add(map.get(index)); + String text = map.get(index); + if (hideBecauseOfBingo(text)) continue; + overlayStrings.add(text); } } if (overlayStrings.isEmpty()) overlayStrings = null; } + private boolean hideBecauseOfBingo(String text) { + if (!SBInfo.getInstance().bingo) return false; + if (!NotEnoughUpdates.INSTANCE.config.miscOverlays.todoOverlayHideAtBingo) return false; + + if (text.contains("Cookie Buff")) return true; + if (text.contains("Godpot")) return true; + if (text.contains("Heavy Pearls")) return true; + if (text.contains("Crimson Isle Quests")) return true; + + return false; + } + public static int beforePearls = -1; public static int afterPearls = -1; public static int availablePearls = -1; diff --git a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/BasicPage.java b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/BasicPage.java index 8a48dd2c..5939e122 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/BasicPage.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/BasicPage.java @@ -313,17 +313,25 @@ public class BasicPage extends GuiProfileViewerPage { } else if (networth == -1) { networth = profile.getNetWorth(profileId); } - + int fontWidth = fr.getStringWidth("Net Worth: " + GuiProfileViewer.numberFormat.format(networth)); + int offset = (fontWidth >= 117 ? 63 + (fontWidth - 117) : 63); if (networth > 0) { - Utils.drawStringCentered( - EnumChatFormatting.GREEN + "Net Worth: " + EnumChatFormatting.GOLD + - GuiProfileViewer.numberFormat.format(networth), - fr, - guiLeft + 165, - guiTop + 38, - true, - 0 - ); + if (fontWidth >= 117) { + + fr.drawString(EnumChatFormatting.GREEN + "Net Worth: " + EnumChatFormatting.GOLD + + GuiProfileViewer.numberFormat.format(networth), guiLeft + 8, guiTop + 38 - fr.FONT_HEIGHT / 2f, 0, true); + + } else { + Utils.drawStringCentered( + EnumChatFormatting.GREEN + "Net Worth: " + EnumChatFormatting.GOLD + + GuiProfileViewer.numberFormat.format(networth), + fr, + guiLeft + 68, + guiTop + 38, + true, + 0 + ); + } if (NotEnoughUpdates.INSTANCE.manager.auctionManager.getBazaarInfo("BOOSTER_COOKIE") != null && NotEnoughUpdates.INSTANCE.manager.auctionManager.getBazaarInfo("BOOSTER_COOKIE").has("avg_buy")) { double networthInCookies = @@ -337,8 +345,8 @@ public class BasicPage extends GuiProfileViewerPage { String networthIRLMoney = GuiProfileViewer.numberFormat.format(Math.round( ((networthInCookies * 325) / 675) * 4.99)); - int fontWidth = fr.getStringWidth("Net Worth: " + GuiProfileViewer.numberFormat.format(networth)); - if (mouseX > guiLeft + 165 - fontWidth / 2 && mouseX < guiLeft + 165 + fontWidth / 2) { + + if (mouseX > guiLeft + offset - fontWidth / 2 && mouseX < guiLeft + offset + fontWidth / 2) { if (mouseY > guiTop + 32 && mouseY < guiTop + 38 + fr.FONT_HEIGHT) { getInstance().tooltipToDisplay = new ArrayList<>(); getInstance() @@ -384,14 +392,21 @@ public class BasicPage extends GuiProfileViewerPage { } } } else { - Utils.drawStringCentered( - EnumChatFormatting.GREEN + "Net Worth: " + stateStr, - fr, - guiLeft + 165, - guiTop + 38, - true, - 0 - ); + int errFontWidth = fr.getStringWidth("Net Worth: " + stateStr); + if (errFontWidth >= 117) { + fr.drawString(EnumChatFormatting.GREEN + "Net Worth: " + stateStr, + guiLeft + 8, guiTop + 38 - fr.FONT_HEIGHT / 2f, 0, true + ); + } else { + Utils.drawStringCentered( + EnumChatFormatting.GREEN + "Net Worth: " + stateStr, + fr, + guiLeft + 63, + guiTop + 38, + true, + 0 + ); + } } if (status != null) { @@ -572,7 +587,7 @@ public class BasicPage extends GuiProfileViewerPage { ); } - // sb lvlL + // sb lvl int sbLevelX = guiLeft + 162; int sbLevelY = guiTop + 90; @@ -585,8 +600,8 @@ public class BasicPage extends GuiProfileViewerPage { GlStateManager.scale(1.5f, 1.5f, 1); Utils.drawItemStack(skull, 0, 0); GlStateManager.popMatrix(); - Utils.drawStringScaled(skyblockLevelColour.toString() + (int) skyblockLevel, fr, - sbLevelX - 2, sbLevelY - 15, true, 0, 1.5f + Utils.drawStringCenteredScaled(skyblockLevelColour.toString() + (int) skyblockLevel, fr, + sbLevelX + 9, sbLevelY - 12, true, 1.5f ); float progress = (float) (skyblockLevel - (long) skyblockLevel); @@ -596,6 +611,12 @@ public class BasicPage extends GuiProfileViewerPage { sbLevelX - 30, sbLevelY + 20, true, 0, 0.9f ); + if (mouseX >= guiLeft + 128 && mouseX <= guiLeft + 216) { + if (mouseY >= guiTop + 69 && mouseY <= guiTop + 131) { + if (Mouse.isButtonDown(0)) onSecondPage = true; + } + } + if (skyblockInfo != null) { int position = 0; for (Map.Entry<String, ItemStack> entry : ProfileViewer.getSkillToSkillDisplayMap().entrySet()) { @@ -623,12 +644,6 @@ public class BasicPage extends GuiProfileViewerPage { getInstance().renderBar(x, y + 6, 80, level.level % 1); } - if (mouseX >= guiLeft + 128 && mouseX <= guiLeft + 216) { - if (mouseY >= guiTop + 69 && mouseY <= guiTop + 131) { - if (Mouse.isButtonDown(0)) onSecondPage = true; - } - } - if (mouseX > x && mouseX < x + 80) { if (mouseY > y - 4 && mouseY < y + 13) { getInstance().tooltipToDisplay = new ArrayList<>(); @@ -794,7 +809,7 @@ public class BasicPage extends GuiProfileViewerPage { EnumChatFormatting.GOLD + GuiProfileViewer.numberFormat.format(roundToNearestInt(senitherWeight.getTotalWeight().getRaw())), fr, - guiLeft + 165, + guiLeft + 63, guiTop + 18, true, 0 @@ -804,7 +819,7 @@ public class BasicPage extends GuiProfileViewerPage { "Senither Weight: " + GuiProfileViewer.numberFormat.format(roundToNearestInt(senitherWeight.getTotalWeight().getRaw())) ); - if (mouseX > guiLeft + 165 - textWidth / 2 && mouseX < guiLeft + 165 + textWidth / 2) { + if (mouseX > guiLeft + 63 - textWidth / 2 && mouseX < guiLeft + 63 + textWidth / 2) { if (mouseY > guiTop + 12 && mouseY < guiTop + 12 + fr.FONT_HEIGHT) { getInstance().tooltipToDisplay = new ArrayList<>(); getInstance() @@ -862,7 +877,7 @@ public class BasicPage extends GuiProfileViewerPage { EnumChatFormatting.GOLD + GuiProfileViewer.numberFormat.format(roundToNearestInt(lilyWeight.getTotalWeight().getRaw())), fr, - guiLeft + 165, + guiLeft + 63, guiTop + 28, true, 0 @@ -871,7 +886,7 @@ public class BasicPage extends GuiProfileViewerPage { int fontWidth = fr.getStringWidth( "Lily Weight: " + GuiProfileViewer.numberFormat.format(roundToNearestInt(lilyWeight.getTotalWeight().getRaw())) ); - if (mouseX > guiLeft + 165 - fontWidth / 2 && mouseX < guiLeft + 165 + fontWidth / 2) { + if (mouseX > guiLeft + 63 - fontWidth / 2 && mouseX < guiLeft + 63 + fontWidth / 2) { if (mouseY > guiTop + 22 && mouseY < guiTop + 22 + fr.FONT_HEIGHT) { getInstance().tooltipToDisplay = new ArrayList<>(); getInstance() diff --git a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/ProfileViewer.java b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/ProfileViewer.java index 610e4090..6f8427ae 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/ProfileViewer.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/ProfileViewer.java @@ -28,7 +28,6 @@ import io.github.moulberry.notenoughupdates.NotEnoughUpdates; import io.github.moulberry.notenoughupdates.profileviewer.bestiary.BestiaryData; import io.github.moulberry.notenoughupdates.profileviewer.weight.senither.SenitherWeight; import io.github.moulberry.notenoughupdates.util.Constants; -import io.github.moulberry.notenoughupdates.util.JsonUtils; import io.github.moulberry.notenoughupdates.util.Utils; import io.github.moulberry.notenoughupdates.util.hypixelapi.ProfileCollectionInfo; import net.minecraft.init.Blocks; @@ -36,7 +35,6 @@ import net.minecraft.init.Items; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompressedStreamTools; -import net.minecraft.nbt.JsonToNBT; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.util.EnumChatFormatting; @@ -44,25 +42,21 @@ import net.minecraft.util.EnumChatFormatting; import javax.annotation.Nullable; import java.io.ByteArrayInputStream; import java.io.IOException; -import java.util.AbstractMap; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Objects; -import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.regex.Pattern; -import java.util.stream.Collectors; public class ProfileViewer { @@ -544,6 +538,7 @@ public class ProfileViewer { manager.apiUtils .newHypixelApiRequest("player") .queryArgument("name", nameF) + .maxCacheAge(Duration.ofSeconds(30)) .requestJson() .thenAccept(jsonObject -> { if ( @@ -703,98 +698,7 @@ public class ProfileViewer { return -1; } - Map<String, Integer> accessories = JsonUtils.getJsonArrayAsStream(inventoryInfo - .get("talisman_bag") - .getAsJsonArray()).map(o -> { - try { - return JsonToNBT.getTagFromJson(o - .getAsJsonObject() - .get("nbttag") - .getAsString()); - } catch (Exception ignored) { - return null; - } - }).filter(Objects::nonNull).map(tag -> { - NBTTagList loreTagList = tag.getCompoundTag("display").getTagList("Lore", 8); - String lastElement = loreTagList.getStringTagAt(loreTagList.tagCount() - 1); - if (lastElement.contains(EnumChatFormatting.OBFUSCATED.toString())) { - lastElement = lastElement.substring(lastElement.indexOf(' ')).trim().substring(4); - } - JsonArray lastElementJsonArray = new JsonArray(); - lastElementJsonArray.add(new JsonPrimitive(lastElement)); - return new AbstractMap.SimpleEntry<>( - tag.getCompoundTag("ExtraAttributes").getString("id"), - Utils.getRarityFromLore(lastElementJsonArray) - ); - }).sorted(Comparator.comparingInt(e -> -e.getValue())).collect(Collectors.toMap( - Map.Entry::getKey, - Map.Entry::getValue, - (v1, v2) -> v1, - LinkedHashMap::new - )); - - Set<String> ignoredTalismans = new HashSet<>(); - int powerAmount = 0; - for (Map.Entry<String, Integer> entry : accessories.entrySet()) { - if (ignoredTalismans.contains(entry.getKey())) { - continue; - } - - JsonArray children = Utils - .getElementOrDefault(Constants.PARENTS, entry.getKey(), new JsonArray()) - .getAsJsonArray(); - for (JsonElement child : children) { - ignoredTalismans.add(child.getAsString()); - } - - if (entry.getKey().equals("HEGEMONY_ARTIFACT")) { - switch (entry.getValue()) { - case 4: - powerAmount += 16; - break; - case 5: - powerAmount += 22; - break; - } - } - if (entry.getKey().equals("ABICASE")) { - if (profileInfo != null && profileInfo.has("nether_island_player_data") && - profileInfo.get("nether_island_player_data").getAsJsonObject().has("abiphone") && profileInfo - .get( - "nether_island_player_data") - .getAsJsonObject() - .get("abiphone") - .getAsJsonObject() - .has("active_contacts")) { // BatChest - int contact = - profileInfo.get("nether_island_player_data").getAsJsonObject().get("abiphone").getAsJsonObject().get( - "active_contacts").getAsJsonArray().size(); - powerAmount += Math.floor(contact / 2); - } - } - switch (entry.getValue()) { - case 0: - case 6: - powerAmount += 3; - break; - case 1: - case 7: - powerAmount += 5; - break; - case 2: - powerAmount += 8; - break; - case 3: - powerAmount += 12; - break; - case 4: - powerAmount += 16; - break; - case 5: - powerAmount += 22; - break; - } - } + int powerAmount = ProfileViewerUtils.getMagicalPower(inventoryInfo, profileInfo); magicalPower.put(profileId, powerAmount); return powerAmount; } diff --git a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/ProfileViewerUtils.java b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/ProfileViewerUtils.java new file mode 100644 index 00000000..66164688 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/ProfileViewerUtils.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2023 NotEnoughUpdates contributors + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * NotEnoughUpdates is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.profileviewer; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import io.github.moulberry.notenoughupdates.NEUManager; +import io.github.moulberry.notenoughupdates.NotEnoughUpdates; +import io.github.moulberry.notenoughupdates.util.Constants; +import io.github.moulberry.notenoughupdates.util.JsonUtils; +import io.github.moulberry.notenoughupdates.util.Utils; +import net.minecraft.nbt.CompressedStreamTools; +import net.minecraft.nbt.JsonToNBT; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.util.EnumChatFormatting; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.AbstractMap; +import java.util.Base64; +import java.util.Comparator; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +public class ProfileViewerUtils { + + public static JsonObject readInventoryInfo(JsonObject profileInfo, String bagName) { + JsonObject inventoryInfo = new JsonObject(); + JsonElement element = Utils.getElement(profileInfo, bagName + ".data"); + + String bytes = Utils.getElementAsString(element, "Hz8IAAAAAAAAAD9iYD9kYD9kAAMAPwI/Gw0AAAA="); + NBTTagCompound inv_contents_nbt; + try { + inv_contents_nbt = CompressedStreamTools.readCompressed( + new ByteArrayInputStream(Base64.getDecoder().decode(bytes)) + ); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + NBTTagList items = inv_contents_nbt.getTagList("i", 10); + JsonArray contents = new JsonArray(); + NEUManager manager = NotEnoughUpdates.INSTANCE.manager; + for (int j = 0; j < items.tagCount(); j++) { + JsonObject item = manager.getJsonFromNBTEntry(items.getCompoundTagAt(j)); + contents.add(item); + } + + inventoryInfo.add(bagName, contents); + return inventoryInfo; + } + + public static int getMagicalPower(JsonObject inventoryInfo, JsonObject profileInfo) { + JsonArray talismanBag = inventoryInfo.get("talisman_bag").getAsJsonArray(); + Map<String, Integer> accessories = JsonUtils.getJsonArrayAsStream(talismanBag).map(o -> { + try { + return JsonToNBT.getTagFromJson(o.getAsJsonObject().get("nbttag").getAsString()); + } catch (Exception ignored) { + return null; + } + }).filter(Objects::nonNull).map(tag -> { + NBTTagList loreTagList = tag.getCompoundTag("display").getTagList("Lore", 8); + String lastElement = loreTagList.getStringTagAt(loreTagList.tagCount() - 1); + if (lastElement.contains(EnumChatFormatting.OBFUSCATED.toString())) { + lastElement = lastElement.substring(lastElement.indexOf(' ')).trim().substring(4); + } + JsonArray lastElementJsonArray = new JsonArray(); + lastElementJsonArray.add(new JsonPrimitive(lastElement)); + return new AbstractMap.SimpleEntry<>(tag.getCompoundTag("ExtraAttributes").getString("id"), + Utils.getRarityFromLore(lastElementJsonArray) + ); + }).sorted(Comparator.comparingInt(e -> -e.getValue())).collect(Collectors.toMap( + Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v1, LinkedHashMap::new + )); + + Set<String> ignoredTalismans = new HashSet<>(); + int powerAmount = 0; + for (Map.Entry<String, Integer> entry : accessories.entrySet()) { + if (ignoredTalismans.contains(entry.getKey())) continue; + + JsonArray children = Utils + .getElementOrDefault(Constants.PARENTS, entry.getKey(), new JsonArray()) + .getAsJsonArray(); + for (JsonElement child : children) { + ignoredTalismans.add(child.getAsString()); + } + + if (entry.getKey().equals("HEGEMONY_ARTIFACT")) { + switch (entry.getValue()) { + case 4: + powerAmount += 16; + break; + case 5: + powerAmount += 22; + break; + } + } + if (entry.getKey().equals("ABICASE")) { + if (profileInfo != null && profileInfo.has("nether_island_player_data")) { + JsonObject data = profileInfo.get("nether_island_player_data").getAsJsonObject(); + if (data.has("abiphone") && data.get("abiphone").getAsJsonObject().has("active_contacts")) { // BatChest + int contact = data.get("abiphone").getAsJsonObject().get("active_contacts").getAsJsonArray().size(); + powerAmount += Math.floor(contact / 2); + } + } + } + switch (entry.getValue()) { + case 0: + case 6: + powerAmount += 3; + break; + case 1: + case 7: + powerAmount += 5; + break; + case 2: + powerAmount += 8; + break; + case 3: + powerAmount += 12; + break; + case 4: + powerAmount += 16; + break; + case 5: + powerAmount += 22; + break; + } + } + return powerAmount; + } +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/LevelPage.java b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/LevelPage.java index 16abf251..770b295a 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/LevelPage.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/LevelPage.java @@ -26,6 +26,7 @@ import io.github.moulberry.notenoughupdates.profileviewer.ProfileViewer; import io.github.moulberry.notenoughupdates.profileviewer.level.task.CoreTaskLevel; import io.github.moulberry.notenoughupdates.profileviewer.level.task.DungeonTaskLevel; import io.github.moulberry.notenoughupdates.profileviewer.level.task.EssenceTaskLevel; +import io.github.moulberry.notenoughupdates.profileviewer.level.task.EventTaskLevel; import io.github.moulberry.notenoughupdates.profileviewer.level.task.MiscTaskLevel; import io.github.moulberry.notenoughupdates.profileviewer.level.task.SkillRelatedTaskLevel; import io.github.moulberry.notenoughupdates.profileviewer.level.task.SlayingTaskLevel; @@ -61,6 +62,8 @@ public class LevelPage { private final SlayingTaskLevel slayingTaskLevel; private final StoryTaskLevel storyTaskLevel; + private final EventTaskLevel eventTaskLevel; + private static final ResourceLocation pv_levels = new ResourceLocation("notenoughupdates:pv_levels.png"); public LevelPage(GuiProfileViewer instance, BasicPage basicPage) { @@ -75,6 +78,7 @@ public class LevelPage { skillRelatedTaskLevel = new SkillRelatedTaskLevel(this); slayingTaskLevel = new SlayingTaskLevel(this); storyTaskLevel = new StoryTaskLevel(this); + eventTaskLevel = new EventTaskLevel(this); } public void drawPage(int mouseX, int mouseY) { @@ -97,13 +101,14 @@ public class LevelPage { JsonObject profileInfo = profile.getProfileInformation(profileId); drawMainBar(skyblockLevel, mouseX, mouseY, guiLeft, guiTop); - coreTaskLevel.drawTask(profileInfo, mouseX, mouseY, guiLeft, guiTop); dungeonTaskLevel.drawTask(profileInfo, mouseX, mouseY, guiLeft, guiTop); essenceTaskLevel.drawTask(profileInfo, mouseX, mouseY, guiLeft, guiTop); miscTaskLevel.drawTask(profileInfo, mouseX, mouseY, guiLeft, guiTop); skillRelatedTaskLevel.drawTask(profileInfo, mouseX, mouseY, guiLeft, guiTop); slayingTaskLevel.drawTask(profileInfo, mouseX, mouseY, guiLeft, guiTop); storyTaskLevel.drawTask(profileInfo, mouseX, mouseY, guiLeft, guiTop); + eventTaskLevel.drawTask(profileInfo, mouseX, mouseY, guiLeft, guiTop); + coreTaskLevel.drawTask(profileInfo, mouseX, mouseY, guiLeft, guiTop); } public void renderLevelBar( @@ -175,14 +180,12 @@ public class LevelPage { renderLevelBar( "Level", BasicPage.skull, - guiLeft + 163, - guiTop + 30, + guiLeft + 163, guiTop + 30, 110, skyblockLevel, - (skyblockLevel - (long) skyblockLevel) * 100, + Math.round((skyblockLevel - (long) skyblockLevel) * 100), 100, - mouseX, - mouseY, + mouseX, mouseY, false, Collections.emptyList() ); diff --git a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/CoreTaskLevel.java b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/CoreTaskLevel.java index be333359..8bb26b09 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/CoreTaskLevel.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/CoreTaskLevel.java @@ -19,8 +19,9 @@ package io.github.moulberry.notenoughupdates.profileviewer.level.task; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import io.github.moulberry.notenoughupdates.profileviewer.PlayerStats; import io.github.moulberry.notenoughupdates.profileviewer.ProfileViewer; import io.github.moulberry.notenoughupdates.profileviewer.level.LevelPage; import io.github.moulberry.notenoughupdates.util.Constants; @@ -80,11 +81,20 @@ public class CoreTaskLevel { } // mp acc - int sbXpGainedMp = levelPage.getProfile().getMagicalPower(levelPage.getProfileId()); + int sbXpGainedMp = 0; + if (object.has("accessory_bag_storage") && + object.getAsJsonObject("accessory_bag_storage").has("highest_magical_power")) { + sbXpGainedMp = object.getAsJsonObject("accessory_bag_storage").get("highest_magical_power").getAsInt(); + } // pets - int petScore = PlayerStats.getPetScore(object); + int petScore = 0; + if (object.has("leveling") && + object.getAsJsonObject("leveling").has("highest_pet_score")) { + petScore = object.getAsJsonObject("leveling").get("highest_pet_score").getAsInt(); + + } int sbXpPetScore = petScore * coreTask.get("pet_score_xp").getAsInt(); // museum is not possible @@ -116,13 +126,32 @@ public class CoreTaskLevel { } } } + + int sbXpBankUpgrades = 0; + + JsonArray completedTasks = object.getAsJsonObject("leveling").get("completed_tasks").getAsJsonArray(); + JsonObject bankUpgradesXp = coreTask.getAsJsonObject("bank_upgrades_xp"); + for (JsonElement completedTask : completedTasks) { + String name = completedTask.getAsString(); + if (bankUpgradesXp.has(name)) { + sbXpBankUpgrades += bankUpgradesXp.get(name).getAsInt(); + } + } + List<String> lore = new ArrayList<>(); lore.add(levelPage.buildLore("Skill Level Up", sbXpGainedSkillLVL, coreTask.get("skill_level_up").getAsInt(), false )); - lore.add(levelPage.buildLore("Museum Progression", - 0, 0, false + + int totalXp = sbXpGainedSkillLVL + sbXpGainedFairy + + sbXpCollection + sbXpMinionTier + sbXpBankUpgrades; + + lore.add(levelPage.buildLore( + "Museum Progression", + 0, + 0, + false )); lore.add(levelPage.buildLore( "Fairy Soul", @@ -141,21 +170,18 @@ public class CoreTaskLevel { sbXpMinionTier, coreTask.get("craft_minions").getAsInt(), false )); lore.add(levelPage.buildLore("Bank Upgrade", - 0, 0, false + sbXpBankUpgrades, coreTask.get("bank_upgrades").getAsInt(), false )); levelPage.renderLevelBar( "Core Task", new ItemStack(Items.nether_star), - guiLeft + 23, - guiTop + 25, + guiLeft + 23, guiTop + 25, 110, 0, - sbXpGainedSkillLVL + sbXpGainedFairy + - sbXpCollection + sbXpMinionTier, + totalXp, levelPage.getConstant().getAsJsonObject("category_xp").get("core_task").getAsInt(), - mouseX, - mouseY, + mouseX, mouseY, true, lore ); diff --git a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/DungeonTaskLevel.java b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/DungeonTaskLevel.java index 157c8362..6900cdf3 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/DungeonTaskLevel.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/DungeonTaskLevel.java @@ -44,9 +44,9 @@ public class DungeonTaskLevel { Map<String, ProfileViewer.Level> skyblockInfo = levelPage.getProfile().getSkyblockInfo(levelPage.getProfileId()); - double sbLevelGainedFloor = 0; - double sbXpGainedClass = 0; - double sbXpGainedLvl = 0; + int sbLevelGainedFloor = 0; + int sbXpGainedClass = 0; + int sbXpGainedLvl = 0; int catacombsLvl = 0; if (skyblockInfo != null && skyblockInfo.containsKey("catacombs")) { ProfileViewer.Level catacombs = skyblockInfo.get("catacombs"); @@ -105,20 +105,20 @@ public class DungeonTaskLevel { lore.add(levelPage.buildLore("Class Level Up", sbXpGainedClass, classLevelUp, false)); lore.add(levelPage.buildLore("Complete Dungeons", sbLevelGainedFloor, completeDungeon, false)); + int totalSbXpGain = sbXpGainedLvl + sbXpGainedClass + sbLevelGainedFloor; + levelPage.renderLevelBar( - "Dungeon", + "Dungeon Task", NotEnoughUpdates.INSTANCE.manager .createItemResolutionQuery() .withKnownInternalName("WITHER_RELIC") .resolveToItemStack(), - guiLeft + 23, - guiTop + 55, + guiLeft + 23, guiTop + 55, 110, catacombsLvl, totalXp, totalGainful, - mouseX, - mouseY, + mouseX, mouseY, true, lore ); diff --git a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/EssenceTaskLevel.java b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/EssenceTaskLevel.java index d64f54c1..c6df6fc1 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/EssenceTaskLevel.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/EssenceTaskLevel.java @@ -25,6 +25,7 @@ import com.google.gson.JsonObject; import io.github.moulberry.notenoughupdates.NotEnoughUpdates; import io.github.moulberry.notenoughupdates.profileviewer.level.LevelPage; import io.github.moulberry.notenoughupdates.util.Constants; +import io.github.moulberry.notenoughupdates.util.Utils; import net.minecraft.util.EnumChatFormatting; import java.util.ArrayList; @@ -88,13 +89,20 @@ public class EssenceTaskLevel { for (Map.Entry<String, EssenceShop> stringEssenceShopEntry : loreMap.entrySet()) { String key = stringEssenceShopEntry.getKey(); EssenceShop value = stringEssenceShopEntry.getValue(); - value.name = NotEnoughUpdates.INSTANCE.manager + JsonObject jsonObject = NotEnoughUpdates.INSTANCE.manager .createItemResolutionQuery() .withKnownInternalName(key) - .resolveToItemListJson() + .resolveToItemListJson(); + if (jsonObject == null){ + Utils.showOutdatedRepoNotification(); + continue; + } + value.name = jsonObject .get("displayname") .getAsString(); - value.max = essenceShopTask.get(key.toLowerCase() + "_shop").getAsInt(); + String name = key.toLowerCase() + "_shop"; + if (!essenceShopTask.has(name)) continue; + value.max = essenceShopTask.get(name).getAsInt(); lore.add(levelPage.buildLore( EnumChatFormatting.getTextWithoutFormattingCodes(value.name), value.current, @@ -109,14 +117,12 @@ public class EssenceTaskLevel { NotEnoughUpdates.INSTANCE.manager.createItemResolutionQuery() .withKnownInternalName("ESSENCE_WITHER") .resolveToItemStack(), - guiLeft + 299, - guiTop + 25, + guiLeft + 299, guiTop + 25, 110, total, total, categoryXp.get("essence_shop_task").getAsInt(), - mouseX, - mouseY, + mouseX, mouseY, true, lore ); diff --git a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/EventTaskLevel.java b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/EventTaskLevel.java new file mode 100644 index 00000000..04ace872 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/EventTaskLevel.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2023 NotEnoughUpdates contributors + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * NotEnoughUpdates is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.profileviewer.level.task; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import io.github.moulberry.notenoughupdates.profileviewer.level.LevelPage; +import net.minecraft.init.Items; +import net.minecraft.item.ItemStack; + +import java.util.ArrayList; +import java.util.List; + +public class EventTaskLevel { + + private final LevelPage levelPage; + + public EventTaskLevel(LevelPage levelPage) {this.levelPage = levelPage;} + + public void drawTask(JsonObject object, int mouseX, int mouseY, int guiLeft, int guiTop) { + List<String> lore = new ArrayList<>(); + + int sbXpMiningFiesta = 0; + int sbXpFishingFestival = 0; + int sbXpSpookyFestival = 0; + JsonObject constant = levelPage.getConstant(); + JsonObject eventTask = constant.getAsJsonObject("event_task"); + + if (object.has("leveling")) { + JsonObject leveling = object.getAsJsonObject("leveling"); + int miningFiestaOresMined = 0; + int fishingFestivalSharksKilled = 0; + if (leveling.has("mining_fiesta_ores_mined")) + miningFiestaOresMined = leveling.get("mining_fiesta_ores_mined").getAsInt(); + if (leveling.has("fishing_festival_sharks_killed")) fishingFestivalSharksKilled = leveling.get( + "fishing_festival_sharks_killed").getAsInt(); + + sbXpMiningFiesta = getCapOrAmount(miningFiestaOresMined, 1_000_000, 5_000); + sbXpFishingFestival = getCapOrAmount(fishingFestivalSharksKilled, 5_000, 50); + + JsonArray completedTasks = leveling.get("completed_tasks").getAsJsonArray(); + JsonObject spookyFestivalXp = eventTask.getAsJsonObject("spooky_festival_xp"); + for (JsonElement completedTask : completedTasks) { + String name = completedTask.getAsString(); + if (spookyFestivalXp.has(name)) { + sbXpSpookyFestival += spookyFestivalXp.get(name).getAsInt(); + } + } + } + + lore.add(levelPage.buildLore("Mining Fiesta", sbXpMiningFiesta, eventTask.get("mining_fiesta").getAsInt(), false)); + lore.add(levelPage.buildLore( + "Fishing Festival", + sbXpFishingFestival, + eventTask.get("fishing_festival").getAsInt(), + false + )); + lore.add(levelPage.buildLore( + "Spooky Festival", + sbXpSpookyFestival, + eventTask.get("spooky_festival").getAsInt(), + false + )); + + int totalXp = sbXpMiningFiesta + sbXpSpookyFestival + + sbXpFishingFestival; + levelPage.renderLevelBar( + "Event Task", + new ItemStack(Items.clock), + guiLeft + 299, guiTop + 115, + 110, + 0, + totalXp, + levelPage.getConstant().getAsJsonObject("category_xp").get("event_task").getAsInt(), + mouseX, mouseY, + true, + lore + ); + } + + private int getCapOrAmount(int miningFiestaOresMined, int cap, int per) { + if (miningFiestaOresMined == 0) return 0; + if (miningFiestaOresMined > cap) { + return cap / per; + } + return miningFiestaOresMined / per; + } +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/MiscTaskLevel.java b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/MiscTaskLevel.java index a717664d..8b7b9d2b 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/MiscTaskLevel.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/MiscTaskLevel.java @@ -27,12 +27,13 @@ import io.github.moulberry.notenoughupdates.profileviewer.level.LevelPage; import io.github.moulberry.notenoughupdates.util.Utils; import net.minecraft.init.Items; import net.minecraft.item.ItemStack; -import net.minecraft.util.EnumChatFormatting; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; public class MiscTaskLevel { @@ -86,24 +87,27 @@ public class MiscTaskLevel { } // abiphone - if (netherIslandPlayerData.has("abiphone")) { - JsonObject abiphone = netherIslandPlayerData.getAsJsonObject("abiphone"); - if (abiphone.has("active_contacts")) sbXpAbiphone = - abiphone.getAsJsonArray("active_contacts").size() * miscellaneousTask.get("abiphone_contacts_xp").getAsInt(); + JsonObject leveling = object.getAsJsonObject("leveling"); + JsonArray completedTask = leveling.get("completed_tasks").getAsJsonArray(); + Stream<JsonElement> stream = StreamSupport.stream(completedTask.spliterator(), true); + long activeContacts = stream.map(JsonElement::getAsString).filter(s -> s.startsWith("ABIPHONE_")).count(); + JsonObject abiphone = netherIslandPlayerData.getAsJsonObject("abiphone"); + if (abiphone.has("active_contacts")) { + sbXpAbiphone = (int) activeContacts * miscellaneousTask.get("abiphone_contacts_xp").getAsInt(); } } // harp int sbXpGainedHarp = 0; JsonObject harpSongsNames = miscellaneousTask.get("harp_songs_names").getAsJsonObject(); - if (object.has("harp_quest")) { - JsonObject harpQuest = object.get("harp_quest").getAsJsonObject(); - for (Map.Entry<String, JsonElement> stringJsonElementEntry : harpSongsNames.entrySet()) { - String key = stringJsonElementEntry.getKey(); - int value = stringJsonElementEntry.getValue().getAsInt(); - if (harpQuest.has(key)) { - sbXpGainedHarp += value; - } + + JsonObject leveling = object.get("leveling").getAsJsonObject(); + if (leveling.has("completed_tasks")) { + JsonArray completedTasks = leveling.get("completed_tasks").getAsJsonArray(); + for (JsonElement completedTask : completedTasks) { + String name = completedTask.getAsString(); + String harpName = name.substring(0, name.lastIndexOf("_")); + if(harpSongsNames.has(harpName))sbXpGainedHarp += harpSongsNames.get(harpName).getAsInt() / 4; } } @@ -116,17 +120,18 @@ public class MiscTaskLevel { JsonObject communityShopUpgradesMax = miscellaneousTask.getAsJsonObject("community_shop_upgrades_max"); int communityShopUpgradesXp = miscellaneousTask.get("community_shop_upgrades_xp").getAsInt(); - - for ( - JsonElement upgradeState : upgradeStates) { - if (!upgradeState.isJsonObject()) continue; - JsonObject value = upgradeState.getAsJsonObject(); - String upgrade = value.get("upgrade").getAsString(); - int tier = value.get("tier").getAsInt(); - if (communityShopUpgradesMax.has(upgrade)) { - int max = communityShopUpgradesMax.get(upgrade).getAsInt(); - if (max >= tier) { - sbXpCommunityUpgrade += communityShopUpgradesXp; + if (upgradeStates != null) { + for ( + JsonElement upgradeState : upgradeStates) { + if (!upgradeState.isJsonObject()) continue; + JsonObject value = upgradeState.getAsJsonObject(); + String upgrade = value.get("upgrade").getAsString(); + int tier = value.get("tier").getAsInt(); + if (communityShopUpgradesMax.has(upgrade)) { + int max = communityShopUpgradesMax.get(upgrade).getAsInt(); + if (max >= tier) { + sbXpCommunityUpgrade += communityShopUpgradesXp; + } } } } @@ -157,10 +162,10 @@ public class MiscTaskLevel { sbXpDojo, miscellaneousTask.get("the_dojo").getAsInt(), false )); lore.add(levelPage.buildLore( - EnumChatFormatting.ITALIC + "Harp Songs", + "Harp Songs", sbXpGainedHarp, miscellaneousTask.get("harp_songs").getAsInt(), false )); - lore.add(levelPage.buildLore(EnumChatFormatting.ITALIC + "Abiphone Contacts", + lore.add(levelPage.buildLore("Abiphone Contacts", sbXpAbiphone, miscellaneousTask.get("abiphone_contacts").getAsInt(), false )); lore.add(levelPage.buildLore("Community Shop Upgrades", @@ -170,22 +175,22 @@ public class MiscTaskLevel { sbXpPersonalBank, miscellaneousTask.get("personal_bank_upgrades").getAsInt(), false )); + int totalXp = sbXpReaperPeppers + sbXpDojo + sbXpGainedHarp + sbXpAbiphone + + sbXpCommunityUpgrade + sbXpPersonalBank; levelPage.renderLevelBar( "Misc. Task", new ItemStack(Items.map), - guiLeft + 299, - guiTop + 55, + guiLeft + 299, guiTop + 55, 110, 0, - sbXpReaperPeppers + sbXpDojo + sbXpGainedHarp + sbXpAbiphone + - sbXpCommunityUpgrade + sbXpPersonalBank, + totalXp, levelPage.getConstant().getAsJsonObject("category_xp").get("miscellaneous_task").getAsInt(), - mouseX, - mouseY, + mouseX, mouseY, true, lore ); + totalXp += sbXpAccessoryUpgrade + sbXpUnlockedPowers; } private int getRankIndex(int pointsTotal) { diff --git a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/SkillRelatedTaskLevel.java b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/SkillRelatedTaskLevel.java index 4ba7951c..06ab6f8c 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/SkillRelatedTaskLevel.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/SkillRelatedTaskLevel.java @@ -43,13 +43,6 @@ public class SkillRelatedTaskLevel { JsonObject skillRelatedTask = levelPage.getConstant().get("skill_related_task").getAsJsonObject(); JsonObject miningObj = skillRelatedTask.get("mining").getAsJsonObject(); - float mithrilPowder = Utils.getElementAsFloat(Utils.getElement(object, "mining_core.powder_mithril"), 0); - float gemstonePowder = Utils.getElementAsFloat(Utils.getElement(object, "mining_core.powder_gemstone"), 0); - float mithril = Utils.getElementAsFloat(Utils.getElement(object, "mining_core.powder_spent_mithril"), 0) + - mithrilPowder; - float gemstone = (Utils.getElementAsFloat(Utils.getElement(object, "mining_core.powder_spent_gemstone"), 0)) + - gemstonePowder; - float hotmXp = Utils.getElementAsFloat(Utils.getElement(object, "mining_core.experience"), 0); ProfileViewer.Level levelObjHotm = ProfileViewer.getLevel( @@ -66,29 +59,31 @@ public class SkillRelatedTaskLevel { hotmXP += hotmXpArray.get(i - 1).getAsInt(); } - int gainByFirstMithrilThing; - if (mithril >= 350_000) { - gainByFirstMithrilThing = (int) (350000 / 2400d); - mithril -= 350_000; - } else { - gainByFirstMithrilThing = (int) (mithril / 2400d); - mithril = 0; - } + float mithrilPowder = Utils.getElementAsFloat(Utils.getElement(object, "mining_core.powder_mithril"), 0); + float gemstonePowder = Utils.getElementAsFloat(Utils.getElement(object, "mining_core.powder_gemstone"), 0); + float mithril = Utils.getElementAsFloat(Utils.getElement(object, "mining_core.powder_spent_mithril"), 0) + + mithrilPowder; + float gemstone = (Utils.getElementAsFloat(Utils.getElement(object, "mining_core.powder_spent_gemstone"), 0)) + + gemstonePowder; - int gainByFirstGemstoneThing; - if (gemstone >= 350_000) { - gainByFirstGemstoneThing = (int) (350_000 / 2500d); - gemstone -= 350_000; - } else { - gainByFirstGemstoneThing = (int) (gemstone / 2500d); - gemstone = 0; - } + // PUNKT NULL + + double totalMithril = mithril + mithrilPowder; + double totalGemstone = gemstone + gemstonePowder; + double mithrilUnder = Math.min(350000.0, totalMithril); + double mithrilOver = Math.max(0, Math.min(totalMithril, 12_500_000.0) - 350000.0); + double gemstoneUnder = Math.min(350000.0, totalGemstone); + double gemstoneOver = Math.max(0, Math.min(totalGemstone, 20_000_000.0) - 350000.0); - int sbXpMithrilPowder = (int) powder(3.75, mithril, 12_500_000); - int sbXpGemstonePowder = (int) powder(4.25, gemstone, 20_000_000); + double mithrilXP = Math.floor(mithrilUnder / 2400.0); + double gemstoneXP = Math.floor(gemstoneUnder / 2500.0); + double mithrilExcess = Math.floor( + 3.75 * (Math.sqrt(1 + 8 * Math.sqrt((1758267.0 / 12_500_000.0) * mithrilOver + 9)) - 3)); + double gemstoneExcess = Math.floor( + 4.25 * (Math.sqrt(1 + 8 * Math.sqrt((1758267.0 / 20_000_000.0) * gemstoneOver + 9)) - 3)); double sbXpHotmTier = - (sbXpMithrilPowder + gainByFirstMithrilThing) + (sbXpGemstonePowder + gainByFirstGemstoneThing) + (mithrilXP + mithrilExcess) + (gemstoneXP + gemstoneExcess) + hotmXP; int sbXpPotmTier = 0; @@ -171,6 +166,18 @@ public class SkillRelatedTaskLevel { } } + int sbXpNucleus = 0; + JsonObject leveling = object.get("leveling").getAsJsonObject(); + if (leveling.has("completions") && leveling.getAsJsonObject("completions").has("NUCLEUS_RUNS")) { + int nucleusRuns = leveling.getAsJsonObject("completions").get("NUCLEUS_RUNS").getAsInt(); + JsonElement nucleusXp = miningObj.get("crystal_nucleus_xp"); + if (nucleusXp == null) { + Utils.showOutdatedRepoNotification(); + } else { + sbXpNucleus += nucleusRuns * nucleusXp.getAsInt(); + } + } + List<String> lore = new ArrayList<>(); lore.add(levelPage.buildLore("Heart of the Mountain", sbXpHotmTier, miningObj.get("hotm").getAsInt(), false)); lore.add(levelPage.buildLore( @@ -179,7 +186,7 @@ public class SkillRelatedTaskLevel { miningObj.get("commission_milestone").getAsInt(), false )); - lore.add(levelPage.buildLore("Crystal Nucleus", 0, 0, false)); + lore.add(levelPage.buildLore("Crystal Nucleus", sbXpNucleus, miningObj.get("crystal_nucleus").getAsInt(), false)); lore.add(levelPage.buildLore( "Anita's Shop Upgrade", sbXpGainedByAnita, @@ -189,37 +196,22 @@ public class SkillRelatedTaskLevel { lore.add(levelPage.buildLore("Peak of the Mountain", sbXpPotmTier, miningObj.get("potm").getAsInt(), false)); lore.add(levelPage.buildLore("Trophy Fish", sbXpTrophyFish, fishingObj.get("trophy_fish").getAsInt(), false)); lore.add(levelPage.buildLore("Rock Milestone", sbXpRockPet, miningObj.get("rock_milestone").getAsInt(), false)); - lore.add(levelPage.buildLore( - "Dolphin Milestone", - sbXpDolphinPet, - fishingObj.get("dolphin_milestone").getAsInt(), - false - )); + lore.add(levelPage.buildLore("Dolphin Milestone", sbXpDolphinPet, fishingObj.get("dolphin_milestone").getAsInt(), false)); + int totalXp = + (int) (sbXpHotmTier + sbXpCommissionMilestone + sbXpGainedByAnita + sbXpPotmTier + sbXpTrophyFish + sbXpRockPet + + sbXpDolphinPet + sbXpNucleus); levelPage.renderLevelBar( "Skill Related Task", new ItemStack(Items.diamond_sword), - guiLeft + 23, - guiTop + 115, + guiLeft + 23, guiTop + 115, 110, 0, - sbXpHotmTier + sbXpCommissionMilestone + sbXpGainedByAnita + sbXpPotmTier + sbXpTrophyFish + sbXpRockPet + - sbXpDolphinPet, + totalXp, levelPage.getConstant().getAsJsonObject("category_xp").get("skill_related_task").getAsInt(), - mouseX, - mouseY, + mouseX, mouseY, true, lore ); } - - private static double powder(double multiplier, float left, int CAP) { - double cons = 1758267; - if (left <= 0) return 0; - - left = Math.min(CAP, left); - - return multiplier * (Math.sqrt(1 + 8 * (Math.sqrt((cons / CAP) * left + 9))) - 3); - } - } diff --git a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/SlayingTaskLevel.java b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/SlayingTaskLevel.java index 3aded435..6b624a52 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/SlayingTaskLevel.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/SlayingTaskLevel.java @@ -233,20 +233,21 @@ public class SlayingTaskLevel { int slayingTaskMax = levelPage.getConstant().getAsJsonObject("category_xp").get("slaying_task").getAsInt(); + int totalXp = sbXpGainedSlayer + bossCollectionXp + mythologicalKillsXp + + sbXpFromDragonKills + sbXpFromSlayerDefeat + sbXpDefeatKuudra + sbXpGainedArachne; levelPage.renderLevelBar( "Slaying Task", new ItemStack(Items.golden_sword), - guiLeft + 23, - guiTop + 85, + guiLeft + 23, guiTop + 85, 110, - 0, sbXpGainedSlayer + bossCollectionXp + mythologicalKillsXp + - sbXpFromDragonKills + sbXpFromSlayerDefeat + sbXpDefeatKuudra + sbXpGainedArachne, + 0, + totalXp, slayingTaskMax, - mouseX, - mouseY, + mouseX, mouseY, true, lore ); + totalXp += sbXpBestiary; } private int loopThroughCollection(int[] array, double value, JsonArray jsonArray) { diff --git a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/StoryTaskLevel.java b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/StoryTaskLevel.java index 2b1b59cb..3846db5f 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/StoryTaskLevel.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/level/task/StoryTaskLevel.java @@ -61,14 +61,12 @@ public class StoryTaskLevel { levelPage.renderLevelBar( "Story Task", new ItemStack(Items.map), - guiLeft + 299, - guiTop + 85, + guiLeft + 299, guiTop + 85, 110, 0, sbXpStory, levelPage.getConstant().getAsJsonObject("category_xp").get("story_task").getAsInt(), - mouseX, - mouseY, + mouseX, mouseY, true, lore ); diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/ApiUtil.java b/src/main/java/io/github/moulberry/notenoughupdates/util/ApiUtil.java index c86ac84f..28298fe0 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/util/ApiUtil.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/util/ApiUtil.java @@ -48,20 +48,33 @@ import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; +import java.time.Duration; import java.util.ArrayList; import java.util.Collection; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.zip.GZIPInputStream; public class ApiUtil { private static final Gson gson = new Gson(); + + private static final Comparator<NameValuePair> nameValuePairComparator = Comparator + .comparing(NameValuePair::getName) + .thenComparing(NameValuePair::getValue); + private static final ExecutorService executorService = Executors.newFixedThreadPool(3); - private static final String USER_AGENT = "NotEnoughUpdates/" + NotEnoughUpdates.VERSION; + private static String getUserAgent() { + if (NotEnoughUpdates.INSTANCE.config.hidden.customUserAgent != null) { + return NotEnoughUpdates.INSTANCE.config.hidden.customUserAgent; + } + return "NotEnoughUpdates/" + NotEnoughUpdates.VERSION; + } private static SSLContext ctx; private final Map<String, CompletableFuture<Void>> updateTasks = new HashMap<>(); @@ -89,11 +102,12 @@ public class ApiUtil { public void updateProfileData(String playerUuid) { if (!updateTasks.getOrDefault(playerUuid, CompletableFuture.completedFuture(null)).isDone()) return; + String uuid = Minecraft.getMinecraft().thePlayer.getUniqueID().toString().replace("-", ""); updateTasks.put(playerUuid, newHypixelApiRequest("skyblock/profiles") - .queryArgument("uuid", Minecraft.getMinecraft().thePlayer.getUniqueID().toString().replace("-", "")) + .queryArgument("uuid", uuid) .requestJson() .handle((jsonObject, throwable) -> { - new ProfileDataLoadedEvent(jsonObject).post(); + new ProfileDataLoadedEvent(uuid, jsonObject).post(); return null; })); @@ -104,6 +118,7 @@ public class ApiUtil { private final List<NameValuePair> queryArguments = new ArrayList<>(); private String baseUrl = null; private boolean shouldGunzip = false; + private Duration maxCacheAge = Duration.ofSeconds(500); private String method = "GET"; private String postData = null; private String postContentType = null; @@ -113,6 +128,15 @@ public class ApiUtil { return this; } + /** + * Specify a cache timeout of {@code null} to signify an uncacheable request. + * Non {@code GET} requests are always uncacheable. + */ + public Request maxCacheAge(Duration maxCacheAge) { + this.maxCacheAge = maxCacheAge; + return this; + } + public Request url(String baseUrl) { this.baseUrl = baseUrl; return this; @@ -154,7 +178,17 @@ public class ApiUtil { return fut; } - public CompletableFuture<String> requestString() { + public String getBaseUrl() { + return baseUrl; + } + + private ApiCache.CacheKey getCacheKey() { + if (!"GET".equals(method)) return null; + queryArguments.sort(nameValuePairComparator); + return new ApiCache.CacheKey(baseUrl, queryArguments, shouldGunzip); + } + + private CompletableFuture<String> requestString0() { return buildUrl().thenApplyAsync(url -> { try { InputStream inputStream = null; @@ -169,7 +203,7 @@ public class ApiUtil { } conn.setConnectTimeout(10000); conn.setReadTimeout(10000); - conn.setRequestProperty("User-Agent", USER_AGENT); + conn.setRequestProperty("User-Agent", getUserAgent()); if (this.postContentType != null) { conn.setRequestProperty("Content-Type", this.postContentType); } @@ -177,7 +211,7 @@ public class ApiUtil { conn.setDoOutput(true); OutputStream os = conn.getOutputStream(); try { - os.write(this.postData.getBytes("utf-8")); + os.write(this.postData.getBytes(StandardCharsets.UTF_8)); } finally { os.close(); } @@ -207,7 +241,16 @@ public class ApiUtil { } catch (IOException e) { throw new RuntimeException(e); // We can rethrow, since supplyAsync catches exceptions. } - }, executorService); + }, executorService).handle((obj, t) -> { + if (t != null) { + System.err.println(ErrorUtil.printStackTraceWithoutApiKey(t)); + } + return obj; + }); + } + + public CompletableFuture<String> requestString() { + return ApiCache.INSTANCE.cacheRequest(this, getCacheKey(), this::requestString0, maxCacheAge); } public CompletableFuture<JsonObject> requestJson() { @@ -215,7 +258,7 @@ public class ApiUtil { } public <T> CompletableFuture<T> requestJson(Class<? extends T> clazz) { - return requestString().thenApply(str -> gson.fromJson(str, clazz)); + return requestString().thenApplyAsync(str -> gson.fromJson(str, clazz)); } } diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/SBInfo.java b/src/main/java/io/github/moulberry/notenoughupdates/util/SBInfo.java index 8ce765aa..a89b281d 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/util/SBInfo.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/util/SBInfo.java @@ -24,6 +24,7 @@ import com.google.gson.JsonObject; import io.github.moulberry.notenoughupdates.NotEnoughUpdates; import io.github.moulberry.notenoughupdates.autosubscribe.NEUAutoSubscribe; import io.github.moulberry.notenoughupdates.listener.ScoreboardLocationChangeListener; +import io.github.moulberry.notenoughupdates.miscfeatures.CookieWarning; import io.github.moulberry.notenoughupdates.miscfeatures.customblockzones.LocationChangeEvent; import io.github.moulberry.notenoughupdates.miscgui.minionhelper.MinionHelperManager; import io.github.moulberry.notenoughupdates.overlays.OverlayManager; @@ -86,6 +87,7 @@ public class SBInfo { public String objective = ""; public String slayer = ""; public boolean stranded = false; + public boolean bingo = false; public String mode = null; @@ -388,6 +390,7 @@ public class SBInfo { isInDungeon = tempIsInDungeon; boolean containsStranded = false; + boolean containsBingo = false; for (String line : lines) { //Slayer stuff if (line.contains("Tarantula Broodfather")) { slayer = "Tarantula"; @@ -422,8 +425,10 @@ public class SBInfo { } } if (line.contains("☀ Stranded")) containsStranded = true; + if (line.contains("Ⓑ Bingo")) containsBingo = true; } stranded = containsStranded; + bingo = containsBingo; if (lines.size() >= 5) { date = Utils.cleanColour(lines.get(1)).trim(); @@ -473,7 +478,6 @@ public class SBInfo { .thenAccept(newJson -> mayorJson = newJson); } - public JsonObject getMayorJson() { return mayorJson; } @@ -482,6 +486,7 @@ public class SBInfo { if (!newProfile.equals(currentProfile)) { currentProfile = newProfile; MinionHelperManager.getInstance().onProfileSwitch(); + CookieWarning.onProfileSwitch(); } } } diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/Utils.java b/src/main/java/io/github/moulberry/notenoughupdates/util/Utils.java index 952eb50f..38debbbc 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/util/Utils.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/util/Utils.java @@ -186,7 +186,7 @@ public class Utils { } int newScale = guiScales.size() > 0 - ? Math.max(0, Math.min(4, guiScales.peek())) + ? Math.max(0, guiScales.peek()) : Minecraft.getMinecraft().gameSettings.guiScale; if (newScale == 0) newScale = Minecraft.getMinecraft().gameSettings.guiScale; @@ -925,6 +925,10 @@ public class Utils { return createItemStack(item, displayName, 0, lore); } + public static ItemStack createItemStackArray(Item item, String displayName, String[] lore) { + return createItemStack(item, displayName, 0, lore); + } + public static ItemStack createItemStack(Block item, String displayName, String... lore) { return createItemStack(Item.getItemFromBlock(item), displayName, lore); } @@ -932,17 +936,7 @@ public class Utils { public static ItemStack createItemStack(Item item, String displayName, int damage, String... lore) { ItemStack stack = new ItemStack(item, 1, damage); NBTTagCompound tag = new NBTTagCompound(); - NBTTagCompound display = new NBTTagCompound(); - NBTTagList Lore = new NBTTagList(); - - for (String line : lore) { - Lore.appendTag(new NBTTagString(line)); - } - - display.setString("Name", displayName); - display.setTag("Lore", Lore); - - tag.setTag("display", display); + addNameAndLore(tag, displayName, lore); tag.setInteger("HideFlags", 254); stack.setTagCompound(tag); @@ -950,6 +944,22 @@ public class Utils { return stack; } + private static void addNameAndLore(NBTTagCompound tag, String displayName, String[] lore) { + NBTTagCompound display = new NBTTagCompound(); + + display.setString("Name", displayName); + + if (lore != null) { + NBTTagList tagLore = new NBTTagList(); + for (String line : lore) { + tagLore.appendTag(new NBTTagString(line)); + } + display.setTag("Lore", tagLore); + } + + tag.setTag("display", display); + } + public static ItemStack editItemStackInfo( ItemStack itemStack, String displayName, @@ -979,15 +989,17 @@ public class Utils { return itemStack; } - public static ItemStack createSkull(String displayName, String uuid, String value) { + return createSkull(displayName, uuid, value, null); + } + + public static ItemStack createSkull(String displayName, String uuid, String value, String[] lore) { ItemStack render = new ItemStack(Items.skull, 1, 3); NBTTagCompound tag = new NBTTagCompound(); NBTTagCompound skullOwner = new NBTTagCompound(); NBTTagCompound properties = new NBTTagCompound(); NBTTagList textures = new NBTTagList(); NBTTagCompound textures_0 = new NBTTagCompound(); - NBTTagCompound display = new NBTTagCompound(); skullOwner.setString("Id", uuid); skullOwner.setString("Name", uuid); @@ -995,8 +1007,7 @@ public class Utils { textures_0.setString("Value", value); textures.appendTag(textures_0); - display.setString("Name", displayName); - tag.setTag("display", display); + addNameAndLore(tag, displayName, lore); properties.setTag("textures", textures); skullOwner.setTag("Properties", properties); @@ -1229,6 +1240,22 @@ public class Utils { drawStringScaled(str, fr, x - len / 2, y - fontHeight / 2, shadow, colour, factor); } + public static void drawStringCenteredScaled( + String str, + FontRenderer fr, + float x, + float y, + boolean shadow, + float factor + ) { + int strLen = fr.getStringWidth(str); + + float x2 = x - strLen / 2f; + float y2 = y - fr.FONT_HEIGHT / 2f; + + drawStringScaled(str, fr, x2, y2, shadow, 0, factor); + } + public static void drawStringCenteredYScaled( String str, FontRenderer fr, diff --git a/src/main/java/io/github/moulberry/notenoughupdates/events/ProfileDataLoadedEvent.java b/src/main/kotlin/io/github/moulberry/notenoughupdates/events/ProfileDataLoadedEvent.kt index 956acfe0..00a535c6 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/events/ProfileDataLoadedEvent.java +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/events/ProfileDataLoadedEvent.kt @@ -16,25 +16,27 @@ * You should have received a copy of the GNU Lesser General Public License * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. */ +package io.github.moulberry.notenoughupdates.events -package io.github.moulberry.notenoughupdates.events; - -import com.google.gson.JsonObject; - -import javax.annotation.Nullable; +import com.google.gson.JsonObject //TODO extend the usage of this event (accessory bag and storage data) -public class ProfileDataLoadedEvent extends NEUEvent { - - @Nullable - private final JsonObject data; +class ProfileDataLoadedEvent(val uuid: String, val data: JsonObject?) : NEUEvent() { + val profileInfo: JsonObject? by lazy { readProfileInfo() } - public ProfileDataLoadedEvent(@Nullable JsonObject entireApiResponse) { - this.data = entireApiResponse; - } + private fun readProfileInfo(): JsonObject? { + if (data == null) return null - @Nullable - public JsonObject getData() { - return data; - } + val skyblockProfiles = data["profiles"].asJsonArray + for (profileEle in skyblockProfiles) { + val profile = profileEle.asJsonObject + if (!profile.has("members")) continue + val members = profile["members"].asJsonObject + if (!members.has(uuid)) continue + if (profile.has("selected") && profile["selected"].asBoolean) { + return members[uuid].asJsonObject + } + } + return null + } } diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/miscfeatures/OldSkyBlockMenu.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscfeatures/OldSkyBlockMenu.kt new file mode 100644 index 00000000..b871a672 --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscfeatures/OldSkyBlockMenu.kt @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2023 NotEnoughUpdates contributors + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * NotEnoughUpdates is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.miscfeatures + +import io.github.moulberry.notenoughupdates.NotEnoughUpdates +import io.github.moulberry.notenoughupdates.autosubscribe.NEUAutoSubscribe +import io.github.moulberry.notenoughupdates.events.ReplaceItemEvent +import io.github.moulberry.notenoughupdates.events.SlotClickEvent +import io.github.moulberry.notenoughupdates.util.Utils +import net.minecraft.client.player.inventory.ContainerLocalMenu +import net.minecraft.init.Items +import net.minecraft.item.Item +import net.minecraft.item.ItemStack +import net.minecraftforge.fml.common.eventhandler.EventPriority +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +@NEUAutoSubscribe +object OldSkyBlockMenu { + + val map: Map<Int, SkyBlockButton> by lazy { + val map = mutableMapOf<Int, SkyBlockButton>() + for (button in SkyBlockButton.values()) { + map[button.slot] = button + } + map + } + + @SubscribeEvent + fun replaceItem(event: ReplaceItemEvent) { + if (!isRightInventory()) return + if (event.inventory !is ContainerLocalMenu) return + + val skyBlockButton = map[event.slotNumber] ?: return + + if (skyBlockButton.requiresBoosterCookie && !CookieWarning.hasActiveBoosterCookie()) { + event.replaceWith(skyBlockButton.itemWithCookieWarning) + } else { + event.replaceWith(skyBlockButton.itemWithoutCookieWarning) + } + } + + @SubscribeEvent(priority = EventPriority.HIGH) + fun onStackClick(event: SlotClickEvent) { + if (!isRightInventory()) return + + val skyBlockButton = map[event.slotId] ?: return + event.isCanceled = true + + if (!skyBlockButton.requiresBoosterCookie || CookieWarning.hasActiveBoosterCookie()) { + NotEnoughUpdates.INSTANCE.sendChatMessage("/" + skyBlockButton.command) + } + } + + private fun isRightInventory(): Boolean { + return NotEnoughUpdates.INSTANCE.hasSkyblockScoreboard() && + NotEnoughUpdates.INSTANCE.config.misc.oldSkyBlockMenu && + Utils.getOpenChestName() == "SkyBlock Menu" + } + + enum class SkyBlockButton( + val command: String, + val slot: Int, + private val displayName: String, + private vararg val displayDescription: String, + private val itemData: ItemData, + val requiresBoosterCookie: Boolean = true, + ) { + TRADES( + "trades", 40, + "Trades", + "View your available trades.", + "These trades are always", + "available and accessible through", + "the SkyBlock Menu.", + itemData = NormalItemData(Items.emerald), + requiresBoosterCookie = false + ), + ACCESSORY( + "accessories", 53, + "Accessory Bag", + "A special bag which can hold", + "Talismans, Rings, Artifacts, Relics, and", + "Orbs within it. All will still", + "work while in this bag!", + itemData = SkullItemData( + "2b73dd76-5fc1-4ac3-8139-6a8992f8ce80", + "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTYxYTkxOGMw" + + "YzQ5YmE4ZDA1M2U1MjJjYjkxYWJjNzQ2ODkzNjdiNGQ4YWEwNmJmYzFiYTkxNTQ3MzA5ODVmZiJ9fX0=" + ) + ), + POTION( + "potionbag", 52, + "Potion Bag", + "A handy bag for holding your", + "Potions in.", + itemData = SkullItemData( + "991c4a18-3283-4629-b0fc-bbce23cd658c", + "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOWY4Yjg" + + "yNDI3YjI2MGQwYTYxZTY0ODNmYzNiMmMzNWE1ODU4NTFlMDhhOWE5ZGYzNzI1NDhiNDE2OGNjODE3YyJ9fX0=" + ) + ), + QUIVER( + "quiver", 44, + "Quiver", + "A masterfully crafted Quiver", + "which holds any kind of", + "projectile you can think of!", + itemData = SkullItemData( + "41758912-e6b1-4700-9de5-04f2cfb9c422", + "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNGNiM2FjZ" + + "GMxMWNhNzQ3YmY3MTBlNTlmNGM4ZTliM2Q5NDlmZGQzNjRjNjg2OTgzMWNhODc4ZjA3NjNkMTc4NyJ9fX0=" + ) + ), + FISHING( + "fishingbag", 43, + "Fishing Bag", + "A useful bag which can hold all", + "types of fish, baits, and fishing", + "loot!", + itemData = SkullItemData( + "508c01d6-eabe-430b-9811-874691ee7ee4", + "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZWI4ZT" + + "I5N2RmNmI4ZGZmY2YxMzVkYmE4NGVjNzkyZDQyMGFkOGVjYjQ1OGQxNDQyODg1NzJhODQ2MDNiMTYzMSJ9fX0=" + ) + ), + SACK_OF_SACKS( + "sacks", 35, + "Sack of Sacks", + "A sack which contains other", + "sacks. Sackception!", + itemData = SkullItemData( + "a206a7eb-70fc-4f9f-8316-c3f69d6ba2ca", + "ewogICJ0aW1lc3RhbXAiIDogMTU5MTMxMDU4NTYwOSwKICAicHJvZmlsZUlkIiA6ICI0MWQzYWJjMmQ3NDk0MDBjOTA5MGQ1NDM0" + + "ZDAzODMxYiIsCiAgInByb2ZpbGVOYW1lIiA6ICJNZWdha2xvb24iLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVl" + + "LAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5l" + + "Y3JhZnQubmV0L3RleHR1cmUvODBhMDc3ZTI0OGQxNDI3NzJlYTgwMDg2NGY4YzU3OGI5ZDM2ODg1YjI5ZGFmODM2YjY0" + + "YTcwNjg4MmI2ZWMxMCIKICAgIH0KICB9Cn0=" + ), + requiresBoosterCookie = false + ), + ; + + val itemWithCookieWarning: ItemStack by lazy { createItem(true) } + val itemWithoutCookieWarning: ItemStack by lazy { createItem(false) } + + private fun createItem(showCookieWarning: Boolean): ItemStack { + val lore = mutableListOf<String>() + for (line in displayDescription) { + lore.add("§7$line") + } + lore.add("") + + if (showCookieWarning) { + lore.add("§cYou need a booster cookie active") + lore.add("§cto use this shortcut!") + } else { + lore.add("§eClick to execute /${command}") + } + val array = lore.toTypedArray() + val name = "§a${displayName}" + return when (itemData) { + is NormalItemData -> Utils.createItemStackArray(itemData.displayIcon, name, array) + is SkullItemData -> Utils.createSkull( + name, + itemData.uuid, + itemData.value, + array + ) + } + } + } + + sealed interface ItemData + + class NormalItemData(val displayIcon: Item) : ItemData + + class SkullItemData(val uuid: String, val value: String) : ItemData +} diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/util/ApiCache.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/ApiCache.kt new file mode 100644 index 00000000..c14df425 --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/ApiCache.kt @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2023 NotEnoughUpdates contributors + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * NotEnoughUpdates is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.util + +import io.github.moulberry.notenoughupdates.NotEnoughUpdates +import io.github.moulberry.notenoughupdates.options.customtypes.NEUDebugFlag +import io.github.moulberry.notenoughupdates.util.ApiUtil.Request +import org.apache.http.NameValuePair +import java.nio.file.Files +import java.nio.file.Path +import java.time.Duration +import java.util.* +import java.util.concurrent.CompletableFuture +import java.util.function.Supplier +import kotlin.io.path.deleteIfExists +import kotlin.io.path.readText +import kotlin.io.path.writeText +import kotlin.time.Duration.Companion.hours +import kotlin.time.Duration.Companion.seconds +import kotlin.time.ExperimentalTime +import kotlin.time.TimeSource +import kotlin.time.toKotlinDuration + +@OptIn(ExperimentalTime::class) + +object ApiCache { + data class CacheKey( + val baseUrl: String, + val requestParameters: List<NameValuePair>, + val shouldGunzip: Boolean, + ) + + data class CacheResult( + private var future: CompletableFuture<String>?, + val firedAt: TimeSource.Monotonic.ValueTimeMark, + private var file: Path? = null, + private var disposed: Boolean = false, + ) { + init { + future!!.thenAcceptAsync { text -> + synchronized(this) { + if (disposed) { + return@synchronized + } + future = null + val f = Files.createTempFile(cacheBaseDir, "api-cache", ".bin") + log("Writing cache to disk: $f") + f.toFile().deleteOnExit() + f.writeText(text) + file = f + } + } + } + + val isAvailable get() = file != null && !disposed + + fun getCachedFuture(): CompletableFuture<String> { + synchronized(this) { + if (disposed) { + return CompletableFuture.supplyAsync { + throw IllegalStateException("Attempting to read from a disposed future at $file. Most likely caused by non synchronized access to ApiCache.cachedRequests") + } + } + val fut = future + if (fut != null) { + return fut + } else { + val text = file!!.readText() + return CompletableFuture.completedFuture(text) + } + } + } + + /** + * Should be called when removing / replacing a request from [cachedRequests]. + * Should only be called while holding a lock on [ApiCache]. + * This deletes the disk cache and smashes the internal state for it to be GCd. + * After calling this method no other method may be called on this object. + */ + internal fun dispose() { + synchronized(this) { + if (disposed) return + log("Disposing cache for $file") + disposed = true + file?.deleteIfExists() + future = null + } + } + } + + private val cacheBaseDir by lazy { + val d = Files.createTempDirectory("neu-cache") + d.toFile().deleteOnExit() + d + } + private val cachedRequests = mutableMapOf<CacheKey, CacheResult>() + val histogramTotalRequests: MutableMap<String, Int> = mutableMapOf() + val histogramNonCachedRequests: MutableMap<String, Int> = mutableMapOf() + + private val timeout = 10.seconds + private val globalMaxCacheAge = 1.hours + + private fun log(message: String) { + NEUDebugFlag.API_CACHE.log(message) + } + + private fun traceApiRequest( + request: Request, + failReason: String?, + ) { + if (!NotEnoughUpdates.INSTANCE.config.hidden.dev) return + val callingClass = Thread.currentThread().stackTrace + .find { + !it.className.startsWith("java.") && + !it.className.startsWith("kotlin.") && + it.className != ApiCache::class.java.name && + it.className != ApiUtil::class.java.name && + it.className != Request::class.java.name + } + val callingClassText = callingClass?.let { + "${it.className}.${it.methodName} (${it.fileName}:${it.lineNumber})" + } ?: "no calling class found" + callingClass?.className?.let { + histogramTotalRequests[it] = (histogramTotalRequests[it] ?: 0) + 1 + if (failReason != null) + histogramNonCachedRequests[it] = (histogramNonCachedRequests[it] ?: 0) + 1 + } + if (failReason != null) { + log("Executing api request for url ${request.baseUrl} by $callingClassText: $failReason") + } else { + log("Cache hit for api request for url ${request.baseUrl} by $callingClassText.") + } + } + + private fun evictCache() { + synchronized(this) { + val it = cachedRequests.iterator() + while (it.hasNext()) { + val next = it.next() + if (next.value.firedAt.elapsedNow() >= globalMaxCacheAge) { + next.value.dispose() + it.remove() + } + } + } + } + + fun cacheRequest( + request: Request, + cacheKey: CacheKey?, + futureSupplier: Supplier<CompletableFuture<String>>, + maxAge: Duration? + ): CompletableFuture<String> { + evictCache() + if (cacheKey == null) { + traceApiRequest(request, "uncacheable request (probably POST)") + return futureSupplier.get() + } + if (maxAge == null) { + traceApiRequest(request, "manually specified as uncacheable") + return futureSupplier.get() + } + fun recache(): CompletableFuture<String> { + return futureSupplier.get().also { + cachedRequests[cacheKey]?.dispose() // Safe to dispose like this because this function is always called in a synchronized block + cachedRequests[cacheKey] = CacheResult(it, TimeSource.Monotonic.markNow()) + } + } + synchronized(this) { + val cachedRequest = cachedRequests[cacheKey] + if (cachedRequest == null) { + traceApiRequest(request, "no cache found") + return recache() + } + + return if (cachedRequest.isAvailable) { + if (cachedRequest.firedAt.elapsedNow() > maxAge.toKotlinDuration()) { + traceApiRequest(request, "outdated cache") + recache() + } else { + // Using local cached request + traceApiRequest(request, null) + cachedRequest.getCachedFuture() + } + } else { + if (cachedRequest.firedAt.elapsedNow() > timeout) { + traceApiRequest(request, "suspiciously slow api response") + recache() + } else { + // Joining ongoing request + traceApiRequest(request, null) + cachedRequest.getCachedFuture() + } + } + } + } + +} diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/util/ErrorUtil.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/ErrorUtil.kt new file mode 100644 index 00000000..f849a40d --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/ErrorUtil.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 NotEnoughUpdates contributors + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * NotEnoughUpdates is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.util + +import io.github.moulberry.notenoughupdates.NotEnoughUpdates +import java.io.ByteArrayOutputStream +import java.io.PrintStream +import java.nio.charset.StandardCharsets + +object ErrorUtil { + @JvmStatic + fun printCensoredStackTrace(e: Throwable, toCensor: List<String>): String { + val baos = ByteArrayOutputStream() + e.printStackTrace(PrintStream(baos, true, StandardCharsets.UTF_8.name())) + var string = String(baos.toByteArray(), StandardCharsets.UTF_8) + toCensor.forEach { + string = string.replace(it, "*".repeat(it.length)) + } + return string + } + + @JvmStatic + fun printStackTraceWithoutApiKey(e: Throwable): String { + return printCensoredStackTrace(e, listOf(NotEnoughUpdates.INSTANCE.config.apiData.apiKey)) + } +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/MinecraftExecutor.java b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/MinecraftExecutor.kt index bf973b76..bb0bc8b4 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/util/MinecraftExecutor.java +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/MinecraftExecutor.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 NotEnoughUpdates contributors + * Copyright (C) 2023 NotEnoughUpdates contributors * * This file is part of NotEnoughUpdates. * @@ -17,21 +17,31 @@ * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. */ -package io.github.moulberry.notenoughupdates.util; +package io.github.moulberry.notenoughupdates.util -import net.minecraft.client.Minecraft; -import org.jetbrains.annotations.NotNull; +import net.minecraft.client.Minecraft +import java.util.concurrent.Executor +import java.util.concurrent.ForkJoinPool -import java.util.concurrent.Executor; +object MinecraftExecutor { -public class MinecraftExecutor implements Executor { + @JvmField + val OnThread = Executor { + val mc = Minecraft.getMinecraft() + if (mc.isCallingFromMinecraftThread) { + it.run() + } else { + Minecraft.getMinecraft().addScheduledTask(it) + } + } - public static MinecraftExecutor INSTANCE = new MinecraftExecutor(); - - private MinecraftExecutor() {} - - @Override - public void execute(@NotNull Runnable runnable) { - Minecraft.getMinecraft().addScheduledTask(runnable); - } + @JvmField + val OffThread = Executor { + val mc = Minecraft.getMinecraft() + if (mc.isCallingFromMinecraftThread) { + ForkJoinPool.commonPool().execute(it) + } else { + it.run() + } + } } diff --git a/src/main/resources/assets/notenoughupdates/pv_levels.png b/src/main/resources/assets/notenoughupdates/pv_levels.png Binary files differindex 21810476..0b6bb093 100644 --- a/src/main/resources/assets/notenoughupdates/pv_levels.png +++ b/src/main/resources/assets/notenoughupdates/pv_levels.png |