Skip to content

Commit

Permalink
Fixes #4253: Just transfer the recipe id instead of every ingredient.
Browse files Browse the repository at this point in the history
  • Loading branch information
yueh committed Sep 7, 2020
1 parent 12ca8d1 commit dfc4035
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 170 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ forge_version=33.0.22
# Provided APIs #
#########################################################
jei_minecraft_version=1.16.2
jei_version=7.1.1.15
jei_version=7.3.2.25
top_version=3.0.3-beta-6
hwyla_version=1.10.8-B72_1.15.2
ctm_version=MC1.15.2-1.1.0.9
Expand Down
287 changes: 184 additions & 103 deletions src/main/java/appeng/core/sync/packets/JEIRecipePacket.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,23 @@

package appeng.core.sync.packets;

import java.util.Arrays;
import java.util.Objects;

import com.google.common.base.Preconditions;

import io.netty.buffer.Unpooled;

import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.inventory.container.Container;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.ListNBT;
import net.minecraft.item.crafting.IRecipe;
import net.minecraft.item.crafting.Ingredient;
import net.minecraft.network.PacketBuffer;
import net.minecraft.util.NonNullList;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.common.crafting.IShapedRecipe;
import net.minecraftforge.items.IItemHandler;

import appeng.api.config.Actionable;
Expand All @@ -42,6 +50,7 @@
import appeng.api.storage.data.IAEItemStack;
import appeng.core.Api;
import appeng.core.sync.BasePacket;
import appeng.core.sync.BasePacketHandler;
import appeng.core.sync.network.INetworkInfo;
import appeng.helpers.IContainerCraftingPacket;
import appeng.items.storage.ViewCellItem;
Expand All @@ -54,152 +63,224 @@

