use of net.minecraft.inventory.CraftingInventory in project Mekanism by mekanism.
the class MekanismShapedRecipe method assemble.
@Override
public ItemStack assemble(CraftingInventory inv) {
if (getResultItem().isEmpty()) {
return ItemStack.EMPTY;
}
ItemStack toReturn = getResultItem().copy();
List<ItemStack> nbtInputs = new ArrayList<>();
for (int i = 0; i < inv.getContainerSize(); i++) {
ItemStack stack = inv.getItem(i);
if (!stack.isEmpty() && stack.hasTag()) {
nbtInputs.add(stack);
}
}
if (nbtInputs.isEmpty()) {
// If none of our items have NBT we can skip checking what data can be transferred
return toReturn;
}
Set<RecipeUpgradeType> supportedTypes = RecipeUpgradeData.getSupportedTypes(toReturn);
if (supportedTypes.isEmpty()) {
// If we have no supported types "fail" gracefully by just not transferring any data
return toReturn;
}
Map<RecipeUpgradeType, List<RecipeUpgradeData<?>>> upgradeInfo = new EnumMap<>(RecipeUpgradeType.class);
// Only bother checking input items that have NBT as ones that do not, don't have any data they may need to transfer
for (ItemStack stack : nbtInputs) {
Set<RecipeUpgradeType> stackSupportedTypes = RecipeUpgradeData.getSupportedTypes(stack);
for (RecipeUpgradeType supportedType : stackSupportedTypes) {
if (supportedTypes.contains(supportedType)) {
RecipeUpgradeData<?> data = RecipeUpgradeData.getUpgradeData(supportedType, stack);
if (data != null) {
// If something went wrong, and we didn't actually get any data don't add it
upgradeInfo.computeIfAbsent(supportedType, type -> new ArrayList<>()).add(data);
}
}
}
}
for (Entry<RecipeUpgradeType, List<RecipeUpgradeData<?>>> entry : upgradeInfo.entrySet()) {
List<RecipeUpgradeData<?>> upgradeData = entry.getValue();
if (!upgradeData.isEmpty()) {
// Skip any empty data, even though we should never have any
RecipeUpgradeData<?> data = RecipeUpgradeData.mergeUpgradeData(upgradeData);
if (data == null || !data.applyToStack(toReturn)) {
// Fail, incompatible data
return ItemStack.EMPTY;
}
}
}
return toReturn;
}
use of net.minecraft.inventory.CraftingInventory in project Mekanism by mekanism.
the class QIOCraftingTransferHandler method transferRecipe.
@Nullable
@Override
public IRecipeTransferError transferRecipe(CONTAINER container, Object rawRecipe, IRecipeLayout recipeLayout, PlayerEntity player, boolean maxTransfer, boolean doTransfer) {
if (!(rawRecipe instanceof ICraftingRecipe)) {
// a crafting recipe, and if it isn't the server won't know how to transfer it anyway
return handlerHelper.createInternalError();
}
byte selectedCraftingGrid = container.getSelectedCraftingGrid();
if (selectedCraftingGrid == -1) {
// as there are no crafting grids being shown
return handlerHelper.createInternalError();
}
ICraftingRecipe recipe = (ICraftingRecipe) rawRecipe;
QIOCraftingWindow craftingWindow = container.getCraftingWindow(selectedCraftingGrid);
// Note: This variable is only used for when doTransfer is false
byte nonEmptyCraftingSlots = 0;
if (!doTransfer) {
CraftingInventory dummy = MekanismUtils.getDummyCraftingInv();
for (int slot = 0; slot < 9; slot++) {
CraftingWindowInventorySlot inputSlot = craftingWindow.getInputSlot(slot);
if (!inputSlot.isEmpty()) {
// Copy it in case any recipe does weird things and tries to mutate the stack
dummy.setItem(slot, StackUtils.size(inputSlot.getStack(), 1));
// Count how many crafting slots are not empty
nonEmptyCraftingSlots++;
}
}
if (recipe.matches(dummy, player.level)) {
// or we may be transferring different items if different ones are shown in JEI
return null;
}
}
// TODO: It may be nice to eventually implement some sort of caching for this, it isn't drastically needed because JEI is smart
// and only calls it once per recipe to decide if it should display the button rather than say calling it every render tick in
// case something changed and the render state should be different. We probably could add some sort of listeners to
// inventory, QIO, and crafting window that if one changes it invalidates the cache of what ingredients are stored, though then
// we wouldn't be able to directly modify the map as we find inputs, and also we still would have to do a lot of this comparison
// logic, unless we can also somehow cache the recipe layout and how it interacts with the other information
int inputCount = 0;
Byte2ObjectMap<Set<HashedItem>> hashedIngredients = new Byte2ObjectArrayMap<>();
for (Map.Entry<Integer, ? extends IGuiIngredient<ItemStack>> entry : recipeLayout.getItemStacks().getGuiIngredients().entrySet()) {
IGuiIngredient<ItemStack> ingredient = entry.getValue();
if (ingredient.isInput()) {
List<ItemStack> validIngredients = ingredient.getAllIngredients();
if (!validIngredients.isEmpty()) {
// If there are valid ingredients, increment the count
inputCount++;
// and convert them to HashedItems
// Note: we use a linked hash set to preserve the order of the ingredients as done in JEI
LinkedHashSet<HashedItem> representations = new LinkedHashSet<>();
// Note: We shouldn't need to convert the item that is part of the recipe to a "reduced" stack form based
// on what the server would send, as the item should already be like that from when the server sent the
// client the recipe. If this turns out to be incorrect due to how some mod does recipes, then we may need
// to change this
ItemStack displayed = ingredient.getDisplayedIngredient();
// so we may as well remove some unneeded copies
if (displayed != null) {
// Start by adding the displayed ingredient if there is one to prioritize it
representations.add(HashedItem.raw(displayed));
}
// we will just end up merging with the displayed ingredient when we get to it as a valid ingredient
for (ItemStack validIngredient : validIngredients) {
representations.add(HashedItem.raw(validIngredient));
}
// Note: We decrement the index by one because JEI uses the first index for the output
int actualIndex = entry.getKey() - 1;
if (actualIndex > Byte.MAX_VALUE || actualIndex < Byte.MIN_VALUE) {
// Note: This should not happen, but validate it doesn't just to ensure if it does, we gracefully error
Mekanism.logger.warn("Error evaluating recipe transfer handler for recipe: {}, had unexpected index: {}", recipe.getId(), actualIndex);
return handlerHelper.createInternalError();
}
hashedIngredients.put((byte) actualIndex, representations);
}
}
}
if (inputCount > 9) {
// I don't believe this ever will happen with a normal crafting recipe but just in case it does, error
// if we have more than nine inputs, we check it as an extra validation step, but we don't hold off on
// converting input ingredients to HashedItems, until we have validated this, as there should never be
// a case where this actually happens except potentially with some really obscure modded recipe
Mekanism.logger.warn("Error evaluating recipe transfer handler for recipe: {}, had more than 9 inputs: {}", recipe.getId(), inputCount);
return handlerHelper.createInternalError();
}
// Get all our available items in the QIO frequency, we flatten the cache to stack together items that
// as far as the client is concerned are the same instead of keeping them UUID separated, and add all
// the items in the currently selected crafting window and the player's inventory to our available items
QIOCraftingTransferHelper qioTransferHelper = container.getTransferHelper(player, craftingWindow);
if (qioTransferHelper.isInvalid()) {
Mekanism.logger.warn("Error initializing QIO transfer handler for crafting window: {}", selectedCraftingGrid);
return handlerHelper.createInternalError();
}
// Note: We do this in a reversed manner (HashedItem -> slots, vs slot -> HashedItem) so that we can more easily
// calculate the split for how we handle maxTransfer by quickly being able to see how many of each type we have
Map<HashedItem, ByteList> matchedItems = new HashMap<>(inputCount);
ByteSet missingSlots = new ByteArraySet(inputCount);
for (Byte2ObjectMap.Entry<Set<HashedItem>> entry : hashedIngredients.byte2ObjectEntrySet()) {
// TODO: Eventually we probably will want to add in some handling for if an item is valid for more than one slot and one combination
// has it being valid and one combination it is not valid. For example if we have a single piece of stone and it is valid in either
// slot 1 or 2 but slot 2 only allows for stone, and slot 1 can accept granite instead and we have granite available. When coming
// up with a solution to this, we also will need to handle the slower comparison method, and make sure that if maxTransfer is true
// then we pick the one that has the most elements we can assign to all slots evenly so that we can craft as many things as possible.
// We currently don't bother with any handling related to this as JEI's own transfer handler it registers for things like the crafting
// table don't currently handle this, though it is something that would be nice to handle and is something I believe vanilla's recipe
// book transfer handler is able to do (RecipeItemHelper/ServerRecipePlayer)
boolean matchFound = false;
for (HashedItem validInput : entry.getValue()) {
HashedItemSource source = qioTransferHelper.getSource(validInput);
if (source != null && source.hasMoreRemaining()) {
// We found a match for this slot, reduce how much of the item we have as an input
source.matchFound();
// mark that we found a match
matchFound = true;
// and which HashedItem the slot's index corresponds to
matchedItems.computeIfAbsent(validInput, item -> new ByteArrayList()).add(entry.getByteKey());
// and stop checking the other possible inputs
break;
}
}
if (!matchFound) {
// If we didn't find a match for the slot, add it as a slot we may be missing
missingSlots.add(entry.getByteKey());
}
}
if (!missingSlots.isEmpty()) {
// After doing the quicker exact match lookup checks, go through any potentially missing slots
// and do the slower more "accurate" check of if the stacks match. This allows us to use JEI's
// system for letting mods declare what things match when it comes down to NBT
Map<HashedItem, String> cachedIngredientUUIDs = new HashMap<>();
for (Map.Entry<HashedItem, HashedItemSource> entry : qioTransferHelper.reverseLookup.entrySet()) {
HashedItemSource source = entry.getValue();
if (source.hasMoreRemaining()) {
// Only look at the source if we still have more items available in it
HashedItem storedHashedItem = entry.getKey();
ItemStack storedItem = storedHashedItem.getStack();
Item storedItemType = storedItem.getItem();
String storedItemUUID = null;
for (ByteIterator missingIterator = missingSlots.iterator(); missingIterator.hasNext(); ) {
byte index = missingIterator.nextByte();
for (HashedItem validIngredient : hashedIngredients.get(index)) {
// Compare the raw item types
if (storedItemType == validIngredient.getStack().getItem()) {
// If they match, compute the identifiers for both stacks as needed
if (storedItemUUID == null) {
// If we haven't retrieved a UUID for the stored stack yet because none of our previous ingredients
// matched the basic item type, retrieve it
storedItemUUID = stackHelper.getUniqueIdentifierForStack(storedItem, UidContext.Recipe);
}
// Next compute the UUID for the ingredient we are missing if we haven't already calculated it
// either in a previous iteration or for a different slot
String ingredientUUID = cachedIngredientUUIDs.computeIfAbsent(validIngredient, ingredient -> stackHelper.getUniqueIdentifierForStack(ingredient.getStack(), UidContext.Recipe));
if (storedItemUUID.equals(ingredientUUID)) {
// If the items are equivalent, reduce how much of the item we have as an input
source.matchFound();
// unmark that the slot is missing a match
missingIterator.remove();
// and mark which HashedItem the slot's index corresponds to
matchedItems.computeIfAbsent(storedHashedItem, item -> new ByteArrayList()).add(index);
// and stop checking the other possible inputs
break;
}
}
}
if (!source.hasMoreRemaining()) {
// If we have "used up" all the input we have available then continue onto the next stored stack
break;
}
}
if (missingSlots.isEmpty()) {
// If we have accounted for all the slots, stop checking for matches
break;
}
}
}
if (!missingSlots.isEmpty()) {
// Note: We have to shift this back up by one as we shifted the indices earlier to make them easier to work with
return handlerHelper.createUserErrorForSlots(MekanismLang.JEI_MISSING_ITEMS.translate(), missingSlots.stream().map(i -> i + 1).collect(Collectors.toList()));
}
}
if (doTransfer || (nonEmptyCraftingSlots > 0 && nonEmptyCraftingSlots >= qioTransferHelper.getEmptyInventorySlots())) {
// Note: If all our crafting inventory slots are not empty, and we don't "obviously" have enough room due to empty slots,
// then we need to calculate how much we can actually transfer and where it is coming from so that we are able to calculate
// if we actually have enough room to shuffle the items around, even though otherwise we would only need to do these
// calculations for when we are transferring items
int toTransfer;
if (maxTransfer) {
// Calculate how much we can actually transfer if we want to transfer as many full sets as possible
long maxToTransfer = Long.MAX_VALUE;
for (Map.Entry<HashedItem, ByteList> entry : matchedItems.entrySet()) {
HashedItem hashedItem = entry.getKey();
HashedItemSource source = qioTransferHelper.getSource(hashedItem);
if (source == null) {
// If something went wrong, and we don't actually have the item we think we do, error
return invalidSource(hashedItem);
}
int maxStack = hashedItem.getStack().getMaxStackSize();
// If we have something that only stacks to one, such as a bucket. Don't limit the max stack size
// of other items to one
long max = maxStack == 1 ? maxToTransfer : Math.min(maxToTransfer, maxStack);
// Note: This will always be at least one as the int list should not be able to become
// larger than the number of items we have available
maxToTransfer = Math.min(max, source.getAvailable() / entry.getValue().size());
}
toTransfer = MathUtils.clampToInt(maxToTransfer);
} else {
toTransfer = 1;
}
QIOFrequency frequency = container.getFrequency();
Byte2ObjectMap<List<SingularHashedItemSource>> sources = new Byte2ObjectArrayMap<>(inputCount);
Map<HashedItemSource, List<List<SingularHashedItemSource>>> shuffleLookup = frequency == null ? Collections.emptyMap() : new HashMap<>(inputCount);
for (Map.Entry<HashedItem, ByteList> entry : matchedItems.entrySet()) {
HashedItem hashedItem = entry.getKey();
HashedItemSource source = qioTransferHelper.getSource(hashedItem);
if (source == null) {
// If something went wrong, and we don't actually have the item we think we do, error
return invalidSource(hashedItem);
}
// Cap the amount to transfer at the max tack size. This way we allow for transferring buckets
// and other stuff with it. This only actually matters if the max stack size is one, due to
// the logic done above when calculating how much to transfer, but we do this regardless here
// as there is no reason not to and then if we decide to widen it up we only have to change one spot
int transferAmount = Math.min(toTransfer, hashedItem.getStack().getMaxStackSize());
for (byte slot : entry.getValue()) {
// Try to use the item and figure out where it is coming from
List<SingularHashedItemSource> actualSources = source.use(transferAmount);
if (actualSources.isEmpty()) {
// If something went wrong, and we don't actually have enough of the item for some reason, error
return invalidSource(hashedItem);
}
sources.put(slot, actualSources);
if (frequency != null) {
// The shuffle lookup only comes into play if we have a frequency so might end up having to check if there is room in it
int elements = entry.getValue().size();
if (elements == 1) {
shuffleLookup.put(source, Collections.singletonList(actualSources));
} else {
shuffleLookup.computeIfAbsent(source, s -> new ArrayList<>(elements)).add(actualSources);
}
}
}
}
if (!hasRoomToShuffle(qioTransferHelper, frequency, craftingWindow, container.getHotBarSlots(), container.getMainInventorySlots(), shuffleLookup)) {
return handlerHelper.createUserErrorWithTooltip(MekanismLang.JEI_INVENTORY_FULL.translate());
}
if (doTransfer) {
// Note: We skip doing a validation check on if the recipe matches or not, as there is a chance that for some recipes
// things may not fully be accurate on the client side with the stacks that JEI lets us know match the recipe, as
// they may require extra NBT that is server side only.
// TODO: If the sources are all from the crafting window and are already in the correct spots, there is no need to send this packet
Mekanism.packetHandler.sendToServer(new PacketQIOFillCraftingWindow(recipe.getId(), maxTransfer, sources));
}
}
return null;
}
use of net.minecraft.inventory.CraftingInventory in project SophisticatedBackpacks by P3pp3rF1y.
the class BackpackUpgradeRecipe method assemble.
@Override
public ItemStack assemble(CraftingInventory inv) {
ItemStack upgradedBackpack = super.assemble(inv);
getBackpack(inv).flatMap(backpack -> Optional.ofNullable(backpack.getTag())).ifPresent(tag -> upgradedBackpack.setTag(tag.copy()));
upgradedBackpack.getCapability(CapabilityBackpackWrapper.getCapabilityInstance()).ifPresent(wrapper -> {
BackpackItem backpackItem = ((BackpackItem) upgradedBackpack.getItem());
wrapper.setSlotNumbers(backpackItem.getNumberOfSlots(), backpackItem.getNumberOfUpgradeSlots());
});
return upgradedBackpack;
}
use of net.minecraft.inventory.CraftingInventory in project minecolonies by ldtteam.
the class GenericRecipe method calculateSecondaryOutputs.
@NotNull
private static List<ItemStack> calculateSecondaryOutputs(@NotNull final IRecipe<?> recipe) {
if (recipe instanceof ICraftingRecipe) {
final List<Ingredient> inputs = recipe.getIngredients();
final CraftingInventory inv = new CraftingInventory(new Container(ContainerType.CRAFTING, 0) {
@Override
public boolean stillValid(@NotNull final PlayerEntity playerIn) {
return false;
}
}, 3, 3);
for (int slot = 0; slot < inputs.size(); ++slot) {
final ItemStack[] stacks = inputs.get(slot).getItems();
if (stacks.length > 0) {
inv.setItem(slot, stacks[0]);
}
}
if (((ICraftingRecipe) recipe).matches(inv, null)) {
return ((ICraftingRecipe) recipe).getRemainingItems(inv).stream().filter(ItemStackUtils::isNotEmpty).filter(// this is filtered out of the inputs too
stack -> stack.getItem() != buildTool.get()).collect(Collectors.toList());
}
}
return Collections.emptyList();
}
use of net.minecraft.inventory.CraftingInventory in project minecolonies by ldtteam.
the class CraftingGuiHandler method updateServer.
@Override
protected void updateServer(@NotNull final WindowCrafting gui) {
final Map<Integer, ItemStack> matrix = new HashMap<>();
final CraftingInventory inventory = gui.getMenu().getInv();
if (gui.isCompleteCrafting()) {
for (int i = 0; i < 9; ++i) {
matrix.put(i, inventory.getItem(i));
}
} else {
matrix.put(0, inventory.getItem(0));
matrix.put(1, inventory.getItem(1));
matrix.put(3, inventory.getItem(2));
matrix.put(4, inventory.getItem(3));
}
final TransferRecipeCraftingTeachingMessage message = new TransferRecipeCraftingTeachingMessage(matrix, gui.isCompleteCrafting());
Network.getNetwork().sendToServer(message);
}
Aggregations