aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/io
diff options
context:
space:
mode:
authorRoman / Linnea Gräf <roman.graef@gmail.com>2023-05-31 13:05:25 +0200
committerGitHub <noreply@github.com>2023-05-31 13:05:25 +0200
commit631cfec46220df2be3ae677f8f58a128303502e7 (patch)
tree041e37ecc7f3f3f655cdbd0b0f868bd890bfb056 /src/main/java/io
parente89d2e7de492c523a941cadf4747d428de6e7250 (diff)
downloadNotEnoughUpdates-631cfec46220df2be3ae677f8f58a128303502e7.tar.gz
NotEnoughUpdates-631cfec46220df2be3ae677f8f58a128303502e7.tar.bz2
NotEnoughUpdates-631cfec46220df2be3ae677f8f58a128303502e7.zip
DungeonNPCProfitOverlay: make use of ItemResolutionQuerys id by name … (#700)
DungeonNPCProfitOverlay: make use of ItemResolutionQuerys id by name resolvers
Diffstat (limited to 'src/main/java/io')
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/DungeonNpcProfitOverlay.java241
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinGuiContainer.java3
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/util/IdentityCharacteristics.java52
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/util/ItemResolutionQuery.java13
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/util/ItemUtils.java1
5 files changed, 179 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 93ea6127..aaae0031 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/util/ItemUtils.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/util/ItemUtils.java
@@ -138,6 +138,7 @@ public class ItemUtils {
}
public static List<String> getLore(ItemStack is) {
+ if (is == null) return new ArrayList<>();
return getLore(is.getTagCompound());
}