diff --git a/src/main/java/codechicken/nei/BookmarkCraftingChain.java b/src/main/java/codechicken/nei/BookmarkCraftingChain.java new file mode 100644 index 000000000..3a7d7ad3a --- /dev/null +++ b/src/main/java/codechicken/nei/BookmarkCraftingChain.java @@ -0,0 +1,312 @@ +package codechicken.nei; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +import net.minecraft.item.ItemStack; +import net.minecraftforge.fluids.FluidStack; + +import codechicken.nei.BookmarkPanel.ItemStackMetadata; +import codechicken.nei.recipe.BookmarkRecipeId; +import codechicken.nei.recipe.StackInfo; + +public class BookmarkCraftingChain { + + public HashMap calculatedItems = new HashMap<>(); + public HashMap multiplier = new HashMap<>(); + + public HashMap inputs = new HashMap<>(); + public HashMap outputs = new HashMap<>(); + public HashMap intermediate = new HashMap<>(); + + protected static class CraftingChainItem { + + public String guid; + public int stackIndex = 0; + public int recipeIndex = 0; + public int factor = 0; + public int count = 0; + public boolean ingredient = false; + public boolean fluidCell = false; + public ItemStack stack = null; + + public CraftingChainItem(ItemStack stack, ItemStackMetadata stackMetadata) { + FluidStack fluid = StackInfo.getFluid(stack); + this.recipeIndex = getRecipeIndex(stackMetadata.recipeId); + this.ingredient = stackMetadata.ingredient; + this.stack = stack; + + if (fluid != null) { + this.stackIndex = getStackIndex(fluid); + this.count = fluid.amount * Math.max(1, stack.stackSize); + this.fluidCell = StackInfo.isFluidContainer(stack); + this.factor = this.fluidCell ? fluid.amount : stackMetadata.factor; + } else { + this.stackIndex = getStackIndex(stack); + this.count = StackInfo.itemStackToNBT(stack).getInteger("Count"); + this.factor = stackMetadata.factor; + } + + this.guid = this.stackIndex + " " + this.recipeIndex; + } + + public ItemStack getItemStack(long count) { + return StackInfo.loadFromNBT( + StackInfo.itemStackToNBT(this.stack), + this.factor > 0 ? (this.fluidCell ? (count / this.factor) : count) : 0); + } + + public static void clearStatic() { + itemCache.clear(); + fluidCache.clear(); + recipeCache.clear(); + } + + private static HashMap itemCache = new HashMap<>(); + + private static int getStackIndex(ItemStack stackA) { + + if (!itemCache.containsKey(stackA)) { + + for (ItemStack item : itemCache.keySet()) { + if (StackInfo.equalItemAndNBT(stackA, item, true)) { + itemCache.put(stackA, itemCache.get(item)); + return itemCache.get(stackA); + } + } + + itemCache.put(stackA, itemCache.size() + fluidCache.size()); + } + + return itemCache.get(stackA); + } + + private static HashMap fluidCache = new HashMap<>(); + + private static int getStackIndex(FluidStack fluidA) { + + if (!fluidCache.containsKey(fluidA)) { + for (FluidStack item : fluidCache.keySet()) { + if (fluidA.isFluidEqual(item)) { + fluidCache.put(fluidA, fluidCache.get(item)); + return fluidCache.get(fluidA); + } + } + + fluidCache.put(fluidA, itemCache.size() + fluidCache.size()); + } + + return fluidCache.get(fluidA); + } + + private static HashMap recipeCache = new HashMap<>(); + + private static int getRecipeIndex(BookmarkRecipeId recipe) { + + if (recipe == null) { + return -1; + } + + if (!recipeCache.containsKey(recipe)) { + for (BookmarkRecipeId item : recipeCache.keySet()) { + if (item.equals(recipe)) { + recipeCache.put(recipe, recipeCache.get(item)); + return recipeCache.get(recipe); + } + } + + recipeCache.put(recipe, recipeCache.size()); + } + + return recipeCache.get(recipe); + } + + } + + protected static class CraftingChainRequest { + + public List items = new ArrayList<>(); + + public HashMap multiplier = new HashMap<>(); + public HashMap counts = new HashMap<>(); + + public HashMap> inputs = new HashMap<>(); + public HashMap> outputs = new HashMap<>(); + + public HashSet initialItems = new HashSet<>(); + public HashSet startedIngrs = new HashSet<>(); + + public CraftingChainRequest(List items, List metadata) { + HashSet inputs = new HashSet<>(); + HashSet outputs = new HashSet<>(); + + for (int index = 0; index < items.size(); index++) { + CraftingChainItem item = new CraftingChainItem(items.get(index), metadata.get(index)); + this.items.add(item); + + if (this.multiplier.get(item.recipeIndex) == null) { + this.multiplier.put(item.recipeIndex, 0); + } + + this.counts.put(item.guid, this.counts.getOrDefault(item.guid, 0L) + item.count); + + if (item.ingredient) { + inputs.add(item.recipeIndex); + + if (this.inputs.get(item.stackIndex) == null) { + this.inputs.put(item.stackIndex, new HashSet<>()); + } + + this.inputs.get(item.stackIndex).add(item); + } else { + outputs.add(item.recipeIndex); + + if (this.outputs.get(item.stackIndex) == null) { + this.outputs.put(item.stackIndex, new HashSet<>()); + } + + this.outputs.get(item.stackIndex).add(item); + } + + } + + for (int recipeIndex : this.multiplier.keySet()) { + if (recipeIndex == -1 || !inputs.contains(recipeIndex) || !outputs.contains(recipeIndex)) { + this.initialItems.add(recipeIndex); + } + } + + for (CraftingChainItem item : this.items) { + if (item.ingredient == false && !this.initialItems.contains(item.recipeIndex)) { + this.startedIngrs.add(item.stackIndex); + } + } + + CraftingChainItem.clearStatic(); + } + + } + + public void refresh(List items, List metadata) { + CraftingChainRequest request = new CraftingChainRequest(items, metadata); + HashMap iShift = new HashMap<>(); + HashMap oShift = new HashMap<>(); + boolean change = true; + int iteration = 100; + + while (--iteration > 0 && change) { + change = false; + for (int stackIndex : request.startedIngrs) { + change = calculateShift(request, stackIndex) || change; + } + } + + this.inputs.clear(); + this.outputs.clear(); + this.intermediate.clear(); + this.calculatedItems.clear(); + this.multiplier.clear(); + + for (CraftingChainItem item : request.items) { + if (request.initialItems.contains(item.recipeIndex)) { + long iCount = calculateCount(request, request.inputs.get(item.stackIndex)) + - iShift.getOrDefault(item.stackIndex, 0L); + this.calculatedItems.put(item.stack, item.stack); + + if (iCount == 0) { + this.inputs.put(item.stack, item.getItemStack(0)); + } else { + long count = Math.min(iCount, item.count); + this.inputs.put(item.stack, item.getItemStack(count)); + iShift.put(item.stackIndex, iShift.getOrDefault(item.stackIndex, 0L) + count); + } + + } + } + + for (CraftingChainItem item : request.items) { + if (!request.initialItems.contains(item.recipeIndex)) { + long count = item.count + item.factor * request.multiplier.get(item.recipeIndex); + this.calculatedItems.put(item.stack, item.getItemStack(count)); + + if (item.ingredient) { // input + long oCount = calculateCount(request, request.outputs.get(item.stackIndex)) + - oShift.getOrDefault(item.stackIndex, 0L); + + if (oCount < count) { + this.inputs.put(item.stack, item.getItemStack(count - oCount)); + oShift.put(item.stackIndex, oShift.getOrDefault(item.stackIndex, 0L) + oCount); + } else if (oCount >= count) { + this.intermediate.put(item.stack, item.getItemStack(0)); + oShift.put(item.stackIndex, oShift.getOrDefault(item.stackIndex, 0L) + count); + } + + } else { // output + long iCount = calculateCount(request, request.inputs.get(item.stackIndex)) + - iShift.getOrDefault(item.stackIndex, 0L); + this.multiplier.put(item.stack, item.factor > 0 ? (int) (count / item.factor) : 0); + + if (iCount < count) { + this.outputs.put(item.stack, item.getItemStack(count - iCount)); + iShift.put(item.stackIndex, iShift.getOrDefault(item.stackIndex, 0L) + iCount); + } else if (iCount >= count) { + this.intermediate.put(item.stack, item.getItemStack(0)); + iShift.put(item.stackIndex, iShift.getOrDefault(item.stackIndex, 0L) + count); + } + + } + + } + } + + } + + private boolean calculateShift(CraftingChainRequest request, int stackIndex) { + + if (!request.outputs.containsKey(stackIndex)) { + return false; + } + + int shift = 0; + int minRecipeIndex = 0; + int minShift = 0x7fffff; + + for (CraftingChainItem item : request.outputs.get(stackIndex)) { + if (!request.initialItems.contains(item.recipeIndex) && item.factor > 0) { + long ingrCount = calculateCount(request, request.inputs.get(item.stackIndex)); + long outputCount = calculateCount(request, request.outputs.get(item.stackIndex)); + + while ((outputCount + item.factor * shift) < ingrCount && shift < minShift) { + shift++; + } + + if (shift < minShift) { + minShift = shift; + minRecipeIndex = item.recipeIndex; + } + } + } + + if (minShift < 0x7fffff) { + request.multiplier.put(minRecipeIndex, request.multiplier.get(minRecipeIndex) + minShift); + return true; + } + + return false; + } + + private long calculateCount(CraftingChainRequest request, HashSet items) { + long count = 0L; + + if (items != null) { + for (CraftingChainItem item : items) { + count += request.counts.get(item.guid) + item.factor * request.multiplier.get(item.recipeIndex); + } + } + + return count; + } + +} diff --git a/src/main/java/codechicken/nei/BookmarkPanel.java b/src/main/java/codechicken/nei/BookmarkPanel.java index db12cfb8e..ac56bda42 100644 --- a/src/main/java/codechicken/nei/BookmarkPanel.java +++ b/src/main/java/codechicken/nei/BookmarkPanel.java @@ -15,20 +15,28 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import javax.annotation.Nullable; +import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.inventory.Slot; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import org.apache.commons.io.IOUtils; import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL12; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; @@ -40,6 +48,8 @@ import codechicken.nei.api.IBookmarkContainerHandler; import codechicken.nei.guihook.GuiContainerManager; import codechicken.nei.recipe.BookmarkRecipeId; +import codechicken.nei.recipe.GuiCraftingRecipe; +import codechicken.nei.recipe.GuiRecipe; import codechicken.nei.recipe.StackInfo; import codechicken.nei.util.NBTJson; import codechicken.nei.util.ReadableNumberConverter; @@ -47,9 +57,9 @@ public class BookmarkPanel extends PanelWidget { protected File bookmarkFile; - protected boolean bookmarksIsLoaded = false; - public int sortedStackIndex = -1; - public int sortedNamespaceIndex = -1; + protected BookmarkLoadingState bookmarksState; + protected SortableItem sortableItem; + protected GroupingItem groupingItem; public Button namespacePrev; public Button namespaceNext; @@ -59,19 +69,50 @@ public class BookmarkPanel extends PanelWidget { protected List namespaces = new ArrayList<>(); protected int activeNamespaceIndex = 0; - protected static class BookmarkStackMeta { + protected static class SortableItem { + + public List items = new ArrayList<>(); + public List metadata = new ArrayList<>(); + + public SortableItem(List items, List metadata) { + this.items = items; + this.metadata = metadata; + } + + } + + protected static class GroupingItem { + + public boolean ungroup; + public int rowIndexA; + public int rowIndexB = -1; + + public GroupingItem(boolean ungroup, int rowIndexA) { + this.ungroup = ungroup; + this.rowIndexA = rowIndexA; + } + } + + protected static class ItemStackMetadata { public int factor; + public Integer groupId; public BookmarkRecipeId recipeId; public boolean ingredient = false; public boolean fluidDisplay = false; - public BookmarkStackMeta(BookmarkRecipeId recipeId, int count, boolean ingredient, boolean fluidDisplay) { + public ItemStackMetadata(BookmarkRecipeId recipeId, int factor, boolean ingredient, Integer groupId, + boolean fluidDisplay) { this.recipeId = recipeId; - this.factor = count; + this.factor = factor; this.ingredient = ingredient; + this.groupId = groupId; this.fluidDisplay = fluidDisplay; } + + public ItemStackMetadata copy() { + return new ItemStackMetadata(this.recipeId, this.factor, this.ingredient, this.groupId, this.fluidDisplay); + } } public static enum BookmarkViewMode { @@ -79,164 +120,580 @@ public static enum BookmarkViewMode { TODO_LIST } + public static enum BookmarkLoadingState { + LOADING, + LOADED + } + + protected static class BookmarkGroup { + + public BookmarkCraftingChain crafting = null; + public BookmarkViewMode viewMode; + + public BookmarkGroup(BookmarkViewMode viewMode) { + this.viewMode = viewMode; + } + + public BookmarkGroup(BookmarkViewMode viewMode, boolean crafting) { + this.viewMode = viewMode; + this.crafting = crafting ? new BookmarkCraftingChain() : null; + } + + public void toggleViewMode() { + if (this.viewMode == BookmarkViewMode.DEFAULT) { + this.viewMode = BookmarkViewMode.TODO_LIST; + } else { + this.viewMode = BookmarkViewMode.DEFAULT; + } + } + + public void toggleCraftingMode() { + if (this.crafting == null) { + this.crafting = new BookmarkCraftingChain(); + } else { + this.crafting = null; + } + } + + public BookmarkGroup copy() { + return new BookmarkGroup(this.viewMode); + } + } + public static class BookmarkGrid extends ItemsGrid { + protected static final int GROUP_PANEL_WIDTH = 7; + protected static final int DEFAULT_GROUP_ID = 0; protected static final float SCALE_SPEED = 0.1f; - protected List> gridMask; - protected List metadata = new ArrayList<>(); + protected List metadata = new ArrayList<>(); protected WeakHashMap animation = new WeakHashMap<>(); + protected RecipeTooltipRenderer recipeTooltipRenderer = null; + + protected Map groups = new HashMap<>(); + protected List gridGroupMask = new ArrayList<>(); + protected int previousPageGroupId = DEFAULT_GROUP_ID; + protected int nextPageGroupId = DEFAULT_GROUP_ID; + protected int focusedGroupId = -1; + protected int pageCount = 0; - protected BookmarkViewMode viewMode = BookmarkViewMode.DEFAULT; - protected boolean[] todoSpaces; - protected int todoItemsCount = 0; + private static class RecipeTooltipRenderer { + + public int slotIndex = 0; + public GuiRecipe gui = null; + public Runnable createRecipeGui = null; + } public BookmarkGrid() { - this.setShowRecipeTooltips(true); + this.groups.put(DEFAULT_GROUP_ID, new BookmarkGroup(BookmarkViewMode.DEFAULT)); } - public void setViewMode(BookmarkViewMode mode) { - if (viewMode == mode) { - return; - } + public BookmarkViewMode getViewMode(int groupId) { + return this.groups.get(groupId).viewMode; + } + + public void setViewMode(int groupId, BookmarkViewMode mode) { + if (this.groups.get(groupId).viewMode != mode) { + this.groups.get(groupId).viewMode = mode; + if (mode == BookmarkViewMode.DEFAULT) { + sortGroup(groupId); + } - viewMode = mode; + onItemsChanged(); + } + } - if (mode == BookmarkViewMode.DEFAULT) { - sortIngredients(false); - todoItemsCount = 0; - gridMask = null; + public void toggleViewMode(int groupId) { + this.groups.get(groupId).toggleViewMode(); + if (this.groups.get(groupId).viewMode == BookmarkViewMode.DEFAULT) { + sortGroup(groupId); } + onItemsChanged(); + } - onGridChanged(); + public boolean getCraftingMode(int groupId) { + return this.groups.get(groupId).crafting != null; + } + + public void setCraftingMode(int groupId, boolean on) { + if ((this.groups.get(groupId).crafting != null) != on) { + this.groups.get(groupId).crafting = on ? new BookmarkCraftingChain() : null; + onItemsChanged(); + } } - public BookmarkViewMode getViewMode() { - return viewMode; + public void toggleCraftingMode(int groupId) { + this.groups.get(groupId).toggleCraftingMode(); + onItemsChanged(); } @Override public int getNumPages() { - if (gridMask != null) { - return gridMask.size(); + + if (gridMask == null) { + getMask(); } - return super.getNumPages(); + return this.pageCount; } @Override protected List getMask() { - if (gridMask != null) { - return gridMask.size() > page ? gridMask.get(page) : new ArrayList<>(); + + if (this.gridMask != null) { + return this.gridMask; } - return super.getMask(); - } + if (this.perPage == 0 || size() == 0) { + this.gridGroupMask = new ArrayList<>(); + this.gridMask = new ArrayList<>(); + return this.gridMask; + } - private void sortIngredients(final boolean forward) { - BookmarkStackMeta meta; - int dir = forward ? 1 : -1; + ItemStackMetadata previousMeta = new ItemStackMetadata(null, 1, false, DEFAULT_GROUP_ID, false); + List itemsMask = new ArrayList<>(); + List groupsMask = new ArrayList<>(); + ItemStackMetadata meta; + int size = size(); + int lastIdx = -1; + int index = 0; + int idx = 0; - for (int i = 0; i < realItems.size(); i++) { - meta = metadata.get(i); + while (idx < size && lastIdx != idx) { + lastIdx = idx; - if (meta.recipeId != null && !meta.ingredient) { - int shift = 0; + for (int r = 0; r < rows && idx < size; r++) { + for (int c = 0; c < columns && idx < size; c++) { + index = r * columns + c; + + if (isInvalidSlot(index)) { + itemsMask.add(null); + } else { + meta = this.metadata.get(idx); + + if (c > 0 && previousMeta.groupId != meta.groupId) { + // new group must start on a new line + itemsMask.add(null); + } else if (getViewMode(meta.groupId) == BookmarkViewMode.DEFAULT) { + previousMeta = meta; + itemsMask.add(idx++); + } else { + + if (c == 0 && (meta.recipeId == null || meta.ingredient == false + || index + 1 < rows * columns && isInvalidSlot(index + 1) + || meta.ingredient && !meta.recipeId.equals(previousMeta.recipeId))) { + // In first column must be an item without recipe, a recipe result, or an ingredient + // if the second column is occupied + previousMeta = meta; + itemsMask.add(idx++); + } else + if (c > 0 && meta.recipeId != null && meta.recipeId.equals(previousMeta.recipeId)) { + previousMeta = meta; + itemsMask.add(idx++); + } else { + itemsMask.add(null); + } - for (int j = i + 1; j < realItems.size(); j++) { - if (meta.recipeId.equals(metadata.get(j).recipeId)) { - if (i + shift + dir != j) { - realItems.add(i + shift + Math.max(dir, 0), realItems.remove(j)); - metadata.add(i + shift + Math.max(dir, 0), metadata.remove(j)); } - shift++; + } + } + groupsMask.add(previousMeta.groupId); + } + } - for (int j = i + shift - 1; j >= 0; j--) { - if (meta.recipeId.equals(metadata.get(j).recipeId)) { - if (i + shift + dir != j) { - realItems.add(i + shift + Math.min(dir, 0), realItems.remove(j)); - metadata.add(i + shift + Math.min(dir, 0), metadata.remove(j)); - } - shift--; + this.pageCount = (int) Math.ceil(itemsMask.size() / (float) (this.rows * this.columns)); + this.page = Math.max(0, Math.min(this.page, this.pageCount - 1)); + this.gridMask = itemsMask + .subList(page * rows * columns, Math.min(itemsMask.size(), (page + 1) * rows * columns)); + this.previousPageGroupId = DEFAULT_GROUP_ID; + this.nextPageGroupId = DEFAULT_GROUP_ID; + + if (page > 0 && (this.page * this.rows) < groupsMask.size() + && groupsMask.get(this.page * this.rows) == groupsMask.get(this.page * this.rows - 1)) { + this.previousPageGroupId = groupsMask.get(this.page * this.rows); + } + + if ((this.page + 1) * this.rows < groupsMask.size() + && groupsMask.get((this.page + 1) * this.rows) == groupsMask.get((this.page + 1) * this.rows - 1)) { + this.nextPageGroupId = groupsMask.get((this.page + 1) * this.rows); + } + + this.gridGroupMask = groupsMask.subList(page * rows, Math.min(groupsMask.size(), (page + 1) * rows)); + + return this.gridMask; + } + + protected int getRowGroupId(int rowIndex) { + if (gridMask == null) { + getMask(); + } + + return rowIndex < this.gridGroupMask.size() ? this.gridGroupMask.get(rowIndex) : DEFAULT_GROUP_ID; + } + + protected int getRowItemIndex(int rowIndex, boolean dir) { + final List mask = getMask(); + final int size = mask.size(); + int i = dir ? 0 : columns - 1; + + while (i >= 0 && i < columns) { + + if ((rowIndex * columns + i) < size && mask.get(rowIndex * columns + i) != null) { + return mask.get(rowIndex * columns + i); + } + + i += dir ? 1 : -1; + } + + return -1; + } + + protected int getHoveredRowIndex(boolean groupPanel) { + final Point mouse = getMousePosition(); + final int leftBorder = marginLeft + paddingLeft; + final int r = (int) ((mouse.y - marginTop) / SLOT_SIZE); + + if (!(new Rectangle4i( + leftBorder - (groupPanel ? BookmarkGrid.GROUP_PANEL_WIDTH : 0), + marginTop, + columns * SLOT_SIZE, + (getLastRowIndex() + 1) * SLOT_SIZE)).contains(mouse.x, mouse.y)) { + return -1; + } + + if (groupPanel && mouse.x >= leftBorder - GROUP_PANEL_WIDTH && mouse.x < leftBorder) { + return r; + } + + if (!groupPanel && !isInvalidSlot(columns * r + ((mouse.x - leftBorder) / SLOT_SIZE))) { + return r; + } + + return -1; + } + + @Override + public void setGridSize(int mleft, int mtop, int w, int h) { + super.setGridSize(mleft + GROUP_PANEL_WIDTH, mtop, w, h); + } + + @Override + public void draw(int mousex, int mousey) { + if (getPerPage() == 0) { + return; + } + + final int focusedRowIndex = getHoveredRowIndex(true); + + if (focusedRowIndex != -1) { + drawRect( + marginLeft + paddingLeft - GROUP_PANEL_WIDTH, + marginTop + focusedRowIndex * SLOT_SIZE, + GROUP_PANEL_WIDTH, + SLOT_SIZE, + 0xee555555); + } + + final GroupingItem groupingItem = LayoutManager.bookmarkPanel.groupingItem; + List groupMask = this.gridGroupMask; + int groupStartIndex = -2; + int previoudGroupId = DEFAULT_GROUP_ID; + + if (groupingItem != null && groupingItem.rowIndexB != -1) { + int topRowIndex = Math.min(groupingItem.rowIndexA, groupingItem.rowIndexB); + int bottomRowIndex = Math.max(groupingItem.rowIndexA, groupingItem.rowIndexB); + int groupIdA = groupMask.get(groupingItem.rowIndexA); + int groupId = groupingItem.ungroup ? DEFAULT_GROUP_ID : (groupIdA == DEFAULT_GROUP_ID ? -1 : groupIdA); + groupMask = new ArrayList<>(groupMask); + + for (int rowIndex = topRowIndex; rowIndex <= bottomRowIndex; rowIndex++) { + groupMask.set(rowIndex, groupId); + } + + if (groupIdA != DEFAULT_GROUP_ID && !groupingItem.ungroup + && groupingItem.rowIndexA != groupingItem.rowIndexB) { + if (groupingItem.rowIndexB == topRowIndex) { + for (int rowIndex = topRowIndex - 1; rowIndex >= 0 + && groupMask.get(rowIndex) == groupIdA; rowIndex--) { + groupMask.set(rowIndex, DEFAULT_GROUP_ID); + } + } else if (groupingItem.rowIndexB == bottomRowIndex) { + for (int rowIndex = bottomRowIndex + 1; rowIndex < groupMask.size() + && groupMask.get(rowIndex) == groupIdA; rowIndex++) { + groupMask.set(rowIndex, DEFAULT_GROUP_ID); } } + } + + } + + if (this.previousPageGroupId != DEFAULT_GROUP_ID && this.previousPageGroupId == groupMask.get(0)) { + previoudGroupId = this.previousPageGroupId; + groupStartIndex = -1; + } - i += Math.max(0, shift); + for (int rowIndex = 0; rowIndex < groupMask.size(); rowIndex++) { + int groupId = groupMask.get(rowIndex); + + if (groupStartIndex != -2 && previoudGroupId != DEFAULT_GROUP_ID && previoudGroupId != groupId) { + drawGroup(Math.max(DEFAULT_GROUP_ID, previoudGroupId), groupStartIndex, rowIndex - 1); + groupStartIndex = -2; + } + + if (groupStartIndex == -2 && groupId != DEFAULT_GROUP_ID) { + groupStartIndex = rowIndex; } + + previoudGroupId = groupId; } + + if (groupStartIndex != -2) { + final int rowIndex = this.nextPageGroupId != DEFAULT_GROUP_ID + && this.nextPageGroupId == groupMask.get(groupMask.size() - 1) ? this.rows : getLastRowIndex(); + drawGroup(Math.max(DEFAULT_GROUP_ID, previoudGroupId), groupStartIndex, rowIndex); + } + + if (NEIClientUtils.shiftKey() && !LayoutManager.bookmarkPanel.inEditingState()) { + final ItemPanelSlot focused = getSlotMouseOver(mousex, mousey); + this.focusedGroupId = focused != null ? this.metadata.get(focused.slotIndex).groupId : -1; + } else { + this.focusedGroupId = -1; + } + + super.draw(mousex, mousey); } - private void generateToDoSpaces() { - todoItemsCount = 0; - gridMask = new ArrayList<>(); + private void drawGroup(int groupId, int rowIndexStart, int rowIndexEnd) { + final int halfWidth = GROUP_PANEL_WIDTH / 2; + final int heightPadding = SLOT_SIZE / 4; + final int leftPosition = marginLeft + paddingLeft - halfWidth - 1; + final int color = this.groups.get(groupId).crafting != null ? 0x6645DA75 : 0xff666666; + int width = (Math.min(rowIndexEnd, this.rows - 1) - Math.max(0, rowIndexStart) + 1) * SLOT_SIZE; + int top = marginTop + Math.max(0, rowIndexStart) * SLOT_SIZE; - if (rows * columns == 0) { - return; + if (rowIndexStart >= 0) { + drawRect(leftPosition, marginTop + rowIndexStart * SLOT_SIZE + heightPadding, halfWidth, 1, color); + top += heightPadding + 1; + width -= heightPadding + 1; } - BookmarkStackMeta meta; - boolean isFirstColumn = false; - boolean isIngredient = false; - int size = size(); - int idx = 0; + if (rowIndexEnd < this.rows) { + drawRect(leftPosition, marginTop + (rowIndexEnd + 1) * SLOT_SIZE - heightPadding, halfWidth, 1, color); + width -= heightPadding; + } - while (idx < size) { - ArrayList pmask = new ArrayList<>(); - int lastIdx = idx; + drawRect(leftPosition, top, 1, width, color); + } - for (int i = 0, perPage = rows * columns; i < perPage && idx < size; i++) { + private void removeDuplicateItems() { + final HashMap recipeCache = new HashMap<>(); + final HashMap itemsCache = new HashMap<>(); + final HashSet unique = new HashSet<>(); + ItemStackMetadata meta; + ItemStack stack; + int index = 0; + String key; - if (isInvalidSlot(i)) { - pmask.add(null); - } else { - meta = getMetadata(idx); - isFirstColumn = (i % columns) == 0; - isIngredient = meta.recipeId != null && meta.ingredient; - - if (isFirstColumn && !isIngredient) { - pmask.add(idx++); - - todoItemsCount++; - } else if (isIngredient && (!isFirstColumn || i + 1 < perPage && isInvalidSlot(i + 1))) { - // The ingredients that do not fit on the first line are moved to the second starting with - // the second column. This condition checks whether the second column is available. If not - // available, the ingredient takes the first column - pmask.add(idx++); - } else { - pmask.add(null); + while (index < this.metadata.size()) { + stack = this.realItems.get(index); + meta = this.metadata.get(index); + key = String.valueOf(meta.groupId); + + if (!itemsCache.containsKey(stack)) { + for (ItemStack item : itemsCache.keySet()) { + if (StackInfo.equalItemAndNBT(stack, item, true)) { + itemsCache.put(stack, itemsCache.get(item)); + break; } + } + } + + if (!itemsCache.containsKey(stack)) { + itemsCache.put(stack, itemsCache.size()); + } + + key = key + ":" + itemsCache.get(stack); + if (meta.recipeId != null) { + if (!recipeCache.containsKey(meta.recipeId)) { + for (BookmarkRecipeId item : recipeCache.keySet()) { + if (item.equals(meta.recipeId)) { + recipeCache.put(meta.recipeId, recipeCache.get(item)); + break; + } + } } + if (!recipeCache.containsKey(meta.recipeId)) { + recipeCache.put(meta.recipeId, recipeCache.size()); + } + + key = key + ":" + recipeCache.get(meta.recipeId); } - if (lastIdx == idx) { - break; + if (unique.contains(key)) { + this.realItems.remove(index); + this.metadata.remove(index); + } else { + unique.add(key); + index++; } + } + + } + + private void sortGroup(int groupId) { + final int dir = getViewMode(groupId) == BookmarkViewMode.TODO_LIST ? 1 : -1; + final int size = this.metadata.size(); + ItemStackMetadata meta; + int idx = 0; - gridMask.add(pmask); + while (idx < size) { + meta = this.metadata.get(idx); + if (meta.groupId == groupId && meta.recipeId != null) { + final HashMap sortingRank = new HashMap<>(); + final ArrayList sortedMetadata = new ArrayList<>(); + final ArrayList sortedItems = new ArrayList<>(); + final ArrayList items = new ArrayList<>(); + + for (int index = idx; index < size; index++) { + if (this.metadata.get(index).groupId == groupId + && meta.recipeId.equals(this.metadata.get(index).recipeId)) { + sortingRank.put(index, (this.metadata.get(index).ingredient ? 1 : -1) * dir); + items.add(index); + } + } + + items.sort((a, b) -> sortingRank.get(a) - sortingRank.get(b)); + + for (int index : items) { + sortedItems.add(this.realItems.get(index)); + sortedMetadata.add(this.metadata.get(index)); + } + + this.realItems.removeAll(sortedItems); + this.metadata.removeAll(sortedMetadata); + + this.realItems.addAll(idx, sortedItems); + this.metadata.addAll(idx, sortedMetadata); + + idx += sortedItems.size(); + } else { + idx++; + } } + } - protected void onGridChanged() { - super.onGridChanged(); + private void sortIngredients() { + if (this.size() == 0) { + return; + } + + removeDuplicateItems(); + boolean inEditingState = LayoutManager.bookmarkPanel.inEditingState(); + + for (int groupId : this.groups.keySet()) { + BookmarkGroup group = this.groups.get(groupId); + + if (group.viewMode == BookmarkViewMode.TODO_LIST) { + sortGroup(groupId); + } + + if (group.crafting != null && !inEditingState) { + ArrayList groupMetadata = new ArrayList<>(); + ArrayList groupItems = new ArrayList<>(); + + for (int idx = 0; idx < this.metadata.size(); idx++) { + if (this.metadata.get(idx).groupId == groupId) { + groupItems.add(this.realItems.get(idx)); + groupMetadata.add(this.metadata.get(idx)); + } + } + + group.crafting.refresh(groupItems, groupMetadata); + } + + } + } + + protected void onItemsChanged() { + sortIngredients(); + onGridChanged(); + } + + protected void createGroup(GroupingItem groupingItem) { + final List mask = getMask(); + final int topRowIndex = Math.min(groupingItem.rowIndexA, groupingItem.rowIndexB); + final int bottomRowIndex = Math.max(groupingItem.rowIndexA, groupingItem.rowIndexB); + final int groupIdA = getRowGroupId(groupingItem.rowIndexA); + final int startMaskIndex = topRowIndex * columns; + final int endMaskIntex = Math.min((bottomRowIndex + 1) * columns, mask.size()); + int startItemIndex = -1; + int endItemIndex = -1; + int groupId = DEFAULT_GROUP_ID; + + if (!groupingItem.ungroup) { + if (groupIdA == DEFAULT_GROUP_ID) { + for (ItemStackMetadata meta : this.metadata) { + if (meta.groupId > groupId) { + groupId = meta.groupId; + } + } + this.groups.put(++groupId, new BookmarkGroup(getViewMode(DEFAULT_GROUP_ID))); + } else { + groupId = groupIdA; + } + } + + for (int index = startMaskIndex; index < endMaskIntex; index++) { + if (mask.get(index) != null) { + startItemIndex = startItemIndex == -1 ? mask.get(index) : startItemIndex; + this.metadata.get(mask.get(index)).groupId = groupId; + endItemIndex = mask.get(index); + } + } + + if (startItemIndex != -1 && endItemIndex != -1 + && groupIdA != DEFAULT_GROUP_ID + && !groupingItem.ungroup + && groupingItem.rowIndexA != groupingItem.rowIndexB) { + if (groupingItem.rowIndexB == topRowIndex) { + for (int idx = startItemIndex - 1; idx >= 0 && this.metadata.get(idx).groupId == groupIdA; idx--) { + this.metadata.get(idx).groupId = DEFAULT_GROUP_ID; + } + } else { + for (int idx = endItemIndex + 1; idx < this.metadata.size() + && this.metadata.get(idx).groupId == groupIdA; idx++) { + this.metadata.get(idx).groupId = DEFAULT_GROUP_ID; + } + } + } - if (getViewMode() == BookmarkViewMode.TODO_LIST) { - sortIngredients(true); - generateToDoSpaces(); + final HashSet usedSetIds = new HashSet<>(); + for (ItemStackMetadata meta : this.metadata) { + usedSetIds.add(meta.groupId); } + + this.groups.keySet().removeIf((Integer k) -> k != DEFAULT_GROUP_ID && !usedSetIds.contains(k)); + + onItemsChanged(); } public int indexOf(ItemStack stackA, BookmarkRecipeId recipeId) { - final boolean useNBT = NEIClientConfig.useNBTInBookmarks(); + return indexOf(stackA, recipeId, DEFAULT_GROUP_ID); + } + + public int indexOf(ItemStack stackA, BookmarkRecipeId recipeId, int groupId) { for (int idx = 0; idx < realItems.size(); idx++) { - final BookmarkStackMeta meta = getMetadata(idx); - if ((recipeId == null && meta.recipeId == null - || recipeId != null && meta.recipeId != null && recipeId.equals(meta.recipeId)) - && StackInfo.equalItemAndNBT(stackA, getItem(idx), useNBT)) { + final ItemStackMetadata meta = getMetadata(idx); + if (meta.groupId == groupId + && (recipeId == null && meta.recipeId == null + || recipeId != null && meta.recipeId != null && recipeId.equals(meta.recipeId)) + && StackInfo.equalItemAndNBT(stackA, getItem(idx), true)) { return idx; } } @@ -244,57 +701,63 @@ public int indexOf(ItemStack stackA, BookmarkRecipeId recipeId) { return -1; } - public BookmarkStackMeta getMetadata(int idx) { + public ItemStack getItem(int idx) { + final ItemStack stack = super.realItems.get(idx); + final ItemStackMetadata meta = this.getMetadata(idx); + final BookmarkCraftingChain crafting = this.groups.get(meta.groupId).crafting; + + if (crafting != null && crafting.calculatedItems.containsKey(stack)) { + return crafting.calculatedItems.get(stack); + } + + return stack; + } + + public ItemStackMetadata getMetadata(int idx) { return metadata.get(idx); } - public void addItem(ItemStack stackA, BookmarkStackMeta meta) { + public void addItem(ItemStack stackA, ItemStackMetadata meta) { addItem(stackA, meta, true); } - public void addItem(ItemStack stackA, BookmarkStackMeta meta, boolean animate) { - realItems.add(stackA); - metadata.add(meta); + public void addItem(ItemStack stackA, ItemStackMetadata meta, boolean animate) { + this.realItems.add(stackA); + this.metadata.add(meta); if (animate && !NEIClientConfig.shouldCacheItemRendering() && NEIClientConfig.areBookmarksAnimated()) { - animation.put(stackA, 0f); + this.animation.put(stackA, 0f); } - onGridChanged(); - } - - public void replaceItem(int idx, ItemStack stack) { - realItems.set(idx, stack); - onGridChanged(); + onItemsChanged(); } - public void removeRecipe(int idx, boolean removeFullRecipe) { - final BookmarkStackMeta meta = getMetadata(idx); + protected void removeRecipe(int idx, boolean removeFullRecipe) { + final ItemStackMetadata meta = getMetadata(idx); if (meta.recipeId != null && (removeFullRecipe || !meta.ingredient)) { - removeRecipe(meta.recipeId); + removeRecipe(meta.recipeId, meta.groupId); } else { removeItem(idx); } } - public void removeRecipe(BookmarkRecipeId recipeIdA) { + protected void removeRecipe(BookmarkRecipeId recipeIdA, int groupId) { BookmarkRecipeId recipeIdB; for (int slotIndex = metadata.size() - 1; slotIndex >= 0; slotIndex--) { - recipeIdB = getRecipeId(slotIndex); + recipeIdB = metadata.get(slotIndex).recipeId; - if (recipeIdB != null && recipeIdB.equals(recipeIdA)) { + if (recipeIdB != null && metadata.get(slotIndex).groupId == groupId && recipeIdB.equals(recipeIdA)) { removeItem(slotIndex); } } } protected boolean removeItem(int idx) { - animation.remove(getItem(idx)); realItems.remove(idx); metadata.remove(idx); - onGridChanged(); + onItemsChanged(); return true; } @@ -302,68 +765,160 @@ public BookmarkRecipeId getRecipeId(int idx) { return getMetadata(idx).recipeId; } - public BookmarkRecipeId findRecipeId(ItemStack stackA) { - final boolean useNBT = NEIClientConfig.useNBTInBookmarks(); + protected void moveItem(SortableItem sortableItem, int slotIndex) { + moveItem( + sortableItem, + slotIndex, + slotIndex < 0 ? DEFAULT_GROUP_ID : this.metadata.get(slotIndex).groupId, + slotIndex < this.realItems.indexOf(sortableItem.items.get(0))); + } - for (int idx = 0; idx < realItems.size(); idx++) { - if (StackInfo.equalItemAndNBT(stackA, getItem(idx), useNBT)) { - return getRecipeId(idx); + protected void moveItem(SortableItem sortableItem, int slotIndex, int groupId, boolean moveUp) { + if (slotIndex == -1) return; + + if (sortableItem.items.indexOf(this.realItems.get(slotIndex)) == -1) { + final ItemStack stackA = this.realItems.get(slotIndex); + + this.realItems.removeAll(sortableItem.items); + this.metadata.removeAll(sortableItem.metadata); + + slotIndex = this.realItems.indexOf(stackA) + (moveUp ? 0 : 1); + + for (ItemStackMetadata sm : sortableItem.metadata) { + sm.groupId = groupId; } + + this.realItems.addAll(slotIndex, sortableItem.items); + this.metadata.addAll(slotIndex, sortableItem.metadata); + + onItemsChanged(); + } else if (sortableItem.metadata.get(0).groupId != groupId) { + + for (ItemStackMetadata meta : sortableItem.metadata) { + meta.groupId = groupId; + } + + onItemsChanged(); } - return null; } - public void moveItem(int src, int dst) { - realItems.add(dst, realItems.remove(src)); - metadata.add(dst, metadata.remove(src)); - onGridChanged(); + protected boolean shouldCacheItemRendering() { + return NEIClientConfig.shouldCacheItemRendering() && this.focusedGroupId == -1; } - private boolean isPartOfFocusedRecipe(ItemPanelSlot focused, int myIdx) { - return NEIClientUtils.shiftKey() && focused != null - && LayoutManager.bookmarkPanel.sortedStackIndex == -1 - && getRecipeId(focused.slotIndex) != null - && getRecipeId(myIdx) != null - && getRecipeId(focused.slotIndex).equals(getRecipeId(myIdx)); + @Override + protected void beforeDrawSlot(@Nullable ItemPanelSlot focus, int idx, Rectangle4i rect) { + + if (LayoutManager.bookmarkPanel.sortableItem != null || !NEIClientUtils.shiftKey()) { + super.beforeDrawSlot(focus, idx, rect); + } else if (NEIClientUtils.shiftKey() && !LayoutManager.bookmarkPanel.inEditingState()) { + ItemStack stack = this.realItems.get(idx); + ItemStackMetadata meta = this.getMetadata(idx); + BookmarkGroup groupMeta = this.groups.get(meta.groupId); + + if (groupMeta.crafting != null && meta.groupId == this.focusedGroupId) { + + if (groupMeta.crafting.inputs.containsKey(stack)) { + drawRect(rect.x, rect.y, rect.w, rect.h, 0x6645DA75); // inputs + } else if (groupMeta.crafting.outputs.containsKey(stack)) { + drawRect(rect.x, rect.y, rect.w, rect.h, 0x9966CCFF); // exports + } + + } else if (focus != null && meta.recipeId != null + && meta.groupId == getMetadata(focus.slotIndex).groupId + && meta.recipeId.equals(this.getRecipeId(focus.slotIndex))) { + drawRect(rect.x, rect.y, rect.w, rect.h, meta.ingredient ? 0x6645DA75 : 0x9966CCFF); // highlight + // recipe + } else { + super.beforeDrawSlot(focus, idx, rect); + } + + } + } @Override - protected void drawSlotOutline(@Nullable ItemPanelSlot focus, int idx, Rectangle4i rect) { + protected void afterDrawSlot(@Nullable ItemPanelSlot focus, int idx, Rectangle4i rect) { + final ItemStackMetadata meta = this.getMetadata(idx); - if (LayoutManager.bookmarkPanel.sortedNamespaceIndex == LayoutManager.bookmarkPanel.activeNamespaceIndex - && LayoutManager.bookmarkPanel.sortedStackIndex == idx) { - drawRect(rect.x, rect.y, rect.w, rect.h, 0xee555555); // highlight - } else if (focus != null) { - BookmarkStackMeta meta = getMetadata(idx); - if (isPartOfFocusedRecipe(focus, idx)) { - drawRect(rect.x, rect.y, rect.w, rect.h, meta.ingredient ? 0x88b3b300 : 0x88009933); // highlight - // recipe - } else if (focus.slotIndex == idx) { - drawRect(rect.x, rect.y, rect.w, rect.h, 0xee555555); // highlight + if (meta.ingredient == true || meta.recipeId == null || LayoutManager.bookmarkPanel.sortableItem != null) { + return; + } + + final ItemStack stack = this.realItems.get(idx); + final BookmarkGroup groupMeta = this.groups.get(meta.groupId); + int multiplier = 0; + + if (NEIClientUtils.shiftKey()) { + final ItemStackMetadata prevMeta = idx > 0 ? this.getMetadata(idx - 1) : null; + if (prevMeta == null || prevMeta.ingredient || !meta.recipeId.equals(prevMeta.recipeId)) { + if (groupMeta.crafting != null && meta.groupId == this.focusedGroupId + && groupMeta.crafting.multiplier.containsKey(stack)) { + multiplier = this.groups.get(meta.groupId).crafting.multiplier.get(stack); + } else if (focus != null && meta.factor > 0 + && meta.recipeId.equals(this.getRecipeId(focus.slotIndex))) { + multiplier = StackInfo.itemStackToNBT(stack).getInteger("Count") / meta.factor; + } } } + + if (multiplier > 0) { + drawRecipeMarker( + rect.x, + rect.y, + GuiContainerManager.getFontRenderer(stack), + "x" + ReadableNumberConverter.INSTANCE.toWideReadableForm(multiplier), + 16777215); + } else if (meta.recipeId != null && !meta.ingredient && NEIClientConfig.showRecipeMarker()) { + drawRecipeMarker(rect.x, rect.y, GuiContainerManager.getFontRenderer(stack), "R", 0xA0A0A0); + } + } @Override protected void drawItem(Rectangle4i rect, int idx) { - if (LayoutManager.bookmarkPanel.sortedNamespaceIndex != LayoutManager.bookmarkPanel.activeNamespaceIndex - || LayoutManager.bookmarkPanel.sortedStackIndex != idx) { - final ItemStack stack = getItem(idx); - final BookmarkStackMeta meta = getMetadata(idx); - final String stackSize = meta.factor < 0 || meta.fluidDisplay ? "" - : ReadableNumberConverter.INSTANCE.toWideReadableForm(stack.stackSize); - if (animation.containsKey(stack) && animation.get(stack) < 1) { - final float currentScale = animation.get(stack) + SCALE_SPEED; - animation.put(stack, currentScale); - drawPoppingItem(rect, stack, stackSize, currentScale); - } else { - GuiContainerManager.drawItem(rect.x + 1, rect.y + 1, stack, true, stackSize); - if (meta.recipeId != null && !meta.ingredient && NEIClientConfig.showRecipeMarker()) { - drawRecipeMarker(rect.x, rect.y, GuiContainerManager.getFontRenderer(stack)); + + if (LayoutManager.bookmarkPanel.sortableItem == null + || LayoutManager.bookmarkPanel.sortableItem.items.get(0) != this.realItems.get(idx)) { + ItemStack realStack = this.realItems.get(idx); + final ItemStackMetadata meta = this.getMetadata(idx); + final BookmarkGroup groupMeta = this.groups.get(meta.groupId); + ItemStack drawStack = realStack; + + if (groupMeta.crafting != null && meta.groupId == this.focusedGroupId) { + + if (groupMeta.crafting.inputs.containsKey(drawStack)) { + drawStack = groupMeta.crafting.inputs.get(drawStack); + } else if (groupMeta.crafting.outputs.containsKey(drawStack)) { + drawStack = groupMeta.crafting.outputs.get(drawStack); + } else if (groupMeta.crafting.intermediate.containsKey(drawStack)) { + drawStack = groupMeta.crafting.intermediate.get(drawStack); } + + } else if (groupMeta.crafting != null && groupMeta.crafting.calculatedItems.containsKey(drawStack)) { + drawStack = groupMeta.crafting.calculatedItems.get(drawStack); } + + String stackSize = meta.fluidDisplay || drawStack.stackSize == 0 ? "" + : ReadableNumberConverter.INSTANCE.toWideReadableForm(drawStack.stackSize); + + if (this.animation.containsKey(realStack) && this.animation.get(realStack) < 1) { + final float currentScale = this.animation.get(realStack) + SCALE_SPEED; + + if (currentScale >= 1) { + this.animation.remove(realStack); + } else { + this.animation.put(realStack, currentScale); + } + + drawPoppingItem(rect, drawStack, stackSize, currentScale); + } else { + GuiContainerManager.drawItem(rect.x + 1, rect.y + 1, drawStack, true, stackSize); + } + } + } protected void drawPoppingItem(Rectangle4i rect, ItemStack stack, String stackSize, float currentScale) { @@ -379,7 +934,7 @@ protected void drawPoppingItem(Rectangle4i rect, ItemStack stack, String stackSi GL11.glScalef(1 / currentScale, 1 / currentScale, 1 / currentScale); } - protected void drawRecipeMarker(int offsetX, int offsetY, FontRenderer fontRenderer) { + protected void drawRecipeMarker(int offsetX, int offsetY, FontRenderer fontRenderer, String text, int color) { final float scaleFactor = fontRenderer.getUnicodeFlag() ? 0.85f : 0.5f; final float inverseScaleFactor = 1.0f / scaleFactor; @@ -388,11 +943,133 @@ protected void drawRecipeMarker(int offsetX, int offsetY, FontRenderer fontRende final int X = (int) (((float) offsetX + 1.0f) * inverseScaleFactor); final int Y = (int) (((float) offsetY + 1.0f) * inverseScaleFactor); - fontRenderer.drawStringWithShadow("R", X, Y, 0xA0A0A0); + fontRenderer.drawStringWithShadow(text, X, Y, color); GL11.glScaled(inverseScaleFactor, inverseScaleFactor, inverseScaleFactor); GuiContainerManager.enable3DRender(); } + + public void update() { + + if (this.recipeTooltipRenderer != null) { + + if (this.recipeTooltipRenderer.createRecipeGui != null) { + this.recipeTooltipRenderer.createRecipeGui.run(); + this.recipeTooltipRenderer.createRecipeGui = null; + } + + if (this.recipeTooltipRenderer.gui != null) { + this.recipeTooltipRenderer.gui.updateAsTooltip(); + } + + } + + } + + public void postDrawTooltips(int mousex, int mousey, List tooltip) { + if (NEIClientConfig.getRecipeTooltipsMode() != 0) { + try { + drawRecipeTooltip(mousex, mousey, tooltip); + } catch (Exception e) { + NEIClientConfig.logger.warn("Cannot draw recipe tooltip", e); + } + } + } + + private void drawRecipeTooltip(int mousex, int mousey, List itemTooltip) { + if (!NEIClientConfig.isLoaded()) { + return; + } + + ItemPanelSlot focused = getSlotMouseOver(mousex, mousey); + + if (focused == null) { + this.recipeTooltipRenderer = null; + return; + } + + final ItemStackMetadata meta = this.metadata.get(focused.slotIndex); + final int tooltipMode = NEIClientConfig.getRecipeTooltipsMode(); + + if (meta.recipeId == null || meta.ingredient) { + this.recipeTooltipRenderer = null; + return; + } + + if (this.groups.get(meta.groupId).viewMode == BookmarkViewMode.DEFAULT && tooltipMode != 1 + && tooltipMode != 3) { + this.recipeTooltipRenderer = null; + return; + } + + if (this.groups.get(meta.groupId).viewMode == BookmarkViewMode.TODO_LIST && tooltipMode != 2 + && tooltipMode != 3) { + this.recipeTooltipRenderer = null; + return; + } + + if (this.recipeTooltipRenderer == null || this.recipeTooltipRenderer.slotIndex != focused.slotIndex) { + + this.recipeTooltipRenderer = new RecipeTooltipRenderer(); + this.recipeTooltipRenderer.slotIndex = focused.slotIndex; + + this.recipeTooltipRenderer.createRecipeGui = () -> { + final int slotIndex = this.recipeTooltipRenderer.slotIndex; + final BookmarkRecipeId recipeId = this.metadata.get(slotIndex).recipeId; + GuiRecipe gui = GuiCraftingRecipe + .createRecipeGui("recipeId", false, this.realItems.get(slotIndex), recipeId); + + if (gui != null) { + gui.initGui(); + gui.guiTop = 0; + gui.guiLeft = 0; + } + + this.recipeTooltipRenderer.gui = gui; + }; + } + + if (this.recipeTooltipRenderer.gui != null) { + final Minecraft mc = Minecraft.getMinecraft(); + int recipeTooltipLines = Math.max(1, itemTooltip.size()); + + GL11.glPushMatrix(); + final float tooltipYOffset; + if (mousey - marginTop > height / 2) { + tooltipYOffset = mousey - this.recipeTooltipRenderer.gui.getHeightAsWidget() + 8; + } else { + tooltipYOffset = mousey + ((recipeTooltipLines < 2) ? 1 : 3 + ((recipeTooltipLines - 1) * 10)); + } + GL11.glTranslatef(mousex, tooltipYOffset, 500); + GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS); + RenderHelper.disableStandardItemLighting(); + this.recipeTooltipRenderer.gui.drawGuiContainerBackgroundLayer(0.0f, -100, -100); + GL11.glPopAttrib(); + + if (this.recipeTooltipRenderer.gui.slotcontainer != null) { + GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS); + RenderHelper.enableGUIStandardItemLighting(); + GL11.glEnable(GL12.GL_RESCALE_NORMAL); + @SuppressWarnings("unchecked") + List slots = (List) this.recipeTooltipRenderer.gui.slotcontainer.inventorySlots; + + for (Slot slot : slots) { + if (slot != null && slot.getStack() != null) { + GuiContainerManager.drawItem(slot.xDisplayPosition, slot.yDisplayPosition, slot.getStack()); + } + } + + GL11.glPopAttrib(); + } + this.recipeTooltipRenderer.gui.drawGuiContainerForegroundLayer(-100, -100); + for (GuiButton btn : this.recipeTooltipRenderer.gui.getOverlayButtons()) { + btn.drawButton(mc, -100, -100); + } + GL11.glPopMatrix(); + } + + } + } public BookmarkPanel() { @@ -409,7 +1086,7 @@ public void init() { public boolean onButtonPress(boolean rightclick) { - if (rightclick) { + if (inEditingState() || rightclick) { return false; } @@ -425,7 +1102,7 @@ public String getRenderLabel() { namespaceNext = new Button("Next") { public boolean onButtonPress(boolean rightclick) { - if (rightclick) { + if (inEditingState() || rightclick) { return false; } @@ -454,18 +1131,22 @@ public String getRenderLabel() { }; namespaces.add(new BookmarkGrid()); - grid = namespaces.get(activeNamespaceIndex); + setNamespace(activeNamespaceIndex); } @Override public String getLabelText() { - int size = grid.size(); - if (((BookmarkGrid) grid).getViewMode() == BookmarkViewMode.TODO_LIST) { - size = ((BookmarkGrid) grid).todoItemsCount; + if (((BookmarkGrid) grid).getCraftingMode(BookmarkGrid.DEFAULT_GROUP_ID)) { + return String.format("§2[§r%d/%d§2]§r", getPage(), Math.max(1, getNumPages())); } - return String.format("(%d/%d) [%d]", getPage(), Math.max(1, getNumPages()), size); + return String.format("%d/%d", getPage(), Math.max(1, getNumPages())); + } + + public boolean inEditingState() { + return this.sortableItem != null || this.draggedStack != null + || this.groupingItem != null && this.groupingItem.rowIndexB != -1; } public void addItem(ItemStack itemStack) { @@ -475,14 +1156,12 @@ public void addItem(ItemStack itemStack) { public void addItem(ItemStack itemStack, boolean saveStackSize) { final BookmarkGrid BGrid = (BookmarkGrid) grid; int idx = BGrid.indexOf(itemStack, true); + if (idx != -1) { BGrid.removeItem(idx); } - addOrRemoveItem(itemStack, null, null, false, saveStackSize); - } - public int getNameSpaceSize() { - return namespaces.size(); + addOrRemoveItem(itemStack, null, null, false, saveStackSize); } public void addOrRemoveItem(ItemStack stackA) { @@ -501,7 +1180,7 @@ public void addOrRemoveItem(ItemStack stackover, final String handlerName, final BGrid.removeRecipe(slot.slotIndex, saveIngredients); } else { final NBTTagCompound nbTagA = StackInfo.itemStackToNBT(stackover, saveStackSize); - final ItemStack normalizedA = StackInfo.loadFromNBT(nbTagA); + final ItemStack normalizedA = StackInfo.loadFromNBT(nbTagA, saveStackSize ? nbTagA.getInteger("Count") : 0); BookmarkRecipeId recipeId = null; if (handlerName != "" && ingredients != null) { @@ -515,41 +1194,45 @@ public void addOrRemoveItem(ItemStack stackover, final String handlerName, final } else { if (saveIngredients && handlerName != "" && ingredients != null) { - final Map unique = new HashMap<>(); - final ArrayList sorted = new ArrayList<>(); + final Map ingredientCount = new HashMap<>(); + final Map uniqueIngredients = new LinkedHashMap<>(); - BGrid.removeRecipe(recipeId); + BGrid.removeRecipe(recipeId, BookmarkGrid.DEFAULT_GROUP_ID); for (PositionedStack stack : ingredients) { final NBTTagCompound nbTag = StackInfo.itemStackToNBT(stack.item, saveStackSize); + final String GUID = StackInfo.getItemStackGUID(stack.item); - if (unique.get(nbTag) == null) { - unique.put(nbTag, 1); - sorted.add(nbTag); + if (!uniqueIngredients.containsKey(GUID)) { + ingredientCount.put(GUID, nbTag.getInteger("Count")); + uniqueIngredients.put(GUID, nbTag); } else { - unique.put(nbTag, unique.get(nbTag) + 1); + ingredientCount.put(GUID, ingredientCount.get(GUID) + nbTag.getInteger("Count")); } + } - for (NBTTagCompound nbTag : sorted) { - final int count = nbTag.getInteger("Count") * unique.get(nbTag); - nbTag.setInteger("Count", count); + for (String GUID : uniqueIngredients.keySet()) { BGrid.addItem( - StackInfo.loadFromNBT(nbTag), - new BookmarkStackMeta( + StackInfo.loadFromNBT( + uniqueIngredients.get(GUID), + saveStackSize ? ingredientCount.get(GUID) : 0), + new ItemStackMetadata( recipeId != null ? recipeId.copy() : null, - (saveStackSize ? 1 : -1) * count, + ingredientCount.get(GUID), true, - nbTag.hasKey("gtFluidName"))); + BookmarkGrid.DEFAULT_GROUP_ID, + uniqueIngredients.get(GUID).hasKey("gtFluidName"))); } } BGrid.addItem( normalizedA, - new BookmarkStackMeta( + new ItemStackMetadata( recipeId, - (saveStackSize ? 1 : -1) * nbTagA.getInteger("Count"), + nbTagA.getInteger("Count"), false, + BookmarkGrid.DEFAULT_GROUP_ID, nbTagA.hasKey("gtFluidName"))); } } @@ -559,20 +1242,23 @@ public void addOrRemoveItem(ItemStack stackover, final String handlerName, final } public BookmarkRecipeId getBookmarkRecipeId(int slotIndex) { - return ((BookmarkGrid) grid).getRecipeId(slotIndex); + ItemStackMetadata meta = ((BookmarkGrid) grid).getMetadata(slotIndex); + return meta.ingredient ? null : meta.recipeId; } public BookmarkRecipeId getBookmarkRecipeId(ItemStack stackA) { - BookmarkRecipeId recipeId = ((BookmarkGrid) grid).findRecipeId(stackA); + final BookmarkGrid BGrid = (BookmarkGrid) grid; + BookmarkRecipeId recipeId = null; - if (recipeId == null) { - for (int idx = 0; idx < getNameSpaceSize() && recipeId == null; idx++) { - if (idx == activeNamespaceIndex) continue; - recipeId = namespaces.get(idx).findRecipeId(stackA); + for (int idx = 0; idx < BGrid.realItems.size(); idx++) { + if (StackInfo.equalItemAndNBT(stackA, BGrid.realItems.get(idx), true)) { + if ((recipeId = getBookmarkRecipeId(idx)) != null) { + return recipeId; + } } } - return recipeId; + return null; } protected String getNamespaceLabelText(boolean shortFormat) { @@ -596,7 +1282,7 @@ protected boolean removeEmptyNamespaces() { if (activeNamespaceIndex != getNameSpaceSize() - 1 && grid.size() == 0) { namespaces.remove(activeNamespaceIndex); - grid = namespaces.get(activeNamespaceIndex); + setNamespace(activeNamespaceIndex); return true; } @@ -604,7 +1290,7 @@ protected boolean removeEmptyNamespaces() { } protected boolean prevNamespace() { - if (!bookmarksIsLoaded) { + if (bookmarksState != BookmarkLoadingState.LOADED) { return false; } @@ -612,18 +1298,16 @@ protected boolean prevNamespace() { removeEmptyNamespaces(); if (activeNamespaceIndex == 0) { - activeNamespaceIndex = getNameSpaceSize() - 1; + setNamespace(getNameSpaceSize() - 1); } else { - activeNamespaceIndex--; + setNamespace(activeNamespaceIndex - 1); } - grid = namespaces.get(activeNamespaceIndex); - return true; } protected boolean nextNamespace() { - if (!bookmarksIsLoaded) { + if (bookmarksState != BookmarkLoadingState.LOADED) { return false; } @@ -632,14 +1316,28 @@ protected boolean nextNamespace() { } if (activeNamespaceIndex == fixCountOfNamespaces() - 1) { - activeNamespaceIndex = 0; + setNamespace(0); } else { - activeNamespaceIndex++; + setNamespace(activeNamespaceIndex + 1); } + return true; + } + + protected void setNamespace(int namespaceIndex) { + activeNamespaceIndex = namespaceIndex; grid = namespaces.get(activeNamespaceIndex); - return true; + if (grid.size() == 0 && activeNamespaceIndex > 0) { + ((BookmarkGrid) grid).setViewMode( + BookmarkGrid.DEFAULT_GROUP_ID, + namespaces.get(activeNamespaceIndex - 1).getViewMode(BookmarkGrid.DEFAULT_GROUP_ID)); + } + + } + + public int getNameSpaceSize() { + return namespaces.size(); } public void setBookmarkFile(String worldPath) { @@ -650,6 +1348,10 @@ public void setBookmarkFile(String worldPath) { dir.mkdirs(); } + if (bookmarksState == BookmarkLoadingState.LOADED) { + saveBookmarks(); + } + bookmarkFile = new File(dir, "bookmarks.ini"); if (!bookmarkFile.exists()) { @@ -674,12 +1376,12 @@ public void setBookmarkFile(String worldPath) { } } - bookmarksIsLoaded = false; + bookmarksState = null; } public void saveBookmarks() { - if (bookmarkFile == null) { + if (bookmarkFile == null || bookmarksState != BookmarkLoadingState.LOADED) { return; } @@ -689,23 +1391,37 @@ public void saveBookmarks() { final BookmarkGrid grid = namespaces.get(grpIdx); JsonObject settings = new JsonObject(); - settings.add("viewmode", new JsonPrimitive(grid.getViewMode().toString())); + JsonObject groups = new JsonObject(); settings.add("active", new JsonPrimitive(activeNamespaceIndex == grpIdx)); + + for (int groupId : grid.groups.keySet()) { + BookmarkGroup group = grid.groups.get(groupId); + JsonObject groupJson = new JsonObject(); + groupJson.add("viewmode", new JsonPrimitive(group.viewMode.toString())); + groupJson.add("crafting", new JsonPrimitive(group.crafting != null)); + groups.add(String.valueOf(groupId), groupJson); + } + + settings.add("groups", groups); strings.add("; " + NBTJson.toJson(settings)); for (int idx = 0; idx < grid.size(); idx++) { try { - final NBTTagCompound nbTag = StackInfo.itemStackToNBT(grid.getItem(idx)); + final NBTTagCompound nbTag = StackInfo.itemStackToNBT(grid.realItems.get(idx)); if (nbTag != null) { JsonObject row = new JsonObject(); - BookmarkStackMeta meta = grid.getMetadata(idx); + ItemStackMetadata meta = grid.metadata.get(idx); row.add("item", NBTJson.toJsonObject(nbTag)); row.add("factor", new JsonPrimitive(meta.factor)); row.add("ingredient", new JsonPrimitive(meta.ingredient)); + if (meta.groupId != BookmarkGrid.DEFAULT_GROUP_ID) { + row.add("groupId", new JsonPrimitive(meta.groupId)); + } + if (meta.recipeId != null) { row.add("recipeId", meta.recipeId.toJsonObject()); } @@ -728,13 +1444,14 @@ public void saveBookmarks() { public void loadBookmarksIfNeeded() { - if (bookmarksIsLoaded) { + if (bookmarksState != null || bookmarksState == BookmarkLoadingState.LOADING) { return; } - bookmarksIsLoaded = true; + bookmarksState = BookmarkLoadingState.LOADING; if (bookmarkFile == null || !bookmarkFile.exists()) { + bookmarksState = BookmarkLoadingState.LOADED; return; } @@ -747,11 +1464,11 @@ public void loadBookmarksIfNeeded() { return; } - namespaces.clear(); - namespaces.add(new BookmarkGrid()); - activeNamespaceIndex = -1; - final JsonParser parser = new JsonParser(); + final List namespaces = new ArrayList<>(); + namespaces.add(new BookmarkGrid()); + BookmarkGrid grid = namespaces.get(0); + int groupId = BookmarkGrid.DEFAULT_GROUP_ID; int namespaceIndex = 0; for (String itemStr : itemStrings) { @@ -765,19 +1482,35 @@ public void loadBookmarksIfNeeded() { if (itemStr.startsWith("; ")) { JsonObject settings = parser.parse(itemStr.substring(2)).getAsJsonObject(); - if (namespaces.get(namespaceIndex).size() > 0) { + if (grid.size() > 0) { // do not create empty namespaces - namespaces.add(new BookmarkGrid()); - namespaceIndex++; + grid = new BookmarkGrid(); + namespaces.add(grid); } if (settings.get("viewmode") != null) { - namespaces.get(namespaceIndex) - .setViewMode(BookmarkViewMode.valueOf(settings.get("viewmode").getAsString())); + grid.groups.get(BookmarkGrid.DEFAULT_GROUP_ID).viewMode = BookmarkViewMode + .valueOf(settings.get("viewmode").getAsString()); + } else if (settings.get("groups") != null && settings.get("groups") instanceof JsonObject) { + final JsonObject jsonObject = (JsonObject) settings.get("groups"); + BookmarkGroup group; + JsonObject value; + + for (Map.Entry jsonEntry : jsonObject.entrySet()) { + if (jsonEntry.getValue() instanceof JsonObject) { + value = (JsonObject) jsonEntry.getValue(); + group = new BookmarkGroup( + value.has("viewmode") + ? BookmarkViewMode.valueOf(value.get("viewmode").getAsString()) + : BookmarkViewMode.DEFAULT, + value.has("crafting") ? value.get("crafting").getAsBoolean() : false); + grid.groups.put(Integer.valueOf(jsonEntry.getKey()), group); + } + } } if (settings.get("active") != null && settings.get("active").getAsBoolean()) { - activeNamespaceIndex = namespaceIndex; + namespaceIndex = namespaces.size() - 1; } continue; @@ -800,14 +1533,17 @@ public void loadBookmarksIfNeeded() { ItemStack itemStack = StackInfo.loadFromNBT(itemStackNBT); if (itemStack != null) { - namespaces.get(namespaceIndex).addItem( - itemStack, - new BookmarkStackMeta( + groupId = jsonObject.has("groupId") ? jsonObject.get("groupId").getAsInt() + : BookmarkGrid.DEFAULT_GROUP_ID; + grid.realItems.add(itemStack); + grid.metadata.add( + new ItemStackMetadata( recipeId, - jsonObject.has("factor") ? jsonObject.get("factor").getAsInt() : 0, + jsonObject.has("factor") ? Math.abs(jsonObject.get("factor").getAsInt()) + : (itemStackNBT.hasKey("gtFluidName") ? 144 : 1), jsonObject.has("ingredient") ? jsonObject.get("ingredient").getAsBoolean() : false, - itemStackNBT.hasKey("gtFluidName")), - false); + grid.groups.containsKey(groupId) ? groupId : BookmarkGrid.DEFAULT_GROUP_ID, + itemStackNBT.hasKey("gtFluidName"))); } else { NEIClientConfig.logger.warn( "Failed to load bookmarked ItemStack from json string, the item no longer exists:\n{}", @@ -819,11 +1555,13 @@ public void loadBookmarksIfNeeded() { } } - if (activeNamespaceIndex == -1) { - activeNamespaceIndex = namespaceIndex; + for (BookmarkGrid gr : namespaces) { + gr.onItemsChanged(); } - grid = namespaces.get(activeNamespaceIndex); + this.namespaces = namespaces; + bookmarksState = BookmarkLoadingState.LOADED; + setNamespace(namespaceIndex); } @Override @@ -919,10 +1657,10 @@ protected ItemStack getDraggedStackWithQuantity(int mouseDownSlot) { return null; } - final BookmarkStackMeta meta = ((BookmarkGrid) grid).getMetadata(mouseDownSlot); + final ItemStackMetadata meta = ((BookmarkGrid) grid).getMetadata(mouseDownSlot); int amount = stack.stackSize; - if (meta.factor < 0 && !meta.fluidDisplay) { + if (amount == 0 && !meta.fluidDisplay) { amount = NEIClientConfig.showItemQuantityWidget() ? NEIClientConfig.getItemQuantity() : 0; if (amount == 0) { @@ -939,62 +1677,149 @@ public void mouseDragged(int mousex, int mousey, int button, long heldTime) { if (button == 0 && NEIClientUtils.shiftKey() && mouseDownSlot >= 0) { ItemPanelSlot mouseOverSlot = getSlotMouseOver(mousex, mousey); - if (sortedStackIndex == -1) { - final ItemStack stack = grid.getItem(mouseDownSlot); + if (this.sortableItem == null) { - if (stack != null + if (grid.getItem(mouseDownSlot) != null && (mouseOverSlot == null || mouseOverSlot.slotIndex != mouseDownSlot || heldTime > 250)) { - sortedNamespaceIndex = activeNamespaceIndex; - sortedStackIndex = mouseDownSlot; + final BookmarkGrid BGrid = (BookmarkGrid) grid; + final ItemStackMetadata meta = BGrid.getMetadata(mouseDownSlot); + final List items = new ArrayList<>(); + final List metadata = new ArrayList<>(); + + if (meta.recipeId == null || meta.ingredient + || BGrid.getViewMode(meta.groupId) == BookmarkViewMode.DEFAULT) { + items.add(BGrid.realItems.get(this.mouseDownSlot)); + metadata.add(BGrid.metadata.get(this.mouseDownSlot)); + } else { + + for (int i = 0; i < BGrid.metadata.size(); i++) { + if (BGrid.metadata.get(i).recipeId != null && meta.groupId == BGrid.metadata.get(i).groupId + && meta.recipeId.equals(BGrid.metadata.get(i).recipeId)) { + items.add(BGrid.realItems.get(i)); + metadata.add(BGrid.metadata.get(i)); + } + } + + } + + this.sortableItem = new SortableItem(items, metadata); grid.onGridChanged(); } } else { + final BookmarkGrid BGrid = (BookmarkGrid) grid; + final ItemStackMetadata sortMeta = this.sortableItem.metadata.get(0); + final BookmarkViewMode sortViewMode = BGrid.getViewMode(sortMeta.groupId); + + if (sortViewMode == BookmarkViewMode.TODO_LIST && !sortMeta.ingredient) { + mouseOverSlot = getSlotMouseOver(grid.marginLeft + grid.paddingLeft, mousey); + + if (mouseOverSlot != null) { + float ySlot = (float) (mousey - BGrid.marginTop) / BookmarkGrid.SLOT_SIZE; + int lastRowIndex = BGrid.getLastRowIndex(); + int overRowIndex = (int) ySlot; + int beforeGroupId = overRowIndex > 0 ? BGrid.getRowGroupId(overRowIndex - 1) + : BookmarkGrid.DEFAULT_GROUP_ID; + int afterGroupId = overRowIndex < lastRowIndex ? BGrid.getRowGroupId(overRowIndex + 1) + : BookmarkGrid.DEFAULT_GROUP_ID; + int overGroupId = BGrid.metadata.get(mouseOverSlot.slotIndex).groupId; + ySlot -= overRowIndex; + + if (this.sortableItem.items.indexOf(BGrid.realItems.get(mouseOverSlot.slotIndex)) == -1 + && overGroupId == sortMeta.groupId) { + + if (mouseOverSlot.slotIndex < BGrid.realItems.indexOf(sortableItem.items.get(0))) { + BGrid.moveItem(this.sortableItem, mouseOverSlot.slotIndex, overGroupId, true); + } else { + BGrid.moveItem( + this.sortableItem, + BGrid.getRowItemIndex(overRowIndex, false), + overGroupId, + false); + } - if (sortedNamespaceIndex != activeNamespaceIndex) { - final BookmarkGrid hoverGrid = namespaces.get(activeNamespaceIndex); - final BookmarkGrid sortedGrid = namespaces.get(sortedNamespaceIndex); + } else if (ySlot <= 0.25) { - if (mouseOverSlot != null || getNextSlot(new Point(mousex, mousey)) >= 0) { - final ItemStack stack = sortedGrid.getItem(sortedStackIndex); - final BookmarkStackMeta meta = sortedGrid.getMetadata(sortedStackIndex); + if (BGrid.getViewMode(beforeGroupId) == BookmarkViewMode.TODO_LIST + && !existsRecipeIdInGroupId(beforeGroupId, sortMeta.recipeId)) { + BGrid.moveItem(this.sortableItem, mouseOverSlot.slotIndex, beforeGroupId, true); + } - sortedGrid.removeItem(sortedStackIndex); - hoverGrid.addItem(stack, meta, false); + } else if (ySlot > 0.25 && ySlot <= 0.5) { - sortedNamespaceIndex = activeNamespaceIndex; - sortedStackIndex = hoverGrid.indexOf(stack, meta.recipeId); - } + if (beforeGroupId != afterGroupId && beforeGroupId != BookmarkGrid.DEFAULT_GROUP_ID + && (afterGroupId != BookmarkGrid.DEFAULT_GROUP_ID + || BGrid.getViewMode(overGroupId) == BookmarkViewMode.DEFAULT) + && BGrid.getViewMode(BookmarkGrid.DEFAULT_GROUP_ID) == BookmarkViewMode.TODO_LIST + && !existsRecipeIdInGroupId(BookmarkGrid.DEFAULT_GROUP_ID, sortMeta.recipeId)) { + beforeGroupId = BookmarkGrid.DEFAULT_GROUP_ID; + } - } else if (mouseOverSlot == null || mouseOverSlot.slotIndex != sortedStackIndex) { - final BookmarkGrid sortedGrid = namespaces.get(sortedNamespaceIndex); - - if (mouseOverSlot != null && sortedGrid.getViewMode() == BookmarkViewMode.DEFAULT) { - sortedGrid.moveItem(sortedStackIndex, mouseOverSlot.slotIndex); - sortedStackIndex = mouseOverSlot.slotIndex; - } else if (sortedGrid.getViewMode() == BookmarkViewMode.TODO_LIST) { - final ItemStack stack = sortedGrid.getItem(sortedStackIndex); - final BookmarkStackMeta meta = sortedGrid.getMetadata(sortedStackIndex); - final boolean isIngredient = sortedStackIndex > 0 && meta.recipeId != null - && meta.ingredient - && meta.recipeId.equals(sortedGrid.getRecipeId(sortedStackIndex - 1)); - - if (!isIngredient) { - mouseOverSlot = getSlotMouseOver(grid.marginLeft + grid.paddingLeft, mousey); - - if (mouseOverSlot != null && sortedStackIndex != mouseOverSlot.slotIndex) { - sortedGrid.moveItem(sortedStackIndex, mouseOverSlot.slotIndex); - sortedStackIndex = sortedGrid.indexOf(stack, meta.recipeId); + if (BGrid.getViewMode(beforeGroupId) == BookmarkViewMode.TODO_LIST + && !existsRecipeIdInGroupId(beforeGroupId, sortMeta.recipeId)) { + BGrid.moveItem(this.sortableItem, mouseOverSlot.slotIndex, beforeGroupId, true); } - } else if (mouseOverSlot != null && meta.recipeId != null - && sortedGrid.getMetadata(mouseOverSlot.slotIndex).ingredient - && meta.recipeId.equals(sortedGrid.getRecipeId(mouseOverSlot.slotIndex))) { - sortedGrid.moveItem(sortedStackIndex, mouseOverSlot.slotIndex); - sortedStackIndex = sortedGrid.indexOf(stack, meta.recipeId); - } + } else if (ySlot > 0.5 && ySlot < 0.75) { + + if (beforeGroupId != afterGroupId + && (beforeGroupId != BookmarkGrid.DEFAULT_GROUP_ID + || BGrid.getViewMode(overGroupId) == BookmarkViewMode.DEFAULT) + && afterGroupId != BookmarkGrid.DEFAULT_GROUP_ID + && BGrid.getViewMode(BookmarkGrid.DEFAULT_GROUP_ID) == BookmarkViewMode.TODO_LIST + && !existsRecipeIdInGroupId(BookmarkGrid.DEFAULT_GROUP_ID, sortMeta.recipeId)) { + afterGroupId = BookmarkGrid.DEFAULT_GROUP_ID; + } + + if (BGrid.getViewMode(afterGroupId) == BookmarkViewMode.TODO_LIST + && !existsRecipeIdInGroupId(afterGroupId, sortMeta.recipeId)) { + BGrid.moveItem( + this.sortableItem, + BGrid.getRowItemIndex(overRowIndex, false), + afterGroupId, + false); + } + + } else if (ySlot >= 0.75) { + + if (BGrid.getViewMode(afterGroupId) == BookmarkViewMode.TODO_LIST + && !existsRecipeIdInGroupId(afterGroupId, sortMeta.recipeId)) { + BGrid.moveItem( + this.sortableItem, + BGrid.getRowItemIndex(overRowIndex, false), + afterGroupId, + false); + } + + } + } - } + + } else if (mouseOverSlot != null + && this.sortableItem.items.indexOf(BGrid.realItems.get(mouseOverSlot.slotIndex)) == -1) { + final ItemStackMetadata meta = BGrid.getMetadata(mouseOverSlot.slotIndex); + + if (meta.groupId == sortMeta.groupId) { + + if (sortViewMode == BookmarkViewMode.DEFAULT) { + BGrid.moveItem(this.sortableItem, mouseOverSlot.slotIndex); + } else if (sortViewMode == BookmarkViewMode.TODO_LIST && meta.recipeId != null + && meta.recipeId.equals(sortMeta.recipeId)) { + BGrid.moveItem(this.sortableItem, mouseOverSlot.slotIndex); + } + + } + + } + + } + + return; + } else if (this.groupingItem != null) { + final int overRowIndex = (mousey - grid.marginTop) / BookmarkGrid.SLOT_SIZE; + + if (this.groupingItem.rowIndexB != -1 || overRowIndex != this.groupingItem.rowIndexA || heldTime > 250) { + this.groupingItem.rowIndexB = Math.max(0, Math.min(overRowIndex, grid.getLastRowIndex())); } return; @@ -1003,20 +1828,29 @@ public void mouseDragged(int mousex, int mousey, int button, long heldTime) { super.mouseDragged(mousex, mousey, button, heldTime); } - private int getNextSlot(final Point point) { + private boolean existsRecipeIdInGroupId(int groupId, BookmarkRecipeId recipeId) { + if (recipeId == null) return false; + + for (ItemStackMetadata meta : ((BookmarkGrid) grid).metadata) { + if (meta.groupId == groupId && this.sortableItem.metadata.indexOf(meta) == -1 + && recipeId.equals(meta.recipeId)) { + return true; + } + } + + return false; + } + + private int getNextSlot() { final List mask = grid.getMask(); final int columns = grid.getColumns(); final int perPage = grid.getRows() * columns; - final boolean line = ((BookmarkGrid) grid).getViewMode() == BookmarkViewMode.TODO_LIST; + final boolean line = ((BookmarkGrid) grid).getViewMode(BookmarkGrid.DEFAULT_GROUP_ID) + == BookmarkViewMode.TODO_LIST; for (int i = mask.size(); i < perPage; i++) { if (!grid.isInvalidSlot(i) && (!line || (i % columns) == 0)) { - - if (point == null || grid.getSlotRect(i).contains(point.x, point.y)) { - return i; - } - - return -1; + return i; } } @@ -1026,18 +1860,14 @@ private int getNextSlot(final Point point) { @Override public void postDraw(int mousex, int mousey) { - if (sortedStackIndex != -1) { + if (this.sortableItem != null) { GuiContainerManager.drawItems.zLevel += 100; - GuiContainerManager.drawItem( - mousex - 8, - mousey - 8, - namespaces.get(sortedNamespaceIndex).getItem(sortedStackIndex).copy(), - true); + GuiContainerManager.drawItem(mousex - 8, mousey - 8, this.sortableItem.items.get(0).copy(), true); GuiContainerManager.drawItems.zLevel -= 100; } if (ItemPanels.itemPanel.draggedStack != null && this.contains(mousex, mousey)) { - final int idx = getNextSlot(null); + final int idx = getNextSlot(); if (idx >= 0) { Rectangle4i rect = grid.getSlotRect(idx); @@ -1051,17 +1881,25 @@ public void postDraw(int mousex, int mousey) { @Override public boolean handleClickExt(int mousex, int mousey, int button) { + if (button == 0 || button == 1) { + final int overRowIndex = ((BookmarkGrid) grid).getHoveredRowIndex(true); + + if (overRowIndex != -1) { + this.groupingItem = new GroupingItem(button == 1, overRowIndex); + return true; + } + } + if (new Rectangle4i(pagePrev.x + pagePrev.w, pagePrev.y, pageNext.x - (pagePrev.x + pagePrev.w), pagePrev.h) .contains(mousex, mousey)) { final BookmarkGrid BGrid = (BookmarkGrid) grid; - if (BGrid.getViewMode() == BookmarkViewMode.DEFAULT) { - BGrid.setViewMode(BookmarkViewMode.TODO_LIST); - } else { - BGrid.setViewMode(BookmarkViewMode.DEFAULT); + if (button == 0) { + BGrid.toggleViewMode(BookmarkGrid.DEFAULT_GROUP_ID); + } else if (button == 1) { + BGrid.toggleCraftingMode(BookmarkGrid.DEFAULT_GROUP_ID); } - saveBookmarks(); return true; } else { return super.handleClickExt(mousex, mousey, button); @@ -1085,13 +1923,31 @@ public List handleTooltip(int mx, int my, List tooltip) { @Override public void mouseUp(int mousex, int mousey, int button) { - if (sortedStackIndex != -1) { - draggedStack = null; - sortedNamespaceIndex = -1; - sortedStackIndex = -1; - mouseDownSlot = -1; + + if (this.sortableItem != null) { + this.sortableItem = null; grid.onGridChanged(); /* make sure grid redraws the new item */ saveBookmarks(); + } else if (this.groupingItem != null) { + final BookmarkGrid BGrid = (BookmarkGrid) grid; + + if (this.groupingItem.rowIndexB != -1) { + BGrid.createGroup(this.groupingItem); + } else { + int groupId = BGrid.getRowGroupId(this.groupingItem.rowIndexA); + + if (groupId != BookmarkGrid.DEFAULT_GROUP_ID) { + if (button == 0) { + BGrid.toggleViewMode(groupId); + } else if (button == 1) { + BGrid.toggleCraftingMode(groupId); + } + } + + } + + this.groupingItem = null; + saveBookmarks(); } else { super.mouseUp(mousex, mousey, button); } @@ -1100,7 +1956,7 @@ public void mouseUp(int mousex, int mousey, int button) { @Override public boolean onMouseWheel(int shift, int mousex, int mousey) { - if (new Rectangle4i( + if (!inEditingState() && new Rectangle4i( namespacePrev.x, namespacePrev.y, namespaceNext.x + namespaceNext.w - namespacePrev.x, @@ -1126,26 +1982,38 @@ public boolean onMouseWheel(int shift, int mousex, int mousey) { if (slot != null) { final BookmarkGrid BGrid = (BookmarkGrid) grid; final BookmarkRecipeId recipeId = BGrid.getRecipeId(slot.slotIndex); - final boolean addFullRecipe = NEIClientUtils.shiftKey(); + final HashMap items = new HashMap<>(); + int shiftMultiplier = 1; if (NEIClientUtils.altKey()) { - shift *= slot.item.getMaxStackSize(); + shiftMultiplier = NEIClientConfig.showItemQuantityWidget() ? NEIClientConfig.getItemQuantity() : 0; + if (shiftMultiplier == 0) { + shiftMultiplier = slot.item.getMaxStackSize(); + } } - if (addFullRecipe) { - BookmarkStackMeta iMeta; + if (NEIClientUtils.shiftKey()) { + ItemStackMetadata iMeta; for (int slotIndex = grid.size() - 1; slotIndex >= 0; slotIndex--) { iMeta = BGrid.getMetadata(slotIndex); if (iMeta.recipeId != null && iMeta.recipeId.equals(recipeId) && slotIndex != slot.slotIndex) { - shiftStackSize(slotIndex, shift); + items.put(slotIndex, shiftStackSize(BGrid, slotIndex, shift, shiftMultiplier)); } } + } - shiftStackSize(slot.slotIndex, shift); + items.put(slot.slotIndex, shiftStackSize(BGrid, slot.slotIndex, shift, shiftMultiplier)); + + for (int slotIndex : items.keySet()) { + if (items.get(slotIndex) != null) { + BGrid.realItems.set(slotIndex, items.get(slotIndex)); + } + } + BGrid.onItemsChanged(); saveBookmarks(); return true; } @@ -1154,29 +2022,31 @@ public boolean onMouseWheel(int shift, int mousex, int mousey) { return super.onMouseWheel(shift, mousex, mousey); } - protected void shiftStackSize(int slotIndex, int shift) { - final BookmarkGrid BGrid = (BookmarkGrid) grid; - final NBTTagCompound nbTag = StackInfo.itemStackToNBT(BGrid.getItem(slotIndex), true); - final BookmarkStackMeta meta = BGrid.getMetadata(slotIndex); - final int factor = Math.abs(meta.factor); + private ItemStack shiftStackSize(BookmarkGrid BGrid, int slotIndex, int shift, int shiftMultiplier) { + final NBTTagCompound nbTag = StackInfo.itemStackToNBT(BGrid.getItem(slotIndex)); + final ItemStackMetadata meta = BGrid.getMetadata(slotIndex); - if (nbTag.getInteger("Count") == factor && meta.factor < 0 && shift > 0) { - meta.factor = factor; - } else { - meta.factor = nbTag.getInteger("Count") == factor && shift < 0 ? -1 * factor : factor; - if ((long) nbTag.getInteger("Count") + ((long) factor * shift) <= Integer.MAX_VALUE) { - nbTag.setInteger("Count", Math.max(nbTag.getInteger("Count") + factor * shift, factor)); - BGrid.replaceItem(slotIndex, StackInfo.loadFromNBT(nbTag)); + if (meta.factor > 0) { + final int multiplier = nbTag.getInteger("Count") / meta.factor; + final long count = ((long) (multiplier + shift * shiftMultiplier) / shiftMultiplier) * shiftMultiplier + * meta.factor; + + if (count <= Integer.MAX_VALUE) { + return StackInfo.loadFromNBT(nbTag, Math.max((int) count, 0)); } } + + return null; } public boolean pullBookmarkItems() { IBookmarkContainerHandler containerHandler = BookmarkContainerInfo .getBookmarkContainerHandler(getGuiContainer()); + if (containerHandler == null) { return false; } + containerHandler.pullBookmarkItemsFromContainer(getGuiContainer(), ((BookmarkGrid) grid).realItems); return true; } diff --git a/src/main/java/codechicken/nei/ClientHandler.java b/src/main/java/codechicken/nei/ClientHandler.java index aebd60e0e..3d2236685 100644 --- a/src/main/java/codechicken/nei/ClientHandler.java +++ b/src/main/java/codechicken/nei/ClientHandler.java @@ -28,6 +28,7 @@ import net.minecraft.world.World; import net.minecraftforge.client.event.RenderWorldLastEvent; import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.world.WorldEvent; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; @@ -48,6 +49,8 @@ import cpw.mods.fml.common.eventhandler.SubscribeEvent; import cpw.mods.fml.common.gameevent.TickEvent; import cpw.mods.fml.common.gameevent.TickEvent.Phase; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; public class ClientHandler { @@ -145,6 +148,7 @@ private void updateMagnetMode(World world, EntityPlayerSP player) { public static void preInit() { loadSerialHandlers(); loadHeightHackHandlers(); + loadHiddenHandlers(); ItemInfo.preInit(); StackInfo.loadGuidFilters(); } @@ -197,6 +201,31 @@ public static void loadHeightHackHandlers() { } } + public static void loadHiddenHandlers() { + File file = NEIClientConfig.hiddenHandlersFile; + if (!file.exists()) { + try (FileWriter writer = new FileWriter(file)) { + NEIClientConfig.logger.info("Creating default hidden handlers list {}", file); + URL defaultHeightHackHandlersResource = ClientHandler.class + .getResource("/assets/nei/cfg/hiddenhandlers.cfg"); + if (defaultHeightHackHandlersResource != null) { + IOUtils.copy(defaultHeightHackHandlersResource.openStream(), writer); + } + } catch (IOException e) { + NEIClientConfig.logger.error("Failed to save default hidden handlers list to file {}", file, e); + } + } + + try (FileReader reader = new FileReader(file)) { + NEIClientConfig.logger.info("Loading hidden handlers from file {}", file); + NEIClientConfig.hiddenHandlerRegex = IOUtils.readLines(reader).stream() + .filter((line) -> !line.startsWith("#")).map(Pattern::compile) + .collect(Collectors.toCollection(HashSet::new)); + } catch (IOException e) { + NEIClientConfig.logger.error("Failed to load hidden handlers from file {}", file, e); + } + } + public static void load() { instance = new ClientHandler(); @@ -305,6 +334,15 @@ public void renderLastEvent(RenderWorldLastEvent event) { if (NEIClientConfig.isEnabled()) WorldOverlayRenderer.render(event.partialTicks); } + @SideOnly(Side.CLIENT) + @SubscribeEvent + public void onWorldUnload(WorldEvent.Unload event) { + Minecraft mc = Minecraft.getMinecraft(); + if (event.world == mc.theWorld) { + NEIClientConfig.unloadWorld(); + } + } + public void loadWorld(World world, boolean fromServer) { if (world != lastworld) { SMPmagneticItems.clear(); diff --git a/src/main/java/codechicken/nei/ContainerPotionCreator.java b/src/main/java/codechicken/nei/ContainerPotionCreator.java index 3f7afd0b0..32a98bea3 100644 --- a/src/main/java/codechicken/nei/ContainerPotionCreator.java +++ b/src/main/java/codechicken/nei/ContainerPotionCreator.java @@ -81,7 +81,6 @@ public InventoryPotionStore() { public void markDirty() { super.markDirty(); NEIClientConfig.global.nbt.setTag("potionStore", tag); - NEIClientConfig.global.saveNBT(); } } diff --git a/src/main/java/codechicken/nei/GuiEnchantmentModifier.java b/src/main/java/codechicken/nei/GuiEnchantmentModifier.java index cf9c429f0..6a03c0196 100644 --- a/src/main/java/codechicken/nei/GuiEnchantmentModifier.java +++ b/src/main/java/codechicken/nei/GuiEnchantmentModifier.java @@ -62,7 +62,6 @@ public static boolean validateEnchantments() { public static void toggleEnchantmentValidation() { NEIClientConfig.world.nbt.setBoolean("validateenchantments", !validateEnchantments()); - NEIClientConfig.world.saveNBT(); } protected void actionPerformed(GuiButton guibutton) { diff --git a/src/main/java/codechicken/nei/ItemList.java b/src/main/java/codechicken/nei/ItemList.java index 8ca7c97af..0fb9a667e 100644 --- a/src/main/java/codechicken/nei/ItemList.java +++ b/src/main/java/codechicken/nei/ItemList.java @@ -88,6 +88,12 @@ public AllMultiItemFilter() { this(new LinkedList<>()); } + public AllMultiItemFilter(ItemFilter a, ItemFilter b) { + this.filters = new ArrayList<>(); + this.filters.add(a); + this.filters.add(b); + } + @Override public boolean matches(ItemStack item) { for (ItemFilter filter : filters) try { @@ -254,7 +260,7 @@ public void execute() { try { filtered = ItemList.forkJoinPool.submit( - () -> items.parallelStream().filter(PresetsWidget::matches).filter(filter::matches) + () -> items.parallelStream().filter(filter::matches) .collect(Collectors.toCollection(ArrayList::new))) .get(); } catch (InterruptedException | ExecutionException e) { diff --git a/src/main/java/codechicken/nei/ItemPanel.java b/src/main/java/codechicken/nei/ItemPanel.java index 255cc9ed0..c66296586 100644 --- a/src/main/java/codechicken/nei/ItemPanel.java +++ b/src/main/java/codechicken/nei/ItemPanel.java @@ -64,18 +64,18 @@ public void refresh(GuiContainer gui) { if (newItems != null) { realItems = newItems; newItems = null; - onGridChanged(); + onItemsChanged(); } super.refresh(gui); } @Override - protected void drawSlotOutline(@Nullable ItemPanelSlot focused, int slotIdx, Rectangle4i rect) { + protected void beforeDrawSlot(@Nullable ItemPanelSlot focused, int slotIdx, Rectangle4i rect) { if (PresetsWidget.inEditMode()) { if (!PresetsWidget.isHidden(getItem(slotIdx))) drawRect(rect.x, rect.y, rect.w, rect.h, 0xee555555); } else { - super.drawSlotOutline(focused, slotIdx, rect); + super.beforeDrawSlot(focused, slotIdx, rect); } } @@ -142,7 +142,7 @@ public void scroll(int i) { } public String getLabelText() { - return String.format("(%d/%d)", getPage(), Math.max(1, getNumPages())); + return String.format("%d/%d", getPage(), Math.max(1, getNumPages())); } protected String getPositioningSettingName() { diff --git a/src/main/java/codechicken/nei/ItemStackSet.java b/src/main/java/codechicken/nei/ItemStackSet.java index 31c0543c3..12b9056c1 100644 --- a/src/main/java/codechicken/nei/ItemStackSet.java +++ b/src/main/java/codechicken/nei/ItemStackSet.java @@ -1,5 +1,7 @@ package codechicken.nei; +import java.util.List; + import net.minecraft.block.Block; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; @@ -27,6 +29,11 @@ public void add(ItemStack item) { put(item, item); } + public ItemStackSet addAll(List items) { + for (ItemStack item : items) add(item); + return this; + } + public boolean contains(ItemStack item) { return get(item) != null; } diff --git a/src/main/java/codechicken/nei/ItemsGrid.java b/src/main/java/codechicken/nei/ItemsGrid.java index 9459b59da..01e0b4796 100644 --- a/src/main/java/codechicken/nei/ItemsGrid.java +++ b/src/main/java/codechicken/nei/ItemsGrid.java @@ -1,37 +1,27 @@ package codechicken.nei; import static codechicken.lib.gui.GuiDraw.drawRect; -import static codechicken.lib.gui.GuiDraw.getMousePosition; -import java.awt.Point; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import javax.annotation.Nullable; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.ScaledResolution; import net.minecraft.client.gui.inventory.GuiContainer; import net.minecraft.client.renderer.OpenGlHelper; -import net.minecraft.client.renderer.RenderHelper; import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.shader.Framebuffer; -import net.minecraft.inventory.Slot; import net.minecraft.item.ItemStack; import org.lwjgl.opengl.GL11; -import org.lwjgl.opengl.GL12; import codechicken.lib.vec.Rectangle4i; import codechicken.nei.ItemPanel.ItemPanelSlot; import codechicken.nei.api.GuiInfo; import codechicken.nei.guihook.GuiContainerManager; -import codechicken.nei.recipe.BookmarkRecipeId; -import codechicken.nei.recipe.GuiCraftingRecipe; -import codechicken.nei.recipe.GuiRecipe; import codechicken.nei.recipe.StackInfo; public class ItemsGrid { @@ -53,7 +43,7 @@ public class ItemsGrid { protected int rows; protected int columns; - protected boolean showRecipeTooltips = false; + protected List gridMask = null; protected boolean[] invalidSlotMap; @@ -79,7 +69,7 @@ public int size() { public int indexOf(ItemStack stackA, boolean useNBT) { for (int idx = 0; idx < realItems.size(); idx++) { - if (StackInfo.equalItemAndNBT(stackA, getItem(idx), useNBT)) { + if (StackInfo.equalItemAndNBT(stackA, realItems.get(idx), useNBT)) { return idx; } } @@ -116,6 +106,10 @@ public int getColumns() { return columns; } + public int getLastRowIndex() { + return this.columns > 0 ? (int) Math.ceil((float) this.getMask().size() / this.columns) - 1 : 0; + } + public void setGridSize(int mleft, int mtop, int w, int h) { marginLeft = mleft; marginTop = mtop; @@ -159,10 +153,11 @@ public void shiftPage(int shift) { protected void onGridChanged() { refreshBuffer = true; + this.gridMask = null; } - public void setShowRecipeTooltips(boolean show) { - this.showRecipeTooltips = show; + protected void onItemsChanged() { + onGridChanged(); } public void refresh(GuiContainer gui) { @@ -198,9 +193,9 @@ private void checkGuiOverlap(GuiContainer gui, int start, int end, int dir) { for (int r = 0; r < rows; r++) { final int idx = columns * r + c; - if (GuiInfo.hideItemPanelSlot(gui, getSlotRect(r, c)) && idx >= 0 - && idx < invalidSlotMap.length - && !invalidSlotMap[idx]) { + if (idx >= 0 && idx < invalidSlotMap.length + && !invalidSlotMap[idx] + && GuiInfo.hideItemPanelSlot(gui, getSlotRect(r, c))) { invalidSlotMap[idx] = true; validColumn = false; perPage--; @@ -238,154 +233,51 @@ public boolean isInvalidSlot(int index) { } protected List getMask() { - List mask = new ArrayList<>(); - int idx = page * perPage; - for (int i = 0; i < rows * columns && idx < size(); i++) { - mask.add(isInvalidSlot(i) ? null : idx++); - } + if (this.gridMask == null) { + this.gridMask = new ArrayList<>(); + int idx = page * perPage; - return mask; - } - - private void drawSlotOutlines(int mousex, int mousey) { - ItemPanelSlot focused = getSlotMouseOver(mousex, mousey); - final List mask = getMask(); - - for (int i = 0; i < mask.size(); i++) { - if (mask.get(i) != null) { - drawSlotOutline(focused, mask.get(i), getSlotRect(i)); + for (int i = 0; i < rows * columns && idx < size(); i++) { + this.gridMask.add(isInvalidSlot(i) ? null : idx++); } - } - } - - private int recipeTooltipSlotIdx = -1; - private int recipeTooltipLines = 2; - private Runnable recipeTooltipUpdater = null; - private GuiRecipe recipeTooltipGui = null; - public void update() { - if (recipeTooltipUpdater != null) { - recipeTooltipUpdater.run(); - recipeTooltipUpdater = null; } - if (recipeTooltipGui != null) { - recipeTooltipGui.updateAsTooltip(); - } - } - private static List listOrEmptyList(final List listOrNull) { - return listOrNull == null ? Collections.emptyList() : listOrNull; + return this.gridMask; } - private void drawRecipeTooltip(int mousex, int mousey, List itemTooltip) { - if (!NEIClientConfig.isLoaded()) { - return; - } + private void beforeDrawItems(int mousex, int mousey, @Nullable ItemPanelSlot focused) { - final Minecraft mc = Minecraft.getMinecraft(); - ItemPanelSlot focused = getSlotMouseOver(mousex, mousey); - if (focused == null) { - recipeTooltipSlotIdx = -1; - recipeTooltipGui = null; - return; - } final List mask = getMask(); - int slotIdx = -1; for (int i = 0; i < mask.size(); i++) { - if (mask.get(i) == null) { - continue; - } - if (focused.slotIndex != mask.get(i)) { - continue; + if (mask.get(i) != null) { + beforeDrawSlot(focused, mask.get(i), getSlotRect(i)); } - slotIdx = i; - break; - } - if (slotIdx == -1) { - recipeTooltipSlotIdx = -1; - recipeTooltipGui = null; - return; - } - - final Point mouseover = getMousePosition(); - final ItemPanelSlot panelSlot = ItemPanels.bookmarkPanel.getSlotMouseOver(mouseover.x, mouseover.y); - final BookmarkRecipeId recipeId; - if (panelSlot != null) { - recipeId = ItemPanels.bookmarkPanel.getBookmarkRecipeId(panelSlot.slotIndex); - } else { - recipeId = ItemPanels.bookmarkPanel.getBookmarkRecipeId(focused.item); - } - if (recipeId == null) { - return; - } - - if (slotIdx != recipeTooltipSlotIdx) { - recipeTooltipSlotIdx = slotIdx; - recipeTooltipGui = null; - recipeTooltipUpdater = () -> { - recipeTooltipGui = GuiCraftingRecipe.createRecipeGui("item", false, false, false, focused.item); - if (recipeTooltipGui != null) { - recipeTooltipGui.limitToOneRecipe(); - recipeTooltipGui.initGui(true); - recipeTooltipGui.guiTop = 0; - recipeTooltipGui.guiLeft = 0; - } - recipeTooltipLines = Math.max(1, itemTooltip.size()); - }; - } - if (recipeTooltipGui == null) { - return; } + } - GL11.glPushMatrix(); - final float tooltipYOffset; - if (mousey - marginTop > height / 2) { - tooltipYOffset = mousey - recipeTooltipGui.getHeightAsWidget() + 8; - } else { - tooltipYOffset = mousey + ((recipeTooltipLines < 2) ? 1 : 3 + ((recipeTooltipLines - 1) * 10)); - } - GL11.glTranslatef(mousex, tooltipYOffset, 500); + private void afterDrawItems(int mousex, int mousey, @Nullable ItemPanelSlot focused) { + final List mask = getMask(); - final GuiContainer gui; - if (mc.currentScreen instanceof GuiRecipe) { - gui = ((GuiRecipe) mc.currentScreen).firstGui; - } else if (mc.currentScreen instanceof GuiContainer) { - gui = (GuiContainer) mc.currentScreen; - } else { - gui = null; - } - GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS); - RenderHelper.disableStandardItemLighting(); - recipeTooltipGui.drawGuiContainerBackgroundLayer(0.0f, -100, -100); - GL11.glPopAttrib(); - if (recipeTooltipGui.slotcontainer != null) { - GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS); - RenderHelper.enableGUIStandardItemLighting(); - GL11.glEnable(GL12.GL_RESCALE_NORMAL); - @SuppressWarnings("unchecked") - List slots = (List) recipeTooltipGui.slotcontainer.inventorySlots; - for (Slot slot : slots) { - if (slot != null && slot.getStack() != null) { - GuiContainerManager.drawItem(slot.xDisplayPosition, slot.yDisplayPosition, slot.getStack()); - } + for (int i = 0; i < mask.size(); i++) { + if (mask.get(i) != null) { + afterDrawSlot(focused, mask.get(i), getSlotRect(i)); } - GL11.glPopAttrib(); - } - recipeTooltipGui.drawGuiContainerForegroundLayer(-100, -100); - for (GuiButton btn : recipeTooltipGui.getOverlayButtons()) { - btn.drawButton(mc, -100, -100); } - GL11.glPopMatrix(); } - protected void drawSlotOutline(@Nullable ItemPanelSlot focused, int slotIdx, Rectangle4i rect) { + public void update() {} + + protected void beforeDrawSlot(@Nullable ItemPanelSlot focused, int slotIdx, Rectangle4i rect) { if (focused != null && focused.slotIndex == slotIdx) { drawRect(rect.x, rect.y, rect.w, rect.h, 0xee555555); // highlight } } + protected void afterDrawSlot(@Nullable ItemPanelSlot focused, int slotIdx, Rectangle4i rect) {} + private void blitExistingBuffer() { Minecraft minecraft = Minecraft.getMinecraft(); GL11.glEnable(GL11.GL_BLEND); @@ -427,12 +319,18 @@ private void drawItems() { GuiContainerManager.disableMatrixStackLogging(); } + protected boolean shouldCacheItemRendering() { + return NEIClientConfig.shouldCacheItemRendering(); + } + public void draw(int mousex, int mousey) { if (getPerPage() == 0) { return; } - if (NEIClientConfig.shouldCacheItemRendering()) { + final ItemPanelSlot focused = getSlotMouseOver(mousex, mousey); + + if (shouldCacheItemRendering()) { if (refreshBuffer) { Minecraft minecraft = Minecraft.getMinecraft(); @@ -457,24 +355,18 @@ public void draw(int mousex, int mousey) { refreshBuffer = false; } - drawSlotOutlines(mousex, mousey); + beforeDrawItems(mousex, mousey, focused); blitExistingBuffer(); } else { - drawSlotOutlines(mousex, mousey); + beforeDrawItems(mousex, mousey, focused); drawItems(); } - } - public void postDrawTooltips(int mousex, int mousey, List tooltip) { - if (NEIClientConfig.showRecipeTooltips() && showRecipeTooltips) { - try { - drawRecipeTooltip(mousex, mousey, tooltip); - } catch (Exception e) { - NEIClientConfig.logger.warn("Cannot draw recipe tooltip", e); - } - } + afterDrawItems(mousex, mousey, focused); } + public void postDrawTooltips(int mousex, int mousey, List tooltip) {} + public void setVisible() { if (getItems().isEmpty() && getMessageOnEmpty() != null) { LayoutManager.addWidget(messageLabel); @@ -510,16 +402,20 @@ public ItemPanelSlot getSlotMouseOver(int mousex, int mousey) { } public boolean contains(int px, int py) { + final Rectangle4i rect = new Rectangle4i( + marginLeft + paddingLeft, + marginTop, + columns * SLOT_SIZE, + rows * SLOT_SIZE); - if (!(new Rectangle4i(marginLeft + paddingLeft, marginTop, columns * SLOT_SIZE, height)).contains(px, py)) { + if (!rect.contains(px, py)) { return false; } final int r = (int) ((py - marginTop) / SLOT_SIZE); final int c = (int) ((px - marginLeft - paddingLeft) / SLOT_SIZE); - final int slt = columns * r + c; - return r >= rows || c >= columns || !isInvalidSlot(slt); + return !isInvalidSlot(columns * r + c); } public String getMessageOnEmpty() { diff --git a/src/main/java/codechicken/nei/LayoutManager.java b/src/main/java/codechicken/nei/LayoutManager.java index a7923d3db..b705650e0 100644 --- a/src/main/java/codechicken/nei/LayoutManager.java +++ b/src/main/java/codechicken/nei/LayoutManager.java @@ -14,7 +14,6 @@ import static codechicken.nei.NEIClientConfig.isHidden; import static codechicken.nei.NEIClientConfig.showIDs; import static codechicken.nei.NEIClientConfig.toggleBooleanSetting; -import static codechicken.nei.NEIClientConfig.world; import static codechicken.nei.NEIClientUtils.cycleGamemode; import static codechicken.nei.NEIClientUtils.decreaseSlotStack; import static codechicken.nei.NEIClientUtils.deleteEverything; @@ -286,7 +285,7 @@ public void postRenderObjects(GuiContainer gui, int mousex, int mousey) { @Override public void postRenderTooltips(GuiContainer gui, int mousex, int mousey, List tooltip) { - if (!isHidden() && isEnabled()) { + if (!isHidden() && isEnabled() && GuiContainerManager.shouldShowTooltip(gui)) { for (Widget widget : drawWidgets) widget.postDrawTooltips(mousex, mousey, tooltip); } } @@ -337,6 +336,8 @@ public static void layout(GuiContainer gui) { if (gui.guiLeft - 4 < 76) visiblity.showWidgets = false; + if (!itemsLoaded) visiblity.showPresetsDropdown = false; + try { GuiInfo.readLock.lock(); GuiInfo.guiHandlers.forEach(handler -> handler.modifyVisiblity(gui, visiblity)); @@ -735,7 +736,7 @@ public void onMouseScrolled(GuiContainer gui, int mousex, int mousey, int scroll @Override public boolean shouldShowTooltip(GuiContainer gui) { - return itemPanel.draggedStack == null && bookmarkPanel.sortedStackIndex == -1; + return itemPanel.draggedStack == null && !bookmarkPanel.inEditingState(); } /** Note: this method isn't actually used by this mod, but NEI add-ons might need it. */ @@ -758,17 +759,21 @@ public void renderSlotUnderlay(GuiContainer gui, Slot slot) { @Override public void renderSlotOverlay(GuiContainer window, Slot slot) { - ItemStack item = slot.getStack(); - if (world.nbt.getBoolean("searchinventories") && (item == null ? !getSearchExpression().equals("") - : !((SearchField) searchField).getFilter().matches(item))) { - GL11.glDisable(GL11.GL_LIGHTING); - GL11.glDisable(GL11.GL_DEPTH_TEST); - GL11.glTranslatef(0, 0, 150); - drawRect(slot.xDisplayPosition, slot.yDisplayPosition, 16, 16, 0x80000000); - GL11.glTranslatef(0, 0, -150); - GL11.glEnable(GL11.GL_LIGHTING); - GL11.glEnable(GL11.GL_DEPTH_TEST); + + if (SearchField.searchInventories()) { + ItemStack item = slot.getStack(); + + if (item == null ? !getSearchExpression().equals("") : !searchField.getFilter().matches(item)) { + GL11.glDisable(GL11.GL_LIGHTING); + GL11.glDisable(GL11.GL_DEPTH_TEST); + GL11.glTranslatef(0, 0, 150); + drawRect(slot.xDisplayPosition, slot.yDisplayPosition, 16, 16, 0x80000000); + GL11.glTranslatef(0, 0, -150); + GL11.glEnable(GL11.GL_LIGHTING); + GL11.glEnable(GL11.GL_DEPTH_TEST); + } } + } public static void drawIcon(int x, int y, Image image) { diff --git a/src/main/java/codechicken/nei/NEIClientConfig.java b/src/main/java/codechicken/nei/NEIClientConfig.java index 2a44c51ff..5257ce0b9 100644 --- a/src/main/java/codechicken/nei/NEIClientConfig.java +++ b/src/main/java/codechicken/nei/NEIClientConfig.java @@ -73,6 +73,7 @@ public class NEIClientConfig { public static final File serialHandlersFile = new File(configDir, "serialhandlers.cfg"); public static final File heightHackHandlersFile = new File(configDir, "heighthackhandlers.cfg"); public static final File handlerOrderingFile = new File(configDir, "handlerordering.csv"); + public static final File hiddenHandlersFile = new File(configDir, "hiddenhandlers.csv"); @Deprecated public static File bookmarkFile; @@ -84,6 +85,10 @@ public class NEIClientConfig { // We use regex here so that we can apply the height hack to entire mods with one entry. public static HashSet heightHackHandlerRegex = new HashSet<>(); + // Set of regexes matching handler Name of handlers that need hide. + // We use regex here so that we can hide to entire mods with one entry. + public static HashSet hiddenHandlerRegex = new HashSet<>(); + // Map of handler ID to sort order. // Handlers will be sorted in ascending order, so smaller numbers show up earlier. // Any handler not in the map will be assigned to 0, and negative numbers are fine. @@ -237,14 +242,14 @@ public boolean onClick(int button) { tag.getTag("inventory.bookmarksEnabled").setComment("Enable/disable bookmarks").getBooleanValue(true); API.addOption(new OptionToggleButton("inventory.bookmarksEnabled", true)); - tag.getTag("inventory.useNBTInBookmarks").setComment("Use NBT in Bookmarks").getBooleanValue(true); - API.addOption(new OptionToggleButton("inventory.useNBTInBookmarks", true)); + tag.getTag("inventory.bookmarksAnimationEnabled").setComment("REI Style Animation in Bookmarks") .getBooleanValue(true); API.addOption(new OptionToggleButton("inventory.bookmarksAnimationEnabled", true)); - tag.getTag("inventory.recipeTooltipsEnabled").setComment("Show recipe tooltips in Bookmarks") - .getBooleanValue(true); - API.addOption(new OptionToggleButton("inventory.recipeTooltipsEnabled", true)); + + tag.getTag("inventory.recipeTooltipsMode").setComment("Show recipe tooltips in Bookmarks").getIntValue(1); + API.addOption(new OptionCycled("inventory.recipeTooltipsMode", 4, true)); + tag.getTag("inventory.showRecipeMarker").setComment("Show Recipe Marker").getBooleanValue(false); API.addOption(new OptionToggleButton("inventory.showRecipeMarker", true)); @@ -256,9 +261,11 @@ public boolean onClick(int button) { tag.getTag("inventory.jei_style_tabs").setComment("Enable/disable JEI Style Tabs").getBooleanValue(true); API.addOption(new OptionToggleButtonBoubs("inventory.jei_style_tabs", true)); + tag.getTag("inventory.jei_style_item_presence_overlay") .setComment("Enable/disable JEI Style item presence overlay on ?-hover").getBooleanValue(true); API.addOption(new OptionToggleButton("inventory.jei_style_item_presence_overlay", true)); + tag.getTag("inventory.jei_style_recipe_catalyst").setComment("Enable/disable JEI Style Recipe Catalysts") .getBooleanValue(true); API.addOption(new OptionToggleButton("inventory.jei_style_recipe_catalyst", true)); @@ -424,6 +431,11 @@ private static void setWorldDefaults() { world.saveNBT(); } + public static void unloadWorld() { + ItemPanels.bookmarkPanel.saveBookmarks(); + world.saveNBT(); + } + public static int getKeyBinding(String string) { return getSetting("keys." + string).getIntValue(Keyboard.KEY_NONE); } @@ -556,16 +568,12 @@ public static boolean isBookmarkPanelHidden() { return !getBooleanSetting("inventory.bookmarksEnabled"); } - public static boolean useNBTInBookmarks() { - return getBooleanSetting("inventory.useNBTInBookmarks"); - } - public static boolean areBookmarksAnimated() { return getBooleanSetting("inventory.bookmarksAnimationEnabled"); } - public static boolean showRecipeTooltips() { - return getBooleanSetting("inventory.recipeTooltipsEnabled"); + public static int getRecipeTooltipsMode() { + return getIntSetting("inventory.recipeTooltipsMode"); } public static boolean showRecipeMarker() { @@ -689,7 +697,6 @@ public static String getSearchExpression() { public static void setSearchExpression(String expression) { world.nbt.setString("search", expression); - world.saveNBT(); } public static boolean isMouseScrollTransferEnabled() { diff --git a/src/main/java/codechicken/nei/NEIClientUtils.java b/src/main/java/codechicken/nei/NEIClientUtils.java index 5af02ff90..e111ccdd8 100644 --- a/src/main/java/codechicken/nei/NEIClientUtils.java +++ b/src/main/java/codechicken/nei/NEIClientUtils.java @@ -394,7 +394,6 @@ public static boolean safeKeyDown(int keyCode) { public static void setItemQuantity(int i) { world.nbt.setInteger("quantity", i); - world.saveNBT(); } public static GuiContainer getGuiContainer() { diff --git a/src/main/java/codechicken/nei/PanelWidget.java b/src/main/java/codechicken/nei/PanelWidget.java index f0a53c1a7..27b9281ce 100644 --- a/src/main/java/codechicken/nei/PanelWidget.java +++ b/src/main/java/codechicken/nei/PanelWidget.java @@ -328,7 +328,7 @@ public void mouseUp(int mousex, int mousey, int button) { ItemPanelSlot hoverSlot = getSlotMouseOver(mousex, mousey); if (hoverSlot != null && hoverSlot.slotIndex == mouseDownSlot && draggedStack == null) { - ItemStack item = hoverSlot.item; + ItemStack item = hoverSlot.item.copy(); if (NEIController.manager.window instanceof GuiRecipe || !NEIClientConfig.canCheatItem(item)) { diff --git a/src/main/java/codechicken/nei/PositionedStack.java b/src/main/java/codechicken/nei/PositionedStack.java index af0b062f1..bcf978eca 100644 --- a/src/main/java/codechicken/nei/PositionedStack.java +++ b/src/main/java/codechicken/nei/PositionedStack.java @@ -26,8 +26,11 @@ public PositionedStack(Object object, int x, int y, boolean genPerms) { relx = x; rely = y; - if (genPerms) generatePermutations(); - else setPermutationToRender(0); + if (genPerms) { + generatePermutations(); + } else { + setPermutationToRender(0); + } } public PositionedStack(Object object, int x, int y) { @@ -77,10 +80,13 @@ public PositionedStack copy() { public void setPermutationToRender(int index) { item = items[index].copy(); - if (item.getItem() == null) item = new ItemStack(Blocks.fire); - else if (item.getItemDamage() == OreDictionary.WILDCARD_VALUE && item.getItem() != null - && item.getItem().isRepairable()) - item.setItemDamage(0); + + if (item.getItem() == null) { + item = new ItemStack(Blocks.fire); + } else if (item.getItemDamage() == OreDictionary.WILDCARD_VALUE && item.getItem() != null + && item.getItem().isRepairable()) { + item.setItemDamage(0); + } } public boolean contains(ItemStack ingredient) { diff --git a/src/main/java/codechicken/nei/PresetsWidget.java b/src/main/java/codechicken/nei/PresetsWidget.java index 55265fd42..7cd3b4e0f 100644 --- a/src/main/java/codechicken/nei/PresetsWidget.java +++ b/src/main/java/codechicken/nei/PresetsWidget.java @@ -6,6 +6,8 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -30,39 +32,55 @@ import codechicken.core.gui.GuiScrollSlot; import codechicken.lib.gui.GuiDraw; import codechicken.lib.vec.Rectangle4i; +import codechicken.nei.ItemList.ItemsLoadedCallback; import codechicken.nei.ItemPanel.ItemPanelSlot; +import codechicken.nei.api.API; +import codechicken.nei.api.IRecipeFilter; +import codechicken.nei.api.IRecipeFilter.IRecipeFilterProvider; import codechicken.nei.api.ItemFilter; +import codechicken.nei.api.ItemFilter.ItemFilterProvider; import codechicken.nei.recipe.GuiRecipe; +import codechicken.nei.recipe.IRecipeHandler; import codechicken.nei.recipe.StackInfo; import codechicken.nei.util.NBTJson; import cpw.mods.fml.common.registry.GameRegistry; -public class PresetsWidget extends Widget { +public class PresetsWidget extends Widget implements ItemFilterProvider, ItemsLoadedCallback, ItemFilter { - public static class PresetTag implements ItemFilter { + protected static enum PresetTagState { + + WHITELIST, + BLACKLIST, + BLACKLIST_PLUS; + + public PresetTagState next() { + if (this == PresetTagState.WHITELIST) return PresetTagState.BLACKLIST; + if (this == PresetTagState.BLACKLIST) return PresetTagState.BLACKLIST_PLUS; + if (this == PresetTagState.BLACKLIST_PLUS) return PresetTagState.WHITELIST; + return this; + } + } + + public static class PresetTag { public String filename = null; public String displayName = ""; - public boolean whitelist = true; - public HashSet items; + public PresetTagState state = PresetTagState.BLACKLIST; + public ItemStackSet items; public PresetTag(String displayName) { - this(displayName, new HashSet<>(), false, null); - } - - public PresetTag(String displayName, HashSet items) { - this(displayName, items, true, null); + this(displayName, null, PresetTagState.BLACKLIST, null); } - public PresetTag(String displayName, HashSet items, boolean whitelist, String filename) { + public PresetTag(String displayName, ItemStackSet items, PresetTagState state, String filename) { this.displayName = displayName; - this.items = items != null ? items : new HashSet<>(); - this.whitelist = whitelist; + this.items = items != null ? items : new ItemStackSet(); + this.state = state; this.filename = filename; } public boolean matches(final ItemStack stack) { - return items.contains(StackInfo.getItemStackGUID(stack)) == whitelist; + return items.contains(stack) == (state == PresetTagState.WHITELIST); } public static PresetTag loadFromFile(File file) { @@ -84,14 +102,18 @@ public static PresetTag loadFromFile(File file) { final JsonParser parser = new JsonParser(); final JsonObject metaObject = parser.parse(itemStrings.remove(0)).getAsJsonObject(); final String displayName = metaObject.get("displayName").getAsString(); - final boolean whitelist = metaObject.get("whitelist").getAsBoolean(); - final HashSet items = new HashSet<>(); - - for (String itemStr : itemStrings) { - items.add(itemStr); + final PresetTagState state = metaObject.has("state") + ? PresetTagState.valueOf(metaObject.get("state").getAsString()) + : PresetTagState.BLACKLIST; + final ItemStackSet items = new ItemStackSet(); + + for (ItemStack stack : ItemList.items) { + if (itemStrings.contains(StackInfo.getItemStackGUID(stack))) { + items.add(stack); + } } - return new PresetTag(displayName, items, whitelist, file.getName()); + return new PresetTag(displayName, items, state, file.getName()); } catch (Throwable th) { NEIClientConfig.logger.error("Failed to load presets ItemStack from file", file); } @@ -127,12 +149,16 @@ public void saveToFile() { final JsonObject row = new JsonObject(); row.add("displayName", new JsonPrimitive(displayName)); - row.add("whitelist", new JsonPrimitive(whitelist)); + row.add("state", new JsonPrimitive(state.toString())); strings.add(NBTJson.toJson(row)); try { - strings.addAll(items); + + for (ItemStack stack : items.values()) { + strings.add(StackInfo.getItemStackGUID(stack)); + } + } catch (JsonSyntaxException e) { NEIClientConfig.logger.error("Failed to stringify presets ItemStack to json string"); } @@ -146,9 +172,8 @@ public void saveToFile() { } } - @SuppressWarnings("unchecked") public PresetTag copy() { - return new PresetTag(displayName, (HashSet) items.clone(), whitelist, null); + return new PresetTag(displayName, new ItemStackSet().addAll(items.values()), state, null); } } @@ -290,7 +315,7 @@ protected void slotClicked(int slot, int button, int mx, int my, int count) { selected.remove(tag); tag.deleteFile(); } else if (direction.contains(mx, my)) { - tag.whitelist = !tag.whitelist; + tag.state = tag.state.next(); tag.saveToFile(); } @@ -299,6 +324,7 @@ protected void slotClicked(int slot, int button, int mx, int my, int count) { PresetsWidget.openListBox = !presets.isEmpty() && (NEIClientUtils.controlKey() || NEIClientUtils.shiftKey() || !option.contains(mx, my)); ItemList.updateFilter.restart(); + PresetsRecipeFilter.blacklist = null; } @Override @@ -319,13 +345,22 @@ protected void drawSlot(int slot, int x, int y, int mx, int my, float frame) { final String displayName = NEIClientUtils .cropText(PresetsWidget.fontRenderer, tag.displayName, option.w - 6); - final String dirName = tag.whitelist ? translate("presets.whitelist.label") - : translate("presets.blacklist.label"); + String stateLabel = translate("presets.blacklist.label"); + + if (tag.state == PresetTagState.WHITELIST) { + stateLabel = translate("presets.whitelist.label"); + } else if (tag.state == PresetTagState.BLACKLIST_PLUS) { + stateLabel = translate("presets.blacklistPlus.label"); + } // blacklist or whitelist LayoutManager.getLayoutStyle() .drawSubsetTag(null, direction.x, direction.y, direction.w, direction.h, directionState, false); - GuiDraw.drawString(dirName, direction.x + 6, direction.y + 5, directionState == 2 ? 0xFFE0E0E0 : 0xFFFFA0); + GuiDraw.drawString( + stateLabel, + direction.x + 6, + direction.y + 5, + directionState == 2 ? 0xFFE0E0E0 : 0xFFFFA0); // option name LayoutManager.getLayoutStyle() @@ -433,6 +468,70 @@ public MouseSelection(int slotIndex, boolean append) { } } + protected static class PresetsRecipeFilter implements IRecipeFilterProvider, IRecipeFilter { + + protected static ItemStackSet blacklist; + + public IRecipeFilter getFilter() { + + if (PresetsWidget.edit != null) { + return null; + } + + if (blacklist == null) { + blacklist = new ItemStackSet(); + + for (PresetTag tag : listbox.getSelected()) { + if (tag.state == PresetTagState.BLACKLIST_PLUS) { + blacklist.addAll(tag.items.values()); + } + } + } + + return blacklist.isEmpty() ? null : this; + } + + @Override + public boolean matches(IRecipeHandler handler, List ingredients, PositionedStack result, + List others) { + + if (matchPositionedStack(ingredients, false)) { + return false; + } + + if (result != null && matchPositionedStack(result)) { + return true; + } + + if (!others.isEmpty() && matchPositionedStack(others, true)) { + return true; + } + + return result == null && others.isEmpty(); + } + + private boolean matchPositionedStack(List items, boolean dir) { + for (PositionedStack pStack : items) { + if (matchPositionedStack(pStack) == dir) { + return true; + } + } + + return false; + } + + private boolean matchPositionedStack(PositionedStack pStack) { + for (ItemStack stack : pStack.items) { + if (!blacklist.contains(stack)) { + return true; + } + } + + return false; + } + + } + protected static final FontRenderer fontRenderer = Minecraft.getMinecraft().fontRenderer; protected static SubsetListBox listbox = new SubsetListBox(); protected static File presetsDir; @@ -517,7 +616,7 @@ public List handleTooltip(int mx, int my, List tooltip) { public boolean onButtonPress(boolean rightclick) { if (!rightclick && edit != null) { - edit.whitelist = !edit.whitelist; + edit.state = edit.state.next(); return true; } @@ -526,20 +625,54 @@ public boolean onButtonPress(boolean rightclick) { @Override public String getRenderLabel() { - return edit != null && edit.whitelist ? translate("presets.whitelist.label") - : translate("presets.blacklist.label"); + + if (edit == null || edit.state == PresetTagState.BLACKLIST) { + return translate("presets.blacklist.label"); + } else if (edit.state == PresetTagState.WHITELIST) { + return translate("presets.whitelist.label"); + } else if (edit.state == PresetTagState.BLACKLIST_PLUS) { + return translate("presets.blacklistPlus.label"); + } + + return translate("presets.blacklist.label"); } @Override public void addTooltips(List tooltip) { if (edit != null) { - tooltip.add( - edit.whitelist ? translate("presets.whitelist.tooltip") - : translate("presets.blacklist.tooltip")); + if (edit.state == PresetTagState.BLACKLIST) { + tooltip.add(translate("presets.blacklist.tooltip")); + } else if (edit.state == PresetTagState.WHITELIST) { + tooltip.add(translate("presets.whitelist.tooltip")); + } else if (edit.state == PresetTagState.BLACKLIST_PLUS) { + tooltip.add(translate("presets.blacklistPlus.tooltip")); + } } } + }; + public void itemsLoaded() { + + if (presetsDir != null) { + listbox = new SubsetListBox(presetsDir); + + edit = null; + openListBox = false; + selectedDisplayName.setText(""); + + ItemList.updateFilter.restart(); + PresetsRecipeFilter.blacklist = null; + } + + } + + public PresetsWidget() { + API.addItemFilter(this); + API.addRecipeFilter(new PresetsRecipeFilter()); + ItemList.loadCallbacks.add(this); + } + protected static void setEditedTag(final PresetTag tag) { edit = tag; openListBox = false; @@ -564,19 +697,15 @@ public static boolean isHidden(final ItemStack item) { if (edit != null) { if (mouseSelection != null && mouseSelection.items.contains(item)) { - return mouseSelection.append != edit.whitelist; + return mouseSelection.append != (edit.state == PresetTagState.WHITELIST); } return !edit.matches(item); } - if (!listbox.getSelected().isEmpty()) { - final List selected = listbox.getSelected(); - - for (int idx = 0; idx < selected.size(); idx++) { - if (!selected.get(idx).matches(item)) { - return true; - } + for (PresetTag tag : listbox.getSelected()) { + if (!tag.matches(item)) { + return true; } } @@ -619,12 +748,10 @@ public static void setHidden(final ItemStack stack, final boolean append) { } protected static void hideItem(final ItemStack stack, boolean append) { - final String guid = StackInfo.getItemStackGUID(stack); - if (append) { - edit.items.add(guid); + edit.items.add(stack); } else { - edit.items.remove(guid); + edit.items.remove(stack); } } @@ -637,24 +764,44 @@ protected static String getModId(final ItemStack stack) { } public static void loadPresets(final String worldPath) { - final File dir = new File(CommonUtils.getMinecraftDir(), "saves/NEI/" + worldPath); presetsDir = new File(CommonUtils.getMinecraftDir(), "saves/NEI/" + worldPath + "/presets"); - if (!dir.exists()) { - dir.mkdirs(); + if (!presetsDir.getParentFile().exists()) { + presetsDir.getParentFile().mkdirs(); } if (!presetsDir.exists()) { presetsDir.mkdirs(); } - listbox = new SubsetListBox(presetsDir); + if (!(new File(presetsDir, "selected.ini")).exists()) { + final File configPresets = new File(NEIClientConfig.configDir, "/presets"); - edit = null; - openListBox = false; - selectedDisplayName.setText(""); + if (configPresets.exists()) { + for (File file : configPresets.listFiles()) { + try { + InputStream src = new FileInputStream(file); + OutputStream dst = new FileOutputStream(new File(presetsDir, file.getName())); + + IOUtils.copy(src, dst); + + src.close(); + dst.close(); + } catch (IOException e) {} + } + } + } + + if (LayoutManager.itemsLoaded) { + listbox = new SubsetListBox(presetsDir); + + edit = null; + openListBox = false; + selectedDisplayName.setText(""); + ItemList.updateFilter.restart(); + PresetsRecipeFilter.blacklist = null; + } - ItemList.updateFilter.restart(); } @Override @@ -903,16 +1050,20 @@ public List handleTooltip(int mx, int my, List tooltip) { return tooltip; } - public static boolean matches(ItemStack stack) { + @Override + public ItemFilter getFilter() { + return this; + } + + @Override + public boolean matches(ItemStack stack) { if (edit != null) { return true; } - final List selected = listbox.getSelected(); - - for (int idx = 0; idx < selected.size(); idx++) { - if (!selected.get(idx).matches(stack)) { + for (PresetTag tag : listbox.getSelected()) { + if (!tag.matches(stack)) { return false; } } diff --git a/src/main/java/codechicken/nei/RecipeSearchField.java b/src/main/java/codechicken/nei/RecipeSearchField.java new file mode 100644 index 000000000..d27362908 --- /dev/null +++ b/src/main/java/codechicken/nei/RecipeSearchField.java @@ -0,0 +1,78 @@ +package codechicken.nei; + +import net.minecraft.util.EnumChatFormatting; + +import codechicken.nei.api.ItemFilter; +import codechicken.nei.util.TextHistory; + +public abstract class RecipeSearchField extends TextField { + + private static final TextHistory history = new TextHistory(); + + public RecipeSearchField(String ident) { + super(ident); + this.field.setVisible(false); + } + + public boolean isVisible() { + return this.field.getVisible(); + } + + public void setVisible(boolean visible) { + this.field.setVisible(visible); + } + + protected abstract boolean noResults(); + + @Override + public int getTextColour() { + if (!text().isEmpty() && !noResults()) { + return focused() ? 0xFFcc3300 : 0xFF993300; + } else { + return focused() ? 0xFFE0E0E0 : 0xFF909090; + } + } + + @Override + public void lastKeyTyped(int keyID, char keyChar) { + + if (focused() && NEIClientConfig.isKeyHashDown("gui.getprevioussearch")) { + handleNavigateHistory(TextHistory.Direction.PREVIOUS); + } + + if (focused() && NEIClientConfig.isKeyHashDown("gui.getnextsearch")) { + handleNavigateHistory(TextHistory.Direction.NEXT); + } + } + + @Override + public String filterText(String s) { + return EnumChatFormatting.getTextWithoutFormattingCodes(s); + } + + public ItemFilter getFilter() { + return SearchField.getFilter(text()); + } + + @Override + public void setFocus(boolean focus) { + final boolean previousFocus = field.isFocused(); + + if (previousFocus != focus) { + history.add(text()); + } + + super.setFocus(focus); + } + + private boolean handleNavigateHistory(TextHistory.Direction direction) { + if (focused()) { + return history.get(direction, text()).map(newText -> { + setText(newText); + return true; + }).orElse(false); + } + return false; + } + +} diff --git a/src/main/java/codechicken/nei/SearchField.java b/src/main/java/codechicken/nei/SearchField.java index c929d40bd..3be9b7500 100644 --- a/src/main/java/codechicken/nei/SearchField.java +++ b/src/main/java/codechicken/nei/SearchField.java @@ -101,7 +101,6 @@ public boolean handleClick(int mousex, int mousey, int button) { if (button == 0) { if (focused() && (System.currentTimeMillis() - lastclicktime < 400)) { // double click NEIClientConfig.world.nbt.setBoolean("searchinventories", !searchInventories()); - NEIClientConfig.world.saveNBT(); } lastclicktime = System.currentTimeMillis(); } @@ -123,11 +122,11 @@ public void lastKeyTyped(int keyID, char keyChar) { if (isVisible() && NEIClientConfig.isKeyHashDown("gui.search")) { setFocus(true); } - if (NEIClientConfig.isKeyHashDown("gui.getprevioussearch") && focused()) { + if (focused() && NEIClientConfig.isKeyHashDown("gui.getprevioussearch")) { handleNavigateHistory(TextHistory.Direction.PREVIOUS); } - if (NEIClientConfig.isKeyHashDown("gui.getnextsearch") && focused()) { + if (focused() && NEIClientConfig.isKeyHashDown("gui.getnextsearch")) { handleNavigateHistory(TextHistory.Direction.NEXT); } } @@ -151,22 +150,33 @@ public static Pattern getPattern(String search) { try { pattern = Pattern.compile(search); } catch (PatternSyntaxException ignored) {} + return pattern == null || pattern.toString().length() == 0 ? null : pattern; } @Override public ItemFilter getFilter() { - String s_filter = text().toLowerCase(); + return getFilter(text()); + } + public static ItemFilter getFilter(String s_filter) { List primary = new LinkedList<>(); List secondary = new LinkedList<>(); + s_filter = s_filter.toLowerCase(); + for (ISearchProvider p : searchProviders) { ItemFilter filter = p.getFilter(s_filter); if (filter != null) (p.isPrimary() ? primary : secondary).add(filter); } - if (!primary.isEmpty()) return new AnyMultiItemFilter(primary); - if (!secondary.isEmpty()) return new AnyMultiItemFilter(secondary); + if (!primary.isEmpty()) { + return new AnyMultiItemFilter(primary); + } + + if (!secondary.isEmpty()) { + return new AnyMultiItemFilter(secondary); + } + return new EverythingItemFilter(); } diff --git a/src/main/java/codechicken/nei/SubsetWidget.java b/src/main/java/codechicken/nei/SubsetWidget.java index 9625f8747..a3824142a 100644 --- a/src/main/java/codechicken/nei/SubsetWidget.java +++ b/src/main/java/codechicken/nei/SubsetWidget.java @@ -533,7 +533,6 @@ private static void saveHidden() { NBTTagList list = dirtyHiddenItems.getAndSet(null); if (list != null) { NEIClientConfig.world.nbt.setTag("hiddenItems", list); - NEIClientConfig.world.saveNBT(); } } diff --git a/src/main/java/codechicken/nei/api/API.java b/src/main/java/codechicken/nei/api/API.java index 8f8b01308..8d9a1b7e3 100644 --- a/src/main/java/codechicken/nei/api/API.java +++ b/src/main/java/codechicken/nei/api/API.java @@ -27,11 +27,13 @@ import codechicken.nei.SearchField.ISearchProvider; import codechicken.nei.SubsetWidget; import codechicken.nei.SubsetWidget.SubsetTag; +import codechicken.nei.api.IRecipeFilter.IRecipeFilterProvider; import codechicken.nei.api.ItemFilter.ItemFilterProvider; import codechicken.nei.config.Option; import codechicken.nei.config.OptionKeyBind; import codechicken.nei.recipe.CatalystInfo; import codechicken.nei.recipe.GuiCraftingRecipe; +import codechicken.nei.recipe.GuiRecipe; import codechicken.nei.recipe.GuiUsageRecipe; import codechicken.nei.recipe.ICraftingHandler; import codechicken.nei.recipe.IRecipeHandler; @@ -160,7 +162,7 @@ public static void setOverrideName(ItemStack item, String name) { /** * Adds an item to the item panel. Any items added using this function will override the default search pattern. - * + * * @param item an item with data */ public static void addItemListEntry(ItemStack item) { @@ -183,7 +185,7 @@ public static void setItemListEntries(Item item, Iterable items) { /** * Add a custom KeyBinding to be configured in the Controls menu. - * + * * @param ident An identifier for your key, eg "shoot" * @param defaultKey The default value, commonly obtained from {@link Keyboard} */ @@ -195,7 +197,7 @@ public static void addKeyBind(String ident, int defaultKey) { /** * Add a custom KeyBinding to be configured in the Controls menu. - * + * * @param ident An identifier for your key, eg "shoot" * @param defaultKey The default value, commonly obtained from {@link Keyboard} */ @@ -210,7 +212,7 @@ public static void addOption(Option option) { /** * Add a new Layout Style for the NEI interface - * + * * @param styleID The Unique ID to be used for storing your style in the config and cycling through avaliable styles * @param style The style to add. */ @@ -220,7 +222,7 @@ public static void addLayoutStyle(int styleID, LayoutStyle style) { /** * Registers a new Infinite Item Handler. - * + * * @param handler The handler to be registered. */ public static void addInfiniteItemHandler(IInfiniteItemHandler handler) { @@ -229,7 +231,7 @@ public static void addInfiniteItemHandler(IInfiniteItemHandler handler) { /** * Registers a new Infinite Item Handler. - * + * * @param block The block to handle, null for all. * @param handler The handler to be registered. */ @@ -239,7 +241,7 @@ public static void registerHighlightIdentifier(Block block, IHighlightHandler ha /** * Tells NEI not to perform any Fast Transfer operations on slots of a particular class - * + * * @param slotClass The class of slot to be exempted */ public static void addFastTransferExemptSlot(Class slotClass) { @@ -248,7 +250,7 @@ public static void addFastTransferExemptSlot(Class slotClass) { /** * Register a new text handler for the block highlight tooltip with a layout specification (HEADER, BODY or FOOTER). - * + * * @param handler The handler to be registered. * @param layout A HUDAugmenterRegistry.Layout entry. HEADER is displayed before BODY which is displayed before * FOOTER. @@ -259,16 +261,22 @@ public static void registerHighlightHandler(IHighlightHandler handler, ItemInfo. /** * Register a mode handler for overriding NEI recipe/utility/cheat mode settings. - * + * * @param handler The handler to be registered. */ public static void registerModeHandler(INEIModeHandler handler) { NEIInfo.modeHandlers.add(handler); } + public static void addRecipeFilter(IRecipeFilterProvider filterProvider) { + synchronized (GuiRecipe.recipeFilterers) { + GuiRecipe.recipeFilterers.add(filterProvider); + } + } + /** * Register a filter provider for the item panel. - * + * * @param filterProvider The filter provider to be registered. */ public static void addItemFilter(ItemFilterProvider filterProvider) { @@ -279,7 +287,7 @@ public static void addItemFilter(ItemFilterProvider filterProvider) { /** * Adds a new tag to the item subset dropdown. - * + * * @param name The fully qualified name, Eg Blocks.MobSpawners. NOT case sensitive * @param filter A filter for matching items that fit in this subset */ @@ -289,7 +297,7 @@ public static void addSubset(String name, ItemFilter filter) { /** * Adds a new tag to the item subset dropdown. - * + * * @param name The fully qualified name, Eg Blocks.MobSpawners. NOT case sensitive * @param items An iterable of itemstacks to be added as a subset */ @@ -315,7 +323,7 @@ public static void addSearchProvider(ISearchProvider provider) { /** * Adds a new sorting option to the item panel sort menu - * + * * @param name A unique id for this sort option. Will be used in the config for saving and translated in the options * gui. Note that if the translation key name.tip exists, it will be used for a tooltip */ @@ -326,7 +334,7 @@ public static void addSortOption(String name, Comparator comparator) /** * Adds an additional item list entry for an item, sorted after the rest of the items are found through the normal * process - * + * * @param item The item to add the variant for * @param variant The stack to appear in the item panel */ @@ -345,7 +353,7 @@ public static void registerStackStringifyHandler(IStackStringifyHandler handler) * Adds an association between an ingredient and what it can craft. (i.e. Furnace ItemStack -> Smelting and Fuel * Recipes) Allows players to see what ingredient they need to craft in order to make recipes from a recipe * category. - * + * * @param stack the ingredient that can craft recipes (like a furnace or crafting table) * @param handler the recipe category handled by the ingredient * @param priority higher priority comes first, default to 0 @@ -366,7 +374,7 @@ public static void addRecipeCatalyst(ItemStack stack, IRecipeHandler handler) { * Adds an association between an ingredient and what it can craft. (i.e. Furnace ItemStack -> Smelting and Fuel * Recipes) Allows players to see what ingredient they need to craft in order to make recipes from a recipe * category. - * + * * @param stack the ingredient that can craft recipes (like a furnace or crafting table) * @param handlerID recipe category identifier (see also {@link RecipeCatalysts#getRecipeID(IRecipeHandler)}) * @param priority higher priority comes first, default to 0 diff --git a/src/main/java/codechicken/nei/api/IRecipeFilter.java b/src/main/java/codechicken/nei/api/IRecipeFilter.java new file mode 100644 index 000000000..65fd942d0 --- /dev/null +++ b/src/main/java/codechicken/nei/api/IRecipeFilter.java @@ -0,0 +1,58 @@ +package codechicken.nei.api; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; + +import codechicken.nei.PositionedStack; +import codechicken.nei.recipe.IRecipeHandler; +import codechicken.nei.recipe.TemplateRecipeHandler; + +public interface IRecipeFilter { + + public static interface IRecipeFilterProvider { + + static HashMap override = new HashMap<>(); + + static boolean filteringAvailable(IRecipeHandler handler) { + return handler instanceof TemplateRecipeHandler && !overrideMethod(handler.getClass().getName()); + } + + static boolean overrideMethod(String className) { + try { + + if (override.containsKey(className)) { + return override.get(className); + } + + String[] methods = new String[] { "getResultStack", "getOtherStacks", "getIngredientStacks" }; + Class cls = Class.forName(className); + + for (String method : methods) { + Method m = cls.getMethod(method, Integer.TYPE); + if (!m.getDeclaringClass().getName().equals("codechicken.nei.recipe.TemplateRecipeHandler")) { + override.put(className, true); + return true; + } + } + + Method m = cls.getMethod("numRecipes"); + if (!m.getDeclaringClass().getName().equals("codechicken.nei.recipe.TemplateRecipeHandler")) { + override.put(className, true); + return true; + } + + override.put(className, false); + return false; + } catch (Throwable e) { + return true; + } + } + + public IRecipeFilter getFilter(); + } + + public boolean matches(IRecipeHandler handler, List ingredients, PositionedStack result, + List others); + +} diff --git a/src/main/java/codechicken/nei/api/ShortcutInputHandler.java b/src/main/java/codechicken/nei/api/ShortcutInputHandler.java index 2cf5d8aa6..cfb4e3ca1 100644 --- a/src/main/java/codechicken/nei/api/ShortcutInputHandler.java +++ b/src/main/java/codechicken/nei/api/ShortcutInputHandler.java @@ -1,5 +1,8 @@ package codechicken.nei.api; +import static codechicken.lib.gui.GuiDraw.getMousePosition; + +import java.awt.Point; import java.util.List; import net.minecraft.client.gui.inventory.GuiContainer; @@ -7,14 +10,18 @@ import org.lwjgl.input.Mouse; +import codechicken.nei.ItemPanel.ItemPanelSlot; import codechicken.nei.ItemPanels; import codechicken.nei.LayoutManager; import codechicken.nei.NEIClientConfig; import codechicken.nei.NEIClientUtils; import codechicken.nei.PositionedStack; +import codechicken.nei.recipe.BookmarkRecipeId; import codechicken.nei.recipe.GuiCraftingRecipe; import codechicken.nei.recipe.GuiRecipe; import codechicken.nei.recipe.GuiUsageRecipe; +import codechicken.nei.recipe.StackInfo; +import codechicken.nei.recipe.stackinfo.GTFluidStackStringifyHandler; public abstract class ShortcutInputHandler { @@ -43,11 +50,11 @@ public static boolean handleKeyEvent(ItemStack stackover) { } if (NEIClientConfig.isKeyHashDown("gui.recipe")) { - return GuiCraftingRecipe.openRecipeGui("item", stackover); + return GuiCraftingRecipe.openRecipeGui("item", normalizeItemStack(stackover)); } if (NEIClientConfig.isKeyHashDown("gui.usage")) { - return GuiUsageRecipe.openRecipeGui("item", stackover); + return GuiUsageRecipe.openRecipeGui("item", normalizeItemStack(stackover)); } if (NEIClientConfig.isKeyHashDown("gui.bookmark")) { @@ -99,14 +106,28 @@ private static boolean hideOverlayRecipe() { return false; } - private static boolean openOverlayRecipe(ItemStack stack, boolean shift) { + private static boolean openOverlayRecipe(ItemStack stackover, boolean shift) { final GuiContainer gui = NEIClientUtils.getGuiContainer(); if (gui == null || gui instanceof GuiRecipe) { return false; } - return GuiCraftingRecipe.openRecipeGui("item", true, shift, stack); + final Point mouseover = getMousePosition(); + ItemPanelSlot panelSlot = ItemPanels.bookmarkPanel.getSlotMouseOver(mouseover.x, mouseover.y); + BookmarkRecipeId recipeId = null; + + if (panelSlot != null) { + recipeId = ItemPanels.bookmarkPanel.getBookmarkRecipeId(panelSlot.slotIndex); + } else { + recipeId = ItemPanels.bookmarkPanel.getBookmarkRecipeId(stackover); + } + + if (recipeId != null) { + return GuiCraftingRecipe.overlayRecipe(stackover, recipeId, shift); + } + + return false; } private static boolean saveRecipeInBookmark(ItemStack stack, boolean saveIngredients, boolean saveStackSize) { @@ -128,4 +149,11 @@ private static boolean saveRecipeInBookmark(ItemStack stack, boolean saveIngredi return false; } + + private static ItemStack normalizeItemStack(ItemStack stack) { + GTFluidStackStringifyHandler.replaceAE2FCFluidDrop = true; + stack = StackInfo.loadFromNBT(StackInfo.itemStackToNBT(stack)); + GTFluidStackStringifyHandler.replaceAE2FCFluidDrop = false; + return stack; + } } diff --git a/src/main/java/codechicken/nei/recipe/BookmarkRecipeId.java b/src/main/java/codechicken/nei/recipe/BookmarkRecipeId.java index b10b23c39..56bf726f3 100644 --- a/src/main/java/codechicken/nei/recipe/BookmarkRecipeId.java +++ b/src/main/java/codechicken/nei/recipe/BookmarkRecipeId.java @@ -16,10 +16,6 @@ public class BookmarkRecipeId { - public int position = -1; - - public int recipetype = -1; - public String handlerName = null; public List ingredients = new ArrayList<>(); @@ -121,43 +117,6 @@ protected JsonArray convertIngredientsToJsonArray(List ingredien return arr; } - /** - * Ensures the {@link BookmarkRecipeId#recipetype} and {@link BookmarkRecipeId#position} fields point to a valid - * recipe. This is needed, because the recipe IDs are loaded lazily on first use. - * - * @param recipeHandlers The subset of recipe handlers to search for the recipe - */ - public void updateTargetRecipe(List recipeHandlers) { - if (this.recipetype == -1 || this.position == -1) { - this.recipetype = 0; - this.position = 0; - - if (this.handlerName != null) { - - for (int j = 0; j < recipeHandlers.size(); j++) { - IRecipeHandler localHandler = recipeHandlers.get(j); - HandlerInfo localHandlerInfo = GuiRecipeTab.getHandlerInfo(localHandler); - - if (localHandlerInfo.getHandlerName().equals(this.handlerName)) { - - if (!this.ingredients.isEmpty()) { - for (int i = 0; i < localHandler.numRecipes(); i++) { - - if (this.equalsIngredients(localHandler.getIngredientStacks(i))) { - this.recipetype = j; - this.position = i; - break; - } - } - } - - break; - } - } - } - } - } - public boolean equals(Object anObject) { if (this == anObject) { return true; diff --git a/src/main/java/codechicken/nei/recipe/GuiCraftingRecipe.java b/src/main/java/codechicken/nei/recipe/GuiCraftingRecipe.java index 83b90db86..d4140d474 100644 --- a/src/main/java/codechicken/nei/recipe/GuiCraftingRecipe.java +++ b/src/main/java/codechicken/nei/recipe/GuiCraftingRecipe.java @@ -6,6 +6,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiScreen; @@ -23,50 +25,76 @@ public class GuiCraftingRecipe extends GuiRecipe { public static ArrayList serialCraftingHandlers = new ArrayList<>(); public static boolean openRecipeGui(String outputId, Object... results) { - return openRecipeGui(outputId, false, false, results); + return createRecipeGui(outputId, true, results) != null; } - public static boolean openRecipeGui(String outputId, final Boolean overlay, final Boolean shift, - Object... results) { - return createRecipeGui(outputId, overlay, shift, true, results) != null; - } - - public static GuiRecipe createRecipeGui(String outputId, final Boolean overlay, final Boolean shift, - final boolean open, Object... results) { + public static GuiRecipe createRecipeGui(String outputId, boolean open, Object... results) { final Minecraft mc = NEIClientUtils.mc(); - - final BookmarkRecipeId recipeId = "item".equals(outputId) - ? getRecipeId(mc.currentScreen, (ItemStack) results[0]) - : getCurrentRecipe(mc.currentScreen); - - if (overlay && recipeId == null) return null; + final BookmarkRecipeId recipeId; + + if ("item".equals(outputId)) { + recipeId = getRecipeId(mc.currentScreen, (ItemStack) results[0]); + } else if ("recipeId".equals(outputId)) { + recipeId = (BookmarkRecipeId) results[1]; + } else { + recipeId = getCurrentRecipe(mc.currentScreen); + } final ArrayList handlers = getCraftingHandlers(outputId, results); if (!handlers.isEmpty()) { - GuiCraftingRecipe gui = new GuiCraftingRecipe(handlers, recipeId); + final GuiCraftingRecipe gui = new GuiCraftingRecipe(handlers, "recipeId".equals(outputId)); if (open) { mc.displayGuiScreen(gui); } - if (recipeId != null) { - gui.openTargetRecipe(gui.recipeId); - } + gui.openTargetRecipe(recipeId); + return gui; + } + + return null; + } + + public static boolean overlayRecipe(ItemStack stack, BookmarkRecipeId recipeId, final Boolean shift) { - if (overlay) { - gui.overlayRecipe(gui.recipeId.position, shift); + if (stack == null || recipeId == null) { + return false; + } + + final ArrayList handlers = getCraftingHandlers("recipeId", stack, recipeId); + + if (!handlers.isEmpty()) { + final GuiCraftingRecipe gui = new GuiCraftingRecipe(handlers, true); + final int recipe = gui.openTargetRecipe(recipeId); + + if (recipe != -1) { + gui.overlayRecipe(recipe, shift); + return true; } - return gui; } - return null; + return false; } public static ArrayList getCraftingHandlers(String outputId, Object... results) { + ArrayList craftinghandlers = GuiCraftingRecipe.craftinghandlers; + ArrayList serialCraftingHandlers = GuiCraftingRecipe.serialCraftingHandlers; + Function recipeHandlerFunction; + + if ("recipeId".equals(outputId)) { + ItemStack stack = (ItemStack) results[0]; + BookmarkRecipeId recipeId = (BookmarkRecipeId) results[1]; + craftinghandlers = filterByHandlerName(craftinghandlers, recipeId.handlerName); + serialCraftingHandlers = filterByHandlerName(serialCraftingHandlers, recipeId.handlerName); + recipeHandlerFunction = h -> h.getRecipeHandler("item", stack); + } else { + recipeHandlerFunction = h -> h.getRecipeHandler(outputId, results); + } + final RecipeHandlerQuery recipeQuery = new RecipeHandlerQuery<>( - h -> h.getRecipeHandler(outputId, results), + recipeHandlerFunction, craftinghandlers, serialCraftingHandlers, "Error while looking up crafting recipe", @@ -76,6 +104,16 @@ public static ArrayList getCraftingHandlers(String outputId, O return recipeQuery.runWithProfiling("recipe.concurrent.crafting"); } + private static ArrayList filterByHandlerName(ArrayList craftinghandlers, + String handlerName) { + return craftinghandlers.stream().filter(h -> getHandlerName(h).equals(handlerName)) + .collect(Collectors.toCollection(ArrayList::new)); + } + + private static String getHandlerName(ICraftingHandler handler) { + return GuiRecipeTab.getHandlerInfo(handler).getHandlerName(); + } + protected static BookmarkRecipeId getRecipeId(GuiScreen gui, ItemStack stackover) { if (gui instanceof GuiRecipe) { @@ -97,10 +135,10 @@ protected static BookmarkRecipeId getRecipeId(GuiScreen gui, ItemStack stackover return ItemPanels.bookmarkPanel.getBookmarkRecipeId(stackover); } - private GuiCraftingRecipe(ArrayList handlers, BookmarkRecipeId recipeId) { + private GuiCraftingRecipe(ArrayList handlers, boolean limitToOneRecipe) { super(NEIClientUtils.mc().currentScreen); + this.limitToOneRecipe = limitToOneRecipe; this.currenthandlers = handlers; - this.recipeId = recipeId; } public static void registerRecipeHandler(ICraftingHandler handler) { diff --git a/src/main/java/codechicken/nei/recipe/GuiRecipe.java b/src/main/java/codechicken/nei/recipe/GuiRecipe.java index eb0f6f8f9..ac94827e9 100644 --- a/src/main/java/codechicken/nei/recipe/GuiRecipe.java +++ b/src/main/java/codechicken/nei/recipe/GuiRecipe.java @@ -1,15 +1,18 @@ package codechicken.nei.recipe; import static codechicken.lib.gui.GuiDraw.fontRenderer; +import static codechicken.nei.NEIClientUtils.translate; import java.awt.Point; import java.awt.Rectangle; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.LinkedList; import java.util.List; import java.util.Optional; import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -21,6 +24,8 @@ import net.minecraft.client.renderer.RenderHelper; import net.minecraft.inventory.Slot; import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumChatFormatting; +import net.minecraftforge.fluids.FluidStack; import org.lwjgl.input.Keyboard; import org.lwjgl.opengl.GL11; @@ -32,11 +37,16 @@ import codechicken.nei.NEIClientConfig; import codechicken.nei.NEIClientUtils; import codechicken.nei.PositionedStack; +import codechicken.nei.RecipeSearchField; +import codechicken.nei.RestartableTask; import codechicken.nei.VisiblityData; import codechicken.nei.api.IGuiContainerOverlay; import codechicken.nei.api.INEIGuiHandler; import codechicken.nei.api.IOverlayHandler; +import codechicken.nei.api.IRecipeFilter; +import codechicken.nei.api.IRecipeFilter.IRecipeFilterProvider; import codechicken.nei.api.IRecipeOverlayRenderer; +import codechicken.nei.api.ItemFilter; import codechicken.nei.api.TaggedInventoryArea; import codechicken.nei.drawable.DrawableBuilder; import codechicken.nei.drawable.DrawableResource; @@ -62,7 +72,9 @@ public abstract class GuiRecipe extends GuiContainer i private static final int buttonWidth = 13; private static final int buttonHeight = 12; - private boolean limitToOneRecipe = false; + public static final List recipeFilterers = new LinkedList<>(); + + protected boolean limitToOneRecipe = false; // Background image final DrawableResource bgTop = new DrawableBuilder("nei:textures/gui/recipebg.png", 0, BG_TOP_Y, 176, BG_TOP_HEIGHT) @@ -81,9 +93,9 @@ public abstract class GuiRecipe extends GuiContainer i BG_BOTTOM_HEIGHT).build(); public ArrayList currenthandlers = new ArrayList<>(); - public int page; public int recipetype; + public BookmarkRecipeId recipeId; public ContainerRecipe slotcontainer; public GuiContainer firstGui; @@ -105,6 +117,137 @@ public abstract class GuiRecipe extends GuiContainer i private int yShift = 0; + public static class ItemRecipeFilter implements IRecipeFilter { + + public ItemFilter filter; + + public ItemRecipeFilter(ItemFilter filter) { + this.filter = filter; + } + + @Override + public boolean matches(IRecipeHandler handler, List ingredients, PositionedStack result, + List others) { + + if (matchPositionedStack(ingredients)) { + return true; + } + + if (matchPositionedStack(result)) { + return true; + } + + if (matchPositionedStack(others)) { + return true; + } + + return false; + } + + private boolean matchPositionedStack(List items) { + for (PositionedStack pStack : items) { + if (matchPositionedStack(pStack)) { + return true; + } + } + return false; + } + + private boolean matchPositionedStack(PositionedStack pStack) { + if (pStack == null) return false; + + for (ItemStack stack : pStack.items) { + if (filter.matches(stack)) { + return true; + } + } + + return false; + } + + } + + public static class AllMultiRecipeFilter implements IRecipeFilter { + + public List filters; + + public AllMultiRecipeFilter(List filters) { + this.filters = filters; + } + + public AllMultiRecipeFilter(IRecipeFilter a, IRecipeFilter b) { + this.filters = new ArrayList<>(); + this.filters.add(a); + this.filters.add(b); + } + + public AllMultiRecipeFilter() { + this(new ArrayList<>()); + } + + @Override + public boolean matches(IRecipeHandler handler, List ingredients, PositionedStack result, + List others) { + for (IRecipeFilter filter : filters) { + try { + if (filter != null && !filter.matches(handler, ingredients, result, others)) return false; + } catch (Exception e) { + NEIClientConfig.logger.error("Exception filtering " + handler + " with " + filter, e); + } + } + return true; + } + } + + protected static final RestartableTask updateFilter = new RestartableTask("NEI Recipe Filtering") { + + @Override + public void execute() { + final GuiScreen currentScreen = NEIClientUtils.mc().currentScreen; + + if (currentScreen instanceof GuiRecipe) { + final GuiRecipe guiRecipe = (GuiRecipe) currentScreen; + final IRecipeHandler handler = guiRecipe.handler; + + if (handler != null && IRecipeFilterProvider.filteringAvailable(handler)) { + + if (GuiRecipe.searchField.text().isEmpty()) { + guiRecipe.updateRecipeList(handler, null); + } else { + final IRecipeFilter filter = new ItemRecipeFilter(GuiRecipe.searchField.getFilter()); + ArrayList filtered = ((TemplateRecipeHandler) handler).getSearchResult(filter); + + if (filtered == null) { + stop(); + } + + if (interrupted()) return; + guiRecipe.updateRecipeList(handler, filtered); + } + + } + + } + + } + + }; + + public static final RecipeSearchField searchField = new RecipeSearchField("") { + + @Override + protected boolean noResults() { + GuiScreen currentScreen = NEIClientUtils.mc().currentScreen; + return !(currentScreen instanceof GuiRecipe) || ((GuiRecipe) currentScreen).handler.numRecipes() > 0; + } + + @Override + public void onTextChange(String oldText) { + updateFilter.restart(); + } + + }; + /** * This will only be true iff height hacking has been configured for the current recipe handler AND we are currently * within the scope of an active {@link CompatibilityHacks} instance. @@ -119,7 +262,10 @@ protected GuiRecipe(GuiScreen prevgui) { this.prevGui = prevgui; this.firstGuiGeneral = prevgui; - if (prevgui instanceof GuiContainer) this.firstGui = (GuiContainer) prevgui; + + if (prevgui instanceof GuiContainer) { + this.firstGui = (GuiContainer) prevgui; + } if (prevgui instanceof IGuiContainerOverlay) { this.firstGui = ((IGuiContainerOverlay) prevgui).getFirstScreen(); @@ -211,16 +357,11 @@ public void close() { } } - @Override - public void initGui() { - initGui(false); - } - @SuppressWarnings("unchecked") - public void initGui(boolean asWidget) { + public void initGui() { xSize = 176; ySize = Math.min(Math.max(height - 68, 166), 370); - if (!asWidget) { + if (!this.limitToOneRecipe) { super.initGui(); } else { this.guiLeft = (this.width - this.xSize) / 2; @@ -234,11 +375,14 @@ public void initGui(boolean asWidget) { this.width = scaledresolution.getScaledWidth(); this.height = scaledresolution.getScaledHeight(); } + guiTop = (height - ySize) / 2 + 10; - currenthandlers = getCurrentRecipeHandlers(); // Probably don't comment me out - if (handler == null) setRecipePage(recipetype); - else initOverlayButtons(); + if (handler == null) { + setRecipePage(recipetype); + } else { + initOverlayButtons(); + } checkYShift(); @@ -293,6 +437,36 @@ private void initOverlayButtons() { itemPresenceCacheRecipe = -1; } + public static IRecipeFilter getRecipeListFilter(IRecipeHandler handler) { + if (IRecipeFilterProvider.filteringAvailable(handler)) { + LinkedList filters = new LinkedList<>(); + + synchronized (recipeFilterers) { + for (IRecipeFilterProvider p : recipeFilterers) { + IRecipeFilter filter = p.getFilter(); + if (filter != null) { + filters.add(filter); + } + } + } + + if (!filters.isEmpty()) { + return filters.size() == 1 ? filters.get(0) : new AllMultiRecipeFilter(filters); + } + } + + return null; + } + + protected void updateRecipeList(IRecipeHandler h, ArrayList filtered) { + synchronized (handler) { + if (handler != null && handler == h && IRecipeFilterProvider.filteringAvailable(handler)) { + ((TemplateRecipeHandler) handler).applySearch(filtered); + changePage(0); + } + } + } + private void checkYShift() { yShift = handlerInfo == null ? 0 : handlerInfo.getYShift(); } @@ -301,14 +475,19 @@ public void setRecipePage(int idx) { setRecipePage(idx, 0); } - public void setRecipePage(int idx, int position) { - recipetype = idx; - if (recipetype < 0) recipetype = currenthandlers.size() - 1; - else if (recipetype >= currenthandlers.size()) recipetype = 0; + public void setRecipePage(int idx, int recipe) { + recipetype = (currenthandlers.size() + idx) % currenthandlers.size(); handler = currenthandlers.get(recipetype); handlerInfo = GuiRecipeTab.getHandlerInfo(handler); - page = Math.min(Math.max(0, position), handler.numRecipes() - 1) / getRecipesPerPage(); + + if (!limitToOneRecipe) { + searchField.setText(""); + searchField.setVisible(false); + } + + page = Math.min(Math.max(0, recipe), handler.numRecipes() - 1) / getRecipesPerPage(); + changePage(0); recipeTabs.calcPageNumber(); checkYShift(); initOverlayButtons(); @@ -318,20 +497,42 @@ public List getOverlayButtons() { return Collections.unmodifiableList(Arrays.asList(overlayButtons)); } - public void openTargetRecipe(BookmarkRecipeId recipeId) { + public int openTargetRecipe(BookmarkRecipeId recipeId) { + int recipe = -1; + int recipetype = 0; - if (recipeId == null) { - return; + this.recipeId = recipeId; + + if (this.recipeId != null) { + for (int j = 0; j < currenthandlers.size(); j++) { + IRecipeHandler localHandler = currenthandlers.get(j); + HandlerInfo localHandlerInfo = GuiRecipeTab.getHandlerInfo(localHandler); + + if (localHandlerInfo.getHandlerName().equals(this.recipeId.handlerName)) { + + if (!this.recipeId.ingredients.isEmpty()) { + for (int i = 0; i < localHandler.numRecipes(); i++) { + if (this.recipeId.equalsIngredients(localHandler.getIngredientStacks(i))) { + recipetype = j; + recipe = i; + break; + } + } + } + + break; + } + } } - recipeId.updateTargetRecipe(currenthandlers); - setRecipePage(recipeId.recipetype, recipeId.position); + setRecipePage(recipetype, Math.max(0, recipe)); + + return recipe; } public List getFocusedRecipeIngredients() { - final int recipesPerPage = getRecipesPerPage(); - for (int idx = page * recipesPerPage; idx < handler.numRecipes() && idx < (page + 1) * recipesPerPage; idx++) { + for (int idx : getRecipeIndices()) { if (recipeInFocus(idx)) { return handler.getIngredientStacks(idx); } @@ -341,9 +542,8 @@ public List getFocusedRecipeIngredients() { } public int prepareFocusedRecipeResultStackSize(ItemStack stackover) { - final int recipesPerPage = getRecipesPerPage(); - for (int idx = page * recipesPerPage; idx < handler.numRecipes() && idx < (page + 1) * recipesPerPage; idx++) { + for (int idx : getRecipeIndices()) { if (recipeInFocus(idx)) { final PositionedStack result = handler.getResultStack(idx); int stackSize = 0; @@ -400,16 +600,32 @@ public List getRecipeIndices() { @Override public void keyTyped(char c, int i) { - if (i == Keyboard.KEY_ESCAPE) // esc - { + + if (searchField.isVisible() && searchField.focused() && searchField.handleKeyPress(i, c)) { + return; + } + + if (i == Keyboard.KEY_ESCAPE) { // esc mc.displayGuiScreen(firstGuiGeneral); NEICPH.sendRequestContainer(); return; } - if (GuiContainerManager.getManager(this).lastKeyTyped(i, c)) return; + + if (searchField.isVisible() && searchField.focused()) { + searchField.lastKeyTyped(i, c); + return; + } + + if (GuiContainerManager.getManager(this).lastKeyTyped(i, c)) { + return; + } try (CompatibilityHacks compatibilityHacks = new CompatibilityHacks()) { - for (int recipe : getRecipeIndices()) if (handler.keyTyped(this, c, i, recipe)) return; + for (int recipe : getRecipeIndices()) { + if (handler.keyTyped(this, c, i, recipe)) { + return; + } + } } if (i == mc.gameSettings.keyBindInventory.getKeyCode()) { @@ -429,14 +645,31 @@ public void keyTyped(char c, int i) { } @Override - protected void mouseClicked(int x, int y, int button) { + protected void mouseClicked(int mousex, int mousey, int button) { + + if (!limitToOneRecipe && handler != null && IRecipeFilterProvider.filteringAvailable(handler)) { + if (new Rectangle(borderPadding + buttonWidth - 2, 15, xSize - (borderPadding + buttonWidth) * 2 + 2, 16) + .contains(mousex - guiLeft, mousey - guiTop)) { + searchField.setVisible(true); + searchField.handleClick(mousex - guiLeft, mousey - guiTop, button); + if (!searchField.focused()) { + searchField.setFocus(true); + } + } else { + searchField.onGuiClick(mousex - guiLeft, mousey - guiTop); + if (searchField.text().isEmpty() && !searchField.focused()) { + searchField.setVisible(false); + } + } + } + try (CompatibilityHacks compatibilityHacks = new CompatibilityHacks()) { for (int recipe : getRecipeIndices()) if (handler.mouseClicked(this, button, recipe)) return; } - if (recipeTabs.mouseClicked(x, y, button)) return; + if (recipeTabs.mouseClicked(mousex, mousey, button)) return; - super.mouseClicked(x, y, button); + super.mouseClicked(mousex, mousey, button); } @Override @@ -455,16 +688,22 @@ public void mouseScrolled(int i) { // If shift is held, try switching to the next recipe handler. Replicates the GuiRecipeTabs.mouseScrolled() // without the checking for the cursor being inside the tabbar. if (NEIClientUtils.shiftKey()) { - if (i < 0) nextType(); - else prevType(); + if (i < 0) { + nextType(); + } else { + prevType(); + } return; } // Finally, if nothing else has handled scrolling, try changing to the next recipe page. if (new Rectangle(guiLeft, guiTop, xSize, ySize).contains(GuiDraw.getMousePosition())) { - if (i > 0) prevPage(); - else nextPage(); + if (i > 0) { + prevPage(); + } else { + nextPage(); + } } } @@ -485,8 +724,10 @@ protected void actionPerformed(GuiButton guibutton) { nextPage(); return; } + if (overlayButtons != null && guibutton.id >= OVERLAY_BUTTON_ID_START && guibutton.id < OVERLAY_BUTTON_ID_START + overlayButtons.length) { + mc.displayGuiScreen(firstGui); overlayRecipe( page * getRecipesPerPage() + guibutton.id - OVERLAY_BUTTON_ID_START, NEIClientUtils.shiftKey()); @@ -511,6 +752,17 @@ public List handleTooltip(GuiContainer gui, int mousex, int mousey, List for (int i : getRecipeIndices()) currenttip = handler.handleTooltip(this, currenttip, i); } recipeTabs.handleTooltip(mousex, mousey, currenttip); + + if (currenttip.isEmpty() + && new Rectangle(borderPadding + buttonWidth - 2, 15, xSize - (borderPadding + buttonWidth) * 2 + 2, 16) + .contains(mousex - guiLeft, mousey - guiTop)) { + + if (!searchField.isVisible() && IRecipeFilterProvider.filteringAvailable(handler)) { + currenttip.add(translate("recipe.search.show")); + } + + } + return currenttip; } @@ -530,13 +782,11 @@ public List handleItemDisplayName(GuiContainer gui, ItemStack itemstack, } private void nextPage() { - page++; - if (page > (handler.numRecipes() - 1) / getRecipesPerPage()) page = 0; + changePage(1); } private void prevPage() { - page--; - if (page < 0) page = (handler.numRecipes() - 1) / getRecipesPerPage(); + changePage(-1); } protected void nextType() { @@ -548,30 +798,44 @@ protected void prevType() { } protected void overlayRecipe(int recipe, final boolean shift) { - boolean moveItems = shift || !NEIClientConfig.requireShiftForOverlayRecipe(); + if (handler == null || !handler.hasOverlay(firstGui, firstGui.inventorySlots, recipe)) { - mc.displayGuiScreen(firstGui); return; } + + final boolean moveItems = shift || !NEIClientConfig.requireShiftForOverlayRecipe(); final IRecipeOverlayRenderer renderer = handler.getOverlayRenderer(firstGui, recipe); final IOverlayHandler overlayHandler = handler.getOverlayHandler(firstGui, recipe); - mc.displayGuiScreen(firstGui); if (renderer == null || moveItems) { if (overlayHandler != null) { - overlayHandler.overlayRecipe(firstGui, currenthandlers.get(recipetype), recipe, moveItems); + overlayHandler.overlayRecipe(firstGui, handler, recipe, moveItems); } } else { LayoutManager.overlayRenderer = renderer; } } + protected void changePage(int shift) { + final int recipesPerPage = getRecipesPerPage(); + final int numRecipes = handler != null ? handler.numRecipes() : 0; + + if (numRecipes > 0) { + final int numPages = (int) Math.ceil(numRecipes / (float) recipesPerPage); + this.page = Math.min(Math.max(0, this.page), numPages); + this.page = (numPages + this.page + shift) % numPages; + } else { + this.page = 0; + } + + } + public void refreshPage() { RecipeCatalysts.updatePosition(ySize - BG_TOP_HEIGHT - (GuiRecipeCatalyst.fullBorder * 2)); + changePage(0); refreshSlots(); final int recipesPerPage = getRecipesPerPage(); final boolean multiplepages = handler.numRecipes() > recipesPerPage; - final int numRecipes = Math.min(handler.numRecipes() - (page * recipesPerPage), recipesPerPage); area.width = handlerInfo.getWidth(); @@ -580,6 +844,13 @@ public void refreshPage() { area.y = guiTop - 4 + yShift; checkYShift(); + if (!limitToOneRecipe) { + searchField.y = 16; + searchField.x = borderPadding + buttonWidth - 2; + searchField.w = xSize - (borderPadding + buttonWidth) * 2 + 1 - 45; + searchField.h = 14; + } + nextpage.enabled = prevpage.enabled = multiplepages; if (firstGui == null) { @@ -602,8 +873,7 @@ public void refreshPage() { private void refreshSlots() { slotcontainer.inventorySlots.clear(); - final int recipesPerPage = getRecipesPerPage(); - for (int i = page * recipesPerPage; i < handler.numRecipes() && i < (page + 1) * recipesPerPage; i++) { + for (int i : getRecipeIndices()) { Point p = getRecipePosition(i); // Legacy recipe handlers only expect a single paging widget at the top of the recipe screen, in contrast, // GTNH NEI moves the recipe paging widget from the bottom to the top, which means said legacy handlers will @@ -665,22 +935,41 @@ public void drawGuiContainerForegroundLayer(int mouseX, int mouseY) { GuiContainerManager.enable2DRender(); final int recipesPerPage = getRecipesPerPage(); final int ySkip = limitToOneRecipe ? 25 : 0; + if (!limitToOneRecipe) { String s = handler.getRecipeName().trim(); fontRendererObj.drawStringWithShadow(s, (xSize - fontRendererObj.getStringWidth(s)) / 2, 5, 0xffffff); - s = NEIClientUtils.translate("recipe.page", page + 1, (handler.numRecipes() - 1) / recipesPerPage + 1); - fontRendererObj.drawStringWithShadow(s, (xSize - fontRendererObj.getStringWidth(s)) / 2, 19, 0xffffff); + + if (!limitToOneRecipe && searchField.isVisible()) { + searchField.draw(mouseX - guiLeft, mouseY - guiTop); + s = NEIClientUtils.cropText( + fontRendererObj, + String.format("%d/%d", page + 1, (handler.numRecipes() - 1) / recipesPerPage + 1), + 45); + fontRendererObj.drawStringWithShadow( + s, + searchField.x + searchField.w + (44 - fontRendererObj.getStringWidth(s)) / 2, + 19, + 0xffffff); + } else { + s = NEIClientUtils.translate("recipe.page", page + 1, (handler.numRecipes() - 1) / recipesPerPage + 1); + fontRendererObj.drawStringWithShadow(s, (xSize - fontRendererObj.getStringWidth(s)) / 2, 19, 0xffffff); + } + } + final boolean drawItemPresence = NEIClientConfig.isJEIStyleItemPresenceOverlayVisible(); GL11.glPushMatrix(); GL11.glTranslatef(5, 32 - ySkip + yShift, 0); try (CompatibilityHacks compatibilityHacks = new CompatibilityHacks()) { - for (int i = page * recipesPerPage; i < handler.numRecipes() && i < (page + 1) * recipesPerPage; i++) { + + for (int i : getRecipeIndices()) { handler.drawForeground(i); - if (drawItemPresence && (isMouseOverOverlayButton(i - page * recipesPerPage) || limitToOneRecipe) - && firstGui != null - && firstGui.inventorySlots != null) { + + if (drawItemPresence && firstGui != null + && firstGui.inventorySlots != null + && (isMouseOverOverlayButton(i - page * recipesPerPage) || limitToOneRecipe)) { List ingredients = handler.getIngredientStacks(i); if (itemPresenceCacheRecipe != i || itemPresenceCacheSlots == null || itemPresenceCacheSlots.size() != ingredients.size()) { @@ -692,15 +981,16 @@ public void drawGuiContainerForegroundLayer(int mouseX, int mouseY) { LayoutManager.drawItemPresenceOverlay(stack.relx, stack.rely, isPresent); } } + GL11.glTranslatef(0, handlerInfo.getHeight(), 0); } + } GL11.glPopMatrix(); } @Override public void drawGuiContainerBackgroundLayer(float f, int mouseX, int mouseY) { - final int recipesPerPage = getRecipesPerPage(); final int ySkip = limitToOneRecipe ? 25 : 0; GL11.glColor4f(1, 1, 1, 1); @@ -732,10 +1022,12 @@ public void drawGuiContainerBackgroundLayer(float f, int mouseX, int mouseY) { GL11.glPushMatrix(); GL11.glTranslatef(guiLeft + 5, guiTop - ySkip + 32 + yShift, 0); try (CompatibilityHacks compatibilityHacks = new CompatibilityHacks()) { - for (int i = page * recipesPerPage; i < handler.numRecipes() && i < (page + 1) * recipesPerPage; i++) { + + for (int i : getRecipeIndices()) { handler.drawBackground(i); GL11.glTranslatef(0, handlerInfo.getHeight(), 0); } + } GL11.glPopMatrix(); } @@ -808,10 +1100,16 @@ private int getRecipesPerPage() { } private int getRecipesPerPage(HandlerInfo handlerInfo) { - if (handlerInfo != null) return Math.max( - Math.min(((ySize - (buttonHeight * 3)) / handlerInfo.getHeight()), handlerInfo.getMaxRecipesPerPage()), - 1); - else return (handler.recipiesPerPage()); + if (handlerInfo != null) { + return Math.max( + Math.min( + ((ySize - (buttonHeight * 3)) / handlerInfo.getHeight()), + handlerInfo.getMaxRecipesPerPage()), + 1); + } else if (handler != null) { + return handler.recipiesPerPage(); + } + return 1; } public Point getRecipePosition(int recipe) { @@ -844,9 +1142,31 @@ public List getInventoryAreas(GuiContainer gui) { @Override public boolean handleDragNDrop(GuiContainer gui, int mousex, int mousey, ItemStack draggedStack, int button) { + + if (!limitToOneRecipe && handler != null + && new Rectangle(borderPadding + buttonWidth - 2, 15, xSize - (borderPadding + buttonWidth) * 2 + 2, 16) + .contains(mousex - guiLeft, mousey - guiTop) + && IRecipeFilterProvider.filteringAvailable(handler)) { + final FluidStack fluidStack = StackInfo.getFluid(draggedStack); + + if (fluidStack != null) { + searchField.setText(formattingText(fluidStack.getLocalizedName())); + } else { + searchField.setText(formattingText(draggedStack.getDisplayName())); + } + + searchField.setVisible(true); + return true; + } + return false; } + protected String formattingText(String displayName) { + return Pattern.compile("[{}()\\[\\].+*?^$\\\\|]") + .matcher(EnumChatFormatting.getTextWithoutFormattingCodes(displayName)).replaceAll("\\\\$0"); + } + @Override public boolean hideItemPanelSlot(GuiContainer gui, int x, int y, int w, int h) { // Because some of the handlers *cough avaritia* are oversized @@ -855,11 +1175,11 @@ public boolean hideItemPanelSlot(GuiContainer gui, int x, int y, int w, int h) { protected static BookmarkRecipeId getCurrentRecipe(GuiScreen gui) { - if (gui instanceof GuiRecipe) { + if (gui instanceof GuiRecipe && !((GuiRecipe) gui).handler.isEmpty()) { GuiRecipe gRecipe = (GuiRecipe) gui; return new BookmarkRecipeId( gRecipe.handlerInfo.getHandlerName(), - gRecipe.getHandler().getIngredientStacks(gRecipe.page * gRecipe.getRecipesPerPage())); + gRecipe.handler.getIngredientStacks(gRecipe.page * gRecipe.getRecipesPerPage())); } return null; diff --git a/src/main/java/codechicken/nei/recipe/GuiRecipeTabs.java b/src/main/java/codechicken/nei/recipe/GuiRecipeTabs.java index b7f852de0..75e0797f0 100644 --- a/src/main/java/codechicken/nei/recipe/GuiRecipeTabs.java +++ b/src/main/java/codechicken/nei/recipe/GuiRecipeTabs.java @@ -43,7 +43,7 @@ public void initLayout() { categoriesPerPage = 0; numHandlers = guiRecipe.currenthandlers.size(); - for (IRecipeHandler handler : guiRecipe.currenthandlers) { + for (int i = 0; i < numHandlers; i++) { if (totalWidth + tabWidth < (guiRecipe.xSize - 4)) { totalWidth += tabWidth; categoriesPerPage++; @@ -79,9 +79,11 @@ public void refreshPage() { IRecipeHandler handler = guiRecipe.currenthandlers.get(index); int tabX = area.x + (i * tabWidth); - if (NEIClientConfig.useCreativeTabStyle()) + if (NEIClientConfig.useCreativeTabStyle()) { tabs.add(new GuiRecipeTabCreative(guiRecipe, handler, tabX, area.y)); - else tabs.add(new GuiRecipeTabJEI(guiRecipe, handler, tabX, area.y)); + } else { + tabs.add(new GuiRecipeTabJEI(guiRecipe, handler, tabX, area.y)); + } } // Maybe add buttons diff --git a/src/main/java/codechicken/nei/recipe/GuiUsageRecipe.java b/src/main/java/codechicken/nei/recipe/GuiUsageRecipe.java index 9a1b66662..fd4ba184f 100644 --- a/src/main/java/codechicken/nei/recipe/GuiUsageRecipe.java +++ b/src/main/java/codechicken/nei/recipe/GuiUsageRecipe.java @@ -26,19 +26,17 @@ public static boolean openRecipeGui(String inputId, Object... ingredients) { Minecraft mc = NEIClientUtils.mc(); BookmarkRecipeId recipeId = getCurrentRecipe(mc.currentScreen); - GuiUsageRecipe gui = new GuiUsageRecipe(handlers, recipeId); + GuiUsageRecipe gui = new GuiUsageRecipe(handlers); mc.displayGuiScreen(gui); - - gui.openTargetRecipe(gui.recipeId); + gui.openTargetRecipe(recipeId); return true; } - private GuiUsageRecipe(ArrayList handlers, BookmarkRecipeId recipeId) { + private GuiUsageRecipe(ArrayList handlers) { super(NEIClientUtils.mc().currentScreen); this.currenthandlers = handlers; - this.recipeId = recipeId; } public static void registerUsageHandler(IUsageHandler handler) { diff --git a/src/main/java/codechicken/nei/recipe/IRecipeHandler.java b/src/main/java/codechicken/nei/recipe/IRecipeHandler.java index 76fb41aa0..88c415bf4 100644 --- a/src/main/java/codechicken/nei/recipe/IRecipeHandler.java +++ b/src/main/java/codechicken/nei/recipe/IRecipeHandler.java @@ -50,16 +50,24 @@ default String getRecipeTabName() { */ int numRecipes(); + /** + * + * @return true if this list contains no elements + */ + default boolean isEmpty() { + return numRecipes() == 0; + } + /** * Draw the background of this recipe handler (basically the slot layout image). - * + * * @param recipe The recipe index to draw at this position. */ void drawBackground(int recipe); /** * Draw the foreground of this recipe handler (for things like progress bars). - * + * * @param recipe The recipe index to draw at this position. */ void drawForeground(int recipe); @@ -74,11 +82,11 @@ default String getRecipeTabName() { /** * - * @param recipetype The recipe index to get items for. + * @param recipe The recipe index to get items for. * @return A list of the other {@link PositionedStack}s in this recipe relative to the top left corner of your * recipe drawing space. For example fuel in furnaces. */ - List getOtherStacks(int recipetype); + List getOtherStacks(int recipe); /** * diff --git a/src/main/java/codechicken/nei/recipe/ProfilerRecipeHandler.java b/src/main/java/codechicken/nei/recipe/ProfilerRecipeHandler.java index 4da62de1d..4bab4e2e0 100644 --- a/src/main/java/codechicken/nei/recipe/ProfilerRecipeHandler.java +++ b/src/main/java/codechicken/nei/recipe/ProfilerRecipeHandler.java @@ -80,7 +80,7 @@ public ArrayList getIngredientStacks(int recipe) { } @Override - public ArrayList getOtherStacks(int recipetype) { + public ArrayList getOtherStacks(int recipe) { return new ArrayList<>(); } diff --git a/src/main/java/codechicken/nei/recipe/RecipeHandlerQuery.java b/src/main/java/codechicken/nei/recipe/RecipeHandlerQuery.java index bb837a286..7a1bd1fd4 100644 --- a/src/main/java/codechicken/nei/recipe/RecipeHandlerQuery.java +++ b/src/main/java/codechicken/nei/recipe/RecipeHandlerQuery.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.concurrent.ExecutionException; import java.util.function.Function; +import java.util.regex.Matcher; import java.util.stream.Collectors; import net.minecraft.client.Minecraft; @@ -34,9 +35,11 @@ class RecipeHandlerQuery { ArrayList runWithProfiling(String profilerSection) { TaskProfiler profiler = ProfilerRecipeHandler.getProfiler(); + TemplateRecipeHandler.disableCycledIngredients = true; profiler.start(profilerSection); try { ArrayList handlers = getRecipeHandlersParallel(); + if (error) { displayRecipeLookupError(); } @@ -46,6 +49,7 @@ ArrayList runWithProfiling(String profilerSection) { displayRecipeLookupError(); return new ArrayList<>(0); } finally { + TemplateRecipeHandler.disableCycledIngredients = false; profiler.end(); } } @@ -60,27 +64,35 @@ private ArrayList getRecipeHandlersParallel() throws InterruptedException, Ex } private ArrayList getSerialHandlersWithRecipes() { + return serialRecipeHandlers.stream().map(handler -> { try { - return recipeHandlerFunction.apply(handler); + return isHidden(handler) ? null : recipeHandlerFunction.apply(handler); } catch (Throwable t) { printLog(t); error = true; return null; } - }).filter(h -> h != null && h.numRecipes() > 0).collect(Collectors.toCollection(ArrayList::new)); + }).filter(h -> h != null && !h.isEmpty()).collect(Collectors.toCollection(ArrayList::new)); } private ArrayList getHandlersWithRecipes() throws InterruptedException, ExecutionException { + return ItemList.forkJoinPool.submit(() -> recipeHandlers.parallelStream().map(handler -> { try { - return recipeHandlerFunction.apply(handler); + return isHidden(handler) ? null : recipeHandlerFunction.apply(handler); } catch (Throwable t) { printLog(t); error = true; return null; } - }).filter(h -> h != null && h.numRecipes() > 0).collect(Collectors.toCollection(ArrayList::new))).get(); + }).filter(h -> h != null && !h.isEmpty()).collect(Collectors.toCollection(ArrayList::new))).get(); + } + + private boolean isHidden(T handler) { + return NEIClientConfig.hiddenHandlerRegex.stream() + .map(pattern -> pattern.matcher(GuiRecipeTab.getHandlerInfo(handler).getHandlerName())) + .anyMatch(Matcher::matches); } private void printLog(Throwable t) { diff --git a/src/main/java/codechicken/nei/recipe/StackInfo.java b/src/main/java/codechicken/nei/recipe/StackInfo.java index b1164aec9..7adabb5d9 100644 --- a/src/main/java/codechicken/nei/recipe/StackInfo.java +++ b/src/main/java/codechicken/nei/recipe/StackInfo.java @@ -8,14 +8,18 @@ import java.net.URL; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.WeakHashMap; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; +import net.minecraftforge.fluids.FluidContainerRegistry; import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.IFluidContainerItem; import org.apache.commons.io.IOUtils; @@ -29,6 +33,13 @@ public class StackInfo { public static final ArrayList stackStringifyHandlers = new ArrayList<>(); private static final HashMap> guidfilters = new HashMap<>(); private static final WeakHashMap guidcache = new WeakHashMap<>(); + private static final LinkedHashMap fluidcache = new LinkedHashMap() { + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > 20; + } + }; static { stackStringifyHandlers.add(new DefaultStackStringifyHandler()); @@ -49,11 +60,24 @@ public static NBTTagCompound itemStackToNBT(ItemStack stack, boolean saveStackSi return nbTag; } + public static ItemStack loadFromNBT(NBTTagCompound nbtTag, long customCount) { + + if (nbtTag != null) { + nbtTag = (NBTTagCompound) nbtTag.copy(); + nbtTag.setInteger("Count", Math.max(customCount <= Integer.MAX_VALUE ? (int) customCount : 0, 0)); + return loadFromNBT(nbtTag); + } + + return null; + } + public static ItemStack loadFromNBT(NBTTagCompound nbtTag) { ItemStack stack = null; - for (int i = stackStringifyHandlers.size() - 1; i >= 0 && nbtTag != null && stack == null; i--) { - stack = stackStringifyHandlers.get(i).convertNBTToItemStack(nbtTag); + if (nbtTag != null) { + for (int i = stackStringifyHandlers.size() - 1; i >= 0 && stack == null; i--) { + stack = stackStringifyHandlers.get(i).convertNBTToItemStack(nbtTag); + } } return stack; @@ -76,15 +100,24 @@ public static boolean equalItemAndNBT(ItemStack stackA, ItemStack stackB, boolea } public static FluidStack getFluid(ItemStack stack) { - FluidStack fluid = null; + FluidStack fluid = fluidcache.get(stack); + + if (fluid == null && !fluidcache.containsKey(stack)) { + + for (int i = stackStringifyHandlers.size() - 1; i >= 0 && fluid == null; i--) { + fluid = stackStringifyHandlers.get(i).getFluid(stack); + } - for (int i = stackStringifyHandlers.size() - 1; i >= 0 && fluid == null; i--) { - fluid = stackStringifyHandlers.get(i).getFluid(stack); + fluidcache.put(stack, fluid); } return fluid; } + public static boolean isFluidContainer(ItemStack stack) { + return stack.getItem() instanceof IFluidContainerItem || FluidContainerRegistry.isContainer(stack); + } + public static String getItemStackGUID(ItemStack stack) { if (!guidcache.containsKey(stack)) { diff --git a/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java b/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java index c9b8c599f..22f2436af 100644 --- a/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java +++ b/src/main/java/codechicken/nei/recipe/TemplateRecipeHandler.java @@ -18,6 +18,8 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; import net.minecraft.client.gui.inventory.GuiContainer; import net.minecraft.init.Blocks; @@ -34,14 +36,17 @@ import codechicken.lib.vec.Rectangle4i; import codechicken.nei.ItemList; +import codechicken.nei.ItemList.AllMultiItemFilter; import codechicken.nei.NEIClientConfig; import codechicken.nei.NEIClientUtils; import codechicken.nei.NEIServerUtils; import codechicken.nei.PositionedStack; import codechicken.nei.api.DefaultOverlayRenderer; import codechicken.nei.api.IOverlayHandler; +import codechicken.nei.api.IRecipeFilter; import codechicken.nei.api.IRecipeOverlayRenderer; import codechicken.nei.api.IStackPositioner; +import codechicken.nei.api.ItemFilter; import codechicken.nei.guihook.GuiContainerManager; import codechicken.nei.guihook.IContainerInputHandler; import codechicken.nei.guihook.IContainerTooltipHandler; @@ -174,7 +179,7 @@ public PositionedStack getOtherStack() { /** * This will perform default cycling of ingredients, mulitItem capable - * + * * @param cycle Current cycle step * @param ingredients List of ItemStacks to cycle * @return The provided list of ingredients, with their permutations cycled to a different permutation, if one @@ -182,30 +187,57 @@ public PositionedStack getOtherStack() { */ public List getCycledIngredients(int cycle, List ingredients) { - if (!NEIClientUtils.shiftKey()) { + if (!NEIClientUtils.shiftKey() && !disableCycledIngredients) { if (NEIClientConfig.useJEIStyledCycledIngredients()) { jeiStyledRenderPermutation(ingredients, cycle); } else { - for (int itemIndex = 0; itemIndex < ingredients.size(); itemIndex++) { - randomRenderPermutation(ingredients.get(itemIndex), cycle + itemIndex); - } + randomRenderPermutation(ingredients, cycle); } } return ingredients; } - public void randomRenderPermutation(PositionedStack stack, long cycle) { - Random rand = new Random(cycle + offset); - stack.setPermutationToRender(Math.abs(rand.nextInt()) % stack.items.length); + protected void randomRenderPermutation(List stacks, long cycle) { + final ItemFilter filter = new AllMultiItemFilter( + ItemList.getItemListFilter(), + GuiRecipe.searchField.getFilter()); + final int randCycle = Math.abs(new Random(cycle + offset).nextInt()); + + for (PositionedStack stack : stacks) { + if (stack.items.length <= 1) continue; + ArrayList filtered = getFilteredIngredientAlternatives(stack, filter); + if (filtered.isEmpty()) { + stack.setPermutationToRender((int) (randCycle % stack.items.length)); + } else { + stack.setPermutationToRender(filtered.get((int) (randCycle % filtered.size()))); + } + } + } - public void jeiStyledRenderPermutation(List stacks, long cycle) { + protected void jeiStyledRenderPermutation(List stacks, long cycle) { + final ItemFilter filter = new AllMultiItemFilter( + ItemList.getItemListFilter(), + GuiRecipe.searchField.getFilter()); + for (PositionedStack stack : stacks) { - stack.setPermutationToRender((int) (cycle % stack.items.length)); + if (stack.items.length <= 1) continue; + ArrayList filtered = getFilteredIngredientAlternatives(stack, filter); + if (filtered.isEmpty()) { + stack.setPermutationToRender((int) (cycle % stack.items.length)); + } else { + stack.setPermutationToRender(filtered.get((int) (cycle % filtered.size()))); + } } } + @Deprecated + protected void randomRenderPermutation(PositionedStack stack, long cycle) { + Random rand = new Random(cycle + offset); + stack.setPermutationToRender((int) (Math.abs(rand.nextInt()) % stack.items.length)); + } + public void setIngredientPermutation(Collection ingredients, ItemStack ingredient) { for (PositionedStack stack : ingredients) { for (int i = 0; i < stack.items.length; i++) { @@ -396,11 +428,29 @@ public void onMouseDragged(GuiContainer gui, int mousex, int mousey, int button, */ public LinkedList transferRects = new LinkedList<>(); + public static boolean disableCycledIngredients = false; + + protected ArrayList filteredRecipes; + + protected ArrayList searchRecipes; + public TemplateRecipeHandler() { loadTransferRects(); RecipeTransferRectHandler.registerRectsToGuis(getRecipeTransferRectGuis(), transferRects); } + protected static ArrayList getFilteredIngredientAlternatives(PositionedStack stack, ItemFilter filter) { + final ArrayList filtered = new ArrayList<>(); + + for (int i = 0; i < stack.items.length; i++) { + if (filter.matches(stack.items[i])) { + filtered.add(i); + } + } + + return filtered; + } + /** * Add all RecipeTransferRects to the transferRects list during this call. Afterward they may be added to the input * handler for the corresponding guis from getRecipeTransferRectGuis @@ -594,8 +644,129 @@ public String specifyTransferRect() { return null; } + public boolean isEmpty() { + + if (arecipes.isEmpty()) { + return true; + } + + final IRecipeFilter filter = GuiRecipe.getRecipeListFilter(this); + + if (filter == null) { + return false; + } + + final boolean disableCycledIngredients = TemplateRecipeHandler.disableCycledIngredients; + TemplateRecipeHandler.disableCycledIngredients = true; + + boolean isEmpty = !IntStream.range(0, arecipes.size()).boxed().anyMatch( + recipe -> filter.matches( + this, + arecipes.get(recipe).getIngredients(), + arecipes.get(recipe).getResult(), + arecipes.get(recipe).getOtherStacks())); + + TemplateRecipeHandler.disableCycledIngredients = disableCycledIngredients; + + return isEmpty; + } + + protected synchronized void applyFilter() { + + if (arecipes.isEmpty() && this.filteredRecipes == null) { + this.filteredRecipes = new ArrayList<>(); + } + + if (this.filteredRecipes != null) { + return; + } + + final Stream items = IntStream.range(0, arecipes.size()).boxed(); + final IRecipeFilter filter = GuiRecipe.getRecipeListFilter(this); + + if (filter == null) { + this.filteredRecipes = items.collect(Collectors.toCollection(ArrayList::new)); + } else { + final boolean disableCycledIngredients = TemplateRecipeHandler.disableCycledIngredients; + TemplateRecipeHandler.disableCycledIngredients = true; + + this.filteredRecipes = items + .filter( + recipe -> filter.matches( + this, + arecipes.get(recipe).getIngredients(), + arecipes.get(recipe).getResult(), + arecipes.get(recipe).getOtherStacks())) + .collect(Collectors.toCollection(ArrayList::new)); + + TemplateRecipeHandler.disableCycledIngredients = disableCycledIngredients; + } + + } + + public void applySearch(ArrayList searchRecipes) { + this.searchRecipes = searchRecipes; + } + + public ArrayList getSearchResult(IRecipeFilter filter) { + if (filteredRecipes == null) { + applyFilter(); + } + + ArrayList filtered = null; + + if (!filteredRecipes.isEmpty()) { + final ArrayList recipes = IntStream.range(0, filteredRecipes.size()).boxed() + .collect(Collectors.toCollection(ArrayList::new)); + final boolean disableCycledIngredients = TemplateRecipeHandler.disableCycledIngredients; + TemplateRecipeHandler.disableCycledIngredients = true; + + try { + filtered = ItemList.forkJoinPool.submit( + () -> recipes.parallelStream() + .filter( + (recipe) -> filter.matches( + this, + arecipes.get(filteredRecipes.get(recipe)).getIngredients(), + arecipes.get(filteredRecipes.get(recipe)).getResult(), + arecipes.get(filteredRecipes.get(recipe)).getOtherStacks())) + .collect(Collectors.toCollection(ArrayList::new))) + .get(); + filtered.sort((a, b) -> a - b); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + + TemplateRecipeHandler.disableCycledIngredients = disableCycledIngredients; + } + + return filtered; + } + + private int ref(int index) { + + if (filteredRecipes == null) { + applyFilter(); + } + + if (searchRecipes != null) { + index = searchRecipes.get(index); + } + + return filteredRecipes.get(index); + } + public int numRecipes() { - return arecipes.size(); + + if (filteredRecipes == null) { + applyFilter(); + } + + if (searchRecipes != null) { + return searchRecipes.size(); + } + + return filteredRecipes.size(); } public void drawBackground(int recipe) { @@ -612,19 +783,19 @@ public void drawForeground(int recipe) { } public List getIngredientStacks(int recipe) { - return arecipes.get(recipe).getIngredients(); + return arecipes.get(ref(recipe)).getIngredients(); } public PositionedStack getResultStack(int recipe) { try { - return arecipes.get(recipe).getResult(); + return arecipes.get(ref(recipe)).getResult(); } catch (ArrayIndexOutOfBoundsException ignored) { return null; } } public List getOtherStacks(int recipe) { - return arecipes.get(recipe).getOtherStacks(); + return arecipes.get(ref(recipe)).getOtherStacks(); } public void onUpdate() { @@ -680,8 +851,12 @@ public boolean keyTyped(GuiRecipe gui, char keyChar, int keyCode, int recipe) @Override public boolean mouseClicked(GuiRecipe gui, int button, int recipe) { - if (button == 0) return transferRect(gui, recipe, false); - else if (button == 1) return transferRect(gui, recipe, true); + + if (button == 0) { + return transferRect(gui, recipe, false); + } else if (button == 1) { + return transferRect(gui, recipe, true); + } return false; } @@ -690,29 +865,60 @@ public boolean mouseClicked(GuiRecipe gui, int button, int recipe) { public boolean mouseScrolled(GuiRecipe gui, int scroll, int recipe) { if (!NEIClientUtils.shiftKey()) return false; - final Point offset = gui.getRecipePosition(recipe); final Point pos = getMousePosition(); + final Point offset = gui.getRecipePosition(recipe); final Point relMouse = new Point(pos.x - gui.guiLeft - offset.x, pos.y - gui.guiTop - offset.y); + final PositionedStack overStack = getIngredientMouseOver(relMouse.x, relMouse.y, recipe); - for (PositionedStack pStack : getIngredientStacks(recipe)) { - if ((new Rectangle4i(pStack.relx, pStack.rely, 18, 18)).contains(relMouse.x, relMouse.y)) { - int index = pStack.items.length + scroll; + if (overStack != null && overStack.items.length > 1) { + final ItemFilter filter = new AllMultiItemFilter( + ItemList.getItemListFilter(), + GuiRecipe.searchField.getFilter()); - for (int i = 0; i < pStack.items.length; i++) { - if (NEIServerUtils.areStacksSameTypeCraftingWithNBT(pStack.items[i], pStack.item)) { - index = index + i; - break; - } + if (NEIClientConfig.useJEIStyledCycledIngredients()) { + ItemStack stack = overStack.item; + for (PositionedStack pStack : getIngredientStacks(recipe)) { + shiftPermutationToRender(pStack, stack, scroll, filter); } - - pStack.setPermutationToRender(index % pStack.items.length); - return true; + } else { + shiftPermutationToRender(overStack, overStack.item, scroll, filter); } + + return true; } return false; } + private void shiftPermutationToRender(PositionedStack pStack, ItemStack stack, int scroll, ItemFilter filter) { + + for (int index = 0; index < pStack.items.length; index++) { + if (NEIServerUtils.areStacksSameTypeCraftingWithNBT(pStack.items[index], stack)) { + ArrayList filtered = getFilteredIngredientAlternatives(pStack, filter); + + if (filtered.isEmpty()) { + pStack.setPermutationToRender((int) ((pStack.items.length + scroll + index) % pStack.items.length)); + } else { + pStack.setPermutationToRender( + filtered.get((int) ((filtered.size() + scroll + index) % filtered.size()))); + } + + break; + } + } + } + + private PositionedStack getIngredientMouseOver(int mousex, int mousey, int recipe) { + + for (PositionedStack pStack : getIngredientStacks(recipe)) { + if ((new Rectangle4i(pStack.relx, pStack.rely, 18, 18)).contains(mousex, mousey)) { + return pStack; + } + } + + return null; + } + private boolean transferRect(GuiRecipe gui, int recipe, boolean usage) { Point offset = gui.getRecipePosition(recipe); return transferRect(gui, transferRects, offset.x, offset.y, usage); diff --git a/src/main/java/codechicken/nei/recipe/stackinfo/DefaultStackStringifyHandler.java b/src/main/java/codechicken/nei/recipe/stackinfo/DefaultStackStringifyHandler.java index 4b1b36070..37975c108 100644 --- a/src/main/java/codechicken/nei/recipe/stackinfo/DefaultStackStringifyHandler.java +++ b/src/main/java/codechicken/nei/recipe/stackinfo/DefaultStackStringifyHandler.java @@ -21,7 +21,7 @@ public NBTTagCompound convertItemStackToNBT(ItemStack stack, boolean saveStackSi final NBTTagCompound nbTag = new NBTTagCompound(); nbTag.setString("strId", strId); - nbTag.setInteger("Count", Math.max(saveStackSize ? stack.stackSize : 1, 1)); + nbTag.setInteger("Count", saveStackSize ? stack.stackSize : 1); nbTag.setShort("Damage", (short) stack.getItemDamage()); if (stack.hasTagCompound()) { diff --git a/src/main/java/codechicken/nei/recipe/stackinfo/GTFluidStackStringifyHandler.java b/src/main/java/codechicken/nei/recipe/stackinfo/GTFluidStackStringifyHandler.java index abcd72b2f..605fc8351 100644 --- a/src/main/java/codechicken/nei/recipe/stackinfo/GTFluidStackStringifyHandler.java +++ b/src/main/java/codechicken/nei/recipe/stackinfo/GTFluidStackStringifyHandler.java @@ -2,6 +2,7 @@ import java.lang.reflect.Method; +import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraftforge.fluids.Fluid; @@ -9,16 +10,18 @@ import net.minecraftforge.fluids.FluidStack; import codechicken.nei.api.IStackStringifyHandler; +import cpw.mods.fml.common.registry.GameRegistry; public class GTFluidStackStringifyHandler implements IStackStringifyHandler { - public static Class GTDisplayFluid = null; - public static Method getFluidDisplayStack = null; - public static Method getFluidFromDisplayStack = null; + protected static Class GTDisplayFluid = null; + protected static Method getFluidDisplayStack = null; + protected static Method getFluidFromDisplayStack = null; + public static boolean replaceAE2FCFluidDrop = false; static { try { - final Class gtUtility = Class.forName("gregtech.api.util.GT_Utility"); + final Class gtUtility = Class.forName("gregtech.api.util.GT_Utility"); GTDisplayFluid = Class.forName("gregtech.common.items.GT_FluidDisplayItem"); getFluidFromDisplayStack = gtUtility.getMethod("getFluidFromDisplayStack", ItemStack.class); @@ -31,13 +34,13 @@ public class GTFluidStackStringifyHandler implements IStackStringifyHandler { public NBTTagCompound convertItemStackToNBT(ItemStack stack, boolean saveStackSize) { - if (GTDisplayFluid != null && GTDisplayFluid.isInstance(stack.getItem())) { + if (replaceAE2FCFluidDrop || stack.getItem() != GameRegistry.findItem("ae2fc", "fluid_drop")) { final FluidStack fluidStack = getFluid(stack); if (fluidStack != null) { final NBTTagCompound nbTag = new NBTTagCompound(); nbTag.setString("gtFluidName", fluidStack.getFluid().getName()); - nbTag.setInteger("Count", saveStackSize ? fluidStack.amount : 1); + nbTag.setInteger("Count", saveStackSize ? fluidStack.amount : 144); return nbTag; } } @@ -66,17 +69,33 @@ public ItemStack convertNBTToItemStack(NBTTagCompound nbtTag) { } public FluidStack getFluid(ItemStack stack) { + final Item item = stack.getItem(); - if (getFluidFromDisplayStack != null && GTDisplayFluid != null && GTDisplayFluid.isInstance(stack.getItem())) { - try { + try { + if (getFluidFromDisplayStack != null && GTDisplayFluid != null && GTDisplayFluid.isInstance(item)) { final Object obj = getFluidFromDisplayStack.invoke(null, stack); if (obj != null) { return (FluidStack) obj; } - } catch (Exception e) {} - } + } else if (item == GameRegistry.findItem("ae2fc", "fluid_packet")) { + NBTTagCompound nbtTag = stack.getTagCompound(); + + return FluidStack.loadFluidStackFromNBT((NBTTagCompound) nbtTag.getTag("FluidStack")); + } else if (item == GameRegistry.findItem("ae2fc", "fluid_drop")) { + NBTTagCompound nbtTag = stack.getTagCompound(); + Fluid fluid = FluidRegistry.getFluid(nbtTag.getString("Fluid").toLowerCase()); + + if (fluid != null) { + FluidStack fluidStack = new FluidStack(fluid, stack.stackSize); + if (nbtTag.hasKey("FluidTag")) { + fluidStack.tag = nbtTag.getCompoundTag("FluidTag"); + } + return fluidStack; + } + } + } catch (Exception e) {} return null; } diff --git a/src/main/resources/assets/nei/lang/en_US.lang b/src/main/resources/assets/nei/lang/en_US.lang index 4b7e9417c..9f3fb4eae 100644 --- a/src/main/resources/assets/nei/lang/en_US.lang +++ b/src/main/resources/assets/nei/lang/en_US.lang @@ -155,12 +155,11 @@ nei.options.inventory.jei_style_recipe_catalyst.false=No nei.options.inventory.saveCurrentRecipeInBookmarksEnabled=Save Current Recipe in Bookmarks nei.options.inventory.saveCurrentRecipeInBookmarksEnabled.true=Enabled nei.options.inventory.saveCurrentRecipeInBookmarksEnabled.false=Disabled -nei.options.inventory.useNBTInBookmarks=Use NBT in Bookmarks -nei.options.inventory.useNBTInBookmarks.true=Yes -nei.options.inventory.useNBTInBookmarks.false=No -nei.options.inventory.recipeTooltipsEnabled=Show recipe tooltips in Bookmarks -nei.options.inventory.recipeTooltipsEnabled.true=Yes -nei.options.inventory.recipeTooltipsEnabled.false=No +nei.options.inventory.recipeTooltipsMode=Show recipe tooltips in Bookmarks +nei.options.inventory.recipeTooltipsMode.0=None +nei.options.inventory.recipeTooltipsMode.1=In Horizontal View +nei.options.inventory.recipeTooltipsMode.2=In Vertical View +nei.options.inventory.recipeTooltipsMode.3=Always nei.options.inventory.bookmarksAnimationEnabled=REI Style Animation in Bookmarks nei.options.inventory.bookmarksAnimationEnabled.true=Yes nei.options.inventory.bookmarksAnimationEnabled.false=No @@ -288,6 +287,7 @@ nei.options.tools.dump.handlers.mode.1=Json nei.recipe.page=Page %s/%s nei.recipe.tooltip=Recipes +nei.recipe.search.show=Click to Search nei.recipe.shaped=Shaped Crafting nei.recipe.shapeless=Shapeless Crafting nei.recipe.furnace=Smelting @@ -348,6 +348,8 @@ nei.presets.blacklist.label=B nei.presets.blacklist.tooltip=Blacklist nei.presets.whitelist.label=W nei.presets.whitelist.tooltip=Whitelist +nei.presets.blacklistPlus.label=B+ +nei.presets.blacklistPlus.tooltip=Blacklist Plus nei.presets.textfield.tooltip.1=Enter - save nei.presets.textfield.tooltip.2=Escape - cancel nei.presets.label.tooltip=Double click to edit diff --git a/src/main/resources/assets/nei/lang/zh_CN.lang b/src/main/resources/assets/nei/lang/zh_CN.lang index 682c1f185..e43edea70 100644 --- a/src/main/resources/assets/nei/lang/zh_CN.lang +++ b/src/main/resources/assets/nei/lang/zh_CN.lang @@ -147,9 +147,6 @@ nei.options.inventory.jei_style_recipe_catalyst.false=否 nei.options.inventory.saveCurrentRecipeInBookmarksEnabled=保存书签中的当前合成表 nei.options.inventory.saveCurrentRecipeInBookmarksEnabled.true=启用 nei.options.inventory.saveCurrentRecipeInBookmarksEnabled.false=禁用 -nei.options.inventory.useNBTInBookmarks=在书签中启用NBT -nei.options.inventory.useNBTInBookmarks.true=是 -nei.options.inventory.useNBTInBookmarks.false=否 nei.options.inventory.bookmarksAnimationEnabled=REI风格书签动画 nei.options.inventory.bookmarksAnimationEnabled.true=是 nei.options.inventory.bookmarksAnimationEnabled.false=否 @@ -271,7 +268,7 @@ nei.recipe.furnace=烧制 nei.recipe.fuel=燃料 nei.recipe.fuel.smeltCount=可冶炼%s个物品 # Deprecated -nei.recipe.fuel.required=需要%s个 +nei.recipe.fuel.required=需要%s个 # Deprecated nei.recipe.fuel.processed=可烧制%s个 # Deprecated