diff options
15 files changed, 735 insertions, 89 deletions
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/NEUManager.java b/src/main/java/io/github/moulberry/notenoughupdates/NEUManager.java index 5c3083a3..ce386b86 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/NEUManager.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/NEUManager.java @@ -103,7 +103,7 @@ public class NEUManager { private final TreeMap<String, JsonObject> itemMap = new TreeMap<>(); private boolean hasBeenLoadedBefore = false; - private final TreeMap<String, HashMap<String, List<Integer>>> titleWordMap = new TreeMap<>(); + public final TreeMap<String, HashMap<String, List<Integer>>> titleWordMap = new TreeMap<>(); private final TreeMap<String, HashMap<String, List<Integer>>> loreWordMap = new TreeMap<>(); public final KeyBinding keybindGive = @@ -323,7 +323,7 @@ public class NEUManager { synchronized (titleWordMap) { int wordIndex = 0; for (String str : json.get("displayname").getAsString().split(" ")) { - str = clean(str); + str = cleanForTitleMapSearch(str); if (!titleWordMap.containsKey(str)) { titleWordMap.put(str, new HashMap<>()); } @@ -341,7 +341,7 @@ public class NEUManager { int wordIndex = 0; for (JsonElement element : json.get("lore").getAsJsonArray()) { for (String str : element.getAsString().split(" ")) { - str = clean(str); + str = cleanForTitleMapSearch(str); if (!loreWordMap.containsKey(str)) { loreWordMap.put(str, new HashMap<>()); } @@ -469,8 +469,8 @@ public class NEUManager { int lastStringMatch = -1; ArrayList<DebugMatch> debugMatches = new ArrayList<>(); - toSearch = clean(toSearch).toLowerCase(); - query = clean(query).toLowerCase(); + toSearch = cleanForTitleMapSearch(toSearch).toLowerCase(); + query = cleanForTitleMapSearch(query).toLowerCase(); String[] splitToSearch = toSearch.split(" "); String[] queryArray = query.split(" "); @@ -687,7 +687,7 @@ public class NEUManager { public Set<String> search(String query, TreeMap<String, HashMap<String, List<Integer>>> wordMap) { HashMap<String, List<Integer>> matches = null; - query = clean(query).toLowerCase(); + query = cleanForTitleMapSearch(query).toLowerCase(); for (String queryWord : query.split(" ")) { HashMap<String, List<Integer>> matchesToKeep = new HashMap<>(); for (HashMap<String, List<Integer>> wordMatches : subMapWithKeysThatAreSuffixes(queryWord, wordMap).values()) { @@ -862,7 +862,7 @@ public class NEUManager { return item; } - private String clean(String str) { + public static String cleanForTitleMapSearch(String str) { return str.replaceAll("(\u00a7.)|[^0-9a-zA-Z ]", "").toLowerCase().trim(); } diff --git a/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java b/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java index a4643eb5..94c7cc2b 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java @@ -169,6 +169,13 @@ public class NotEnoughUpdates { private File neuDir; private boolean hasSkyblockScoreboard; + public NotEnoughUpdates() { + // Budget Construction Event + ((AccessorMinecraft) FMLClientHandler.instance().getClient()) + .onGetDefaultResourcePacks() + .add(new NEURepoResourcePack(null, "neurepo")); + } + public File getConfigFile() { return this.configFile; } @@ -181,13 +188,6 @@ public class NotEnoughUpdates { return this.neuDir; } - public NotEnoughUpdates() { - // Budget Construction Event - ((AccessorMinecraft) FMLClientHandler.instance().getClient()) - .onGetDefaultResourcePacks() - .add(new NEURepoResourcePack(null, "neurepo")); - } - /** * Instantiates NEUIo, NEUManager and NEUOverlay instances. Registers keybinds and adds a shutdown hook to clear tmp folder. */ diff --git a/src/main/java/io/github/moulberry/notenoughupdates/events/GuiContainerBackgroundDrawnEvent.java b/src/main/java/io/github/moulberry/notenoughupdates/events/GuiContainerBackgroundDrawnEvent.java new file mode 100644 index 00000000..bdb6d1a1 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/events/GuiContainerBackgroundDrawnEvent.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2022 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 lombok.Value; +import net.minecraft.client.gui.inventory.GuiContainer; + +@Value +public class GuiContainerBackgroundDrawnEvent extends NEUEvent { + public GuiContainer container; + public float partialTicks; +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AbiphoneFavourites.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AbiphoneFavourites.java index 3366fd1e..750e674d 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AbiphoneFavourites.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AbiphoneFavourites.java @@ -24,6 +24,7 @@ import io.github.moulberry.notenoughupdates.autosubscribe.NEUAutoSubscribe; import io.github.moulberry.notenoughupdates.core.config.KeybindHelper; import io.github.moulberry.notenoughupdates.core.util.StringUtils; import io.github.moulberry.notenoughupdates.core.util.render.RenderUtils; +import io.github.moulberry.notenoughupdates.events.GuiContainerBackgroundDrawnEvent; import io.github.moulberry.notenoughupdates.events.ReplaceItemEvent; import io.github.moulberry.notenoughupdates.events.SlotClickEvent; import io.github.moulberry.notenoughupdates.options.NEUConfig; @@ -202,10 +203,11 @@ public class AbiphoneFavourites { return isAbiphoneShowOnlyFavourites() && !getFavouriteContacts().contains(name); } - public void onDrawBackground(GuiScreen screen) { + @SubscribeEvent + public void onDrawBackground(GuiContainerBackgroundDrawnEvent event) { if (isWrongInventory()) return; - GuiContainer container = (GuiContainer) screen; + GuiContainer container = event.getContainer(); for (Slot slot : container.inventorySlots.inventorySlots) { if (slot == null) continue; 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 a5bb99b8..8aca73d1 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.GuiContainerBackgroundDrawnEvent; import io.github.moulberry.notenoughupdates.events.SlotClickEvent; import io.github.moulberry.notenoughupdates.listener.RenderListener; import io.github.moulberry.notenoughupdates.miscfeatures.AbiphoneFavourites; @@ -330,6 +331,6 @@ public abstract class MixinGuiContainer extends GuiScreen { @Inject(method = "drawScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GlStateManager;color(FFFF)V", ordinal = 1)) private void drawBackground(int mouseX, int mouseY, float partialTicks, CallbackInfo ci) { - AbiphoneFavourites.getInstance().onDrawBackground(this); + new GuiContainerBackgroundDrawnEvent(((GuiContainer) (Object) this), partialTicks).post(); } } diff --git a/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinRenderItem.java b/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinRenderItem.java index 7c2cfcbf..b6e0e45a 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinRenderItem.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinRenderItem.java @@ -25,6 +25,7 @@ import io.github.moulberry.notenoughupdates.core.ChromaColour; import io.github.moulberry.notenoughupdates.listener.RenderListener; import io.github.moulberry.notenoughupdates.miscfeatures.ItemCooldowns; import io.github.moulberry.notenoughupdates.miscfeatures.ItemCustomizeManager; +import io.github.moulberry.notenoughupdates.miscfeatures.inventory.MuseumItemHighlighter; import io.github.moulberry.notenoughupdates.profileviewer.GuiProfileViewer; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; @@ -148,51 +149,6 @@ public abstract class MixinRenderItem { @Shadow abstract void renderModel(IBakedModel model, int color); - /*@Redirect(method="renderEffect", - at=@At( - value = "INVOKE", - target = "Lnet/minecraft/client/renderer/entity/RenderItem;renderModel(Lnet/minecraft/client/resources/model/IBakedModel;I)V" - ) - ) - public void renderEffect_renderModel(RenderItem renderItem, IBakedModel model, int colour) { - if(customEnchGlint != null) { - renderModel(model, ChromaColour.specialToChromaRGB(customEnchGlint)); - } else { - renderModel(model, colour); - } - } - - @Redirect(method="renderEffect", - at=@At( - value = "INVOKE", - target = "Lnet/minecraft/client/renderer/texture/TextureManager;bindTexture(Lnet/minecraft/util/ResourceLocation;)V" - ) - ) - public void renderEffect_bindTexture(TextureManager textureManager, ResourceLocation location) { - if(customEnchGlint != null) { - textureManager.bindTexture(GlintManager.getCustomGlintTexture()); - GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); - GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); - } else { - textureManager.bindTexture(location); - } - } - - @Redirect(method="renderEffect", - at=@At( - value = "INVOKE", - target = "Lnet/minecraft/client/renderer/GlStateManager;blendFunc(II)V" - ) - ) - public void renderEffect_blendFunc(int src, int dst) { - if(dst != 1) { - GlStateManager.blendFunc(src, dst); - } else if(customEnchGlint != null) { - GlintManager.setCustomBlendFunc(customEnchGlint); - } else { - GlStateManager.blendFunc(GL11.GL_SRC_COLOR, 1); - } - }*/ @Inject(method = "renderItemIntoGUI", at = @At("HEAD")) public void renderItemHead(ItemStack stack, int x, int y, CallbackInfo ci) { 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 371d657e..4f524c26 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/options/NEUConfig.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/options/NEUConfig.java @@ -54,6 +54,7 @@ import io.github.moulberry.notenoughupdates.options.seperateSections.Mining; import io.github.moulberry.notenoughupdates.options.seperateSections.MinionHelper; import io.github.moulberry.notenoughupdates.options.seperateSections.Misc; import io.github.moulberry.notenoughupdates.options.seperateSections.MiscOverlays; +import io.github.moulberry.notenoughupdates.options.seperateSections.Museum; import io.github.moulberry.notenoughupdates.options.seperateSections.NeuAuctionHouse; import io.github.moulberry.notenoughupdates.options.seperateSections.Notifications; import io.github.moulberry.notenoughupdates.options.seperateSections.PetOverlay; @@ -183,6 +184,12 @@ public class NEUConfig extends Config { @Expose @Category( + name = "Museum",desc = "Musem overlays" + ) + public Museum museum = new Museum(); + + @Expose + @Category( name = "GUI Locations", desc = "Edit the GUI locations of everything here" ) @@ -246,8 +253,8 @@ public class NEUConfig extends Config { @Expose @Category( - name = "Todo Overlay", - desc = "Todo Overlay" + name = "Misc Overlays", + desc = "Miscellaneous Overlays" ) public MiscOverlays miscOverlays = new MiscOverlays(); 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 714dd7b7..064a9304 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,7 @@ 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.ConfigEditorButton; +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; @@ -431,4 +431,5 @@ public class MiscOverlays { @ConfigEditorBoolean @ConfigAccordionId(id = 0) public boolean todoIcons = true; + } diff --git a/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/Museum.java b/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/Museum.java new file mode 100644 index 00000000..6f03c2bb --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/Museum.java @@ -0,0 +1,64 @@ +/* + * 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.options.seperateSections; + +import com.google.gson.annotations.Expose; +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.ConfigEditorDropdown; +import io.github.moulberry.notenoughupdates.core.config.annotations.ConfigOption; + +public class Museum { + + @Expose + @ConfigOption( + name = "Show Museum Items", + desc = "Show real items instead of green dye in the museum" + ) + @ConfigEditorBoolean + public boolean museumItemShow = false; + + @Expose + @ConfigOption( + name = "Highlight virtual museum items", + desc = "Highlight virtual museum items with a background color" + ) + @ConfigEditorColour + public String museumItemColor = "0:255:0:255:0"; + + @Expose + @ConfigOption( + name = "Show Items to donate", + desc = "Show the cheapest items you have not yet donated to the Museum" + ) + @ConfigEditorBoolean + public boolean museumCheapestItemOverlay = true; + + @Expose + @ConfigOption( + name = "Value calculation", + desc = "Choose the source for the value calculation" + ) + @ConfigEditorDropdown( + values = {"Lowest BIN", "Craft cost"} + ) + public int museumCheapestItemOverlayValueSource = 0; + +} 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 ea5e13ab..280a0d3d 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/util/ItemResolutionQuery.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/util/ItemResolutionQuery.java @@ -24,6 +24,7 @@ import com.google.gson.JsonParseException; import io.github.moulberry.notenoughupdates.NEUManager; import io.github.moulberry.notenoughupdates.NotEnoughUpdates; import io.github.moulberry.notenoughupdates.core.util.StringUtils; +import lombok.var; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Gui; import net.minecraft.client.gui.inventory.GuiChest; @@ -36,9 +37,10 @@ import net.minecraft.nbt.NBTTagCompound; import javax.annotation.Nullable; import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Locale; -import java.util.Map; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -188,31 +190,63 @@ public class ItemResolutionQuery { return null; } + /** + * Search for an item by the display name + * + * @param displayName The display name of the item we are searching + * @param mayBeMangled Whether the item name may be mangled (for example: reforges, stars) + * @return the internal neu item id of that item, or null + */ + public static String findInternalNameByDisplayName(String displayName, boolean mayBeMangled) { + var cleanDisplayName = StringUtils.cleanColour(displayName); + var manager = NotEnoughUpdates.INSTANCE.manager; + 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()); + if (cleanItemDisplayName.length() == 0) continue; + if (mayBeMangled + ? !cleanDisplayName.contains(cleanItemDisplayName) + : !cleanItemDisplayName.equals(cleanDisplayName)) { + continue; + } + if (cleanItemDisplayName.length() > bestMatchLength) { + bestMatchLength = cleanItemDisplayName.length(); + bestMatch = internalName; + } + } + return bestMatch; + } + + /** + * Find potential item ids for a given display name. This function is over eager to give results, + * and may give invalid results, but if there is a matching item in the repository it will return <em>at least</em> + * that item. This should be used as a first filtering pass. Use {@link #findInternalNameByDisplayName} for a more + * user-friendly API. + * + * @param displayName The display name of the item we are searching + * @return a list of internal neu item ids some of which may have a matching display name + */ + public static Set<String> findInternalNameCandidatesForDisplayName(String displayName) { + var cleanDisplayName = NEUManager.cleanForTitleMapSearch(displayName); + var titleWordMap = NotEnoughUpdates.INSTANCE.manager.titleWordMap; + var candidates = new HashSet<String>(); + for (var partialDisplayName : cleanDisplayName.split(" ")) { + if (!titleWordMap.containsKey(partialDisplayName)) continue; + candidates.addAll(titleWordMap.get(partialDisplayName).keySet()); + } + return candidates; + } + private String resolveItemInCatacombsRngMeter() { List<String> lore = ItemUtils.getLore(compound); if (lore.size() > 16) { String s = lore.get(15); if (s.equals("§7Selected Drop")) { String displayName = lore.get(16); - return getInternalNameByDisplayName(displayName); - } - } - - return null; - } - - private String getInternalNameByDisplayName(String displayName) { - String cleanDisplayName = StringUtils.cleanColour(displayName); - for (Map.Entry<String, JsonObject> entry : NotEnoughUpdates.INSTANCE.manager - .getItemInformation() - .entrySet()) { - - JsonObject object = entry.getValue(); - if (object.has("displayname")) { - String name = object.get("displayname").getAsString(); - if (StringUtils.cleanColour(name).equals(cleanDisplayName)) { - return entry.getKey(); - } + return findInternalNameByDisplayName(displayName, false); } } diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/LRUCache.java b/src/main/java/io/github/moulberry/notenoughupdates/util/LRUCache.java index f107d522..6adbc30d 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/util/LRUCache.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/util/LRUCache.java @@ -32,13 +32,15 @@ public interface LRUCache<K, V> extends Function<K, V> { } static <K, V> LRUCache<K, V> memoize(Function<K, V> mapper, IntSupplier maxCacheSize) { - Map<K, V> cache = new LinkedHashMap<K, V>(10, 0.75F, true) { + Map<K, Object> cache = new LinkedHashMap<K, Object>(10, 0.75F, true) { @Override - protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { + protected boolean removeEldestEntry(Map.Entry<K, Object> eldest) { return this.size() > maxCacheSize.getAsInt(); } }; - Map<K, V> synchronizedCache = Collections.synchronizedMap(cache); + Object SENTINEL_CACHE_RESULT_NULL = new Object(); + Function<K, Object> sentinelAwareMapper = mapper.andThen(it -> it == null ? SENTINEL_CACHE_RESULT_NULL : it); + Map<K, Object> synchronizedCache = Collections.synchronizedMap(cache); return new LRUCache<K, V>() { @Override public void clearCache() { @@ -52,7 +54,8 @@ public interface LRUCache<K, V> extends Function<K, V> { @Override public V apply(K k) { - return synchronizedCache.computeIfAbsent(k, mapper); + Object value = synchronizedCache.computeIfAbsent(k, sentinelAwareMapper); + return value == SENTINEL_CACHE_RESULT_NULL ? null : (V) value; } }; } diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/miscfeatures/inventory/MuseumCheapestItemOverlay.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscfeatures/inventory/MuseumCheapestItemOverlay.kt new file mode 100644 index 00000000..ccaf5ad8 --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscfeatures/inventory/MuseumCheapestItemOverlay.kt @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2022 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.inventory + +import io.github.moulberry.notenoughupdates.NotEnoughUpdates +import io.github.moulberry.notenoughupdates.autosubscribe.NEUAutoSubscribe +import io.github.moulberry.notenoughupdates.core.util.ArrowPagesUtils +import io.github.moulberry.notenoughupdates.mixins.AccessorGuiContainer +import io.github.moulberry.notenoughupdates.util.MuseumUtil +import io.github.moulberry.notenoughupdates.util.MuseumUtil.DonationState.MISSING +import io.github.moulberry.notenoughupdates.util.Utils +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.GuiScreen +import net.minecraft.client.gui.inventory.GuiChest +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.init.Items +import net.minecraft.inventory.Slot +import net.minecraft.util.EnumChatFormatting +import net.minecraft.util.ResourceLocation +import net.minecraftforge.client.event.GuiScreenEvent +import net.minecraftforge.client.event.GuiScreenEvent.BackgroundDrawnEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import org.lwjgl.input.Mouse +import org.lwjgl.opengl.GL11 + +@NEUAutoSubscribe +object MuseumCheapestItemOverlay { + data class MuseumItem( + var name: String, + var internalNames: List<String>, + var value: Double, + var priceRefreshedAt: Long + ) + + private const val ITEMS_PER_PAGE = 8 + + private val backgroundResource: ResourceLocation = ResourceLocation("notenoughupdates:dungeon_chest_worth.png") + + val config get() = NotEnoughUpdates.INSTANCE.config.museum + + /** + * The top left position of the arrows to be drawn, used by [ArrowPagesUtils] + */ + private var topLeft = intArrayOf(237, 110) + private var currentPage: Int = 0 + private var previousSlots: List<Slot> = emptyList() + private var itemsToDonate: MutableList<MuseumItem> = emptyList<MuseumItem>().toMutableList() + + /** + *category -> was the highest page visited? + */ + private var checkedPages: HashMap<String, Boolean> = hashMapOf( + //this page only shows items when you have already donated them -> there is no useful information to gather + "Special Items" to true, + "Weapons" to false, + "Armor Sets" to false, + "Rarities" to false + ) + + /** + * Draw the overlay and parse items, if applicable + */ + @SubscribeEvent + fun onDrawBackground(event: BackgroundDrawnEvent) { + if (!shouldRender(event.gui)) return + val chest = event.gui as GuiChest + + val slots = chest.inventorySlots.inventorySlots + if (!slots.equals(previousSlots)) { + checkIfHighestPageWasVisited(slots) + parseItems(slots) + updateOutdatedValues() + sortByValue() + } + previousSlots = slots + + val xSize = (event.gui as AccessorGuiContainer).xSize + val guiLeft = (event.gui as AccessorGuiContainer).guiLeft + val guiTop = (event.gui as AccessorGuiContainer).guiTop + + drawBackground(guiLeft, xSize, guiTop) + drawLines(guiLeft, guiTop) + } + + /** + * Pass on mouse clicks to [ArrowPagesUtils], if applicable + */ + @SubscribeEvent + fun onMouseClick(event: GuiScreenEvent.MouseInputEvent.Pre) { + if (!shouldRender(event.gui)) return + if (!Mouse.getEventButtonState()) return + val guiLeft = (event.gui as AccessorGuiContainer).guiLeft + val guiTop = (event.gui as AccessorGuiContainer).guiTop + ArrowPagesUtils.onPageSwitchMouse( + guiLeft, guiTop, topLeft, currentPage, totalPages() + ) { pageChange: Int -> currentPage = pageChange } + } + + /** + * Sort the collected items by their calculated value + */ + private fun sortByValue() { + itemsToDonate.sortBy { it.value } + } + + /** + * Update all values that have not been updated for the last minute + */ + private fun updateOutdatedValues() { + val time = System.currentTimeMillis() + itemsToDonate.filter { time - it.priceRefreshedAt >= 60000 } + .forEach { + it.value = calculateValue(it.internalNames) + it.priceRefreshedAt = time + } + } + + /** + * Calculate the value of an item as displayed in the museum, which may consist of multiple pieces + */ + private fun calculateValue(internalNames: List<String>): Double { + var totalValue = 0.0 + internalNames.forEach { + val itemValue: Double = + when (config.museumCheapestItemOverlayValueSource) { + 0 -> NotEnoughUpdates.INSTANCE.manager.auctionManager.getBazaarOrBin(it, false) + 1 -> NotEnoughUpdates.INSTANCE.manager.auctionManager.getCraftCost(it)?.craftCost ?: return@forEach + else -> -1.0 //unreachable + } + if (itemValue == -1.0 || itemValue == 0.0) { + totalValue = Double.MAX_VALUE + return@forEach + } else { + totalValue += itemValue + } + } + if (totalValue == 0.0) { + totalValue = Double.MAX_VALUE + } + + return totalValue + } + + /** + * Draw the lines containing the displayname and value over the background + */ + private fun drawLines(guiLeft: Int, guiTop: Int) { + val lines = buildLines() + lines.forEachIndexed { index, line -> + if (index == ITEMS_PER_PAGE && !visitedAllPages()) { + Minecraft.getMinecraft().fontRendererObj.drawStringWithShadow( + "${EnumChatFormatting.RED}Visit all pages for accurate info!", + (guiLeft + 185).toFloat(), + (guiTop + 85).toFloat(), + 0 + ) + } else { + Utils.renderAlignedString( + "${EnumChatFormatting.RESET}${line.name}", + if (line.value == Double.MAX_VALUE) "${EnumChatFormatting.RED}Unknown" else "${EnumChatFormatting.AQUA}${ + Utils.shortNumberFormat( + line.value, + 0 + ) + }", + (guiLeft + 187).toFloat(), + (guiTop + 5 + (index * 10)).toFloat(), + 160 + ) + } + } + + ArrowPagesUtils.onDraw(guiLeft, guiTop, topLeft, currentPage, totalPages()) + return + } + + /** + * Create the list of [MuseumItem]s that should be displayed on the current page + */ + private fun buildLines(): List<MuseumItem> { + val list = emptyList<MuseumItem>().toMutableList() + for (i in (if (currentPage == 0) ITEMS_PER_PAGE else ITEMS_PER_PAGE + 1) * (currentPage)..(if (currentPage == 0) ITEMS_PER_PAGE else ITEMS_PER_PAGE + 1) * currentPage + ITEMS_PER_PAGE) { + if (i >= itemsToDonate.size) { + break + } + list.add(itemsToDonate[i]) + } + return list + } + + /** + * Parse the not already donated items present in the currently open Museum page + */ + private fun parseItems(slots: List<Slot>) { + // Iterate upper chest with 56 slots + val time = System.currentTimeMillis() + val armor = Utils.getOpenChestName().endsWith("Armor Sets") + for (i in 0..53) { + val stack = slots[i].stack ?: continue + val parsedItems = MuseumUtil.findMuseumItem(stack, armor) ?: continue + when (parsedItems.state) { + MISSING -> + if (itemsToDonate.none { it.internalNames == parsedItems.skyblockItemIds }) + itemsToDonate.add( + MuseumItem( + stack.displayName, + parsedItems.skyblockItemIds, + calculateValue(parsedItems.skyblockItemIds), + time + ) + ) + + else -> itemsToDonate.retainAll { it.internalNames != parsedItems.skyblockItemIds } + } + } + } + + /** + * Check if the highest page for the current category is currently open and update [checkedPages] accordingly + */ + private fun checkIfHighestPageWasVisited(slots: List<Slot>) { + val category = getCategory() + val nextPageSlot = slots[53] + // If the "Next Page" arrow is missing, we are at the highest page + if ((nextPageSlot.stack ?: return).item != Items.arrow) { + checkedPages[category] = true + } + } + + /** + * Draw the background texture to the right side of the open Museum Page + */ + private fun drawBackground(guiLeft: Int, xSize: Int, guiTop: Int) { + Minecraft.getMinecraft().textureManager.bindTexture(backgroundResource) + GL11.glColor4f(1F, 1F, 1F, 1F) + GlStateManager.disableLighting() + Utils.drawTexturedRect( + guiLeft.toFloat() + xSize + 4, + guiTop.toFloat(), + 180F, + 101F, + 0F, + 180 / 256F, + 0F, + 101 / 256F, + GL11.GL_NEAREST + ) + } + + /** + * Determine if the overlay should be active based on the config option and the currently open GuiChest, if applicable + */ + private fun shouldRender(gui: GuiScreen): Boolean = + config.museumCheapestItemOverlay && gui is GuiChest && Utils.getOpenChestName() + .startsWith("Museum ➜") + + /** + * Determine the name of the currently open Museum Category + */ + private fun getCategory(): String = Utils.getOpenChestName().substring(9, Utils.getOpenChestName().length) + + /** + * Determine if all useful pages have been visited + */ + private fun visitedAllPages(): Boolean = !checkedPages.containsValue(false) + + /** + * Calculate the total amount of pages the overlay should have + */ + private fun totalPages(): Int = when (itemsToDonate.size % ITEMS_PER_PAGE) { + 0 -> itemsToDonate.size / ITEMS_PER_PAGE + else -> (itemsToDonate.size / ITEMS_PER_PAGE) + 1 + } +} diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/miscfeatures/inventory/MuseumItemHighlighter.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscfeatures/inventory/MuseumItemHighlighter.kt new file mode 100644 index 00000000..945449ba --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscfeatures/inventory/MuseumItemHighlighter.kt @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2022 Linnea Gräf + * + * 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.inventory + +import io.github.moulberry.notenoughupdates.NotEnoughUpdates +import io.github.moulberry.notenoughupdates.autosubscribe.NEUAutoSubscribe +import io.github.moulberry.notenoughupdates.core.ChromaColour +import io.github.moulberry.notenoughupdates.core.util.StringUtils +import io.github.moulberry.notenoughupdates.events.GuiContainerBackgroundDrawnEvent +import io.github.moulberry.notenoughupdates.events.ReplaceItemEvent +import io.github.moulberry.notenoughupdates.events.RepositoryReloadEvent +import io.github.moulberry.notenoughupdates.util.ItemResolutionQuery +import io.github.moulberry.notenoughupdates.util.ItemUtils +import io.github.moulberry.notenoughupdates.util.LRUCache +import io.github.moulberry.notenoughupdates.util.MuseumUtil +import net.minecraft.client.gui.Gui +import net.minecraft.init.Items +import net.minecraft.inventory.ContainerChest +import net.minecraft.inventory.IInventory +import net.minecraft.item.EnumDyeColor +import net.minecraft.item.ItemStack +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +@NEUAutoSubscribe +object MuseumItemHighlighter { + + private val manager get() = NotEnoughUpdates.INSTANCE.manager + private val config get() = NotEnoughUpdates.INSTANCE.config.museum + + private fun getHighlightColor() = ChromaColour.specialToChromaRGB(config.museumItemColor) + + + private val findRawItemForName = LRUCache.memoize(::findRawItemForName0, 4 * 7 * 2) + + @SubscribeEvent + fun onRepositoryReload(event: RepositoryReloadEvent) { + findRawItemForName.clearCache() + } + + private fun findRawItemForName0(arg: Pair<String, Boolean>): ItemStack? { + val (name, armor) = arg + return MuseumUtil.findItemsByName(name, armor).firstOrNull()?.let { manager.createItem(it) } + } + + + @SubscribeEvent + fun onItemOverride(event: ReplaceItemEvent) { + if (!config.museumItemShow) return + if (!isMuseumInventory(event.inventory)) return + val original = event.original ?: return + if (!isCompletedRetrievedItem(original)) return + val armor = StringUtils.cleanColour(event.inventory.displayName.unformattedText).endsWith("Armor Sets") + val rawItem = findRawItemForName.apply(original.displayName to armor) ?: return + val hydratedItem = hydrateMuseumItem(rawItem, original) + event.replaceWith(hydratedItem) + } + + fun isCompletedRetrievedItem(itemStack: ItemStack): Boolean { + return itemStack.hasDisplayName() && itemStack.item == Items.dye && EnumDyeColor.byDyeDamage(itemStack.itemDamage) == EnumDyeColor.LIME + } + + fun isMuseumInventory(inventory: IInventory): Boolean { + return StringUtils.cleanColour(inventory.displayName.unformattedText).startsWith("Museum ➜") + } + + @SubscribeEvent + fun onBackgroundDrawn(event: GuiContainerBackgroundDrawnEvent) { + val egui = event.container ?: return + val chest = egui.inventorySlots as? ContainerChest ?: return + if (!config.museumItemShow) return + if (!isMuseumInventory(chest.lowerChestInventory)) return + val fixedHighlightColor = getHighlightColor() + for (slot in chest.inventorySlots) { + if (slot == null || slot.stack == null) continue + if (isHydratedMuseumItem(slot.stack) || isCompletedRetrievedItem(slot.stack)) { + val left = slot.xDisplayPosition + val top = slot.yDisplayPosition + Gui.drawRect( + left, top, + left + 16, top + 16, + fixedHighlightColor + ) + } + } + } + + fun hydrateMuseumItem(rawItem: ItemStack, original: ItemStack) = rawItem.copy().apply { + setStackDisplayName(original.displayName) + val originalLore = ItemUtils.getLore(original).toMutableList() + ItemUtils.setLore(this, originalLore) + val data = ItemUtils.getOrCreateTag(this) + val extraAttributes = data.getCompoundTag("ExtraAttributes") + extraAttributes.setByte("donated_museum", 1) + data.setTag("ExtraAttributes", extraAttributes) + data.setBoolean(MUSEUM_HYDRATED_ITEM_TAG, true) + } + + fun isHydratedMuseumItem(stack: ItemStack): Boolean { + return ItemUtils.getOrCreateTag(stack).getBoolean(MUSEUM_HYDRATED_ITEM_TAG) + } + + const val MUSEUM_HYDRATED_ITEM_TAG = "NEU_HYDRATED_MUSEUM_ITEM" + +} diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/util/KotlinStringUtils.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/KotlinStringUtils.kt new file mode 100644 index 00000000..dc1e800c --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/KotlinStringUtils.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2022 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 net.minecraft.util.StringUtils + +fun String.stripControlCodes(): String = StringUtils.stripControlCodes(this) diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/util/MuseumUtil.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/MuseumUtil.kt new file mode 100644 index 00000000..dd52d175 --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/MuseumUtil.kt @@ -0,0 +1,113 @@ +/* + * 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.NEUManager +import io.github.moulberry.notenoughupdates.NotEnoughUpdates +import net.minecraft.item.EnumDyeColor +import net.minecraft.item.ItemDye +import net.minecraft.item.ItemStack + +object MuseumUtil { + + data class MuseumItem( + /** + * A potentially non-exhaustive list of item ids that are required for this museum donation. + */ + val skyblockItemIds: List<String>, + val state: DonationState, + ) + + enum class DonationState { + /** + * Donated armor only shows one piece, so we use that for id resolution, which might result in incomplete + * results (hence the separate state). This still means that the entire set is donated, but it is guaranteed to + * be only a partial result. Other values of this enum do not guarantee a full result, but at least they do not + * guarantee a partial one. + */ + DONATED_PRESENT_PARTIAL, + DONATED_PRESENT, + DONATED_VACANT, + MISSING, + } + + fun findMuseumItem(stack: ItemStack, isOnArmorPage: Boolean): MuseumItem? { + val item = stack.item ?: return null + val items by lazy { findItemsByName(stack.displayName, isOnArmorPage)} + if (item is ItemDye) { + val dyeColor = EnumDyeColor.byDyeDamage(stack.itemDamage) + if (dyeColor == EnumDyeColor.LIME) { + // Item is donated, but not present in the museum + return MuseumItem(items, DonationState.DONATED_VACANT) + } else if (dyeColor == EnumDyeColor.GRAY) { + // Item is not donated + return MuseumItem(items, DonationState.MISSING) + } + // Otherwise unknown item, try to analyze as normal item. + } + val skyblockId = NotEnoughUpdates.INSTANCE.manager.createItemResolutionQuery().withItemStack(stack) + .resolveInternalName() + if (skyblockId != null) { + return MuseumItem( + listOf(skyblockId), + if (isOnArmorPage) DonationState.DONATED_PRESENT_PARTIAL else DonationState.DONATED_PRESENT + ) + } + return MuseumItem( + items, + DonationState.DONATED_PRESENT + ) + } + + fun findItemsByName(displayName: String, armor: Boolean): List<String> { + return (if (armor) + findMuseumArmorSetByName(displayName) + else + listOf(findMuseumItemByName(displayName))).filterNotNull() + + } + + fun findMuseumItemByName(displayName: String): String? = + ItemResolutionQuery.findInternalNameByDisplayName(displayName, true) + + + fun findMuseumArmorSetByName(displayName: String): List<String?> { + val armorSlots = arrayOf( + "HELMET", + "LEGGINGS", + "CHESTPLATE", + "BOOTS" + ) + val monochromeName = NEUManager.cleanForTitleMapSearch(displayName) + val results = ItemResolutionQuery.findInternalNameCandidatesForDisplayName(displayName) + .asSequence() + .filter { + val item = NotEnoughUpdates.INSTANCE.manager.createItem(it) + val name = NEUManager.cleanForTitleMapSearch(item.displayName) + monochromeName.replace("armor", "") in name + } + .toSet() + return armorSlots.map { armorSlot -> + results.singleOrNull { armorSlot in it } + } + } + + +} |