diff options
Diffstat (limited to 'src/main/java/gregtech/nei')
19 files changed, 1645 insertions, 0 deletions
diff --git a/src/main/java/gregtech/nei/GT_NEI_DefaultHandler.java b/src/main/java/gregtech/nei/GT_NEI_DefaultHandler.java new file mode 100644 index 0000000000..99e7dc5459 --- /dev/null +++ b/src/main/java/gregtech/nei/GT_NEI_DefaultHandler.java @@ -0,0 +1,801 @@ +package gregtech.nei; + +import static gregtech.api.enums.GT_Values.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.GT_Mod; +import gregtech.api.enums.ItemList; +import gregtech.api.enums.OrePrefixes; +import gregtech.api.enums.SteamVariant; +import gregtech.api.gui.GT_GUIColorOverride; +import gregtech.api.gui.modularui.GT_UITextures; +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.GT_OreDictUnificator; +import gregtech.api.util.GT_OverclockCalculator; +import gregtech.api.util.GT_Recipe; +import gregtech.api.util.GT_Utility; +import gregtech.common.blocks.GT_Item_Machines; +import gregtech.common.gui.modularui.UIHelper; + +public class GT_NEI_DefaultHandler 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 GT_GUIColorOverride colorOverride = GT_GUIColorOverride + .get(GT_UITextures.BACKGROUND_NEI_SINGLE_RECIPE.location); + private int neiTextColorOverride = -1; + + public GT_NEI_DefaultHandler(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() != GT_Mod.gregtechproxy.getNEIReloadCount() + || (cache = cacheHolder.getCachedRecipes()) == null) { + try { + RecipeCategory defaultCategory = recipeMap.getDefaultRecipeCategory(); + Collection<GT_Recipe> recipes; + if (this.recipeCategory == defaultCategory) { + // This is main category, so merge categories that are configured as such + Stream<GT_Recipe> recipesToMerge = recipeMap.getBackend() + .getRecipeCategoryMap() + .entrySet() + .stream() + .flatMap(entry -> { + boolean merge = entry.getKey() != defaultCategory + && GT_Mod.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 (GT_Mod.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(GT_Mod.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 GT_NEI_DefaultHandler(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 = GT_OreDictUnificator.getAssociation(aResult); + + ArrayList<ItemStack> tResults = new ArrayList<>(); + tResults.add(aResult); + tResults.add(GT_OreDictUnificator.get(true, aResult)); + if ((tPrefixMaterial != null) && (!tPrefixMaterial.mBlackListed) + && (!tPrefixMaterial.mPrefix.mFamiliarPrefixes.isEmpty())) { + for (OrePrefixes tPrefix : tPrefixMaterial.mPrefix.mFamiliarPrefixes) { + tResults.add(GT_OreDictUnificator.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 = GT_Utility.getFluidForFilledItem(aStack, true); + FluidStack tFluidStack; + if (tFluid != null) { + tFluidStack = tFluid; + tResults.add(GT_Utility.getFluidDisplayStack(tFluid, false)); + } else tFluidStack = GT_Utility.getFluidFromDisplayStack(aStack); + if (tFluidStack != null) { + tResults.addAll(GT_Utility.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 = GT_OreDictUnificator.getAssociation(aInput); + ArrayList<ItemStack> tInputs = new ArrayList<>(); + tInputs.add(aInput); + tInputs.add(GT_OreDictUnificator.get(false, aInput)); + if ((tPrefixMaterial != null) && (!tPrefixMaterial.mPrefix.mFamiliarPrefixes.isEmpty())) { + for (OrePrefixes tPrefix : tPrefixMaterial.mPrefix.mFamiliarPrefixes) { + tInputs.add(GT_OreDictUnificator.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]; + GT_NEI_DefaultHandler handler = (GT_NEI_DefaultHandler) newInstance(); + if (RecipeCatalysts.containsCatalyst(handler, candidate)) { + IMetaTileEntity metaTile = GT_Item_Machines.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) { + GT_NEI_DefaultHandler handler = (GT_NEI_DefaultHandler) 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) { + GT_Recipe recipe = cachedRecipe.mRecipe; + if (overclockDescriber == null) { + // By default, assume generic LV EU with no overclocks + overclockDescriber = new EUNoOverclockDescriber((byte) 1, uiProperties.amperage); + } + + GT_OverclockCalculator calculator = overclockDescriber.createCalculator( + new GT_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 ? GT_OreDictUnificator.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 GT_Recipe mRecipe; + public final List<PositionedStack> mOutputs; + public final List<PositionedStack> mInputs; + + public CachedDefaultRecipe(GT_Recipe 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 GT_Recipe.GT_Recipe_WithAlt + ? ((GT_Recipe.GT_Recipe_WithAlt) aRecipe).getAltRepresentativeInput(i) + : aRecipe.getRepresentativeInput(i); + if (input != null) { + mInputs.add( + new FixedPositionedStack( + input, + GT_NEI_DefaultHandler.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, + GT_NEI_DefaultHandler.this.neiProperties.renderRealStackSizes, + widget.getPos().x + 1, + widget.getPos().y + 1, + aRecipe.getOutputChance(i), + GT_NEI_DefaultHandler.this.neiProperties.unificateOutput)); + } + } else if (widget.getMcSlot() + .getItemHandler() == specialSlotInventory) { + if (aRecipe.mSpecialItems != null) { + mInputs.add( + new FixedPositionedStack( + aRecipe.mSpecialItems, + GT_NEI_DefaultHandler.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( + GT_Utility.getFluidDisplayStack(aRecipe.mFluidInputs[i], true), + GT_NEI_DefaultHandler.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( + GT_Utility.getFluidDisplayStack(aRecipe.mFluidOutputs[i], true), + GT_NEI_DefaultHandler.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 >= GT_NEI_DefaultHandler.this.uiProperties.maxItemInputs && aRecipe.mInputs[i] != null) { + mInputs.add( + new FixedPositionedStack( + aRecipe.mInputs[i], + GT_NEI_DefaultHandler.this.neiProperties.renderRealStackSizes, + pos.x + 1, + pos.y + 1, + true)); + } + }, (i, backgrounds, pos) -> { + if (i >= GT_NEI_DefaultHandler.this.uiProperties.maxItemOutputs && aRecipe.mOutputs[i] != null) { + mOutputs.add( + new FixedPositionedStack( + aRecipe.mOutputs[i], + GT_NEI_DefaultHandler.this.neiProperties.renderRealStackSizes, + pos.x + 1, + pos.y + 1, + aRecipe.getOutputChance(i), + GT_NEI_DefaultHandler.this.neiProperties.unificateOutput)); + } + }, (i, backgrounds, pos) -> {}, (i, backgrounds, pos) -> { + if (i >= GT_NEI_DefaultHandler.this.uiProperties.maxFluidInputs && aRecipe.mFluidInputs[i] != null + && aRecipe.mFluidInputs[i].getFluid() != null) { + mInputs.add( + new FixedPositionedStack( + GT_Utility.getFluidDisplayStack(aRecipe.mFluidInputs[i], true), + GT_NEI_DefaultHandler.this.neiProperties.renderRealStackSizes, + pos.x + 1, + pos.y + 1)); + } + }, (i, backgrounds, pos) -> { + if (i >= GT_NEI_DefaultHandler.this.uiProperties.maxFluidOutputs && aRecipe.mFluidOutputs[i] != null + && aRecipe.mFluidOutputs[i].getFluid() != null) { + mOutputs.add( + new FixedPositionedStack( + GT_Utility.getFluidDisplayStack(aRecipe.mFluidOutputs[i], true), + GT_NEI_DefaultHandler.this.neiProperties.renderRealStackSizes, + pos.x + 1, + pos.y + 1)); + } + }, + IDrawable.EMPTY, + IDrawable.EMPTY, + GT_NEI_DefaultHandler.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 = GT_Utility + .getTier(recipe.mRecipe.mEUt / GT_NEI_DefaultHandler.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(); + } + } +} diff --git a/src/main/java/gregtech/nei/NEIHandlerAbsoluteTooltip.java b/src/main/java/gregtech/nei/NEIHandlerAbsoluteTooltip.java new file mode 100644 index 0000000000..bc1c6b5630 --- /dev/null +++ b/src/main/java/gregtech/nei/NEIHandlerAbsoluteTooltip.java @@ -0,0 +1,54 @@ +package gregtech.nei; + +import java.awt.Dimension; +import java.awt.Point; +import java.awt.Rectangle; +import java.util.List; + +import codechicken.lib.gui.GuiDraw; + +public class NEIHandlerAbsoluteTooltip { + + private final Rectangle area; + private final String tooltip; + private Dimension displaySize; + + public NEIHandlerAbsoluteTooltip(String tooltip, Rectangle area) { + this.tooltip = tooltip; + this.area = area; + } + + public void handleTooltip(List<String> currenttip, int recipeIndex) { + displaySize = GuiDraw.displaySize(); + if (shouldAddTooltip(recipeIndex)) { + currenttip.add(tooltip); + } + } + + private boolean shouldAddTooltip(int recipeIndex) { + return isPageFirstRecipe(recipeIndex) && mouseInArea(); + } + + private boolean mouseInArea() { + Point mousePos = getRelMouse(); + return area.contains(mousePos); + } + + private Point getRelMouse() { + int ySize = Math.min(Math.max(displaySize.height - 68, 166), 370); + int guiLeft = (displaySize.width - 176) / 2; + int guiTop = (displaySize.height - ySize) / 2 + 10; + Point mousePos = GuiDraw.getMousePosition(); + return new Point(mousePos.x - guiLeft - 5, mousePos.y - guiTop - 38); + } + + private boolean isPageFirstRecipe(int recipe) { + int actualRecipesPerPage = getActualRecipesPerPage(); + return actualRecipesPerPage < 2 || recipe % 2 == 0; + } + + private int getActualRecipesPerPage() { + int ySize = Math.min(Math.max(displaySize.height - 68, 166), 370); + return (ySize - (12 * 3)) / 135; + } +} diff --git a/src/main/java/gregtech/nei/NEI_GT_Config.java b/src/main/java/gregtech/nei/NEI_GT_Config.java new file mode 100644 index 0000000000..198f71fdb0 --- /dev/null +++ b/src/main/java/gregtech/nei/NEI_GT_Config.java @@ -0,0 +1,183 @@ +package gregtech.nei; + +import java.util.Collection; +import java.util.Comparator; +import java.util.Map; + +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ListMultimap; + +import codechicken.nei.api.API; +import codechicken.nei.api.IConfigureNEI; +import codechicken.nei.event.NEIRegisterHandlerInfosEvent; +import codechicken.nei.recipe.GuiCraftingRecipe; +import codechicken.nei.recipe.GuiUsageRecipe; +import codechicken.nei.recipe.HandlerInfo; +import codechicken.nei.recipe.TemplateRecipeHandler; +import cpw.mods.fml.common.event.FMLInterModComms; +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import gregtech.api.GregTech_API; +import gregtech.api.enums.GT_Values; +import gregtech.api.enums.ItemList; +import gregtech.api.interfaces.metatileentity.IMetaTileEntity; +import gregtech.api.interfaces.tileentity.RecipeMapWorkable; +import gregtech.api.recipe.RecipeCategory; +import gregtech.api.recipe.RecipeMap; +import gregtech.api.recipe.RecipeMaps; +import gregtech.api.util.GT_ModHandler; +import gregtech.common.items.GT_MetaGenerated_Item_01; +import gregtech.common.items.GT_MetaGenerated_Item_02; +import gregtech.common.items.GT_MetaGenerated_Item_03; +import gregtech.nei.dumper.BatchModeSupportDumper; +import gregtech.nei.dumper.InputSeparationSupportDumper; +import gregtech.nei.dumper.MaterialDumper; +import gregtech.nei.dumper.MetaItemDumper; +import gregtech.nei.dumper.MetaTileEntityDumper; +import gregtech.nei.dumper.RecipeLockingSupportDumper; +import gregtech.nei.dumper.VoidProtectionSupportDumper; + +public class NEI_GT_Config implements IConfigureNEI { + + /** + * This map determines the order in which NEI handlers will be registered and displayed in tabs. + * + * <p> + * Handlers will be displayed in ascending order of integer value. Any recipe map that is not present in this map + * will be assigned a value of 0. Negative values are fine. + */ + private static final ImmutableMap<RecipeMap<?>, Integer> RECIPE_MAP_ORDERING = ImmutableMap + .<RecipeMap<?>, Integer>builder() + .put(RecipeMaps.assemblylineVisualRecipes, 1) + .put(RecipeMaps.scannerFakeRecipes, 2) + .build(); + + private static final Comparator<GT_NEI_DefaultHandler> RECIPE_MAP_HANDLER_COMPARATOR = Comparator + .comparingInt(handler -> RECIPE_MAP_ORDERING.getOrDefault(handler.getRecipeMap(), 0)); + + private static ListMultimap<RecipeCategory, RecipeMapWorkable> RECIPE_CATALYST_INDEX; + + public static boolean sIsAdded = true; + + private static void addHandler(TemplateRecipeHandler handler) { + FMLInterModComms.sendRuntimeMessage( + GT_Values.GT, + "NEIPlugins", + "register-crafting-handler", + "gregtech@" + handler.getRecipeName() + "@" + handler.getOverlayIdentifier()); + GuiCraftingRecipe.craftinghandlers.add(handler); + GuiUsageRecipe.usagehandlers.add(handler); + } + + @Override + public void loadConfig() { + sIsAdded = false; + registerHandlers(); + registerCatalysts(); + registerItemEntries(); + registerDumpers(); + sIsAdded = true; + } + + private void registerHandlers() { + RecipeCategory.ALL_RECIPE_CATEGORIES.values() + .stream() + .filter( + recipeCategory -> recipeCategory.recipeMap.getFrontend() + .getNEIProperties().registerNEI) + .map(GT_NEI_DefaultHandler::new) + .sorted(RECIPE_MAP_HANDLER_COMPARATOR) + .forEach(NEI_GT_Config::addHandler); + } + + private void registerCatalysts() { + for (Map.Entry<RecipeCategory, Collection<RecipeMapWorkable>> entry : RECIPE_CATALYST_INDEX.asMap() + .entrySet()) { + entry.getValue() + .forEach( + recipeMapWorkable -> API.addRecipeCatalyst( + recipeMapWorkable.getStackForm(1), + entry.getKey().unlocalizedName, + recipeMapWorkable.getRecipeCatalystPriority())); + } + API.addRecipeCatalyst( + GT_ModHandler.getIC2Item("nuclearReactor", 1, null), + RecipeMaps.ic2NuclearFakeRecipes.unlocalizedName); + } + + private void registerItemEntries() { + API.addItemListEntry(ItemList.VOLUMETRIC_FLASK.get(1)); + } + + private void registerDumpers() { + API.addOption(new MetaTileEntityDumper()); + API.addOption(new MaterialDumper()); + API.addOption(new MetaItemDumper(GT_MetaGenerated_Item_01.INSTANCE, "metaitem01")); + API.addOption(new MetaItemDumper(GT_MetaGenerated_Item_02.INSTANCE, "metaitem02")); + API.addOption(new MetaItemDumper(GT_MetaGenerated_Item_03.INSTANCE, "metaitem03")); + API.addOption(new VoidProtectionSupportDumper()); + API.addOption(new InputSeparationSupportDumper()); + API.addOption(new BatchModeSupportDumper()); + API.addOption(new RecipeLockingSupportDumper()); + } + + @SubscribeEvent + public void registerHandlerInfo(NEIRegisterHandlerInfosEvent event) { + if (RECIPE_CATALYST_INDEX == null) { + // This method will be called earlier than #loadConfig + generateRecipeCatalystIndex(); + } + RecipeCategory.ALL_RECIPE_CATEGORIES.values() + .forEach(recipeCategory -> { + HandlerInfo.Builder builder = createHandlerInfoBuilderTemplate(recipeCategory); + HandlerInfo handlerInfo; + if (recipeCategory.handlerInfoCreator != null) { + handlerInfo = recipeCategory.handlerInfoCreator.apply(builder) + .build(); + } else { + // Infer icon from recipe catalysts + RECIPE_CATALYST_INDEX.get(recipeCategory) + .stream() + .findFirst() + .ifPresent(catalyst -> builder.setDisplayStack(catalyst.getStackForm(1))); + handlerInfo = builder.build(); + } + event.registerHandlerInfo(handlerInfo); + }); + } + + private HandlerInfo.Builder createHandlerInfoBuilderTemplate(RecipeCategory recipeCategory) { + return new HandlerInfo.Builder( + recipeCategory.unlocalizedName, + recipeCategory.ownerMod.getName(), + recipeCategory.ownerMod.getModId()).setShiftY(6) + .setHeight(135) + .setMaxRecipesPerPage(2); + } + + private static void generateRecipeCatalystIndex() { + ImmutableListMultimap.Builder<RecipeCategory, RecipeMapWorkable> builder = new ImmutableListMultimap.Builder<>(); + builder + .orderValuesBy(Comparator.comparing(recipeMapWorkable -> -recipeMapWorkable.getRecipeCatalystPriority())); + for (int i = 1; i < GregTech_API.METATILEENTITIES.length; i++) { + IMetaTileEntity mte = GregTech_API.METATILEENTITIES[i]; + if (!(mte instanceof RecipeMapWorkable recipeMapWorkable)) continue; + for (RecipeMap<?> recipeMap : recipeMapWorkable.getAvailableRecipeMaps()) { + for (RecipeCategory recipeCategory : recipeMap.getAssociatedCategories()) { + builder.put(recipeCategory, recipeMapWorkable); + } + } + } + RECIPE_CATALYST_INDEX = builder.build(); + } + + @Override + public String getName() { + return "GregTech NEI Plugin"; + } + + @Override + public String getVersion() { + return "(5.03a)"; + } +} diff --git a/src/main/java/gregtech/nei/RecipeDisplayInfo.java b/src/main/java/gregtech/nei/RecipeDisplayInfo.java new file mode 100644 index 0000000000..f9cc1a9a8c --- /dev/null +++ b/src/main/java/gregtech/nei/RecipeDisplayInfo.java @@ -0,0 +1,99 @@ +package gregtech.nei; + +import static gregtech.api.util.GT_Utility.isStringInvalid; + +import java.util.List; + +import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; + +import net.minecraft.client.Minecraft; + +import gregtech.api.objects.overclockdescriber.OverclockDescriber; +import gregtech.api.recipe.RecipeMap; +import gregtech.api.util.FieldsAreNonnullByDefault; +import gregtech.api.util.GT_OverclockCalculator; +import gregtech.api.util.GT_Recipe; +import gregtech.api.util.MethodsReturnNonnullByDefault; + +/** + * Holds info used for drawing descriptions on NEI. + */ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@FieldsAreNonnullByDefault +public class RecipeDisplayInfo { + + /** + * Recipe to show description. + */ + public final GT_Recipe recipe; + + /** + * RecipeMap the recipe belongs to. + */ + public final RecipeMap<?> recipeMap; + + /** + * When user looks up usage for machine, NEI will show all the recipes that the machine can process, taking tier of + * the machine into consideration. This object can be used to show info around overclocked EU/t and duration. + */ + public final OverclockDescriber overclockDescriber; + + /** + * Pre-built overclock calculator, used for drawing OC information. Do not calculate it again. + */ + public final GT_OverclockCalculator calculator; + + /** + * Current Y position for drawing description. + */ + private int yPos; + + private final int neiTextColorOverride; + + RecipeDisplayInfo(GT_Recipe recipe, RecipeMap<?> recipeMap, OverclockDescriber overclockDescriber, + GT_OverclockCalculator calculator, int descriptionYOffset, int neiTextColorOverride) { + this.recipe = recipe; + this.recipeMap = recipeMap; + this.overclockDescriber = overclockDescriber; + this.calculator = calculator; + this.yPos = descriptionYOffset; + this.neiTextColorOverride = neiTextColorOverride; + } + + /** + * Draws text. + */ + public void drawText(@Nullable String text) { + drawText(text, 10); + } + + /** + * Draws text. + * + * @param yShift y position to shift after this text + */ + public void drawText(@Nullable String text, int yShift) { + drawText(text, 5, yShift); + } + + /** + * Draws text. + * + * @param xStart x position to start drawing + * @param yShift y position to shift after this text + */ + public void drawText(@Nullable String text, int xStart, int yShift) { + if (isStringInvalid(text)) return; + Minecraft.getMinecraft().fontRenderer + .drawString(text, xStart, yPos, neiTextColorOverride != -1 ? neiTextColorOverride : 0x000000); + yPos += yShift; + } + + public void drawTextMultipleLines(List<String> texts) { + for (String text : texts) { + drawText(text, 10); + } + } +} diff --git a/src/main/java/gregtech/nei/dumper/BatchModeSupportDumper.java b/src/main/java/gregtech/nei/dumper/BatchModeSupportDumper.java new file mode 100644 index 0000000000..ea0168073a --- /dev/null +++ b/src/main/java/gregtech/nei/dumper/BatchModeSupportDumper.java @@ -0,0 +1,10 @@ +package gregtech.nei.dumper; + +import gregtech.api.interfaces.modularui.ControllerWithOptionalFeatures; + +public class BatchModeSupportDumper extends MultiBlockFeatureSupportDumper { + + public BatchModeSupportDumper() { + super("batch_mode", ControllerWithOptionalFeatures::supportsBatchMode); + } +} diff --git a/src/main/java/gregtech/nei/dumper/GregTechIDDumper.java b/src/main/java/gregtech/nei/dumper/GregTechIDDumper.java new file mode 100644 index 0000000000..0c2f20ac22 --- /dev/null +++ b/src/main/java/gregtech/nei/dumper/GregTechIDDumper.java @@ -0,0 +1,61 @@ +package gregtech.nei.dumper; + +import static gregtech.api.enums.Mods.NewHorizonsCoreMod; + +import net.minecraft.util.ChatComponentTranslation; +import net.minecraft.util.ChatStyle; +import net.minecraft.util.EnumChatFormatting; + +import codechicken.nei.NEIClientUtils; +import codechicken.nei.config.DataDumper; + +public abstract class GregTechIDDumper extends DataDumper { + + public GregTechIDDumper(String name) { + super("tools.dump.gt5u." + name); + } + + @Override + public Iterable<String[]> dump(int modeInt) { + return dump(getMode(modeInt)); + } + + protected abstract Iterable<String[]> dump(Mode mode); + + @Override + public String modeButtonText() { + return NEIClientUtils.lang.translate("options.tools.dump.gt5u.mode." + getMode()); + } + + @Override + public void dumpFile() { + super.dumpFile(); + logWarn(); + } + + protected void super$dumpFile() { + super.dumpFile(); + } + + protected void logWarn() { + if (!NewHorizonsCoreMod.isModLoaded()) { + NEIClientUtils.printChatMessage( + new ChatComponentTranslation("nei.options.tools.dump.gt5u.warn_env") + .setChatStyle(new ChatStyle().setColor(EnumChatFormatting.DARK_RED))); + } + } + + @Override + public int modeCount() { + return Mode.values().length; + } + + protected Mode getMode(int modeInt) { + return Mode.values()[modeInt]; + } + + protected enum Mode { + FREE, + USED + } +} diff --git a/src/main/java/gregtech/nei/dumper/InputSeparationSupportDumper.java b/src/main/java/gregtech/nei/dumper/InputSeparationSupportDumper.java new file mode 100644 index 0000000000..5b0d293827 --- /dev/null +++ b/src/main/java/gregtech/nei/dumper/InputSeparationSupportDumper.java @@ -0,0 +1,10 @@ +package gregtech.nei.dumper; + +import gregtech.api.interfaces.modularui.ControllerWithOptionalFeatures; + +public class InputSeparationSupportDumper extends MultiBlockFeatureSupportDumper { + + public InputSeparationSupportDumper() { + super("input_separation", ControllerWithOptionalFeatures::supportsInputSeparation); + } +} diff --git a/src/main/java/gregtech/nei/dumper/MaterialDumper.java b/src/main/java/gregtech/nei/dumper/MaterialDumper.java new file mode 100644 index 0000000000..3234779de8 --- /dev/null +++ b/src/main/java/gregtech/nei/dumper/MaterialDumper.java @@ -0,0 +1,39 @@ +package gregtech.nei.dumper; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import gregtech.api.GregTech_API; +import gregtech.api.enums.Materials; + +public class MaterialDumper extends GregTechIDDumper { + + public MaterialDumper() { + super("material"); + } + + @Override + public String[] header() { + return new String[] { "id", "name", }; + } + + @Override + protected Iterable<String[]> dump(Mode mode) { + List<String[]> dump = new ArrayList<>(); + Map<Integer, Materials> idMap = Arrays.stream(GregTech_API.sGeneratedMaterials) + .filter(Objects::nonNull) + .collect(Collectors.toMap(m -> m.mMetaItemSubID, m -> m)); + for (int i = 0; i < 1000; i++) { + if (mode == Mode.FREE && !idMap.containsKey(i)) { + dump.add(new String[] { String.valueOf(i), "", }); + } else if (mode == Mode.USED && idMap.containsKey(i)) { + dump.add(new String[] { String.valueOf(i), idMap.get(i).mName, }); + } + } + return dump; + } +} diff --git a/src/main/java/gregtech/nei/dumper/MetaItemDumper.java b/src/main/java/gregtech/nei/dumper/MetaItemDumper.java new file mode 100644 index 0000000000..519af48e93 --- /dev/null +++ b/src/main/java/gregtech/nei/dumper/MetaItemDumper.java @@ -0,0 +1,60 @@ +package gregtech.nei.dumper; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.item.ItemStack; +import net.minecraft.util.ChatComponentText; + +import codechicken.nei.NEIClientUtils; +import gregtech.api.items.GT_MetaGenerated_Item; + +public class MetaItemDumper extends GregTechIDDumper { + + private final GT_MetaGenerated_Item item; + private final String nameSuffix; + + public MetaItemDumper(GT_MetaGenerated_Item item, String name) { + super(name); + this.nameSuffix = name; + this.item = item; + } + + @Override + public String[] header() { + return new String[] { "id", "stackName", "metaID" }; + } + + @Override + protected Iterable<String[]> dump(Mode mode) { + List<String[]> list = new ArrayList<>(); + for (int i = 0; i < item.mItemAmount; i++) { + int metaID = item.mOffset + i; + boolean generated = item.mEnabledItems.get(i); + if (mode == Mode.FREE && !generated) { + list.add(new String[] { String.valueOf(i), "", String.valueOf(metaID), }); + } else if (mode == Mode.USED && generated) { + list.add( + new String[] { String.valueOf(i), new ItemStack(item, 1, metaID).getDisplayName(), + String.valueOf(metaID), }); + } + } + return list; + } + + @Override + public void dumpFile() { + super.super$dumpFile(); + NEIClientUtils.printChatMessage( + new ChatComponentText(String.format("mOffset: %s, mItemAmount: %s", item.mOffset, item.mItemAmount))); + logWarn(); + } + + @Override + public String translateN(String s, Object... args) { + if (name.equals(s) || (name + "s").equals(s)) { + return nameSuffix; + } + return super.translateN(s, args); + } +} diff --git a/src/main/java/gregtech/nei/dumper/MetaTileEntityDumper.java b/src/main/java/gregtech/nei/dumper/MetaTileEntityDumper.java new file mode 100644 index 0000000000..ee30cdfb8a --- /dev/null +++ b/src/main/java/gregtech/nei/dumper/MetaTileEntityDumper.java @@ -0,0 +1,37 @@ +package gregtech.nei.dumper; + +import java.util.ArrayList; +import java.util.List; + +import gregtech.api.GregTech_API; +import gregtech.api.interfaces.metatileentity.IMetaTileEntity; + +public class MetaTileEntityDumper extends GregTechIDDumper { + + public MetaTileEntityDumper() { + super("metatileentity"); + } + + @Override + public String[] header() { + return new String[] { "id", "stackName", "className", }; + } + + @Override + protected Iterable<String[]> dump(Mode mode) { + List<String[]> list = new ArrayList<>(); + for (int i = 1; i < GregTech_API.METATILEENTITIES.length; i++) { + IMetaTileEntity mte = GregTech_API.METATILEENTITIES[i]; + if (mode == Mode.FREE && mte == null) { + list.add(new String[] { String.valueOf(i), "", "", }); + } else if (mode == Mode.USED && mte != null) { + list.add( + new String[] { String.valueOf(i), mte.getStackForm(1) + .getDisplayName(), + mte.getClass() + .getSimpleName() }); + } + } + return list; + } +} diff --git a/src/main/java/gregtech/nei/dumper/MultiBlockFeatureSupportDumper.java b/src/main/java/gregtech/nei/dumper/MultiBlockFeatureSupportDumper.java new file mode 100644 index 0000000000..dc378ff9ad --- /dev/null +++ b/src/main/java/gregtech/nei/dumper/MultiBlockFeatureSupportDumper.java @@ -0,0 +1,46 @@ +package gregtech.nei.dumper; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import codechicken.nei.config.DataDumper; +import gregtech.api.GregTech_API; +import gregtech.api.interfaces.metatileentity.IMetaTileEntity; +import gregtech.api.interfaces.modularui.ControllerWithOptionalFeatures; + +public abstract class MultiBlockFeatureSupportDumper extends DataDumper { + + private final Function<ControllerWithOptionalFeatures, Boolean> isFeatureSupported; + + public MultiBlockFeatureSupportDumper(String name, + Function<ControllerWithOptionalFeatures, Boolean> isFeatureSupported) { + super("tools.dump.gt5u." + name); + this.isFeatureSupported = isFeatureSupported; + } + + @Override + public String[] header() { + return new String[] { "className" }; + } + + @Override + public Iterable<String[]> dump(int mode) { + List<String[]> list = new ArrayList<>(); + for (int i = 1; i < GregTech_API.METATILEENTITIES.length; i++) { + IMetaTileEntity mte = GregTech_API.METATILEENTITIES[i]; + if (!(mte instanceof ControllerWithOptionalFeatures controller)) continue; + if (!isFeatureSupported.apply(controller)) { + list.add( + new String[] { controller.getClass() + .getName() }); + } + } + return list; + } + + @Override + public int modeCount() { + return 1; + } +} diff --git a/src/main/java/gregtech/nei/dumper/RecipeLockingSupportDumper.java b/src/main/java/gregtech/nei/dumper/RecipeLockingSupportDumper.java new file mode 100644 index 0000000000..05deba6c39 --- /dev/null +++ b/src/main/java/gregtech/nei/dumper/RecipeLockingSupportDumper.java @@ -0,0 +1,10 @@ +package gregtech.nei.dumper; + +import gregtech.api.interfaces.modularui.ControllerWithOptionalFeatures; + +public class RecipeLockingSupportDumper extends MultiBlockFeatureSupportDumper { + + public RecipeLockingSupportDumper() { + super("recipe_locking", ControllerWithOptionalFeatures::supportsSingleRecipeLocking); + } +} diff --git a/src/main/java/gregtech/nei/dumper/VoidProtectionSupportDumper.java b/src/main/java/gregtech/nei/dumper/VoidProtectionSupportDumper.java new file mode 100644 index 0000000000..b48efb2cdc --- /dev/null +++ b/src/main/java/gregtech/nei/dumper/VoidProtectionSupportDumper.java @@ -0,0 +1,10 @@ +package gregtech.nei.dumper; + +import gregtech.api.interfaces.modularui.ControllerWithOptionalFeatures; + +public class VoidProtectionSupportDumper extends MultiBlockFeatureSupportDumper { + + public VoidProtectionSupportDumper() { + super("void_protection", ControllerWithOptionalFeatures::supportsVoidProtection); + } +} diff --git a/src/main/java/gregtech/nei/formatter/DefaultSpecialValueFormatter.java b/src/main/java/gregtech/nei/formatter/DefaultSpecialValueFormatter.java new file mode 100644 index 0000000000..1c4d486319 --- /dev/null +++ b/src/main/java/gregtech/nei/formatter/DefaultSpecialValueFormatter.java @@ -0,0 +1,36 @@ +package gregtech.nei.formatter; + +import static gregtech.api.util.GT_Utility.trans; + +import java.util.Collections; +import java.util.List; + +import javax.annotation.ParametersAreNonnullByDefault; + +import gregtech.GT_Mod; +import gregtech.api.util.MethodsReturnNonnullByDefault; +import gregtech.nei.RecipeDisplayInfo; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +public class DefaultSpecialValueFormatter implements INEISpecialInfoFormatter { + + public static DefaultSpecialValueFormatter INSTANCE = new DefaultSpecialValueFormatter(); + + @Override + public List<String> format(RecipeDisplayInfo recipeInfo) { + int specialValue = recipeInfo.recipe.mSpecialValue; + if (specialValue == -100 && GT_Mod.gregtechproxy.mLowGravProcessing) { + return Collections.singletonList(trans("159", "Needs Low Gravity")); + } else if (specialValue == -200 && GT_Mod.gregtechproxy.mEnableCleanroom) { + return Collections.singletonList(trans("160", "Needs Cleanroom")); + } else if (specialValue == -201) { + return Collections.singletonList(trans("206", "Scan for Assembly Line")); + } else if (specialValue == -300 && GT_Mod.gregtechproxy.mEnableCleanroom) { + return Collections.singletonList(trans("160.1", "Needs Cleanroom & LowGrav")); + } else if (specialValue == -400) { + return Collections.singletonList(trans("216", "Deprecated Recipe")); + } + return Collections.emptyList(); + } +} diff --git a/src/main/java/gregtech/nei/formatter/FuelSpecialValueFormatter.java b/src/main/java/gregtech/nei/formatter/FuelSpecialValueFormatter.java new file mode 100644 index 0000000000..dcfe2617dd --- /dev/null +++ b/src/main/java/gregtech/nei/formatter/FuelSpecialValueFormatter.java @@ -0,0 +1,27 @@ +package gregtech.nei.formatter; + +import java.util.Collections; +import java.util.List; + +import javax.annotation.ParametersAreNonnullByDefault; + +import net.minecraft.util.StatCollector; + +import gregtech.api.util.GT_Utility; +import gregtech.api.util.MethodsReturnNonnullByDefault; +import gregtech.nei.RecipeDisplayInfo; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +public class FuelSpecialValueFormatter implements INEISpecialInfoFormatter { + + public static FuelSpecialValueFormatter INSTANCE = new FuelSpecialValueFormatter(); + + @Override + public List<String> format(RecipeDisplayInfo recipeInfo) { + return Collections.singletonList( + StatCollector.translateToLocalFormatted( + "GT5U.nei.fuel", + GT_Utility.formatNumbers(recipeInfo.recipe.mSpecialValue * 1000L))); + } +} diff --git a/src/main/java/gregtech/nei/formatter/FusionSpecialValueFormatter.java b/src/main/java/gregtech/nei/formatter/FusionSpecialValueFormatter.java new file mode 100644 index 0000000000..77cd41b343 --- /dev/null +++ b/src/main/java/gregtech/nei/formatter/FusionSpecialValueFormatter.java @@ -0,0 +1,59 @@ +package gregtech.nei.formatter; + +import java.util.Collections; +import java.util.List; + +import javax.annotation.ParametersAreNonnullByDefault; + +import net.minecraft.util.StatCollector; + +import gregtech.api.enums.GT_Values; +import gregtech.api.util.GT_Utility; +import gregtech.api.util.MethodsReturnNonnullByDefault; +import gregtech.nei.RecipeDisplayInfo; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +public class FusionSpecialValueFormatter implements INEISpecialInfoFormatter { + + public static final FusionSpecialValueFormatter INSTANCE = new FusionSpecialValueFormatter(); + private static final long M = 1000000; + + @Override + public List<String> format(RecipeDisplayInfo recipeInfo) { + int euToStart = recipeInfo.recipe.mSpecialValue; + int voltage = recipeInfo.recipe.mEUt; + int tier = getFusionTier(euToStart, voltage); + + return Collections.singletonList( + StatCollector.translateToLocalFormatted("GT5U.nei.start_eu", GT_Utility.formatNumbers(euToStart), tier)); + } + + public static int getFusionTier(long startupPower, long voltage) { + int tier; + if (startupPower <= 10 * M * 16) { + tier = 1; + } else if (startupPower <= 20 * M * 16) { + tier = 2; + } else if (startupPower <= 40 * M * 16) { + tier = 3; + } else if (startupPower <= 320 * M * 16) { + tier = 4; + } else { + tier = 5; + } + + if (voltage <= GT_Values.V[6]) { + // no-op + } else if (voltage <= GT_Values.V[7]) { + tier = Math.max(tier, 2); + } else if (voltage <= GT_Values.V[8]) { + tier = Math.max(tier, 3); + } else if (voltage <= GT_Values.V[9]) { + tier = Math.max(tier, 4); + } else { + tier = 5; + } + return tier; + } +} diff --git a/src/main/java/gregtech/nei/formatter/HeatingCoilSpecialValueFormatter.java b/src/main/java/gregtech/nei/formatter/HeatingCoilSpecialValueFormatter.java new file mode 100644 index 0000000000..f5c17a1163 --- /dev/null +++ b/src/main/java/gregtech/nei/formatter/HeatingCoilSpecialValueFormatter.java @@ -0,0 +1,30 @@ +package gregtech.nei.formatter; + +import java.util.Collections; +import java.util.List; + +import javax.annotation.ParametersAreNonnullByDefault; + +import net.minecraft.util.StatCollector; + +import gregtech.api.enums.HeatingCoilLevel; +import gregtech.api.util.GT_Utility; +import gregtech.api.util.MethodsReturnNonnullByDefault; +import gregtech.nei.RecipeDisplayInfo; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +public class HeatingCoilSpecialValueFormatter implements INEISpecialInfoFormatter { + + public static final HeatingCoilSpecialValueFormatter INSTANCE = new HeatingCoilSpecialValueFormatter(); + + @Override + public List<String> format(RecipeDisplayInfo recipeInfo) { + int heat = recipeInfo.recipe.mSpecialValue; + return Collections.singletonList( + StatCollector.translateToLocalFormatted( + "GT5U.nei.heat_capacity", + GT_Utility.formatNumbers(heat), + HeatingCoilLevel.getDisplayNameFromHeat(heat, false))); + } +} diff --git a/src/main/java/gregtech/nei/formatter/INEISpecialInfoFormatter.java b/src/main/java/gregtech/nei/formatter/INEISpecialInfoFormatter.java new file mode 100644 index 0000000000..21228240d4 --- /dev/null +++ b/src/main/java/gregtech/nei/formatter/INEISpecialInfoFormatter.java @@ -0,0 +1,24 @@ +package gregtech.nei.formatter; + +import java.util.List; + +import javax.annotation.ParametersAreNonnullByDefault; + +import gregtech.api.util.MethodsReturnNonnullByDefault; +import gregtech.nei.RecipeDisplayInfo; + +/** + * Getter for description for {@link gregtech.api.util.GT_Recipe#mSpecialValue} etc. that will be drawn on NEI. + */ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@FunctionalInterface +public interface INEISpecialInfoFormatter { + + /** + * @param recipeInfo Recipe info to draw description. You can retrieve special value with + * {@code recipeInfo.recipe.mSpecialValue}. + * @return List of strings containing info for special value etc. + */ + List<String> format(RecipeDisplayInfo recipeInfo); +} diff --git a/src/main/java/gregtech/nei/formatter/SimpleSpecialValueFormatter.java b/src/main/java/gregtech/nei/formatter/SimpleSpecialValueFormatter.java new file mode 100644 index 0000000000..8f2098c0a9 --- /dev/null +++ b/src/main/java/gregtech/nei/formatter/SimpleSpecialValueFormatter.java @@ -0,0 +1,49 @@ +package gregtech.nei.formatter; + +import java.util.Collections; +import java.util.List; + +import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; + +import net.minecraft.util.StatCollector; + +import gregtech.api.util.GT_Utility; +import gregtech.api.util.MethodsReturnNonnullByDefault; +import gregtech.nei.RecipeDisplayInfo; + +/** + * Simple formatter for recipe's special value. + */ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +public class SimpleSpecialValueFormatter implements INEISpecialInfoFormatter { + + @Nullable + private final String translationKey; + private final int multiplier; + + /** + * @param translationKey Localization key to format + * @param multiplier Number to multiply to special value for display + */ + public SimpleSpecialValueFormatter(@Nullable String translationKey, int multiplier) { + this.translationKey = translationKey; + this.multiplier = multiplier; + } + + /** + * @param translationKey Localization key to format + */ + public SimpleSpecialValueFormatter(@Nullable String translationKey) { + this(translationKey, 1); + } + + @Override + public List<String> format(RecipeDisplayInfo recipeInfo) { + return Collections.singletonList( + StatCollector.translateToLocalFormatted( + translationKey, + GT_Utility.formatNumbers((long) recipeInfo.recipe.mSpecialValue * multiplier))); + } +} |