Search in sources :

Example 1 with HashedItem

use of mekanism.common.lib.inventory.HashedItem in project Mekanism by mekanism.

the class QIOCraftingTransferHandler method hasRoomToShuffle.

/**
 * Loosely based on how {@link mekanism.common.content.qio.QIOServerCraftingTransferHandler}'s hasRoomToShuffle method works.
 */
private static boolean hasRoomToShuffle(QIOCraftingTransferHelper qioTransferHelper, @Nullable QIOFrequency frequency, QIOCraftingWindow craftingWindow, List<HotBarSlot> hotBarSlots, List<MainInventorySlot> mainInventorySlots, Map<HashedItemSource, List<List<SingularHashedItemSource>>> shuffleLookup) {
    // Map used to keep track of inputs while also merging identical inputs, so we can cut down
    // on how many times we have to check if things can stack
    Object2IntMap<HashedItem> leftOverInput = new Object2IntArrayMap<>(9);
    for (byte slotIndex = 0; slotIndex < 9; slotIndex++) {
        IInventorySlot slot = craftingWindow.getInputSlot(slotIndex);
        if (!slot.isEmpty()) {
            // Note: We can use raw as we are not modifying the stack or persisting the reference
            HashedItem type = HashedItem.raw(slot.getStack());
            HashedItemSource source = qioTransferHelper.getSource(type);
            if (source == null) {
                // Something went wrong, this should never be null for the things in the crafting slots
                return false;
            }
            int remaining = source.getSlotRemaining(slotIndex);
            if (remaining > 0) {
                // Don't bother adding any that we fully used
                leftOverInput.mergeInt(type, remaining, Integer::sum);
            }
        }
    }
    if (!leftOverInput.isEmpty()) {
        // If we have any leftover inputs in the crafting inventory, then get a simulated view of what the player's inventory
        // will look like after things are changed
        BaseSimulatedInventory simulatedInventory = new BaseSimulatedInventory(hotBarSlots, mainInventorySlots) {

            @Override
            protected int getRemaining(int slot, ItemStack currentStored) {
                HashedItemSource source = qioTransferHelper.getSource(HashedItem.raw(currentStored));
                if (source == null) {
                    return currentStored.getCount();
                }
                return source.getSlotRemaining((byte) (slot + 9));
            }
        };
        Object2IntMap<HashedItem> stillLeftOver = simulatedInventory.shuffleInputs(leftOverInput, frequency != null);
        if (stillLeftOver == null) {
            // If we have remaining items and no frequency then we don't have room to shuffle
            return false;
        }
        if (!stillLeftOver.isEmpty() && frequency != null) {
            // If we still have left over things try adding them to the frequency. We only are able to do a rough check and estimate
            // on if the frequency has room or not as depending on how things are stored in the drives there is a chance that we
            // do not actually have as much item space or types available, but this is the best we can do on the client side
            // Note: We validate the frequency is not null, even though it shouldn't be null if we have anything still left over
            // Note: We calculate these numbers as a difference so that it is easier to make sure none of the numbers accidentally overflow
            int availableItemTypes = frequency.getTotalItemTypeCapacity() - frequency.getTotalItemTypes(true);
            long availableItemSpace = frequency.getTotalItemCountCapacity() - frequency.getTotalItemCount();
            Object2BooleanMap<HashedItemSource> usedQIOSource = new Object2BooleanArrayMap<>(shuffleLookup.size());
            for (Map.Entry<HashedItemSource, List<List<SingularHashedItemSource>>> entry : shuffleLookup.entrySet()) {
                HashedItemSource source = entry.getKey();
                boolean usedQIO = false;
                for (List<SingularHashedItemSource> usedSources : entry.getValue()) {
                    for (SingularHashedItemSource usedSource : usedSources) {
                        UUID qioSource = usedSource.getQioSource();
                        if (qioSource != null) {
                            // Free up however much space as we used of the item
                            availableItemSpace += usedSource.getUsed();
                            if (source.getQIORemaining(qioSource) == 0) {
                                // If we used all that is available, we need to also free up an item type
                                availableItemTypes++;
                                usedQIO = true;
                            }
                        }
                    }
                }
                usedQIOSource.put(source, usedQIO);
            }
            for (Object2IntMap.Entry<HashedItem> entry : stillLeftOver.object2IntEntrySet()) {
                availableItemSpace -= entry.getIntValue();
                if (availableItemSpace <= 0) {
                    // No room for all our items, fail
                    return false;
                }
                HashedItemSource source = qioTransferHelper.getSource(entry.getKey());
                if (source == null) {
                    // Something went wrong, this should never be null for the things in the crafting slots
                    return false;
                } else if (source.hasQIOSources()) {
                    // It is stored, check to make sure it isn't a type we are removing at least one of fully
                    if (usedQIOSource.containsKey(source) && usedQIOSource.getBoolean(source)) {
                        // if it is, then we need to reclaim the item type as being available
                        availableItemTypes--;
                        if (availableItemTypes <= 0) {
                            // Not enough room for types
                            return false;
                        }
                    }
                } else {
                    // The item is not stored in the QIO frequency, we need to use an item type up
                    // Note: This is not super accurate due to the fact that we don't know for
                    // certain if our used source actually matched or differed in server side only
                    // NBT, but it is the best we can do on the client side
                    availableItemTypes--;
                    if (availableItemTypes <= 0) {
                        // Not enough room for types
                        return false;
                    }
                }
            }
        }
    }
    return true;
}
Also used : IInventorySlot(mekanism.api.inventory.IInventorySlot) HashedItem(mekanism.common.lib.inventory.HashedItem) HashedItemSource(mekanism.common.content.qio.QIOCraftingTransferHelper.HashedItemSource) SingularHashedItemSource(mekanism.common.content.qio.QIOCraftingTransferHelper.SingularHashedItemSource) SingularHashedItemSource(mekanism.common.content.qio.QIOCraftingTransferHelper.SingularHashedItemSource) Object2IntMap(it.unimi.dsi.fastutil.objects.Object2IntMap) Object2BooleanArrayMap(it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap) Object2IntArrayMap(it.unimi.dsi.fastutil.objects.Object2IntArrayMap) BaseSimulatedInventory(mekanism.common.content.qio.QIOCraftingTransferHelper.BaseSimulatedInventory) List(java.util.List) ArrayList(java.util.ArrayList) ByteList(it.unimi.dsi.fastutil.bytes.ByteList) ByteArrayList(it.unimi.dsi.fastutil.bytes.ByteArrayList) ItemStack(net.minecraft.item.ItemStack) UUID(java.util.UUID) Object2BooleanMap(it.unimi.dsi.fastutil.objects.Object2BooleanMap) Object2IntArrayMap(it.unimi.dsi.fastutil.objects.Object2IntArrayMap) Object2BooleanArrayMap(it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap) Map(java.util.Map) Byte2ObjectArrayMap(it.unimi.dsi.fastutil.bytes.Byte2ObjectArrayMap) HashMap(java.util.HashMap) Byte2ObjectMap(it.unimi.dsi.fastutil.bytes.Byte2ObjectMap) Object2IntMap(it.unimi.dsi.fastutil.objects.Object2IntMap)