public class JEIRecipePacket extends BasePacket {

private ItemStack[][] recipe;
private ResourceLocation recipeId;

public JEIRecipePacket(final PacketBuffer stream) {
final CompoundNBT comp = stream.readCompoundTag();
if (comp != null) {
this.recipe = new ItemStack[9][];
for (int x = 0; x < this.recipe.length; x++) {
final ListNBT list = comp.getList("#" + x, 10);
if (list.size() > 0) {
this.recipe[x] = new ItemStack[list.size()];
for (int y = 0; y < list.size(); y++) {
this.recipe[x][y] = ItemStack.read(list.getCompound(y));
}
}
}
}
final String id = stream.readString(Short.MAX_VALUE);
this.recipeId = new ResourceLocation(id);
}

// api
public JEIRecipePacket(final CompoundNBT recipe) {
public JEIRecipePacket(final String recipeId) {
final PacketBuffer data = new PacketBuffer(Unpooled.buffer());

data.writeInt(this.getPacketID());

data.writeCompoundTag(recipe);
data.writeString(recipeId);

this.configureWrite(data);
}

/**
* Servside handler for this packet.
*
* Makes use of {@link Preconditions#checkArgument(boolean)} as the
* {@link BasePacketHandler} is catching them and in general these cases should
* never happen except in an error case and should be logged then.
*/
@Override
public void serverPacketData(final INetworkInfo manager, final PlayerEntity player) {
// Setup and verification
final ServerPlayerEntity pmp = (ServerPlayerEntity) player;
final Container con = pmp.openContainer;
Preconditions.checkArgument(con instanceof IContainerCraftingPacket);

if (!(con instanceof IContainerCraftingPacket)) {
return;
}
final IRecipe<?> recipe = player.getEntityWorld().getRecipeManager().getRecipe(this.recipeId).orElse(null);
Preconditions.checkArgument(recipe != null);

final IContainerCraftingPacket cct = (IContainerCraftingPacket) con;
final IGridNode node = cct.getNetworkNode();

if (node == null) {
return;
}
Preconditions.checkArgument(node != null);

final IGrid grid = node.getGrid();
if (grid == null) {
return;
}
Preconditions.checkArgument(grid != null);

final IStorageGrid inv = grid.getCache(IStorageGrid.class);
final IEnergyGrid energy = grid.getCache(IEnergyGrid.class);
Preconditions.checkArgument(inv != null);

final ISecurityGrid security = grid.getCache(ISecurityGrid.class);
Preconditions.checkArgument(security != null);

final IEnergyGrid energy = grid.getCache(IEnergyGrid.class);
final ICraftingGrid crafting = grid.getCache(ICraftingGrid.class);
final IItemHandler craftMatrix = cct.getInventoryByName("crafting");
final IItemHandler playerInventory = cct.getInventoryByName("player");

if (inv != null && this.recipe != null && security != null) {
final IMEMonitor<IAEItemStack> storage = inv
.getInventory(Api.instance().storage().getStorageChannel(IItemStorageChannel.class));
final IPartitionList<IAEItemStack> filter = ViewCellItem.createFilter(cct.getViewCells());

for (int x = 0; x < craftMatrix.getSlots(); x++) {
ItemStack currentItem = craftMatrix.getStackInSlot(x);

// prepare slots
if (!currentItem.isEmpty()) {
// already the correct item?
ItemStack newItem = this.canUseInSlot(x, currentItem);

// put away old item
if (newItem != currentItem && security.hasPermission(player, SecurityPermissions.INJECT)) {
final IAEItemStack in = AEItemStack.fromItemStack(currentItem);
final IAEItemStack out = cct.useRealItems()
? Platform.poweredInsert(energy, storage, in, cct.getActionSource())
: null;
if (out != null) {
currentItem = out.createItemStack();
} else {
currentItem = ItemStack.EMPTY;
}
final IMEMonitor<IAEItemStack> storage = inv
.getInventory(Api.instance().storage().getStorageChannel(IItemStorageChannel.class));
final IPartitionList<IAEItemStack> filter = ViewCellItem.createFilter(cct.getViewCells());
final NonNullList<Ingredient> ingredients = this.ensure3by3CraftingMatrix(recipe);

// Handle each slot
for (int x = 0; x < craftMatrix.getSlots(); x++) {
ItemStack currentItem = craftMatrix.getStackInSlot(x);
Ingredient ingredient = ingredients.get(x);

// prepare slots
if (!currentItem.isEmpty()) {
// already the correct item? True, skip everything else
ItemStack newItem = this.canUseInSlot(ingredient, currentItem);

// put away old item, if not correct
if (newItem != currentItem && security.hasPermission(player, SecurityPermissions.INJECT)) {
final IAEItemStack in = AEItemStack.fromItemStack(currentItem);
final IAEItemStack out = cct.useRealItems()
? Platform.poweredInsert(energy, storage, in, cct.getActionSource())
: null;
if (out != null) {
currentItem = out.createItemStack();
} else {
currentItem = ItemStack.EMPTY;
}
}
}

// Find item or pattern from the network
if (currentItem.isEmpty() && security.hasPermission(player, SecurityPermissions.EXTRACT)) {
IAEItemStack out;

if (currentItem.isEmpty() && this.recipe[x] != null) {
// for each variant
for (int y = 0; y < this.recipe[x].length && currentItem.isEmpty(); y++) {
final IAEItemStack request = AEItemStack.fromItemStack(this.recipe[x][y]);
if (request != null) {
// try ae
if ((filter == null || filter.isListed(request))
&& security.hasPermission(player, SecurityPermissions.EXTRACT)) {
request.setStackSize(1);
IAEItemStack out;

if (cct.useRealItems()) {
out = Platform.poweredExtraction(energy, storage, request, cct.getActionSource());
} else {
// Query the crafting grid if there is a pattern providing the item
if (!crafting.getCraftingFor(request, null, 0, null).isEmpty()) {
out = request;
} else {
// Fall back using an existing item
out = storage.extractItems(request, Actionable.SIMULATE, cct.getActionSource());
}
}

if (out != null) {
currentItem = out.createItemStack();
}
}

// try inventory
if (currentItem.isEmpty()) {
AdaptorItemHandler ad = new AdaptorItemHandler(playerInventory);

if (cct.useRealItems()) {
currentItem = ad.removeItems(1, this.recipe[x][y], null);
} else {
currentItem = ad.simulateRemove(1, this.recipe[x][y], null);
}
}
if (cct.useRealItems()) {
IAEItemStack request = findBestMatchingItemStack(ingredient, filter, storage, cct);
out = Platform.poweredExtraction(energy, storage, request.setStackSize(1), cct.getActionSource());
} else {
out = findBestMatchingPattern(ingredient, filter, crafting, storage, cct);
if (out == null) {
out = findBestMatchingItemStack(ingredient, filter, storage, cct);
}
}

if (out != null) {
currentItem = out.createItemStack();
}
}

// If still nothing, search the player inventory.
if (currentItem.isEmpty()) {
ItemStack[] matchingStacks = ingredient.getMatchingStacks();
for (int y = 0; y < matchingStacks.length; y++) {
if (currentItem.isEmpty()) {
AdaptorItemHandler ad = new AdaptorItemHandler(playerInventory);

if (cct.useRealItems()) {
currentItem = ad.removeItems(1, matchingStacks[y], null);
} else {
currentItem = ad.simulateRemove(1, matchingStacks[y], null);
}
}
}
ItemHandlerUtil.setStackInSlot(craftMatrix, x, currentItem);
}
con.onCraftMatrixChanged(new WrapperInvItemHandler(craftMatrix));
ItemHandlerUtil.setStackInSlot(craftMatrix, x, currentItem);
}
con.onCraftMatrixChanged(new WrapperInvItemHandler(craftMatrix));
}

/**
*
* @param slot
* @param is itemstack
* @return is if it can be used, else EMPTY
* Expand any recipe to a 3x3 matrix.
*
* Will throw an {@link IllegalArgumentException} in case it has more than 9 or
* a shaped recipe is either wider or higher than 3. ingredients.
*
* @param recipe
* @return
*/
private ItemStack canUseInSlot(int slot, ItemStack is) {
if (this.recipe[slot] != null) {
for (ItemStack option : this.recipe[slot]) {
if (is.isItemEqual(option)) {
return is;
private NonNullList<Ingredient> ensure3by3CraftingMatrix(IRecipe<?> recipe) {
NonNullList<Ingredient> ingredients = recipe.getIngredients();
NonNullList<Ingredient> expandedIngredients = NonNullList.withSize(9, Ingredient.EMPTY);

Preconditions.checkArgument(ingredients.size() <= 9);

// shaped recipes can be either 2x2, 2x3, 3x2, or 3x3. Expand to 3x3
if (recipe instanceof IShapedRecipe) {
IShapedRecipe<?> shapedRecipe = (IShapedRecipe<?>) recipe;
int width = shapedRecipe.getRecipeWidth();
int height = shapedRecipe.getRecipeHeight();
Preconditions.checkArgument(width <= 3 && height <= 3);

for (int h = 0; h < height; h++) {
for (int w = 0; w < width; w++) {
int source = w + h * width;
int target = w + h * 3;
Ingredient i = ingredients.get(source);
expandedIngredients.set(target, i);
}
}
}
return ItemStack.EMPTY;
// Anything else should a flat list
else {
for (int i = 0; i < ingredients.size(); i++) {
expandedIngredients.set(i, ingredients.get(i));
}
}

return expandedIngredients;
}

/**
*
* @param ingredient
* @param slot
* @param is itemstack
* @return is if it can be used, else EMPTY
*/
private ItemStack canUseInSlot(Ingredient ingredient, ItemStack is) {
return Arrays.stream(ingredient.getMatchingStacks()).filter(p -> p.isItemEqual(is)).findFirst()
.orElse(ItemStack.EMPTY);
}

/**
* Finds the first matching itemstack with the highest stored amount.
*
* @param ingredients
* @param filter
* @param storage
* @param cct
* @return
*/
private IAEItemStack findBestMatchingItemStack(Ingredient ingredients, IPartitionList<IAEItemStack> filter,
IMEMonitor<IAEItemStack> storage, IContainerCraftingPacket cct) {
return Arrays.stream(ingredients.getMatchingStacks()).map(AEItemStack::fromItemStack)
.filter(r -> (filter == null || filter.isListed(r))).map(s -> s.setStackSize(Long.MAX_VALUE))
.map(s -> storage.extractItems(s, Actionable.SIMULATE, cct.getActionSource())).filter(Objects::nonNull)
.sorted((left, right) -> {
return Long.compare(right.getStackSize(), left.getStackSize());
}).findFirst().orElse(null);
}

/**
* This tries to find the first pattern matching the list of ingredients.
*
* As additional condition, it sorts by the stored amount to return the one with
* the highest stored amount.
*
* @param ingredients
* @param filter
* @param crafting
* @param storage
* @param cct
* @return
*/
private IAEItemStack findBestMatchingPattern(Ingredient ingredients, IPartitionList<IAEItemStack> filter,
ICraftingGrid crafting, IMEMonitor<IAEItemStack> storage, IContainerCraftingPacket cct) {
return Arrays.stream(ingredients.getMatchingStacks()).map(AEItemStack::fromItemStack)
.filter(r -> (filter == null || filter.isListed(r)))
.map(s -> s.setCraftable(!crafting.getCraftingFor(s, null, 0, null).isEmpty()))
.filter(IAEItemStack::isCraftable).map(s -> {
final IAEItemStack stored = storage.extractItems(s, Actionable.SIMULATE, cct.getActionSource());
return s.setStackSize(stored != null ? stored.getStackSize() : 0);
}).sorted((left, right) -> {
final int craftable = Boolean.compare(left.isCraftable(), right.isCraftable());
return craftable != 0 ? craftable : Long.compare(right.getStackSize(), left.getStackSize());
}).findFirst().orElse(null);
}

}
7 changes: 5 additions & 2 deletions src/main/java/appeng/integration/modules/jei/JEIPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,13 @@ public void registerCategories(IRecipeCategoryRegistration registry) {

@Override
public void registerRecipeTransferHandlers(IRecipeTransferRegistration registration) {

// Allow recipe transfer from JEI to crafting and pattern terminal
registration.addRecipeTransferHandler(new RecipeTransferHandler<>(CraftingTermContainer.class),
registration.addRecipeTransferHandler(
new RecipeTransferHandler<>(CraftingTermContainer.class, registration.getTransferHelper()),
VanillaRecipeCategoryUid.CRAFTING);
registration.addRecipeTransferHandler(new RecipeTransferHandler<>(PatternTermContainer.class),
registration.addRecipeTransferHandler(
new RecipeTransferHandler<>(PatternTermContainer.class, registration.getTransferHelper()),
VanillaRecipeCategoryUid.CRAFTING);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ public boolean isEnabled() {
return true;
}

public IJeiRuntime getRuntime() {
return runtime;
}

@Override
public String getSearchText() {
return Strings.nullToEmpty(this.runtime.getIngredientFilter().getFilterText());
Expand Down

0 comments on commit dfc4035

Please sign in to comment.