aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/gregtech/nei/GTNEIDefaultHandler.java
diff options
context:
space:
mode:
authorNotAPenguin <michiel.vandeginste@gmail.com>2024-09-02 23:17:17 +0200
committerGitHub <noreply@github.com>2024-09-02 23:17:17 +0200
commit1b820de08a05070909a267e17f033fcf58ac8710 (patch)
tree02831a025986a06b20f87e5bcc69d1e0c639a342 /src/main/java/gregtech/nei/GTNEIDefaultHandler.java
parentafd3fd92b6a6ab9ab0d0dc3214e6bc8ff7a86c9b (diff)
downloadGT5-Unofficial-1b820de08a05070909a267e17f033fcf58ac8710.tar.gz
GT5-Unofficial-1b820de08a05070909a267e17f033fcf58ac8710.tar.bz2
GT5-Unofficial-1b820de08a05070909a267e17f033fcf58ac8710.zip
The Great Renaming (#3014)
* move kekztech to a single root dir * move detrav to a single root dir * move gtnh-lanthanides to a single root dir * move tectech and delete some gross reflection in gt++ * remove more reflection inside gt5u * delete more reflection in gt++ * fix imports * move bartworks and bwcrossmod * fix proxies * move galactigreg and ggfab * move gtneioreplugin * try to fix gt++ bee loader * apply the rename rules to BW * apply rename rules to bwcrossmod * apply rename rules to detrav scanner mod * apply rename rules to galacticgreg * apply rename rules to ggfab * apply rename rules to goodgenerator * apply rename rules to gtnh-lanthanides * apply rename rules to gt++ * apply rename rules to kekztech * apply rename rules to kubatech * apply rename rules to tectech * apply rename rules to gt apply the rename rules to gt * fix tt import * fix mui hopefully * fix coremod except intergalactic * rename assline recipe class * fix a class name i stumbled on * rename StructureUtility to GTStructureUtility to prevent conflict with structurelib * temporary rename of GTTooltipDataCache to old name * fix gt client/server proxy names
Diffstat (limited to 'src/main/java/gregtech/nei/GTNEIDefaultHandler.java')
-rw-r--r--src/main/java/gregtech/nei/GTNEIDefaultHandler.java801
1 files changed, 801 insertions, 0 deletions
diff --git a/src/main/java/gregtech/nei/GTNEIDefaultHandler.java b/src/main/java/gregtech/nei/GTNEIDefaultHandler.java
new file mode 100644
index 0000000000..3887ad321b
--- /dev/null
+++ b/src/main/java/gregtech/nei/GTNEIDefaultHandler.java
@@ -0,0 +1,801 @@
+package gregtech.nei;
+
+import static gregtech.api.enums.GTValues.V;
+
+import java.awt.Rectangle;
+import java.lang.ref.SoftReference;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.FontRenderer;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.StatCollector;
+import net.minecraftforge.fluids.FluidStack;
+
+import org.apache.commons.lang3.Range;
+import org.lwjgl.opengl.GL11;
+
+import com.gtnewhorizons.modularui.api.GlStateManager;
+import com.gtnewhorizons.modularui.api.UIInfos;
+import com.gtnewhorizons.modularui.api.drawable.IDrawable;
+import com.gtnewhorizons.modularui.api.forge.ItemStackHandler;
+import com.gtnewhorizons.modularui.api.math.Pos2d;
+import com.gtnewhorizons.modularui.api.screen.ModularWindow;
+import com.gtnewhorizons.modularui.api.widget.Widget;
+import com.gtnewhorizons.modularui.common.widget.SlotWidget;
+
+import codechicken.nei.NEIClientUtils;
+import codechicken.nei.PositionedStack;
+import codechicken.nei.recipe.GuiRecipe;
+import codechicken.nei.recipe.ICraftingHandler;
+import codechicken.nei.recipe.IUsageHandler;
+import codechicken.nei.recipe.RecipeCatalysts;
+import codechicken.nei.recipe.TemplateRecipeHandler;
+import gregtech.GTMod;
+import gregtech.api.enums.ItemList;
+import gregtech.api.enums.OrePrefixes;
+import gregtech.api.enums.SteamVariant;
+import gregtech.api.gui.GUIColorOverride;
+import gregtech.api.gui.modularui.GTUITextures;
+import gregtech.api.interfaces.metatileentity.IMetaTileEntity;
+import gregtech.api.interfaces.tileentity.IOverclockDescriptionProvider;
+import gregtech.api.objects.ItemData;
+import gregtech.api.objects.overclockdescriber.EUNoOverclockDescriber;
+import gregtech.api.objects.overclockdescriber.OverclockDescriber;
+import gregtech.api.recipe.BasicUIProperties;
+import gregtech.api.recipe.NEIRecipeProperties;
+import gregtech.api.recipe.RecipeCategory;
+import gregtech.api.recipe.RecipeCategorySetting;
+import gregtech.api.recipe.RecipeMap;
+import gregtech.api.recipe.RecipeMapFrontend;
+import gregtech.api.util.GTOreDictUnificator;
+import gregtech.api.util.GTRecipe;
+import gregtech.api.util.GTUtility;
+import gregtech.api.util.OverclockCalculator;
+import gregtech.common.blocks.ItemMachines;
+import gregtech.common.gui.modularui.UIHelper;
+
+public class GTNEIDefaultHandler extends TemplateRecipeHandler {
+
+ private static final int offsetX = 5;
+ private static final int offsetY = 11;
+ protected static final Pos2d WINDOW_OFFSET = new Pos2d(-offsetX, -offsetY);
+
+ private static final ConcurrentMap<RecipeCategory, SortedRecipeListCache> CACHE = new ConcurrentHashMap<>();
+
+ private static final int RECIPE_NAME_WIDTH = 140;
+
+ /**
+ * Static version of {@link TemplateRecipeHandler#cycleticks}. Can be referenced from cached recipes.
+ */
+ private static int cycleTicksStatic = Math.abs((int) System.currentTimeMillis());
+ /**
+ * Basically {@link #cycleTicksStatic} but always updated even while holding shift
+ */
+ private static int drawTicks;
+ private static final int PROGRESSBAR_CYCLE_TICKS = 200;
+
+ protected final RecipeCategory recipeCategory;
+ protected final RecipeMap<?> recipeMap;
+ protected final RecipeMapFrontend frontend;
+ protected final BasicUIProperties uiProperties;
+ protected final NEIRecipeProperties neiProperties;
+
+ protected final ModularWindow modularWindow;
+ protected final ItemStackHandler itemInputsInventory;
+ protected final ItemStackHandler itemOutputsInventory;
+ protected final ItemStackHandler specialSlotInventory;
+ protected final ItemStackHandler fluidInputsInventory;
+ protected final ItemStackHandler fluidOutputsInventory;
+
+ protected OverclockDescriber overclockDescriber;
+ /**
+ * Localized name of this handler displayed on the top.
+ */
+ private String recipeNameDisplay;
+ /**
+ * Tooltip shown while hovering over header of this handler. Can be null if the full name fits in the screen.
+ */
+ private NEIHandlerAbsoluteTooltip recipeNameTooltip;
+
+ protected final GUIColorOverride colorOverride = GUIColorOverride
+ .get(GTUITextures.BACKGROUND_NEI_SINGLE_RECIPE.location);
+ private int neiTextColorOverride = -1;
+
+ public GTNEIDefaultHandler(RecipeCategory recipeCategory) {
+ this.recipeCategory = recipeCategory;
+ this.recipeMap = recipeCategory.recipeMap;
+ this.frontend = recipeMap.getFrontend();
+ this.uiProperties = frontend.getUIProperties();
+ this.neiProperties = frontend.getNEIProperties();
+ uiProperties.neiTransferRect.forEach(transferRect -> {
+ transferRect = new Rectangle(transferRect);
+ transferRect.translate(WINDOW_OFFSET.x, WINDOW_OFFSET.y);
+ this.transferRects.add(new RecipeTransferRect(transferRect, recipeMap.unlocalizedName));
+ });
+
+ ModularWindow.Builder builder = frontend.createNEITemplate(
+ itemInputsInventory = new ItemStackHandler(uiProperties.maxItemInputs),
+ itemOutputsInventory = new ItemStackHandler(uiProperties.maxItemOutputs),
+ specialSlotInventory = new ItemStackHandler(1),
+ fluidInputsInventory = new ItemStackHandler(uiProperties.maxFluidInputs),
+ fluidOutputsInventory = new ItemStackHandler(uiProperties.maxFluidOutputs),
+ () -> ((float) getDrawTicks() % PROGRESSBAR_CYCLE_TICKS) / PROGRESSBAR_CYCLE_TICKS,
+ WINDOW_OFFSET);
+ modularWindow = builder.build();
+ UIInfos.initializeWindow(Minecraft.getMinecraft().thePlayer, modularWindow);
+ }
+
+ public RecipeMap<?> getRecipeMap() {
+ return recipeMap;
+ }
+
+ private SortedRecipeListCache getCacheHolder() {
+ return CACHE.computeIfAbsent(recipeCategory, m -> new SortedRecipeListCache());
+ }
+
+ public List<CachedDefaultRecipe> getCache() {
+ SortedRecipeListCache cacheHolder = getCacheHolder();
+ List<CachedDefaultRecipe> cache;
+ if (cacheHolder.getCachedRecipesVersion() != GTMod.gregtechproxy.getNEIReloadCount()
+ || (cache = cacheHolder.getCachedRecipes()) == null) {
+ try {
+ RecipeCategory defaultCategory = recipeMap.getDefaultRecipeCategory();
+ Collection<GTRecipe> recipes;
+ if (this.recipeCategory == defaultCategory) {
+ // This is main category, so merge categories that are configured as such
+ Stream<GTRecipe> recipesToMerge = recipeMap.getBackend()
+ .getRecipeCategoryMap()
+ .entrySet()
+ .stream()
+ .flatMap(entry -> {
+ boolean merge = entry.getKey() != defaultCategory
+ && GTMod.gregtechproxy.recipeCategorySettings
+ .getOrDefault(entry.getKey(), RecipeCategorySetting.getDefault())
+ == RecipeCategorySetting.MERGE;
+ return merge ? entry.getValue()
+ .stream() : Stream.empty();
+ });
+ recipes = Stream.concat(
+ recipesToMerge,
+ recipeMap.getBackend()
+ .getRecipesByCategory(defaultCategory)
+ .stream())
+ .collect(Collectors.toList());
+ } else {
+ // This is "sub" category
+ if (GTMod.gregtechproxy.recipeCategorySettings
+ .getOrDefault(recipeCategory, RecipeCategorySetting.getDefault())
+ == RecipeCategorySetting.ENABLE) {
+ recipes = recipeMap.getBackend()
+ .getRecipesByCategory(recipeCategory);
+ } else {
+ recipes = Collections.emptyList();
+ }
+ }
+ cache = recipes.stream() // do not use parallel stream. This is already parallelized by NEI
+ .filter(r -> !r.mHidden)
+ .sorted(neiProperties.comparator)
+ .map(CachedDefaultRecipe::new)
+ .collect(Collectors.toList());
+ // while the NEI parallelize handlers, for each individual handler it still uses sequential execution
+ // model,
+ // so we do not need any synchronization here
+ // even if it does break, at worst case it's just recreating the cache multiple times, which should be
+ // fine
+ cacheHolder.setCachedRecipes(cache);
+ cacheHolder.setCachedRecipesVersion(GTMod.gregtechproxy.getNEIReloadCount());
+ } catch (Exception e) {
+ throw new RuntimeException(
+ "Could not construct GT NEI Handler cache for category " + recipeCategory
+ + ", display name "
+ + recipeNameDisplay,
+ e);
+ }
+ }
+ return cache;
+ }
+
+ @Override
+ public TemplateRecipeHandler newInstance() {
+ return new GTNEIDefaultHandler(recipeCategory);
+ }
+
+ @Override
+ public void loadCraftingRecipes(String outputId, Object... results) {
+ if (outputId.equals(recipeMap.unlocalizedName)) {
+ if (results.length > 0 && results[0] instanceof OverclockDescriber) {
+ overclockDescriber = (OverclockDescriber) results[0];
+ if (neiProperties.useCustomFilter) {
+ loadTieredRecipesWithCustomFilter(overclockDescriber);
+ } else {
+ loadTieredRecipesUpTo(overclockDescriber.getTier());
+ }
+ } else {
+ arecipes.addAll(getCache());
+ }
+ } else {
+ super.loadCraftingRecipes(outputId, results);
+ }
+ }
+
+ @Override
+ public void loadCraftingRecipes(ItemStack aResult) {
+ ItemData tPrefixMaterial = GTOreDictUnificator.getAssociation(aResult);
+
+ ArrayList<ItemStack> tResults = new ArrayList<>();
+ tResults.add(aResult);
+ tResults.add(GTOreDictUnificator.get(true, aResult));
+ if ((tPrefixMaterial != null) && (!tPrefixMaterial.mBlackListed)
+ && (!tPrefixMaterial.mPrefix.mFamiliarPrefixes.isEmpty())) {
+ for (OrePrefixes tPrefix : tPrefixMaterial.mPrefix.mFamiliarPrefixes) {
+ tResults.add(GTOreDictUnificator.get(tPrefix, tPrefixMaterial.mMaterial.mMaterial, 1L));
+ }
+ }
+ if (aResult.getUnlocalizedName()
+ .startsWith("gt.blockores")) {
+ for (int i = 0; i < 8; i++) {
+ tResults.add(new ItemStack(aResult.getItem(), 1, aResult.getItemDamage() % 1000 + i * 1000));
+ }
+ }
+ addFluidStacks(aResult, tResults);
+ for (CachedDefaultRecipe recipe : getCache()) {
+ if (tResults.stream()
+ .anyMatch(stack -> recipe.contains(recipe.mOutputs, stack))) arecipes.add(recipe);
+ }
+ }
+
+ private void addFluidStacks(ItemStack aStack, ArrayList<ItemStack> tResults) {
+ FluidStack tFluid = GTUtility.getFluidForFilledItem(aStack, true);
+ FluidStack tFluidStack;
+ if (tFluid != null) {
+ tFluidStack = tFluid;
+ tResults.add(GTUtility.getFluidDisplayStack(tFluid, false));
+ } else tFluidStack = GTUtility.getFluidFromDisplayStack(aStack);
+ if (tFluidStack != null) {
+ tResults.addAll(GTUtility.getContainersFromFluid(tFluidStack));
+ }
+ }
+
+ private void loadTieredRecipesWithCustomFilter(OverclockDescriber overclockDescriber) {
+ arecipes.addAll(getTieredRecipesWithCustomFilter(overclockDescriber));
+ }
+
+ private List<CachedDefaultRecipe> getTieredRecipesWithCustomFilter(OverclockDescriber overclockDescriber) {
+ List<CachedDefaultRecipe> recipes = getCache();
+ if (!recipes.isEmpty()) {
+ recipes = recipes.stream()
+ .filter(recipe -> overclockDescriber.canHandle(recipe.mRecipe))
+ .collect(Collectors.toList());
+ }
+ return recipes;
+ }
+
+ private void loadTieredRecipesUpTo(byte upperTier) {
+ arecipes.addAll(getTieredRecipes(upperTier));
+ }
+
+ private List<CachedDefaultRecipe> getTieredRecipes(byte upperTier) {
+ List<CachedDefaultRecipe> recipes = getCache();
+ if (!recipes.isEmpty()) {
+ Range<Integer> indexRange = getCacheHolder().getIndexRangeForTiers((byte) 0, upperTier);
+ recipes = recipes.subList(indexRange.getMinimum(), indexRange.getMaximum() + 1);
+ }
+ return recipes;
+ }
+
+ @Override
+ public void loadUsageRecipes(ItemStack aInput) {
+ ItemData tPrefixMaterial = GTOreDictUnificator.getAssociation(aInput);
+ ArrayList<ItemStack> tInputs = new ArrayList<>();
+ tInputs.add(aInput);
+ tInputs.add(GTOreDictUnificator.get(false, aInput));
+ if ((tPrefixMaterial != null) && (!tPrefixMaterial.mPrefix.mFamiliarPrefixes.isEmpty())) {
+ for (OrePrefixes tPrefix : tPrefixMaterial.mPrefix.mFamiliarPrefixes) {
+ tInputs.add(GTOreDictUnificator.get(tPrefix, tPrefixMaterial.mMaterial.mMaterial, 1L));
+ }
+ }
+ addFluidStacks(aInput, tInputs);
+ for (CachedDefaultRecipe recipe : getCache()) {
+ if (tInputs.stream()
+ .anyMatch(stack -> recipe.contains(recipe.mInputs, stack))) arecipes.add(recipe);
+ }
+ }
+
+ @Override
+ public IUsageHandler getUsageAndCatalystHandler(String inputId, Object... ingredients) {
+ if (inputId.equals("item")) {
+ ItemStack candidate = (ItemStack) ingredients[0];
+ GTNEIDefaultHandler handler = (GTNEIDefaultHandler) newInstance();
+ if (RecipeCatalysts.containsCatalyst(handler, candidate)) {
+ IMetaTileEntity metaTile = ItemMachines.getMetaTileEntity(candidate);
+ OverclockDescriber overclockDescriber;
+ if (metaTile instanceof IOverclockDescriptionProvider provider) {
+ overclockDescriber = provider.getOverclockDescriber();
+ } else {
+ overclockDescriber = null;
+ }
+ handler.loadCraftingRecipes(recipeMap.unlocalizedName, overclockDescriber);
+ return handler;
+ }
+ }
+ return this.getUsageHandler(inputId, ingredients);
+ }
+
+ @Override
+ public ICraftingHandler getRecipeHandler(String outputId, Object... results) {
+ GTNEIDefaultHandler handler = (GTNEIDefaultHandler) super.getRecipeHandler(outputId, results);
+ if (results.length > 0 && results[0] instanceof OverclockDescriber) {
+ handler.overclockDescriber = (OverclockDescriber) results[0];
+ }
+ return handler;
+ }
+
+ @Override
+ public String getOverlayIdentifier() {
+ return recipeCategory.unlocalizedName;
+ }
+
+ @Override
+ public void drawBackground(int recipe) {
+ drawUI(modularWindow);
+ }
+
+ @Override
+ public void drawForeground(int recipe) {
+ GL11.glColor4f(1, 1, 1, 1);
+ GL11.glDisable(GL11.GL_LIGHTING);
+ drawExtras(recipe);
+ }
+
+ @Override
+ public void onUpdate() {
+ super.onUpdate();
+ if (!NEIClientUtils.shiftKey()) cycleTicksStatic++;
+ drawTicks++;
+ }
+
+ @Override
+ public int recipiesPerPage() {
+ return 1;
+ }
+
+ @Override
+ public String getRecipeName() {
+ if (recipeNameDisplay == null) {
+ recipeNameDisplay = computeRecipeName();
+ neiTextColorOverride = colorOverride.getTextColorOrDefault("nei", -1);
+ }
+ return recipeNameDisplay;
+ }
+
+ private String computeRecipeName() {
+ String recipeName = StatCollector.translateToLocal(recipeCategory.unlocalizedName);
+ if (overclockDescriber != null) {
+ String suffix = "(" + overclockDescriber.getTierString() + ")";
+ // Space will be cropped if title exceeds
+ return shrinkRecipeName(recipeName + " ", suffix);
+ } else {
+ return shrinkRecipeName(recipeName, "");
+ }
+ }
+
+ private String shrinkRecipeName(final String originalRecipeName, final String suffix) {
+ FontRenderer fontRenderer = Minecraft.getMinecraft().fontRenderer;
+ int suffixWidth = fontRenderer.getStringWidth(suffix);
+ if (fontRenderer.getStringWidth(originalRecipeName) + suffixWidth <= RECIPE_NAME_WIDTH) {
+ return originalRecipeName + suffix;
+ }
+
+ final String ellipsis = "...";
+ final int ellipsisWidth = fontRenderer.getStringWidth(ellipsis);
+ String recipeName = originalRecipeName;
+ do {
+ recipeName = recipeName.substring(0, recipeName.length() - 1);
+ } while (fontRenderer.getStringWidth(recipeName) + ellipsisWidth + suffixWidth > RECIPE_NAME_WIDTH);
+ setupRecipeNameTooltip(originalRecipeName + suffix);
+ return recipeName + ellipsis + suffix;
+ }
+
+ private void setupRecipeNameTooltip(String tooltip) {
+ recipeNameTooltip = new NEIHandlerAbsoluteTooltip(tooltip, new Rectangle(13, -34, RECIPE_NAME_WIDTH - 1, 11));
+ }
+
+ @Override
+ public String getRecipeTabName() {
+ return StatCollector.translateToLocal(recipeCategory.unlocalizedName);
+ }
+
+ @Override
+ public String getGuiTexture() {
+ // not called
+ return "";
+ }
+
+ @Override
+ public List<String> handleItemTooltip(GuiRecipe<?> gui, ItemStack aStack, List<String> currentTip,
+ int aRecipeIndex) {
+ if (recipeNameTooltip != null) {
+ recipeNameTooltip.handleTooltip(currentTip, aRecipeIndex);
+ }
+ if (aStack == null) {
+ return currentTip;
+ }
+
+ CachedRecipe tObject = this.arecipes.get(aRecipeIndex);
+ if (tObject instanceof CachedDefaultRecipe) {
+ currentTip = frontend.handleNEIItemTooltip(aStack, currentTip, (CachedDefaultRecipe) tObject);
+ }
+ return currentTip;
+ }
+
+ @Override
+ public void drawExtras(int aRecipeIndex) {
+ CachedDefaultRecipe cachedRecipe = ((CachedDefaultRecipe) this.arecipes.get(aRecipeIndex));
+
+ drawDescription(cachedRecipe);
+ frontend.drawNEIOverlays(cachedRecipe);
+ }
+
+ private void drawDescription(CachedDefaultRecipe cachedRecipe) {
+ GTRecipe recipe = cachedRecipe.mRecipe;
+ if (overclockDescriber == null) {
+ // By default, assume generic LV EU with no overclocks
+ overclockDescriber = new EUNoOverclockDescriber((byte) 1, uiProperties.amperage);
+ }
+
+ OverclockCalculator calculator = overclockDescriber.createCalculator(
+ new OverclockCalculator().setRecipeEUt(recipe.mEUt)
+ .setDuration(recipe.mDuration),
+ recipe);
+ calculator.calculate();
+
+ frontend.drawDescription(
+ new RecipeDisplayInfo(
+ recipe,
+ recipeMap,
+ overclockDescriber,
+ calculator,
+ getDescriptionYOffset(),
+ neiTextColorOverride));
+ }
+
+ protected int getDescriptionYOffset() {
+ return neiProperties.recipeBackgroundSize.height + neiProperties.recipeBackgroundOffset.y + WINDOW_OFFSET.y + 3;
+ }
+
+ protected void drawUI(ModularWindow window) {
+ for (IDrawable background : window.getBackground()) {
+ GlStateManager.pushMatrix();
+ GlStateManager.translate(
+ WINDOW_OFFSET.x + neiProperties.recipeBackgroundOffset.x,
+ WINDOW_OFFSET.y + neiProperties.recipeBackgroundOffset.y,
+ 0);
+ GlStateManager.color(1f, 1f, 1f, 1f);
+ background.draw(Pos2d.ZERO, window.getSize(), 0);
+ GlStateManager.popMatrix();
+ }
+
+ for (Widget widget : window.getChildren()) {
+ // NEI already did translation, so we can't use Widget#drawInternal here
+ GlStateManager.pushMatrix();
+ GlStateManager.translate(widget.getPos().x, widget.getPos().y, 0);
+ GlStateManager.color(1, 1, 1, window.getAlpha());
+ GlStateManager.enableBlend();
+
+ // maybe we can use Minecraft#timer but none of the IDrawables use partialTicks
+ widget.drawBackground(0);
+
+ // noinspection OverrideOnly // It's either suppressing this warning or changing ModularUI
+ widget.draw(0);
+ GlStateManager.popMatrix();
+ }
+ }
+
+ public static int getDrawTicks() {
+ return drawTicks;
+ }
+
+ public static class FixedPositionedStack extends PositionedStack {
+
+ public static final DecimalFormat chanceFormat = new DecimalFormat("##0.##%");
+ public final int mChance;
+ public final int realStackSize;
+ public final boolean renderRealStackSize;
+
+ public FixedPositionedStack(Object object, boolean renderRealStackSizes, int x, int y) {
+ this(object, renderRealStackSizes, x, y, 0, true);
+ }
+
+ public FixedPositionedStack(Object object, boolean renderRealStackSizes, int x, int y, boolean aUnificate) {
+ this(object, renderRealStackSizes, x, y, 0, aUnificate);
+ }
+
+ public FixedPositionedStack(Object object, boolean renderRealStackSize, int x, int y, int aChance,
+ boolean aUnificate) {
+ super(aUnificate ? GTOreDictUnificator.getNonUnifiedStacks(object) : object, x, y, true);
+ this.mChance = aChance;
+ realStackSize = item != null ? item.stackSize : 0;
+ this.renderRealStackSize = renderRealStackSize;
+ if (!renderRealStackSize) {
+ for (ItemStack stack : items) {
+ stack.stackSize = 1;
+ }
+ }
+ }
+
+ public boolean isChanceBased() {
+ return mChance > 0 && mChance < 10000;
+ }
+
+ public String getChanceText() {
+ return chanceFormat.format((float) mChance / 10000);
+ }
+
+ public boolean isNotConsumed() {
+ return !isFluid() && item.stackSize == 0;
+ }
+
+ public boolean isFluid() {
+ return ItemList.Display_Fluid.isStackEqual(item, true, true);
+ }
+ }
+
+ public class CachedDefaultRecipe extends TemplateRecipeHandler.CachedRecipe {
+
+ public final GTRecipe mRecipe;
+ public final List<PositionedStack> mOutputs;
+ public final List<PositionedStack> mInputs;
+
+ public CachedDefaultRecipe(GTRecipe aRecipe) {
+ super();
+ this.mRecipe = aRecipe;
+ mOutputs = new ArrayList<>();
+ mInputs = new ArrayList<>();
+
+ for (Widget child : modularWindow.getChildren()) {
+ if (child instanceof SlotWidget widget) {
+ if (widget.getMcSlot()
+ .getItemHandler() == itemInputsInventory) {
+ int i = widget.getMcSlot()
+ .getSlotIndex();
+ Object input = aRecipe instanceof GTRecipe.GTRecipe_WithAlt
+ ? ((GTRecipe.GTRecipe_WithAlt) aRecipe).getAltRepresentativeInput(i)
+ : aRecipe.getRepresentativeInput(i);
+ if (input != null) {
+ mInputs.add(
+ new FixedPositionedStack(
+ input,
+ GTNEIDefaultHandler.this.neiProperties.renderRealStackSizes,
+ widget.getPos().x + 1,
+ widget.getPos().y + 1,
+ true));
+ }
+ } else if (widget.getMcSlot()
+ .getItemHandler() == itemOutputsInventory) {
+ int i = widget.getMcSlot()
+ .getSlotIndex();
+ ItemStack output = aRecipe.getRepresentativeOutput(i);
+ if (output != null) {
+ mOutputs.add(
+ new FixedPositionedStack(
+ output,
+ GTNEIDefaultHandler.this.neiProperties.renderRealStackSizes,
+ widget.getPos().x + 1,
+ widget.getPos().y + 1,
+ aRecipe.getOutputChance(i),
+ GTNEIDefaultHandler.this.neiProperties.unificateOutput));
+ }
+ } else if (widget.getMcSlot()
+ .getItemHandler() == specialSlotInventory) {
+ if (aRecipe.mSpecialItems != null) {
+ mInputs.add(
+ new FixedPositionedStack(
+ aRecipe.mSpecialItems,
+ GTNEIDefaultHandler.this.neiProperties.renderRealStackSizes,
+ widget.getPos().x + 1,
+ widget.getPos().y + 1));
+ }
+
+ } else if (widget.getMcSlot()
+ .getItemHandler() == fluidInputsInventory) {
+ int i = widget.getMcSlot()
+ .getSlotIndex();
+ if (aRecipe.mFluidInputs.length > i && aRecipe.mFluidInputs[i] != null
+ && aRecipe.mFluidInputs[i].getFluid() != null) {
+ mInputs.add(
+ new FixedPositionedStack(
+ GTUtility.getFluidDisplayStack(aRecipe.mFluidInputs[i], true),
+ GTNEIDefaultHandler.this.neiProperties.renderRealStackSizes,
+ widget.getPos().x + 1,
+ widget.getPos().y + 1));
+ }
+ } else if (widget.getMcSlot()
+ .getItemHandler() == fluidOutputsInventory) {
+ int i = widget.getMcSlot()
+ .getSlotIndex();
+ if (aRecipe.mFluidOutputs.length > i && aRecipe.mFluidOutputs[i] != null
+ && aRecipe.mFluidOutputs[i].getFluid() != null) {
+ mOutputs.add(
+ new FixedPositionedStack(
+ GTUtility.getFluidDisplayStack(aRecipe.mFluidOutputs[i], true),
+ GTNEIDefaultHandler.this.neiProperties.renderRealStackSizes,
+ widget.getPos().x + 1,
+ widget.getPos().y + 1));
+ }
+ }
+ }
+ }
+
+ // items and fluids that exceed usual count
+ UIHelper.forEachSlots((i, backgrounds, pos) -> {
+ if (i >= GTNEIDefaultHandler.this.uiProperties.maxItemInputs && aRecipe.mInputs[i] != null) {
+ mInputs.add(
+ new FixedPositionedStack(
+ aRecipe.mInputs[i],
+ GTNEIDefaultHandler.this.neiProperties.renderRealStackSizes,
+ pos.x + 1,
+ pos.y + 1,
+ true));
+ }
+ }, (i, backgrounds, pos) -> {
+ if (i >= GTNEIDefaultHandler.this.uiProperties.maxItemOutputs && aRecipe.mOutputs[i] != null) {
+ mOutputs.add(
+ new FixedPositionedStack(
+ aRecipe.mOutputs[i],
+ GTNEIDefaultHandler.this.neiProperties.renderRealStackSizes,
+ pos.x + 1,
+ pos.y + 1,
+ aRecipe.getOutputChance(i),
+ GTNEIDefaultHandler.this.neiProperties.unificateOutput));
+ }
+ }, (i, backgrounds, pos) -> {}, (i, backgrounds, pos) -> {
+ if (i >= GTNEIDefaultHandler.this.uiProperties.maxFluidInputs && aRecipe.mFluidInputs[i] != null
+ && aRecipe.mFluidInputs[i].getFluid() != null) {
+ mInputs.add(
+ new FixedPositionedStack(
+ GTUtility.getFluidDisplayStack(aRecipe.mFluidInputs[i], true),
+ GTNEIDefaultHandler.this.neiProperties.renderRealStackSizes,
+ pos.x + 1,
+ pos.y + 1));
+ }
+ }, (i, backgrounds, pos) -> {
+ if (i >= GTNEIDefaultHandler.this.uiProperties.maxFluidOutputs && aRecipe.mFluidOutputs[i] != null
+ && aRecipe.mFluidOutputs[i].getFluid() != null) {
+ mOutputs.add(
+ new FixedPositionedStack(
+ GTUtility.getFluidDisplayStack(aRecipe.mFluidOutputs[i], true),
+ GTNEIDefaultHandler.this.neiProperties.renderRealStackSizes,
+ pos.x + 1,
+ pos.y + 1));
+ }
+ },
+ IDrawable.EMPTY,
+ IDrawable.EMPTY,
+ GTNEIDefaultHandler.this.frontend.getUIProperties(),
+ aRecipe.mInputs.length,
+ aRecipe.mOutputs.length,
+ aRecipe.mFluidInputs.length,
+ aRecipe.mFluidOutputs.length,
+ SteamVariant.NONE,
+ WINDOW_OFFSET);
+ }
+
+ @Override
+ public List<PositionedStack> getIngredients() {
+ return getCycledIngredients(cycleTicksStatic / 10, this.mInputs);
+ }
+
+ @Override
+ public PositionedStack getResult() {
+ return null;
+ }
+
+ @Override
+ public List<PositionedStack> getOtherStacks() {
+ return this.mOutputs;
+ }
+ }
+
+ private class SortedRecipeListCache {
+
+ private int mCachedRecipesVersion = -1;
+
+ @Nullable
+ private SoftReference<List<CachedDefaultRecipe>> mCachedRecipes;
+
+ private Map<Byte, Range<Integer>> mTierIndexes;
+ private Range<Byte> mTierRange;
+
+ public int getCachedRecipesVersion() {
+ return mCachedRecipesVersion;
+ }
+
+ public void setCachedRecipesVersion(int aCachedRecipesVersion) {
+ this.mCachedRecipesVersion = aCachedRecipesVersion;
+ }
+
+ @Nullable
+ public List<CachedDefaultRecipe> getCachedRecipes() {
+ return mCachedRecipes == null ? null : mCachedRecipes.get();
+ }
+
+ public void setCachedRecipes(@Nonnull List<CachedDefaultRecipe> aCachedRecipes) {
+ this.mCachedRecipes = new SoftReference<>(aCachedRecipes);
+ }
+
+ public Range<Integer> getIndexRangeForTiers(byte lowerTier, byte upperTier) {
+ if (mTierIndexes == null) {
+ computeTierIndexes();
+ }
+ return Range.between(getLowIndexForTier(lowerTier), getHighIndexForTier(upperTier));
+ }
+
+ private void computeTierIndexes() {
+ // Holds 16 elements without rehashing
+ mTierIndexes = new HashMap<>(V.length + 1, 1f);
+ assert mCachedRecipes != null;
+ Iterator<CachedDefaultRecipe> iterator = Objects.requireNonNull(mCachedRecipes.get())
+ .iterator();
+
+ int index = 0;
+ int minIndex = 0;
+ int maxIndex = -1;
+ byte previousTier = -1;
+ byte lowestTier = 0;
+ while (iterator.hasNext()) {
+ CachedDefaultRecipe recipe = iterator.next();
+ byte recipeTier = GTUtility
+ .getTier(recipe.mRecipe.mEUt / GTNEIDefaultHandler.this.recipeMap.getAmperage());
+ if (recipeTier != previousTier) {
+ if (maxIndex != -1) {
+ mTierIndexes.put(previousTier, Range.between(minIndex, maxIndex));
+ } else {
+ lowestTier = recipeTier;
+ }
+ minIndex = index;
+ previousTier = recipeTier;
+ }
+ maxIndex = index;
+ index++;
+ if (!iterator.hasNext()) {
+ mTierIndexes.put(recipeTier, Range.between(minIndex, maxIndex));
+ mTierRange = Range.between(lowestTier, recipeTier);
+ }
+ }
+ }
+
+ private int getLowIndexForTier(byte lowerTier) {
+ byte lowTier = (byte) Math.max(mTierRange.getMinimum(), lowerTier);
+ while (mTierIndexes.get(lowTier) == null) {
+ lowTier++;
+ }
+ return mTierIndexes.get(lowTier)
+ .getMinimum();
+ }
+
+ private int getHighIndexForTier(byte upperTier) {
+ byte highTier = (byte) Math.min(mTierRange.getMaximum(), upperTier);
+ while (mTierIndexes.get(highTier) == null) {
+ highTier--;
+ }
+ return mTierIndexes.get(highTier)
+ .getMaximum();
+ }
+ }
+}