use of mekanism.common.inventory.slot.CraftingWindowInventorySlot 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.inventory.slot.CraftingWindowInventorySlot in project Mekanism by mekanism.
the class QIOCraftingWindow method performCraft.
/**
* @apiNote For use with shift clicking
*/
public void performCraft(@Nonnull PlayerEntity player, List<HotBarSlot> hotBarSlots, List<MainInventorySlot> mainInventorySlots) {
if (lastRecipe == null || outputSlot.isEmpty()) {
// Note: lastRecipe will always null on the client, so we can assume we are server side below
return;
}
World world = holder.getHolderWorld();
if (!validateAndUnlockRecipe(world, player)) {
// If the recipe isn't valid, fail
return;
}
QIOFrequency frequency = holder.getFrequency();
// Mark that we are crafting so changes to the slots below don't force a bunch of recalculations to take place
craftingStarted(player);
// Figure out the base of the result stack after crafting (onCreated can adjust it slightly)
ItemStack result = outputSlot.getStack().copy();
Item resultItem = result.getItem();
resultItem.onCraftedBy(result, world, player);
Stat<Item> itemCraftedStat = Stats.ITEM_CRAFTED.get(resultItem);
int maxToCraft = calculateMaxCraftAmount(result, frequency);
int amountPerCraft = result.getCount();
// Note: We initialized crafted here instead of in the for loop so that we can query how much was actually crafted
int crafted = 0;
remainderHelper.reset();
replacementHelper.reset();
boolean recheckOutput = false;
LastInsertTarget lastInsertTarget = new LastInsertTarget();
NonNullList<ItemStack> remaining = lastRecipe.getRemainingItems(craftingInventory);
for (; crafted < maxToCraft; crafted += amountPerCraft) {
if (recheckOutput && changedWhileCrafting) {
// If our inputs changed while crafting, and we are supposed to recheck the output,
// update the contents of the output slot and the recipe that we are performing as
// if there is an NBT sensitive recipe, the output may have changed
recheckOutput = false;
changedWhileCrafting = false;
ICraftingRecipe oldRecipe = lastRecipe;
updateOutputSlot(world);
if (oldRecipe != lastRecipe) {
// is enabled, and they only have access to one of the crafting recipes
break;
}
ItemStack updatedOutput = outputSlot.getStack();
if (updatedOutput.isEmpty() || updatedOutput.getItem() != resultItem) {
// If we can't craft anymore or the resulting item changed entirely, stop crafting
break;
}
// If they may still be compatible, copy the stack, and apply the onCreated to it so that
// we can adjust the NBT if it needs adjusting
ItemStack potentialUpdatedOutput = updatedOutput.copy();
resultItem.onCraftedBy(potentialUpdatedOutput, world, player);
if (!ItemStack.matches(result, potentialUpdatedOutput)) {
// This also has a side effect of not requiring us to then recalculate the value of maxToCraft
break;
}
// We also need to make sure to update the remaining items as even though the recipe still outputs the same result
// the remaining items may have changed such as durability of a container item, and we want to make sure to use
// the proper remaining stacks
remaining = lastRecipe.getRemainingItems(craftingInventory);
}
// Simulate insertion into hotbar and then main inventory, allowing for inserting into empty slots,
// as we just want to do a quick general check to see if there is room for the result, we will do
// the secondary checks afterwards while working on actually inserting into it
// The reason this is needed is that if we only have space for two more items, but our crafting recipe will
// produce three more, then we won't have room for that singular extra item and need to exit
ItemStack simulatedRemainder = MekanismContainer.insertItemCheckAll(hotBarSlots, result, windowData, Action.SIMULATE);
simulatedRemainder = MekanismContainer.insertItemCheckAll(mainInventorySlots, simulatedRemainder, windowData, Action.SIMULATE);
if (!simulatedRemainder.isEmpty()) {
// some contents ended up in their storage system instead
break;
}
// Actually transfer the output to the player's inventory now that we know it will fit
ItemStack toInsert = lastInsertTarget.tryInserting(hotBarSlots, mainInventorySlots, windowData, result);
if (!toInsert.isEmpty()) {
// If something went horribly wrong adding it to the player's inventory given we calculated there was room
// and suddenly a few lines down there is no longer room, then just drop the items as the player
player.drop(toInsert, false);
}
boolean stopCrafting = false;
// Update slots with remaining contents
for (int index = 0; index < remaining.size(); index++) {
ItemStack remainder = remaining.get(index);
CraftingWindowInventorySlot inputSlot = inputSlots[index];
if (inputSlot.getCount() > 1) {
// If the input slot contains an item that is stacked, reduce the size of it by one
// Note: We "ignore" the fact that the container item may still be valid for the recipe, if the input is stacked
useInput(inputSlot);
} else if (inputSlot.getCount() == 1) {
// Else if the input slot only has a single item in it, try removing from the frequency
if (frequency == null || remainderHelper.isStackStillValid(world, remainder, index)) {
// If the remaining item is still valid for the recipe in that slot, or we don't have a frequency, and it is the
// last stack in the slot, remove the stack from the slot
useInput(inputSlot);
// and mark that we should recheck our output as the recipe output may have changed, or we may
// no longer have enough inputs to craft an output
recheckOutput = true;
} else {
// Otherwise, try and remove the stack from the QIO frequency
ItemStack current = inputSlot.getStack();
ItemStack removed = frequency.removeItem(current, 1);
if (removed.isEmpty()) {
// If we were not able to remove any from the frequency, remove it from the crafting grid
useInput(inputSlot);
// see if we have another valid input stored in the frequency and replace it with it if we do
replacementHelper.findEquivalentItem(world, frequency, inputSlot, index, current);
// and stop crafting even if we have another valid item for that spot, as we want to give the player a chance
// to notice the item it will be using changed in case it got replaced with some very expensive alternative
stopCrafting = true;
}
}
} else if (!remainder.isEmpty()) {
// Otherwise, if the slot is empty, but we don't have an empty remaining stack because of a mod doing odd things
// or having some edge case behavior that creates items in a slot, mark that we need to recheck our output.
// Technically we maybe would fail to add the item to the slot, but given that is highly unlikely we just
// recheck anyway
recheckOutput = true;
}
addRemainingItem(player, frequency, inputSlot, remainder, true);
}
if (stopCrafting) {
// Note: We need to increment the amount crafted here, as breaking will skip the increment
// that happens at the end of the loop
crafted += amountPerCraft;
break;
}
}
if (crafted > 0) {
// Add to the stat how much of the item the player crafted that the player crafted the item
player.awardStat(itemCraftedStat, crafted);
// Note: We don't fire a crafting event as we don't want to allow for people to modify the output
// stack or more importantly the input inventory during crafting
// TODO: If this ends up causing major issues with some weird way another mod ends up doing crafting
// we can evaluate how we want to handle it then/try to integrate support for firing it.
// BasicEventHooks.firePlayerCraftingEvent(player, result, craftingInventory);
}
// Mark that we are done crafting
craftingFinished(world);
}
use of mekanism.common.inventory.slot.CraftingWindowInventorySlot in project Mekanism by mekanism.
the class QIOServerCraftingTransferHandler method bail.
/**
* Bails out if something went horribly wrong and didn't get caught by simulations, and send the various items back to the inventory.
*/
private void bail(Byte2ObjectMap<ItemStack> targetContents, Byte2ObjectMap<ItemStack> remainingCraftingGridContents, String format, Object... args) {
Mekanism.logger.warn(format, args);
SelectedWindowData windowData = craftingWindow.getWindowData();
for (ItemStack stack : targetContents.values()) {
// We don't attempt to try and return the contents being moved to the crafting inventory to their original slots
// as we don't keep track of that data and in theory unless something goes majorly wrong we should never end
// up bailing anyways
// TODO: Eventually we may want to try and make it first try to return to the same slots it came from but it doesn't matter that much
returnItem(stack, windowData);
}
// Put the items that were in the crafting window in the player's inventory
for (Byte2ObjectMap.Entry<ItemStack> entry : remainingCraftingGridContents.byte2ObjectEntrySet()) {
ItemStack stack = entry.getValue();
CraftingWindowInventorySlot inputSlot = craftingWindow.getInputSlot(entry.getByteKey());
if (ItemHandlerHelper.canItemStacksStack(inputSlot.getStack(), stack)) {
stack = inputSlot.insertItem(stack, Action.EXECUTE, AutomationType.MANUAL);
if (stack.isEmpty()) {
continue;
}
}
returnItem(stack, windowData);
}
}
use of mekanism.common.inventory.slot.CraftingWindowInventorySlot 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.inventory.slot.CraftingWindowInventorySlot in project Mekanism by mekanism.
the class QIOCraftingWindow method performCraft.
@Nonnull
public ItemStack performCraft(@Nonnull PlayerEntity player, @Nonnull ItemStack result, int amountCrafted) {
// Maybe for now an IQIOIntegratedCraftingRecipe or something like that that we can call?
if (amountCrafted == 0 || lastRecipe == null || result.isEmpty()) {
// Note: lastRecipe will always null on the client, so we can assume we are server side below
return ItemStack.EMPTY;
}
World world = holder.getHolderWorld();
if (!validateAndUnlockRecipe(world, player)) {
// If the recipe isn't valid, fail
return ItemStack.EMPTY;
}
QIOFrequency frequency = holder.getFrequency();
// Mark that we are crafting so changes to the slots below don't force a bunch of recalculations to take place
craftingStarted(player);
// Craft the result
result.onCraftedBy(world, player, amountCrafted);
// Note: We don't fire a crafting event as we don't want to allow for people to modify the output
// stack or more importantly the input inventory during crafting
// TODO: If this ends up causing major issues with some weird way another mod ends up doing crafting
// we can evaluate how we want to handle it then/try to integrate support for firing it.
// BasicEventHooks.firePlayerCraftingEvent(player, result, craftingInventory);
NonNullList<ItemStack> remaining = lastRecipe.getRemainingItems(craftingInventory);
remainderHelper.reset();
replacementHelper.reset();
// Update slots with remaining contents
for (int index = 0; index < remaining.size(); index++) {
ItemStack remainder = remaining.get(index);
CraftingWindowInventorySlot inputSlot = inputSlots[index];
if (inputSlot.getCount() > 1) {
// If the input slot contains an item that is stacked, reduce the size of it by one
// Note: We "ignore" the fact that the container item may still be valid for the recipe, if the input is stacked
useInput(inputSlot);
} else if (inputSlot.getCount() == 1) {
// Else if the input slot only has a single item in it, try removing from the frequency
if (frequency == null || remainderHelper.isStackStillValid(world, remainder, index)) {
// If we have no frequency or the remaining item is still valid for the recipe in that slot,
// remove from the crafting window
useInput(inputSlot);
} else {
// Otherwise, try and remove the stack from the QIO frequency
ItemStack current = inputSlot.getStack();
ItemStack removed = frequency.removeItem(current, 1);
if (removed.isEmpty()) {
// If we were not able to remove any from the frequency, remove it from the crafting grid
useInput(inputSlot);
// see if we have another valid input stored in the frequency and replace it with it if we do
replacementHelper.findEquivalentItem(world, frequency, inputSlot, index, current);
}
}
}
// Note: No special handling needed here for if the remainder is empty
addRemainingItem(player, frequency, inputSlot, remainder, false);
}
// Mark that we are done crafting
craftingFinished(world);
return result;
}
Aggregations