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;
}
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));
}
}
}
}
}
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;
}
}
}
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);
}
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;
}
Aggregations