From d307eccba0533a3f7093703e446d8ce85cf03f94 Mon Sep 17 00:00:00 2001 From: Sara Freimer Date: Sun, 15 Aug 2021 16:23:07 -0400 Subject: [PATCH] More work on fixing various issues with the QIO including some related to the crafting windows: - Fix accidentally having broken being able to extract from the output slot when adding support for doLimitedCrafting #7203 - Fix rounding in QIO GUI of items stored which caused things like showing 100000.0K when there are 9,999,935 items stored instead of showing 9999.9K and make 9,999,999 display as 9999.9K as well rather than rounding up to 10M. We also now only display the decimal place if it is in use, and actually make use of it at times rather than it just being ignored and always being zero - Fix the QIO sometimes showing multiple stacks of the same item in the GUI because of it not properly removing the old stack #7229 - Fix desyncs when splitting stacks via quick crafting and then releasing the remaining stacks over the QIO viewer by not inserting items into the QIO viewer on release from quick crafting, and requiring a second click instead --- .../java/mekanism/client/gui/GuiMekanism.java | 5 +++ .../java/mekanism/client/gui/IGuiWrapper.java | 4 +++ .../gui/element/scroll/GuiSlotScroll.java | 19 +++++++++-- .../common/content/qio/QIOCraftingWindow.java | 5 +-- .../common/content/qio/QIOFrequency.java | 32 ++++++++++++++++--- .../slot/VirtualCraftingOutputSlot.java | 5 ++- .../container/tile/MekanismTileContainer.java | 1 - .../PacketQIOFillCraftingWindow.java | 2 +- 8 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/main/java/mekanism/client/gui/GuiMekanism.java b/src/main/java/mekanism/client/gui/GuiMekanism.java index bd6f87f7c3c..379c37ba8a2 100644 --- a/src/main/java/mekanism/client/gui/GuiMekanism.java +++ b/src/main/java/mekanism/client/gui/GuiMekanism.java @@ -532,6 +532,11 @@ public ItemRenderer getItemRenderer() { return itemRenderer; } + @Override + public boolean currentlyQuickCrafting() { + return isQuickCrafting && !quickCraftSlots.isEmpty(); + } + @Override public void addWindow(GuiWindow window) { GuiWindow top = windows.isEmpty() ? null : windows.iterator().next(); diff --git a/src/main/java/mekanism/client/gui/IGuiWrapper.java b/src/main/java/mekanism/client/gui/IGuiWrapper.java index f9324d4657a..6abead994f5 100644 --- a/src/main/java/mekanism/client/gui/IGuiWrapper.java +++ b/src/main/java/mekanism/client/gui/IGuiWrapper.java @@ -72,6 +72,10 @@ default void removeWindow(GuiWindow window) { Mekanism.logger.error("Tried to call 'removeWindow' but unsupported in {}", getClass().getName()); } + default boolean currentlyQuickCrafting() { + return false; + } + @Nullable default GuiWindow getWindowHovering(double mouseX, double mouseY) { Mekanism.logger.error("Tried to call 'getWindowHovering' but unsupported in {}", getClass().getName()); 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 e409bcc1932..1d69afe6724 100644 --- a/src/main/java/mekanism/client/gui/element/scroll/GuiSlotScroll.java +++ b/src/main/java/mekanism/client/gui/element/scroll/GuiSlotScroll.java @@ -1,6 +1,8 @@ package mekanism.client.gui.element.scroll; import com.mojang.blaze3d.matrix.MatrixStack; +import java.math.RoundingMode; +import java.text.DecimalFormat; import java.util.Collections; import java.util.List; import java.util.function.Supplier; @@ -24,6 +26,11 @@ public class GuiSlotScroll extends GuiRelativeElement { private static final ResourceLocation SLOTS = MekanismUtils.getResource(ResourceType.GUI_SLOT, "slots.png"); private static final ResourceLocation SLOTS_DARK = MekanismUtils.getResource(ResourceType.GUI_SLOT, "slots_dark.png"); + private static final DecimalFormat COUNT_FORMAT = new DecimalFormat("#.#"); + + static { + COUNT_FORMAT.setRoundingMode(RoundingMode.FLOOR); + } private final GuiScrollBar scrollBar; @@ -91,6 +98,10 @@ public boolean mouseScrolled(double mouseX, double mouseY, double delta) { @Override public boolean mouseReleased(double mouseX, double mouseY, int button) { + if (gui().currentlyQuickCrafting()) { + //If the player is currently quick crafting don't do any special handling for as if they clicked in the screen + return super.mouseReleased(mouseX, mouseY, button); + } super.mouseReleased(mouseX, mouseY, button); IScrollableSlot slot = getSlot(mouseX, mouseY, x, y); clickHandler.onClick(slot, button, Screen.hasShiftDown(), minecraft.player.inventory.getCarried()); @@ -167,16 +178,18 @@ private void renderSlotText(MatrixStack matrix, String text, int x, int y) { } private String getCountText(long count) { + //Note: For cases like 9,999,999 we intentionally display as 9999.9K instead of 10M so that people + // do not think they have more stored than they actually have just because it is rounding up if (count <= 1) { return null; } else if (count < 10_000) { return Long.toString(count); } else if (count < 10_000_000) { - return Double.toString(Math.round(count / 1_000D)) + "K"; + return COUNT_FORMAT.format(count / 1_000D) + "K"; } else if (count < 10_000_000_000L) { - return Double.toString(Math.round(count / 1_000_000D)) + "M"; + return COUNT_FORMAT.format(count / 1_000_000D) + "M"; } else if (count < 10_000_000_000_000L) { - return Double.toString(Math.round(count / 1_000_000_000D)) + "B"; + return COUNT_FORMAT.format(count / 1_000_000_000D) + "B"; } return ">10T"; } diff --git a/src/main/java/mekanism/common/content/qio/QIOCraftingWindow.java b/src/main/java/mekanism/common/content/qio/QIOCraftingWindow.java index 04f0aaa2f8a..b0f3e66827b 100644 --- a/src/main/java/mekanism/common/content/qio/QIOCraftingWindow.java +++ b/src/main/java/mekanism/common/content/qio/QIOCraftingWindow.java @@ -528,8 +528,9 @@ private boolean updateRemainderHelperInputs(boolean updated, @Nonnull ItemStack private class QIOCraftingInventory extends CraftingInventory { - //TODO - 10.1: Suppress warning and also add a note that we override all the places where it being - // null would cause issues, and we handle slots individually as well + //Note: We suppress the warning about this passing null as the container as we override all methods that + // that make use of the container to use our handler instead + @SuppressWarnings("ConstantConditions") public QIOCraftingInventory() { super(null, 0, 0); } diff --git a/src/main/java/mekanism/common/content/qio/QIOFrequency.java b/src/main/java/mekanism/common/content/qio/QIOFrequency.java index c5cda5c7dda..8536131eeed 100644 --- a/src/main/java/mekanism/common/content/qio/QIOFrequency.java +++ b/src/main/java/mekanism/common/content/qio/QIOFrequency.java @@ -54,8 +54,13 @@ public class QIOFrequency extends Frequency { private final Map> modIDLookupMap = new HashMap<>(); // efficiently keep track of the items for use in fuzzy lookup utilized by the items stored private final Map> fuzzyItemLookupMap = new HashMap<>(); - //Keep track of a UUID for each hashed item + // keep track of a UUID for each hashed item private final BiMap itemTypeLookup = HashBiMap.create(); + // allows for lazily removing the UUIDs assigned to items in the itemTypeLookup BiMap without having any issues + // come up related to updatedItems being a set and UUIDAwareHashedItems server side intentionally not comparing + // the UUIDs, so then if multiple add/remove calls happened at once the items in need of updating potentially + // would sync using a different UUID to the client, causing the client to not know the old stack needed to be removed + private final Set uuidsToInvalidate = new HashSet<>(); // a sensitive cache for wildcard tag lookups (wildcard -> [matching tags]) private final SetMultimap tagWildcardCache = HashMultimap.create(); private final Set failedWildcardTags = new HashSet<>(); @@ -141,7 +146,15 @@ private QIOItemTypeData createTypeDataForAbsent(HashedItem type) { }).add(type); //Fuzzy item lookup has no wildcard cache related to it fuzzyItemLookupMap.computeIfAbsent(stack.getItem(), item -> new HashSet<>()).add(type); - itemTypeLookup.put(type, UUID.randomUUID()); + UUID oldUUID = getUUIDForType(type); + if (oldUUID != null) { + //If there was a UUID stored and prepped to be invalidated, remove it from the UUIDS we are trying to invalidate + // so that it is able to continue being used/sync'd to the client + uuidsToInvalidate.remove(oldUUID); + } else { + // otherwise, create a new uuid for use with this item type + itemTypeLookup.put(type, UUID.randomUUID()); + } return new QIOItemTypeData(type); } @@ -154,7 +167,7 @@ public ItemStack removeItem(ItemStack stack, int amount) { } public ItemStack removeByType(@Nullable HashedItem itemType, int amount) { - if (itemDataMap.isEmpty()) { + if (itemDataMap.isEmpty() || amount <= 0) { return ItemStack.EMPTY; } @@ -180,7 +193,11 @@ public ItemStack removeByType(@Nullable HashedItem itemType, int amount) { private void removeItemData(HashedItem type) { itemDataMap.remove(type); - itemTypeLookup.remove(type); + //If the item has a UUID that corresponds to it, add that UUID to our list of uuids to invalidate + UUID toInvalidate = getUUIDForType(type); + if (toInvalidate != null) { + uuidsToInvalidate.add(toInvalidate); + } //Note: We need to copy the tags to a new collection as otherwise when we start removing them from the lookup // they will also get removed from this view Set tags = new HashSet<>(tagLookupMap.getKeys(type)); @@ -346,6 +363,13 @@ public QIODriveData getDriveData(QIODriveKey key) { @Override public void tick() { super.tick(); + if (!uuidsToInvalidate.isEmpty()) { + //If we have uuids we need to invalidate the Item UUID pairing of them + for (UUID uuidToInvalidate : uuidsToInvalidate) { + itemTypeLookup.inverse().remove(uuidToInvalidate); + } + uuidsToInvalidate.clear(); + } if (!updatedItems.isEmpty() || needsUpdate) { Object2LongMap map = new Object2LongOpenHashMap<>(); updatedItems.forEach(type -> { diff --git a/src/main/java/mekanism/common/inventory/container/slot/VirtualCraftingOutputSlot.java b/src/main/java/mekanism/common/inventory/container/slot/VirtualCraftingOutputSlot.java index 0ea9a0b858e..879b8c8135a 100644 --- a/src/main/java/mekanism/common/inventory/container/slot/VirtualCraftingOutputSlot.java +++ b/src/main/java/mekanism/common/inventory/container/slot/VirtualCraftingOutputSlot.java @@ -90,7 +90,10 @@ public ItemStack onTake(@Nonnull PlayerEntity player, @Nonnull ItemStack stack) @Override public boolean mayPickup(@Nonnull PlayerEntity player) { - return canCraft && super.mayPickup(player); + if (player.level.isClientSide || !(player instanceof ServerPlayerEntity)) { + return canCraft && super.mayPickup(player); + } + return craftingWindow.canViewRecipe((ServerPlayerEntity) player) && super.mayPickup(player); } @Nonnull diff --git a/src/main/java/mekanism/common/inventory/container/tile/MekanismTileContainer.java b/src/main/java/mekanism/common/inventory/container/tile/MekanismTileContainer.java index 822d7e839c5..27e91e8fa20 100644 --- a/src/main/java/mekanism/common/inventory/container/tile/MekanismTileContainer.java +++ b/src/main/java/mekanism/common/inventory/container/tile/MekanismTileContainer.java @@ -75,7 +75,6 @@ protected void addSlots() { } if (tile.supportsUpgrades()) { //Add the virtual slot for the upgrade (add them before the main inventory to make sure they take priority in targeting) - //TODO - 10.1: Test this and test how it handles addSlot(upgradeSlot = tile.getComponent().getUpgradeSlot().createContainerSlot()); addSlot(upgradeOutputSlot = tile.getComponent().getUpgradeOutputSlot().createContainerSlot()); } diff --git a/src/main/java/mekanism/common/network/to_server/PacketQIOFillCraftingWindow.java b/src/main/java/mekanism/common/network/to_server/PacketQIOFillCraftingWindow.java index c8c51efb97c..fd78f5facef 100644 --- a/src/main/java/mekanism/common/network/to_server/PacketQIOFillCraftingWindow.java +++ b/src/main/java/mekanism/common/network/to_server/PacketQIOFillCraftingWindow.java @@ -275,7 +275,7 @@ private void transferItems(@Nullable QIOFrequency frequency, QIOCraftingWindow c targetContents.put(entry.getByteKey(), stack); } } - //Extract what items are still + //Extract what items are still in the window Byte2ObjectMap remainingCraftingGridContents = new Byte2ObjectArrayMap<>(9); for (byte i = 0; i < 9; i++) { CraftingWindowInventorySlot inputSlot = craftingWindow.getInputSlot(i);