Example 2 with HashedItem

use of mekanism.common.lib.inventory.HashedItem in project Mekanism by mekanism.

the class TileEntityFactory method distributeItems.

private void distributeItems(Map<HashedItem, RecipeProcessInfo> processes) {
    for (Entry<HashedItem, RecipeProcessInfo> entry : processes.entrySet()) {
        RecipeProcessInfo recipeProcessInfo = entry.getValue();
        int processCount = recipeProcessInfo.processes.size();
        if (processCount == 1) {
            // If there is only one process with the item in it; short-circuit, no balancing is needed
            continue;
        }
        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 numberPerSlot = recipeProcessInfo.totalCount / processCount;
        if (numberPerSlot == maxStackSize) {
            // If all the slots are already maxed out; short-circuit, no balancing is needed
            continue;
        }
        int remainder = recipeProcessInfo.totalCount % processCount;
        int minPerSlot = recipeProcessInfo.getMinPerSlot();
        if (minPerSlot > 1) {
            int perSlotRemainder = numberPerSlot % minPerSlot;
            if (perSlotRemainder > 0) {
                // Reduce the number we distribute per slot by what our excess
                // is if we are trying to balance it by the size of the input
                // required by the recipe
                numberPerSlot -= perSlotRemainder;
                // and then add how many items we removed to our remainder
                remainder += perSlotRemainder * processCount;
            // Note: After this processing the remainder is at most:
            // processCount - 1 + processCount * (minPerSlot - 1) =
            // processCount - 1 + processCount * minPerSlot - processCount =
            // processCount * minPerSlot - 1
            // Which means that reducing the remainder by minPerSlot for each
            // slot while we still have a remainder, will make sure
            }
            if (numberPerSlot + minPerSlot > maxStackSize) {
                // If adding how much we want per slot would cause the slot to overflow
                // we reduce how much we set per slot to how much there is room for
                // Note: we can do this safely because while our remainder may be
                // processCount * minPerSlot - 1 (as shown above), if we are in
                // this if statement, that means that we really have at most:
                // processCount * maxStackSize - 1 items being distributed and
                // have: processCount * numberPerSlot + remainder
                // which means that our remainder is actually at most:
                // processCount * (maxStackSize - numberPerSlot) - 1
                // so we can safely set our per slot distribution to maxStackSize - numberPerSlot
                minPerSlot = maxStackSize - numberPerSlot;
            }
        }
        for (int i = 0; i < processCount; i++) {
            ProcessInfo processInfo = recipeProcessInfo.processes.get(i);
            FactoryInputInventorySlot inputSlot = processInfo.getInputSlot();
            int sizeForSlot = numberPerSlot;
            if (remainder > 0) {
                // If we have a remainder, factor it into our slots
                if (remainder > minPerSlot) {
                    // If our remainder is greater than how much we need to fill out the min amount for the slot based
                    // on the recipe then, to keep it distributed as evenly as possible, increase our size for the slot
                    // by how much we need, and decrease our remainder by that amount
                    sizeForSlot += minPerSlot;
                    remainder -= minPerSlot;
                } else {
                    // Otherwise, add our entire remainder to the size for slot, and mark our remainder as fully used
                    sizeForSlot += remainder;
                    remainder = 0;
                }
            }
            if (inputSlot.isEmpty()) {
                // a stack for the slot and setting it
                if (sizeForSlot > 0) {
                    // Note: We use setStackUnchecked here, as there is a very small chance that
                    // the stack is not actually valid for the slot because of a reload causing
                    // recipes to change. If this is the case, then we want to properly not crash,
                    // but we would rather not add any extra overhead about revalidating the item
                    // each time as it can get somewhat expensive.
                    inputSlot.setStackUnchecked(item.createStack(sizeForSlot));
                }
            } else {
                // Slot is not currently empty
                if (sizeForSlot == 0) {
                    // If the amount of the item we want to set it to is zero (all got used by earlier stacks, which might
                    // happen if the recipe requires a stacked input (minPerSlot > 1)), then we need to set the slot to empty
                    inputSlot.setEmpty();
                } else if (inputSlot.getCount() != sizeForSlot) {
                    // Otherwise, if our slot doesn't already contain the amount we want it to,
                    // we need to adjust how much is stored in it, and log an error if it changed
                    // by a different amount then we expected
                    // Note: We use setStackSize here rather than setStack to avoid an unnecessary stack copy call
                    // as copying item stacks can sometimes be rather expensive in a heavily modded environment
                    MekanismUtils.logMismatchedStackSize(sizeForSlot, inputSlot.setStackSize(sizeForSlot, Action.EXECUTE));
                }
            }
        }
    }
}
Also used : HashedItem(mekanism.common.lib.inventory.HashedItem) FactoryInputInventorySlot(mekanism.common.inventory.slot.FactoryInputInventorySlot) ProcessInfo(mekanism.common.base.ProcessInfo)

