Smart-Fallback & Alchemic Chemistry Set Fix (#935)

Co-authored-by: FFS <FFS@OhFuckOff>
Co-authored-by: slprime <31038811+slprime@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-05-27 02:29:59 +03:00
1 parent 2e9ab88692
commit 31fff21a55
3 files changed
+253 -6

No files matched your search

@@ -197,7 +197,7 @@ public class DefaultOverlayHandler implements IOverlayHandler {
return ingredSlots;
}
private void moveIngredients(GuiContainer gui, List<IngredientDistribution> assignedIngredients, int multiplier) {
protected void moveIngredients(GuiContainer gui, List<IngredientDistribution> assignedIngredients, int multiplier) {
final Slot craftingSlot = getCraftingResultSlot(gui);
for (Slot slot : gui.inventorySlots.inventorySlots) {
@@ -249,7 +249,7 @@ public class DefaultOverlayHandler implements IOverlayHandler {
}
}
private int calculateRecipeQuantity(List<IngredientDistribution> assignedIngredients) {
protected int calculateRecipeQuantity(List<IngredientDistribution> assignedIngredients) {
int quantity = Integer.MAX_VALUE;
for (IngredientDistribution distrib : assignedIngredients) {
@@ -275,7 +275,7 @@ public class DefaultOverlayHandler implements IOverlayHandler {
return quantity;
}
private Slot[][] assignIngredSlots(GuiContainer gui, List<PositionedStack> ingredients,
protected Slot[][] assignIngredSlots(GuiContainer gui, List<PositionedStack> ingredients,
List<IngredientDistribution> assignedIngredients) {
Slot[][] recipeSlots = mapIngredSlots(gui, ingredients); // setup the slot map
@@ -320,7 +320,7 @@ public class DefaultOverlayHandler implements IOverlayHandler {
return recipeSlots;
}
private List<IngredientDistribution> assignIngredients(List<PositionedStack> ingredients,
protected List<IngredientDistribution> assignIngredients(List<PositionedStack> ingredients,
List<DistributedIngred> ingredStacks) {
ArrayList<IngredientDistribution> assignedIngredients = new ArrayList<>();
for (PositionedStack posstack : ingredients) // assign what we need and have
@@ -358,7 +358,7 @@ public class DefaultOverlayHandler implements IOverlayHandler {
return assignedIngredients;
}
private void findInventoryQuantities(GuiContainer gui, List<DistributedIngred> ingredStacks) {
protected void findInventoryQuantities(GuiContainer gui, List<DistributedIngred> ingredStacks) {
for (Slot slot : gui.inventorySlots.inventorySlots) /* work out how much we have to go round */ {
if (slot.getHasStack() && canMoveFrom(slot, gui)) {
final ItemStack pstack = slot.getStack();
@@ -390,7 +390,7 @@ public class DefaultOverlayHandler implements IOverlayHandler {
}
}
private List<DistributedIngred> getPermutationIngredients(List<PositionedStack> ingredients) {
protected List<DistributedIngred> getPermutationIngredients(List<PositionedStack> ingredients) {
ArrayList<DistributedIngred> ingredStacks = new ArrayList<>();
for (PositionedStack posstack : ingredients) /* work out what we need */ {
for (ItemStack pstack : posstack.items) {
@@ -77,9 +77,24 @@ public class RecipeHandlerRef {
return this.handler.getOverlayHandler(firstGui, this.recipeIndex);
}
if (firstGui != null && canUseSmartOverlay(firstGui)) {
return SmartOverlayHandler.INSTANCE;
}
return null;
}
private boolean canUseSmartOverlay(GuiContainer gui) {
if (!(this.handler instanceof TemplateRecipeHandler templateHandler)
|| templateHandler.getOverlayIdentifier() == null
|| templateHandler.getGuiClass() == null
|| !templateHandler.getGuiClass().isAssignableFrom(gui.getClass())) {
return false;
}
return SmartOverlayHandler.canHandle(gui, this.handler, this.recipeIndex);
}
public boolean canFillCraftingGrid(GuiContainer gui) {
final IOverlayHandler overlayHandler = getOverlayHandler(gui);
@@ -0,0 +1,232 @@
package codechicken.nei.recipe;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.inventory.Slot;
import net.minecraft.inventory.SlotCrafting;
import net.minecraft.item.ItemStack;
import codechicken.nei.FastTransferManager;
import codechicken.nei.PositionedStack;
public class SmartOverlayHandler extends DefaultOverlayHandler {
public static final SmartOverlayHandler INSTANCE = new SmartOverlayHandler();
private static final int MAX_RECIPE_SLOTS = 12;
private static final int MAX_COST_DELTA = 16 * 16;
private SmartOverlayHandler() {}
public static boolean canHandle(GuiContainer gui, IRecipeHandler handler, int recipeIndex) {
return INSTANCE.resolveSlots(gui, handler.getIngredientStacks(recipeIndex)) != null;
}
@Override
public int transferRecipe(GuiContainer gui, IRecipeHandler handler, int recipeIndex, int multiplier) {
final List<PositionedStack> ingredients = handler.getIngredientStacks(recipeIndex);
final List<DistributedIngred> ingredStacks = getPermutationIngredients(ingredients);
final Slot[][] recipeSlots = resolveSlots(gui, ingredients);
if (recipeSlots == null || !clearMappedSlots(gui, recipeSlots)) return 0;
findInventoryQuantities(gui, ingredStacks);
final List<IngredientDistribution> assignedIngredients = assignIngredients(ingredients, ingredStacks);
if (assignedIngredients == null) return 0;
assignIngredSlots(gui, ingredients, assignedIngredients);
multiplier = Math.min(multiplier == 0 ? 64 : multiplier, calculateRecipeQuantity(assignedIngredients));
moveIngredients(gui, assignedIngredients, Math.max(1, multiplier));
return assignedIngredients.stream().anyMatch(distrib -> distrib.distrib.distributed == 0) ? 0 : multiplier;
}
@Override
public boolean canFillCraftingGrid(GuiContainer firstGui, IRecipeHandler handler, int recipeIndex) {
return canHandle(firstGui, handler, recipeIndex);
}
@Override
protected Set<Slot> getCraftMatrixSlots(GuiContainer gui, IRecipeHandler handler) {
return Collections.emptySet();
}
@Override
public Slot[][] mapIngredSlots(GuiContainer gui, List<PositionedStack> ingredients) {
Slot[][] exactSlots = super.mapIngredSlots(gui, ingredients);
if (!hasEmptyMappings(exactSlots)) {
return exactSlots;
}
Slot[][] smartSlots = resolveSlots(gui, ingredients);
return smartSlots == null ? exactSlots : smartSlots;
}
private boolean clearMappedSlots(GuiContainer gui, Slot[][] recipeSlots) {
Set<Slot> cleared = new HashSet<>();
for (Slot[] slots : recipeSlots) {
for (Slot slot : slots) {
if (cleared.add(slot) && slot.getHasStack() && slot.canTakeStack(gui.mc.thePlayer)) {
FastTransferManager.clickSlot(gui, slot.slotNumber, 0, 1);
if (slot.getHasStack()) return false;
}
}
}
FastTransferManager.dropHeldItem(gui);
return gui.mc.thePlayer.inventory.getItemStack() == null;
}
private Slot[][] resolveSlots(GuiContainer gui, List<PositionedStack> ingredients) {
if (ingredients == null || ingredients.isEmpty() || ingredients.size() > MAX_RECIPE_SLOTS) {
return null;
}
List<Slot> candidates = new ArrayList<>();
for (Slot slot : gui.inventorySlots.inventorySlots) {
if (isDestinationCandidate(slot)) {
candidates.add(slot);
}
}
if (candidates.size() < ingredients.size()) {
return null;
}
Assignment best = new Assignment();
Assignment secondBest = new Assignment();
searchAssignments(
ingredients,
candidates,
new boolean[candidates.size()],
new Slot[ingredients.size()],
0,
0,
best,
secondBest);
if (best.slots == null || secondBest.cost - best.cost <= MAX_COST_DELTA) {
return null;
}
Slot[][] resolved = new Slot[ingredients.size()][];
for (int i = 0; i < ingredients.size(); i++) {
resolved[i] = new Slot[] { best.slots[i] };
}
return resolved;
}
private void searchAssignments(List<PositionedStack> ingredients, List<Slot> candidates, boolean[] used,
Slot[] assigned, int ingredientIndex, int cost, Assignment best, Assignment secondBest) {
if (cost >= secondBest.cost) {
return;
}
if (ingredientIndex == ingredients.size()) {
if (cost < best.cost) {
secondBest.copyFrom(best);
best.set(cost, assigned);
} else if (cost < secondBest.cost) {
secondBest.set(cost, assigned);
}
return;
}
PositionedStack ingredient = ingredients.get(ingredientIndex);
for (int i = 0; i < candidates.size(); i++) {
if (used[i]) continue;
Slot slot = candidates.get(i);
int slotCost = getSlotCost(ingredient, slot);
if (slotCost < 0) continue;
used[i] = true;
assigned[ingredientIndex] = slot;
searchAssignments(
ingredients,
candidates,
used,
assigned,
ingredientIndex + 1,
cost + slotCost,
best,
secondBest);
assigned[ingredientIndex] = null;
used[i] = false;
}
}
private int getSlotCost(PositionedStack ingredient, Slot slot) {
boolean acceptsAnyPermutation = false;
for (ItemStack permutation : ingredient.items) {
if (canUseSlot(slot, permutation)) {
acceptsAnyPermutation = true;
break;
}
}
if (!acceptsAnyPermutation) {
return -1;
}
int dx = slot.xDisplayPosition - (ingredient.relx + offsetx);
int dy = slot.yDisplayPosition - (ingredient.rely + offsety);
int cost = dx * dx + dy * dy;
if (slot.getHasStack()) {
cost += 10_000;
}
return cost;
}
private boolean canUseSlot(Slot slot, ItemStack stack) {
if (stack == null || stack.stackSize <= 0 || !slot.isItemValid(stack)) {
return false;
}
if (!slot.inventory.isItemValidForSlot(slot.getSlotIndex(), stack)) {
return false;
}
return !slot.getHasStack() || canStack(slot.getStack(), stack);
}
private boolean isDestinationCandidate(Slot slot) {
return slot != null && !(slot.inventory instanceof InventoryPlayer) && !(slot instanceof SlotCrafting);
}
private boolean hasEmptyMappings(Slot[][] recipeSlots) {
for (Slot[] recipeSlot : recipeSlots) {
if (recipeSlot.length == 0) {
return true;
}
}
return false;
}
private static class Assignment {
int cost = Integer.MAX_VALUE;
Slot[] slots;
void set(int cost, Slot[] slots) {
this.cost = cost;
this.slots = slots.clone();
}
void copyFrom(Assignment other) {
this.cost = other.cost;
this.slots = other.slots == null ? null : other.slots.clone();
}
}
}