/* * Copyright (C) 2022-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 . */ package io.github.moulberry.notenoughupdates.overlays; import io.github.moulberry.notenoughupdates.NotEnoughUpdates; import io.github.moulberry.notenoughupdates.core.config.Position; import io.github.moulberry.notenoughupdates.core.util.StringUtils; import io.github.moulberry.notenoughupdates.miscfeatures.StorageManager; import io.github.moulberry.notenoughupdates.options.NEUConfig; import io.github.moulberry.notenoughupdates.util.ItemResolutionQuery; import io.github.moulberry.notenoughupdates.util.SBInfo; import io.github.moulberry.notenoughupdates.util.Utils; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.inventory.GuiChest; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.inventory.ContainerChest; import net.minecraft.inventory.IInventory; import net.minecraft.item.ItemStack; import net.minecraft.util.EnumChatFormatting; import org.lwjgl.util.vector.Vector2f; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; import static io.github.moulberry.notenoughupdates.util.Utils.showOutdatedRepoNotification; public class CrystalHollowOverlay extends TextOverlay { private static final Minecraft mc = Minecraft.getMinecraft(); private final StorageManager storageManager = StorageManager.getInstance(); private final Pattern notFoundPattern = Pattern.compile( "\\[NPC] Keeper of \\w+: Talk to me when you have found a (?[a-z-A-Z ]+)!"); private final Pattern foundPattern = Pattern.compile( "\\[NPC] Keeper of \\w+: Excellent! You have returned the (?[a-z-A-Z ]+) to its rightful place!"); private final Pattern resetPattern = Pattern.compile( "\\[NPC] Keeper of \\w+: (You found all of the items! Behold\\.\\.\\. the Jade Crystal!)"); private final Pattern alreadyFoundPattern = Pattern.compile( "\\[NPC] Keeper of \\w+: You have already restored this Dwarf's (?[a-z-A-Z ]+)!"); private final Pattern givePattern = Pattern.compile( "\\[NPC] Professor Robot: Thanks for bringing me the (?[a-zA-Z0-9 ]+)! Bring me (\\d+|one) more components? to fix the giant!"); private final Pattern notFinalPattern = Pattern.compile( "\\[NPC] Professor Robot: That's not the final component! Bring me a (?[a-zA-Z0-9 ]+) to gain access to Automaton Prime's storage container!"); private final Pattern obtainCrystalPattern = Pattern.compile(" +(?[a-zA-Z]+) Crystal"); private final Pattern crystalNotPlacedPattern = Pattern.compile( ".*: You haven't placed the (?[a-zA-Z]+) Crystal yet!"); private final Pattern crystalPlacedPattern = Pattern.compile( ".*: You have already placed the (?[a-zA-Z]+) Crystal!"); private final Pattern crystalPlacePattern = Pattern.compile("✦ You placed the (?[a-zA-Z]+) Crystal!"); private final Pattern crystalReclaimPattern = Pattern.compile("✦ You reclaimed the (?[a-zA-Z]+) Crystal!"); public CrystalHollowOverlay( Position position, Supplier> dummyStrings, Supplier styleSupplier ) { super(position, dummyStrings, styleSupplier); } private final Pattern hotmCrystalNotFoundPattern = Pattern.compile("(?[a-zA-Z]+) \\u2716 Not Found"); private final Pattern hotmCrystalNotPlacedPattern = Pattern.compile("(?[a-zA-Z]+) \\u2716 Not Placed"); private final Pattern hotmCrystalPlacedPattern = Pattern.compile("(?[a-zA-Z]+) \\u2714 Placed"); private void updateHotmCrystalState(IInventory lower) { NEUConfig.HiddenProfileSpecific perProfileConfig = NotEnoughUpdates.INSTANCE.config.getProfileSpecific(); if (perProfileConfig == null) return; ItemStack crystalStateStack = lower.getStackInSlot(50); if (crystalStateStack == null || !crystalStateStack.hasTagCompound()) { return; } String name = Utils.cleanColour(crystalStateStack.getDisplayName()).trim(); if (!name.equals("Crystal Hollows Crystals")) { return; } String[] lore = NotEnoughUpdates.INSTANCE.manager.getLoreFromNBT(crystalStateStack.getTagCompound()); for (String line : lore) { if (line == null) { continue; } String cleanLine = Utils.cleanColour(line).trim(); Matcher hotmCrystalNotPlacedMatcher = hotmCrystalNotPlacedPattern.matcher(cleanLine); Matcher hotmCrystalNotFoundMatcher = hotmCrystalNotFoundPattern.matcher(cleanLine); Matcher hotmCrystalPlacedMatcher = hotmCrystalPlacedPattern.matcher(cleanLine); if (hotmCrystalNotFoundMatcher.matches() && perProfileConfig.crystals.containsKey(hotmCrystalNotFoundMatcher.group("crystal"))) { perProfileConfig.crystals.put(hotmCrystalNotFoundMatcher.group("crystal"), 0); } else if (hotmCrystalNotPlacedMatcher.matches() && perProfileConfig.crystals.containsKey( hotmCrystalNotPlacedMatcher.group("crystal"))) { perProfileConfig.crystals.put(hotmCrystalNotPlacedMatcher.group("crystal"), 1); } else if (hotmCrystalPlacedMatcher.matches() && perProfileConfig.crystals.containsKey(hotmCrystalPlacedMatcher.group("crystal"))) { perProfileConfig.crystals.put(hotmCrystalPlacedMatcher.group("crystal"), 2); } } } @Override public void updateFrequent() { if (Minecraft.getMinecraft().currentScreen instanceof GuiChest) { GuiChest chest = (GuiChest) Minecraft.getMinecraft().currentScreen; ContainerChest container = (ContainerChest) chest.inventorySlots; IInventory lower = container.getLowerChestInventory(); String containerName = lower.getDisplayName().getUnformattedText(); if (containerName.equals("Heart of the Mountain") && lower.getSizeInventory() >= 54) { updateHotmCrystalState(lower); } } } @Override public boolean isEnabled() { return NotEnoughUpdates.INSTANCE.config.mining.crystalHollowOverlay; } @Override public void update() { overlayStrings = null; if (!isEnabled() || SBInfo.getInstance().getLocation() == null || !SBInfo.getInstance().getLocation().equals("crystal_hollows")) return; NEUConfig.HiddenProfileSpecific hidden = NotEnoughUpdates.INSTANCE.config.getProfileSpecific(); if (hidden == null) return; overlayStrings = new ArrayList<>(); HashMap inventoryData = new HashMap<>(); for (String key : hidden.automatonParts.keySet()) inventoryData.put(key, 0); for (String key : hidden.divanMinesParts.keySet()) inventoryData.put(key, 0); HashMap storageData = new HashMap<>(inventoryData); for (ItemStack item : mc.thePlayer.inventory.mainInventory) if (item != null) { String name = Utils.cleanColour(item.getDisplayName()); if (inventoryData.containsKey(name)) inventoryData.put(name, inventoryData.get(name) + item.stackSize); } for (Map.Entry entry : storageManager.storageConfig.displayToStorageIdMap.entrySet()) { int storageId = entry.getValue(); StorageManager.StoragePage page = storageManager.getPage(storageId, false); if (page != null && page.rows > 0) for (ItemStack item : page.items) if (item != null) { String name = Utils.cleanColour(item.getDisplayName()); if (storageData.containsKey(name)) storageData.put(name, storageData.get(name) + item.stackSize); } } for (int i : NotEnoughUpdates.INSTANCE.config.mining.crystalHollowText) { switch (i) { case 0: if (crystalCheck()) { for (String part : hidden.crystals.keySet()) { switch (hidden.crystals.get(part)) { case 2: if (!NotEnoughUpdates.INSTANCE.config.mining.crystalHollowHideDone) overlayStrings.add( EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowPartColor] + part + ": " + EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowPlacedColor] + "Placed"); break; case 1: overlayStrings.add( EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowPartColor] + part + ": " + EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowCollectedColor] + "Collected"); break; case 0: overlayStrings.add( EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowPartColor] + part + ": " + EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowMissingColor] + "Missing"); break; } } } break; case 1: if (crystalCheck()) { int count = getCountCrystal(hidden.crystals); float percent = (float) count / hidden.crystals.size() * 100; overlayStrings.add( EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowPartColor] + "Crystals: " + getColor(percent) + count + "/" + hidden.crystals.size()); } break; case 2: if (crystalCheck()) { int count = getCountCrystal(hidden.crystals); float percent = (float) count / hidden.crystals.size() * 100; overlayStrings.add( EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowPartColor] + "Crystals: " + getColor(percent) + StringUtils.formatToTenths(percent) + "%"); } break; case 3: if (automatonCheck()) renderParts(hidden.automatonParts, inventoryData, storageData); break; case 4: if (automatonCheck()) renderPartsNumbers(hidden.automatonParts, inventoryData, storageData); break; case 5: if (automatonCheck()) renderCount("Automaton parts", hidden.automatonParts, inventoryData, storageData); break; case 6: if (automatonCheck()) renderPercent("Automaton parts", hidden.automatonParts, inventoryData, storageData); break; case 7: if (divanCheck()) renderParts(hidden.divanMinesParts, inventoryData, storageData); break; case 8: if (divanCheck()) renderPartsNumbers(hidden.divanMinesParts, inventoryData, storageData); break; case 9: if (divanCheck()) renderCount("Mines of Divan parts", hidden.divanMinesParts, inventoryData, storageData); break; case 10: if (divanCheck()) renderPercent("Mines of Divan parts", hidden.divanMinesParts, inventoryData, storageData); break; } } } private void renderParts( HashMap parts, HashMap inventoryData, HashMap storageData ) { for (String part : parts.keySet()) { if (parts.get(part) && !NotEnoughUpdates.INSTANCE.config.mining.crystalHollowHideDone) overlayStrings.add( EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowPartColor] + part + ": " + EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowDoneColor] + "Done"); else if (inventoryData.get(part) >= 1) overlayStrings.add( EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowPartColor] + part + ": " + EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowInventoryColor] + "In Inventory"); else if (storageData.get(part) >= 1) overlayStrings.add( EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowPartColor] + part + ": " + EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowStorageColor] + "In Storage"); else overlayStrings.add( EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowPartColor] + part + ": " + EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowMissingColor] + "Missing"); } } private void renderPartsNumbers( HashMap parts, HashMap inventoryData, HashMap storageData ) { for (String part : parts.keySet()) { if (parts.get(part)) overlayStrings.add( EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowPartColor] + part + ": " + EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowDoneColor] + (inventoryData.get(part) + storageData.get(part))); else if (inventoryData.get(part) >= 1) overlayStrings.add( EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowPartColor] + part + ": " + EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowInventoryColor] + (inventoryData.get(part) + storageData.get(part))); else if (storageData.get(part) >= 1) overlayStrings.add( EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowPartColor] + part + ": " + EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowStorageColor] + (inventoryData.get(part) + storageData.get(part))); else overlayStrings.add( EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowPartColor] + part + ": " + EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowMissingColor] + (inventoryData.get(part) + storageData.get(part))); } } private void renderCount( String text, HashMap parts, HashMap inventoryData, HashMap storageData ) { int count = getCount(parts, inventoryData, storageData); float percent = (float) count / parts.size() * 100; overlayStrings.add( EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowPartColor] + text + ": " + getColor(percent) + count + "/" + parts.size()); } private void renderPercent( String text, HashMap parts, HashMap inventoryData, HashMap storageData ) { int count = getCount(parts, inventoryData, storageData); float percent = (float) count / parts.size() * 100; overlayStrings.add( EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowPartColor] + text + ": " + getColor(percent) + StringUtils.formatToTenths(percent) + "%"); } private EnumChatFormatting getColor(float percent) { if (percent >= 66) return EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowAllColor]; else if (percent >= 33) return EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowMiddleColor]; else return EnumChatFormatting.values()[NotEnoughUpdates.INSTANCE.config.mining.crystalHollowNoneColor]; } private int getCount( HashMap parts, HashMap inventoryData, HashMap storageData ) { int count = 0; for (String part : parts.keySet()) if (parts.get(part) || inventoryData.get(part) > 0 || storageData.get(part) > 0) count++; return count; } private int getCountCrystal(HashMap parts) { int count = 0; for (String part : parts.keySet()) if (parts.get(part) > 0) count++; return count; } private boolean automatonCheck() { return NotEnoughUpdates.INSTANCE.config.mining.crystalHollowAutomatonLocation == 0 || NotEnoughUpdates.INSTANCE.config.mining.crystalHollowAutomatonLocation == 1 && SBInfo.getInstance().location.equals("Precursor Remnants") || NotEnoughUpdates.INSTANCE.config.mining.crystalHollowAutomatonLocation >= 1 && SBInfo.getInstance().location.equals("Lost Precursor City"); } private boolean divanCheck() { return NotEnoughUpdates.INSTANCE.config.mining.crystalHollowDivanLocation == 0 || NotEnoughUpdates.INSTANCE.config.mining.crystalHollowDivanLocation == 1 && SBInfo.getInstance().location.equals("Mithril Deposits") || NotEnoughUpdates.INSTANCE.config.mining.crystalHollowDivanLocation >= 1 && SBInfo.getInstance().location.equals("Mines of Divan"); } private boolean crystalCheck() { return NotEnoughUpdates.INSTANCE.config.mining.crystalHollowCrystalLocation == 0 || !divanCheck() && !automatonCheck(); } public void message(String message) { NEUConfig.HiddenProfileSpecific hidden = NotEnoughUpdates.INSTANCE.config.getProfileSpecific(); if (hidden == null) return; Matcher crystalNotPlacedMatcher = crystalNotPlacedPattern.matcher(message); Matcher crystalPlacedMatcher = crystalPlacedPattern.matcher(message); Matcher crystalPlaceMatcher = crystalPlacePattern.matcher(message); Matcher crystalReclaimMatcher = crystalReclaimPattern.matcher(message); if (message.startsWith(" CRYSTAL NUCLEUS LOOT BUNDLE")) hidden.crystals.replaceAll((k, v) -> 0); if (crystalNotPlacedMatcher.matches() && hidden.crystals.containsKey(crystalNotPlacedMatcher.group("crystal"))) { hidden.crystals.put(crystalNotPlacedMatcher.group("crystal"), 1); resetCrystal(hidden, crystalNotPlacedMatcher.group("crystal")); } else if (crystalPlacedMatcher.matches() && hidden.crystals.containsKey(crystalPlacedMatcher.group("crystal"))) { hidden.crystals.put(crystalPlacedMatcher.group("crystal"), 2); resetCrystal(hidden, crystalPlacedMatcher.group("crystal")); } else if (crystalPlaceMatcher.matches() && hidden.crystals.containsKey(crystalPlaceMatcher.group("crystal"))) hidden.crystals.put(crystalPlaceMatcher.group("crystal"), 2); else if (crystalReclaimMatcher.matches() && hidden.crystals.containsKey(crystalReclaimMatcher.group("crystal"))) hidden.crystals.put(crystalReclaimMatcher.group("crystal"), 1); else if (message.startsWith("[NPC] Keeper of ")) { Matcher foundMatcher = foundPattern.matcher(message); Matcher alreadyFoundMatcher = alreadyFoundPattern.matcher(message); Matcher notFoundMatcher = notFoundPattern.matcher(message); Matcher resetMatcher = resetPattern.matcher(message); if (foundMatcher.matches() && hidden.divanMinesParts.containsKey(foundMatcher.group("item"))) hidden.divanMinesParts.put(foundMatcher.group("item"), true); else if (notFoundMatcher.matches() && hidden.divanMinesParts.containsKey(notFoundMatcher.group("item"))) hidden.divanMinesParts.put(notFoundMatcher.group("item"), false); else if (resetMatcher.matches()) hidden.divanMinesParts.replaceAll((k, v) -> false); else if (alreadyFoundMatcher.matches() && hidden.divanMinesParts.containsKey(alreadyFoundMatcher.group("item"))) hidden.divanMinesParts.put(alreadyFoundMatcher.group("item"), true); } else if (message.startsWith(" ")) { Matcher crystalMatcher = obtainCrystalPattern.matcher(message); if (crystalMatcher.matches() && hidden.crystals.containsKey(crystalMatcher.group("crystal"))) hidden.crystals.put(crystalMatcher.group("crystal"), 1); else { String item = message.replace(" ", ""); if (hidden.automatonParts.containsKey(item)) hidden.automatonParts.put(item, false); } } else if (message.startsWith("[NPC] Professor Robot: ")) { if (message.equals("[NPC] Professor Robot: That's not one of the components I need! Bring me one of the missing components:")) { hidden.automatonParts.replaceAll((k, v) -> true); } else if (message.startsWith("[NPC] Professor Robot: You've brought me all of the components")) { hidden.automatonParts.replaceAll((k, v) -> false); } else { Matcher giveMatcher = givePattern.matcher(message); Matcher notFinalMatcher = notFinalPattern.matcher(message); if (giveMatcher.matches()) { String item = giveMatcher.group("part"); if (hidden.automatonParts.containsKey(item)) { hidden.automatonParts.put(item, true); } } else if (notFinalMatcher.matches()) { String item = notFinalMatcher.group("part"); if (hidden.automatonParts.containsKey(item)) { hidden.automatonParts.replaceAll((k, v) -> true); hidden.automatonParts.put(item, false); } } } } } private void resetCrystal(NEUConfig.HiddenProfileSpecific hidden, String crystal) { switch (crystal) { case "Sapphire": hidden.automatonParts.replaceAll((k, v) -> false); break; case "Jade": hidden.divanMinesParts.replaceAll((k, v) -> false); break; } } @Override protected void renderLine(String line, Vector2f position, boolean dummy) { if (!NotEnoughUpdates.INSTANCE.config.mining.crystalHollowIcons) return; GlStateManager.enableDepth(); ItemStack icon = null; String cleaned = Utils.cleanColour(line); String beforeColon = cleaned.split(":")[0]; if (crystallHollowsIcons == null) { setupCrystallHollowsIcons(); } if (crystallHollowsIcons.containsKey(beforeColon)) { icon = crystallHollowsIcons.get(beforeColon); } if (icon != null) { GlStateManager.pushMatrix(); GlStateManager.translate(position.x, position.y, 0); GlStateManager.scale(0.5f, 0.5f, 1f); Utils.drawItemStack(icon, 0, 0); GlStateManager.popMatrix(); position.x += 12; } super.renderLine(line, position, dummy); } private static Map crystallHollowsIcons; private static void setupCrystallHollowsIcons() { crystallHollowsIcons = new HashMap() { { addItem("Scavenged Lapis Sword", "DWARVEN_LAPIS_SWORD"); addItem("Scavenged Golden Hammer", "DWARVEN_GOLD_HAMMER"); addItem("Scavenged Diamond Axe", "DWARVEN_DIAMOND_AXE"); addItem("Scavenged Emerald Hammer", "DWARVEN_EMERALD_HAMMER"); addItem("Electron Transmitter", "ELECTRON_TRANSMITTER"); addItem("FTX 3070", "FTX_3070"); addItem("Robotron Reflector", "ROBOTRON_REFLECTOR"); addItem("Superlite Motor", "SUPERLITE_MOTOR"); addItem("Control Switch", "CONTROL_SWITCH"); addItem("Synthetic Heart", "SYNTHETIC_HEART"); addItem("Amber", "PERFECT_AMBER_GEM"); addItem("Sapphire", "PERFECT_SAPPHIRE_GEM"); addItem("Jade", "PERFECT_JADE_GEM"); addItem("Amethyst", "PERFECT_AMETHYST_GEM"); addItem("Topaz", "PERFECT_TOPAZ_GEM"); } private void addItem(String eventName, String internalName) { ItemStack itemStack = new ItemResolutionQuery(NotEnoughUpdates.INSTANCE.manager) .withKnownInternalName(internalName).resolveToItemStack(); if (itemStack == null) { showOutdatedRepoNotification(internalName); return; } put(eventName, itemStack.copy()); } }; } @Override protected Vector2f getSize(List strings) { if (NotEnoughUpdates.INSTANCE.config.mining.crystalHollowIcons) return super.getSize(strings).translate(12, 0); return super.getSize(strings); } }