Example 3 with HashedItem

use of mekanism.common.lib.inventory.HashedItem in project Mekanism by mekanism.

the class TileEntityFactory method addEmptySlotsAsTargets.

private void addEmptySlotsAsTargets(Map<HashedItem, RecipeProcessInfo> processes, List<ProcessInfo> emptyProcesses) {
    for (Entry<HashedItem, RecipeProcessInfo> entry : processes.entrySet()) {
        RecipeProcessInfo recipeProcessInfo = entry.getValue();
        int minPerSlot = recipeProcessInfo.getMinPerSlot();
        int maxSlots = recipeProcessInfo.totalCount / minPerSlot;
        if (maxSlots <= 1) {
            // If we don't have enough to even fill the input for a slot for a single recipe; skip
            continue;
        }
        // Otherwise, if we have at least enough items for two slots see how many we already have with items in them
        int processCount = recipeProcessInfo.processes.size();
        if (maxSlots <= processCount) {
            // If we don't have enough extra to fill another slot skip
            continue;
        }
        // Note: This is some arbitrary input stack one of the stacks contained
        ItemStack sourceStack = entry.getKey().getStack();
        int emptyToAdd = maxSlots - processCount;
        int added = 0;
        List<ProcessInfo> toRemove = new ArrayList<>();
        for (ProcessInfo emptyProcess : emptyProcesses) {
            if (inputProducesOutput(emptyProcess.getProcess(), sourceStack, emptyProcess.getOutputSlot(), emptyProcess.getSecondaryOutputSlot(), true)) {
                // If the input is valid for the stuff in the empty process' output slot
                // then add our empty process to our recipeProcessInfo, and mark
                // the empty process as accounted for
                recipeProcessInfo.processes.add(emptyProcess);
                toRemove.add(emptyProcess);
                added++;
                if (added >= emptyToAdd) {
                    // If we added as many as we could based on how much input we have; exit
                    break;
                }
            }
        }
        emptyProcesses.removeAll(toRemove);
        if (emptyProcesses.isEmpty()) {
            // for purposes of distributing empty slots among them
            break;
        }
    }
}
Also used : HashedItem(mekanism.common.lib.inventory.HashedItem) ArrayList(java.util.ArrayList) ProcessInfo(mekanism.common.base.ProcessInfo) ItemStack(net.minecraft.item.ItemStack)

