use of mekanism.common.content.qio.QIOCraftingTransferHelper.SingularHashedItemSource 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.content.qio.QIOCraftingTransferHelper.SingularHashedItemSource in project Mekanism by mekanism.
the class PacketQIOFillCraftingWindow method decode.
public static PacketQIOFillCraftingWindow decode(PacketBuffer buffer) {
ResourceLocation recipeID = buffer.readResourceLocation();
boolean maxTransfer = buffer.readBoolean();
byte slotCount = buffer.readByte();
Byte2ObjectMap<List<SingularHashedItemSource>> sources = new Byte2ObjectArrayMap<>(slotCount);
for (byte slot = 0; slot < slotCount; slot++) {
byte targetSlot = buffer.readByte();
int subSourceCount = maxTransfer ? buffer.readVarInt() : 1;
List<SingularHashedItemSource> slotSources = new ArrayList<>(subSourceCount);
sources.put(targetSlot, slotSources);
for (int i = 0; i < subSourceCount; i++) {
byte sourceSlot = buffer.readByte();
int count = maxTransfer ? buffer.readVarInt() : 1;
if (sourceSlot == -1) {
slotSources.add(new SingularHashedItemSource(buffer.readUUID(), count));
} else {
slotSources.add(new SingularHashedItemSource(sourceSlot, count));
}
}
}
return new PacketQIOFillCraftingWindow(recipeID, maxTransfer, sources);
}
use of mekanism.common.content.qio.QIOCraftingTransferHelper.SingularHashedItemSource in project Mekanism by mekanism.
the class QIOServerCraftingTransferHandler method transferItems.
private void transferItems(Byte2ObjectMap<List<SingularHashedItemSource>> sources) {
SelectedWindowData windowData = craftingWindow.getWindowData();
// Extract items that will be put into the crafting window
Byte2ObjectMap<ItemStack> targetContents = new Byte2ObjectArrayMap<>(sources.size());
for (Byte2ObjectMap.Entry<List<SingularHashedItemSource>> entry : sources.byte2ObjectEntrySet()) {
for (SingularHashedItemSource source : entry.getValue()) {
byte slot = source.getSlot();
ItemStack stack;
if (slot == -1) {
UUID qioSource = source.getQioSource();
// Neither the source nor the frequency can be null here as we validated that during simulation
HashedItem storedItem = frequency.getTypeByUUID(qioSource);
if (storedItem == null) {
bail(targetContents, "Received transfer request from: {}, for: {}, could not find stored item with UUID: {}. " + "This likely means that more of it was requested than is stored.", player, recipeID, qioSource);
return;
}
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());
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());
}
} else {
int actualSlot;
String slotType;
if (slot < 9) {
// Crafting Window
actualSlot = slot;
slotType = "crafting window";
stack = craftingWindow.getInputSlot(slot).extractItem(source.getUsed(), Action.EXECUTE, AutomationType.MANUAL);
} else if (slot < 9 + PlayerInventory.getSelectionSize()) {
// Hotbar
actualSlot = slot - 9;
slotType = "hotbar";
stack = hotBarSlots.get(actualSlot).remove(source.getUsed());
} else {
// Main inventory
actualSlot = slot - 9 - PlayerInventory.getSelectionSize();
slotType = "main inventory";
stack = mainInventorySlots.get(actualSlot).remove(source.getUsed());
}
if (stack.isEmpty()) {
bail(targetContents, "Received transfer request from: {}, for: {}, could not extract item from {} slot: {}. " + "This likely means that more of it was requested than is stored.", player, recipeID, slotType, actualSlot);
return;
} else if (stack.getCount() < source.getUsed()) {
Mekanism.logger.warn("Received transfer request from: {}, for: {}, but was unable to extract the expected amount: {} from {} slot: {}. " + "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(), slotType, actualSlot, stack.getCount());
}
}
byte targetSlot = entry.getByteKey();
if (targetContents.containsKey(targetSlot)) {
ItemStack existing = targetContents.get(targetSlot);
if (ItemHandlerHelper.canItemStacksStack(existing, stack)) {
int needed = existing.getMaxStackSize() - existing.getCount();
if (stack.getCount() <= needed) {
existing.grow(stack.getCount());
} else {
existing.grow(needed);
// Note: We can safely modify the stack as all our ways of extracting return a new stack
stack.shrink(needed);
Mekanism.logger.warn("Received transfer request from: {}, for: {}, but contents could not fully fit into target slot: {}. " + "This should not be able to happen, returning excess stack, and attempting to continue.", player, recipeID, targetSlot);
returnItem(stack, windowData);
}
} else {
Mekanism.logger.warn("Received transfer request from: {}, for: {}, but contents could not stack into target slot: {}. " + "This should not be able to happen, returning extra stack, and attempting to continue.", player, recipeID, targetSlot);
returnItem(stack, windowData);
}
} else {
// Note: We can safely modify the stack as all our ways of extracting return a new stack
targetContents.put(targetSlot, stack);
}
}
}
// Extract what items are still in the window
Byte2ObjectMap<ItemStack> remainingCraftingGridContents = new Byte2ObjectArrayMap<>(9);
for (byte slot = 0; slot < 9; slot++) {
CraftingWindowInventorySlot inputSlot = craftingWindow.getInputSlot(slot);
if (!inputSlot.isEmpty()) {
ItemStack stack = inputSlot.extractItem(inputSlot.getCount(), Action.EXECUTE, AutomationType.MANUAL);
if (!stack.isEmpty()) {
remainingCraftingGridContents.put(slot, stack);
} else {
bail(targetContents, remainingCraftingGridContents, "Received transfer request from: {}, for: {}, but failed to remove items from crafting " + "input slot: {}. This should not be possible as it should have been caught by an earlier check.", player, recipeID, slot);
return;
}
}
}
// Insert items for the crafting window into it
for (ObjectIterator<Byte2ObjectMap.Entry<ItemStack>> iter = targetContents.byte2ObjectEntrySet().iterator(); iter.hasNext(); ) {
Byte2ObjectMap.Entry<ItemStack> entry = iter.next();
byte targetSlot = entry.getByteKey();
CraftingWindowInventorySlot inputSlot = craftingWindow.getInputSlot(targetSlot);
ItemStack remainder = inputSlot.insertItem(entry.getValue(), Action.EXECUTE, AutomationType.MANUAL);
if (remainder.isEmpty()) {
// If it was fully inserted, remove the entry from what we have left to deal with
iter.remove();
} else {
// otherwise, update the stack for what is remaining and also print a warning as this should have been caught earlier,
// as we then will handle any remaining contents at the end (though we shouldn't have any)
// Note: We need to use put, as entry#setValue is not supported in fastutil maps
targetContents.put(targetSlot, remainder);
Mekanism.logger.warn("Received transfer request from: {}, for: {}, but was unable to fully insert it into the {} crafting input slot. " + "This should not be possible as it should have been caught during simulation. Attempting to continue anyways.", player, recipeID, targetSlot);
}
}
// Put the items that were in the crafting window in the player's inventory
for (Byte2ObjectMap.Entry<ItemStack> entry : remainingCraftingGridContents.byte2ObjectEntrySet()) {
// Insert into player's inventory
ItemStack stack = returnItemToInventory(entry.getValue(), windowData);
if (!stack.isEmpty()) {
// If we couldn't insert it all, try recombining with the slots they were in the crafting window
// (only if the type matches though)
CraftingWindowInventorySlot inputSlot = craftingWindow.getInputSlot(entry.getByteKey());
if (ItemHandlerHelper.canItemStacksStack(inputSlot.getStack(), stack)) {
stack = inputSlot.insertItem(stack, Action.EXECUTE, AutomationType.MANUAL);
}
if (!stack.isEmpty()) {
// If we couldn't insert it, then try to put the remaining items in the frequency
if (frequency != null) {
stack = frequency.addItem(stack);
}
if (!stack.isEmpty()) {
// If we couldn't insert it all, either because there was no frequency or it didn't have room for it all
// drop it as the player, and print a warning as ideally we should never have been able to get to this
// point as our simulation should have marked it as invalid
// Note: In theory we should never get to this point due to having accurate simulations ahead of time
player.drop(stack, false);
Mekanism.logger.warn("Received transfer request from: {}, for: {}, and was unable to fit all contents that were in the crafting window " + "into the player's inventory/QIO system; dropping items by player.", player, recipeID);
}
}
}
}
if (!targetContents.isEmpty()) {
// If we have any contents we wanted to move remaining try to return them, in theory
// this should never happen but in case it does make sure we don't void any items
bail(targetContents, "Received transfer request from: {}, for: {}, but ended up with {} items that could not be transferred into " + "the proper crafting grid slot. This should not be possible as it should have been caught during simulation.", player, recipeID, targetContents.size());
}
}
use of mekanism.common.content.qio.QIOCraftingTransferHelper.SingularHashedItemSource in project Mekanism by mekanism.
the class QIOServerCraftingTransferHandler method tryTransfer.
private void tryTransfer(ICraftingRecipe recipe, Byte2ObjectMap<List<SingularHashedItemSource>> sources) {
// need to be able to extract the contents afterwards anyway
for (byte slot = 0; slot < 9; slot++) {
CraftingWindowInventorySlot inputSlot = craftingWindow.getInputSlot(slot);
if (!inputSlot.isEmpty()) {
ItemStack available = inputSlot.extractItem(inputSlot.getCount(), Action.SIMULATE, AutomationType.INTERNAL);
if (available.getCount() < inputSlot.getCount()) {
// TODO: Eventually it would be nice if we added in some support so that if an item is staying put in its crafting slot
// we don't actually need to do any validation of if it can be extracted from when it will just end up in the same spot anyways
// but for now this isn't that major of a concern as our slots don't actually have any restrictions on them in regards to extracting
Mekanism.logger.warn("Received transfer request from: {}, for: {}, and was unable to extract all items from crafting input slot: {}.", player, recipeID, slot);
return;
}
availableItems.put(slot, new SlotData(available));
}
}
for (Byte2ObjectMap.Entry<List<SingularHashedItemSource>> entry : sources.byte2ObjectEntrySet()) {
byte targetSlot = entry.getByteKey();
if (targetSlot < 0 || targetSlot >= 9) {
Mekanism.logger.warn("Received transfer request from: {}, for: {}, with an invalid target slot id: {}.", player, recipeID, targetSlot);
return;
}
int stackSize = 0;
List<SingularHashedItemSource> singleSources = entry.getValue();
for (Iterator<SingularHashedItemSource> iter = singleSources.iterator(); iter.hasNext(); ) {
SingularHashedItemSource source = iter.next();
byte slot = source.getSlot();
int used;
if (slot == -1) {
used = simulateQIOSource(targetSlot, source.getQioSource(), source.getUsed(), stackSize);
} else {
used = simulateSlotSource(targetSlot, slot, source.getUsed(), stackSize);
}
if (used == -1) {
// Error occurred and was logged, exit
return;
} else if (used == 0) {
// Unable to use any of this source due to it not stacking with an earlier one for example
// remove this source
iter.remove();
} else {
if (used < source.getUsed()) {
// If we used less than we were expected to (most likely due to stack sizes) then we need
// to decrease the amount of the source being used
source.setUsed(used);
}
stackSize += used;
}
}
if (singleSources.isEmpty()) {
// There should always be at least one (the first source) that didn't get removed, but in case something went wrong,
// and it got removed anyway, then we catch it here and fail
Mekanism.logger.warn("Received transfer request from: {}, for: {}, that had no valid sources, this should not be possible.", player, recipeID);
return;
}
ItemStack resultItem = recipeToTest.get(targetSlot);
if (!resultItem.isEmpty() && resultItem.getMaxStackSize() < stackSize) {
// Note: This should never happen as if it would happen it should be caught in the above simulation and have the amount used reduced to not happen
Mekanism.logger.warn("Received transfer request from: {}, for: {}, that tried to transfer more items into: {} than can stack ({}) in one slot.", player, recipeID, targetSlot, resultItem.getMaxStackSize());
return;
}
}
CraftingInventory dummy = MekanismUtils.getDummyCraftingInv();
for (int slot = 0; slot < 9; slot++) {
dummy.setItem(slot, StackUtils.size(recipeToTest.get(slot), 1));
}
if (!recipe.matches(dummy, player.level)) {
Mekanism.logger.warn("Received transfer request from: {}, but source items aren't valid for the requested recipe: {}.", player, recipeID);
} else if (!hasRoomToShuffle()) {
// Note: Uses debug logging level as there are a couple cases this might not be 100% accurate on the client side
Mekanism.logger.debug("Received transfer request from: {}, but there is not enough room to shuffle items around for the requested recipe: {}.", player, recipeID);
} else {
transferItems(sources);
}
}
use of mekanism.common.content.qio.QIOCraftingTransferHelper.SingularHashedItemSource in project Mekanism by mekanism.
the class PacketQIOFillCraftingWindow method encode.
@Override
public void encode(PacketBuffer buffer) {
buffer.writeResourceLocation(recipeID);
buffer.writeBoolean(maxTransfer);
// Cast to byte as this should always be at most 9
buffer.writeByte((byte) sources.size());
for (Byte2ObjectMap.Entry<List<SingularHashedItemSource>> entry : sources.byte2ObjectEntrySet()) {
// Target Slot
buffer.writeByte(entry.getByteKey());
// Source slot
List<SingularHashedItemSource> slotSources = entry.getValue();
if (maxTransfer) {
// We "cheat" by only writing the list size if we are transferring as many items as possible as
// the list will always be of size one
buffer.writeVarInt(slotSources.size());
}
for (SingularHashedItemSource source : slotSources) {
byte sourceSlot = source.getSlot();
// We "cheat" here by just writing the source slot regardless of if we are in the crafting window, main inventory, or QIO
// as then we can use the not a valid value as indication that we have a UUID following for QIO source, and otherwise we
// get away with not having to write some sort of identifier for which type of data we are transferring
buffer.writeByte(sourceSlot);
if (maxTransfer) {
// We "cheat" by only writing the amount used if we are transferring as many items as possible as
// this will always just be one
buffer.writeVarInt(source.getUsed());
}
if (sourceSlot == -1) {
// If we don't actually have a source slot, that means we need to write the UUID
// as it is being transferred out of the QIO
UUID qioSource = source.getQioSource();
if (qioSource == null) {
throw new IllegalStateException("Invalid QIO crafting window transfer source.");
}
buffer.writeUUID(qioSource);
}
}
}
}
Aggregations