mirror of
https://github.com/GTNewHorizons/NotEnoughItems.git
synced 2026-10-25 20:32:20 +00:00
Recipe Group Optimization (#1054)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
9f3697bb67
commit
a685efb3b7
11 files changed
+654
-214
No files matched your search
@@ -316,6 +316,10 @@ public class FavoriteRecipes {
|
||||
load();
|
||||
}
|
||||
|
||||
public static boolean hasManual() {
|
||||
return manualFavorites.size() > 0;
|
||||
}
|
||||
|
||||
public static boolean containsManual(ItemStack stack) {
|
||||
return getManualFavorite(stack) != null;
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ public class PositionedStack implements Cloneable {
|
||||
}
|
||||
|
||||
public List<ItemStack> getFilteredPermutations(ItemFilter additionalFilter) {
|
||||
List<ItemStack> items = Arrays.asList(this.items);
|
||||
List<ItemStack> items = new ArrayList<>(Arrays.asList(this.items));
|
||||
|
||||
items = filteringPermutations(items, item -> !ItemInfo.isHidden(item));
|
||||
items = filteringPermutations(items, PresetsList.getItemFilter());
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.awt.Point;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -35,6 +36,7 @@ public class GuiCraftingRecipe extends GuiRecipe<ICraftingHandler> {
|
||||
public static GuiRecipe<?> createRecipeGui(String outputId, boolean open, Object... results) {
|
||||
final Minecraft mc = NEIClientUtils.mc();
|
||||
final RecipeId recipeId;
|
||||
List<ItemStack> pinnedIngredients = null;
|
||||
|
||||
for (int i = 0; i < results.length; i++) {
|
||||
if (results[i] instanceof ItemStack stack) {
|
||||
@@ -46,6 +48,12 @@ public class GuiCraftingRecipe extends GuiRecipe<ICraftingHandler> {
|
||||
recipeId = getRecipeId(mc.currentScreen, (ItemStack) results[0]);
|
||||
} else if ("recipeId".equals(outputId)) {
|
||||
recipeId = (RecipeId) results[1];
|
||||
|
||||
// optional third argument: ingredients to pin the found recipe with
|
||||
if (results.length > 2 && results[2] instanceof List<?>list) {
|
||||
pinnedIngredients = extractIngredients(list);
|
||||
}
|
||||
|
||||
} else {
|
||||
recipeId = getCurrentRecipeId(mc.currentScreen);
|
||||
}
|
||||
@@ -65,13 +73,25 @@ public class GuiCraftingRecipe extends GuiRecipe<ICraftingHandler> {
|
||||
mc.displayGuiScreen(gui);
|
||||
}
|
||||
|
||||
gui.openTargetRecipe(recipeId);
|
||||
gui.openTargetRecipe(recipeId, pinnedIngredients);
|
||||
return gui;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static List<ItemStack> extractIngredients(List<?> list) {
|
||||
final List<ItemStack> ingredients = new ArrayList<>(list.size());
|
||||
|
||||
for (Object item : list) {
|
||||
if (item instanceof ItemStack stack) {
|
||||
ingredients.add(stack);
|
||||
}
|
||||
}
|
||||
|
||||
return ingredients;
|
||||
}
|
||||
|
||||
public static ArrayList<ICraftingHandler> getCraftingHandlers(String outputId, Object... results) {
|
||||
ArrayList<ICraftingHandler> craftinghandlers = GuiCraftingRecipe.craftinghandlers;
|
||||
ArrayList<ICraftingHandler> serialCraftingHandlers = GuiCraftingRecipe.serialCraftingHandlers;
|
||||
|
||||
@@ -524,6 +524,13 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
|
||||
}
|
||||
|
||||
public int openTargetRecipe(RecipeId recipeId) {
|
||||
return openTargetRecipe(recipeId, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pinnedIngredients when not null, the found recipe is paused with these ingredients shown
|
||||
*/
|
||||
public int openTargetRecipe(RecipeId recipeId, List<ItemStack> pinnedIngredients) {
|
||||
int recipeIndex = -1;
|
||||
int recipetype = 0;
|
||||
|
||||
@@ -551,6 +558,10 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
|
||||
setRecipePage(recipetype);
|
||||
this.handlerPages.gotoRecipeIndex(Math.max(0, recipeIndex));
|
||||
|
||||
if (pinnedIngredients != null && recipeIndex >= 0) {
|
||||
this.handlerPages.pinRecipe(recipeIndex, pinnedIngredients);
|
||||
}
|
||||
|
||||
return recipeIndex;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
import codechicken.nei.NEIClientConfig;
|
||||
import codechicken.nei.Widget;
|
||||
import codechicken.nei.recipe.widget.RecipeWidget;
|
||||
@@ -42,6 +44,8 @@ public class RecipePageManager {
|
||||
}
|
||||
|
||||
this.cacheKey = cacheKey;
|
||||
|
||||
final List<RecipeWidget> pausedWidgets = getPausedWidgets();
|
||||
this.pageWidgets.clear();
|
||||
|
||||
final boolean allowOverflowY = this.handlerInfo.isAllowOverflowY();
|
||||
@@ -76,6 +80,15 @@ public class RecipePageManager {
|
||||
for (int groupIndex = 0; groupIndex < groupedRecipes.size(); groupIndex++) {
|
||||
final RecipeWidget widget = new RecipeWidget(this.handler.original, groupedRecipes.get(groupIndex));
|
||||
|
||||
// keep the pause of the rebuilt widgets
|
||||
for (RecipeWidget paused : pausedWidgets) {
|
||||
final int pausedIndex = paused.getRecipeHandlerRef().recipeIndex;
|
||||
|
||||
if (widget.containsRecipeIndex(pausedIndex)) {
|
||||
widget.pinRecipe(pausedIndex, paused.getPinnedIngredients());
|
||||
}
|
||||
}
|
||||
|
||||
widget.w = width;
|
||||
|
||||
if (allowOverflowY) {
|
||||
@@ -144,6 +157,33 @@ public class RecipePageManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<RecipeWidget> getPausedWidgets() {
|
||||
final List<RecipeWidget> paused = new ArrayList<>();
|
||||
|
||||
for (List<Widget> widgets : this.pageWidgets) {
|
||||
for (Widget widget : widgets) {
|
||||
if (widget instanceof RecipeWidget recipeWidget && recipeWidget.isPaused()) {
|
||||
paused.add(recipeWidget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return paused;
|
||||
}
|
||||
|
||||
public boolean pinRecipe(int recipeIndex, List<ItemStack> ingredients) {
|
||||
|
||||
for (List<Widget> widgets : this.pageWidgets) {
|
||||
for (Widget widget : widgets) {
|
||||
if (widget instanceof RecipeWidget recipeWidget && recipeWidget.containsRecipeIndex(recipeIndex)) {
|
||||
return recipeWidget.pinRecipe(recipeIndex, ingredients);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public List<Widget> getCurrentPageWidgets() {
|
||||
if (this.pageWidgets.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package codechicken.nei.recipe.widget;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -50,6 +51,11 @@ public class PermutationsCycler<K> {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void invalidate() {
|
||||
this.favoriteRevision = -1;
|
||||
validate();
|
||||
}
|
||||
|
||||
public boolean contains(K key) {
|
||||
validate();
|
||||
return this.permutations.containsKey(key);
|
||||
@@ -79,6 +85,11 @@ public class PermutationsCycler<K> {
|
||||
return items;
|
||||
}
|
||||
|
||||
public int getFavoriteCount(K key) {
|
||||
get(key);
|
||||
return this.favoriteCounts.getOrDefault(key, 0);
|
||||
}
|
||||
|
||||
public ItemStack getCycled(K key, int cycle) {
|
||||
final List<ItemStack> items = get(key);
|
||||
final int favoriteCount = this.favoriteCounts.getOrDefault(key, 0);
|
||||
@@ -96,4 +107,10 @@ public class PermutationsCycler<K> {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Hash key with the same equality as NEIServerUtils.areStacksSameType
|
||||
public static Object getStackKey(ItemStack stack) {
|
||||
return Arrays
|
||||
.asList(stack.getItem(), stack.getHasSubtypes() ? stack.getItemDamage() : 0, stack.stackTagCompound);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,23 +2,109 @@ package codechicken.nei.recipe.widget;
|
||||
|
||||
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.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
import codechicken.nei.FavoriteRecipes;
|
||||
import codechicken.nei.NEIServerUtils;
|
||||
import codechicken.nei.PositionedStack;
|
||||
import codechicken.nei.recipe.GuiRecipeTab;
|
||||
import codechicken.nei.recipe.HandlerInfo;
|
||||
import codechicken.nei.recipe.IRecipeHandler;
|
||||
import codechicken.nei.recipe.StackInfo;
|
||||
|
||||
public class RecipeGroup {
|
||||
|
||||
// position and items of a stack at load time, the handler may change the stack later
|
||||
protected static class StackState {
|
||||
|
||||
public final int relx;
|
||||
public final int rely;
|
||||
public final ItemStack[] items;
|
||||
|
||||
public StackState(PositionedStack pStack) {
|
||||
this.relx = pStack.relx;
|
||||
this.rely = pStack.rely;
|
||||
this.items = pStack.items;
|
||||
}
|
||||
}
|
||||
|
||||
// filtered permutations of a slot, favorites first, with their indices in PositionedStack.items
|
||||
protected static class FilteredSlot {
|
||||
|
||||
public final List<ItemStack> items;
|
||||
public final int[] indices;
|
||||
public final int favoriteCount;
|
||||
|
||||
public FilteredSlot(PositionedStack pStack) {
|
||||
this.items = pStack.getFilteredPermutations();
|
||||
this.indices = new int[this.items.size()];
|
||||
|
||||
// filtering keeps the original instances, so an identity lookup finds the source index
|
||||
for (int i = 0; i < this.indices.length; i++) {
|
||||
this.indices[i] = indexOfInstance(pStack.items, this.items.get(i));
|
||||
}
|
||||
|
||||
int favoriteCount = 0;
|
||||
|
||||
while (this.items.size() > 1 && favoriteCount < this.items.size()
|
||||
&& FavoriteRecipes.containsManual(this.items.get(favoriteCount))) {
|
||||
favoriteCount++;
|
||||
}
|
||||
|
||||
this.favoriteCount = favoriteCount;
|
||||
}
|
||||
|
||||
private static int indexOfInstance(ItemStack[] items, ItemStack stack) {
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
if (items[i] == stack) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected static class MemberStacks {
|
||||
|
||||
public final List<PositionedStack> inputs;
|
||||
public final List<PositionedStack> outputs;
|
||||
public final List<PositionedStack> auxiliaries;
|
||||
public final List<PositionedStack> cycling;
|
||||
public final List<StackState> states = new ArrayList<>();
|
||||
public final Map<Point, PositionedStack> slots = new HashMap<>();
|
||||
public final Map<Point, FilteredSlot> filtered = new HashMap<>();
|
||||
|
||||
public MemberStacks(List<PositionedStack> inputs, List<PositionedStack> outputs,
|
||||
List<PositionedStack> auxiliaries) {
|
||||
this.inputs = inputs;
|
||||
this.outputs = outputs;
|
||||
this.auxiliaries = auxiliaries;
|
||||
|
||||
if (auxiliaries.isEmpty()) {
|
||||
this.cycling = inputs;
|
||||
} else {
|
||||
this.cycling = new ArrayList<>(inputs);
|
||||
this.cycling.addAll(auxiliaries);
|
||||
}
|
||||
|
||||
for (PositionedStack pStack : this.cycling) {
|
||||
this.slots.putIfAbsent(new Point(pStack.relx, pStack.rely), pStack);
|
||||
}
|
||||
|
||||
for (PositionedStack pStack : inputs) this.states.add(new StackState(pStack));
|
||||
for (PositionedStack pStack : outputs) this.states.add(new StackState(pStack));
|
||||
for (PositionedStack pStack : auxiliaries) this.states.add(new StackState(pStack));
|
||||
}
|
||||
}
|
||||
|
||||
private static final List<Integer> SINGLE_MEMBER = Collections.singletonList(0);
|
||||
|
||||
private final IRecipeHandler handler;
|
||||
@@ -26,16 +112,33 @@ public class RecipeGroup {
|
||||
private final HandlerInfo handlerInfo;
|
||||
private final PermutationsCycler<Point> slotPermutations;
|
||||
|
||||
private final MemberStacks[] memberStacks;
|
||||
private Map<Point, PositionedStack[]> slotStacks = null;
|
||||
private final Set<Point> sharedSlots = new HashSet<>();
|
||||
private List<Integer> allowedMembers = null;
|
||||
private Boolean cycling = null;
|
||||
|
||||
public RecipeGroup(IRecipeHandler handler, List<Integer> recipeIndices) {
|
||||
this.handler = handler;
|
||||
this.recipeIndices = new ArrayList<>(recipeIndices);
|
||||
this.handlerInfo = GuiRecipeTab.getHandlerInfo(handler);
|
||||
this.memberStacks = new MemberStacks[this.recipeIndices.size()];
|
||||
this.slotPermutations = new PermutationsCycler<Point>(
|
||||
new HashMap<>(),
|
||||
new HashMap<>(),
|
||||
this::collectSlotPermutations).onInvalidate(() -> this.allowedMembers = null);
|
||||
this::collectSlotPermutations).onInvalidate(() -> {
|
||||
this.allowedMembers = null;
|
||||
this.cycling = null;
|
||||
this.slotStacks = null;
|
||||
|
||||
for (MemberStacks stacks : this.memberStacks) {
|
||||
if (stacks != null) {
|
||||
stacks.filtered.clear();
|
||||
}
|
||||
}
|
||||
|
||||
this.sharedSlots.clear();
|
||||
});
|
||||
}
|
||||
|
||||
public int size() {
|
||||
@@ -58,34 +161,49 @@ public class RecipeGroup {
|
||||
return this.recipeIndices.contains(recipeIndex);
|
||||
}
|
||||
|
||||
public int indexOf(int recipeIndex) {
|
||||
return this.recipeIndices.indexOf(recipeIndex);
|
||||
}
|
||||
|
||||
public List<ItemStack> getPermutations(Point slotKey) {
|
||||
return this.slotPermutations.get(slotKey);
|
||||
}
|
||||
|
||||
public boolean hasPermutation(int member, Point slotKey, ItemStack stack) {
|
||||
this.slotPermutations.validate();
|
||||
|
||||
if (stack == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final List<ItemStack> permutations = getFilteredPermutations(member, slotKey);
|
||||
return permutations != null && PermutationsCycler.indexOf(permutations, stack) != -1;
|
||||
}
|
||||
|
||||
// the given member if it has the item in the slot, otherwise the next member that has it
|
||||
public int findMember(Point slotKey, ItemStack stack, int from) {
|
||||
|
||||
for (int step = 0; step < size(); step++) {
|
||||
final int member = (from + step) % size();
|
||||
|
||||
if (hasPermutation(member, slotKey, stack)) {
|
||||
return member;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public List<PositionedStack> getInputs(int member) {
|
||||
return this.handler.getIngredientStacks(getRecipeIndex(member));
|
||||
return getStacks(member).inputs;
|
||||
}
|
||||
|
||||
public List<PositionedStack> getOutputs(int member) {
|
||||
final int recipeIndex = getRecipeIndex(member);
|
||||
final PositionedStack pStackResult = this.handler.getResultStack(recipeIndex);
|
||||
return pStackResult != null ? Arrays.asList(pStackResult) : this.handler.getOtherStacks(recipeIndex);
|
||||
}
|
||||
|
||||
public List<PositionedStack> getCatalysts(int member) {
|
||||
final int recipeIndex = getRecipeIndex(member);
|
||||
|
||||
if (this.handler.getResultStack(recipeIndex) == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return this.handler.getOtherStacks(recipeIndex);
|
||||
return getStacks(member).outputs;
|
||||
}
|
||||
|
||||
public List<PositionedStack> getCyclingStacks(int member) {
|
||||
final List<PositionedStack> stacks = new ArrayList<>(getInputs(member));
|
||||
stacks.addAll(getCatalysts(member));
|
||||
return stacks;
|
||||
return getStacks(member).cycling;
|
||||
}
|
||||
|
||||
public Point getSlotKey(PositionedStack pStack) {
|
||||
@@ -93,39 +211,108 @@ public class RecipeGroup {
|
||||
}
|
||||
|
||||
public PositionedStack getSlot(int member, Point slotKey) {
|
||||
|
||||
for (PositionedStack pStack : getCyclingStacks(member)) {
|
||||
if (pStack.relx == slotKey.x && pStack.rely == slotKey.y) {
|
||||
return pStack;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return getStacks(member).slots.get(slotKey);
|
||||
}
|
||||
|
||||
public PositionedStack getOutputSlot(int member, Point slotKey) {
|
||||
protected MemberStacks getStacks(int member) {
|
||||
|
||||
for (PositionedStack pStack : getOutputs(member)) {
|
||||
if (pStack.relx == slotKey.x && pStack.rely == slotKey.y) {
|
||||
return pStack;
|
||||
}
|
||||
if (this.memberStacks[member] == null) {
|
||||
this.memberStacks[member] = loadStacks(member);
|
||||
}
|
||||
|
||||
return null;
|
||||
return this.memberStacks[member];
|
||||
}
|
||||
|
||||
public List<ItemStack> getOutputVariants(Point slotKey) {
|
||||
final List<ItemStack> items = new ArrayList<>();
|
||||
protected MemberStacks loadStacks(int member) {
|
||||
final int recipeIndex = getRecipeIndex(member);
|
||||
final List<PositionedStack> inputs = new ArrayList<>(this.handler.getIngredientStacks(recipeIndex));
|
||||
final PositionedStack pStackResult = this.handler.getResultStack(recipeIndex);
|
||||
final List<PositionedStack> others = new ArrayList<>(this.handler.getOtherStacks(recipeIndex));
|
||||
|
||||
for (int member = 0; member < size(); member++) {
|
||||
final PositionedStack slot = getOutputSlot(member, slotKey);
|
||||
if (pStackResult != null) {
|
||||
return new MemberStacks(inputs, Collections.singletonList(pStackResult), others);
|
||||
}
|
||||
|
||||
if (slot != null && PermutationsCycler.indexOf(items, slot.item) == -1) {
|
||||
items.add(slot.item);
|
||||
return new MemberStacks(inputs, others, Collections.emptyList());
|
||||
}
|
||||
|
||||
// re-reads the member stacks from the handler, keeps the cached instances when nothing changed
|
||||
public void refresh(int member) {
|
||||
|
||||
if (this.memberStacks[member] == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final MemberStacks stacks = loadStacks(member);
|
||||
|
||||
if (!isSameStacks(this.memberStacks[member], stacks)) {
|
||||
this.memberStacks[member] = stacks;
|
||||
this.slotPermutations.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
// index into PositionedStack.items of the permutation to render, -1 when the slot is unknown
|
||||
public int getCycledPermutationIndex(int member, Point slotKey, int index) {
|
||||
this.slotPermutations.validate();
|
||||
|
||||
final FilteredSlot filtered = getFilteredSlot(member, slotKey);
|
||||
|
||||
if (filtered == null || filtered.indices.length == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
final int size = filtered.favoriteCount > 0 ? filtered.favoriteCount : filtered.indices.length;
|
||||
return filtered.indices[index % size];
|
||||
}
|
||||
|
||||
protected boolean isSameStacks(MemberStacks stacksA, MemberStacks stacksB) {
|
||||
|
||||
if (stacksA.inputs.size() != stacksB.inputs.size() || stacksA.outputs.size() != stacksB.outputs.size()
|
||||
|| stacksA.auxiliaries.size() != stacksB.auxiliaries.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < stacksA.states.size(); i++) {
|
||||
final StackState stateA = stacksA.states.get(i);
|
||||
final StackState stateB = stacksB.states.get(i);
|
||||
|
||||
if (stateA.relx != stateB.relx || stateA.rely != stateB.rely || !isSameItems(stateA.items, stateB.items)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean isSameItems(ItemStack[] itemsA, ItemStack[] itemsB) {
|
||||
|
||||
if (itemsA == itemsB) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (itemsA.length != itemsB.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < itemsA.length; i++) {
|
||||
if (!isSameStack(itemsA[i], itemsB[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// true when the group has something to cycle: several members or a slot with several permutations
|
||||
public boolean canCycle() {
|
||||
this.slotPermutations.validate();
|
||||
|
||||
if (this.cycling == null) {
|
||||
this.cycling = size() > 1 || getStacks(0).slots.keySet().stream()
|
||||
.anyMatch(slotKey -> getFilteredPermutations(0, slotKey).size() > 1);
|
||||
}
|
||||
|
||||
return this.cycling;
|
||||
}
|
||||
|
||||
public List<Integer> getAllowedMembers() {
|
||||
@@ -135,17 +322,20 @@ public class RecipeGroup {
|
||||
return SINGLE_MEMBER;
|
||||
}
|
||||
|
||||
if (this.allowedMembers == null) {
|
||||
final Set<Point> favoriteSlots = new HashSet<>();
|
||||
if (this.allowedMembers == null && !FavoriteRecipes.hasManual()) {
|
||||
this.allowedMembers = new ArrayList<>(size());
|
||||
|
||||
for (int member = 0; member < size(); member++) {
|
||||
for (PositionedStack pStack : getCyclingStacks(member)) {
|
||||
final Point slotKey = getSlotKey(pStack);
|
||||
final List<ItemStack> items = getPermutations(slotKey);
|
||||
this.allowedMembers.add(member);
|
||||
}
|
||||
|
||||
if (items.size() > 1 && items.stream().anyMatch(FavoriteRecipes::containsManual)) {
|
||||
favoriteSlots.add(slotKey);
|
||||
}
|
||||
} else if (this.allowedMembers == null) {
|
||||
final Set<Point> favoriteSlots = new HashSet<>();
|
||||
|
||||
for (Point slotKey : getSlotStacks().keySet()) {
|
||||
// a shared slot gives every member the same misses, so it can't affect the filter
|
||||
if (!this.sharedSlots.contains(slotKey) && this.slotPermutations.getFavoriteCount(slotKey) > 0) {
|
||||
favoriteSlots.add(slotKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,6 +345,72 @@ public class RecipeGroup {
|
||||
return this.allowedMembers;
|
||||
}
|
||||
|
||||
protected Map<Point, PositionedStack[]> getSlotStacks() {
|
||||
|
||||
if (this.slotStacks == null) {
|
||||
this.slotStacks = new LinkedHashMap<>();
|
||||
|
||||
for (int member = 0; member < size(); member++) {
|
||||
for (PositionedStack pStack : getCyclingStacks(member)) {
|
||||
final PositionedStack[] stacks = this.slotStacks
|
||||
.computeIfAbsent(getSlotKey(pStack), key -> new PositionedStack[size()]);
|
||||
|
||||
if (stacks[member] == null) {
|
||||
stacks[member] = pStack;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<Point, PositionedStack[]> entry : this.slotStacks.entrySet()) {
|
||||
if (isSharedSlot(entry.getValue())) {
|
||||
this.sharedSlots.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.slotStacks;
|
||||
}
|
||||
|
||||
// all members hold the same permutations in this slot
|
||||
protected boolean isSharedSlot(PositionedStack[] stacks) {
|
||||
ItemStack[] first = null;
|
||||
|
||||
for (PositionedStack pStack : stacks) {
|
||||
if (pStack == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (first == null) {
|
||||
first = pStack.items;
|
||||
} else if (!isSameItems(first, pStack.items)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean isSameStack(ItemStack stackA, ItemStack stackB) {
|
||||
return stackA == stackB || NEIServerUtils.areStacksSameType(stackA, stackB)
|
||||
&& StackInfo.getAmount(stackA) == StackInfo.getAmount(stackB);
|
||||
}
|
||||
|
||||
protected List<ItemStack> getFilteredPermutations(int member, Point slotKey) {
|
||||
final FilteredSlot filtered = getFilteredSlot(member, slotKey);
|
||||
return filtered != null ? filtered.items : null;
|
||||
}
|
||||
|
||||
protected FilteredSlot getFilteredSlot(int member, Point slotKey) {
|
||||
final MemberStacks stacks = getStacks(member);
|
||||
final PositionedStack slot = stacks.slots.get(slotKey);
|
||||
|
||||
if (slot == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return stacks.filtered.computeIfAbsent(slotKey, key -> new FilteredSlot(slot));
|
||||
}
|
||||
|
||||
protected List<Integer> filterMembers(Set<Point> favoriteSlots) {
|
||||
final List<Integer> indices = new ArrayList<>();
|
||||
int minMisses = Integer.MAX_VALUE;
|
||||
@@ -179,9 +435,9 @@ public class RecipeGroup {
|
||||
int misses = 0;
|
||||
|
||||
for (Point slotKey : favoriteSlots) {
|
||||
final PositionedStack slot = getSlot(member, slotKey);
|
||||
final List<ItemStack> permutations = getFilteredPermutations(member, slotKey);
|
||||
|
||||
if (slot != null && slot.getFilteredPermutations().stream().noneMatch(FavoriteRecipes::containsManual)) {
|
||||
if (permutations != null && permutations.stream().noneMatch(FavoriteRecipes::containsManual)) {
|
||||
misses++;
|
||||
}
|
||||
}
|
||||
@@ -190,21 +446,31 @@ public class RecipeGroup {
|
||||
}
|
||||
|
||||
protected List<ItemStack> collectSlotPermutations(Point slotKey) {
|
||||
final List<ItemStack> items = new ArrayList<>();
|
||||
|
||||
if (!getSlotStacks().containsKey(slotKey)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final Map<Object, ItemStack> items = new LinkedHashMap<>();
|
||||
final boolean shared = this.sharedSlots.contains(slotKey);
|
||||
|
||||
for (int member = 0; member < size(); member++) {
|
||||
final PositionedStack slot = getSlot(member, slotKey);
|
||||
final List<ItemStack> permutations = getFilteredPermutations(member, slotKey);
|
||||
|
||||
if (slot != null) {
|
||||
for (ItemStack stack : slot.getFilteredPermutations()) {
|
||||
if (PermutationsCycler.indexOf(items, stack) == -1) {
|
||||
items.add(stack);
|
||||
if (permutations != null) {
|
||||
for (ItemStack stack : permutations) {
|
||||
if (stack != null) {
|
||||
items.putIfAbsent(PermutationsCycler.getStackKey(stack), stack);
|
||||
}
|
||||
}
|
||||
|
||||
if (shared) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
return new ArrayList<>(items.values());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package codechicken.nei.recipe.widget;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -21,7 +22,6 @@ import org.lwjgl.opengl.GL12;
|
||||
import codechicken.lib.gui.GuiDraw;
|
||||
import codechicken.lib.vec.Rectangle4i;
|
||||
import codechicken.nei.FavoriteRecipes;
|
||||
import codechicken.nei.ItemsTooltipLineHandler;
|
||||
import codechicken.nei.KeyManager;
|
||||
import codechicken.nei.NEIClientConfig;
|
||||
import codechicken.nei.NEIClientUtils;
|
||||
@@ -29,6 +29,8 @@ import codechicken.nei.PositionedStack;
|
||||
import codechicken.nei.Widget;
|
||||
import codechicken.nei.api.IGuiContainerOverlay;
|
||||
import codechicken.nei.api.ShortcutInputHandler;
|
||||
import codechicken.nei.drawable.DrawableBuilder;
|
||||
import codechicken.nei.drawable.DrawableResource;
|
||||
import codechicken.nei.guihook.GuiContainerManager;
|
||||
import codechicken.nei.recipe.AcceptsFollowingTooltipLineHandler;
|
||||
import codechicken.nei.recipe.Badge;
|
||||
@@ -47,28 +49,35 @@ import codechicken.nei.util.NEIMouseUtils;
|
||||
public class RecipeWidget extends Widget {
|
||||
|
||||
protected static final int TICKS_PER_CYCLE = 20;
|
||||
protected static final DrawableResource PAUSE_MARKER = new DrawableBuilder(
|
||||
"nei:textures/pinned_pause.png",
|
||||
0,
|
||||
0,
|
||||
8,
|
||||
8).setTextureSize(8, 8).build();
|
||||
protected static final int PAUSE_MARKER_OFFSET = 1;
|
||||
|
||||
protected final RecipeGroup group;
|
||||
protected final IRecipeHandler handler;
|
||||
protected final HandlerInfo handlerInfo;
|
||||
|
||||
protected final PermutationsCycler<PositionedStack> renderPermutations;
|
||||
|
||||
protected final Map<PositionedStack, List<Badge>> badgeCache = new WeakHashMap<>();
|
||||
protected final Map<Integer, List<GuiRecipeButton>> buttonCache = new HashMap<>();
|
||||
|
||||
protected AcceptsFollowingTooltipLineHandler acceptsTooltip;
|
||||
protected ItemsTooltipLineHandler variantsTooltip;
|
||||
protected Point variantsSlotKey;
|
||||
|
||||
protected int activeMember = 0;
|
||||
protected int memberCursor = -1;
|
||||
protected int slotCycle = 0;
|
||||
// single state counter: member = allowed[cycle % size], permutation index = cycle / size
|
||||
protected int cycle = 0;
|
||||
protected int cycleticks = 0;
|
||||
protected int lastcycle = -1;
|
||||
protected int favoriteRevision = FavoriteRecipes.getRevision();
|
||||
|
||||
protected boolean showAsWidget = false;
|
||||
protected boolean update = true;
|
||||
protected boolean paused = false;
|
||||
protected List<ItemStack> pinnedIngredients = null;
|
||||
protected Map<Point, ItemStack> pinnedSlots = Collections.emptyMap();
|
||||
|
||||
public RecipeWidget(RecipeHandlerRef handlerRef) {
|
||||
this(handlerRef.handler, Collections.singletonList(handlerRef.recipeIndex));
|
||||
@@ -82,13 +91,6 @@ public class RecipeWidget extends Widget {
|
||||
this.group = group;
|
||||
this.handler = group.getHandler();
|
||||
this.handlerInfo = group.getHandlerInfo();
|
||||
this.renderPermutations = new PermutationsCycler<PositionedStack>(
|
||||
new WeakHashMap<>(),
|
||||
new WeakHashMap<>(),
|
||||
PositionedStack::getFilteredPermutations).onInvalidate(() -> {
|
||||
this.badgeCache.clear();
|
||||
this.acceptsTooltip = null;
|
||||
});
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -117,6 +119,104 @@ public class RecipeWidget extends Widget {
|
||||
this.showAsWidget = show;
|
||||
}
|
||||
|
||||
public boolean isPaused() {
|
||||
return this.paused;
|
||||
}
|
||||
|
||||
public List<ItemStack> getPinnedIngredients() {
|
||||
return this.pinnedIngredients;
|
||||
}
|
||||
|
||||
public void setPaused(boolean paused) {
|
||||
final boolean wasPaused = this.paused;
|
||||
this.paused = paused && this.group.canCycle();
|
||||
|
||||
if (!this.paused) {
|
||||
this.pinnedIngredients = null;
|
||||
this.pinnedSlots = Collections.emptyMap();
|
||||
|
||||
if (wasPaused) {
|
||||
syncCycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses the widget on the recipe. Every given ingredient needs an input slot of its own that accepts it, otherwise
|
||||
* the recipe is not pinned.
|
||||
*/
|
||||
public boolean pinRecipe(int recipeIndex, List<ItemStack> ingredients) {
|
||||
final int member = this.group.indexOf(recipeIndex);
|
||||
|
||||
if (member < 0 || !this.group.canCycle()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Map<Point, ItemStack> slots = ingredients != null
|
||||
? matchIngredients(this.group.getInputs(member), ingredients)
|
||||
: Collections.emptyMap();
|
||||
|
||||
if (slots == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.pinnedIngredients = ingredients;
|
||||
this.pinnedSlots = slots;
|
||||
this.paused = true;
|
||||
setActiveMember(member);
|
||||
updatePermutations();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// assigns each ingredient to its own slot (bipartite matching), null when some ingredient has no slot
|
||||
private Map<Point, ItemStack> matchIngredients(List<PositionedStack> stacks, List<ItemStack> ingredients) {
|
||||
final int[] slotOwners = new int[stacks.size()];
|
||||
Arrays.fill(slotOwners, -1);
|
||||
|
||||
for (int ingredient = 0; ingredient < ingredients.size(); ingredient++) {
|
||||
if (!assignIngredient(ingredient, stacks, ingredients, slotOwners, new boolean[stacks.size()])) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
final Map<Point, ItemStack> slots = new HashMap<>();
|
||||
|
||||
for (int slot = 0; slot < slotOwners.length; slot++) {
|
||||
if (slotOwners[slot] != -1) {
|
||||
slots.put(this.group.getSlotKey(stacks.get(slot)), ingredients.get(slotOwners[slot]));
|
||||
}
|
||||
}
|
||||
|
||||
return slots;
|
||||
}
|
||||
|
||||
private boolean assignIngredient(int ingredient, List<PositionedStack> stacks, List<ItemStack> ingredients,
|
||||
int[] slotOwners, boolean[] visited) {
|
||||
|
||||
for (int slot = 0; slot < stacks.size(); slot++) {
|
||||
if (!visited[slot] && stacks.get(slot).getPermutationIndex(ingredients.get(ingredient)) != -1) {
|
||||
visited[slot] = true;
|
||||
|
||||
if (slotOwners[slot] == -1
|
||||
|| assignIngredient(slotOwners[slot], stacks, ingredients, slotOwners, visited)) {
|
||||
slotOwners[slot] = ingredient;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void applyPinnedIngredient(PositionedStack pStack) {
|
||||
final ItemStack ingredient = this.pinnedSlots.get(this.group.getSlotKey(pStack));
|
||||
|
||||
if (ingredient != null) {
|
||||
pStack.setPermutationToRender(ingredient);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
|
||||
@@ -142,11 +242,11 @@ public class RecipeWidget extends Widget {
|
||||
return this.buttonCache.computeIfAbsent(this.activeMember, this::createButtons);
|
||||
}
|
||||
|
||||
protected List<GuiRecipeButton> getRecipeButtonsIfInit() {
|
||||
private List<GuiRecipeButton> getRecipeButtonsIfInit() {
|
||||
return this.buttonCache.getOrDefault(this.activeMember, Collections.emptyList());
|
||||
}
|
||||
|
||||
protected List<GuiRecipeButton> createButtons(int member) {
|
||||
private List<GuiRecipeButton> createButtons(int member) {
|
||||
|
||||
if (this.group.getOutputs(member).isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
@@ -156,7 +256,7 @@ public class RecipeWidget extends Widget {
|
||||
.unmodifiableList(initButtons(RecipeHandlerRef.of(this.handler, this.group.getRecipeIndex(member))));
|
||||
}
|
||||
|
||||
protected List<GuiRecipeButton> initButtons(RecipeHandlerRef ref) {
|
||||
private List<GuiRecipeButton> initButtons(RecipeHandlerRef ref) {
|
||||
final GuiRecipe<?> guiRecipe = getGuiRecipe();
|
||||
|
||||
if (guiRecipe != null) {
|
||||
@@ -186,7 +286,7 @@ public class RecipeWidget extends Widget {
|
||||
|
||||
}
|
||||
|
||||
protected List<GuiRecipeButton> getDefaultButtons(RecipeHandlerRef ref) {
|
||||
private List<GuiRecipeButton> getDefaultButtons(RecipeHandlerRef ref) {
|
||||
GuiContainer guiContainer = NEIClientUtils.getGuiContainer();
|
||||
final List<GuiRecipeButton> buttons = new ArrayList<>();
|
||||
final boolean showFavorites = NEIClientConfig.favoritesEnabled() && handlerInfo.getShowFavoritesButton();
|
||||
@@ -217,13 +317,17 @@ public class RecipeWidget extends Widget {
|
||||
if (this.update) {
|
||||
this.update = false;
|
||||
|
||||
if (tickCycle()) {
|
||||
|
||||
if (this.showAsWidget || !contains(mouseX, mouseY)) {
|
||||
nextMember();
|
||||
}
|
||||
|
||||
updatePermutations(this.lastcycle);
|
||||
if (this.lastcycle == -1) {
|
||||
this.lastcycle = 0;
|
||||
applyCycle();
|
||||
} else if (tickCycle() && !isCycleFrozen(mouseX, mouseY)) {
|
||||
this.cycle++;
|
||||
applyCycle();
|
||||
} else if (this.favoriteRevision != FavoriteRecipes.getRevision()) {
|
||||
this.favoriteRevision = FavoriteRecipes.getRevision();
|
||||
this.badgeCache.clear();
|
||||
this.acceptsTooltip = null;
|
||||
updatePermutations();
|
||||
}
|
||||
|
||||
for (GuiRecipeButton button : getRecipeButtons()) {
|
||||
@@ -244,21 +348,7 @@ public class RecipeWidget extends Widget {
|
||||
|
||||
GuiContainerManager.enableMatrixStackLogging();
|
||||
|
||||
for (PositionedStack pStack : getInputs()) {
|
||||
|
||||
if (!this.renderPermutations.contains(pStack)) {
|
||||
updatePermutationsFor(pStack);
|
||||
}
|
||||
|
||||
drawItem(pStack, mouseX, mouseY, yShift, true);
|
||||
}
|
||||
|
||||
for (PositionedStack pStack : getCatalysts()) {
|
||||
|
||||
if (!this.renderPermutations.contains(pStack)) {
|
||||
updatePermutationsFor(pStack);
|
||||
}
|
||||
|
||||
for (PositionedStack pStack : getCyclingStacks()) {
|
||||
drawItem(pStack, mouseX, mouseY, yShift, true);
|
||||
}
|
||||
|
||||
@@ -283,6 +373,10 @@ public class RecipeWidget extends Widget {
|
||||
GL11.glTranslatef(-this.x, -this.y - yShift, 0);
|
||||
GL11.glPopAttrib();
|
||||
|
||||
if (this.paused) {
|
||||
NEIClientUtils.gl2DRenderContext(this::drawPauseMarker);
|
||||
}
|
||||
|
||||
if (!this.showAsWidget) {
|
||||
final Minecraft mc = NEIClientUtils.mc();
|
||||
for (GuiRecipeButton button : getRecipeButtons()) {
|
||||
@@ -297,7 +391,22 @@ public class RecipeWidget extends Widget {
|
||||
DebugHandlerWidget.instance.drawGuiPlaceholder(this);
|
||||
}
|
||||
|
||||
protected void drawItem(PositionedStack pStack, int mouseX, int mouseY, int yShift, boolean input) {
|
||||
private void drawPauseMarker() {
|
||||
GL11.glColor4f(1, 1, 1, 1);
|
||||
PAUSE_MARKER.draw(this.x + PAUSE_MARKER_OFFSET, this.y + PAUSE_MARKER_OFFSET);
|
||||
}
|
||||
|
||||
private boolean isPauseMarkerMouseOver(int mouseX, int mouseY) {
|
||||
final int left = this.x + PAUSE_MARKER_OFFSET;
|
||||
final int top = this.y + PAUSE_MARKER_OFFSET;
|
||||
|
||||
return this.paused && mouseX >= left
|
||||
&& mouseX < left + PAUSE_MARKER.getWidth()
|
||||
&& mouseY >= top
|
||||
&& mouseY < top + PAUSE_MARKER.getHeight();
|
||||
}
|
||||
|
||||
private void drawItem(PositionedStack pStack, int mouseX, int mouseY, int yShift, boolean input) {
|
||||
pStack.draw(mouseX - this.x, mouseY - this.y - yShift);
|
||||
|
||||
if (this.handlerInfo.getShowBadge()) {
|
||||
@@ -310,7 +419,7 @@ public class RecipeWidget extends Widget {
|
||||
}
|
||||
}
|
||||
|
||||
protected List<Badge> getBadges(PositionedStack pStack, boolean input) {
|
||||
private List<Badge> getBadges(PositionedStack pStack, boolean input) {
|
||||
|
||||
return this.badgeCache.computeIfAbsent(pStack, k -> {
|
||||
final List<Badge> badges = k.getBadges();
|
||||
@@ -339,7 +448,7 @@ public class RecipeWidget extends Widget {
|
||||
|
||||
}
|
||||
|
||||
protected void drawBadge(PositionedStack pStack, boolean input) {
|
||||
private void drawBadge(PositionedStack pStack, boolean input) {
|
||||
final List<Badge> badges = getBadges(pStack, input);
|
||||
|
||||
for (Badge badge : badges) {
|
||||
@@ -357,6 +466,7 @@ public class RecipeWidget extends Widget {
|
||||
|
||||
if (hovered != null && getPermutations(hovered).size() > 1) {
|
||||
FavoriteRecipes.toggleFavoriteItem(hovered.item);
|
||||
setPaused(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -383,6 +493,12 @@ public class RecipeWidget extends Widget {
|
||||
@Override
|
||||
public boolean handleClick(int mouseX, int mouseY, int button) {
|
||||
|
||||
if (button == 0 && isPauseMarkerMouseOver(mouseX, mouseY)) {
|
||||
NEIClientUtils.playClickSound();
|
||||
setPaused(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ShortcutInputHandler.handleMouseClick(getStackMouseOver(mouseX, mouseY))) {
|
||||
return true;
|
||||
}
|
||||
@@ -421,6 +537,11 @@ public class RecipeWidget extends Widget {
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
if (isPauseMarkerMouseOver(mouseX, mouseY)) {
|
||||
tooltip.add(NEIClientUtils.translate("recipe.pinned"));
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
final List<String> buttonTooltip = forEachButtons(
|
||||
button -> button.contains(mouseX, mouseY) ? button.handleTooltip(tooltip) : null,
|
||||
null);
|
||||
@@ -481,30 +602,6 @@ public class RecipeWidget extends Widget {
|
||||
tooltip.add(GuiDraw.TOOLTIP_HANDLER + GuiDraw.getTipLineId(this.acceptsTooltip));
|
||||
}
|
||||
|
||||
final Point outputKey = cycling == null && hovered != null && this.group.size() > 1
|
||||
? this.group.getSlotKey(hovered)
|
||||
: null;
|
||||
|
||||
if (outputKey == null || !NEIClientConfig.showCycledIngredientsTooltip()) {
|
||||
this.variantsTooltip = null;
|
||||
this.variantsSlotKey = null;
|
||||
} else if (!outputKey.equals(this.variantsSlotKey)) {
|
||||
final List<ItemStack> variants = this.group.getOutputVariants(outputKey);
|
||||
|
||||
this.variantsSlotKey = outputKey;
|
||||
this.variantsTooltip = variants.size() > 1
|
||||
? new ItemsTooltipLineHandler(NEIClientUtils.translate("recipe.group"), variants, false, 4)
|
||||
: null;
|
||||
|
||||
if (this.variantsTooltip != null) {
|
||||
this.variantsTooltip.setActiveStack(hovered.item);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.variantsTooltip != null) {
|
||||
tooltip.add(GuiDraw.TOOLTIP_HANDLER + GuiDraw.getTipLineId(this.variantsTooltip));
|
||||
}
|
||||
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
@@ -563,7 +660,7 @@ public class RecipeWidget extends Widget {
|
||||
return guiRecipe != null && this.handler.mouseScrolled(guiRecipe, scroll, getRecipeIndex());
|
||||
}
|
||||
|
||||
protected boolean scrollPermutations(int scroll, PositionedStack hovered) {
|
||||
private boolean scrollPermutations(int scroll, PositionedStack hovered) {
|
||||
final Point slotKey = this.group.getSlotKey(hovered);
|
||||
final List<ItemStack> items = this.group.getPermutations(slotKey);
|
||||
|
||||
@@ -571,46 +668,23 @@ public class RecipeWidget extends Widget {
|
||||
return false;
|
||||
}
|
||||
|
||||
final List<PositionedStack> slots = new ArrayList<>(this.group.size());
|
||||
final int current = Math.max(0, PermutationsCycler.indexOf(items, hovered.item));
|
||||
final int direction = scroll > 0 ? -1 : 1;
|
||||
|
||||
for (int member = 0; member < this.group.size(); member++) {
|
||||
slots.add(this.group.getSlot(member, slotKey));
|
||||
}
|
||||
|
||||
final int anchor = getCursorMember();
|
||||
final int currentItem = PermutationsCycler.indexOf(items, hovered.item);
|
||||
final List<int[]> variants = new ArrayList<>();
|
||||
int current = 0;
|
||||
|
||||
for (int itemIndex = 0; itemIndex < items.size(); itemIndex++) {
|
||||
final ItemStack stack = items.get(itemIndex);
|
||||
int member = containsStack(slots.get(this.activeMember), stack) ? this.activeMember : -1;
|
||||
|
||||
for (int offset = 0; member == -1 && offset < slots.size(); offset++) {
|
||||
if (containsStack(slots.get((anchor + offset) % slots.size()), stack)) {
|
||||
member = (anchor + offset) % slots.size();
|
||||
}
|
||||
}
|
||||
for (int step = 1; step <= items.size(); step++) {
|
||||
final ItemStack stack = items.get(Math.floorMod(current + direction * step, items.size()));
|
||||
final int member = this.group.findMember(slotKey, stack, this.activeMember);
|
||||
|
||||
if (member != -1) {
|
||||
if (itemIndex == currentItem) {
|
||||
current = variants.size();
|
||||
}
|
||||
variants.add(new int[] { itemIndex, member });
|
||||
switchMember(member, stack);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (variants.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int[] next = variants.get(((current - scroll) % variants.size() + variants.size()) % variants.size());
|
||||
switchMember(next[1], items.get(next[0]));
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean scrollMembers(int scroll) {
|
||||
private boolean scrollMembers(int scroll) {
|
||||
final int size = this.group.size();
|
||||
|
||||
if (size <= 1) {
|
||||
@@ -622,14 +696,19 @@ public class RecipeWidget extends Widget {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void switchMember(int member, ItemStack stack) {
|
||||
private void switchMember(int member, ItemStack stack) {
|
||||
final Map<Point, ItemStack> rendered = new HashMap<>();
|
||||
|
||||
setPaused(false);
|
||||
|
||||
for (PositionedStack pStack : getCyclingStacks()) {
|
||||
rendered.put(this.group.getSlotKey(pStack), pStack.item);
|
||||
}
|
||||
|
||||
setActiveMember(member);
|
||||
syncCycle();
|
||||
this.group.refresh(member);
|
||||
this.badgeCache.clear();
|
||||
|
||||
for (PositionedStack pStack : getCyclingStacks()) {
|
||||
final ItemStack renderStack = containsStack(pStack, stack) ? stack
|
||||
@@ -637,6 +716,8 @@ public class RecipeWidget extends Widget {
|
||||
|
||||
if (containsStack(pStack, renderStack)) {
|
||||
pStack.setPermutationToRender(renderStack);
|
||||
} else {
|
||||
updatePermutationsFor(pStack);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -644,10 +725,9 @@ public class RecipeWidget extends Widget {
|
||||
notifyPermutationsChanged();
|
||||
}
|
||||
|
||||
protected boolean tickCycle() {
|
||||
private boolean tickCycle() {
|
||||
|
||||
if (!NEIClientUtils.shiftKey() && (this.cycleticks++ / TICKS_PER_CYCLE) != this.lastcycle
|
||||
|| this.lastcycle == -1) {
|
||||
if ((this.cycleticks++ / TICKS_PER_CYCLE) != this.lastcycle) {
|
||||
this.lastcycle = this.cycleticks / TICKS_PER_CYCLE;
|
||||
return true;
|
||||
}
|
||||
@@ -655,20 +735,45 @@ public class RecipeWidget extends Widget {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void nextMember() {
|
||||
final List<Integer> allowed = this.group.getAllowedMembers();
|
||||
private boolean isCycleFrozen(int mouseX, int mouseY) {
|
||||
|
||||
this.memberCursor = (this.memberCursor + 1) % allowed.size();
|
||||
setActiveMember(allowed.get(this.memberCursor));
|
||||
if (this.paused || NEIClientUtils.shiftKey()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !this.showAsWidget && contains(mouseX, mouseY)
|
||||
&& (getPositionedStackMouseOver(mouseX, mouseY) != null
|
||||
|| forEachButtons(button -> button.contains(mouseX, mouseY) ? true : null, false));
|
||||
}
|
||||
|
||||
protected int getCursorMember() {
|
||||
final List<Integer> allowed = this.group.getAllowedMembers();
|
||||
return allowed.get(Math.max(0, this.memberCursor) % allowed.size());
|
||||
private void applyCycle() {
|
||||
|
||||
if (!this.paused) {
|
||||
final List<Integer> allowed = this.group.getAllowedMembers();
|
||||
setActiveMember(allowed.get(this.cycle % allowed.size()));
|
||||
}
|
||||
|
||||
this.group.refresh(this.activeMember);
|
||||
updatePermutations();
|
||||
}
|
||||
|
||||
protected void updatePermutations(int cycle) {
|
||||
this.slotCycle = cycle;
|
||||
private int getPermutationIndex() {
|
||||
return this.cycle / this.group.getAllowedMembers().size();
|
||||
}
|
||||
|
||||
// keep the permutation index, move the member part of the counter to the active member
|
||||
private void syncCycle() {
|
||||
final List<Integer> allowed = this.group.getAllowedMembers();
|
||||
final int allowedIndex = allowed.indexOf(this.activeMember);
|
||||
|
||||
if (allowedIndex >= 0) {
|
||||
this.cycle = this.cycle / allowed.size() * allowed.size() + allowedIndex;
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePermutations() {
|
||||
// badges depend on the rendered item
|
||||
this.badgeCache.clear();
|
||||
|
||||
for (PositionedStack pStack : getCyclingStacks()) {
|
||||
updatePermutationsFor(pStack);
|
||||
@@ -678,7 +783,7 @@ public class RecipeWidget extends Widget {
|
||||
notifyPermutationsChanged();
|
||||
}
|
||||
|
||||
protected void updateTooltipActiveStack() {
|
||||
private void updateTooltipActiveStack() {
|
||||
|
||||
if (this.acceptsTooltip != null && this.acceptsTooltip.tooltipGUID instanceof Point slotKey) {
|
||||
final PositionedStack pStack = this.group.getSlot(this.activeMember, slotKey);
|
||||
@@ -688,29 +793,20 @@ public class RecipeWidget extends Widget {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.variantsTooltip != null) {
|
||||
final PositionedStack pStack = this.group.getOutputSlot(this.activeMember, this.variantsSlotKey);
|
||||
|
||||
if (pStack != null) {
|
||||
this.variantsTooltip.setActiveStack(pStack.item);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void updatePermutationsFor(PositionedStack pStack) {
|
||||
private void updatePermutationsFor(PositionedStack pStack) {
|
||||
final int index = this.group
|
||||
.getCycledPermutationIndex(this.activeMember, this.group.getSlotKey(pStack), getPermutationIndex());
|
||||
|
||||
if (!this.renderPermutations.contains(pStack) || this.renderPermutations.get(pStack).size() > 1) {
|
||||
final ItemStack stack = this.renderPermutations.getCycled(pStack, this.slotCycle);
|
||||
|
||||
if (stack != null) {
|
||||
pStack.setPermutationToRender(stack);
|
||||
}
|
||||
if (index >= 0) {
|
||||
pStack.setPermutationToRender(index);
|
||||
}
|
||||
|
||||
applyPinnedIngredient(pStack);
|
||||
}
|
||||
|
||||
protected void setActiveMember(int member) {
|
||||
private void setActiveMember(int member) {
|
||||
|
||||
if (member != this.activeMember) {
|
||||
this.activeMember = member;
|
||||
@@ -722,22 +818,21 @@ public class RecipeWidget extends Widget {
|
||||
|
||||
}
|
||||
|
||||
protected boolean containsStack(PositionedStack pStack, ItemStack stack) {
|
||||
return pStack != null && stack != null
|
||||
&& PermutationsCycler.indexOf(pStack.getFilteredPermutations(), stack) != -1;
|
||||
private boolean containsStack(PositionedStack pStack, ItemStack stack) {
|
||||
return pStack != null && this.group.hasPermutation(this.activeMember, this.group.getSlotKey(pStack), stack);
|
||||
}
|
||||
|
||||
protected List<ItemStack> getPermutations(PositionedStack pStack) {
|
||||
private List<ItemStack> getPermutations(PositionedStack pStack) {
|
||||
return this.group.getPermutations(this.group.getSlotKey(pStack));
|
||||
}
|
||||
|
||||
protected void notifyPermutationsChanged() {
|
||||
private void notifyPermutationsChanged() {
|
||||
for (GuiRecipeButton button : getRecipeButtonsIfInit()) {
|
||||
button.onPermutationsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
protected <R> R forEachButtons(Function<GuiRecipeButton, R> callback, R defaultValue) {
|
||||
private <R> R forEachButtons(Function<GuiRecipeButton, R> callback, R defaultValue) {
|
||||
|
||||
for (GuiRecipeButton button : getRecipeButtonsIfInit()) {
|
||||
button.xPosition += this.x;
|
||||
@@ -773,7 +868,7 @@ public class RecipeWidget extends Widget {
|
||||
return cycling != null ? cycling : getOutputStackMouseOver(mx, my);
|
||||
}
|
||||
|
||||
protected PositionedStack getOutputStackMouseOver(int mx, int my) {
|
||||
private PositionedStack getOutputStackMouseOver(int mx, int my) {
|
||||
final int yShift = this.handlerInfo.getYShift();
|
||||
|
||||
for (PositionedStack pStack : getOutputs()) {
|
||||
@@ -785,16 +880,10 @@ public class RecipeWidget extends Widget {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected PositionedStack getCyclingStackMouseOver(int mx, int my) {
|
||||
private PositionedStack getCyclingStackMouseOver(int mx, int my) {
|
||||
final int yShift = this.handlerInfo.getYShift();
|
||||
|
||||
for (PositionedStack pStack : getInputs()) {
|
||||
if (pStack.contains(mx - this.x, my - this.y - yShift)) {
|
||||
return pStack;
|
||||
}
|
||||
}
|
||||
|
||||
for (PositionedStack pStack : getCatalysts()) {
|
||||
for (PositionedStack pStack : getCyclingStacks()) {
|
||||
if (pStack.contains(mx - this.x, my - this.y - yShift)) {
|
||||
return pStack;
|
||||
}
|
||||
@@ -809,7 +898,7 @@ public class RecipeWidget extends Widget {
|
||||
return pStack != null ? pStack.item : null;
|
||||
}
|
||||
|
||||
protected GuiRecipe<?> getGuiRecipe() {
|
||||
private GuiRecipe<?> getGuiRecipe() {
|
||||
final GuiContainer guiContainer = NEIClientUtils.getGuiContainer();
|
||||
|
||||
if (guiContainer instanceof GuiRecipe<?>guiRecipe) {
|
||||
@@ -819,18 +908,10 @@ public class RecipeWidget extends Widget {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected List<PositionedStack> getInputs() {
|
||||
return this.group.getInputs(this.activeMember);
|
||||
}
|
||||
|
||||
protected List<PositionedStack> getOutputs() {
|
||||
return this.group.getOutputs(this.activeMember);
|
||||
}
|
||||
|
||||
protected List<PositionedStack> getCatalysts() {
|
||||
return this.group.getCatalysts(this.activeMember);
|
||||
}
|
||||
|
||||
protected List<PositionedStack> getCyclingStacks() {
|
||||
return this.group.getCyclingStacks(this.activeMember);
|
||||
}
|
||||
|
||||
@@ -685,8 +685,8 @@ nei.recipe.concurrent.usage=Concurrent Usage - Total
|
||||
nei.recipe.concurrent.crafting=Concurrent Crafting - Total
|
||||
nei.recipe.accepts=Accepts following
|
||||
nei.recipe.accepts.scroll=Change Item
|
||||
nei.recipe.group=Variants
|
||||
nei.recipe.group.scroll=Change Variant (%s/%s)
|
||||
nei.recipe.pinned=Click to resume cycling
|
||||
|
||||
nei.recipe.overlay=Recipe Overlay
|
||||
nei.recipe.overlay.missing=Missing following
|
||||
|
||||
@@ -422,6 +422,7 @@ nei.recipe.profiler.usage=Профилирование использовани
|
||||
nei.recipe.concurrent.usage=Одновременное использование - всего
|
||||
nei.recipe.concurrent.crafting=Одновременный крафт - всего
|
||||
nei.recipe.accepts=Принимает следующее
|
||||
nei.recipe.pinned=Нажмите, чтобы продолжить перебор
|
||||
|
||||
nei.recipe.overlay=отображение рецепта
|
||||
nei.recipe.overlay.missing=Отсутствует следующее
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 554 B |
Reference in new issue
Block a user