Example 4 with HashedItem

use of mekanism.common.lib.inventory.HashedItem in project Mekanism by mekanism.

the class TileEntityFactory method sortInventory.

// End methods IComputerTile
private void sortInventory() {
    Map<HashedItem, RecipeProcessInfo> processes = new HashMap<>();
    List<ProcessInfo> emptyProcesses = new ArrayList<>();
    for (ProcessInfo processInfo : processInfoSlots) {
        IInventorySlot inputSlot = processInfo.getInputSlot();
        if (inputSlot.isEmpty()) {
            emptyProcesses.add(processInfo);
        } else {
            ItemStack inputStack = inputSlot.getStack();
            HashedItem item = HashedItem.raw(inputStack);
            RecipeProcessInfo recipeProcessInfo = processes.computeIfAbsent(item, i -> new RecipeProcessInfo());
            recipeProcessInfo.processes.add(processInfo);
            recipeProcessInfo.totalCount += inputStack.getCount();
            if (recipeProcessInfo.lazyMinPerSlot == null && !CommonWorldTickHandler.flushTagAndRecipeCaches) {
                // If we don't have a lazily initialized min per slot calculation set for it yet
                // and our cache is not invalid/out of date due to a reload
                CachedRecipe<RECIPE> cachedRecipe = getCachedRecipe(processInfo.getProcess());
                if (isCachedRecipeValid(cachedRecipe, inputStack)) {
                    // And our current process has a cached recipe then set the lazily initialized per slot value
                    // Note: If something goes wrong, and we end up with zero as how much we need as an input
                    // we just bump the value up to one to make sure we properly handle it
                    recipeProcessInfo.lazyMinPerSlot = () -> Math.max(1, getNeededInput(cachedRecipe.getRecipe(), inputStack));
                }
            }
        }
    }
    if (processes.isEmpty()) {
        // If all input slots are empty, just exit
        return;
    }
    for (Entry<HashedItem, RecipeProcessInfo> entry : processes.entrySet()) {
        RecipeProcessInfo recipeProcessInfo = entry.getValue();
        if (recipeProcessInfo.lazyMinPerSlot == null) {
            // If we don't have a lazy initializer for our minPerSlot setup, that means that there is
            // no valid cached recipe for any of the slots of this type currently, so we want to try and
            // get the recipe we will have for the first slot, once we end up with more items in the stack
            recipeProcessInfo.lazyMinPerSlot = () -> {
                // Note: We put all of this logic in the lazy init, so that we don't actually call any of this
                // 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));
                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.getProcess(), largerInput, processInfo.getOutputSlot(), processInfo.getSecondaryOutputSlot(), true);
                if (recipe != null) {
                    return Math.max(1, getNeededInput(recipe, largerInput));
                }
                return 1;
            };
        }
    }
    if (!emptyProcesses.isEmpty()) {
        // If we have any empty slots, we need to factor them in as valid slots for items to transferred to
        addEmptySlotsAsTargets(processes, emptyProcesses);
    // Note: Any remaining empty slots are "ignored" as we don't have any
    // spare items to distribute to them
    }
    // Distribute items among the slots
    distributeItems(processes);
}
Also used : IInventorySlot(mekanism.api.inventory.IInventorySlot) HashedItem(mekanism.common.lib.inventory.HashedItem) HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) ProcessInfo(mekanism.common.base.ProcessInfo) ItemStack(net.minecraft.item.ItemStack)

