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

Commit

Permalink
Distinct Input Buses (#451)
Browse files Browse the repository at this point in the history
* Create checks on master commits

* Add badges to README

update required licensing information

* remove gradle.properties from checks

* Revert "remove gradle.properties from checks"

This reverts commit 2e131db.

* Add command handler

Remove BuildCraftAPI as its unneeded

* Update fs permission

* add execution permission

* Add executable bit to gradlew

* remove old execution enabling

* Add logic for distinct bus multiblocks

First attempt

* Fix casting error

* Add itemstack caching override

Offers much better efficiency than using stock one from AbstractRecipeLogic

* First working test done

* Cleanup, combine into Large Simple Multi

* Save distinct to NBT to prevent clearing

* Fix crash with some GTCE multiblocks

* Small fix
  • Loading branch information
serenibyss authored Apr 21, 2021
1 parent 6fb4447 commit fc12279
Show file tree
Hide file tree
Showing 2 changed files with 174 additions and 19 deletions.
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") :
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()
.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

0 comments on commit fc12279

Please sign in to comment.