diff options
Diffstat (limited to 'src')
6 files changed, 208 insertions, 131 deletions
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/DungeonNpcProfitOverlay.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/DungeonNpcProfitOverlay.java index c06563c2..96e0765d 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/DungeonNpcProfitOverlay.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/DungeonNpcProfitOverlay.java @@ -19,24 +19,29 @@ package io.github.moulberry.notenoughupdates.miscfeatures; -import com.google.gson.JsonArray; 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.ButtonExclusionZoneEvent; +import io.github.moulberry.notenoughupdates.events.DrawSlotReturnEvent; import io.github.moulberry.notenoughupdates.mixins.AccessorGuiContainer; +import io.github.moulberry.notenoughupdates.util.ItemResolutionQuery; import io.github.moulberry.notenoughupdates.util.ItemUtils; import io.github.moulberry.notenoughupdates.util.Rectangle; -import io.github.moulberry.notenoughupdates.util.SBInfo; import io.github.moulberry.notenoughupdates.util.Utils; +import lombok.Getter; +import lombok.var; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Gui; import net.minecraft.client.gui.ScaledResolution; import net.minecraft.client.gui.inventory.GuiChest; import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.init.Items; +import net.minecraft.init.Blocks; +import net.minecraft.inventory.ContainerChest; +import net.minecraft.inventory.IInventory; import net.minecraft.inventory.Slot; +import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.ResourceLocation; @@ -49,11 +54,13 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import java.awt.*; import java.util.ArrayList; import java.util.Comparator; +import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; @NEUAutoSubscribe public class DungeonNpcProfitOverlay { @@ -64,41 +71,54 @@ public class DungeonNpcProfitOverlay { private static final Pattern chestNamePattern = Pattern.compile(".+ Catacombs - Floor .+"); private static final Pattern essencePattern = Pattern.compile( "^§.(?<essenceType>\\w+) Essence §.x(?<essenceAmount>\\d+)$"); - private static final Pattern enchantedBookPattern = Pattern.compile("^§.Enchanted Book \\((?<enchantName>.*)\\)"); - private static List<DungeonChest> chestProfits; - private static List<Slot> previousSlots; + private static final Pattern enchantedBookPattern = Pattern.compile("^§.Enchanted Book \\((?<enchantName>.*)§.\\)"); + private static final Map<Integer, DungeonChest> chestProfits = new HashMap<>(); + private static List<DungeonChest> orderedChestProfits = new ArrayList<>(); /** * Check the current status for the overlay * * @return if the overlay is rendering right now */ - public static boolean isRendering() { - return NotEnoughUpdates.INSTANCE.config.dungeons.croesusProfitOverlay && chestProfits != null; + public boolean isRendering() { + return NotEnoughUpdates.INSTANCE.config.dungeons.croesusProfitOverlay && !chestProfits.isEmpty(); + } + + private boolean isChestOverview(IInventory inventory) { + return chestNamePattern.matcher(StringUtils.cleanColour( + inventory.getDisplayName() + .getUnformattedText())).matches(); + } + + private boolean isChestOverview(GuiChest chest) { + ContainerChest inventorySlots = (ContainerChest) chest.inventorySlots; + return isChestOverview(inventorySlots.getLowerChestInventory()); } /** * Highlight the slot that is being drawn if applicable. Called by MixinGuiContainer * - * @param slot the slot to be checked * @see io.github.moulberry.notenoughupdates.mixins.MixinGuiContainer#drawSlotRet(Slot, CallbackInfo) */ - public static void onDrawSlot(Slot slot) { - if (isRendering() && NotEnoughUpdates.INSTANCE.config.dungeons.croesusHighlightHighestProfit) { - for (DungeonChest chestProfit : chestProfits) { - if (chestProfit.shouldHighlight) { - if (slot.slotNumber == chestProfit.slot) { - Gui.drawRect( - slot.xDisplayPosition, - slot.yDisplayPosition, - slot.xDisplayPosition + 16, - slot.yDisplayPosition + 16, - Color.GREEN.getRGB() - ); - } - } - } + @SubscribeEvent + public void onDrawSlot(DrawSlotReturnEvent event) { + if (!NotEnoughUpdates.INSTANCE.config.dungeons.croesusProfitOverlay + || !NotEnoughUpdates.INSTANCE.config.dungeons.croesusHighlightHighestProfit + || !isChestOverview(event.getSlot().inventory)) { + return; } + var slot = event.getSlot(); + var chestProfit = getPotentialChest(slot); + if (chestProfit == null || !chestProfit.shouldHighlight || slot.slotNumber != chestProfit.slot) { + return; + } + Gui.drawRect( + slot.xDisplayPosition, + slot.yDisplayPosition, + slot.xDisplayPosition + 16, + slot.yDisplayPosition + 16, + Color.GREEN.getRGB() + ); } @SubscribeEvent @@ -117,92 +137,83 @@ public class DungeonNpcProfitOverlay { @SubscribeEvent public void onDrawBackground(GuiScreenEvent.BackgroundDrawnEvent event) { if (!NotEnoughUpdates.INSTANCE.config.dungeons.croesusProfitOverlay || !(event.gui instanceof GuiChest)) { - chestProfits = null; - previousSlots = null; - return; - } - - String lastOpenChestName = SBInfo.getInstance().lastOpenChestName; - Matcher matcher = chestNamePattern.matcher(lastOpenChestName); - if (!matcher.matches()) { - chestProfits = null; - previousSlots = null; + chestProfits.clear(); return; } GuiChest guiChest = (GuiChest) event.gui; - List<Slot> slots = guiChest.inventorySlots.inventorySlots; - - if (chestProfits == null || chestProfits.isEmpty() || !slots.equals(previousSlots)) { - updateDungeonChests(slots); + if (!isChestOverview(guiChest)) { + chestProfits.clear(); + return; } - previousSlots = guiChest.inventorySlots.inventorySlots; - render(guiChest); } - /** - * Update the profit applicable for the chests currently visible - * - * @param inventorySlots list of Slots from the GUI containing the dungeon chest previews - */ - private void updateDungeonChests(List<Slot> inventorySlots) { - chestProfits = new ArrayList<>(); - //loop through the upper chest - for (int i = 0; i < 27; i++) { - Slot inventorySlot = inventorySlots.get(i); - if (inventorySlot == null) { - continue; + private @Nullable DungeonChest getPotentialChest(@Nullable Slot slot) { + if (slot == null) return null; + DungeonChest chestProfit = chestProfits.get(slot.slotNumber); + if (chestProfit == null) { + long s = System.currentTimeMillis(); + updatePotentialChest(slot.getStack(), slot); + long d = System.currentTimeMillis() - s; + if (d > 10) { + System.out.println("Finished analyzing slow croesus slot. Took " + d + " ms"); + ItemUtils.getLore(slot.getStack()).forEach(System.out::println); } + } + return chestProfits.get(slot.slotNumber); + } - ItemStack stack = inventorySlot.getStack(); - if (stack != null && stack.getItem() != null && stack.getItem() == Items.skull) { - //each item is a DungeonChest - DungeonChest dungeonChest = new DungeonChest(); - dungeonChest.slot = i; - - List<String> lore = ItemUtils.getLore(stack); - if ("§7Contents".equals(lore.get(0))) { - dungeonChest.name = stack.getDisplayName(); - List<SkyblockItem> items = new ArrayList<>(); - for (String s : lore) { - //check if this line is showing the cost of opening the Chest - if (s.endsWith(" Coins")) { - String coinString = StringUtils.cleanColour(s); - int whitespace = coinString.indexOf(' '); - if (whitespace != -1) { - String amountString = coinString.substring(0, whitespace).replace(",", ""); - dungeonChest.costToOpen = Integer.parseInt(amountString); - continue; - } - } else if (s.equals("§aFREE")) { - dungeonChest.costToOpen = 0; - } - - //check if the line can be converted to a SkyblockItem - SkyblockItem skyblockItem = SkyblockItem.createFromLoreEntry(s); - if (skyblockItem != null) { - items.add(skyblockItem); - } - } - dungeonChest.items = items; - if (dungeonChest.costToOpen != -1) { - dungeonChest.calculateProfitAndBuildLore(); - chestProfits.add(dungeonChest); - } + private void updatePotentialChest(ItemStack stack, Slot slot) { + if (stack == null || stack.getItem() == Item.getItemFromBlock(Blocks.stained_glass_pane)) return; + DungeonChest dungeonChest = new DungeonChest(); + dungeonChest.slot = slot.slotNumber; + + List<String> lore = ItemUtils.getLore(stack); + if (lore.size() == 0 || !"§7Contents".equals(lore.get(0))) { + return; + } + dungeonChest.name = stack.getDisplayName(); + List<SkyblockItem> items = new ArrayList<>(); + for (String s : lore) { + + if ("§7Contents".equals(s) || "".equals(s) || "§7Cost".equals(s) || "§cCan't open another chest!".equals(s) || + "§aAlready opened!".equals(s) || + "§eClick to open!".equals(s)) continue; + + //check if this line is showing the cost of opening the Chest + if (s.endsWith(" Coins")) { + String coinString = StringUtils.cleanColour(s); + int whitespace = coinString.indexOf(' '); + if (whitespace != -1) { + String amountString = coinString.substring(0, whitespace).replace(",", ""); + dungeonChest.costToOpen = Integer.parseInt(amountString); + continue; } + } else if (s.equals("§aFREE")) { + dungeonChest.costToOpen = 0; + continue; } - } - if (NotEnoughUpdates.INSTANCE.config.dungeons.croesusSortByProfit) { - chestProfits.sort(Comparator.comparing(DungeonChest::getProfit).reversed()); + //check if the line can be converted to a SkyblockItem + SkyblockItem skyblockItem = SkyblockItem.createFromLoreEntry(s); + if (skyblockItem != null) { + items.add(skyblockItem); + } else + System.out.println("Unexpected line " + s + " while analyzing croesus lore"); } - - if (NotEnoughUpdates.INSTANCE.config.dungeons.croesusHighlightHighestProfit && chestProfits.size() >= 1) { - List<DungeonChest> copiedList = new ArrayList<>(chestProfits); - copiedList.sort(Comparator.comparing(DungeonChest::getProfit).reversed()); - - copiedList.get(0).shouldHighlight = true; + dungeonChest.items = items; + if (dungeonChest.costToOpen != -1) { + dungeonChest.calculateProfitAndBuildLore(); + chestProfits.put(slot.slotNumber, dungeonChest); } + orderedChestProfits = chestProfits.values().stream() + .sorted(NotEnoughUpdates.INSTANCE.config.dungeons.croesusSortByProfit + ? Comparator.comparing(DungeonChest::getProfit).reversed() + : Comparator.comparing(DungeonChest::getSlot)) + .collect(Collectors.toList()); + chestProfits.values().forEach(it -> it.shouldHighlight = false); + chestProfits.values().stream().max(Comparator.comparing(DungeonChest::getProfit)).ifPresent(it -> + it.shouldHighlight = true); } public void render(GuiChest guiChest) { @@ -214,8 +225,8 @@ public class DungeonNpcProfitOverlay { GlStateManager.disableLighting(); Utils.drawTexturedRect(guiLeft + xSize + 4, guiTop, 180, 101, 0, 180 / 256f, 0, 101 / 256f, GL11.GL_NEAREST); - for (int i = 0; i < chestProfits.size(); i++) { - DungeonChest chestProfit = chestProfits.get(i); + for (int i = 0; i < orderedChestProfits.size(); i++) { + DungeonChest chestProfit = orderedChestProfits.get(i); int x = guiLeft + xSize + 14; int y = guiTop + 6 + (i * 10); Utils.renderAlignedString( @@ -257,6 +268,7 @@ public class DungeonNpcProfitOverlay { private List<String> lore; private int costToOpen = -1; private String name; + @Getter private int slot; private boolean shouldHighlight; private double profit; @@ -329,20 +341,9 @@ public class DungeonNpcProfitOverlay { Matcher enchantedBookMatcher = enchantedBookPattern.matcher(line); if (enchantedBookMatcher.matches()) { - String enchant = StringUtils.cleanColour(enchantedBookMatcher.group("enchantName")); - - for (Map.Entry<String, JsonObject> entry : NotEnoughUpdates.INSTANCE.manager - .getItemInformation() - .entrySet()) { - String displayName = StringUtils.cleanColour(entry.getValue().get("displayname").getAsString()); - if (displayName.equals("Enchanted Book")) { - JsonArray lore = entry.getValue().get("lore").getAsJsonArray(); - String enchantName = StringUtils.cleanColour(lore.get(0).getAsString()); - if (enchant.equals(enchantName)) { - return new SkyblockItem(entry.getKey(), 1); - } - } - } + String enchantName = ItemResolutionQuery.resolveEnchantmentByName(enchantedBookMatcher.group("enchantName")); + if (enchantName == null) return null; + return new SkyblockItem(enchantName, 1); } else if (essenceMatcher.matches() && NotEnoughUpdates.INSTANCE.config.dungeons.useEssenceCostFromBazaar) { String essenceType = essenceMatcher.group("essenceType"); String essenceAmount = essenceMatcher.group("essenceAmount"); @@ -359,18 +360,12 @@ public class DungeonNpcProfitOverlay { int amount = Integer.parseInt(essenceAmount); return new SkyblockItem(internalName, amount); } else { - String s = StringUtils.cleanColour(line.trim()); - for (Map.Entry<String, JsonObject> entries : NotEnoughUpdates.INSTANCE.manager - .getItemInformation() - .entrySet()) { - String displayName = entries.getValue().get("displayname").getAsString(); - String cleanDisplayName = StringUtils.cleanColour(displayName); - if (s.equals(cleanDisplayName)) { - return new SkyblockItem(entries.getKey(), 1); - } - } + // Remove Book (from hot potato book), as a perf optimization since "book" is a very common phrase + String id = ItemResolutionQuery.findInternalNameByDisplayName( + line.trim().replace("Book", ""), true); + if (id == null) return null; + return new SkyblockItem(id, 1); } - return null; } /** diff --git a/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinGuiContainer.java b/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinGuiContainer.java index adae04f8..04f3ba93 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinGuiContainer.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinGuiContainer.java @@ -21,6 +21,7 @@ package io.github.moulberry.notenoughupdates.mixins; import io.github.moulberry.notenoughupdates.NEUOverlay; import io.github.moulberry.notenoughupdates.NotEnoughUpdates; +import io.github.moulberry.notenoughupdates.events.DrawSlotReturnEvent; import io.github.moulberry.notenoughupdates.events.GuiContainerBackgroundDrawnEvent; import io.github.moulberry.notenoughupdates.events.SlotClickEvent; import io.github.moulberry.notenoughupdates.listener.RenderListener; @@ -78,7 +79,7 @@ public abstract class MixinGuiContainer extends GuiScreen { @Inject(method = "drawSlot", at = @At("RETURN")) public void drawSlotRet(Slot slotIn, CallbackInfo ci) { SlotLocking.getInstance().drawSlot(slotIn); - DungeonNpcProfitOverlay.onDrawSlot(slotIn); + new DrawSlotReturnEvent(slotIn).post(); } @Inject(method = "drawSlot", at = @At("HEAD"), cancellable = true) diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/IdentityCharacteristics.java b/src/main/java/io/github/moulberry/notenoughupdates/util/IdentityCharacteristics.java new file mode 100644 index 00000000..76607ea0 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/util/IdentityCharacteristics.java @@ -0,0 +1,52 @@ +/* + * 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; + +/** + * Wrapper around a {@link T} that implements hashing and equality according to object identity instead of the objects + * default equals implementation. + */ +public final class IdentityCharacteristics<T> { + private final T object; + + public IdentityCharacteristics(T object) { + this.object = object; + } + + public static <T> IdentityCharacteristics<T> of(T object) { + return new IdentityCharacteristics<>(object); + } + + public T getObject() { + return object; + } + + @Override + public int hashCode() { + return System.identityHashCode(object); + } + + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") + @Override + public boolean equals(Object obj) { + return obj == object; + } + +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/ItemResolutionQuery.java b/src/main/java/io/github/moulberry/notenoughupdates/util/ItemResolutionQuery.java index cfeecf66..33ed176e 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/util/ItemResolutionQuery.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/util/ItemResolutionQuery.java @@ -46,7 +46,7 @@ import java.util.regex.Pattern; public class ItemResolutionQuery { - private static final Pattern ENCHANTED_BOOK_NAME_PATTERN = Pattern.compile("^((?:§.)+)([^§]+) ([IVXL]+)$"); + private static final Pattern ENCHANTED_BOOK_NAME_PATTERN = Pattern.compile("^((?:§.)*)([^§]+) ([IVXL]+)$"); private static final String EXTRA_ATTRIBUTES = "ExtraAttributes"; private static final List<String> PET_RARITIES = Arrays.asList( "COMMON", @@ -203,9 +203,7 @@ public class ItemResolutionQuery { String bestMatch = null; int bestMatchLength = -1; for (String internalName : findInternalNameCandidatesForDisplayName(cleanDisplayName)) { - var item = manager.createItem(internalName); - if (item.getDisplayName() == null) continue; - var cleanItemDisplayName = StringUtils.cleanColour(item.getDisplayName()); + var cleanItemDisplayName = StringUtils.cleanColour(manager.getDisplayName(internalName)); if (cleanItemDisplayName.length() == 0) continue; if (mayBeMangled ? !cleanDisplayName.contains(cleanItemDisplayName) @@ -234,6 +232,7 @@ public class ItemResolutionQuery { var titleWordMap = NotEnoughUpdates.INSTANCE.manager.titleWordMap; var candidates = new HashSet<String>(); for (var partialDisplayName : cleanDisplayName.split(" ")) { + if ("".equals(partialDisplayName)) continue; if (!titleWordMap.containsKey(partialDisplayName)) continue; candidates.addAll(titleWordMap.get(partialDisplayName).keySet()); } @@ -253,7 +252,7 @@ public class ItemResolutionQuery { return null; } - private String resolveEnchantmentByName(String name) { + public static String resolveEnchantmentByName(String name) { Matcher matcher = ENCHANTED_BOOK_NAME_PATTERN.matcher(name); if (!matcher.matches()) return null; String format = matcher.group(1).toLowerCase(Locale.ROOT); @@ -261,12 +260,12 @@ public class ItemResolutionQuery { String romanLevel = matcher.group(3); boolean ultimate = (format.contains("§l")); - return ((ultimate && !name.equals("Ultimate Wise")) ? "ULTIMATE_" : "") + return ((ultimate && !enchantmentName.equals("Ultimate Wise")) ? "ULTIMATE_" : "") + turboCheck(enchantmentName).replace(" ", "_").replace("-", "_").toUpperCase(Locale.ROOT) + ";" + Utils.parseRomanNumeral(romanLevel); } - private String turboCheck(String text) { + private static String turboCheck(String text) { if (text.equals("Turbo-Cocoa")) return "Turbo-Coco"; if (text.equals("Turbo-Cacti")) return "Turbo-Cactus"; diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/ItemUtils.java b/src/main/java/io/github/moulberry/notenoughupdates/util/ItemUtils.java index f8f42b80..a0eaa12e 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/util/ItemUtils.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/util/ItemUtils.java @@ -119,6 +119,7 @@ public class ItemUtils { } public static List<String> getLore(ItemStack is) { + if (is == null) return new ArrayList<>(); return getLore(is.getTagCompound()); } diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/events/DrawSlotReturnEvent.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/events/DrawSlotReturnEvent.kt new file mode 100644 index 00000000..43ff56f4 --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/events/DrawSlotReturnEvent.kt @@ -0,0 +1,29 @@ +/* + * 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.events + +import net.minecraft.inventory.Slot +import net.minecraft.item.ItemStack +import net.minecraft.nbt.CompressedStreamTools +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.util.* + +data class DrawSlotReturnEvent(val slot: Slot) : NEUEvent() |