Example 5 with HashedItem

use of mekanism.common.lib.inventory.HashedItem in project Mekanism by mekanism.

the class QIORecipeData method merge.

@Nullable
@Override
public QIORecipeData merge(QIORecipeData other) {
    if (itemCount <= Long.MAX_VALUE - other.itemCount) {
        // Protect against overflow
        Object2LongMap<HashedItem> fullItemMap = new Object2LongOpenHashMap<>();
        fullItemMap.putAll(itemMap);
        for (Entry<HashedItem> entry : other.itemMap.object2LongEntrySet()) {
            HashedItem type = entry.getKey();
            fullItemMap.put(type, fullItemMap.getOrDefault(type, 0L) + entry.getLongValue());
        }
        return new QIORecipeData(fullItemMap, itemCount + other.itemCount);
    }
    return null;
}
Also used : Object2LongOpenHashMap(it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap) HashedItem(mekanism.common.lib.inventory.HashedItem) Nullable(javax.annotation.Nullable)

Aggregations

HashedItem (mekanism.common.lib.inventory.HashedItem)23 ItemStack (net.minecraft.item.ItemStack)13 ArrayList (java.util.ArrayList)6 HashMap (java.util.HashMap)6 UUID (java.util.UUID)6 Map (java.util.Map)5 IInventorySlot (mekanism.api.inventory.IInventorySlot)5 Byte2ObjectArrayMap (it.unimi.dsi.fastutil.bytes.Byte2ObjectArrayMap)4 Byte2ObjectMap (it.unimi.dsi.fastutil.bytes.Byte2ObjectMap)4 Object2IntMap (it.unimi.dsi.fastutil.objects.Object2IntMap)4 Object2LongOpenHashMap (it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap)4 List (java.util.List)4 UUIDAwareHashedItem (mekanism.common.lib.inventory.HashedItem.UUIDAwareHashedItem)4 Object2IntArrayMap (it.unimi.dsi.fastutil.objects.Object2IntArrayMap)3 Object2LongMap (it.unimi.dsi.fastutil.objects.Object2LongMap)3 HashSet (java.util.HashSet)3 Nullable (javax.annotation.Nullable)3 ProcessInfo (mekanism.common.base.ProcessInfo)3 BaseSimulatedInventory (mekanism.common.content.qio.QIOCraftingTransferHelper.BaseSimulatedInventory)3 QIOFrequency (mekanism.common.content.qio.QIOFrequency)3