diff --git a/build.gradle b/build.gradle index 177833f1..288ea231 100644 --- a/build.gradle +++ b/build.gradle @@ -25,6 +25,8 @@ repositories { maven { url 'https://jitpack.io' } maven { url 'https://maven.terraformersmc.com/releases' } maven { url "https://ladysnake.jfrog.io/artifactory/mods" } + maven { url = "https://dvs1.progwml6.com/files/maven/" } + maven { url = "https://modmaven.dev" } flatDir { dirs 'libs' } @@ -50,6 +52,12 @@ dependencies { modCompileOnly "me.shedaniel:RoughlyEnoughItems-fabric:${project.rei_version}" modCompileOnly "me.shedaniel:RoughlyEnoughItems-api-fabric:${project.rei_version}" + // compile against the JEI API but do not include it at runtime + modCompileOnlyApi "mezz.jei:jei-${project.minecraft_version}-common-api:${project.jei_version}" + modCompileOnlyApi "mezz.jei:jei-${project.minecraft_version}-fabric-api:${project.jei_version}" + // at runtime, use the full JEI jar for Fabric + modRuntimeOnly "mezz.jei:jei-${project.minecraft_version}-fabric:${project.jei_version}" + //needed for trinkets, otherwise BetterEnd would require users to install trinkets modApi "dev.onyxstudios.cardinal-components-api:cardinal-components-base:${project.cca_version}" modCompileOnly "dev.emi:trinkets:${project.trinkets_version}" diff --git a/gradle.properties b/gradle.properties index 21f4efa7..83bbae92 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,5 +16,6 @@ archives_base_name=better-end patchouli_version=1.19-73-FABRIC bclib_version=2.0.17 rei_version=9.1.500 +jei_version=11.1.0.235 trinkets_version=3.4.0 cca_version=5.0.0-beta.1 diff --git a/src/main/java/org/betterx/betterend/blocks/entities/EndStoneSmelterBlockEntity.java b/src/main/java/org/betterx/betterend/blocks/entities/EndStoneSmelterBlockEntity.java index fbf77d0e..e4e4c197 100644 --- a/src/main/java/org/betterx/betterend/blocks/entities/EndStoneSmelterBlockEntity.java +++ b/src/main/java/org/betterx/betterend/blocks/entities/EndStoneSmelterBlockEntity.java @@ -275,7 +275,7 @@ public class EndStoneSmelterBlockEntity extends BaseContainerBlockEntity impleme } boolean accepted = blockEntity.canAcceptRecipeOutput(recipe); if (!burning && accepted) { - blockEntity.burnTime = blockEntity.getFuelTime(fuel); + blockEntity.burnTime = EndStoneSmelterBlockEntity.getFuelTime(fuel); blockEntity.fuelTime = blockEntity.burnTime; burning = blockEntity.isBurning(); if (burning) { @@ -419,7 +419,7 @@ public class EndStoneSmelterBlockEntity extends BaseContainerBlockEntity impleme return true; } - protected int getFuelTime(ItemStack fuel) { + public static int getFuelTime(ItemStack fuel) { if (fuel.isEmpty()) { return 0; } diff --git a/src/main/java/org/betterx/betterend/integration/jei/JEIAlloyingCategory.java b/src/main/java/org/betterx/betterend/integration/jei/JEIAlloyingCategory.java new file mode 100644 index 00000000..a16d12de --- /dev/null +++ b/src/main/java/org/betterx/betterend/integration/jei/JEIAlloyingCategory.java @@ -0,0 +1,171 @@ +package org.betterx.betterend.integration.jei; + +import org.betterx.betterend.BetterEnd; +import org.betterx.betterend.recipe.builders.AlloyingRecipe; +import org.betterx.betterend.registry.EndBlocks; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import mezz.jei.api.constants.ModIds; +import mezz.jei.api.constants.VanillaTypes; +import mezz.jei.api.gui.builder.IRecipeLayoutBuilder; +import mezz.jei.api.gui.drawable.IDrawable; +import mezz.jei.api.gui.drawable.IDrawableAnimated; +import mezz.jei.api.gui.drawable.IDrawableStatic; +import mezz.jei.api.gui.ingredient.IRecipeSlotsView; +import mezz.jei.api.helpers.IGuiHelper; +import mezz.jei.api.recipe.IFocusGroup; +import mezz.jei.api.recipe.RecipeIngredientRole; +import mezz.jei.api.recipe.RecipeType; +import mezz.jei.api.recipe.category.IRecipeCategory; + +public class JEIAlloyingCategory implements IRecipeCategory { + public static final RecipeType TYPE = RecipeType.create( + BetterEnd.MOD_ID, + AlloyingRecipe.GROUP, + AlloyingRecipe.class + ); + public static final String TEXTURE_GUI_PATH = "textures/gui/"; + public static final String TEXTURE_GUI_VANILLA = TEXTURE_GUI_PATH + "gui_vanilla.png"; + public static final ResourceLocation RECIPE_GUI_VANILLA = new ResourceLocation(ModIds.JEI_ID, TEXTURE_GUI_VANILLA); + public static final int width = 116; + public static final int height = 54; + + protected final IDrawableStatic staticFlame; + protected final IDrawableStatic addonSlot; + protected final IDrawableAnimated animatedFlame; + + private final IDrawable background; + private final IDrawable icon; + private final Component title; + private final LoadingCache cachedArrows; + + public JEIAlloyingCategory(IGuiHelper guiHelper) { + staticFlame = guiHelper.createDrawable(RECIPE_GUI_VANILLA, 82, 114, 14, 14); + animatedFlame = guiHelper.createAnimatedDrawable(staticFlame, 300, IDrawableAnimated.StartDirection.TOP, true); + background = guiHelper.createDrawable(RECIPE_GUI_VANILLA, 0, 114, 82, 54); + title = Component.translatable(EndBlocks.END_STONE_SMELTER.getDescriptionId()); + icon = guiHelper.createDrawableIngredient(VanillaTypes.ITEM_STACK, new ItemStack(EndBlocks.END_STONE_SMELTER)); + this.cachedArrows = CacheBuilder.newBuilder() + .maximumSize(25) + .build(new CacheLoader<>() { + @Override + public IDrawableAnimated load(Integer cookTime) { + return guiHelper.drawableBuilder( + RECIPE_GUI_VANILLA, + 82, + 128, + 24, + 17 + ) + .buildAnimated( + cookTime, + IDrawableAnimated.StartDirection.LEFT, + false + ); + } + }); + + addonSlot = guiHelper.getSlotDrawable(); + } + + protected IDrawableAnimated getArrow(AlloyingRecipe recipe) { + int cookTime = recipe.getSmeltTime(); + if (cookTime <= 0) { + cookTime = 0; + } + return this.cachedArrows.getUnchecked(cookTime); + } + + @Override + public RecipeType getRecipeType() { + return TYPE; + } + + @Override + public Component getTitle() { + return title; + } + + @Override + public IDrawable getBackground() { + return background; + } + + @Override + public IDrawable getIcon() { + return icon; + } + + @Override + public void setRecipe(IRecipeLayoutBuilder builder, AlloyingRecipe recipe, IFocusGroup focuses) { + builder.addSlot(RecipeIngredientRole.INPUT, 1, 1) + .addIngredients(recipe.getIngredients().get(0)); + + if (recipe.getIngredients().size() > 1) { + builder.addSlot(RecipeIngredientRole.INPUT, 21, 1) + .addIngredients(recipe.getIngredients().get(1)); + } + + builder.addSlot(RecipeIngredientRole.OUTPUT, 61, 19) + .addItemStack(recipe.getResultItem()); + } + + @Override + public boolean isHandled(AlloyingRecipe recipe) { + return !recipe.isSpecial(); + } + + @Override + public void draw( + AlloyingRecipe recipe, + IRecipeSlotsView recipeSlotsView, + PoseStack poseStack, + double mouseX, + double mouseY + ) { + animatedFlame.draw(poseStack, 1, 20); + + if (recipe.getIngredients().size() > 1) { + addonSlot.draw(poseStack, 20, 0); + } + + IDrawableAnimated arrow = getArrow(recipe); + arrow.draw(poseStack, 24, 18); + + drawExperience(recipe, poseStack, 0); + drawCookTime(recipe, poseStack, 45); + + } + + protected void drawExperience(AlloyingRecipe recipe, PoseStack poseStack, int y) { + float experience = recipe.getExperience(); + if (experience > 0) { + Component experienceString = Component.translatable("gui.jei.category.smelting.experience", experience); + Minecraft minecraft = Minecraft.getInstance(); + Font fontRenderer = minecraft.font; + int stringWidth = fontRenderer.width(experienceString); + fontRenderer.draw(poseStack, experienceString, background.getWidth() - stringWidth, y, 0xFF808080); + } + } + + protected void drawCookTime(AlloyingRecipe recipe, PoseStack poseStack, int y) { + int cookTime = recipe.getSmeltTime(); + if (cookTime > 0) { + int cookTimeSeconds = cookTime / 20; + Component timeString = Component.translatable("gui.jei.category.smelting.time.seconds", cookTimeSeconds); + Minecraft minecraft = Minecraft.getInstance(); + net.minecraft.client.gui.Font fontRenderer = minecraft.font; + int stringWidth = fontRenderer.width(timeString); + fontRenderer.draw(poseStack, timeString, background.getWidth() - stringWidth, y, 0xFF808080); + } + } +} diff --git a/src/main/java/org/betterx/betterend/integration/jei/JEIAlloyingFuelCategory.java b/src/main/java/org/betterx/betterend/integration/jei/JEIAlloyingFuelCategory.java new file mode 100644 index 00000000..f0cd9ca5 --- /dev/null +++ b/src/main/java/org/betterx/betterend/integration/jei/JEIAlloyingFuelCategory.java @@ -0,0 +1,183 @@ +package org.betterx.betterend.integration.jei; + +import org.betterx.betterend.BetterEnd; +import org.betterx.betterend.blocks.entities.EndStoneSmelterBlockEntity; +import org.betterx.betterend.recipe.builders.AlloyingRecipe; +import org.betterx.betterend.registry.EndBlocks; +import org.betterx.ui.layout.values.Rectangle; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.network.chat.Component; +import net.minecraft.world.item.ItemStack; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import mezz.jei.api.constants.VanillaTypes; +import mezz.jei.api.gui.builder.IRecipeLayoutBuilder; +import mezz.jei.api.gui.drawable.IDrawable; +import mezz.jei.api.gui.drawable.IDrawableAnimated; +import mezz.jei.api.gui.drawable.IDrawableStatic; +import mezz.jei.api.gui.ingredient.IRecipeSlotsView; +import mezz.jei.api.helpers.IGuiHelper; +import mezz.jei.api.recipe.IFocusGroup; +import mezz.jei.api.recipe.RecipeIngredientRole; +import mezz.jei.api.recipe.RecipeType; +import mezz.jei.api.recipe.category.IRecipeCategory; +import mezz.jei.api.recipe.vanilla.IJeiFuelingRecipe; +import mezz.jei.api.runtime.IIngredientManager; + +import java.text.NumberFormat; +import java.util.Comparator; +import java.util.List; +import org.jetbrains.annotations.Unmodifiable; + + +public class JEIAlloyingFuelCategory implements IRecipeCategory { + public static final RecipeType FUEL_TYPE = RecipeType.create( + BetterEnd.MOD_ID, + AlloyingRecipe.GROUP + "_fuel", + IJeiFuelingRecipe.class + ); + + private final IDrawableStatic background; + private final IDrawable icon; + private final Component localizedName; + private final LoadingCache cachedFlames; + private final Rectangle textArea; + + public static List getFuelRecipes(IIngredientManager ingredientManager) { + return ingredientManager.getAllIngredients(VanillaTypes.ITEM_STACK).stream() + .mapMulti((stack, consumer) -> { + if (EndStoneSmelterBlockEntity.canUseAsFuel(stack)) { + final int time = EndStoneSmelterBlockEntity.getFuelTime(stack); + if (time > 0) { + final List inputs = List.of(stack); + consumer.accept(new IJeiFuelingRecipe() { + @Override + public @Unmodifiable List getInputs() { + return inputs; + } + + @Override + public int getBurnTime() { + return time; + } + }); + } + } + + }) + .sorted(Comparator.comparingInt(IJeiFuelingRecipe::getBurnTime)) + .toList(); + } + + public JEIAlloyingFuelCategory(IGuiHelper guiHelper) { + + // width of the recipe depends on the text, which is different in each language + Minecraft minecraft = Minecraft.getInstance(); + Font fontRenderer = minecraft.font; + Component maxSmeltCountText = createSmeltCountText(10000000 * 200); + int maxStringWidth = fontRenderer.width(maxSmeltCountText.getString()); + int backgroundHeight = 34; + int textPadding = 20; + + background = guiHelper.drawableBuilder(JEIAlloyingCategory.RECIPE_GUI_VANILLA, 0, 134, 18, backgroundHeight) + .addPadding(0, 0, 0, textPadding + maxStringWidth) + .build(); + + textArea = new Rectangle(20, 0, textPadding + maxStringWidth, backgroundHeight); + + icon = guiHelper.createDrawableIngredient( + VanillaTypes.ITEM_STACK, + new ItemStack(EndBlocks.END_STONE_SMELTER) + ); + ; + localizedName = Component.translatable("gui.jei.category.fuel"); + + this.cachedFlames = CacheBuilder.newBuilder() + .maximumSize(25) + .build(new CacheLoader<>() { + @Override + public IDrawableAnimated load(Integer burnTime) { + return guiHelper.drawableBuilder( + JEIAlloyingCategory.RECIPE_GUI_VANILLA, + 82, + 114, + 14, + 14 + ) + .buildAnimated( + burnTime, + IDrawableAnimated.StartDirection.TOP, + true + ); + } + }); + } + + @Override + public IDrawable getBackground() { + return background; + } + + @Override + public RecipeType getRecipeType() { + return FUEL_TYPE; + } + + @Override + public Component getTitle() { + return localizedName; + } + + @Override + public IDrawable getIcon() { + return icon; + } + + @Override + public void setRecipe(IRecipeLayoutBuilder builder, IJeiFuelingRecipe recipe, IFocusGroup focuses) { + builder.addSlot(RecipeIngredientRole.INPUT, 1, 17) + .addItemStacks(recipe.getInputs()); + } + + @Override + public void draw( + IJeiFuelingRecipe recipe, + IRecipeSlotsView recipeSlotsView, + PoseStack poseStack, + double mouseX, + double mouseY + ) { + int burnTime = recipe.getBurnTime(); + IDrawableAnimated flame = cachedFlames.getUnchecked(burnTime); + flame.draw(poseStack, 1, 0); + Minecraft minecraft = Minecraft.getInstance(); + Font font = minecraft.font; + Component smeltCountText = createSmeltCountText(burnTime); + int width = font.width(smeltCountText); + int height = font.lineHeight; + + font.draw( + poseStack, + smeltCountText, + this.textArea.left + (this.textArea.width - width) / 2, + this.textArea.top + (this.textArea.height - height) / 2 + 1, + 0xFF808080 + ); + } + + private static Component createSmeltCountText(int burnTime) { + if (burnTime == 200) { + return Component.translatable("gui.jei.category.fuel.smeltCount.single"); + } else { + NumberFormat numberInstance = NumberFormat.getNumberInstance(); + numberInstance.setMaximumFractionDigits(2); + String smeltCount = numberInstance.format(burnTime / 200f); + return Component.translatable("gui.jei.category.fuel.smeltCount", smeltCount); + } + } +} diff --git a/src/main/java/org/betterx/betterend/integration/jei/JEIPlugin.java b/src/main/java/org/betterx/betterend/integration/jei/JEIPlugin.java new file mode 100644 index 00000000..a5c122e9 --- /dev/null +++ b/src/main/java/org/betterx/betterend/integration/jei/JEIPlugin.java @@ -0,0 +1,71 @@ +package org.betterx.betterend.integration.jei; + +import org.betterx.betterend.BetterEnd; +import org.betterx.betterend.recipe.builders.AlloyingRecipe; +import org.betterx.betterend.registry.EndBlocks; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; + +import mezz.jei.api.IModPlugin; +import mezz.jei.api.helpers.IGuiHelper; +import mezz.jei.api.helpers.IJeiHelpers; +import mezz.jei.api.registration.IRecipeCatalystRegistration; +import mezz.jei.api.registration.IRecipeCategoryRegistration; +import mezz.jei.api.registration.IRecipeRegistration; +import mezz.jei.api.registration.IRecipeTransferRegistration; +import mezz.jei.api.runtime.IIngredientManager; + +public class JEIPlugin implements IModPlugin { + public final static ResourceLocation PLUGIN_ID = BetterEnd.makeID("jei_plugin"); + + + @Override + public ResourceLocation getPluginUid() { + return PLUGIN_ID; + } + + @Override + public void registerCategories(IRecipeCategoryRegistration registration) { + IModPlugin.super.registerCategories(registration); + + IJeiHelpers jeiHelpers = registration.getJeiHelpers(); + IGuiHelper guiHelper = jeiHelpers.getGuiHelper(); + + registration.addRecipeCategories(new JEIAlloyingCategory(guiHelper)); + registration.addRecipeCategories(new JEIAlloyingFuelCategory(guiHelper)); + } + + @Override + public void registerRecipes(IRecipeRegistration registration) { + Minecraft minecraft = Minecraft.getInstance(); + ClientLevel world = minecraft.level; + var recipeManager = world.getRecipeManager(); + IIngredientManager ingredientManager = registration.getIngredientManager(); + + IModPlugin.super.registerRecipes(registration); + registration.addRecipes(JEIAlloyingCategory.TYPE, recipeManager.getAllRecipesFor(AlloyingRecipe.TYPE)); + registration.addRecipes( + JEIAlloyingFuelCategory.FUEL_TYPE, + JEIAlloyingFuelCategory.getFuelRecipes(ingredientManager) + ); + } + + @Override + public void registerRecipeCatalysts(IRecipeCatalystRegistration registration) { + IModPlugin.super.registerRecipeCatalysts(registration); + registration.addRecipeCatalyst( + new ItemStack(EndBlocks.END_STONE_SMELTER), + JEIAlloyingCategory.TYPE, + JEIAlloyingFuelCategory.FUEL_TYPE + ); + } + + @Override + public void registerRecipeTransferHandlers(IRecipeTransferRegistration registration) { + IModPlugin.super.registerRecipeTransferHandlers(registration); + + } +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 9d47cfd3..b4629ceb 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -30,6 +30,9 @@ ], "rei_client": [ "org.betterx.betterend.integration.rei.REIPlugin" + ], + "jei_mod_plugin": [ + "org.betterx.betterend.integration.jei.JEIPlugin" ] }, "accessWidener": "betterend.accesswidener",