Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,12 @@ protected IMultipleTankHandler getOutputTank() {
return metaTileEntity.getExportFluids();
}

public Recipe getParallelRecipe() {
if (previousRecipe == null) return null;
return findParallelRecipe(previousRecipe, getInputInventory(), getInputTank(),
getOutputInventory(), getOutputTank(), getMaxParallelVoltage(), getParallelLimit());
}

/**
* @return true if energy is consumed by this Recipe Logic, otherwise false
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,8 @@ protected void configureDisplayText(MultiblockUIBuilder builder) {
.addEnergyTierLine(GTUtility.getTierByVoltage(recipeMapWorkable.getMaxVoltage()))
.addParallelsLine(recipeMapWorkable.getParallelLimit())
.addWorkingStatusLine()
.addProgressLine(recipeMapWorkable.getProgressPercent());
.addProgressLine(recipeMapWorkable.getProgress(), recipeMapWorkable.getMaxProgress())
.addRecipeOutputLine(recipeMapWorkable);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ protected void configureDisplayText(MultiblockUIBuilder builder) {
.addEnergyTierLine(GTUtility.getTierByVoltage(recipeMapWorkable.getMaxVoltage()))
.addParallelsLine(recipeMapWorkable.getParallelLimit())
.addWorkingStatusLine()
.addProgressLine(recipeMapWorkable.getProgressPercent());
.addProgressLine(recipeMapWorkable.getProgress(), recipeMapWorkable.getMaxProgress())
.addRecipeOutputLine(recipeMapWorkable);
}

protected void configureWarningText(MultiblockUIBuilder builder) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ protected void configureDisplayText(MultiblockUIBuilder builder) {
})
.addParallelsLine(recipeMapWorkable.getParallelLimit())
.addWorkingStatusLine()
.addProgressLine(recipeMapWorkable.getProgressPercent());
.addProgressLine(recipeMapWorkable.getProgress(), recipeMapWorkable.getMaxProgress())
.addRecipeOutputLine(recipeMapWorkable);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,38 @@

import gregtech.api.GTValues;
import gregtech.api.capability.IEnergyContainer;
import gregtech.api.capability.impl.AbstractRecipeLogic;
import gregtech.api.mui.GTByteBufAdapters;
import gregtech.api.mui.drawable.GTObjectDrawable;
import gregtech.api.recipes.Recipe;
import gregtech.api.recipes.RecipeMap;
import gregtech.api.recipes.chance.boost.ChanceBoostFunction;
import gregtech.api.recipes.chance.output.impl.ChancedFluidOutput;
import gregtech.api.recipes.chance.output.impl.ChancedItemOutput;
import gregtech.api.util.FluidStackHashStrategy;
import gregtech.api.util.GTUtility;
import gregtech.api.util.ItemStackHashStrategy;
import gregtech.api.util.KeyUtil;
import gregtech.api.util.TextFormattingUtil;
import gregtech.common.ConfigHolder;

import net.minecraft.item.ItemStack;
import net.minecraft.network.PacketBuffer;
import net.minecraft.util.text.TextFormatting;
import net.minecraftforge.fluids.FluidStack;

import com.cleanroommc.modularui.api.drawable.IDrawable;
import com.cleanroommc.modularui.api.drawable.IKey;
import com.cleanroommc.modularui.api.drawable.IRichTextBuilder;
import com.cleanroommc.modularui.utils.serialization.ByteBufAdapters;
import com.cleanroommc.modularui.utils.serialization.IByteBufDeserializer;
import com.cleanroommc.modularui.utils.serialization.IByteBufSerializer;
import com.cleanroommc.modularui.value.sync.PanelSyncManager;
import com.cleanroommc.modularui.value.sync.SyncHandler;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.objects.Object2LongLinkedOpenCustomHashMap;
import it.unimi.dsi.fastutil.objects.Object2LongMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand Down Expand Up @@ -46,6 +61,8 @@ public class MultiblockUIBuilder {
private final InternalSyncHandler syncHandler = new InternalSyncHandler();
private final KeyManager manager = new InternalKeyManager();

private static final int DEFAULT_MAX_RECIPE_LINES = 25;

@Nullable
private InternalSyncer syncer;

Expand Down Expand Up @@ -313,17 +330,23 @@ public MultiblockUIBuilder addIdlingLine(boolean checkState) {
}

/**
* Adds a simple progress line that displays progress as a percentage.
* Adds a progress line that displays recipe progress as "time / total time (percentage)".
* <br>
* Added if structure is formed and the machine is active.
*
* @param progressPercent Progress formatted as a range of [0,1] representing the progress of the recipe.
* @param progress current progress.
* @param maxProgress total progress to be made.
*/
public MultiblockUIBuilder addProgressLine(double progressPercent) {
public MultiblockUIBuilder addProgressLine(int progress, int maxProgress) {
if (!isStructureFormed || !isActive) return this;
addKey(KeyUtil.lang(TextFormatting.GRAY,
"gregtech.multiblock.progress",
(int) (getSyncer().syncDouble(progressPercent) * 100)));

progress = getSyncer().syncInt(progress);
maxProgress = getSyncer().syncInt(maxProgress);

addKey(KeyUtil.lang(TextFormatting.WHITE, "gregtech.multiblock.recipe_progress",
String.format("%,3.2f", (float) progress / 20),
String.format("%,3.2f", (float) maxProgress / 20),
String.format("%,3.1f", (float) progress / maxProgress * 100f)));
return this;
}

Expand Down Expand Up @@ -484,6 +507,201 @@ public MultiblockUIBuilder addRecipeMapLine(RecipeMap<?> map) {
return this;
}

/**
* Adds the current outputs of a recipe from recipe logic. Items then fluids.
*
* @param arl an instance of an {@link AbstractRecipeLogic} to gather the outputs from.
*/
public MultiblockUIBuilder addRecipeOutputLine(@NotNull AbstractRecipeLogic arl) {
return addRecipeOutputLine(arl, DEFAULT_MAX_RECIPE_LINES);
}

/**
* Adds the current outputs of a recipe from recipe logic. Items then fluids.
*
* @param arl an instance of an {@link AbstractRecipeLogic} to gather the outputs from.
* @param maxLines the maximum number of lines to print until truncating with {@code ...}
*/
public MultiblockUIBuilder addRecipeOutputLine(AbstractRecipeLogic arl, int maxLines) {
// todo doing this every tick on the server is probably not good
Recipe recipe = arl.getParallelRecipe();

if (getSyncer().syncBoolean(recipe == null)) return this;
if (getSyncer().syncBoolean(arl.getRecipeMap() == null)) return this;

// noinspection DataFlowIssue
long eut = getSyncer().syncLong(() -> recipe.getEUt());
long maxVoltage = getSyncer().syncLong(arl.getMaximumOverclockVoltage());

List<ItemStack> itemOutputs = new ArrayList<>();
List<ChancedItemOutput> chancedItemOutputs = new ArrayList<>();
List<FluidStack> fluidOutputs = new ArrayList<>();
List<ChancedFluidOutput> chancedFluidOutputs = new ArrayList<>();

if (isServer()) {
// recipe is checked indirectly for null
// noinspection DataFlowIssue
itemOutputs.addAll(recipe.getOutputs());
chancedItemOutputs.addAll(recipe.getChancedOutputs().getChancedEntries());
fluidOutputs.addAll(recipe.getFluidOutputs());
chancedFluidOutputs.addAll(recipe.getChancedFluidOutputs().getChancedEntries());
}

itemOutputs = getSyncer().syncCollection(itemOutputs, ByteBufAdapters.ITEM_STACK);
fluidOutputs = getSyncer().syncCollection(fluidOutputs, ByteBufAdapters.FLUID_STACK);
chancedItemOutputs = getSyncer().syncCollection(chancedItemOutputs, GTByteBufAdapters.CHANCED_ITEM_OUTPUT);
chancedFluidOutputs = getSyncer().syncCollection(chancedFluidOutputs, GTByteBufAdapters.CHANCED_FLUID_OUTPUT);

addKey(KeyUtil.string(TextFormatting.GRAY, "Producing: "), Operation.NEW_LINE);

var chanceFunction = arl.getRecipeMap().getChanceFunction();
int recipeTier = GTUtility.getTierByVoltage(eut);
int machineTier = GTUtility.getOCTierByVoltage(maxVoltage);

// items

Object2LongMap<ItemStack> itemMap = new Object2LongLinkedOpenCustomHashMap<>(
ItemStackHashStrategy.comparingAllButCount());

for (ItemStack stack : itemOutputs) {
itemMap.merge(stack, (long) stack.getCount(), Long::sum);
}

for (var stack : itemMap.keySet()) {
addItemOutputLine(stack, itemMap.getLong(stack), arl.getMaxProgress());
}

for (var chancedItemOutput : chancedItemOutputs) {
addChancedItemOutputLine(chancedItemOutput, chanceFunction, recipeTier, machineTier, arl.getMaxProgress());
}

// fluids

Object2LongMap<FluidStack> fluidMap = new Object2LongLinkedOpenCustomHashMap<>(
FluidStackHashStrategy.comparingAllButAmount);

for (FluidStack stack : fluidOutputs) {
fluidMap.merge(stack, (long) stack.amount, Long::sum);
}

for (var stack : fluidMap.keySet()) {
addFluidOutputLine(stack, fluidMap.getLong(stack), arl.getMaxProgress());
}

for (var chancedFluidOutput : chancedFluidOutputs) {
addChancedFluidOutputLine(chancedFluidOutput, chanceFunction, recipeTier, machineTier,
arl.getMaxProgress());
}
return this;
}

/**
* Add an item output of a recipe to the display.
*
* @param stack the {@link ItemStack} to display.
* @param recipeLength the recipe length, in ticks.
*/
private void addItemOutputLine(@NotNull ItemStack stack, long count, int recipeLength) {
IKey name = KeyUtil.string(TextFormatting.AQUA, stack.getDisplayName());
IKey amount = KeyUtil.number(TextFormatting.GOLD, count);
IKey rate = KeyUtil.string(TextFormatting.WHITE,
formatRecipeRate(getSyncer().syncInt(recipeLength), count));

addKey(new GTObjectDrawable(stack, count)
.asIcon()
.asHoverable()
.addTooltipLine(formatRecipeData(name, amount, rate)), Operation.ADD);
// addKey(IKey.SPACE, Operation.ADD);
}

/**
* Add the fluid outputs of a recipe to the display.
*
* @param stack a {@link FluidStack}s to display.
* @param recipeLength the recipe length, in ticks.
*/
private void addFluidOutputLine(@NotNull FluidStack stack, long count, int recipeLength) {
IKey name = KeyUtil.fluid(TextFormatting.AQUA, stack);
IKey amount = KeyUtil.number(TextFormatting.GOLD, count);
IKey rate = KeyUtil.string(TextFormatting.WHITE,
formatRecipeRate(getSyncer().syncInt(recipeLength), count));

addKey(new GTObjectDrawable(stack, count)
.asIcon()
.asHoverable()
.addTooltipLine(formatRecipeData(name, amount, rate)), Operation.ADD);
// addKey(IKey.SPACE, Operation.ADD);
}

/**
* Add a chanced item output of a recipe to the display.
*
* @param chancedItemOutput chanced item output
* @param boostFunction function to calculate boosted chance
* @param recipeTier voltage tier of the recipe
* @param machineTier machine tier
* @param recipeLength max duration of the recipe
*/
private void addChancedItemOutputLine(@NotNull ChancedItemOutput chancedItemOutput,
ChanceBoostFunction boostFunction,
int recipeTier, int machineTier, int recipeLength) {
var stack = chancedItemOutput.getIngredient();
IKey name = KeyUtil.string(TextFormatting.AQUA, stack.getDisplayName());
IKey amount = KeyUtil.number(TextFormatting.GOLD, stack.getCount());
IKey rate = KeyUtil.string(TextFormatting.WHITE,
formatRecipeRate(getSyncer().syncInt(recipeLength), stack.getCount()));

addKey(new GTObjectDrawable(chancedItemOutput, stack.getCount())
.setBoostFunction(entry -> boostFunction.getBoostedChance(entry, recipeTier, machineTier))
.asIcon()
.asHoverable()
.addTooltipLine(formatRecipeData(name, amount, rate)), Operation.ADD);
// addKey(IKey.SPACE, Operation.ADD);
}

/**
* Add a chanced fluid output of a recipe to the display.
*
* @param chancedFluidOutput chanced fluid output
* @param boostFunction function to calculate boosted chance
* @param recipeTier voltage tier of the recipe
* @param machineTier machine tier
* @param recipeLength max duration of the recipe
*/
private void addChancedFluidOutputLine(@NotNull ChancedFluidOutput chancedFluidOutput,
ChanceBoostFunction boostFunction,
int recipeTier, int machineTier, int recipeLength) {
var stack = chancedFluidOutput.getIngredient();
IKey name = KeyUtil.fluid(TextFormatting.AQUA, stack);
IKey amount = KeyUtil.number(TextFormatting.GOLD, stack.amount);
IKey rate = KeyUtil.string(TextFormatting.WHITE,
formatRecipeRate(getSyncer().syncInt(recipeLength), stack.amount));

addKey(new GTObjectDrawable(chancedFluidOutput, stack.amount)
.setBoostFunction(entry -> boostFunction.getBoostedChance(entry, recipeTier, machineTier))
.asIcon()
.asHoverable()
.addTooltipLine(formatRecipeData(name, amount, rate)), Operation.ADD);
// addKey(IKey.SPACE, Operation.ADD);
}

private static String formatRecipeRate(int recipeLength, long amount) {
float perSecond = ((float) amount / recipeLength) * 20f;

String rate;
if (perSecond > 1) {
rate = "(" + String.format("%,.2f", perSecond).replaceAll("\\.?0+$", "") + "/s)";
} else {
rate = "(" + String.format("%,.2f", 1 / (perSecond)).replaceAll("\\.?0+$", "") + "s/ea)";
}

return rate;
}

private static IKey formatRecipeData(IKey name, IKey amount, IKey rate) {
return IKey.comp(name, KeyUtil.string(TextFormatting.WHITE, " x "), amount, IKey.SPACE, rate);
}

/** Insert an empty line into the text list. */
public MultiblockUIBuilder addEmptyLine() {
addKey(IKey.LINE_FEED);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package gregtech.api.metatileentity.multiblock.ui;

import gregtech.api.mui.GTByteBufAdapters;

import com.cleanroommc.modularui.utils.serialization.ByteBufAdapters;
import com.cleanroommc.modularui.utils.serialization.IByteBufAdapter;
import com.cleanroommc.modularui.utils.serialization.IByteBufDeserializer;
import com.cleanroommc.modularui.utils.serialization.IByteBufSerializer;
import com.cleanroommc.modularui.utils.serialization.IEquals;
import org.jetbrains.annotations.NotNull;

import java.math.BigInteger;
Expand All @@ -16,11 +17,6 @@

public interface UISyncer {

IByteBufAdapter<BigInteger> BIG_INT = ByteBufAdapters.makeAdapter(
buffer -> new BigInteger(buffer.readByteArray()),
(buffer, value) -> buffer.writeByteArray(value.toByteArray()),
IEquals.defaultTester());

/**
* Calls the supplier server side only so there's no potential NPEs for the client
*
Expand Down Expand Up @@ -98,7 +94,7 @@ default float syncFloat(float initial) {
}

default BigInteger syncBigInt(BigInteger initial) {
return syncObject(initial, BIG_INT);
return syncObject(initial, GTByteBufAdapters.BIG_INT);
}

<T> T syncObject(T initial, IByteBufSerializer<T> serializer, IByteBufDeserializer<T> deserializer);
Expand Down
Loading