Skip to content
This repository has been archived by the owner on Sep 17, 2022. It is now read-only.

Distinct Input Buses #451

Merged
merged 20 commits into from
Apr 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
import gregicadditions.capabilities.impl.GARecipeMapMultiblockController;
import gregicadditions.item.components.*;
import gregicadditions.utils.GALog;
import gregicadditions.utils.Tuple;
import gregtech.api.capability.IMultipleTankHandler;
import gregtech.api.gui.Widget;
import gregtech.api.metatileentity.MetaTileEntity;
import gregtech.api.metatileentity.multiblock.MultiblockAbility;
import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
import gregtech.api.multiblock.BlockWorldState;
Expand All @@ -20,6 +21,7 @@
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.resources.I18n;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentTranslation;
Expand All @@ -28,6 +30,7 @@
import net.minecraftforge.fluids.IFluidTank;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.IItemHandlerModifiable;

import javax.annotation.Nullable;
Expand All @@ -36,6 +39,9 @@
import java.util.function.Predicate;
import java.util.stream.IntStream;

import static gregtech.api.gui.widgets.AdvancedTextWidget.withButton;
import static gregtech.api.gui.widgets.AdvancedTextWidget.withHoverTextTranslate;

abstract public class LargeSimpleRecipeMapMultiblockController extends GARecipeMapMultiblockController {

private int EUtPercentage = 100;
Expand All @@ -44,6 +50,15 @@ abstract public class LargeSimpleRecipeMapMultiblockController extends GARecipeM
private int stack = 1;
public long maxVoltage = 0;

/**
* When false, this multiblock will behave like any other.
* When true, this multiblock will treat each of its input buses as distinct,
* checking recipes for them independently. This is useful for many machines, for example the
* Large Extruder, where the player may want to put one extruder shape per bus, rather than
* one machine per extruder shape.
*/
protected boolean isDistinct = false;

DecimalFormat formatter = new DecimalFormat("#0.0");

/**
Expand Down Expand Up @@ -216,16 +231,48 @@ public boolean checkRecipe(Recipe recipe, boolean consumeIfSuccess) {
protected void addDisplayText(List<ITextComponent> textList) {
super.addDisplayText(textList);
textList.add(new TextComponentTranslation("gregtech.multiblock.universal.framework", this.maxVoltage));

ITextComponent buttonText = new TextComponentTranslation("gtadditions.multiblock.universal.distinct");
buttonText.appendText(" ");
ITextComponent button = withButton((isDistinct ?
new TextComponentTranslation("gtadditions.multiblock.universal.distinct.yes") :
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont understand why I used universal instead of common -_-

new TextComponentTranslation("gtadditions.multiblock.universal.distinct.no")), "distinct");
withHoverTextTranslate(button, "gtadditions.multiblock.universal.distinct.info");
buttonText.appendSibling(button);
textList.add(buttonText);
}

@Override
protected void handleDisplayClick(String componentData, Widget.ClickData clickData) {
super.handleDisplayClick(componentData, clickData);
isDistinct = !isDistinct;
}

@Override
public NBTTagCompound writeToNBT(NBTTagCompound data) {
super.writeToNBT(data);
data.setBoolean("Distinct", isDistinct);
return data;
}

@Override
public void readFromNBT(NBTTagCompound data) {
super.readFromNBT(data);
isDistinct = data.getBoolean("Distinct");
}

public static class LargeSimpleMultiblockRecipeLogic extends GAMultiblockRecipeLogic {

private int EUtPercentage = 100;
private int durationPercentage = 100;
private int chancePercentage = 100;
private int stack = 1;
private final int EUtPercentage;
private final int durationPercentage;
private final int chancePercentage;
private final int stack;
public RecipeMap<?> recipeMap;

// Fields used for distinct mode
protected int lastRecipeIndex = 0;
protected ItemStack[][] lastItemInputsMatrix;


public LargeSimpleMultiblockRecipeLogic(RecipeMapMultiblockController tileEntity, int EUtPercentage, int durationPercentage, int chancePercentage, int stack) {
super(tileEntity);
Expand All @@ -252,12 +299,21 @@ public int getStack() {
return stack;
}

protected List<IItemHandlerModifiable> getInputBuses() {
RecipeMapMultiblockController controller = (RecipeMapMultiblockController) metaTileEntity;
return controller.getAbilities(MultiblockAbility.IMPORT_ITEMS);
}

@Override
/**
* From multi-smelter.
*
*/
protected void trySearchNewRecipe() {
if (metaTileEntity instanceof LargeSimpleRecipeMapMultiblockController && ((LargeSimpleRecipeMapMultiblockController) metaTileEntity).isDistinct) {
trySearchNewRecipeDistinct();
} else trySearchNewRecipeCombined();
}

// Combined buses code =========================================================================================

private void trySearchNewRecipeCombined() {
long maxVoltage = getMaxVoltage();
if (metaTileEntity instanceof LargeSimpleRecipeMapMultiblockController)
maxVoltage = ((LargeSimpleRecipeMapMultiblockController) metaTileEntity).maxVoltage;
Expand Down Expand Up @@ -285,24 +341,119 @@ protected void trySearchNewRecipe() {
}
}

@Override
protected Recipe findRecipe(long maxVoltage, IItemHandlerModifiable inputs, IMultipleTankHandler fluidInputs) {
List<IItemHandlerModifiable> itemInputs = ((RecipeMapMultiblockController) this.getMetaTileEntity()).getAbilities(MultiblockAbility.IMPORT_ITEMS);
// Distinct buses code =========================================================================================

private void trySearchNewRecipeDistinct() {
long maxVoltage = getMaxVoltage();
Recipe currentRecipe = null;
List<IItemHandlerModifiable> importInventory = getInputBuses();
IMultipleTankHandler importFluids = getInputTank();

Tuple<Recipe, IItemHandlerModifiable> recipePerInput = itemInputs.stream()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you rewrite lots of code, but the only thing to change is that single line

Copy link
Member Author

@serenibyss serenibyss Apr 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its possible that it would be "functional" with a single change there, but there would be many places where the code is largely inefficient. I rewrote trySearchNewRecipe and checkRecipeInputsDirty because when the buses are treated distinctly, the caching for these methods will fail every single time. But with this change, it properly caches and will prioritize continuing with the same bus rather than swapping to other buses randomly.

Though in my testing, the separate buses feature from before was not functional, and would often use molds/shapes from other buses rather than the one in the current bus. But if I am wrong on this, and the single change in findRecipe is all that was needed to make this functional, then my code is still more efficient, as it will be able to stop in trySearchNewRecipe instead of improperly finding a recipe, and then scanning all buses again to know it has failed in findRecipe.

.map(iItemHandlerModifiable -> new Tuple<>(recipeMap.findRecipe(maxVoltage, iItemHandlerModifiable, fluidInputs, 0), iItemHandlerModifiable))
.filter(tuple -> tuple.getKey() != null)
.findFirst().orElse(new Tuple<>(recipeMap.findRecipe(maxVoltage, inputs, fluidInputs, 0), inputs));
// Our caching implementation
// This guarantees that if we get a recipe cache hit, our efficiency is no different from other machines
if (previousRecipe != null && previousRecipe.matches(false, importInventory.get(lastRecipeIndex), importFluids)) {
currentRecipe = previousRecipe;
if (setupAndConsumeRecipeInputs(currentRecipe, lastRecipeIndex)) {
setupRecipe(currentRecipe);
return;
}
}

if (recipePerInput.getKey() == null) {
return null;
// On a cache miss, our efficiency is much worse, as it will check
// each bus individually instead of the combined inventory all at once.
for (int i = 0; i < importInventory.size(); i++) {
IItemHandlerModifiable bus = importInventory.get(i);
boolean dirty = checkRecipeInputsDirty(bus, importFluids, i);
if (dirty || forceRecipeRecheck) {
this.forceRecipeRecheck = false;
currentRecipe = findRecipe(maxVoltage, bus, importFluids);
if (currentRecipe != null) {
this.previousRecipe = currentRecipe;
}
}
if (currentRecipe != null && setupAndConsumeRecipeInputs(currentRecipe, i)) {
lastRecipeIndex = i;
setupRecipe(currentRecipe);
break;
}
}
}

return createRecipe(maxVoltage, recipePerInput.getValue(), fluidInputs, recipePerInput.getKey());
// Replacing this for optimization reasons
protected boolean checkRecipeInputsDirty(IItemHandler inputs, IMultipleTankHandler fluidInputs, int index) {
boolean shouldRecheckRecipe = false;

if (lastItemInputsMatrix == null || lastItemInputsMatrix.length != getInputBuses().size()) {
lastItemInputsMatrix = new ItemStack[getInputBuses().size()][];
GALog.logger.info("Num buses: " + getInputBuses().size());
}
if (lastItemInputsMatrix[index] == null || lastItemInputsMatrix[index].length != inputs.getSlots()) {
this.lastItemInputsMatrix[index] = new ItemStack[inputs.getSlots()];
Arrays.fill(lastItemInputsMatrix[index], ItemStack.EMPTY);
}
if (lastFluidInputs == null || lastFluidInputs.length != fluidInputs.getTanks()) {
this.lastFluidInputs = new FluidStack[fluidInputs.getTanks()];
}
for (int i = 0; i < lastItemInputsMatrix[index].length; i++) {
ItemStack currentStack = inputs.getStackInSlot(i);
ItemStack lastStack = lastItemInputsMatrix[index][i];
if (!areItemStacksEqual(currentStack, lastStack)) {
this.lastItemInputsMatrix[index][i] = currentStack.isEmpty() ? ItemStack.EMPTY : currentStack.copy();
shouldRecheckRecipe = true;
} else if (currentStack.getCount() != lastStack.getCount()) {
lastStack.setCount(currentStack.getCount());
shouldRecheckRecipe = true;
}
}
for (int i = 0; i < lastFluidInputs.length; i++) {
FluidStack currentStack = fluidInputs.getTankAt(i).getFluid();
FluidStack lastStack = lastFluidInputs[i];
if ((currentStack == null && lastStack != null) ||
(currentStack != null && !currentStack.isFluidEqual(lastStack))) {
this.lastFluidInputs[i] = currentStack == null ? null : currentStack.copy();
shouldRecheckRecipe = true;
} else if (currentStack != null && lastStack != null &&
currentStack.amount != lastStack.amount) {
lastStack.amount = currentStack.amount;
shouldRecheckRecipe = true;
}
}
return shouldRecheckRecipe;
}

protected boolean setupAndConsumeRecipeInputs(Recipe recipe, int index) {
RecipeMapMultiblockController controller = (RecipeMapMultiblockController) metaTileEntity;
if (controller.checkRecipe(recipe, false)) {

int[] resultOverclock = calculateOverclock(recipe.getEUt(), recipe.getDuration());
int totalEUt = resultOverclock[0] * resultOverclock[1];
IItemHandlerModifiable importInventory = getInputBuses().get(index);
IItemHandlerModifiable exportInventory = getOutputInventory();
IMultipleTankHandler importFluids = getInputTank();
IMultipleTankHandler exportFluids = getOutputTank();
boolean setup = (totalEUt >= 0 ? getEnergyStored() >= (totalEUt > getEnergyCapacity() / 2 ? resultOverclock[0] : totalEUt) :
(getEnergyStored() - resultOverclock[0] <= getEnergyCapacity())) &&
MetaTileEntity.addItemsToItemHandler(exportInventory, true, recipe.getAllItemOutputs(exportInventory.getSlots())) &&
MetaTileEntity.addFluidsToFluidHandler(exportFluids, true, recipe.getFluidOutputs()) &&
recipe.matches(true, importInventory, importFluids);

if (setup) {
controller.checkRecipe(recipe, true);
return true;
}
}
return false;
}

// Shared recipe generation code ===============================================================================

@Override
protected Recipe findRecipe(long maxVoltage, IItemHandlerModifiable inputs, IMultipleTankHandler fluidInputs) {
Recipe recipe = super.findRecipe(maxVoltage, inputs, fluidInputs);
if (recipe != null)
return createRecipe(maxVoltage, inputs, fluidInputs, recipe);
return null;
}

protected Recipe createRecipe(long maxVoltage, IItemHandlerModifiable inputs, IMultipleTankHandler fluidInputs, Recipe matchingRecipe) {
int maxItemsLimit = this.stack;
Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/assets/gtadditions/lang/en_us.lang
Original file line number Diff line number Diff line change
Expand Up @@ -2692,6 +2692,10 @@ gtadditions.multiblock.universal.tooltip.2=EUt Multiplier: §e%.1f§r
gtadditions.multiblock.universal.tooltip.3=Duration Multiplier: §e%.1f§r
gtadditions.multiblock.universal.tooltip.4=Max Parallel: §e%d§7 Per Overclocking Tier
gtadditions.multiblock.universal.tooltip.5=Boost Chance: §e%d%%§r
gtadditions.multiblock.universal.distinct=Distinct Buses:
gtadditions.multiblock.universal.distinct.yes=§aYes
gtadditions.multiblock.universal.distinct.no=§cNo
gtadditions.multiblock.universal.distinct.info=If enabled, each bus will be treated as fully distinct from eachother for recipe lookup. Useful for example for Extruder Shapes, Laser Lenses, etc..
gtadditions.multiblock.fusion_reactor.heat=Heat: %d
gtadditions.multiblock.fusion_reactor.tooltip.1=EU To Start: %s
gtadditions.multiblock.central_monitor.height=Screen Height: %d
Expand Down