diff --git a/src/main/java/gregtech/api/recipes/RecipeMap.java b/src/main/java/gregtech/api/recipes/RecipeMap.java index a2c2ef7b1f4..2afd8edf6e1 100644 --- a/src/main/java/gregtech/api/recipes/RecipeMap.java +++ b/src/main/java/gregtech/api/recipes/RecipeMap.java @@ -99,7 +99,7 @@ public class RecipeMap> { public ChanceBoostFunction chanceFunction = DEFAULT_CHANCE_FUNCTION; public final String unlocalizedName; - + private boolean jeiOverclockButton = true; private final R recipeBuilderSample; private int maxInputs; private int maxOutputs; @@ -365,6 +365,15 @@ public RecipeMap> getSmallRecipeMap() { return smallRecipeMap; } + public RecipeMap disableJeiOverclockButton() { + this.jeiOverclockButton = false; + return this; + } + + public boolean jeiOverclockButtonEnabled() { + return this.jeiOverclockButton; + } + /** * Internal usage only, use {@link RecipeBuilder#buildAndRegister()} * diff --git a/src/main/java/gregtech/api/recipes/RecipeMapBuilder.java b/src/main/java/gregtech/api/recipes/RecipeMapBuilder.java index 24e52ad07ce..ad417eba1ed 100644 --- a/src/main/java/gregtech/api/recipes/RecipeMapBuilder.java +++ b/src/main/java/gregtech/api/recipes/RecipeMapBuilder.java @@ -33,6 +33,7 @@ public class RecipeMapBuilder> { private boolean modifyFluidInputs = true; private int fluidOutputs; private boolean modifyFluidOutputs = true; + private boolean jeiOverclockButton = true; private boolean isGenerator; @@ -265,6 +266,11 @@ public RecipeMapBuilder(@NotNull String unlocalizedName, @NotNull B defaultRecip return this; } + public @NotNull RecipeMapBuilder disableJeiOverclockButton() { + this.jeiOverclockButton = false; + return this; + } + /** * Add a recipe build action to be performed upon this RecipeMap's builder's recipe registration. * @@ -297,6 +303,9 @@ public RecipeMapBuilder(@NotNull String unlocalizedName, @NotNull B defaultRecip if (buildActions != null) { recipeMap.onRecipeBuild(buildActions); } + if (!jeiOverclockButton) { + recipeMap.disableJeiOverclockButton(); + } return recipeMap; } } diff --git a/src/main/java/gregtech/api/recipes/RecipeMaps.java b/src/main/java/gregtech/api/recipes/RecipeMaps.java index 414ead42aba..94453eab348 100644 --- a/src/main/java/gregtech/api/recipes/RecipeMaps.java +++ b/src/main/java/gregtech/api/recipes/RecipeMaps.java @@ -526,6 +526,7 @@ public final class RecipeMaps { .fluidOutputs(1) .ui(CokeOvenUI::new) .sound(GTSoundEvents.FIRE) + .disableJeiOverclockButton() .build(); /** @@ -1289,6 +1290,7 @@ public final class RecipeMaps { .modifyFluidInputs(false) .modifyFluidOutputs(false) .sound(GTSoundEvents.FIRE) + .disableJeiOverclockButton() .build(); /** @@ -1338,7 +1340,7 @@ public final class RecipeMaps { @ZenProperty public static final RecipeMap RESEARCH_STATION_RECIPES = new RecipeMapResearchStation<>( - "research_station", new ComputationRecipeBuilder(), ResearchStationUI::new); + "research_station", new ComputationRecipeBuilder(), ResearchStationUI::new).disableJeiOverclockButton(); @ZenProperty public static final RecipeMap ROCK_BREAKER_RECIPES = new RecipeMapBuilder<>("rock_breaker", @@ -1486,6 +1488,7 @@ public final class RecipeMaps { .sound(GTSoundEvents.COMBUSTION) .allowEmptyOutputs() .generator() + .disableJeiOverclockButton() .build(); @ZenProperty @@ -1497,6 +1500,7 @@ public final class RecipeMaps { .sound(GTSoundEvents.TURBINE) .allowEmptyOutputs() .generator() + .disableJeiOverclockButton() .build(); @ZenProperty @@ -1509,6 +1513,7 @@ public final class RecipeMaps { .sound(GTSoundEvents.TURBINE) .allowEmptyOutputs() .generator() + .disableJeiOverclockButton() .build(); @ZenProperty @@ -1520,6 +1525,7 @@ public final class RecipeMaps { .sound(GTSoundEvents.COMBUSTION) .allowEmptyOutputs() .generator() + .disableJeiOverclockButton() .build(); @ZenProperty @@ -1532,6 +1538,7 @@ public final class RecipeMaps { .sound(GTSoundEvents.TURBINE) .allowEmptyOutputs() .generator() + .disableJeiOverclockButton() .build(); private RecipeMaps() {} diff --git a/src/main/java/gregtech/integration/jei/basic/OreByProduct.java b/src/main/java/gregtech/integration/jei/basic/OreByProduct.java index 8e062d069c0..2deb2ac6ca1 100755 --- a/src/main/java/gregtech/integration/jei/basic/OreByProduct.java +++ b/src/main/java/gregtech/integration/jei/basic/OreByProduct.java @@ -1,5 +1,7 @@ package gregtech.integration.jei.basic; +import gregtech.api.GTValues; +import gregtech.api.GregTechAPI; import gregtech.api.recipes.chance.output.impl.ChancedItemOutput; import gregtech.api.unification.OreDictUnifier; import gregtech.api.unification.material.Material; @@ -10,12 +12,18 @@ import gregtech.api.unification.ore.OrePrefix; import gregtech.client.utils.TooltipHelper; import gregtech.common.metatileentities.MetaTileEntities; +import gregtech.integration.jei.utils.JeiInteractableText; +import net.minecraft.client.Minecraft; +import net.minecraft.client.audio.PositionedSoundRecord; +import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.resources.I18n; import net.minecraft.init.Blocks; import net.minecraft.init.Items; +import net.minecraft.init.SoundEvents; import net.minecraft.item.ItemStack; import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fml.client.config.GuiUtils; import net.minecraftforge.oredict.OreDictionary; import com.google.common.collect.ImmutableList; @@ -25,6 +33,7 @@ import mezz.jei.api.ingredients.VanillaTypes; import mezz.jei.api.recipe.IRecipeWrapper; import org.apache.commons.lang3.tuple.Pair; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; @@ -37,6 +46,8 @@ public class OreByProduct implements IRecipeWrapper { private static final int NUM_INPUTS = 21; + public final List jeiTexts = new ArrayList<>(); + public static void addOreByProductPrefix(OrePrefix orePrefix) { if (!ORES.contains(orePrefix)) { ORES.add(orePrefix); @@ -275,6 +286,33 @@ public OreByProduct(Material material) { } else { addEmptyOutputs(6); } + + // just here because if highTier is disabled, if a recipe is (incorrectly) registering + // UIV+ recipes, this allows it to go up to the recipe tier for that recipe only + int maxTier = GregTechAPI.isHighTier() ? GTValues.UIV : GTValues.MAX_TRUE; + // scuffed positioning because we can't have good ui(until mui soontm) + jeiTexts.add( + new JeiInteractableText(0, 160, GTValues.VOCNF[GTValues.LV], 0x111111, GTValues.LV, true) + .setTooltipBuilder((state, tooltip) -> { + tooltip.add(I18n.format("gregtech.jei.overclock_button", GTValues.VOCNF[state])); + tooltip.add(TooltipHelper.BLINKING_CYAN + I18n.format("gregtech.jei.overclock_warn")); + }) + .setClickAction((minecraft, text, mouseX, mouseY, mouseButton) -> { + int state = text.getState(); + if (mouseButton == 0) { + // increment tier if left click + if (++state > maxTier) state = GTValues.LV; + } else if (mouseButton == 1) { + // decrement tier if right click + if (--state < GTValues.LV) state = maxTier; + } else if (mouseButton == 2) { + // reset tier if middle click + state = GTValues.LV; + } else return false; + text.setCurrentText(GTValues.VOCNF[state]); + text.setState(state); + return true; + })); } @Override @@ -290,6 +328,18 @@ public void addTooltip(int slotIndex, boolean input, Object ingredient, List tooltip = new ArrayList<>(); + text.buildTooltip(tooltip); + if (tooltip.isEmpty()) continue; + int width = (int) (minecraft.displayWidth / 2f + recipeWidth / 2f); + GuiUtils.drawHoveringText(tooltip, mouseX, mouseY, width, minecraft.displayHeight, + Math.min(150, width - mouseX - 5), minecraft.fontRenderer); + GlStateManager.disableLighting(); + } + } + } + + @Override + public boolean handleClick(@NotNull Minecraft minecraft, int mouseX, int mouseY, int mouseButton) { + for (JeiInteractableText text : jeiTexts) { + if (text.isHovering(mouseX, mouseY) && + text.getTextClickAction().click(minecraft, text, mouseX, mouseY, mouseButton)) { + Minecraft.getMinecraft().getSoundHandler() + .playSound(PositionedSoundRecord.getMasterRecord(SoundEvents.UI_BUTTON_CLICK, 1.0F)); + return true; + } + } + return false; + } } diff --git a/src/main/java/gregtech/integration/jei/recipe/GTRecipeWrapper.java b/src/main/java/gregtech/integration/jei/recipe/GTRecipeWrapper.java index 4d88c1f1e5b..143b863e77f 100644 --- a/src/main/java/gregtech/integration/jei/recipe/GTRecipeWrapper.java +++ b/src/main/java/gregtech/integration/jei/recipe/GTRecipeWrapper.java @@ -1,6 +1,7 @@ package gregtech.integration.jei.recipe; import gregtech.api.GTValues; +import gregtech.api.GregTechAPI; import gregtech.api.gui.GuiTextures; import gregtech.api.gui.widgets.TankWidget; import gregtech.api.items.metaitem.MetaItem; @@ -29,6 +30,7 @@ import gregtech.integration.RecipeCompatUtil; import gregtech.integration.jei.utils.AdvancedRecipeWrapper; import gregtech.integration.jei.utils.JeiButton; +import gregtech.integration.jei.utils.JeiInteractableText; import net.minecraft.client.Minecraft; import net.minecraft.client.resources.I18n; @@ -54,7 +56,6 @@ public class GTRecipeWrapper extends AdvancedRecipeWrapper { private final RecipeMap recipeMap; private final Recipe recipe; - private final List sortedInputs; private final List sortedFluidInputs; @@ -66,6 +67,8 @@ public GTRecipeWrapper(RecipeMap recipeMap, Recipe recipe) { this.sortedInputs.sort(GTRecipeInput.RECIPE_INPUT_COMPARATOR); this.sortedFluidInputs = new ArrayList<>(recipe.getFluidInputs()); this.sortedFluidInputs.sort(GTRecipeInput.RECIPE_INPUT_COMPARATOR); + + initExtras(); } public Recipe getRecipe() { @@ -212,6 +215,18 @@ public void addIngredientTooltips(@NotNull Collection tooltip, boolean n tooltip.add(TooltipHelper.BLINKING_CYAN + I18n.format("gregtech.recipe.chance", chance, boost)); } + + // Add the total chance to the tooltip + if (recipeMap.jeiOverclockButtonEnabled()) { + int tier = jeiTexts.get(0).getState(); + int recipeTier = Math.max(GTValues.LV, GTUtility.getTierByVoltage(recipe.getEUt())); + int tierDifference = tier - recipeTier; + + // The total chance may or may not max out at 100%. + // TODO possibly change in the future. + double totalChance = Math.min(chance + boost * tierDifference, 100); + tooltip.add(I18n.format("gregtech.recipe.chance_total", GTValues.VOCNF[tier], totalChance)); + } } } else if (notConsumed) { tooltip.add(TooltipHelper.BLINKING_CYAN + I18n.format("gregtech.recipe.not_consumed")); @@ -255,34 +270,38 @@ public void drawInfo(@NotNull Minecraft minecraft, int recipeWidth, int recipeHe .count(); int yPosition = recipeHeight - ((unhiddenCount + defaultLines) * 10 - 3); + // [EUt, duration, color] + long[] overclockResult = calculateJeiOverclock(); + // Default entries if (drawTotalEU) { - long eu = recipe.getEUt() * recipe.getDuration(); // sadly we still need a custom override here, since computation uses duration and EU/t very differently - if (storage.contains(TotalComputationProperty.getInstance()) && - storage.contains(ComputationProperty.getInstance())) { - int minimumCWUt = storage.get(ComputationProperty.getInstance(), 1); + if (recipe.hasProperty(TotalComputationProperty.getInstance()) && + recipe.hasProperty(ComputationProperty.getInstance())) { + long eu = Math.abs(recipe.getEUt()) * recipe.getDuration(); + int minimumCWUt = recipe.getProperty(ComputationProperty.getInstance(), 1); minecraft.fontRenderer.drawString(I18n.format("gregtech.recipe.max_eu", eu / minimumCWUt), 0, yPosition, 0x111111); } else { - minecraft.fontRenderer.drawString(I18n.format("gregtech.recipe.total", eu), 0, yPosition, 0x111111); + minecraft.fontRenderer.drawString( + I18n.format("gregtech.recipe.total", overclockResult[0] * overclockResult[1]), 0, yPosition, + (int) overclockResult[2]); } } if (drawEUt) { + // scuffed way of dealing with 2 eu/t recipes, just recomputing instead of checking if eu/t <= 2 minecraft.fontRenderer.drawString( - I18n.format( - recipeMap.getRecipeMapUI().isGenerator() ? "gregtech.recipe.eu_inverted" : - "gregtech.recipe.eu", - recipe.getEUt(), GTValues.VN[GTUtility.getTierByVoltage(recipe.getEUt())]), - 0, yPosition += LINE_HEIGHT, 0x111111); + I18n.format(recipe.getEUt() >= 0 ? "gregtech.recipe.eu" : "gregtech.recipe.eu_inverted", + overclockResult[0], + GTValues.VOCNF[GTUtility.getOCTierByVoltage(overclockResult[0])]), + 0, yPosition += LINE_HEIGHT, (int) overclockResult[2]); } if (drawDuration) { minecraft.fontRenderer.drawString( I18n.format("gregtech.recipe.duration", - TextFormattingUtil.formatNumbers(recipe.getDuration() / 20.0)), - 0, yPosition += LINE_HEIGHT, 0x111111); + TextFormattingUtil.formatNumbers(overclockResult[1] / 20D)), + 0, yPosition += LINE_HEIGHT, (int) overclockResult[2]); } - // Property custom entries for (var propertyEntry : storage.entrySet()) { if (!propertyEntry.getKey().isHidden()) { @@ -348,6 +367,63 @@ public void initExtras() { LocalizationUtils.format("gregtech.jei.ct_recipe.tooltip"))) .setClickAction((mc, x, y, button) -> false) .setActiveSupplier(creativeTweaker)); + + if (recipeMap != null && recipeMap.jeiOverclockButtonEnabled()) { + int recipeTier = Math.max(GTValues.LV, GTUtility.getTierByVoltage(recipe.getEUt())); + // just here because if highTier is disabled, if a recipe is (incorrectly) registering + // UIV+ recipes, this allows it to go up to the recipe tier for that recipe only + int maxTier = Math.max(recipeTier, GregTechAPI.isHighTier() ? GTValues.UIV : GTValues.MAX_TRUE); + int minTier = Math.max(GTValues.LV, GTUtility.getTierByVoltage(recipe.getEUt())); + // scuffed positioning because we can't have good ui(until mui soontm) + jeiTexts.add( + new JeiInteractableText(0, 90 - LINE_HEIGHT, GTValues.VOCNF[recipeTier], 0x111111, recipeTier, true) + .setTooltipBuilder((state, tooltip) -> { + tooltip.add(I18n.format("gregtech.jei.overclock_button", GTValues.VOCNF[state])); + tooltip.add(TooltipHelper.BLINKING_CYAN + I18n.format("gregtech.jei.overclock_warn")); + }) + .setClickAction((minecraft, text, mouseX, mouseY, mouseButton) -> { + int state = text.getState(); + if (mouseButton == 0) { + // increment tier if left click + if (++state > maxTier) state = minTier; + } else if (mouseButton == 1) { + // decrement tier if right click + if (--state < minTier) state = maxTier; + } else if (mouseButton == 2) { + // reset tier if middle click + state = minTier; + } else return false; + text.setCurrentText(GTValues.VOCNF[state]); + text.setState(state); + return true; + })); + } + } + + public long[] calculateJeiOverclock() { + // simple case + if (!recipeMap.jeiOverclockButtonEnabled()) + return new long[] { recipe.getEUt(), recipe.getDuration(), 0x111111 }; + + // ULV doesn't overclock to LV, so treat ULV recipes as LV + int recipeTier = Math.max(GTValues.LV, GTUtility.getTierByVoltage(recipe.getEUt())); + // tier difference *should* not be negative here since at least displayOCTier() == recipeTier + int tierDifference = jeiTexts.get(0).getState() - recipeTier; + // there isn't any overclocking + if (tierDifference == 0) return new long[] { recipe.getEUt(), recipe.getDuration(), 0x111111 }; + + long[] result = new long[3]; + // if duration is less than 0.5, that means even with one less overclock, the recipe would still 1 tick + // so add the yellow warning + // LCR and fusion get manual overrides for now + double duration = Math.floor(recipe.getDuration() / + Math.pow(recipeMap == RecipeMaps.LARGE_CHEMICAL_RECIPES ? 4 : 2, tierDifference)); + result[2] = duration <= 0.5 ? 0xFFFF55 : 0x111111; + result[0] = Math.abs(recipe.getEUt()) * + (int) Math.pow(recipeMap == RecipeMaps.FUSION_RECIPES ? 2 : 4, tierDifference); + result[1] = Math.max(1, (int) duration); + + return result; } public ChancedItemOutput getOutputChance(int slot) { diff --git a/src/main/java/gregtech/integration/jei/utils/AdvancedRecipeWrapper.java b/src/main/java/gregtech/integration/jei/utils/AdvancedRecipeWrapper.java index 4cdf86c5c75..e0ce681ba97 100644 --- a/src/main/java/gregtech/integration/jei/utils/AdvancedRecipeWrapper.java +++ b/src/main/java/gregtech/integration/jei/utils/AdvancedRecipeWrapper.java @@ -16,6 +16,7 @@ public abstract class AdvancedRecipeWrapper implements IRecipeWrapper { protected final List buttons = new ArrayList<>(); + protected final List jeiTexts = new ArrayList<>(); public AdvancedRecipeWrapper() { initExtras(); @@ -41,6 +42,19 @@ public void drawInfo(@NotNull Minecraft minecraft, int recipeWidth, int recipeHe GlStateManager.disableLighting(); } } + + for (JeiInteractableText text : jeiTexts) { + text.render(minecraft, recipeWidth, recipeHeight, mouseX, mouseY); + if (text.isHovering(mouseX, mouseY)) { + List tooltip = new ArrayList<>(); + text.buildTooltip(tooltip); + if (tooltip.isEmpty()) continue; + int width = (int) (minecraft.displayWidth / 2f + recipeWidth / 2f); + GuiUtils.drawHoveringText(tooltip, mouseX, mouseY, width, minecraft.displayHeight, + Math.min(150, width - mouseX - 5), minecraft.fontRenderer); + GlStateManager.disableLighting(); + } + } } @Override @@ -53,6 +67,14 @@ public boolean handleClick(@NotNull Minecraft minecraft, int mouseX, int mouseY, return true; } } + for (JeiInteractableText text : jeiTexts) { + if (text.isHovering(mouseX, mouseY) && + text.getTextClickAction().click(minecraft, text, mouseX, mouseY, mouseButton)) { + Minecraft.getMinecraft().getSoundHandler() + .playSound(PositionedSoundRecord.getMasterRecord(SoundEvents.UI_BUTTON_CLICK, 1.0F)); + return true; + } + } return false; } } diff --git a/src/main/java/gregtech/integration/jei/utils/JeiInteractableText.java b/src/main/java/gregtech/integration/jei/utils/JeiInteractableText.java new file mode 100644 index 00000000000..333adfaec21 --- /dev/null +++ b/src/main/java/gregtech/integration/jei/utils/JeiInteractableText.java @@ -0,0 +1,107 @@ +package gregtech.integration.jei.utils; + +import net.minecraft.client.Minecraft; + +import java.util.List; +import java.util.function.BiConsumer; + +public class JeiInteractableText { + + private final int x; + private final int y; + private final boolean invertX; + private int color; + private String currentText; + private int textWidth; + private TextClickAction textClickAction; + private BiConsumer> tooltipBuilder; + private int state; + + /** + * Creates a new text object when can handle clicks and update state when clicked + * + * @param x x value, 0 on the left border, increases moving right. + * @param y x value, 0 on the top border, increases moving down. + * @param defaultText the text that should be initially displayed(without any clicks) + * @param color the default color of the text, overridden by in-text formatting codes + * @param baseState the default state of the button, it is used for tooltip and general information storage + * @param invertX instead defines x as the distance from the right border, + * this takes into account the text width, + * ensuring the rightmost part of the text is always aligned + */ + public JeiInteractableText(int x, int y, String defaultText, int color, int baseState, boolean invertX) { + this.x = x; + this.y = y; + this.invertX = invertX; + this.currentText = defaultText; + this.textWidth = Minecraft.getMinecraft().fontRenderer.getStringWidth(defaultText); + this.color = color; + this.state = baseState; + } + + public void render(Minecraft minecraft, int recipeWidth, int recipeHeight, int mouseX, int mouseY) { + minecraft.fontRenderer.drawString(currentText, invertX ? recipeWidth - x - textWidth : x, y, color); + } + + public JeiInteractableText setTooltipBuilder(BiConsumer> builder) { + this.tooltipBuilder = builder; + return this; + } + + public boolean isHovering(int mouseX, int mouseY) { + if (!(mouseY >= y && mouseY <= y + 10)) return false; + // seems like recipeWidth is always 176 + if (invertX) return 176 - textWidth - x <= mouseX && mouseX <= 176 - x; + return mouseX >= x && mouseX <= x + textWidth; + } + + public void setCurrentText(String text) { + this.currentText = text; + this.textWidth = Minecraft.getMinecraft().fontRenderer.getStringWidth(currentText); + } + + public String getCurrentText() { + return this.currentText; + } + + /** + * This is overriden by in-text formatting codes! + * + * @param color The color to set the text to + */ + public void setColor(int color) { + this.color = color; + } + + public int getColor() { + return this.color; + } + + public JeiInteractableText setClickAction(TextClickAction action) { + this.textClickAction = action; + return this; + } + + public TextClickAction getTextClickAction() { + return this.textClickAction; + } + + public void setState(int state) { + this.state = state; + } + + public int getState() { + return this.state; + } + + public void buildTooltip(List baseTooltip) { + if (tooltipBuilder == null) return; + tooltipBuilder.accept(this.state, baseTooltip); + } + + @FunctionalInterface + public interface TextClickAction { + + boolean click(Minecraft minecraft, JeiInteractableText text, int mouseX, int mouseY, int mouseButton); + } +} diff --git a/src/main/resources/assets/gregtech/lang/en_us.lang b/src/main/resources/assets/gregtech/lang/en_us.lang index 389d68cd3ce..a1126a52adc 100644 --- a/src/main/resources/assets/gregtech/lang/en_us.lang +++ b/src/main/resources/assets/gregtech/lang/en_us.lang @@ -5430,6 +5430,7 @@ gregtech.recipe.amperage=Amperage: %,d gregtech.recipe.not_consumed=Does not get consumed in the process gregtech.recipe.chance=Chance: %s%% +%s%%/tier gregtech.recipe.chance_logic=Chance: %s%% +%s%%/tier (%s) +gregtech.recipe.chance_total=At %s§r: %f%% gregtech.chance_logic.or=OR gregtech.chance_logic.and=AND gregtech.chance_logic.xor=XOR @@ -5566,6 +5567,9 @@ gregtech.jei.fluid.dep_chance_hover=The percentage chance for the vein to be dep gregtech.jei.fluid.dep_amount_hover=The amount the vein will be depleted by gregtech.jei.fluid.dep_yield_hover=The maximum yield of the vein when it is fully depleted +gregtech.jei.overclock_button=§rThe speed of the recipe at %s +gregtech.jei.overclock_warn=Results may not be fully accurate for some recipes! + gregtech.jei.materials.average_mass=Average mass: %,d gregtech.jei.materials.average_protons=Average protons: %,d gregtech.jei.materials.average_neutrons=Average neutrons: %,d