package codechicken.nei.bookmark; import java.awt.Color; import java.awt.Point; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.WeakHashMap; import java.util.stream.Collectors; import net.minecraft.item.ItemStack; import org.lwjgl.opengl.GL11; import codechicken.lib.gui.GuiDraw; import codechicken.lib.vec.Rectangle4i; import codechicken.nei.BookmarkPanel.BookmarkViewMode; import codechicken.nei.ItemPanels; import codechicken.nei.ItemStackSet; import codechicken.nei.ItemsGrid; import codechicken.nei.ItemsGrid.MouseContext; import codechicken.nei.LayoutManager; import codechicken.nei.NEIClientConfig; import codechicken.nei.NEIClientUtils; import codechicken.nei.bookmark.BookmarkItem.BookmarkItemType; import codechicken.nei.recipe.Recipe; import codechicken.nei.recipe.Recipe.RecipeId; import codechicken.nei.recipe.Recipe.RecipeIngredient; import codechicken.nei.recipe.StackInfo; import codechicken.nei.recipe.chain.RecipeChainMath; public class BookmarkGrid extends ItemsGrid { public static class BookmarkMouseContext extends MouseContext { public final int groupId; public final RecipeId recipeId; public final boolean controlKey; public final boolean shiftKey; public BookmarkMouseContext(int slotIndex, int rowIndex, int columnIndex) { super(slotIndex, rowIndex, columnIndex); this.controlKey = NEIClientUtils.controlKey(); this.shiftKey = NEIClientUtils.shiftKey(); this.groupId = -1; this.recipeId = null; } public BookmarkMouseContext(int slotIndex, int rowIndex, int columnIndex, int groupId, RecipeId recipeId) { super(slotIndex, rowIndex, columnIndex); this.controlKey = NEIClientUtils.controlKey(); this.shiftKey = NEIClientUtils.shiftKey(); this.groupId = groupId; this.recipeId = recipeId; } @Override public int hashCode() { return Objects .hash(this.groupId, this.rowIndex, this.slotIndex, this.recipeId, this.controlKey, this.shiftKey); } public boolean equals(Object obj) { if (super.equals(obj) && obj instanceof BookmarkMouseContext hover) { return this.groupId == hover.groupId && this.controlKey == hover.controlKey && this.shiftKey == hover.shiftKey && (this.recipeId == hover.recipeId || this.recipeId != null && this.recipeId.equals(hover.recipeId)); } return false; } } protected static final Color DRAG_COLOR = new Color(0x66555555, true); public static final Color GROUP_CHAIN_COLOR = new Color(0x6645DA75, true); public static final Color GROUP_NONE_COLOR = new Color(0xff666666, true); public static final Color RECIPE_COLOR = new Color(0x99A033A0, true); public static final Color PLACEHOLDER_COLOR = new Color(0x66222222, true); public static final int DEFAULT_GROUP_ID = 0; public static final int GROUP_PANEL_WIDTH = 7; protected static final float SCALE_SPEED = 0.1f; protected List bookmarkItems = new ArrayList<>(); protected final WeakHashMap animation = new WeakHashMap<>(); protected final Map groups = new HashMap<>(); protected final BookmarkGridGenerator gridGenerator; private boolean isDirty = false; public BookmarkGrid() { this.groups.put(DEFAULT_GROUP_ID, new BookmarkGroup(BookmarkViewMode.DEFAULT)); this.gridGenerator = new BookmarkGridGenerator(this); } 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; onItemsChanged(); } } public void toggleViewMode(int groupId) { this.groups.get(groupId).toggleViewMode(); onItemsChanged(); } public void toggleCollapsedState(int groupId) { this.groups.get(groupId).toggleCollapsedState(); onItemsChanged(); } public void toggleCollapsedRecipe(int groupId, RecipeId recipeId) { final BookmarkGroup group = getGroup(groupId); final RecipeId collapsedRecipeId = group.crafting.recipeRelations.entrySet().stream() .filter(entry -> entry.getValue().contains(recipeId)).map(entry -> entry.getKey()).findAny() .orElse(recipeId); group.toggleCollapsedRecipe(collapsedRecipeId); if (group.crafting.recipeInMiddle.contains(collapsedRecipeId)) { if (group.collapsedRecipes.contains(collapsedRecipeId)) { for (BookmarkItem item : this.bookmarkItems) { if (item.groupId == groupId && collapsedRecipeId.equals(item.recipeId) && item.multiplier == 1) { item.multiplier = 0; } } } else { for (BookmarkItem item : this.bookmarkItems) { if (item.groupId == groupId && collapsedRecipeId.equals(item.recipeId) && item.multiplier == 0) { item.multiplier = 1; } } } } onItemsChanged(); } public boolean isCraftingMode(int groupId) { return this.groups.get(groupId).crafting != null; } public void setCraftingMode(int groupId, boolean on) { this.groups.get(groupId).setCraftingMode(on); onItemsChanged(); } public void toggleCraftingMode(int groupId) { this.groups.get(groupId).toggleCraftingMode(); onItemsChanged(); } public BookmarkGroup getGroup(int groupId) { return this.groups.get(groupId); } public int addGroup(BookmarkGroup group) { int groupId = this.bookmarkItems.stream().mapToInt(item -> item.groupId).max().orElse(DEFAULT_GROUP_ID) + 1; this.groups.put(groupId, group); return groupId; } @Override public int getNumPages() { if (this.gridGenerator.gridMask == null) { getMask(); } return this.gridGenerator.pageCount; } @Override public int size() { return this.bookmarkItems.size(); } @Override public boolean isEmpty() { return this.bookmarkItems.isEmpty(); } @Override public List getMask() { if (this.isDirty) { this.isDirty = false; this.bookmarkItems = new ArrayList<>(new LinkedHashSet<>(this.bookmarkItems)); sortIngredients(); this.realItems.clear(); for (BookmarkItem item : this.bookmarkItems) { this.realItems.add(item.itemStack); } } if (this.gridGenerator.gridMask != null) { return this.gridGenerator.gridMask; } this.gridGenerator.generate(); this.page = Math.max(0, Math.min(this.page, this.gridGenerator.pageCount - 1)); if (ItemPanels.bookmarkPanel.sortableItem != null) { ItemPanels.bookmarkPanel.sortableItem.update(); } return this.gridGenerator.gridMask; } @Override protected BookmarkMouseContext getMouseContext(int mousex, int mousey) { if (contains(mousex, mousey) && !LayoutManager.bookmarkPanel.inEditingState()) { return generateMouseContext(mousex, mousey); } return null; } private BookmarkMouseContext generateMouseContext(int mousex, int mousey) { final BookmarksGridSlot hovered = getSlotMouseOver(mousex, mousey); if (hovered != null) { return new BookmarkMouseContext( hovered.slotIndex, hovered.slotIndex / columns, hovered.slotIndex % columns, hovered.getGroupId(), this.gridGenerator.itemToRecipe.getOrDefault(hovered.itemIndex, hovered.getRecipeId())); } final int rowIndex = getHoveredRowIndex(false); if (rowIndex != -1) { final int groupId = getRowGroupId(rowIndex); final int rowItemIndex = getRowItemIndex(rowIndex, false); if (rowItemIndex != Integer.MIN_VALUE) { int columnIndex = getColumnIndex(mousex); BookmarkGroup group = getGroup(groupId); RecipeId recipeId = null; if (!group.collapsed) { recipeId = this.gridGenerator.itemToRecipe .getOrDefault(rowItemIndex, getCalculatedItem(rowItemIndex).recipeId); } return new BookmarkMouseContext( -1, rowIndex, columnIndex < 0 || columnIndex > this.columns ? -1 : columnIndex, groupId, recipeId); } } return null; } public int getRowGroupId(int rowIndex) { if (this.gridGenerator.gridMask == null) { getMask(); } return this.gridGenerator.getRowGroupId(rowIndex); } public int getRowItemIndex(int rowIndex, boolean firstItemIndex) { if (this.gridGenerator.gridMask == null) { getMask(); } int absoluteSlotIndex = (this.page * this.rows + rowIndex) * this.columns + (firstItemIndex ? 0 : this.columns - 1); for (int index = 0; index < this.columns; index++) { if (this.gridGenerator.slotToItem.containsKey(absoluteSlotIndex + index * (firstItemIndex ? 1 : -1))) { return this.gridGenerator.slotToItem.get(absoluteSlotIndex + index * (firstItemIndex ? 1 : -1)); } } return Integer.MIN_VALUE; } public int getHoveredRowIndex(boolean groupPanel) { final Point mouse = GuiDraw.getMousePosition(); final int leftBorder = marginLeft + paddingLeft; final int r = getRowIndex(mouse.y); if (this.isEmpty()) { return -1; } 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 - GROUP_PANEL_WIDTH, h); } private void sortIngredients() { for (int itemIndex = 0; itemIndex < this.bookmarkItems.size(); itemIndex++) { final BookmarkItem item = this.bookmarkItems.get(itemIndex); if (item.groupId != DEFAULT_GROUP_ID) { final List items = this.bookmarkItems.stream().filter(itm -> itm.groupId == item.groupId) .collect(Collectors.toList()); this.bookmarkItems.removeAll(items); this.bookmarkItems.addAll(itemIndex, items); itemIndex += items.size() - 1; } } for (Entry entry : this.groups.entrySet()) { final Map recipeState = new HashMap<>(); final BookmarkGroup group = entry.getValue(); final int groupId = entry.getKey(); if (group.viewMode == BookmarkViewMode.TODO_LIST || group.crafting != null) { sortItemsInsideGroup(groupId); } for (BookmarkItem item : this.bookmarkItems) { if (item.recipeId == null && item.type != BookmarkItemType.ITEM) { item.type = BookmarkItemType.ITEM; } else if (item.groupId == groupId && item.recipeId != null && item.type != BookmarkItemType.ITEM) { recipeState.put( item.recipeId, recipeState.getOrDefault(item.recipeId, 0) | (item.type == BookmarkItemType.INGREDIENT ? 1 : 2)); } } for (BookmarkItem item : this.bookmarkItems) { if (item.groupId == groupId && item.type != BookmarkItemType.ITEM && recipeState.getOrDefault(item.recipeId, 0) != 3) { if (item.type == BookmarkItemType.INGREDIENT) { item.recipeId = null; } recipeState.remove(item.recipeId); item.type = BookmarkItemType.ITEM; } } group.collapsedRecipes.removeIf(recipeId -> !recipeState.containsKey(recipeId)); if (group.crafting != null) { group.crafting.refresh(createChainItems(groupId), group.collapsedRecipes); } } } public void sortItemsInsideGroup(int groupId) { final int size = this.bookmarkItems.size(); int itemIndex = 0; while (itemIndex < size) { final BookmarkItem item = this.bookmarkItems.get(itemIndex); if (item.groupId == groupId && item.recipeId != null) { final Map sortingRank = new HashMap<>(); final List sortedItems = new ArrayList<>(); final List items = new ArrayList<>(); for (int index = itemIndex; index < size; index++) { if (item.equalsRecipe(this.bookmarkItems.get(index))) { sortingRank .put(index, this.bookmarkItems.get(index).type == BookmarkItemType.INGREDIENT ? 1 : -1); items.add(index); } } items.sort((a, b) -> sortingRank.get(a) - sortingRank.get(b)); for (int index : items) { sortedItems.add(this.bookmarkItems.get(index)); } this.bookmarkItems.removeAll(sortedItems); this.bookmarkItems.addAll(itemIndex, sortedItems); itemIndex += sortedItems.size(); } else { itemIndex++; } } } @Override public void draw(int mousex, int mousey) { if (getPerPage() == 0) { return; } if (this.gridGenerator.gridMask == null) { getMask(); } if (!this.isEmpty() && this.gridGenerator.maxAbsoluteSlotIndex > 0) { final GroupingItem groupingItem = ItemPanels.bookmarkPanel.groupingItem; final int sortableGroupId = ItemPanels.bookmarkPanel.sortableGroup != null ? ItemPanels.bookmarkPanel.sortableGroup.groupId : -3; final int rowShift = this.page * this.rows; Map groups = this.gridGenerator.rowToGroupId; if (groupingItem != null && groupingItem.hasEndRow()) { groups = groupingItem.prepareGroups(groups); } else if (sortableGroupId != -3) { for (int rowIndex = 0; rowIndex < this.rows; rowIndex++) { if (groups.getOrDefault(rowShift + rowIndex, DEFAULT_GROUP_ID) == sortableGroupId) { NEIClientUtils.drawRect( marginLeft + paddingLeft - GROUP_PANEL_WIDTH, marginTop + rowIndex * SLOT_SIZE, GROUP_PANEL_WIDTH, SLOT_SIZE, DRAG_COLOR); } } } else if (!ItemPanels.bookmarkPanel.inEditingState()) { final int focusedRowIndex = getHoveredRowIndex(true); if (focusedRowIndex != -1) { NEIClientUtils.drawRect( marginLeft + paddingLeft - GROUP_PANEL_WIDTH, marginTop + focusedRowIndex * SLOT_SIZE, GROUP_PANEL_WIDTH, SLOT_SIZE, HIGHLIGHT_COLOR); } } int previousPageGroupId = groups.getOrDefault(rowShift - 1, DEFAULT_GROUP_ID); int nextPageGroupId = groups.getOrDefault(rowShift + this.rows, DEFAULT_GROUP_ID); int previousGroupId = DEFAULT_GROUP_ID; int groupStartIndex = -2; if (previousPageGroupId == groups.get(rowShift)) { previousGroupId = previousPageGroupId; groupStartIndex = -1; } for (int rowIndex = 0; rowIndex < this.rows && groups.containsKey(rowShift + rowIndex); rowIndex++) { int groupId = groups.get(rowShift + rowIndex); if (groupStartIndex != -2 && previousGroupId != groupId) { if (previousGroupId != DEFAULT_GROUP_ID && previousGroupId != sortableGroupId) { drawGroup(previousGroupId, groupStartIndex, rowIndex - 1); } else { drawShadowGroup(groupStartIndex, rowIndex - 1); } groupStartIndex = -2; } if (groupStartIndex == -2) { groupStartIndex = rowIndex; } previousGroupId = groupId; } if (groupStartIndex != -2) { final int rowIndex = nextPageGroupId != DEFAULT_GROUP_ID && nextPageGroupId == groups.get(rowShift + this.rows - 1) ? this.rows : getLastRowIndex(); if (previousGroupId != DEFAULT_GROUP_ID && previousGroupId != sortableGroupId) { drawGroup(previousGroupId, groupStartIndex, rowIndex); } else { drawShadowGroup(groupStartIndex, rowIndex); } } } super.draw(mousex, mousey); } public 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 Color color = groupId > DEFAULT_GROUP_ID && this.groups.get(groupId).crafting != null ? GROUP_CHAIN_COLOR : GROUP_NONE_COLOR; int height = (Math.min(rowIndexEnd, this.rows - 1) - Math.max(0, rowIndexStart) + 1) * SLOT_SIZE; int top = marginTop + Math.max(0, rowIndexStart) * SLOT_SIZE; if (rowIndexStart >= 0) { NEIClientUtils .drawRect(leftPosition, marginTop + rowIndexStart * SLOT_SIZE + heightPadding, halfWidth, 1, color); top += heightPadding + 1; height -= heightPadding + 1; } if (rowIndexEnd < this.rows) { NEIClientUtils.drawRect( leftPosition, marginTop + (rowIndexEnd + 1) * SLOT_SIZE - heightPadding, halfWidth, 1, color); height -= heightPadding; } NEIClientUtils.drawRect(leftPosition, top, 1, height, color); } private void drawShadowGroup(int rowIndexStart, int rowIndexEnd) { int top = marginTop + Math.max(0, rowIndexStart) * SLOT_SIZE; int height = (Math.min(rowIndexEnd, this.rows - 1) - Math.max(0, rowIndexStart) + 1) * SLOT_SIZE; NEIClientUtils.drawRect( marginLeft + paddingLeft - GROUP_PANEL_WIDTH / 2 - 1, top + 2, 1, height - 4, PLACEHOLDER_COLOR); } @Override public void onItemsChanged() { this.isDirty = true; onGridChanged(); } @Override public void onGridChanged() { this.gridGenerator.gridMask = null; super.onGridChanged(); } public Map createChainItems(int groupId) { final Map chainItems = new HashMap<>(); for (int itemIndex = 0; itemIndex < this.bookmarkItems.size(); itemIndex++) { BookmarkItem item = this.bookmarkItems.get(itemIndex); if (item.groupId == groupId) { chainItems.put(itemIndex, item.copy()); } } return chainItems; } public RecipeChainMath createRecipeChainMath(int groupId) { return createRecipeChainMath(groupId, null); } public RecipeChainMath createRecipeChainMath(int groupId, RecipeId recipeId) { final BookmarkGroup group = getGroup(groupId); final List chainItems = new ArrayList<>(); final Set collapsedRecipes = group.crafting != null && recipeId == null ? group.collapsedRecipes : Collections.emptySet(); Set recipeRelations = null; if (group.collapsed) { recipeId = null; } if (group.crafting != null && recipeId != null) { for (Map.Entry> entry : group.crafting.recipeRelations.entrySet()) { if (entry.getValue().contains(recipeId)) { recipeId = entry.getKey(); break; } } } if (group.crafting != null && group.crafting.recipeRelations.containsKey(recipeId)) { recipeRelations = group.crafting.recipeRelations.get(recipeId); } else if (recipeId != null) { recipeRelations = new HashSet<>(Arrays.asList(recipeId)); } for (int itemIndex = 0; itemIndex < this.bookmarkItems.size(); itemIndex++) { final BookmarkItem item = this.bookmarkItems.get(itemIndex); if (item.groupId == groupId && (recipeRelations == null || recipeRelations.contains(item.recipeId))) { if (recipeId != null && group.crafting != null && group.crafting.calculatedItems.containsKey(itemIndex) && recipeId.equals(item.recipeId)) { chainItems.add( item.copyWithMultiplier( group.crafting.calculatedItems.get(itemIndex).getCalculatedMultiplier())); } else { chainItems.add(item.copy()); } } } if (group.crafting == null) { for (BookmarkItem item : chainItems) { item.type = BookmarkItemType.ITEM; item.recipeId = null; } } return RecipeChainMath.of(chainItems, collapsedRecipes); } public void createGroup(GroupingItem groupingItem) { final Map> rowItems = collectItemsToRows(); final Map newGroups = new HashMap<>(); boolean change = false; for (Map.Entry entry : groupingItem.prepareGroups(this.gridGenerator.rowToGroupId) .entrySet()) { if (this.gridGenerator.rowToGroupId.get(entry.getKey()) != entry.getValue()) { int groupId = entry.getValue(); if (groupId == Integer.MIN_VALUE) { groupId = newGroups.computeIfAbsent( groupId, grId -> addGroup(new BookmarkGroup(getViewMode(DEFAULT_GROUP_ID)))); } else if (groupId < 0) { groupId = newGroups .computeIfAbsent(groupId, grId -> addGroup(new BookmarkGroup(getViewMode(Math.abs(grId))))); } for (BookmarkItem item : rowItems.getOrDefault(entry.getKey(), Collections.emptyList())) { item.groupId = groupId; } change = true; } } if (change) { this.bookmarkItems = rowItems.values().stream().flatMap(items -> items.stream()) .collect(Collectors.toList()); final Set usedGroupIds = new HashSet<>(); for (BookmarkItem item : this.bookmarkItems) { usedGroupIds.add(item.groupId); } this.groups.keySet().removeIf(k -> k != DEFAULT_GROUP_ID && !usedGroupIds.contains(k)); onItemsChanged(); } } private Map> collectItemsToRows() { final Map> results = new HashMap<>(); final int totalRows = (int) Math.ceil(this.gridGenerator.maxAbsoluteSlotIndex / (float) this.columns); final List allItems = new ArrayList<>(this.bookmarkItems); for (int rowIndex = 0; rowIndex < totalRows; rowIndex++) { int itemIndex = Integer.MIN_VALUE; for (int index = 0; index < this.columns; index++) { if (this.gridGenerator.slotToItem.containsKey(rowIndex * this.columns + index)) { itemIndex = this.gridGenerator.slotToItem.get(rowIndex * this.columns + index); break; } } if (itemIndex != Integer.MIN_VALUE) { final BookmarkItem item = getCalculatedItem(itemIndex); final BookmarkGroup group = getGroup(item.groupId); final List items = new ArrayList<>(); if (group.collapsed || group.crafting != null && group.viewMode == BookmarkViewMode.DEFAULT) { for (BookmarkItem itm : allItems) { if (itm.groupId == item.groupId) { items.add(itm); } } } else if (item.recipeId != null && group.crafting != null && group.crafting.itemToRecipe.containsKey(itemIndex)) { final Set recipeRelations = group.crafting.recipeRelations .getOrDefault(item.recipeId, Collections.singleton(item.recipeId)); for (BookmarkItem itm : allItems) { if (itm.groupId == item.groupId && recipeRelations.contains(itm.recipeId)) { items.add(itm); } } } else { for (int index = 0; index < this.columns; index++) { if (this.gridGenerator.slotToItem.containsKey(rowIndex * this.columns + index)) { final BookmarkItem itm = this.bookmarkItems .get(this.gridGenerator.slotToItem.get(rowIndex * this.columns + index)); if (allItems.contains(itm)) { items.add(itm); } } } } results.put(rowIndex, items); allItems.removeAll(items); } } results.put(totalRows, allItems); return results; } public void removeGroup(int groupId) { this.bookmarkItems.removeIf(item -> item.groupId == groupId); if (groupId != DEFAULT_GROUP_ID) { this.groups.remove(groupId); } onItemsChanged(); } public void moveGroup(SortableGroup sortableGroup, int overRowIndex) { int absoluteRowIndex = this.page * this.rows + overRowIndex; int absoluteSlotIndex = absoluteRowIndex * this.columns; int overGroupId = getRowGroupId(overRowIndex); // move group to same grid if (this != sortableGroup.grid) { final BookmarkGroup sourceGroup = sortableGroup.grid.getGroup(sortableGroup.groupId); final List itemsToMove = sortableGroup.getBookmarkItems(); sortableGroup.grid.removeGroup(sortableGroup.groupId); final int groupId = this.addGroup(sourceGroup); for (BookmarkItem item : itemsToMove) { item.groupId = groupId; this.bookmarkItems.add(item); } sortableGroup.grid = this; sortableGroup.groupId = groupId; this.onItemsChanged(); this.getMask();// force generate grid } if (sortableGroup.groupId == overGroupId) { return; } boolean mouseDown = false; for (int rowId = 0; rowId < absoluteRowIndex && !mouseDown; rowId++) { if (this.gridGenerator.rowToGroupId.get(rowId) == sortableGroup.groupId) { mouseDown = true; } } if (mouseDown) { absoluteRowIndex += 1; absoluteSlotIndex += this.columns; } if (mouseDown && absoluteSlotIndex >= this.gridGenerator.maxAbsoluteSlotIndex) { final int itemIndex = this.gridGenerator.slotToItem .getOrDefault(this.gridGenerator.maxAbsoluteSlotIndex - 1, Integer.MIN_VALUE); if (itemIndex == Integer.MIN_VALUE || getCalculatedItem(itemIndex).groupId == sortableGroup.groupId) { return; } moveItems(null, sortableGroup.getBookmarkItems()); } else if (this.gridGenerator.rowToGroupId.containsKey(absoluteRowIndex)) { overGroupId = this.gridGenerator.rowToGroupId.get(absoluteRowIndex); if (overGroupId != DEFAULT_GROUP_ID && overGroupId == this.gridGenerator.rowToGroupId.getOrDefault(absoluteRowIndex - 1, DEFAULT_GROUP_ID)) { return; } final int itemIndex = this.gridGenerator.slotToItem.getOrDefault(absoluteSlotIndex, Integer.MIN_VALUE); BookmarkItem bookmarkItem = getCalculatedItem(itemIndex); if (bookmarkItem == null) { return; } if (bookmarkItem.groupId != DEFAULT_GROUP_ID) { for (int i = 0; i < this.bookmarkItems.size(); i++) { if (this.bookmarkItems.get(i).groupId == bookmarkItem.groupId) { bookmarkItem = this.bookmarkItems.get(i); break; } } } moveItems(bookmarkItem, sortableGroup.getBookmarkItems()); } } public int indexOf(int groupId, ItemStack stackA, RecipeId recipeId, boolean ingredient) { for (int itemIndex = 0; itemIndex < this.bookmarkItems.size(); itemIndex++) { final BookmarkItem item = this.bookmarkItems.get(itemIndex); if (item.groupId == groupId && ingredient == (item.type == BookmarkItemType.INGREDIENT) && (recipeId == item.recipeId || recipeId != null && recipeId.equals(item.recipeId)) && StackInfo.equalItemAndNBT(stackA, item.itemStack, true)) { return itemIndex; } } return -1; } public BookmarkItem getCalculatedItem(int itemIndex) { if (this.gridGenerator.caclulatedItems.containsKey(itemIndex)) { return this.gridGenerator.caclulatedItems.get(itemIndex).getItem(); } return null; } public BookmarkItem getBookmarkItem(int itemIndex) { if (itemIndex < 0 || itemIndex >= this.bookmarkItems.size()) { return null; } return this.bookmarkItems.get(itemIndex); } public int getAbsoluteSlotIndex(int itemIndex) { return this.gridGenerator.itemToSlot.getOrDefault(itemIndex, -1); } public List getSortedItems() { return new ArrayList<>(this.gridGenerator.slotToItem.values()); } public void addItem(BookmarkItem item, boolean animate) { this.bookmarkItems.add(item); if (animate && NEIClientConfig.getGridRenderingCacheMode() == 0 && NEIClientConfig.areBookmarksAnimated()) { this.animation.put(item, 0f); } onItemsChanged(); } public void addRecipe(Recipe recipe, int multiplier, int groupId) { final ItemStackSet seenResults = new ItemStackSet(); final ItemStackSet seenIngredients = new ItemStackSet(); final List results = new ArrayList<>(); final List ingredients = new ArrayList<>(); for (RecipeIngredient result : recipe.getResults()) { ItemStack stack = result.getItemStack(); if (!seenResults.contains(stack)) { seenResults.add(stack); results.add(stack); } } for (RecipeIngredient ingr : recipe.getIngredients()) { ItemStack stack = ingr.getItemStack(); if (!seenIngredients.contains(stack)) { seenIngredients.add(stack); ingredients.add(stack); } } for (ItemStack stack : results) { this.addItem( BookmarkItem.builder(groupId, stack, recipe, BookmarkItemType.RESULT).multiplier(multiplier) .build(), true); } for (ItemStack stack : ingredients) { this.addItem( BookmarkItem.builder(groupId, stack, recipe, BookmarkItemType.INGREDIENT).multiplier(multiplier) .build(), true); } } public void removeRecipe(int itemIndex, boolean removeFullRecipe) { final BookmarkItem item = getCalculatedItem(itemIndex); if (item == null) { return; } if (!removeFullRecipe && item.recipeId != null && item.type == BookmarkItemType.RESULT) { removeFullRecipe = this.bookmarkItems.stream() .noneMatch(m -> m.type == BookmarkItemType.RESULT && !m.equals(item) && item.equalsRecipe(m)); } if (item.recipeId != null && item.type != BookmarkItemType.ITEM && removeFullRecipe) { removeRecipe(item.recipeId, item.groupId); } else if (itemIndex >= 0) { this.bookmarkItems.remove(itemIndex); onItemsChanged(); } } public boolean removeRecipe(RecipeId recipeId, int groupId) { if (this.bookmarkItems.removeIf(item -> item.equalsRecipe(recipeId, groupId))) { onItemsChanged(); return true; } return false; } public boolean existsRecipe(RecipeId recipeId, int groupId) { return this.bookmarkItems.stream() .anyMatch(item -> item.type != BookmarkItemType.ITEM && item.equalsRecipe(recipeId, groupId)); } public RecipeId getRecipeId(int itemIndex) { final BookmarkItem bookmarkItem = getCalculatedItem(itemIndex); return bookmarkItem != null ? bookmarkItem.recipeId : null; } public void moveItem(SortableItem sortableItem, int overRowIndex, int overGroupId, boolean moveAfter) { final BookmarkGroup overGroup = getGroup(overGroupId); int absoluteSlotIndex = (this.page * this.rows + overRowIndex) * this.columns; if (sortableItem.isCollapsedRecipe && sortableItem.bookmarkItem.groupId != overGroupId || overGroup.collapsed || overGroup.viewMode == BookmarkViewMode.DEFAULT) { return; } if (sortableItem.bookmarkItem.groupId != overGroupId) { for (BookmarkItem item : this.bookmarkItems) { if (sortableItem.containsItem(item)) { item.groupId = overGroupId; } } sortableItem.bookmarkItem.groupId = overGroupId; onItemsChanged(); } if (moveAfter) { absoluteSlotIndex += this.columns; } if (moveAfter && absoluteSlotIndex >= this.gridGenerator.maxAbsoluteSlotIndex) { final int itemIndex = this.gridGenerator.slotToItem .getOrDefault(this.gridGenerator.maxAbsoluteSlotIndex - 1, Integer.MIN_VALUE); if (itemIndex == Integer.MIN_VALUE || sortableItem.items.contains(itemIndex)) { return; } moveItems(null, sortableItem.getBookmarkItems()); } else if (this.gridGenerator.slotToItem.containsKey(absoluteSlotIndex)) { final int itemIndex = this.gridGenerator.slotToItem.get(absoluteSlotIndex); final List sortedIndexes = new ArrayList<>(this.gridGenerator.slotToItem.values()); BookmarkItem bookmarkItem = getCalculatedItem(itemIndex); final BookmarkGroup group = getGroup(bookmarkItem.groupId); final int sortIndex = sortedIndexes.indexOf(itemIndex); if (sortIndex == -1 || sortableItem.items.contains(itemIndex) || sortIndex > 0 && sortableItem.items.contains(sortedIndexes.get(sortIndex - 1))) { return; } if (group.crafting != null && sortIndex > 0) { final RecipeId recipeId = group.crafting.itemToRecipe.getOrDefault(itemIndex, bookmarkItem.recipeId); if (recipeId != null) { if (recipeId.equals(this.gridGenerator.itemToRecipe.get(sortedIndexes.get(sortIndex - 1)))) { return; } for (int i = 0; i < this.bookmarkItems.size(); i++) { if (this.bookmarkItems.get(i).groupId == bookmarkItem.groupId && recipeId.equals(this.bookmarkItems.get(i).recipeId)) { bookmarkItem = this.bookmarkItems.get(i); break; } } } } moveItems(bookmarkItem, sortableItem.getBookmarkItems()); } } public void moveItem(SortableItem sortableItem, int itemIndex) { final List sortedIndexes = new ArrayList<>(this.gridGenerator.slotToItem.values()); int sortIndex = sortedIndexes.indexOf(itemIndex); final boolean moveAfter = sortIndex > sortedIndexes.indexOf(sortableItem.getItemIndex()); final BookmarkGroup group = getGroup(sortableItem.bookmarkItem.groupId); BookmarkItem bookmarkItem = getCalculatedItem(itemIndex); if (bookmarkItem == null || sortIndex == -1 || sortableItem.items.contains(itemIndex) || group.collapsed) { return; } if (moveAfter) { sortIndex = findNextBookmarkItem(itemIndex); if (sortIndex == -1) { return; } if (sortIndex < sortedIndexes.size()) { bookmarkItem = getCalculatedItem(sortedIndexes.get(sortIndex)); } else { bookmarkItem = null; } } if (bookmarkItem != null && (group.crafting != null && group.viewMode == BookmarkViewMode.DEFAULT || group.viewMode == BookmarkViewMode.TODO_LIST && sortableItem.bookmarkItem.type == BookmarkItemType.RESULT)) { BookmarkItem prevBookmarkItem = sortIndex > 1 ? getCalculatedItem(sortedIndexes.get(sortIndex - 1)) : null; while (sortIndex > 0 && prevBookmarkItem != null && prevBookmarkItem.type != BookmarkItemType.ITEM && bookmarkItem.equalsRecipe(prevBookmarkItem)) { prevBookmarkItem = sortIndex > 1 ? getCalculatedItem(sortedIndexes.get(sortIndex - 1)) : null; sortIndex--; } bookmarkItem = getCalculatedItem(sortedIndexes.get(sortIndex)); } moveItems(bookmarkItem, sortableItem.getBookmarkItems()); } private int findNextBookmarkItem(int itemIndex) { final List sortedIndexes = new ArrayList<>(this.gridGenerator.slotToItem.values()); final int sortIndex = sortedIndexes.indexOf(itemIndex); final BookmarkItem target = getCalculatedItem(itemIndex); final BookmarkGroup group = getGroup(target.groupId); int nextSortIndex = sortIndex + 1; if (group.viewMode == BookmarkViewMode.TODO_LIST) { final int rowId = this.gridGenerator.itemToSlot.get(itemIndex) / this.columns; while (nextSortIndex < sortedIndexes.size() && (this.gridGenerator.itemToSlot.get(sortedIndexes.get(nextSortIndex)) / this.columns) == rowId) { nextSortIndex++; } } if (nextSortIndex < sortedIndexes.size()) { final int nextItemIndex = sortedIndexes.get(nextSortIndex); if (group.viewMode == BookmarkViewMode.TODO_LIST && target.type == BookmarkItemType.RESULT && target.equalsRecipe(getCalculatedItem(nextItemIndex))) { return -1; } if (group.crafting != null) { final RecipeId recipeId = group.crafting.itemToRecipe.get(itemIndex); if (recipeId != null && recipeId.equals(this.gridGenerator.itemToRecipe.get(nextItemIndex))) { return -1; } } return nextSortIndex; } return Integer.MAX_VALUE; } private void moveItems(BookmarkItem bookmarkItem, List items) { this.bookmarkItems.removeAll(items); final int index = bookmarkItem == null ? this.bookmarkItems.size() : this.bookmarkItems.indexOf(bookmarkItem); this.bookmarkItems.addAll(index, items); onItemsChanged(); } public void shiftGroupAmount(int targetGroupId, long shift) { final BookmarkGroup group = getGroup(targetGroupId); if (group.crafting == null) { final List items = new ArrayList<>(); final Set sizes = new HashSet<>(); for (BookmarkItem item : this.bookmarkItems) { if (item.groupId == targetGroupId && item.getAmount() > 0) { sizes.add(Math.max(item.getStackSize(), item.getStackSize(item.getAmount(1)))); items.add(item); } } final long gcd = sizes.stream().reduce(0L, (a, b) -> gcd(a, b)); long multiplier = Integer.MAX_VALUE; for (BookmarkItem item : items) { final long amount = item.getAmount(); final long customFactor = (amount + gcd - 1) / gcd; final long customMultiplier = customFactor > 0 ? ((amount + customFactor - 1) / customFactor) : 0; multiplier = Math.min(shiftMultiplier(customMultiplier, shift, 1), multiplier); } for (BookmarkItem item : items) { final long amount = item.getAmount(); final long customFactor = (amount + gcd - 1) / gcd; item.multiplier = item.getMultiplierFromAmount(customFactor * multiplier); } } else { final Map recipeMultipliers = new HashMap<>(); final Set outputRecipes = group.crafting.outputRecipes; final List items = new ArrayList<>(); for (BookmarkItem item : this.bookmarkItems) { if (!item.emptyFactor() && outputRecipes.stream().anyMatch(recipeId -> item.equalsRecipe(recipeId, targetGroupId))) { final long multiplier = recipeMultipliers.getOrDefault(item.recipeId, (long) Integer.MAX_VALUE); final boolean recipeInMiddle = group.crafting != null && group.crafting.recipeInMiddle.contains(item.recipeId); recipeMultipliers.put( item.recipeId, Math.min( shiftMultiplier( Math.max(recipeInMiddle ? 1 : 0, item.multiplier), shift, recipeInMiddle ? 1 : 0), multiplier)); items.add(item); } } for (BookmarkItem item : items) { item.multiplier = recipeMultipliers.getOrDefault(item.recipeId, 0L); } } this.onItemsChanged(); } public void shiftItemAmount(int targetItemIndex, long shift) { final BookmarkItem targetItem = getCalculatedItem(targetItemIndex); final BookmarkGroup group = getGroup(targetItem.groupId); if (group.collapsed) { shiftGroupAmount(targetItem.groupId, shift); } else if (targetItem.recipeId != null && targetItem.type != BookmarkItemType.ITEM) { final RecipeId recipeId = this.gridGenerator.itemToRecipe .getOrDefault(targetItemIndex, targetItem.recipeId); final boolean recipeInMiddle = group.crafting != null && group.crafting.recipeInMiddle.contains(recipeId); long multiplier = Integer.MAX_VALUE; for (BookmarkItem item : this.bookmarkItems) { if (item.equalsRecipe(recipeId, targetItem.groupId) && !item.emptyFactor()) { multiplier = Math.min( shiftMultiplier( Math.max(recipeInMiddle ? 1 : 0, item.multiplier), shift, recipeInMiddle ? 1 : 0), multiplier); } } for (BookmarkItem item : this.bookmarkItems) { if (item.equalsRecipe(recipeId, targetItem.groupId) && !item.emptyFactor()) { item.multiplier = multiplier; } } } else { for (BookmarkItem item : this.bookmarkItems) { if (targetItem.equals(item)) { item.multiplier = shiftMultiplier(item.multiplier, shift, 0); break; } } } this.onItemsChanged(); } private long shiftMultiplier(long multiplier, long shift, int minMultiplier) { final long currentMultiplier = ((multiplier + shift) / shift) * shift; return Math.min( Integer.MAX_VALUE, currentMultiplier <= 0 && multiplier > 1 ? 1 : Math.max(minMultiplier, currentMultiplier)); } private long gcd(long a, long b) { return b == 0 ? a : gcd(b, a % b); } @Override protected void beforeDrawItems(int mousex, int mousey, BookmarkMouseContext mouseContext) { if (LayoutManager.bookmarkPanel.inEditingState()) { final SortableGroup sortableGroup = LayoutManager.bookmarkPanel.sortableGroup; final SortableItem sortableItem = LayoutManager.bookmarkPanel.sortableItem; final List borders = new ArrayList<>(); for (BookmarksGridSlot slot : getMask()) { Rectangle4i rect = getSlotRect(slot.slotIndex); if (sortableGroup != null && slot.getGroupId() == sortableGroup.groupId) { NEIClientUtils.drawRect(rect.x, rect.y, rect.w, rect.h, DRAG_COLOR); } else if (sortableItem != null && sortableItem.items.contains(slot.itemIndex)) { NEIClientUtils.drawRect(rect.x, rect.y, rect.w, rect.h, DRAG_COLOR); borders.add(slot); } else { borders.add(slot); } } for (BookmarksGridSlot slot : borders) { slot.drawBorder(getSlotRect(slot.slotIndex)); } } else { super.beforeDrawItems(mousex, mousey, mouseContext); } } @Override protected void afterDrawItems(int mousex, int mousey, BookmarkMouseContext mouseContext) { if (LayoutManager.bookmarkPanel.inEditingState()) { final SortableGroup sortableGroup = LayoutManager.bookmarkPanel.sortableGroup; final SortableItem sortableItem = LayoutManager.bookmarkPanel.sortableItem; for (BookmarksGridSlot slot : getMask()) { if ((sortableGroup == null || slot.getGroupId() != sortableGroup.groupId) && (sortableItem == null || !sortableItem.items.contains(slot.itemIndex))) { slot.afterDraw(getSlotRect(slot.slotIndex), mouseContext); } } } else { super.afterDrawItems(mousex, mousey, mouseContext); } } @Override protected void drawItems() { final SortableGroup sortableGroup = LayoutManager.bookmarkPanel.sortableGroup; final SortableItem sortableItem = LayoutManager.bookmarkPanel.sortableItem; for (BookmarksGridSlot slot : getMask()) { if ((sortableGroup == null || slot.getGroupId() != sortableGroup.groupId) && (sortableItem == null || !sortableItem.items.contains(slot.itemIndex))) { final BookmarkItem item = slot.getBookmarkItem(); if (this.animation.containsKey(item) && this.animation.get(item) < 1) { final float currentScale = this.animation.get(item) + SCALE_SPEED; if (currentScale >= 1) { this.animation.remove(item); } else { this.animation.put(item, currentScale); } drawPoppingItem(slot, getSlotRect(slot.slotIndex), currentScale); } else { slot.drawItem(getSlotRect(slot.slotIndex)); } } } } protected void drawPoppingItem(BookmarksGridSlot slot, Rectangle4i rect, float currentScale) { final float inverseScaleFactor = 1f / currentScale; final float shiftX = (rect.x + (rect.w - rect.w * currentScale) / 2f) * inverseScaleFactor; final float shiftY = (rect.y + (rect.h - rect.h * currentScale) / 2f) * inverseScaleFactor; GL11.glScalef(currentScale, currentScale, 1); GL11.glTranslated(shiftX, shiftY, 0); slot.drawItem(new Rectangle4i(0, 0, rect.w, rect.h)); GL11.glTranslated(-1 * shiftX, -1 * shiftY, 0); GL11.glScalef(inverseScaleFactor, inverseScaleFactor, 1); } }