diff --git a/LICENSE b/LICENSE index 633f8c902b9..a88904758d5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017-2022 Aidan C. Brady +Copyright (c) 2017-2023 Aidan C. Brady Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 8308ca6b8fb..44501cd5502 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ If you would like to help translate Mekanism, you can do so through [Crowdin](ht Mekanism is licensed under the MIT license. You may use it in modpacks, reviews, or any other form as long as you abide by the terms below. -Copyright 2017-2022 Aidan C. Brady +Copyright 2017-2023 Aidan C. Brady Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/src/api/java/mekanism/api/gear/config/ModuleEnumData.java b/src/api/java/mekanism/api/gear/config/ModuleEnumData.java index 65ef4123f98..17205273dae 100644 --- a/src/api/java/mekanism/api/gear/config/ModuleEnumData.java +++ b/src/api/java/mekanism/api/gear/config/ModuleEnumData.java @@ -66,7 +66,7 @@ public ModuleEnumData(TYPE def, int selectableCount) { * * @deprecated since 10.3.2, use the version ({@link #ModuleEnumData(Enum)}) that figures out the class automatically. */ - @Deprecated(forRemoval = true) + @Deprecated(forRemoval = true, since = "10.3.2") public ModuleEnumData(Class enumClass, TYPE def) { this(def); Objects.requireNonNull(enumClass, "Enum Class cannot be null."); @@ -81,7 +81,7 @@ public ModuleEnumData(Class enumClass, TYPE def) { * * @deprecated since 10.3.2, use the version ({@link #ModuleEnumData(Enum, int)}) that figures out the class automatically. */ - @Deprecated(forRemoval = true) + @Deprecated(forRemoval = true, since = "10.3.2") public ModuleEnumData(Class enumClass, int selectableCount, TYPE def) { this(def, selectableCount); Objects.requireNonNull(enumClass, "Enum Class cannot be null."); diff --git a/src/api/java/mekanism/api/inventory/IHashedItem.java b/src/api/java/mekanism/api/inventory/IHashedItem.java new file mode 100644 index 00000000000..37cc112ed59 --- /dev/null +++ b/src/api/java/mekanism/api/inventory/IHashedItem.java @@ -0,0 +1,64 @@ +package mekanism.api.inventory; + +import net.minecraft.MethodsReturnNonnullByDefault; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.Nullable; + +/** + * Represents an "item type" for comparing {@link ItemStack ItemStack's} without size. + * + * @since 10.3.6 + */ +@MethodsReturnNonnullByDefault +public interface IHashedItem { + + /** + * Gets the internal {@link ItemStack} that backs this item type. It is IMPORTANT to not modify the returned value. + * + * @return The internal {@link ItemStack} that backs this item type. + * + * @apiNote Do not modify the returned value. This is only exposed for cases where the stack is needed for performance reasons and mutation is not needed. + */ + ItemStack getInternalStack(); + + /** + * Creates a mutable {@link ItemStack} of this type with the given size. + * + * @param size Size of the stack to create. + * + * @return A new {@link ItemStack} of this type. + */ + ItemStack createStack(int size); + + /** + * Helper to get the {@link Item} that this item type represents. + * + * @return The {@link Item} that this item type represents. + */ + default Item getItem() { + return getInternalStack().getItem(); + } + + /** + * Helper to get the max stack size of the {@link Item} that this item type represents. + * + * @return Max stack size of the {@link Item} that this item type represents. + */ + default int getMaxStackSize() { + return getInternalStack().getMaxStackSize(); + } + + /** + * Helper to get the tag of the internal {@link ItemStack} that backs this item type. It is IMPORTANT to not modify the returned tag. + * + * @return Tag of the internal {@link ItemStack} that backs this item type. + * + * @apiNote Do not modify the returned tag. + */ + @Nullable + default CompoundTag getInternalTag() { + return getInternalStack().getTag(); + } +} \ No newline at end of file diff --git a/src/api/java/mekanism/api/inventory/qio/IQIOFrequency.java b/src/api/java/mekanism/api/inventory/qio/IQIOFrequency.java index 90e69dc954e..04c19c1d2c8 100644 --- a/src/api/java/mekanism/api/inventory/qio/IQIOFrequency.java +++ b/src/api/java/mekanism/api/inventory/qio/IQIOFrequency.java @@ -3,6 +3,7 @@ import java.util.function.ObjLongConsumer; import mekanism.api.Action; import mekanism.api.IFrequency; +import mekanism.api.inventory.IHashedItem; import net.minecraft.world.item.ItemStack; /** @@ -29,6 +30,16 @@ public interface IQIOFrequency extends IFrequency { */ void forAllStored(ObjLongConsumer consumer); + /** + * Performs the given action for every item type stored in this QIO Frequency. Each action will be provided with the stored {@link IHashedItem} representing the type, + * and a long representing the amount of that item type that is stored. + * + * @param consumer Action to be performed. + * + * @since 10.3.6 + */ + void forAllHashedStored(ObjLongConsumer consumer); + /** * Attempts to insert a given item type into this QIO Frequency. * diff --git a/src/main/java/mekanism/client/gui/element/scroll/GuiSlotScroll.java b/src/main/java/mekanism/client/gui/element/scroll/GuiSlotScroll.java index 58a946953b7..7d16818ab8a 100644 --- a/src/main/java/mekanism/client/gui/element/scroll/GuiSlotScroll.java +++ b/src/main/java/mekanism/client/gui/element/scroll/GuiSlotScroll.java @@ -16,6 +16,7 @@ import mekanism.common.MekanismLang; import mekanism.common.inventory.ISlotClickHandler; import mekanism.common.inventory.ISlotClickHandler.IScrollableSlot; +import mekanism.common.lib.inventory.HashedItem; import mekanism.common.util.MekanismUtils; import mekanism.common.util.MekanismUtils.ResourceType; import mekanism.common.util.text.TextUtils; @@ -137,7 +138,7 @@ private void renderSlot(PoseStack matrix, IScrollableSlot slot, int slotX, int s if (isSlotEmpty(slot)) { return; } - gui().renderItemWithOverlay(matrix, slot.getItem().getStack(), slotX + 1, slotY + 1, 1, ""); + gui().renderItemWithOverlay(matrix, slot.getItem().getInternalStack(), slotX + 1, slotY + 1, 1, ""); if (slot.getCount() > 1) { renderSlotText(matrix, getCountText(slot.getCount()), slotX + 1, slotY + 1); } @@ -148,7 +149,7 @@ private void renderSlotTooltip(PoseStack matrix, IScrollableSlot slot, int slotX if (isSlotEmpty(slot)) { return; } - ItemStack stack = slot.getItem().getStack(); + ItemStack stack = slot.getItem().getInternalStack(); long count = slot.getCount(); if (count < 10_000) { gui().renderItemTooltip(matrix, stack, slotX, slotY); @@ -160,7 +161,10 @@ private void renderSlotTooltip(PoseStack matrix, IScrollableSlot slot, int slotX } private boolean isSlotEmpty(IScrollableSlot slot) { - return slot.getItem() == null || slot.getItem().getStack().isEmpty(); + //Slot's item is not null in default impl, but check in case we make it null at some point + // and also validate if the internal stack is empty in case it is raw and there is some edge case + HashedItem item = slot.getItem(); + return item == null || item.getInternalStack().isEmpty(); } private void renderSlotText(PoseStack matrix, String text, int x, int y) { @@ -203,6 +207,6 @@ private List getSlotList() { @Override public Object getIngredient(double mouseX, double mouseY) { IScrollableSlot slot = getSlot(mouseX, mouseY); - return slot == null ? null : slot.getItem().getStack(); + return slot == null ? null : slot.getItem().getInternalStack(); } } diff --git a/src/main/java/mekanism/client/gui/qio/GuiQIORedstoneAdapter.java b/src/main/java/mekanism/client/gui/qio/GuiQIORedstoneAdapter.java index ffc0ca1817d..287741833ed 100644 --- a/src/main/java/mekanism/client/gui/qio/GuiQIORedstoneAdapter.java +++ b/src/main/java/mekanism/client/gui/qio/GuiQIORedstoneAdapter.java @@ -53,9 +53,10 @@ protected void addGuiElements() { .tooltip(GuiQIOFilterHandler.getFrequencyTooltip(tile))); addRenderableWidget(new GuiInnerScreen(this, 27, 30, imageWidth - 27 - 8, 64, () -> { List list = new ArrayList<>(); - list.add(tile.getItemType().isEmpty() ? MekanismLang.QIO_ITEM_TYPE_UNDEFINED.translate() : tile.getItemType().getHoverName()); + ItemStack itemType = tile.getItemType(); + list.add(itemType.isEmpty() ? MekanismLang.QIO_ITEM_TYPE_UNDEFINED.translate() : itemType.getHoverName()); list.add(MekanismLang.QIO_TRIGGER_COUNT.translate(TextUtils.format(tile.getCount()))); - if (!tile.getItemType().isEmpty() && tile.getQIOFrequency() != null) { + if (!itemType.isEmpty() && tile.getQIOFrequency() != null) { list.add(MekanismLang.QIO_STORED_COUNT.translate(TextUtils.format(tile.getStoredCount()))); } list.add(MekanismLang.QIO_FUZZY_MODE.translate(tile.getFuzzyMode())); @@ -80,9 +81,7 @@ private void setCount() { protected void drawForegroundText(@NotNull PoseStack matrix, int mouseX, int mouseY) { renderTitleText(matrix); drawString(matrix, playerInventoryTitle, inventoryLabelX, inventoryLabelY, titleTextColor()); - if (tile.getItemType() != null) { - renderItem(matrix, tile.getItemType(), 8, 31); - } + renderItem(matrix, tile.getItemType(), 8, 31); super.drawForegroundText(matrix, mouseX, mouseY); } diff --git a/src/main/java/mekanism/client/jei/QIOCraftingTransferHandler.java b/src/main/java/mekanism/client/jei/QIOCraftingTransferHandler.java index 55f72d64ce3..416b6c5f039 100644 --- a/src/main/java/mekanism/client/jei/QIOCraftingTransferHandler.java +++ b/src/main/java/mekanism/client/jei/QIOCraftingTransferHandler.java @@ -20,6 +20,7 @@ import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.function.Function; import mekanism.api.inventory.IInventorySlot; import mekanism.api.math.MathUtils; import mekanism.common.Mekanism; @@ -62,12 +63,12 @@ public class QIOCraftingTransferHandler containerClass; - private final IStackHelper stackHelper; + private final Function recipeUUIDFunction; public QIOCraftingTransferHandler(IRecipeTransferHandlerHelper handlerHelper, IStackHelper stackHelper, Class containerClass) { this.handlerHelper = handlerHelper; - this.stackHelper = stackHelper; this.containerClass = containerClass; + this.recipeUUIDFunction = hashed -> stackHelper.getUniqueIdentifierForStack(hashed.getInternalStack(), UidContext.Recipe); } @Override @@ -162,7 +163,9 @@ record TrackedIngredients(IRecipeSlotView view, Set representations) //Then add all valid ingredients in the order they appear in JEI. Because we are using a set // we will just end up merging with the displayed ingredient when we get to it as a valid ingredient for (ItemStack validIngredient : validIngredients) { - representations.add(HashedItem.raw(validIngredient)); + if (!validIngredient.isEmpty()) {//Shouldn't be empty but validate it just in case + representations.add(HashedItem.raw(validIngredient)); + } } hashedIngredients.put((byte) index, new TrackedIngredients(slotView, representations)); } @@ -217,24 +220,22 @@ record TrackedIngredients(IRecipeSlotView view, Set representations) if (source.hasMoreRemaining()) { //Only look at the source if we still have more items available in it HashedItem storedHashedItem = entry.getKey(); - ItemStack storedItem = storedHashedItem.getStack(); - Item storedItemType = storedItem.getItem(); + Item storedItemType = storedHashedItem.getItem(); String storedItemUUID = null; for (ByteIterator missingIterator = missingSlots.iterator(); missingIterator.hasNext(); ) { byte index = missingIterator.nextByte(); for (HashedItem validIngredient : hashedIngredients.get(index).representations()) { //Compare the raw item types - if (storedItemType == validIngredient.getStack().getItem()) { + if (storedItemType == validIngredient.getItem()) { //If they match, compute the identifiers for both stacks as needed if (storedItemUUID == null) { //If we haven't retrieved a UUID for the stored stack yet because none of our previous ingredients // matched the basic item type, retrieve it - storedItemUUID = stackHelper.getUniqueIdentifierForStack(storedItem, UidContext.Recipe); + storedItemUUID = recipeUUIDFunction.apply(storedHashedItem); } //Next compute the UUID for the ingredient we are missing if we haven't already calculated it // either in a previous iteration or for a different slot - String ingredientUUID = cachedIngredientUUIDs.computeIfAbsent(validIngredient, - ingredient -> stackHelper.getUniqueIdentifierForStack(ingredient.getStack(), UidContext.Recipe)); + String ingredientUUID = cachedIngredientUUIDs.computeIfAbsent(validIngredient, recipeUUIDFunction); if (storedItemUUID.equals(ingredientUUID)) { //If the items are equivalent, reduce how much of the item we have as an input source.matchFound(); @@ -280,7 +281,7 @@ record TrackedIngredients(IRecipeSlotView view, Set representations) //If something went wrong, and we don't actually have the item we think we do, error return invalidSource(hashedItem); } - int maxStack = hashedItem.getStack().getMaxStackSize(); + int maxStack = hashedItem.getMaxStackSize(); //If we have something that only stacks to one, such as a bucket. Don't limit the max stack size // of other items to one long max = maxStack == 1 ? maxToTransfer : Math.min(maxToTransfer, maxStack); @@ -306,7 +307,7 @@ record TrackedIngredients(IRecipeSlotView view, Set representations) // and other stuff with it. This only actually matters if the max stack size is one, due to // the logic done above when calculating how much to transfer, but we do this regardless here // as there is no reason not to and then if we decide to widen it up we only have to change one spot - int transferAmount = Math.min(toTransfer, hashedItem.getStack().getMaxStackSize()); + int transferAmount = Math.min(toTransfer, hashedItem.getMaxStackSize()); for (byte slot : entry.getValue()) { //Try to use the item and figure out where it is coming from List actualSources = source.use(transferAmount); @@ -341,8 +342,7 @@ record TrackedIngredients(IRecipeSlotView view, Set representations) } private IRecipeTransferError invalidSource(@NotNull HashedItem type) { - ItemStack stack = type.getStack(); - Mekanism.logger.warn("Error finding source for: {} with nbt: {}. This should not be possible happen.", stack.getItem(), stack.getTag()); + Mekanism.logger.warn("Error finding source for: {} with nbt: {}. This should not be possible.", type.getItem(), type.getInternalTag()); return handlerHelper.createInternalError(); } diff --git a/src/main/java/mekanism/common/content/qio/QIOCraftingTransferHelper.java b/src/main/java/mekanism/common/content/qio/QIOCraftingTransferHelper.java index 39e05b7011f..df5051ba86f 100644 --- a/src/main/java/mekanism/common/content/qio/QIOCraftingTransferHelper.java +++ b/src/main/java/mekanism/common/content/qio/QIOCraftingTransferHelper.java @@ -288,7 +288,7 @@ protected BaseSimulatedInventory(List hotBarSlots, List consumer) { itemDataMap.forEach((type, data) -> consumer.accept(type.createStack(1), data.getCount())); } + @Override + public void forAllHashedStored(ObjLongConsumer consumer) { + itemDataMap.forEach((type, data) -> consumer.accept(type, data.getCount())); + } + @Override public long massInsert(ItemStack stack, long amount, Action action) { if (stack.isEmpty() || amount <= 0) { @@ -148,7 +154,7 @@ public ItemStack addItem(ItemStack stack) { } private QIOItemTypeData createTypeDataForAbsent(HashedItem type) { - ItemStack stack = type.getStack(); + ItemStack stack = type.getInternalStack(); List tags = TagCache.getItemTags(stack); if (!tags.isEmpty()) { boolean hasAllKeys = tagLookupMap.hasAllKeys(tags); @@ -238,7 +244,7 @@ private void removeItemData(HashedItem type) { tagWildcardCache.clear(); //Note: We don't need to clear the failed wildcard tags as if we are removing tags they still won't have any matches } - ItemStack stack = type.getStack(); + ItemStack stack = type.getInternalStack(); String modID = MekanismUtils.getModId(stack); Set itemsForMod = modIDLookupMap.get(modID); //In theory if we are removing an item, and it existed we should have a set corresponding to it, @@ -390,7 +396,7 @@ public int getTotalItemTypeCapacity() { @Override public long getStored(ItemStack type) { - return getStored(HashedItem.raw(type)); + return type.isEmpty() ? 0 : getStored(HashedItem.raw(type)); } public long getStored(HashedItem itemType) { @@ -444,7 +450,7 @@ public void tick() { //Note: We only need to clear tags here as the modids cannot change just because a reload happened tagLookupMap.clear(); tagWildcardCache.clear(); - itemDataMap.values().forEach(item -> tagLookupMap.putAll(TagCache.getItemTags(item.itemType.getStack()), item.itemType)); + itemDataMap.values().forEach(item -> tagLookupMap.putAll(TagCache.getItemTags(item.itemType.getInternalStack()), item.itemType)); } } diff --git a/src/main/java/mekanism/common/content/qio/QIOGlobalItemLookup.java b/src/main/java/mekanism/common/content/qio/QIOGlobalItemLookup.java index 5f95ab61da4..7ced1586760 100644 --- a/src/main/java/mekanism/common/content/qio/QIOGlobalItemLookup.java +++ b/src/main/java/mekanism/common/content/qio/QIOGlobalItemLookup.java @@ -168,7 +168,7 @@ protected SerializedHashedItem(HashedItem other) { public CompoundTag getNbtRepresentation() { if (nbtRepresentation == null) { - nbtRepresentation = getStack().serializeNBT(); + nbtRepresentation = internalToNBT(); //Override to ensure that it gets stored with a count of one in case it was raw // and that then when we read it we don't create it with extra size nbtRepresentation.putByte(NBTConstants.COUNT, (byte) 1); diff --git a/src/main/java/mekanism/common/content/qio/QIOServerCraftingTransferHandler.java b/src/main/java/mekanism/common/content/qio/QIOServerCraftingTransferHandler.java index 8e452b9afbd..2402bff93bf 100644 --- a/src/main/java/mekanism/common/content/qio/QIOServerCraftingTransferHandler.java +++ b/src/main/java/mekanism/common/content/qio/QIOServerCraftingTransferHandler.java @@ -251,8 +251,9 @@ private int addStackToRecipe(byte targetSlot, ItemData slotData, int used, byte used = slotData.getAvailable(); } ItemStack currentRecipeTarget = recipeToTest.get(targetSlot); + ItemStack slotStack = slotData.getStack(); if (currentRecipeTarget.isEmpty()) { - int max = slotData.getStack().getMaxStackSize(); + int max = slotStack.getMaxStackSize(); if (used > max) { //This should never happen unless the player has an oversized stack in their inventory Mekanism.logger.warn("Received transfer request from: {}, for: {}, but the item being moved can only stack to: {} but a stack of size: {} was " @@ -260,8 +261,8 @@ private int addStackToRecipe(byte targetSlot, ItemData slotData, int used, byte used = max; } //We copy the stack in case any mods do dumb things in their recipes and would end up mutating our stacks that shouldn't be mutated by accident - recipeToTest.set(targetSlot, slotData.getStack().copy()); - } else if (!ItemHandlerHelper.canItemStacksStack(currentRecipeTarget, slotData.getStack())) { + recipeToTest.set(targetSlot, slotStack.copy()); + } else if (!ItemHandlerHelper.canItemStacksStack(currentRecipeTarget, slotStack)) { //If our stack can't stack with the item we already are going to put in the slot, fail "gracefully" //Note: debug level because this may happen due to not knowing all NBT Mekanism.logger.debug("Received transfer request from: {}, for: {}, but found items for target slot: {} cannot stack. " @@ -426,13 +427,13 @@ private void transferItems(Byte2ObjectMap> source stack = frequency.removeByType(storedItem, source.getUsed()); if (stack.isEmpty()) { bail(targetContents, "Received transfer request from: {}, for: {}, but could not extract item: {} with nbt: {} from the QIO.", - player, recipeID, storedItem.getStack().getItem(), storedItem.getStack().getTag()); + player, recipeID, storedItem.getItem(), storedItem.getInternalTag()); return; } else if (stack.getCount() < source.getUsed()) { Mekanism.logger.warn("Received transfer request from: {}, for: {}, but was unable to extract the expected amount: {} of item: {} " + "with nbt: {} from the QIO. This should not be possible as it should have been caught during simulation. Attempting " - + "to continue anyways with the actual extracted amount of {}.", player, recipeID, source.getUsed(), - storedItem.getStack().getItem(), storedItem.getStack().getTag(), stack.getCount()); + + "to continue anyways with the actual extracted amount of {}.", player, recipeID, source.getUsed(), storedItem.getItem(), + storedItem.getInternalTag(), stack.getCount()); } } else { int actualSlot; @@ -781,7 +782,7 @@ public boolean isEmpty() { @Override public ItemStack getStack() { - return type == null ? ItemStack.EMPTY : type.getStack(); + return type == null ? ItemStack.EMPTY : type.getInternalStack(); } @Override diff --git a/src/main/java/mekanism/common/inventory/container/QIOItemViewerContainer.java b/src/main/java/mekanism/common/inventory/container/QIOItemViewerContainer.java index 3d4d5a2dda8..fd0d94fc008 100644 --- a/src/main/java/mekanism/common/inventory/container/QIOItemViewerContainer.java +++ b/src/main/java/mekanism/common/inventory/container/QIOItemViewerContainer.java @@ -455,7 +455,7 @@ public void updateSearch(String queryText) { list = new ArrayList<>(); ISearchQuery query = SearchQueryParser.parse(queryText); for (IScrollableSlot slot : itemList) { - if (query.matches(slot.getItem().getStack())) { + if (query.matches(slot.getItem().getInternalStack())) { list.add(slot); } } @@ -474,7 +474,7 @@ public void onClick(IScrollableSlot slot, int button, boolean hasShiftDown, Item } if (button == 0) { if (heldItem.isEmpty() && slot != null) { - int toTake = Math.min(slot.getItem().getStack().getMaxStackSize(), MathUtils.clampToInt(slot.getCount())); + int toTake = Math.min(slot.getItem().getMaxStackSize(), MathUtils.clampToInt(slot.getCount())); Mekanism.packetHandler().sendToServer(PacketQIOItemViewerSlotInteract.take(slot.getItemUUID(), toTake)); } else if (!heldItem.isEmpty()) { Mekanism.packetHandler().sendToServer(PacketQIOItemViewerSlotInteract.put(heldItem.getCount())); @@ -482,7 +482,7 @@ public void onClick(IScrollableSlot slot, int button, boolean hasShiftDown, Item } else if (button == 1) { if (heldItem.isEmpty() && slot != null) { //Cap it out at the max stack size of the item, but try to take half of what is stored (taking at least one if it is a single item) - int toTake = Mth.clamp(MathUtils.clampToInt(slot.getCount() / 2), 1, slot.getItem().getStack().getMaxStackSize()); + int toTake = Mth.clamp(MathUtils.clampToInt(slot.getCount() / 2), 1, slot.getItem().getMaxStackSize()); Mekanism.packetHandler().sendToServer(PacketQIOItemViewerSlotInteract.take(slot.getItemUUID(), toTake)); } else if (!heldItem.isEmpty()) { Mekanism.packetHandler().sendToServer(PacketQIOItemViewerSlotInteract.put(1)); @@ -519,12 +519,12 @@ public long getCount() { @Override public String getModID() { - return MekanismUtils.getModId(getItem().getStack()); + return MekanismUtils.getModId(getItem().getInternalStack()); } @Override public String getDisplayName() { - return getItem().getStack().getHoverName().getString(); + return getItem().getInternalStack().getHoverName().getString(); } } diff --git a/src/main/java/mekanism/common/lib/inventory/HashedItem.java b/src/main/java/mekanism/common/lib/inventory/HashedItem.java index 1b83e816e03..dad5795ca34 100644 --- a/src/main/java/mekanism/common/lib/inventory/HashedItem.java +++ b/src/main/java/mekanism/common/lib/inventory/HashedItem.java @@ -1,7 +1,11 @@ package mekanism.common.lib.inventory; +import java.util.Objects; import java.util.UUID; +import mekanism.api.annotations.NothingNullByDefault; +import mekanism.api.inventory.IHashedItem; import mekanism.common.util.StackUtils; +import net.minecraft.nbt.CompoundTag; import net.minecraft.world.item.ItemStack; import net.minecraftforge.items.ItemHandlerHelper; import org.jetbrains.annotations.NotNull; @@ -12,9 +16,10 @@ * * @author aidancbrady */ -public class HashedItem { +@NothingNullByDefault +public class HashedItem implements IHashedItem { - public static HashedItem create(@NotNull ItemStack stack) { + public static HashedItem create(ItemStack stack) { return new HashedItem(StackUtils.size(stack, 1)); } @@ -24,40 +29,65 @@ public static HashedItem create(@NotNull ItemStack stack) { * @apiNote When using this, you should be very careful to not accidentally modify the backing stack, this is mainly for use where we want to use an {@link ItemStack} * as a key in a map that is local to a single method, and don't want the overhead of copying the stack when it is not needed. */ - public static HashedItem raw(@NotNull ItemStack stack) { + public static HashedItem raw(ItemStack stack) { return new HashedItem(stack); } - @NotNull private final ItemStack itemStack; private final int hashCode; - protected HashedItem(@NotNull ItemStack stack) { + protected HashedItem(ItemStack stack) { this.itemStack = stack; this.hashCode = initHashCode(); } protected HashedItem(HashedItem other) { - this.itemStack = other.itemStack; - this.hashCode = other.hashCode; + this(other.itemStack, other.hashCode); } - @NotNull + protected HashedItem(ItemStack stack, int hashCode) { + this.itemStack = stack; + this.hashCode = hashCode; + } + + //TODO: Deprecate in favor of getInternalStack? + @Deprecated(forRemoval = true, since = "10.3.6") public ItemStack getStack() { return itemStack; } - @NotNull + @Override + public ItemStack getInternalStack() { + return itemStack; + } + + @Override public ItemStack createStack(int size) { return StackUtils.size(itemStack, size); } + /** + * @apiNote Main use is to ensure that this HashedItem is not raw, but to allow skipping recalculating the hash code. This will cause a stack copy if used on an + * already not-raw HashedItem, so ideally this should only be called on raw stacks and otherwise properly kept track of by the caller. + */ + public HashedItem recreate() { + return new HashedItem(createStack(1), hashCode); + } + + /** + * Helper to serialize the internal stack to nbt. + */ + @NotNull + public CompoundTag internalToNBT() { + return itemStack.serializeNBT(); + } + @Override public boolean equals(Object obj) { if (obj == this) { return true; } - return obj instanceof HashedItem other && ItemHandlerHelper.canItemStacksStack(itemStack, other.itemStack); + return obj instanceof IHashedItem other && ItemHandlerHelper.canItemStacksStack(itemStack, other.getInternalStack()); } @Override @@ -66,8 +96,7 @@ public int hashCode() { } private int initHashCode() { - int code = 1; - code = 31 * code + itemStack.getItem().hashCode(); + int code = itemStack.getItem().hashCode(); if (itemStack.hasTag()) { code = 31 * code + itemStack.getTag().hashCode(); } @@ -79,21 +108,32 @@ private int initHashCode() { public static class UUIDAwareHashedItem extends HashedItem { + @Nullable private final UUID uuid; + private final int uuidBasedHash; private final boolean overrideHash; /** + * @param uuid Should not be null unless something went wrong reading the packet. + * * @apiNote For use on the client side, hash is taken into account for equals and hashCode */ - public UUIDAwareHashedItem(ItemStack stack, UUID uuid) { + public UUIDAwareHashedItem(ItemStack stack, @Nullable UUID uuid) { super(StackUtils.size(stack, 1)); this.uuid = uuid; - this.overrideHash = true; + if (this.uuid == null) { + this.overrideHash = false; + this.uuidBasedHash = super.hashCode(); + } else { + this.overrideHash = true; + this.uuidBasedHash = Objects.hash(super.hashCode(), this.uuid); + } } - public UUIDAwareHashedItem(HashedItem other, UUID uuid) { + public UUIDAwareHashedItem(HashedItem other, @NotNull UUID uuid) { super(other); this.uuid = uuid; + this.uuidBasedHash = super.hashCode(); this.overrideHash = false; } @@ -106,8 +146,9 @@ public UUID getUUID() { public boolean equals(Object obj) { if (obj == this) { return true; - } - if (overrideHash && uuid != null) { + } else if (overrideHash) { + //Note: UUID cannot be null if overrideHash is true + //noinspection DataFlowIssue return obj instanceof UUIDAwareHashedItem uuidAware && uuid.equals(uuidAware.uuid) && super.equals(obj); } return super.equals(obj); @@ -115,10 +156,7 @@ public boolean equals(Object obj) { @Override public int hashCode() { - if (overrideHash && uuid != null) { - return 31 * super.hashCode() + uuid.hashCode(); - } - return super.hashCode(); + return uuidBasedHash; } /** diff --git a/src/main/java/mekanism/common/lib/inventory/TileTransitRequest.java b/src/main/java/mekanism/common/lib/inventory/TileTransitRequest.java index 190af986927..9eb50e70672 100644 --- a/src/main/java/mekanism/common/lib/inventory/TileTransitRequest.java +++ b/src/main/java/mekanism/common/lib/inventory/TileTransitRequest.java @@ -65,7 +65,9 @@ public void addSlot(int id, ItemStack stack) { public ItemStack use(int amount) { Direction side = getSide(); IItemHandler handler = InventoryUtils.assertItemHandler("TileTransitRequest", tile, side); - if (handler != null) { + if (handler != null && !slotMap.isEmpty()) { + HashedItem itemType = getItemType(); + ItemStack itemStack = itemType.getInternalStack(); ObjectIterator iterator = slotMap.int2IntEntrySet().iterator(); while (iterator.hasNext()) { Int2IntMap.Entry entry = iterator.next(); @@ -73,16 +75,16 @@ public ItemStack use(int amount) { int currentCount = entry.getIntValue(); int toUse = Math.min(amount, currentCount); ItemStack ret = handler.extractItem(slot, toUse, false); - boolean stackable = InventoryUtils.areItemsStackable(getItemType().getStack(), ret); + boolean stackable = InventoryUtils.areItemsStackable(itemStack, ret); if (!stackable || ret.getCount() != toUse) { // be loud if an InvStack's prediction doesn't line up Mekanism.logger.warn("An inventory's returned content {} does not line up with TileTransitRequest's prediction.", stackable ? "count" : "type"); - Mekanism.logger.warn("TileTransitRequest item: {}, toUse: {}, ret: {}, slot: {}", getItemType().getStack(), toUse, ret, slot); + Mekanism.logger.warn("TileTransitRequest item: {}, toUse: {}, ret: {}, slot: {}", itemStack, toUse, ret, slot); Mekanism.logger.warn("Tile: {} {} {}", RegistryUtils.getName(tile.getType()), tile.getBlockPos(), side); } amount -= toUse; totalCount -= toUse; if (totalCount == 0) { - itemMap.remove(getItemType()); + itemMap.remove(itemType); } currentCount = currentCount - toUse; if (currentCount == 0) { diff --git a/src/main/java/mekanism/common/network/to_client/PacketQIOItemViewerGuiSync.java b/src/main/java/mekanism/common/network/to_client/PacketQIOItemViewerGuiSync.java index 14712996f75..5a8e1a31cb4 100644 --- a/src/main/java/mekanism/common/network/to_client/PacketQIOItemViewerGuiSync.java +++ b/src/main/java/mekanism/common/network/to_client/PacketQIOItemViewerGuiSync.java @@ -59,7 +59,7 @@ public void encode(FriendlyByteBuf buffer) { buffer.writeVarLong(countCapacity); buffer.writeVarInt(typeCapacity); BasePacketHandler.writeMap(buffer, itemMap, (key, value, buf) -> { - buf.writeItem(key.getStack()); + buf.writeItem(key.getInternalStack()); //Shouldn't be null unless something failed, but if it does try to handle it relatively gracefully BasePacketHandler.writeOptional(buf, key.getUUID(), FriendlyByteBuf::writeUUID); buf.writeVarLong(value); diff --git a/src/main/java/mekanism/common/network/to_server/PacketQIOItemViewerSlotInteract.java b/src/main/java/mekanism/common/network/to_server/PacketQIOItemViewerSlotInteract.java index 106af3dbf1a..02ff06e9f41 100644 --- a/src/main/java/mekanism/common/network/to_server/PacketQIOItemViewerSlotInteract.java +++ b/src/main/java/mekanism/common/network/to_server/PacketQIOItemViewerSlotInteract.java @@ -58,7 +58,7 @@ public void handle(NetworkEvent.Context context) { if (itemType != null) { if (type == Type.TAKE) { //Should always be true but validate it before actually removing from the QIO - if (InventoryUtils.areItemsStackable(curStack, itemType.getStack())) { + if (InventoryUtils.areItemsStackable(curStack, itemType.getInternalStack())) { ItemStack ret = freq.removeByType(itemType, count); if (curStack.isEmpty()) { player.containerMenu.setCarried(ret); @@ -69,7 +69,7 @@ public void handle(NetworkEvent.Context context) { player.containerMenu.getCarried())); } } else if (type == Type.SHIFT_TAKE) { - ItemStack ret = freq.removeByType(itemType, itemType.getStack().getMaxStackSize()); + ItemStack ret = freq.removeByType(itemType, itemType.getMaxStackSize()); if (!ret.isEmpty()) { ItemStack remainder = container.insertIntoPlayerInventory(player.getUUID(), ret); if (!remainder.isEmpty()) { diff --git a/src/main/java/mekanism/common/tile/factory/TileEntityFactory.java b/src/main/java/mekanism/common/tile/factory/TileEntityFactory.java index 79052bd2916..8832e1e2ac5 100644 --- a/src/main/java/mekanism/common/tile/factory/TileEntityFactory.java +++ b/src/main/java/mekanism/common/tile/factory/TileEntityFactory.java @@ -557,7 +557,7 @@ private void sortInventory() { // until it is needed. That way if we have no empty slots and all our input slots are filled // we don't do any extra processing here, and can properly short circuit HashedItem item = entry.getKey(); - ItemStack largerInput = item.createStack(Math.min(item.getStack().getMaxStackSize(), recipeProcessInfo.totalCount)); + ItemStack largerInput = item.createStack(Math.min(item.getMaxStackSize(), recipeProcessInfo.totalCount)); ProcessInfo processInfo = recipeProcessInfo.processes.get(0); //Try getting a recipe for our input with a larger size, and update the cache if we find one RECIPE recipe = getRecipeForInput(processInfo.process(), largerInput, processInfo.outputSlot(), processInfo.secondaryOutputSlot(), true); @@ -594,7 +594,7 @@ private void addEmptySlotsAsTargets(Map processes continue; } //Note: This is some arbitrary input stack one of the stacks contained - ItemStack sourceStack = entry.getKey().getStack(); + ItemStack sourceStack = entry.getKey().getInternalStack(); int emptyToAdd = maxSlots - processCount; int added = 0; List toRemove = new ArrayList<>(); @@ -631,7 +631,7 @@ private void distributeItems(Map processes) { } HashedItem item = entry.getKey(); //Note: This isn't based on any limits the slot may have (but we currently don't have any reduced ones here, so it doesn't matter) - int maxStackSize = item.getStack().getMaxStackSize(); + int maxStackSize = item.getMaxStackSize(); int numberPerSlot = recipeProcessInfo.totalCount / processCount; if (numberPerSlot == maxStackSize) { //If all the slots are already maxed out; short-circuit, no balancing is needed diff --git a/src/main/java/mekanism/common/tile/machine/TileEntityFormulaicAssemblicator.java b/src/main/java/mekanism/common/tile/machine/TileEntityFormulaicAssemblicator.java index 1ab4bad8fcc..9f5f0d2af9b 100644 --- a/src/main/java/mekanism/common/tile/machine/TileEntityFormulaicAssemblicator.java +++ b/src/main/java/mekanism/common/tile/machine/TileEntityFormulaicAssemblicator.java @@ -146,11 +146,11 @@ protected IInventorySlotHolder getInitialInventory(IContentsListener listener) { } IntList indices = formula.getIngredientIndices(level, stack); if (!indices.isEmpty()) { - HashedItem stockItem = stockControlMap[index]; - if (!stockControl || stockItem == null) { - return true; + if (stockControl) { + HashedItem stockItem = stockControlMap[index]; + return stockItem == null || ItemHandlerHelper.canItemStacksStack(stockItem.getInternalStack(), stack); } - return ItemHandlerHelper.canItemStacksStack(stockItem.getStack(), stack); + return true; } return false; }, BasicInventorySlot.alwaysTrue, listener, 8 + slotX * 18, 98 + slotY * 18); @@ -486,8 +486,8 @@ private void organizeStock() { // build map of what items we have to organize Object2IntMap storedMap = new Object2IntOpenHashMap<>(); for (IInventorySlot inputSlot : inputSlots) { - ItemStack stack = inputSlot.getStack(); - if (!stack.isEmpty()) { + if (!inputSlot.isEmpty()) { + ItemStack stack = inputSlot.getStack(); HashedItem hashed = HashedItem.create(stack); storedMap.put(hashed, storedMap.getOrDefault(hashed, 0) + stack.getCount()); } @@ -500,7 +500,7 @@ private void organizeStock() { unused.add(i); } else if (storedMap.containsKey(hashedItem)) { int stored = storedMap.getInt(hashedItem); - int count = Math.min(hashedItem.getStack().getMaxStackSize(), stored); + int count = Math.min(hashedItem.getMaxStackSize(), stored); if (count == stored) { storedMap.removeInt(hashedItem); } else { @@ -558,7 +558,7 @@ private boolean setSlotIfChanged(Object2IntMap storedMap, IInventory Object2IntMap.Entry next = storedMap.object2IntEntrySet().iterator().next(); HashedItem item = next.getKey(); int stored = next.getIntValue(); - int count = Math.min(item.getStack().getMaxStackSize(), stored); + int count = Math.min(item.getMaxStackSize(), stored); if (count == stored) { storedMap.removeInt(item); empty = storedMap.isEmpty(); diff --git a/src/main/java/mekanism/common/tile/machine/TileEntityNutritionalLiquifier.java b/src/main/java/mekanism/common/tile/machine/TileEntityNutritionalLiquifier.java index 6cc5ce3072d..df0d0e1b9a5 100644 --- a/src/main/java/mekanism/common/tile/machine/TileEntityNutritionalLiquifier.java +++ b/src/main/java/mekanism/common/tile/machine/TileEntityNutritionalLiquifier.java @@ -160,7 +160,7 @@ protected void onUpdateServer() { } else { HashedItem item = HashedItem.raw(inputSlot.getStack()); if (!item.equals(lastPasteItem)) { - lastPasteItem = HashedItem.create(item.getStack()); + lastPasteItem = item.recreate(); needsPacket = true; } } @@ -216,7 +216,7 @@ public ItemStack getRenderStack() { if (lastPasteItem == null) { return ItemStack.EMPTY; } - return lastPasteItem.getStack(); + return lastPasteItem.getInternalStack(); } @NotNull @@ -226,8 +226,8 @@ public CompoundTag getReducedUpdateTag() { updateTag.put(NBTConstants.FLUID_STORED, fluidTank.serializeNBT()); CompoundTag item = new CompoundTag(); if (lastPasteItem != null) { - NBTUtils.writeRegistryEntry(item, NBTConstants.ID, ForgeRegistries.ITEMS, lastPasteItem.getStack().getItem()); - CompoundTag tag = lastPasteItem.getStack().getTag(); + NBTUtils.writeRegistryEntry(item, NBTConstants.ID, ForgeRegistries.ITEMS, lastPasteItem.getItem()); + CompoundTag tag = lastPasteItem.getInternalTag(); if (tag != null) { item.put(NBTConstants.TAG, tag.copy()); } diff --git a/src/main/java/mekanism/common/tile/qio/TileEntityQIORedstoneAdapter.java b/src/main/java/mekanism/common/tile/qio/TileEntityQIORedstoneAdapter.java index f007f0938ec..6df945a5b00 100644 --- a/src/main/java/mekanism/common/tile/qio/TileEntityQIORedstoneAdapter.java +++ b/src/main/java/mekanism/common/tile/qio/TileEntityQIORedstoneAdapter.java @@ -24,12 +24,14 @@ import net.minecraftforge.client.model.data.ModelProperty; import net.minecraftforge.registries.ForgeRegistries; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public class TileEntityQIORedstoneAdapter extends TileEntityQIOComponent { public static final ModelProperty POWERING_PROPERTY = new ModelProperty<>(); private boolean prevPowering; + @Nullable private HashedItem itemType = null; private boolean fuzzy; private long count = 0; @@ -52,7 +54,7 @@ private long getFreqStored() { if (freq == null || itemType == null) { return 0; } else if (fuzzy) { - return freq.getTypesForItem(itemType.getStack().getItem()).stream().mapToLong(freq::getStored).sum(); + return freq.getTypesForItem(itemType.getItem()).stream().mapToLong(freq::getStored).sum(); } return freq.getStored(itemType); } @@ -104,7 +106,7 @@ public ModelData getModelData() { public void writeSustainedData(CompoundTag dataMap) { super.writeSustainedData(dataMap); if (itemType != null) { - dataMap.put(NBTConstants.SINGLE_ITEM, itemType.getStack().serializeNBT()); + dataMap.put(NBTConstants.SINGLE_ITEM, itemType.internalToNBT()); } dataMap.putLong(NBTConstants.AMOUNT, count); dataMap.putBoolean(NBTConstants.FUZZY_MODE, fuzzy); @@ -146,7 +148,7 @@ public void handleUpdateTag(@NotNull CompoundTag tag) { @ComputerMethod(nameOverride = "getTargetItem") public ItemStack getItemType() { - return itemType == null ? ItemStack.EMPTY : itemType.getStack(); + return itemType == null ? ItemStack.EMPTY : itemType.getInternalStack(); } @ComputerMethod(nameOverride = "getTriggerAmount")