From 14c113ff2c08ed2385405567286197759441ef75 Mon Sep 17 00:00:00 2001 From: Alexdoru <57050655+Alexdoru@users.noreply.github.com> Date: Thu, 14 Sep 2023 11:23:12 +0200 Subject: [PATCH 01/14] remove unused logger --- .../java/locusway/overloadedarmorbar/OverloadedArmorBar.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/locusway/overloadedarmorbar/OverloadedArmorBar.java b/src/main/java/locusway/overloadedarmorbar/OverloadedArmorBar.java index 9716af8..7b0e457 100644 --- a/src/main/java/locusway/overloadedarmorbar/OverloadedArmorBar.java +++ b/src/main/java/locusway/overloadedarmorbar/OverloadedArmorBar.java @@ -19,8 +19,6 @@ public class OverloadedArmorBar { public static final String VERSION = "GRADLETOKEN_VERSION"; public static final String GUI_FACTORY_CLASS = "locusway.overloadedarmorbar.client.gui.GuiFactory"; - public static org.apache.logging.log4j.Logger logger; - @SidedProxy( modId = MODID, clientSide = "locusway.overloadedarmorbar.proxy.ClientProxy", @@ -29,7 +27,6 @@ public class OverloadedArmorBar { @Mod.EventHandler public void preInit(FMLPreInitializationEvent event) { - logger = event.getModLog(); proxy.registerEvents(); String configDir = event.getModConfigurationDirectory().toString(); ConfigurationHandler.init(configDir); From 08d1cf82492fc9f0c2fa7ac84f0efda7378ebf5a Mon Sep 17 00:00:00 2001 From: Alexdoru <57050655+Alexdoru@users.noreply.github.com> Date: Thu, 14 Sep 2023 11:24:46 +0200 Subject: [PATCH 02/14] remove static instance of the renderer and make renderer extend Gui to avoid copying vanilla code --- ...ventHandler.java => ArmorBarRenderer.java} | 56 +++++++------------ .../overloadedarmorbar/proxy/ClientProxy.java | 4 +- 2 files changed, 23 insertions(+), 37 deletions(-) rename src/main/java/locusway/overloadedarmorbar/overlay/{OverlayEventHandler.java => ArmorBarRenderer.java} (73%) diff --git a/src/main/java/locusway/overloadedarmorbar/overlay/OverlayEventHandler.java b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java similarity index 73% rename from src/main/java/locusway/overloadedarmorbar/overlay/OverlayEventHandler.java rename to src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java index be6d79a..53ffb9d 100644 --- a/src/main/java/locusway/overloadedarmorbar/overlay/OverlayEventHandler.java +++ b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java @@ -1,11 +1,11 @@ package locusway.overloadedarmorbar.overlay; -import static locusway.overloadedarmorbar.ConfigurationHandler.alwaysShowArmorBar; +import static net.minecraftforge.client.event.RenderGameOverlayEvent.ElementType.ARMOR; import locusway.overloadedarmorbar.ConfigurationHandler; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.gui.Gui; import net.minecraft.entity.SharedMonsterAttributes; import net.minecraft.entity.ai.attributes.IAttributeInstance; import net.minecraft.entity.player.EntityPlayer; @@ -15,48 +15,32 @@ import org.lwjgl.opengl.GL11; +import cpw.mods.fml.common.eventhandler.EventPriority; import cpw.mods.fml.common.eventhandler.SubscribeEvent; -/* +/** * Class which handles the render event and hides the vanilla armor bar */ -public class OverlayEventHandler { +public class ArmorBarRenderer extends Gui { - public static void drawTexturedModalRect(int x, int y, int textureX, int textureY, int width, int height) { - Minecraft.getMinecraft().ingameGUI.drawTexturedModalRect(x, y, textureX, textureY, width, height); - } - - public static final OverlayEventHandler INSTANCE = new OverlayEventHandler(); - - private OverlayEventHandler() {} - - // Class handles the drawing of the armor bar private final static int UNKNOWN_ARMOR_VALUE = -1; - private int previousArmorValue = UNKNOWN_ARMOR_VALUE; private final static int ARMOR_ICON_SIZE = 9; private final static int ARMOR_SECOND_HALF_ICON_SIZE = 4; + private int previousArmorValue = UNKNOWN_ARMOR_VALUE; private final Minecraft mc = Minecraft.getMinecraft(); private ArmorIcon[] armorIcons; - @SubscribeEvent(receiveCanceled = true) - public void onRenderGameOverlayEventPre(RenderGameOverlayEvent event) { - if (event.type != RenderGameOverlayEvent.ElementType.ARMOR) return; - ScaledResolution scale = event.resolution; - int scaledWidth = scale.getScaledWidth(); - int scaledHeight = scale.getScaledHeight(); - /* Don't render the vanilla armor bar */ + @SubscribeEvent(priority = EventPriority.HIGHEST, receiveCanceled = true) + public void onRenderGameOverlayEvent(RenderGameOverlayEvent.Pre event) { + if (event.type != ARMOR) return; + renderArmorBar(event.resolution.getScaledWidth(), event.resolution.getScaledHeight()); event.setCanceled(true); - INSTANCE.renderArmorBar(scaledWidth, scaledHeight); - } - - private int calculateArmorValue() { - return ForgeHooks.getTotalArmorValue(mc.thePlayer); } public void renderArmorBar(int screenWidth, int screenHeight) { EntityPlayer player = mc.thePlayer; - int currentArmorValue = calculateArmorValue(); + int currentArmorValue = ForgeHooks.getTotalArmorValue(mc.thePlayer); int xStart = screenWidth / 2 - 91; int yStart = screenHeight - 39; @@ -96,11 +80,12 @@ public void renderArmorBar(int screenWidth, int screenHeight) { GL11.glColor4f(color.Red, color.Green, color.Blue, color.Alpha); if (currentArmorValue > 20) { // Draw the full icon as we have wrapped - drawTexturedModalRect(xPosition, yPosition, 34, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); + this.drawTexturedModalRect(xPosition, yPosition, 34, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); } else { - if (ConfigurationHandler.showEmptyArmorIcons && (alwaysShowArmorBar || currentArmorValue > 0)) { + if (ConfigurationHandler.showEmptyArmorIcons + && (ConfigurationHandler.alwaysShowArmorBar || currentArmorValue > 0)) { // Draw the empty armor icon - drawTexturedModalRect(xPosition, yPosition, 16, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); + this.drawTexturedModalRect(xPosition, yPosition, 16, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); } } break; @@ -109,7 +94,7 @@ public void renderArmorBar(int screenWidth, int screenHeight) { ArmorIconColor secondHalfColor = icon.secondaryArmorIconColor; GL11.glColor4f(firstHalfColor.Red, firstHalfColor.Green, firstHalfColor.Blue, firstHalfColor.Alpha); - drawTexturedModalRect(xPosition, yPosition, 25, 9, 5, ARMOR_ICON_SIZE); + this.drawTexturedModalRect(xPosition, yPosition, 25, 9, 5, ARMOR_ICON_SIZE); GL11.glColor4f( secondHalfColor.Red, @@ -118,7 +103,7 @@ public void renderArmorBar(int screenWidth, int screenHeight) { secondHalfColor.Alpha); if (currentArmorValue > 20) { // Draw the second half as full as we have wrapped - drawTexturedModalRect( + this.drawTexturedModalRect( xPosition + 5, yPosition, 39, @@ -127,7 +112,7 @@ public void renderArmorBar(int screenWidth, int screenHeight) { ARMOR_ICON_SIZE); } else { // Draw the second half as empty - drawTexturedModalRect( + this.drawTexturedModalRect( xPosition + 5, yPosition, 30, @@ -139,7 +124,7 @@ public void renderArmorBar(int screenWidth, int screenHeight) { case FULL: ArmorIconColor fullColor = icon.primaryArmorIconColor; GL11.glColor4f(fullColor.Red, fullColor.Green, fullColor.Blue, fullColor.Alpha); - drawTexturedModalRect(xPosition, yPosition, 34, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); + this.drawTexturedModalRect(xPosition, yPosition, 34, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); break; default: break; @@ -153,6 +138,7 @@ public void renderArmorBar(int screenWidth, int screenHeight) { public void forceUpdate() { // Setting to unknown value will cause a refresh next render - INSTANCE.previousArmorValue = UNKNOWN_ARMOR_VALUE; + previousArmorValue = UNKNOWN_ARMOR_VALUE; } + } diff --git a/src/main/java/locusway/overloadedarmorbar/proxy/ClientProxy.java b/src/main/java/locusway/overloadedarmorbar/proxy/ClientProxy.java index 50e2dea..d7c1a90 100644 --- a/src/main/java/locusway/overloadedarmorbar/proxy/ClientProxy.java +++ b/src/main/java/locusway/overloadedarmorbar/proxy/ClientProxy.java @@ -1,6 +1,6 @@ package locusway.overloadedarmorbar.proxy; -import locusway.overloadedarmorbar.overlay.OverlayEventHandler; +import locusway.overloadedarmorbar.overlay.ArmorBarRenderer; import net.minecraftforge.common.MinecraftForge; @@ -8,6 +8,6 @@ public class ClientProxy extends CommonProxy { @Override public void registerEvents() { - MinecraftForge.EVENT_BUS.register(OverlayEventHandler.INSTANCE); + MinecraftForge.EVENT_BUS.register(new ArmorBarRenderer()); } } From 958522b45f2251115dd927bfb7110a9d3776fb96 Mon Sep 17 00:00:00 2001 From: Alexdoru <57050655+Alexdoru@users.noreply.github.com> Date: Thu, 14 Sep 2023 11:54:46 +0200 Subject: [PATCH 03/14] add missing calls that are present in the vanilla method that this mod overrides --- .../overlay/ArmorBarRenderer.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java index 53ffb9d..ab359a4 100644 --- a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java +++ b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java @@ -10,6 +10,7 @@ import net.minecraft.entity.ai.attributes.IAttributeInstance; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.util.MathHelper; +import net.minecraftforge.client.GuiIngameForge; import net.minecraftforge.client.event.RenderGameOverlayEvent; import net.minecraftforge.common.ForgeHooks; @@ -33,16 +34,15 @@ public class ArmorBarRenderer extends Gui { @SubscribeEvent(priority = EventPriority.HIGHEST, receiveCanceled = true) public void onRenderGameOverlayEvent(RenderGameOverlayEvent.Pre event) { + if (event.type != ARMOR) return; - renderArmorBar(event.resolution.getScaledWidth(), event.resolution.getScaledHeight()); - event.setCanceled(true); - } + mc.mcProfiler.startSection("armor"); + GL11.glEnable(GL11.GL_BLEND); - public void renderArmorBar(int screenWidth, int screenHeight) { EntityPlayer player = mc.thePlayer; - int currentArmorValue = ForgeHooks.getTotalArmorValue(mc.thePlayer); - int xStart = screenWidth / 2 - 91; - int yStart = screenHeight - 39; + int currentArmorValue = ForgeHooks.getTotalArmorValue(player); + int xStart = event.resolution.getScaledWidth() / 2 - 91; + int yStart = event.resolution.getScaledHeight() - 39; IAttributeInstance playerHealthAttribute = player.getEntityAttribute(SharedMonsterAttributes.maxHealth); float playerHealth = (float) playerHealthAttribute.getAttributeValue(); @@ -67,10 +67,6 @@ public void renderArmorBar(int screenWidth, int screenHeight) { previousArmorValue = currentArmorValue; } - // Push to avoid lasting changes - GL11.glPushMatrix(); - GL11.glEnable(3042); - int armorIconCounter = 0; for (ArmorIcon icon : armorIcons) { int xPosition = xStart + armorIconCounter * 8; @@ -132,8 +128,12 @@ public void renderArmorBar(int screenWidth, int screenHeight) { armorIconCounter++; } // Revert our state back - GL11.glColor4f(1, 1, 1, 1); - GL11.glPopMatrix(); + GL11.glColor4f(1F, 1F, 1F, 1F); + GuiIngameForge.left_height += 10; + GL11.glDisable(GL11.GL_BLEND); + mc.mcProfiler.endSection(); + event.setCanceled(true); + } public void forceUpdate() { From 9bfa26ed97bdca3975785de81f4d9f9319aea218 Mon Sep 17 00:00:00 2001 From: Alexdoru <57050655+Alexdoru@users.noreply.github.com> Date: Thu, 14 Sep 2023 12:03:43 +0200 Subject: [PATCH 04/14] use the GuiIngameForge.left_height field to place the armor render properly instead of computing the health bar size manually --- .../ConfigurationHandler.java | 6 --- .../overlay/ArmorBarRenderer.java | 40 +++++-------------- 2 files changed, 11 insertions(+), 35 deletions(-) diff --git a/src/main/java/locusway/overloadedarmorbar/ConfigurationHandler.java b/src/main/java/locusway/overloadedarmorbar/ConfigurationHandler.java index 1866329..30f988f 100644 --- a/src/main/java/locusway/overloadedarmorbar/ConfigurationHandler.java +++ b/src/main/java/locusway/overloadedarmorbar/ConfigurationHandler.java @@ -14,7 +14,6 @@ public class ConfigurationHandler { "#7F00FF" }; public static boolean alwaysShowArmorBar = false; public static boolean showEmptyArmorIcons = false; - public static boolean offset = false; public static void init(String configDir) { if (config == null) { @@ -32,11 +31,6 @@ private static void loadConfiguration() { Configuration.CATEGORY_GENERAL, new String[] { "#FFFFFF", "#FF5500", "#FFC747", "#27FFE3", "#00FF00", "#7F00FF" }, "Colors must be specified in #RRGGBB format"); - offset = config.getBoolean( - "Override for Armor shift", - Configuration.CATEGORY_GENERAL, - false, - "Set to true if the armor bar display's incorrectly"); showEmptyArmorIcons = config .getBoolean("Show empty armor icons?", Configuration.CATEGORY_GENERAL, false, "Show empty armor icons"); diff --git a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java index ab359a4..b5753f3 100644 --- a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java +++ b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java @@ -6,10 +6,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Gui; -import net.minecraft.entity.SharedMonsterAttributes; -import net.minecraft.entity.ai.attributes.IAttributeInstance; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.util.MathHelper; import net.minecraftforge.client.GuiIngameForge; import net.minecraftforge.client.event.RenderGameOverlayEvent; import net.minecraftforge.common.ForgeHooks; @@ -39,25 +36,10 @@ public void onRenderGameOverlayEvent(RenderGameOverlayEvent.Pre event) { mc.mcProfiler.startSection("armor"); GL11.glEnable(GL11.GL_BLEND); - EntityPlayer player = mc.thePlayer; - int currentArmorValue = ForgeHooks.getTotalArmorValue(player); - int xStart = event.resolution.getScaledWidth() / 2 - 91; - int yStart = event.resolution.getScaledHeight() - 39; - - IAttributeInstance playerHealthAttribute = player.getEntityAttribute(SharedMonsterAttributes.maxHealth); - float playerHealth = (float) playerHealthAttribute.getAttributeValue(); - - // Fake that the player health only goes up to 20 so that it does not make the bar float above the health bar - if (!ConfigurationHandler.offset && playerHealth > 20) playerHealth = 20; - - float absorptionAmount = MathHelper.ceiling_float_int(player.getAbsorptionAmount()); - - // Clamp the absorption value to 20 so that it doesn't make the bar float above the health bar - if (!ConfigurationHandler.offset && absorptionAmount > 20) absorptionAmount = 20; - - int numberOfHealthBars = (int) Math.ceil(playerHealth / 20) + (int) Math.ceil(absorptionAmount / 20); - int i2 = Math.max(10 - (numberOfHealthBars - 2), 3); - int yPosition = yStart - (numberOfHealthBars - 1) * i2 - 10; + final EntityPlayer player = mc.thePlayer; + final int currentArmorValue = ForgeHooks.getTotalArmorValue(player); + final int left = event.resolution.getScaledWidth() / 2 - 91; + final int top = event.resolution.getScaledHeight() - GuiIngameForge.left_height; // Save some CPU cycles by only recalculating armor when it changes if (currentArmorValue != previousArmorValue) { @@ -69,19 +51,19 @@ public void onRenderGameOverlayEvent(RenderGameOverlayEvent.Pre event) { int armorIconCounter = 0; for (ArmorIcon icon : armorIcons) { - int xPosition = xStart + armorIconCounter * 8; + int xPosition = left + armorIconCounter * 8; switch (icon.armorIconType) { case NONE: ArmorIconColor color = icon.primaryArmorIconColor; GL11.glColor4f(color.Red, color.Green, color.Blue, color.Alpha); if (currentArmorValue > 20) { // Draw the full icon as we have wrapped - this.drawTexturedModalRect(xPosition, yPosition, 34, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); + this.drawTexturedModalRect(xPosition, top, 34, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); } else { if (ConfigurationHandler.showEmptyArmorIcons && (ConfigurationHandler.alwaysShowArmorBar || currentArmorValue > 0)) { // Draw the empty armor icon - this.drawTexturedModalRect(xPosition, yPosition, 16, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); + this.drawTexturedModalRect(xPosition, top, 16, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); } } break; @@ -90,7 +72,7 @@ public void onRenderGameOverlayEvent(RenderGameOverlayEvent.Pre event) { ArmorIconColor secondHalfColor = icon.secondaryArmorIconColor; GL11.glColor4f(firstHalfColor.Red, firstHalfColor.Green, firstHalfColor.Blue, firstHalfColor.Alpha); - this.drawTexturedModalRect(xPosition, yPosition, 25, 9, 5, ARMOR_ICON_SIZE); + this.drawTexturedModalRect(xPosition, top, 25, 9, 5, ARMOR_ICON_SIZE); GL11.glColor4f( secondHalfColor.Red, @@ -101,7 +83,7 @@ public void onRenderGameOverlayEvent(RenderGameOverlayEvent.Pre event) { // Draw the second half as full as we have wrapped this.drawTexturedModalRect( xPosition + 5, - yPosition, + top, 39, 9, ARMOR_SECOND_HALF_ICON_SIZE, @@ -110,7 +92,7 @@ public void onRenderGameOverlayEvent(RenderGameOverlayEvent.Pre event) { // Draw the second half as empty this.drawTexturedModalRect( xPosition + 5, - yPosition, + top, 30, 9, ARMOR_SECOND_HALF_ICON_SIZE, @@ -120,7 +102,7 @@ public void onRenderGameOverlayEvent(RenderGameOverlayEvent.Pre event) { case FULL: ArmorIconColor fullColor = icon.primaryArmorIconColor; GL11.glColor4f(fullColor.Red, fullColor.Green, fullColor.Blue, fullColor.Alpha); - this.drawTexturedModalRect(xPosition, yPosition, 34, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); + this.drawTexturedModalRect(xPosition, top, 34, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); break; default: break; From 75a06a36101115366e6d474b473c95783c6335b2 Mon Sep 17 00:00:00 2001 From: Alexdoru <57050655+Alexdoru@users.noreply.github.com> Date: Thu, 14 Sep 2023 12:27:02 +0200 Subject: [PATCH 05/14] rewrite color parsing in ArmorIconColor --- .../overlay/ArmorBarRenderer.java | 14 +++++----- .../overlay/ArmorIconColor.java | 27 ++++++++++--------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java index b5753f3..578668e 100644 --- a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java +++ b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java @@ -55,7 +55,7 @@ public void onRenderGameOverlayEvent(RenderGameOverlayEvent.Pre event) { switch (icon.armorIconType) { case NONE: ArmorIconColor color = icon.primaryArmorIconColor; - GL11.glColor4f(color.Red, color.Green, color.Blue, color.Alpha); + GL11.glColor4f(color.red, color.green, color.blue, color.alpha); if (currentArmorValue > 20) { // Draw the full icon as we have wrapped this.drawTexturedModalRect(xPosition, top, 34, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); @@ -71,14 +71,14 @@ public void onRenderGameOverlayEvent(RenderGameOverlayEvent.Pre event) { ArmorIconColor firstHalfColor = icon.primaryArmorIconColor; ArmorIconColor secondHalfColor = icon.secondaryArmorIconColor; - GL11.glColor4f(firstHalfColor.Red, firstHalfColor.Green, firstHalfColor.Blue, firstHalfColor.Alpha); + GL11.glColor4f(firstHalfColor.red, firstHalfColor.green, firstHalfColor.blue, firstHalfColor.alpha); this.drawTexturedModalRect(xPosition, top, 25, 9, 5, ARMOR_ICON_SIZE); GL11.glColor4f( - secondHalfColor.Red, - secondHalfColor.Green, - secondHalfColor.Blue, - secondHalfColor.Alpha); + secondHalfColor.red, + secondHalfColor.green, + secondHalfColor.blue, + secondHalfColor.alpha); if (currentArmorValue > 20) { // Draw the second half as full as we have wrapped this.drawTexturedModalRect( @@ -101,7 +101,7 @@ public void onRenderGameOverlayEvent(RenderGameOverlayEvent.Pre event) { break; case FULL: ArmorIconColor fullColor = icon.primaryArmorIconColor; - GL11.glColor4f(fullColor.Red, fullColor.Green, fullColor.Blue, fullColor.Alpha); + GL11.glColor4f(fullColor.red, fullColor.green, fullColor.blue, fullColor.alpha); this.drawTexturedModalRect(xPosition, top, 34, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); break; default: diff --git a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorIconColor.java b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorIconColor.java index a4a10bf..5bc8a0f 100644 --- a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorIconColor.java +++ b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorIconColor.java @@ -8,26 +8,29 @@ */ public class ArmorIconColor { - public float Red; - public float Blue; - public float Green; - public final float Alpha; - public static final Pattern pattern = Pattern.compile("^#[0-9A-Fa-f]{6}$"); + private static final Pattern pattern = Pattern.compile("^#[0-9A-Fa-f]{6}$"); + + public float red; + public float blue; + public float green; + public final float alpha; public ArmorIconColor() { - Red = Blue = Green = Alpha = 1.0f; + red = blue = green = alpha = 1.0f; } - /* - * Convert from #RRGGBB format. If string is not in correct format this function will set the color to black. + /** + * Converts from #RRGGBB format. If string is not in correct format this function will set the color to white. */ public void setColorFromHex(String colorHex) { - // Check the color hex is valid otherwise default to white Matcher matcher = pattern.matcher(colorHex); if (matcher.matches()) { - Red = Integer.valueOf(colorHex.substring(1, 3), 16).floatValue() / 255; - Green = Integer.valueOf(colorHex.substring(3, 5), 16).floatValue() / 255; - Blue = Integer.valueOf(colorHex.substring(5, 7), 16).floatValue() / 255; + final int color = Integer.parseInt(colorHex.substring(1, 7), 16); + red = (float) (color >> 16 & 0xFF) / 255F; + green = (float) (color >> 8 & 0xFF) / 255F; + blue = (float) (color & 0xFF) / 255F; + } else { + red = green = blue = 1F; } } } From d1ae6ae2185bc1f26f5614268c01ed74875cdf1b Mon Sep 17 00:00:00 2001 From: Alexdoru <57050655+Alexdoru@users.noreply.github.com> Date: Thu, 14 Sep 2023 12:43:56 +0200 Subject: [PATCH 06/14] use suggested configuration file --- .../locusway/overloadedarmorbar/ConfigurationHandler.java | 5 ++--- .../locusway/overloadedarmorbar/OverloadedArmorBar.java | 4 ++-- .../overloadedarmorbar/client/gui/GuiFactory.java | 1 + .../overloadedarmorbar/client/gui/ModGUIConfig.java | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/locusway/overloadedarmorbar/ConfigurationHandler.java b/src/main/java/locusway/overloadedarmorbar/ConfigurationHandler.java index 30f988f..9658c10 100644 --- a/src/main/java/locusway/overloadedarmorbar/ConfigurationHandler.java +++ b/src/main/java/locusway/overloadedarmorbar/ConfigurationHandler.java @@ -15,10 +15,9 @@ public class ConfigurationHandler { public static boolean alwaysShowArmorBar = false; public static boolean showEmptyArmorIcons = false; - public static void init(String configDir) { + public static void init(File configDir) { if (config == null) { - File path = new File(configDir + "/" + OverloadedArmorBar.MODID + ".cfg"); - config = new Configuration(path); + config = new Configuration(configDir); loadConfiguration(); } } diff --git a/src/main/java/locusway/overloadedarmorbar/OverloadedArmorBar.java b/src/main/java/locusway/overloadedarmorbar/OverloadedArmorBar.java index 7b0e457..bacb868 100644 --- a/src/main/java/locusway/overloadedarmorbar/OverloadedArmorBar.java +++ b/src/main/java/locusway/overloadedarmorbar/OverloadedArmorBar.java @@ -28,8 +28,8 @@ public class OverloadedArmorBar { @Mod.EventHandler public void preInit(FMLPreInitializationEvent event) { proxy.registerEvents(); - String configDir = event.getModConfigurationDirectory().toString(); - ConfigurationHandler.init(configDir); + ConfigurationHandler.init(event.getSuggestedConfigurationFile()); FMLCommonHandler.instance().bus().register(new ConfigurationHandler()); } + } diff --git a/src/main/java/locusway/overloadedarmorbar/client/gui/GuiFactory.java b/src/main/java/locusway/overloadedarmorbar/client/gui/GuiFactory.java index da446d4..a4593e7 100644 --- a/src/main/java/locusway/overloadedarmorbar/client/gui/GuiFactory.java +++ b/src/main/java/locusway/overloadedarmorbar/client/gui/GuiFactory.java @@ -7,6 +7,7 @@ import cpw.mods.fml.client.IModGuiFactory; +@SuppressWarnings("unused") public class GuiFactory implements IModGuiFactory { @Override diff --git a/src/main/java/locusway/overloadedarmorbar/client/gui/ModGUIConfig.java b/src/main/java/locusway/overloadedarmorbar/client/gui/ModGUIConfig.java index 1bb9bbf..9366399 100644 --- a/src/main/java/locusway/overloadedarmorbar/client/gui/ModGUIConfig.java +++ b/src/main/java/locusway/overloadedarmorbar/client/gui/ModGUIConfig.java @@ -1,7 +1,6 @@ package locusway.overloadedarmorbar.client.gui; -import static locusway.overloadedarmorbar.ConfigurationHandler.getConfig; - +import locusway.overloadedarmorbar.ConfigurationHandler; import locusway.overloadedarmorbar.OverloadedArmorBar; import net.minecraft.client.gui.GuiScreen; @@ -15,11 +14,12 @@ public class ModGUIConfig extends GuiConfig { public ModGUIConfig(GuiScreen guiScreen) { super( guiScreen, - new ConfigElement(getConfig().getCategory(Configuration.CATEGORY_GENERAL)).getChildElements(), + new ConfigElement<>(ConfigurationHandler.getConfig().getCategory(Configuration.CATEGORY_GENERAL)) + .getChildElements(), OverloadedArmorBar.MODID, false, false, - GuiConfig.getAbridgedConfigPath(getConfig().toString())); + GuiConfig.getAbridgedConfigPath(ConfigurationHandler.getConfig().toString())); } } From 38571acc20258aca20ddc306231ffac66a79a539 Mon Sep 17 00:00:00 2001 From: Alexdoru <57050655+Alexdoru@users.noreply.github.com> Date: Fri, 15 Sep 2023 09:41:33 +0200 Subject: [PATCH 07/14] Delete proxy and only run mod initialization on client side --- .../ConfigurationHandler.java | 2 +- .../OverloadedArmorBar.java | 20 +++++++++---------- .../overloadedarmorbar/proxy/ClientProxy.java | 13 ------------ .../overloadedarmorbar/proxy/CommonProxy.java | 6 ------ 4 files changed, 10 insertions(+), 31 deletions(-) delete mode 100644 src/main/java/locusway/overloadedarmorbar/proxy/ClientProxy.java delete mode 100644 src/main/java/locusway/overloadedarmorbar/proxy/CommonProxy.java diff --git a/src/main/java/locusway/overloadedarmorbar/ConfigurationHandler.java b/src/main/java/locusway/overloadedarmorbar/ConfigurationHandler.java index 9658c10..19ef87f 100644 --- a/src/main/java/locusway/overloadedarmorbar/ConfigurationHandler.java +++ b/src/main/java/locusway/overloadedarmorbar/ConfigurationHandler.java @@ -15,7 +15,7 @@ public class ConfigurationHandler { public static boolean alwaysShowArmorBar = false; public static boolean showEmptyArmorIcons = false; - public static void init(File configDir) { + public ConfigurationHandler(File configDir) { if (config == null) { config = new Configuration(configDir); loadConfiguration(); diff --git a/src/main/java/locusway/overloadedarmorbar/OverloadedArmorBar.java b/src/main/java/locusway/overloadedarmorbar/OverloadedArmorBar.java index bacb868..c0faf55 100644 --- a/src/main/java/locusway/overloadedarmorbar/OverloadedArmorBar.java +++ b/src/main/java/locusway/overloadedarmorbar/OverloadedArmorBar.java @@ -1,10 +1,13 @@ package locusway.overloadedarmorbar; -import locusway.overloadedarmorbar.proxy.CommonProxy; +import locusway.overloadedarmorbar.overlay.ArmorBarRenderer; + +import net.minecraftforge.common.MinecraftForge; + import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.Mod; -import cpw.mods.fml.common.SidedProxy; import cpw.mods.fml.common.event.FMLPreInitializationEvent; +import cpw.mods.fml.relauncher.Side; @Mod( modid = OverloadedArmorBar.MODID, @@ -19,17 +22,12 @@ public class OverloadedArmorBar { public static final String VERSION = "GRADLETOKEN_VERSION"; public static final String GUI_FACTORY_CLASS = "locusway.overloadedarmorbar.client.gui.GuiFactory"; - @SidedProxy( - modId = MODID, - clientSide = "locusway.overloadedarmorbar.proxy.ClientProxy", - serverSide = "locusway.overloadedarmorbar.proxy.CommonProxy") - public static CommonProxy proxy; - @Mod.EventHandler public void preInit(FMLPreInitializationEvent event) { - proxy.registerEvents(); - ConfigurationHandler.init(event.getSuggestedConfigurationFile()); - FMLCommonHandler.instance().bus().register(new ConfigurationHandler()); + if (event.getSide() == Side.CLIENT) { + MinecraftForge.EVENT_BUS.register(new ArmorBarRenderer()); + FMLCommonHandler.instance().bus().register(new ConfigurationHandler(event.getSuggestedConfigurationFile())); + } } } diff --git a/src/main/java/locusway/overloadedarmorbar/proxy/ClientProxy.java b/src/main/java/locusway/overloadedarmorbar/proxy/ClientProxy.java deleted file mode 100644 index d7c1a90..0000000 --- a/src/main/java/locusway/overloadedarmorbar/proxy/ClientProxy.java +++ /dev/null @@ -1,13 +0,0 @@ -package locusway.overloadedarmorbar.proxy; - -import locusway.overloadedarmorbar.overlay.ArmorBarRenderer; - -import net.minecraftforge.common.MinecraftForge; - -public class ClientProxy extends CommonProxy { - - @Override - public void registerEvents() { - MinecraftForge.EVENT_BUS.register(new ArmorBarRenderer()); - } -} diff --git a/src/main/java/locusway/overloadedarmorbar/proxy/CommonProxy.java b/src/main/java/locusway/overloadedarmorbar/proxy/CommonProxy.java deleted file mode 100644 index ddd0df5..0000000 --- a/src/main/java/locusway/overloadedarmorbar/proxy/CommonProxy.java +++ /dev/null @@ -1,6 +0,0 @@ -package locusway.overloadedarmorbar.proxy; - -public class CommonProxy { - - public void registerEvents() {} -} From 9c908deb417e26dba8f5320bb0a4d5714494c77b Mon Sep 17 00:00:00 2001 From: Alexdoru <57050655+Alexdoru@users.noreply.github.com> Date: Fri, 15 Sep 2023 09:52:26 +0200 Subject: [PATCH 08/14] move method and class to compute armor bars to the ArmorBarRenderer class --- .../ConfigurationHandler.java | 7 +- .../overloadedarmorbar/overlay/ArmorBar.java | 80 ----------- .../overlay/ArmorBarRenderer.java | 132 +++++++++++++++--- .../overloadedarmorbar/overlay/ArmorIcon.java | 33 ----- 4 files changed, 118 insertions(+), 134 deletions(-) delete mode 100644 src/main/java/locusway/overloadedarmorbar/overlay/ArmorBar.java delete mode 100644 src/main/java/locusway/overloadedarmorbar/overlay/ArmorIcon.java diff --git a/src/main/java/locusway/overloadedarmorbar/ConfigurationHandler.java b/src/main/java/locusway/overloadedarmorbar/ConfigurationHandler.java index 19ef87f..a4cc10e 100644 --- a/src/main/java/locusway/overloadedarmorbar/ConfigurationHandler.java +++ b/src/main/java/locusway/overloadedarmorbar/ConfigurationHandler.java @@ -2,6 +2,8 @@ import java.io.File; +import locusway.overloadedarmorbar.overlay.ArmorBarRenderer; + import net.minecraftforge.common.config.Configuration; import cpw.mods.fml.client.event.ConfigChangedEvent; @@ -33,7 +35,10 @@ private static void loadConfiguration() { showEmptyArmorIcons = config .getBoolean("Show empty armor icons?", Configuration.CATEGORY_GENERAL, false, "Show empty armor icons"); - if (config.hasChanged()) config.save(); + if (config.hasChanged()) { + config.save(); + ArmorBarRenderer.forceUpdate(); + } } @SubscribeEvent diff --git a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBar.java b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBar.java deleted file mode 100644 index bbac1d9..0000000 --- a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBar.java +++ /dev/null @@ -1,80 +0,0 @@ -package locusway.overloadedarmorbar.overlay; - -import locusway.overloadedarmorbar.ConfigurationHandler; - -/* - * Class manages the calculations required to determine the correct color(s) to use - */ -public class ArmorBar { - - private static void setArmorIconColor(ArmorIcon icon, String[] colors, int scale, int armorValue) { - int currentScale = scale; - int previousScale = scale - 1; - - // Force last color if we have run out of colors on the list - if (currentScale > colors.length - 1) { - currentScale = colors.length - 1; - } - if (previousScale > colors.length - 1) { - previousScale = colors.length - 1; - } - - // Previous scale is -1 between 0 and 20 points of armor, so reset to 0 for sane value - if (previousScale < 0) { - previousScale = 0; - } - - // Covers 2 (FULL) and 1 (HALF) - Primary Color - if (armorValue >= 1) { - // Should be current tier color - icon.primaryArmorIconColor.setColorFromHex(colors[currentScale]); - } - - // Covers 1 (HALF) - Secondary Color - if (armorValue == 1) { - // Should be previous tier color - icon.secondaryArmorIconColor.setColorFromHex(colors[previousScale]); - } - - if (armorValue == 0) { - // Should be previous tier color - icon.primaryArmorIconColor.setColorFromHex(colors[previousScale]); - } - } - - public static ArmorIcon[] calculateArmorIcons(int playerArmorValue) { - ArmorIcon[] armorIcons = new ArmorIcon[10]; - - // Calculate which color scale to use - int scale = playerArmorValue / 20; - - // Scale the value down for each position - int counter = playerArmorValue - (scale * 20); - - // Handle exact wrap around situation - if (scale > 0 && counter == 0) { - // Show we are maxed out at previous scale - scale -= 1; - counter = 20; - } - - for (int i = 0; i < 10; i++) { - armorIcons[i] = new ArmorIcon(); - setArmorIconColor(armorIcons[i], ConfigurationHandler.colorValues, scale, counter); - if (counter >= 2) { - // We have at least a full icon to show - armorIcons[i].armorIconType = ArmorIcon.Type.FULL; - counter -= 2; - } else if (counter == 1) { - // We have a half icon to show - armorIcons[i].armorIconType = ArmorIcon.Type.HALF; - counter -= 1; - } else { - // Empty icon - armorIcons[i].armorIconType = ArmorIcon.Type.NONE; - } - } - - return armorIcons; - } -} diff --git a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java index 578668e..b2d8e8f 100644 --- a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java +++ b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java @@ -6,7 +6,6 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Gui; -import net.minecraft.entity.player.EntityPlayer; import net.minecraftforge.client.GuiIngameForge; import net.minecraftforge.client.event.RenderGameOverlayEvent; import net.minecraftforge.common.ForgeHooks; @@ -25,9 +24,13 @@ public class ArmorBarRenderer extends Gui { private final static int ARMOR_ICON_SIZE = 9; private final static int ARMOR_SECOND_HALF_ICON_SIZE = 4; - private int previousArmorValue = UNKNOWN_ARMOR_VALUE; - private final Minecraft mc = Minecraft.getMinecraft(); - private ArmorIcon[] armorIcons; + private static int previousArmorValue = UNKNOWN_ARMOR_VALUE; + private static final Minecraft mc = Minecraft.getMinecraft(); + private static ArmorIcon[] armorIcons; + + public static void forceUpdate() { + previousArmorValue = UNKNOWN_ARMOR_VALUE; + } @SubscribeEvent(priority = EventPriority.HIGHEST, receiveCanceled = true) public void onRenderGameOverlayEvent(RenderGameOverlayEvent.Pre event) { @@ -36,25 +39,25 @@ public void onRenderGameOverlayEvent(RenderGameOverlayEvent.Pre event) { mc.mcProfiler.startSection("armor"); GL11.glEnable(GL11.GL_BLEND); - final EntityPlayer player = mc.thePlayer; - final int currentArmorValue = ForgeHooks.getTotalArmorValue(player); + // uncomment to debug + // final int currentArmorValue = (int) ((System.currentTimeMillis() / 200L) % 140); + final int currentArmorValue = ForgeHooks.getTotalArmorValue(mc.thePlayer); final int left = event.resolution.getScaledWidth() / 2 - 91; final int top = event.resolution.getScaledHeight() - GuiIngameForge.left_height; - // Save some CPU cycles by only recalculating armor when it changes + // Save some CPU cycles by caching the armor bars to render if (currentArmorValue != previousArmorValue) { - // Calculate here - armorIcons = ArmorBar.calculateArmorIcons(currentArmorValue); - // Save value for next cycle + armorIcons = calculateArmorIcons(currentArmorValue); previousArmorValue = currentArmorValue; } int armorIconCounter = 0; for (ArmorIcon icon : armorIcons) { int xPosition = left + armorIconCounter * 8; - switch (icon.armorIconType) { - case NONE: - ArmorIconColor color = icon.primaryArmorIconColor; + armorIconCounter++; + switch (icon.iconType) { + case EMPTY: + ArmorIconColor color = icon.mainColor; GL11.glColor4f(color.red, color.green, color.blue, color.alpha); if (currentArmorValue > 20) { // Draw the full icon as we have wrapped @@ -68,8 +71,8 @@ public void onRenderGameOverlayEvent(RenderGameOverlayEvent.Pre event) { } break; case HALF: - ArmorIconColor firstHalfColor = icon.primaryArmorIconColor; - ArmorIconColor secondHalfColor = icon.secondaryArmorIconColor; + ArmorIconColor firstHalfColor = icon.mainColor; + ArmorIconColor secondHalfColor = icon.secondaryColor; GL11.glColor4f(firstHalfColor.red, firstHalfColor.green, firstHalfColor.blue, firstHalfColor.alpha); this.drawTexturedModalRect(xPosition, top, 25, 9, 5, ARMOR_ICON_SIZE); @@ -100,14 +103,13 @@ public void onRenderGameOverlayEvent(RenderGameOverlayEvent.Pre event) { } break; case FULL: - ArmorIconColor fullColor = icon.primaryArmorIconColor; + ArmorIconColor fullColor = icon.mainColor; GL11.glColor4f(fullColor.red, fullColor.green, fullColor.blue, fullColor.alpha); this.drawTexturedModalRect(xPosition, top, 34, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); break; default: break; } - armorIconCounter++; } // Revert our state back GL11.glColor4f(1F, 1F, 1F, 1F); @@ -118,9 +120,99 @@ public void onRenderGameOverlayEvent(RenderGameOverlayEvent.Pre event) { } - public void forceUpdate() { - // Setting to unknown value will cause a refresh next render - previousArmorValue = UNKNOWN_ARMOR_VALUE; + private static ArmorIcon[] calculateArmorIcons(int armorPoints) { + ArmorIcon[] armorIcons = new ArmorIcon[10]; + + // Calculate which color scale to use + int scale = armorPoints / 20; + + // Scale the value down for each position + int counter = armorPoints - (scale * 20); + + // Handle exact wrap around situation + if (scale > 0 && counter == 0) { + // Show we are maxed out at previous scale + scale -= 1; + counter = 20; + } + + for (int i = 0; i < 10; i++) { + armorIcons[i] = new ArmorIcon(); + setArmorIconColor(armorIcons[i], ConfigurationHandler.colorValues, scale, counter); + if (counter >= 2) { + // We have at least a full icon to show + armorIcons[i].iconType = ArmorIcon.Type.FULL; + counter -= 2; + } else if (counter == 1) { + // We have a half icon to show + armorIcons[i].iconType = ArmorIcon.Type.HALF; + counter -= 1; + } else { + // Empty icon + armorIcons[i].iconType = ArmorIcon.Type.EMPTY; + } + } + + return armorIcons; + + } + + private static void setArmorIconColor(ArmorIcon icon, String[] colors, int scale, int armorValue) { + int currentScale = scale; + int previousScale = scale - 1; + + // Force last color if we have run out of colors on the list + if (currentScale > colors.length - 1) { + currentScale = colors.length - 1; + } + if (previousScale > colors.length - 1) { + previousScale = colors.length - 1; + } + + // Previous scale is -1 between 0 and 20 points of armor, so reset to 0 for sane value + if (previousScale < 0) { + previousScale = 0; + } + + // Covers 2 (FULL) and 1 (HALF) - Primary Color + if (armorValue >= 1) { + // Should be current tier color + icon.mainColor.setColorFromHex(colors[currentScale]); + } + + // Covers 1 (HALF) - Secondary Color + if (armorValue == 1) { + // Should be previous tier color + icon.secondaryColor.setColorFromHex(colors[previousScale]); + } + + if (armorValue == 0) { + // Should be previous tier color + icon.mainColor.setColorFromHex(colors[previousScale]); + } + } + + static class ArmorIcon { + + public Type iconType; + /** The main color of the icon */ + public final ArmorIconColor mainColor; + /** When of Type = HALF, this is the color of the right-hand side of the icon */ + public final ArmorIconColor secondaryColor; + + public ArmorIcon() { + iconType = Type.EMPTY; + mainColor = new ArmorIconColor(); + secondaryColor = new ArmorIconColor(); + } + + /** The type of armor icon to render */ + public enum Type { + EMPTY, + HALF, + FULL + } + } } diff --git a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorIcon.java b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorIcon.java deleted file mode 100644 index 1b4752d..0000000 --- a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorIcon.java +++ /dev/null @@ -1,33 +0,0 @@ -package locusway.overloadedarmorbar.overlay; - -/* - * Class wraps the information required to draw an individual armor icon - */ -public class ArmorIcon { - - public Type armorIconType; - - /* - * Type = FULL, Type = NONE: The color of the icon Type = HALF: The color of the left-hand side of the icon - */ - public final ArmorIconColor primaryArmorIconColor; - /* - * Type = HALF: The color of the right-hand side of the icon - */ - public final ArmorIconColor secondaryArmorIconColor; - - public ArmorIcon() { - armorIconType = Type.NONE; - primaryArmorIconColor = new ArmorIconColor(); - secondaryArmorIconColor = new ArmorIconColor(); - } - - /* - * The type of armor icon to show. - */ - public enum Type { - NONE, - HALF, - FULL - } -} From 5a68a3ff32ee178d2eb3b9f7328742a7c98ffa65 Mon Sep 17 00:00:00 2001 From: Alexdoru <57050655+Alexdoru@users.noreply.github.com> Date: Fri, 15 Sep 2023 10:48:25 +0200 Subject: [PATCH 09/14] delete ArmorIconColor class and store the colors as int, only call GlColor if the color actually is different --- .../ConfigurationHandler.java | 24 ++++++ .../overlay/ArmorBarRenderer.java | 78 +++++++++---------- .../overlay/ArmorIconColor.java | 36 --------- 3 files changed, 63 insertions(+), 75 deletions(-) delete mode 100644 src/main/java/locusway/overloadedarmorbar/overlay/ArmorIconColor.java diff --git a/src/main/java/locusway/overloadedarmorbar/ConfigurationHandler.java b/src/main/java/locusway/overloadedarmorbar/ConfigurationHandler.java index a4cc10e..9be89f2 100644 --- a/src/main/java/locusway/overloadedarmorbar/ConfigurationHandler.java +++ b/src/main/java/locusway/overloadedarmorbar/ConfigurationHandler.java @@ -1,6 +1,8 @@ package locusway.overloadedarmorbar; import java.io.File; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import locusway.overloadedarmorbar.overlay.ArmorBarRenderer; @@ -14,6 +16,7 @@ public class ConfigurationHandler { public static Configuration config; public static String[] colorValues = new String[] { "#FFFFFF", "#FF5500", "#FFC747", "#27FFE3", "#00FF00", "#7F00FF" }; + public static int[] colorValuesI; public static boolean alwaysShowArmorBar = false; public static boolean showEmptyArmorIcons = false; @@ -35,12 +38,32 @@ private static void loadConfiguration() { showEmptyArmorIcons = config .getBoolean("Show empty armor icons?", Configuration.CATEGORY_GENERAL, false, "Show empty armor icons"); + fillColorValuesI(); + if (config.hasChanged()) { config.save(); ArmorBarRenderer.forceUpdate(); } } + private static void fillColorValuesI() { + colorValuesI = new int[colorValues.length]; + for (int i = 0; i < colorValues.length; i++) { + colorValuesI[i] = parseColor(colorValues[i]); + } + } + + private static final Pattern colorPattern = Pattern.compile("^#[0-9A-Fa-f]{6}$"); + + private static int parseColor(String colorValue) { + final Matcher matcher = colorPattern.matcher(colorValue); + if (matcher.matches()) { + return Integer.parseInt(colorValue.substring(1, 7), 16); + } else { + return 0xFFFFFF; + } + } + @SubscribeEvent public void onConfigurationChangeEvent(ConfigChangedEvent.OnConfigChangedEvent event) { if (event.modID.equalsIgnoreCase(OverloadedArmorBar.MODID)) loadConfiguration(); @@ -49,4 +72,5 @@ public void onConfigurationChangeEvent(ConfigChangedEvent.OnConfigChangedEvent e public static Configuration getConfig() { return config; } + } diff --git a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java index b2d8e8f..f34d571 100644 --- a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java +++ b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java @@ -52,36 +52,27 @@ public void onRenderGameOverlayEvent(RenderGameOverlayEvent.Pre event) { } int armorIconCounter = 0; + int colorState = 0xFFFFFF; for (ArmorIcon icon : armorIcons) { - int xPosition = left + armorIconCounter * 8; + final int xPosition = left + armorIconCounter * 8; armorIconCounter++; switch (icon.iconType) { case EMPTY: - ArmorIconColor color = icon.mainColor; - GL11.glColor4f(color.red, color.green, color.blue, color.alpha); if (currentArmorValue > 20) { + colorState = setColor(icon.mainColor, colorState); // Draw the full icon as we have wrapped this.drawTexturedModalRect(xPosition, top, 34, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); - } else { - if (ConfigurationHandler.showEmptyArmorIcons - && (ConfigurationHandler.alwaysShowArmorBar || currentArmorValue > 0)) { - // Draw the empty armor icon - this.drawTexturedModalRect(xPosition, top, 16, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); - } - } + } else if (ConfigurationHandler.showEmptyArmorIcons + && (ConfigurationHandler.alwaysShowArmorBar || currentArmorValue > 0)) { + colorState = setColor(icon.mainColor, colorState); + // Draw the empty armor icon + this.drawTexturedModalRect(xPosition, top, 16, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); + } break; case HALF: - ArmorIconColor firstHalfColor = icon.mainColor; - ArmorIconColor secondHalfColor = icon.secondaryColor; - - GL11.glColor4f(firstHalfColor.red, firstHalfColor.green, firstHalfColor.blue, firstHalfColor.alpha); + colorState = setColor(icon.mainColor, colorState); this.drawTexturedModalRect(xPosition, top, 25, 9, 5, ARMOR_ICON_SIZE); - - GL11.glColor4f( - secondHalfColor.red, - secondHalfColor.green, - secondHalfColor.blue, - secondHalfColor.alpha); + colorState = setColor(icon.secondaryColor, colorState); if (currentArmorValue > 20) { // Draw the second half as full as we have wrapped this.drawTexturedModalRect( @@ -103,16 +94,16 @@ public void onRenderGameOverlayEvent(RenderGameOverlayEvent.Pre event) { } break; case FULL: - ArmorIconColor fullColor = icon.mainColor; - GL11.glColor4f(fullColor.red, fullColor.green, fullColor.blue, fullColor.alpha); + colorState = setColor(icon.mainColor, colorState); this.drawTexturedModalRect(xPosition, top, 34, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); break; default: break; } } - // Revert our state back - GL11.glColor4f(1F, 1F, 1F, 1F); + if (colorState != 0xFFFFFF) { + GL11.glColor4f(1F, 1F, 1F, 1F); + } GuiIngameForge.left_height += 10; GL11.glDisable(GL11.GL_BLEND); mc.mcProfiler.endSection(); @@ -120,6 +111,15 @@ public void onRenderGameOverlayEvent(RenderGameOverlayEvent.Pre event) { } + private static int setColor(int color, int colorState) { + if (colorState == color) return colorState; + final float red = (float) (color >> 16 & 0xFF) / 255F; + final float green = (float) (color >> 8 & 0xFF) / 255F; + final float blue = (float) (color & 0xFF) / 255F; + GL11.glColor4f(red, green, blue, 1F); + return color; + } + private static ArmorIcon[] calculateArmorIcons(int armorPoints) { ArmorIcon[] armorIcons = new ArmorIcon[10]; @@ -138,7 +138,7 @@ private static ArmorIcon[] calculateArmorIcons(int armorPoints) { for (int i = 0; i < 10; i++) { armorIcons[i] = new ArmorIcon(); - setArmorIconColor(armorIcons[i], ConfigurationHandler.colorValues, scale, counter); + setArmorIconColor(armorIcons[i], scale, counter); if (counter >= 2) { // We have at least a full icon to show armorIcons[i].iconType = ArmorIcon.Type.FULL; @@ -157,16 +157,18 @@ private static ArmorIcon[] calculateArmorIcons(int armorPoints) { } - private static void setArmorIconColor(ArmorIcon icon, String[] colors, int scale, int armorValue) { + private static void setArmorIconColor(ArmorIcon icon, int scale, int armorValue) { + int currentScale = scale; int previousScale = scale - 1; + final int colorsLength = ConfigurationHandler.colorValuesI.length; // Force last color if we have run out of colors on the list - if (currentScale > colors.length - 1) { - currentScale = colors.length - 1; + if (currentScale > colorsLength - 1) { + currentScale = colorsLength - 1; } - if (previousScale > colors.length - 1) { - previousScale = colors.length - 1; + if (previousScale > colorsLength - 1) { + previousScale = colorsLength - 1; } // Previous scale is -1 between 0 and 20 points of armor, so reset to 0 for sane value @@ -177,18 +179,18 @@ private static void setArmorIconColor(ArmorIcon icon, String[] colors, int scale // Covers 2 (FULL) and 1 (HALF) - Primary Color if (armorValue >= 1) { // Should be current tier color - icon.mainColor.setColorFromHex(colors[currentScale]); + icon.mainColor = ConfigurationHandler.colorValuesI[currentScale]; } // Covers 1 (HALF) - Secondary Color if (armorValue == 1) { - // Should be previous tier color - icon.secondaryColor.setColorFromHex(colors[previousScale]); + // Only right side of icon should be previous tier color + icon.secondaryColor = ConfigurationHandler.colorValuesI[previousScale]; } if (armorValue == 0) { - // Should be previous tier color - icon.mainColor.setColorFromHex(colors[previousScale]); + // All icon should be previous tier color + icon.mainColor = ConfigurationHandler.colorValuesI[previousScale]; } } @@ -196,14 +198,12 @@ static class ArmorIcon { public Type iconType; /** The main color of the icon */ - public final ArmorIconColor mainColor; + public int mainColor = 0xFFFFFF; /** When of Type = HALF, this is the color of the right-hand side of the icon */ - public final ArmorIconColor secondaryColor; + public int secondaryColor = 0xFFFFFF; public ArmorIcon() { iconType = Type.EMPTY; - mainColor = new ArmorIconColor(); - secondaryColor = new ArmorIconColor(); } /** The type of armor icon to render */ diff --git a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorIconColor.java b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorIconColor.java deleted file mode 100644 index 5bc8a0f..0000000 --- a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorIconColor.java +++ /dev/null @@ -1,36 +0,0 @@ -package locusway.overloadedarmorbar.overlay; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/* - * Class representing the color of the armor icon - */ -public class ArmorIconColor { - - private static final Pattern pattern = Pattern.compile("^#[0-9A-Fa-f]{6}$"); - - public float red; - public float blue; - public float green; - public final float alpha; - - public ArmorIconColor() { - red = blue = green = alpha = 1.0f; - } - - /** - * Converts from #RRGGBB format. If string is not in correct format this function will set the color to white. - */ - public void setColorFromHex(String colorHex) { - Matcher matcher = pattern.matcher(colorHex); - if (matcher.matches()) { - final int color = Integer.parseInt(colorHex.substring(1, 7), 16); - red = (float) (color >> 16 & 0xFF) / 255F; - green = (float) (color >> 8 & 0xFF) / 255F; - blue = (float) (color & 0xFF) / 255F; - } else { - red = green = blue = 1F; - } - } -} From 76e7ef73c26ab28a190b1a70d75450c50e72580e Mon Sep 17 00:00:00 2001 From: Alexdoru <57050655+Alexdoru@users.noreply.github.com> Date: Fri, 15 Sep 2023 11:37:10 +0200 Subject: [PATCH 10/14] use the same ArmorIcon instances all the time and just update its fields --- .../overlay/ArmorBarRenderer.java | 104 +++++++----------- 1 file changed, 37 insertions(+), 67 deletions(-) diff --git a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java index f34d571..8a92e9f 100644 --- a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java +++ b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java @@ -20,13 +20,12 @@ */ public class ArmorBarRenderer extends Gui { - private final static int UNKNOWN_ARMOR_VALUE = -1; - private final static int ARMOR_ICON_SIZE = 9; - private final static int ARMOR_SECOND_HALF_ICON_SIZE = 4; - - private static int previousArmorValue = UNKNOWN_ARMOR_VALUE; + private static final int UNKNOWN_ARMOR_VALUE = -1; + private static final int ARMOR_ICON_SIZE = 9; + private static final int ARMOR_SECOND_HALF_ICON_SIZE = 4; + private static final ArmorIcon[] armorIcons = new ArmorIcon[10]; private static final Minecraft mc = Minecraft.getMinecraft(); - private static ArmorIcon[] armorIcons; + private static int previousArmorValue = UNKNOWN_ARMOR_VALUE; public static void forceUpdate() { previousArmorValue = UNKNOWN_ARMOR_VALUE; @@ -47,15 +46,14 @@ public void onRenderGameOverlayEvent(RenderGameOverlayEvent.Pre event) { // Save some CPU cycles by caching the armor bars to render if (currentArmorValue != previousArmorValue) { - armorIcons = calculateArmorIcons(currentArmorValue); + updateArmorIcons(currentArmorValue); previousArmorValue = currentArmorValue; } - int armorIconCounter = 0; int colorState = 0xFFFFFF; - for (ArmorIcon icon : armorIcons) { - final int xPosition = left + armorIconCounter * 8; - armorIconCounter++; + for (int i = 0; i < armorIcons.length; i++) { + final ArmorIcon icon = armorIcons[i]; + final int xPosition = left + i * 8; switch (icon.iconType) { case EMPTY: if (currentArmorValue > 20) { @@ -120,78 +118,30 @@ private static int setColor(int color, int colorState) { return color; } - private static ArmorIcon[] calculateArmorIcons(int armorPoints) { - ArmorIcon[] armorIcons = new ArmorIcon[10]; - + private static void updateArmorIcons(int armorPoints) { // Calculate which color scale to use int scale = armorPoints / 20; - // Scale the value down for each position int counter = armorPoints - (scale * 20); - // Handle exact wrap around situation if (scale > 0 && counter == 0) { // Show we are maxed out at previous scale scale -= 1; counter = 20; } - - for (int i = 0; i < 10; i++) { - armorIcons[i] = new ArmorIcon(); - setArmorIconColor(armorIcons[i], scale, counter); + for (int i = 0; i < armorIcons.length; i++) { + if (armorIcons[i] == null) armorIcons[i] = new ArmorIcon(); + armorIcons[i].updateColors(scale, counter); if (counter >= 2) { - // We have at least a full icon to show armorIcons[i].iconType = ArmorIcon.Type.FULL; counter -= 2; } else if (counter == 1) { - // We have a half icon to show armorIcons[i].iconType = ArmorIcon.Type.HALF; counter -= 1; } else { - // Empty icon armorIcons[i].iconType = ArmorIcon.Type.EMPTY; } } - - return armorIcons; - - } - - private static void setArmorIconColor(ArmorIcon icon, int scale, int armorValue) { - - int currentScale = scale; - int previousScale = scale - 1; - final int colorsLength = ConfigurationHandler.colorValuesI.length; - - // Force last color if we have run out of colors on the list - if (currentScale > colorsLength - 1) { - currentScale = colorsLength - 1; - } - if (previousScale > colorsLength - 1) { - previousScale = colorsLength - 1; - } - - // Previous scale is -1 between 0 and 20 points of armor, so reset to 0 for sane value - if (previousScale < 0) { - previousScale = 0; - } - - // Covers 2 (FULL) and 1 (HALF) - Primary Color - if (armorValue >= 1) { - // Should be current tier color - icon.mainColor = ConfigurationHandler.colorValuesI[currentScale]; - } - - // Covers 1 (HALF) - Secondary Color - if (armorValue == 1) { - // Only right side of icon should be previous tier color - icon.secondaryColor = ConfigurationHandler.colorValuesI[previousScale]; - } - - if (armorValue == 0) { - // All icon should be previous tier color - icon.mainColor = ConfigurationHandler.colorValuesI[previousScale]; - } } static class ArmorIcon { @@ -202,15 +152,35 @@ static class ArmorIcon { /** When of Type = HALF, this is the color of the right-hand side of the icon */ public int secondaryColor = 0xFFFFFF; - public ArmorIcon() { - iconType = Type.EMPTY; + public void updateColors(int scale, int armorValue) { + int currentScale = scale; + int previousScale = scale - 1; + // Prevent array out of bounds exception + final int arrayLength = ConfigurationHandler.colorValuesI.length; + currentScale = Math.min(arrayLength - 1, currentScale); + previousScale = Math.min(arrayLength - 1, previousScale); + previousScale = Math.max(0, previousScale); + // Covers 2 (FULL) and 1 (HALF) - Primary Color + if (armorValue >= 1) { + // Icon should be of current tier color + this.mainColor = ConfigurationHandler.colorValuesI[currentScale]; + } + // Covers 1 (HALF) - Secondary Color + if (armorValue == 1) { + // Only right side of icon should be of previous tier color + this.secondaryColor = ConfigurationHandler.colorValuesI[previousScale]; + } + if (armorValue == 0) { + // Icon should be of previous tier color + this.mainColor = ConfigurationHandler.colorValuesI[previousScale]; + } } /** The type of armor icon to render */ public enum Type { EMPTY, - HALF, - FULL + FULL, + HALF } } From 0cda03d1b570e376081449484bb678e9294718d9 Mon Sep 17 00:00:00 2001 From: Alexdoru <57050655+Alexdoru@users.noreply.github.com> Date: Fri, 15 Sep 2023 12:12:21 +0200 Subject: [PATCH 11/14] cache the config logic directly when creating the ArmorIcon array --- .../overlay/ArmorBarRenderer.java | 60 +++++++------------ 1 file changed, 23 insertions(+), 37 deletions(-) diff --git a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java index 8a92e9f..5220929 100644 --- a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java +++ b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java @@ -22,7 +22,8 @@ public class ArmorBarRenderer extends Gui { private static final int UNKNOWN_ARMOR_VALUE = -1; private static final int ARMOR_ICON_SIZE = 9; - private static final int ARMOR_SECOND_HALF_ICON_SIZE = 4; + private static final int ARMOR_FIRST_HALF_WIDTH = 5; + private static final int ARMOR_SECOND_HALF_WIDTH = 4; private static final ArmorIcon[] armorIcons = new ArmorIcon[10]; private static final Minecraft mc = Minecraft.getMinecraft(); private static int previousArmorValue = UNKNOWN_ARMOR_VALUE; @@ -56,45 +57,22 @@ public void onRenderGameOverlayEvent(RenderGameOverlayEvent.Pre event) { final int xPosition = left + i * 8; switch (icon.iconType) { case EMPTY: - if (currentArmorValue > 20) { - colorState = setColor(icon.mainColor, colorState); - // Draw the full icon as we have wrapped - this.drawTexturedModalRect(xPosition, top, 34, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); - } else if (ConfigurationHandler.showEmptyArmorIcons - && (ConfigurationHandler.alwaysShowArmorBar || currentArmorValue > 0)) { - colorState = setColor(icon.mainColor, colorState); - // Draw the empty armor icon - this.drawTexturedModalRect(xPosition, top, 16, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); - } - break; - case HALF: colorState = setColor(icon.mainColor, colorState); - this.drawTexturedModalRect(xPosition, top, 25, 9, 5, ARMOR_ICON_SIZE); - colorState = setColor(icon.secondaryColor, colorState); - if (currentArmorValue > 20) { - // Draw the second half as full as we have wrapped - this.drawTexturedModalRect( - xPosition + 5, - top, - 39, - 9, - ARMOR_SECOND_HALF_ICON_SIZE, - ARMOR_ICON_SIZE); - } else { - // Draw the second half as empty - this.drawTexturedModalRect( - xPosition + 5, - top, - 30, - 9, - ARMOR_SECOND_HALF_ICON_SIZE, - ARMOR_ICON_SIZE); - } + drawTexturedModalRect(xPosition, top, 16, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); break; case FULL: colorState = setColor(icon.mainColor, colorState); - this.drawTexturedModalRect(xPosition, top, 34, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); + drawTexturedModalRect(xPosition, top, 34, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); + break; + case HALF: + colorState = setColor(icon.mainColor, colorState); + drawTexturedModalRect(xPosition, top, 25, 9, ARMOR_FIRST_HALF_WIDTH, ARMOR_ICON_SIZE); + colorState = setColor(icon.secondaryColor, colorState); + // Draw the second half as full if the player has more than one bar of armor, empty otherwise + final int textureX = currentArmorValue > 20 ? 39 : 30; + drawTexturedModalRect(xPosition + 5, top, textureX, 9, ARMOR_SECOND_HALF_WIDTH, ARMOR_ICON_SIZE); break; + case NONE: default: break; } @@ -139,7 +117,14 @@ private static void updateArmorIcons(int armorPoints) { armorIcons[i].iconType = ArmorIcon.Type.HALF; counter -= 1; } else { - armorIcons[i].iconType = ArmorIcon.Type.EMPTY; + if (armorPoints > 20) { + armorIcons[i].iconType = ArmorIcon.Type.FULL; + } else if (ConfigurationHandler.showEmptyArmorIcons + && (ConfigurationHandler.alwaysShowArmorBar || armorPoints > 0)) { + armorIcons[i].iconType = ArmorIcon.Type.EMPTY; + } else { + armorIcons[i].iconType = ArmorIcon.Type.NONE; + } } } } @@ -180,7 +165,8 @@ public void updateColors(int scale, int armorValue) { public enum Type { EMPTY, FULL, - HALF + HALF, + NONE } } From 9b0c7d384b980e64533a6506e4e1d325828f9575 Mon Sep 17 00:00:00 2001 From: Alexdoru <57050655+Alexdoru@users.noreply.github.com> Date: Fri, 15 Sep 2023 12:30:45 +0200 Subject: [PATCH 12/14] fix the config logic --- .../locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java index 5220929..6525f74 100644 --- a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java +++ b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java @@ -119,8 +119,8 @@ private static void updateArmorIcons(int armorPoints) { } else { if (armorPoints > 20) { armorIcons[i].iconType = ArmorIcon.Type.FULL; - } else if (ConfigurationHandler.showEmptyArmorIcons - && (ConfigurationHandler.alwaysShowArmorBar || armorPoints > 0)) { + } else if (ConfigurationHandler.showEmptyArmorIcons && armorPoints > 0 + || ConfigurationHandler.alwaysShowArmorBar) { armorIcons[i].iconType = ArmorIcon.Type.EMPTY; } else { armorIcons[i].iconType = ArmorIcon.Type.NONE; From 0f13d8a0e29008b6bb8667b8758e1895cf8a7784 Mon Sep 17 00:00:00 2001 From: Alexdoru <57050655+Alexdoru@users.noreply.github.com> Date: Sun, 17 Sep 2023 05:03:41 +0200 Subject: [PATCH 13/14] don't receive the event if canceled --- .../locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java index 6525f74..7aa3510 100644 --- a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java +++ b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java @@ -32,7 +32,7 @@ public static void forceUpdate() { previousArmorValue = UNKNOWN_ARMOR_VALUE; } - @SubscribeEvent(priority = EventPriority.HIGHEST, receiveCanceled = true) + @SubscribeEvent(priority = EventPriority.HIGHEST) public void onRenderGameOverlayEvent(RenderGameOverlayEvent.Pre event) { if (event.type != ARMOR) return; From 4103bcb766037f96d920198036e8bb507cde5b11 Mon Sep 17 00:00:00 2001 From: Martin Robertz Date: Sun, 17 Sep 2023 14:12:38 +0200 Subject: [PATCH 14/14] update bs+sa+deps --- build.gradle | 685 +++++++++++++----- gradle/wrapper/gradle-wrapper.jar | Bin 61608 -> 62076 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- .../ConfigurationHandler.java | 3 +- .../OverloadedArmorBar.java | 3 +- .../client/gui/ModGUIConfig.java | 5 +- .../overlay/ArmorBarRenderer.java | 3 +- 7 files changed, 513 insertions(+), 188 deletions(-) diff --git a/build.gradle b/build.gradle index fe8a2e7..b894d64 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,4 @@ -//version: 1675110695 +//version: 1692122114 /* DO NOT CHANGE THIS FILE! Also, you may replace this file at any time if there is an update available. @@ -6,27 +6,28 @@ */ -import com.diffplug.blowdryer.Blowdryer -import com.github.jengelman.gradle.plugins.shadow.tasks.ConfigureShadowRelocation import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import com.gtnewhorizons.retrofuturagradle.ObfuscationAttribute import com.gtnewhorizons.retrofuturagradle.mcp.ReobfuscatedJar +import com.gtnewhorizons.retrofuturagradle.minecraft.RunMinecraftTask +import com.gtnewhorizons.retrofuturagradle.util.Distribution import com.matthewprenger.cursegradle.CurseArtifact import com.matthewprenger.cursegradle.CurseRelation import com.modrinth.minotaur.dependencies.ModDependency import com.modrinth.minotaur.dependencies.VersionDependency import org.gradle.internal.logging.text.StyledTextOutput.Style import org.gradle.internal.logging.text.StyledTextOutputFactory -import org.jetbrains.gradle.ext.* +import org.gradle.internal.xml.XmlTransformer +import org.jetbrains.gradle.ext.Application +import org.jetbrains.gradle.ext.Gradle +import javax.inject.Inject import java.nio.file.Files import java.nio.file.Paths import java.util.concurrent.TimeUnit -import java.util.zip.ZipEntry -import java.util.zip.ZipOutputStream buildscript { repositories { - mavenLocal() mavenCentral() maven { @@ -47,6 +48,8 @@ buildscript { name 'Scala CI dependencies' url 'https://repo1.maven.org/maven2/' } + + mavenLocal() } } plugins { @@ -58,22 +61,26 @@ plugins { id 'org.jetbrains.kotlin.jvm' version '1.8.0' apply false id 'org.jetbrains.kotlin.kapt' version '1.8.0' apply false id 'com.google.devtools.ksp' version '1.8.0-1.0.9' apply false - id 'org.ajoberstar.grgit' version '4.1.1' // 4.1.1 is the last jvm8 supporting version ,unused, available for addon.gradle - id 'com.github.johnrengelman.shadow' version '7.1.2' apply false - id 'com.palantir.git-version' version '0.13.0' apply false // 0.13.0 is the last jvm8 supporting version - id 'de.undercouch.download' version '5.3.0' + id 'org.ajoberstar.grgit' version '4.1.1' // 4.1.1 is the last jvm8 supporting version, unused, available for addon.gradle + id 'com.github.johnrengelman.shadow' version '8.1.1' apply false + id 'com.palantir.git-version' version '3.0.0' apply false + id 'de.undercouch.download' version '5.4.0' id 'com.github.gmazzo.buildconfig' version '3.1.0' apply false // Unused, available for addon.gradle - id 'com.diffplug.spotless' version '6.7.2' apply false + id 'com.diffplug.spotless' version '6.13.0' apply false // 6.13.0 is the last jvm8 supporting version id 'com.modrinth.minotaur' version '2.+' apply false id 'com.matthewprenger.cursegradle' version '1.4.0' apply false - id 'com.gtnewhorizons.retrofuturagradle' version '1.1.2' + id 'com.gtnewhorizons.retrofuturagradle' version '1.3.24' } + +print("You might want to check out './gradlew :faq' if your build fails.\n") + boolean settingsupdated = verifySettingsGradle() settingsupdated = verifyGitAttributes() || settingsupdated if (settingsupdated) throw new GradleException("Settings has been updated, please re-run task.") -if (project.file('.git/HEAD').isFile()) { +// In submodules, .git is a file pointing to the real git dir +if (project.file('.git/HEAD').isFile() || project.file('.git').isFile()) { apply plugin: 'com.palantir.git-version' } @@ -108,6 +115,8 @@ propertyDefaultIfUnset("usesMixinDebug", project.usesMixins) propertyDefaultIfUnset("forceEnableMixins", false) propertyDefaultIfUnset("channel", "stable") propertyDefaultIfUnset("mappingsVersion", "12") +propertyDefaultIfUnset("usesMavenPublishing", true) +propertyDefaultIfUnset("mavenPublishUrl", "http://jenkins.usrv.eu:8081/nexus/content/repositories/releases") propertyDefaultIfUnset("modrinthProjectId", "") propertyDefaultIfUnset("modrinthRelations", "") propertyDefaultIfUnset("curseForgeProjectId", "") @@ -122,7 +131,10 @@ propertyDefaultIfUnset("gradleTokenGroupName", "") propertyDefaultIfUnset("enableModernJavaSyntax", false) // On by default for new projects only propertyDefaultIfUnset("enableGenericInjection", false) // On by default for new projects only -project.extensions.add(Blowdryer, "Blowdryer", Blowdryer) // Make blowdryer available in "apply from:" scripts +// this is meant to be set using the user wide property file. by default we do nothing. +propertyDefaultIfUnset("ideaOverrideBuildType", "") // Can be nothing, "gradle" or "idea" + +project.extensions.add(com.diffplug.blowdryer.Blowdryer, "Blowdryer", com.diffplug.blowdryer.Blowdryer) // Make blowdryer available in "apply from:" scripts if (!disableSpotless) { apply plugin: 'com.diffplug.spotless' apply from: Blowdryer.file('spotless.gradle') @@ -143,13 +155,21 @@ java { } else { languageVersion.set(projectJavaVersion) } - vendor.set(JvmVendorSpec.ADOPTIUM) + vendor.set(JvmVendorSpec.AZUL) } if (!noPublishedSources) { withSourcesJar() } } +tasks.withType(JavaCompile).configureEach { + options.encoding = "UTF-8" +} + +tasks.withType(ScalaCompile).configureEach { + options.encoding = "UTF-8" +} + pluginManager.withPlugin('org.jetbrains.kotlin.jvm') { // If Kotlin is enabled in the project kotlin { @@ -183,14 +203,34 @@ configurations { canBeConsumed = false canBeResolved = false } + + create("devOnlyNonPublishable") { + description = "Runtime and compiletime dependencies that are not published alongside the jar (compileOnly + runtimeOnlyNonPublishable)" + canBeConsumed = false + canBeResolved = false + } + compileOnly.extendsFrom(devOnlyNonPublishable) + runtimeOnlyNonPublishable.extendsFrom(devOnlyNonPublishable) } if (enableModernJavaSyntax.toBoolean()) { + repositories { + mavenCentral { + mavenContent { + includeGroup("me.eigenraven.java8unsupported") + } + } + } + dependencies { annotationProcessor 'com.github.bsideup.jabel:jabel-javac-plugin:1.0.0' + // workaround for https://github.com/bsideup/jabel/issues/174 + annotationProcessor 'net.java.dev.jna:jna-platform:5.13.0' compileOnly('com.github.bsideup.jabel:jabel-javac-plugin:1.0.0') { transitive = false // We only care about the 1 annotation class } + // Allow using jdk.unsupported classes like sun.misc.Unsafe in the compiled code, working around JDK-8206937. + patchedMinecraft('me.eigenraven.java8unsupported:java-8-unsupported-shim:1.0.0') } tasks.withType(JavaCompile).configureEach { @@ -202,7 +242,7 @@ if (enableModernJavaSyntax.toBoolean()) { javaCompiler.set(javaToolchains.compilerFor { languageVersion.set(JavaLanguageVersion.of(17)) - vendor.set(JvmVendorSpec.ADOPTIUM) + vendor.set(JvmVendorSpec.AZUL) }) } } @@ -234,12 +274,14 @@ if (apiPackage) { } if (accessTransformersFile) { - String targetFile = "src/main/resources/META-INF/" + accessTransformersFile - if (!getFile(targetFile).exists()) { - throw new GradleException("Could not resolve \"accessTransformersFile\"! Could not find " + targetFile) + for (atFile in accessTransformersFile.split(",")) { + String targetFile = "src/main/resources/META-INF/" + atFile.trim() + if (!getFile(targetFile).exists()) { + throw new GradleException("Could not resolve \"accessTransformersFile\"! Could not find " + targetFile) + } + tasks.deobfuscateMergedJarToSrg.accessTransformerFiles.from(targetFile) + tasks.srgifyBinpatchedJar.accessTransformerFiles.from(targetFile) } - tasks.deobfuscateMergedJarToSrg.accessTransformerFiles.from(targetFile) - tasks.srgifyBinpatchedJar.accessTransformerFiles.from(targetFile) } else { boolean atsFound = false for (File at : sourceSets.getByName("main").resources.files) { @@ -317,7 +359,27 @@ catch (Exception ignored) { String identifiedVersion String versionOverride = System.getenv("VERSION") ?: null try { - identifiedVersion = versionOverride == null ? gitVersion() : versionOverride + // Produce a version based on the tag, or for branches something like 0.2.2-configurable-maven-and-extras.38+43090270b6-dirty + if (versionOverride == null) { + def gitDetails = versionDetails() + def isDirty = gitVersion().endsWith(".dirty") // No public API for this, isCleanTag has a different meaning + String branchName = gitDetails.branchName ?: (System.getenv('GIT_BRANCH') ?: 'git') + if (branchName.startsWith('origin/')) { + branchName = branchName.minus('origin/') + } + branchName = branchName.replaceAll("[^a-zA-Z0-9-]+", "-") // sanitize branch names for semver + identifiedVersion = gitDetails.lastTag ?: '${gitDetails.gitHash}' + if (gitDetails.commitDistance > 0) { + identifiedVersion += "-${branchName}.${gitDetails.commitDistance}+${gitDetails.gitHash}" + if (isDirty) { + identifiedVersion += "-dirty" + } + } else if (isDirty) { + identifiedVersion += "-${branchName}+${gitDetails.gitHash}-dirty" + } + } else { + identifiedVersion = versionOverride + } } catch (Exception ignored) { out.style(Style.Failure).text( @@ -339,9 +401,13 @@ if (identifiedVersion == versionOverride) { group = "com.github.GTNewHorizons" if (project.hasProperty("customArchiveBaseName") && customArchiveBaseName) { - archivesBaseName = customArchiveBaseName + base { + archivesName = customArchiveBaseName + } } else { - archivesBaseName = modId + base { + archivesName = modId + } } @@ -367,12 +433,14 @@ minecraft { injectMissingGenerics.set(true) } + username = developmentEnvironmentUserName.toString() + + lwjgl3Version = "3.3.2" + // Enable assertions in the current mod extraRunJvmArguments.add("-ea:${modGroup}") if (usesMixins.toBoolean() || forceEnableMixins.toBoolean()) { - extraTweakClasses.add("org.spongepowered.asm.launch.MixinTweaker") - if (usesMixinDebug.toBoolean()) { extraRunJvmArguments.addAll([ "-Dmixin.debug.countInjections=true", @@ -401,6 +469,16 @@ configurations.configureEach { } } } + def obfuscationAttr = it.attributes.getAttribute(ObfuscationAttribute.OBFUSCATION_ATTRIBUTE) + if (obfuscationAttr != null && obfuscationAttr.name == ObfuscationAttribute.SRG) { + resolutionStrategy.eachDependency { DependencyResolveDetails details -> + // Remap CoFH core cursemaven dev jar to the obfuscated version for runObfClient/Server + if (details.requested.group == 'curse.maven' && details.requested.name.endsWith('-69162') && details.requested.version == '2388751') { + details.useVersion '2388750' + details.because 'Pick obfuscated jar' + } + } + } } // Ensure tests have access to minecraft classes @@ -413,10 +491,19 @@ sourceSets { } } -if (file('addon.gradle').exists()) { +if (file('addon.gradle.kts').exists()) { + apply from: 'addon.gradle.kts' +} else if (file('addon.gradle').exists()) { apply from: 'addon.gradle' } +// File for local tweaks not commited to Git +if (file('addon.local.gradle.kts').exists()) { + apply from: 'addon.local.gradle.kts' +} else if (file('addon.local.gradle').exists()) { + apply from: 'addon.local.gradle' +} + // Allow unsafe repos but warn repositories.configureEach { repo -> if (repo instanceof org.gradle.api.artifacts.repositories.UrlArtifactRepository) { @@ -427,11 +514,19 @@ repositories.configureEach { repo -> } } -apply from: 'repositories.gradle' +if (file('repositories.gradle.kts').exists()) { + apply from: 'repositories.gradle.kts' +} else if (file('repositories.gradle').exists()) { + apply from: 'repositories.gradle' +} else { + logger.error("Neither repositories.gradle.kts nor repositories.gradle was found, make sure you extracted the full ExampleMod template.") + throw new RuntimeException("Missing repositories.gradle[.kts]") +} configurations { + runtimeClasspath.extendsFrom(runtimeOnlyNonPublishable) + testRuntimeClasspath.extendsFrom(runtimeOnlyNonPublishable) for (config in [compileClasspath, runtimeClasspath, testCompileClasspath, testRuntimeClasspath]) { - config.extendsFrom(runtimeOnlyNonPublishable) if (usesShadowedDependencies.toBoolean()) { config.extendsFrom(shadowImplementation) // TODO: remove Compile after all uses are refactored to Implementation @@ -467,31 +562,42 @@ repositories { maven { name 'Overmind forge repo mirror' url 'https://gregtech.overminddl1.com/' - mavenContent { - excludeGroup("net.minecraftforge") // missing the `universal` artefact - } } maven { name = "GTNH Maven" url = "http://jenkins.usrv.eu:8081/nexus/content/groups/public/" allowInsecureProtocol = true } - if (usesMixins.toBoolean() || forceEnableMixins.toBoolean()) { - if (usesMixinDebug.toBoolean()) { - maven { - name = "Fabric Maven" - url = "https://maven.fabricmc.net/" - } + maven { + name 'sonatype' + url 'https://oss.sonatype.org/content/repositories/snapshots/' + content { + includeGroup "org.lwjgl" } } if (includeWellKnownRepositories.toBoolean()) { - maven { - name "CurseMaven" - url "https://cursemaven.com" - content { + exclusiveContent { + forRepository { + maven { + name "CurseMaven" + url "https://cursemaven.com" + } + } + filter { includeGroup "curse.maven" } } + exclusiveContent { + forRepository { + maven { + name = "Modrinth" + url = "https://api.modrinth.com/maven" + } + } + filter { + includeGroup "maven.modrinth" + } + } maven { name = "ic2" url = "https://maven.ic2.player.to/" @@ -515,35 +621,78 @@ repositories { } } +def mixinProviderGroup = "io.github.legacymoddingmc" +def mixinProviderModule = "unimixins" +def mixinProviderVersion = "0.1.7.1" +def mixinProviderSpecNoClassifer = "${mixinProviderGroup}:${mixinProviderModule}:${mixinProviderVersion}" +def mixinProviderSpec = "${mixinProviderSpecNoClassifer}:dev" +ext.mixinProviderSpec = mixinProviderSpec + +def mixingConfigRefMap = 'mixins.' + modId + '.refmap.json' + dependencies { if (usesMixins.toBoolean()) { annotationProcessor('org.ow2.asm:asm-debug-all:5.0.3') annotationProcessor('com.google.guava:guava:24.1.1-jre') annotationProcessor('com.google.code.gson:gson:2.8.6') - annotationProcessor('com.gtnewhorizon:gtnhmixins:2.1.10:processor') + annotationProcessor(mixinProviderSpec) if (usesMixinDebug.toBoolean()) { runtimeOnlyNonPublishable('org.jetbrains:intellij-fernflower:1.2.1.16') } } - if (usesMixins.toBoolean() || forceEnableMixins.toBoolean()) { - implementation('com.gtnewhorizon:gtnhmixins:2.1.10') + if (usesMixins.toBoolean()) { + implementation(modUtils.enableMixins(mixinProviderSpec, mixingConfigRefMap)) + } else if (forceEnableMixins.toBoolean()) { + runtimeOnlyNonPublishable(mixinProviderSpec) } } pluginManager.withPlugin('org.jetbrains.kotlin.kapt') { if (usesMixins.toBoolean()) { dependencies { - kapt('com.gtnewhorizon:gtnhmixins:2.1.10:processor') + kapt(mixinProviderSpec) } } } -apply from: 'dependencies.gradle' +// Replace old mixin mods with unimixins +// https://docs.gradle.org/8.0.2/userguide/resolution_rules.html#sec:substitution_with_classifier +configurations.all { + resolutionStrategy.dependencySubstitution { + substitute module('com.gtnewhorizon:gtnhmixins') using module(mixinProviderSpecNoClassifer) withClassifier("dev") because("Unimixins replaces other mixin mods") + substitute module('com.github.GTNewHorizons:Mixingasm') using module(mixinProviderSpecNoClassifer) withClassifier("dev") because("Unimixins replaces other mixin mods") + substitute module('com.github.GTNewHorizons:SpongePoweredMixin') using module(mixinProviderSpecNoClassifer) withClassifier("dev") because("Unimixins replaces other mixin mods") + substitute module('com.github.GTNewHorizons:SpongeMixins') using module(mixinProviderSpecNoClassifer) withClassifier("dev") because("Unimixins replaces other mixin mods") + substitute module('io.github.legacymoddingmc:unimixins') using module(mixinProviderSpecNoClassifer) withClassifier("dev") because("Our previous unimixins upload was missing the dev classifier") + } +} -def mixingConfigRefMap = 'mixins.' + modId + '.refmap.json' -def mixinTmpDir = buildDir.path + File.separator + 'tmp' + File.separator + 'mixins' -def refMap = "${mixinTmpDir}" + File.separator + mixingConfigRefMap -def mixinSrg = "${mixinTmpDir}" + File.separator + "mixins.srg" +dependencies { + constraints { + def minGtnhLibVersion = "0.0.13" + implementation("com.github.GTNewHorizons:GTNHLib:${minGtnhLibVersion}") { + because("fixes duplicate mod errors in java 17 configurations using old gtnhlib") + } + runtimeOnly("com.github.GTNewHorizons:GTNHLib:${minGtnhLibVersion}") { + because("fixes duplicate mod errors in java 17 configurations using old gtnhlib") + } + devOnlyNonPublishable("com.github.GTNewHorizons:GTNHLib:${minGtnhLibVersion}") { + because("fixes duplicate mod errors in java 17 configurations using old gtnhlib") + } + runtimeOnlyNonPublishable("com.github.GTNewHorizons:GTNHLib:${minGtnhLibVersion}") { + because("fixes duplicate mod errors in java 17 configurations using old gtnhlib") + } + } +} + +if (file('dependencies.gradle.kts').exists()) { + apply from: 'dependencies.gradle.kts' +} else if (file('dependencies.gradle').exists()) { + apply from: 'dependencies.gradle' +} else { + logger.error("Neither dependencies.gradle.kts nor dependencies.gradle was found, make sure you extracted the full ExampleMod template.") + throw new RuntimeException("Missing dependencies.gradle[.kts]") +} tasks.register('generateAssets') { group = "GTNH Buildscript" @@ -575,46 +724,17 @@ tasks.register('generateAssets') { } if (usesMixins.toBoolean()) { - tasks.named("reobfJar", ReobfuscatedJar).configure { - extraSrgFiles.from(mixinSrg) - } - tasks.named("processResources").configure { dependsOn("generateAssets") } tasks.named("compileJava", JavaCompile).configure { - doFirst { - new File(mixinTmpDir).mkdirs() - } options.compilerArgs += [ - "-AreobfSrgFile=${tasks.reobfJar.srg.get().asFile}", - "-AoutSrgFile=${mixinSrg}", - "-AoutRefMapFile=${refMap}", // Elan: from what I understand they are just some linter configs so you get some warning on how to properly code "-XDenableSunApiLintControl", "-XDignore.symbol.file" ] } - - pluginManager.withPlugin('org.jetbrains.kotlin.kapt') { - kapt { - correctErrorTypes = true - javacOptions { - option("-AreobfSrgFile=${tasks.reobfJar.srg.get().asFile}") - option("-AoutSrgFile=$mixinSrg") - option("-AoutRefMapFile=$refMap") - } - } - tasks.configureEach { task -> - if (task.name == "kaptKotlin") { - task.doFirst { - new File(mixinTmpDir).mkdirs() - } - } - } - } - } tasks.named("processResources", ProcessResources).configure { @@ -632,10 +752,158 @@ tasks.named("processResources", ProcessResources).configure { } if (usesMixins.toBoolean()) { - from refMap + dependsOn("compileJava", "compileScala") } } +ext.java17Toolchain = (JavaToolchainSpec spec) -> { + spec.languageVersion.set(JavaLanguageVersion.of(17)) + spec.vendor.set(JvmVendorSpec.matching("jetbrains")) +} + +ext.java17DependenciesCfg = configurations.create("java17Dependencies") { + extendsFrom(configurations.getByName("runtimeClasspath")) // Ensure consistent transitive dependency resolution + canBeConsumed = false +} +ext.java17PatchDependenciesCfg = configurations.create("java17PatchDependencies") { + canBeConsumed = false +} + +dependencies { + def lwjgl3ifyVersion = '1.4.0' + def asmVersion = '9.4' + if (modId != 'lwjgl3ify') { + java17Dependencies("com.github.GTNewHorizons:lwjgl3ify:${lwjgl3ifyVersion}") + } + if (modId != 'hodgepodge') { + java17Dependencies('com.github.GTNewHorizons:Hodgepodge:2.2.26') + } + + java17PatchDependencies('net.minecraft:launchwrapper:1.17.2') {transitive = false} + java17PatchDependencies("org.ow2.asm:asm:${asmVersion}") + java17PatchDependencies("org.ow2.asm:asm-commons:${asmVersion}") + java17PatchDependencies("org.ow2.asm:asm-tree:${asmVersion}") + java17PatchDependencies("org.ow2.asm:asm-analysis:${asmVersion}") + java17PatchDependencies("org.ow2.asm:asm-util:${asmVersion}") + java17PatchDependencies('org.ow2.asm:asm-deprecated:7.1') + java17PatchDependencies("org.apache.commons:commons-lang3:3.12.0") + java17PatchDependencies("com.github.GTNewHorizons:lwjgl3ify:${lwjgl3ifyVersion}:forgePatches") {transitive = false} +} + +ext.java17JvmArgs = [ + // Java 9+ support + "--illegal-access=warn", + "-Djava.security.manager=allow", + "-Dfile.encoding=UTF-8", + "--add-opens", "java.base/jdk.internal.loader=ALL-UNNAMED", + "--add-opens", "java.base/java.net=ALL-UNNAMED", + "--add-opens", "java.base/java.nio=ALL-UNNAMED", + "--add-opens", "java.base/java.io=ALL-UNNAMED", + "--add-opens", "java.base/java.lang=ALL-UNNAMED", + "--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED", + "--add-opens", "java.base/java.text=ALL-UNNAMED", + "--add-opens", "java.base/java.util=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.reflect=ALL-UNNAMED", + "--add-opens", "java.base/sun.nio.ch=ALL-UNNAMED", + "--add-opens", "jdk.naming.dns/com.sun.jndi.dns=ALL-UNNAMED,java.naming", + "--add-opens", "java.desktop/sun.awt.image=ALL-UNNAMED", + "--add-modules", "jdk.dynalink", + "--add-opens", "jdk.dynalink/jdk.dynalink.beans=ALL-UNNAMED", + "--add-modules", "java.sql.rowset", + "--add-opens", "java.sql.rowset/javax.sql.rowset.serial=ALL-UNNAMED" +] + +ext.hotswapJvmArgs = [ + // DCEVM advanced hot reload + "-XX:+AllowEnhancedClassRedefinition", + "-XX:HotswapAgent=fatjar" +] + +ext.setupHotswapAgentTask = tasks.register("setupHotswapAgent") { + group = "GTNH Buildscript" + description = "Installs a recent version of HotSwapAgent into the Java 17 JetBrains runtime directory" + def hsaUrl = 'https://github.com/HotswapProjects/HotswapAgent/releases/download/1.4.2-SNAPSHOT/hotswap-agent-1.4.2-SNAPSHOT.jar' + def targetFolderProvider = javaToolchains.launcherFor(java17Toolchain).map {it.metadata.installationPath.dir("lib/hotswap")} + def targetFilename = "hotswap-agent.jar" + onlyIf { + !targetFolderProvider.get().file(targetFilename).asFile.exists() + } + doLast { + def targetFolder = targetFolderProvider.get() + targetFolder.asFile.mkdirs() + download.run { + src hsaUrl + dest targetFolder.file(targetFilename).asFile + overwrite false + tempAndMove true + } + } +} + +public abstract class RunHotswappableMinecraftTask extends RunMinecraftTask { + // IntelliJ doesn't seem to allow commandline arguments so we also support an env variable + private boolean enableHotswap = Boolean.valueOf(System.getenv("HOTSWAP")); + + @Input + public boolean getEnableHotswap() { return enableHotswap } + @Option(option = "hotswap", description = "Enables HotSwapAgent for enhanced class reloading under a debugger") + public boolean setEnableHotswap(boolean enable) { enableHotswap = enable } + + @Inject + public RunHotswappableMinecraftTask(Distribution side, String superTask, org.gradle.api.invocation.Gradle gradle) { + super(side, gradle) + + this.lwjglVersion = 3 + this.javaLauncher = project.javaToolchains.launcherFor(project.java17Toolchain) + this.extraJvmArgs.addAll(project.java17JvmArgs) + this.extraJvmArgs.addAll(project.provider(() -> enableHotswap ? project.hotswapJvmArgs : [])) + + this.classpath(project.java17PatchDependenciesCfg) + if (side == Distribution.CLIENT) { + this.classpath(project.minecraftTasks.lwjgl3Configuration) + } + // Use a raw provider instead of map to not create a dependency on the task + this.classpath(project.provider(() -> project.tasks.named(superTask, RunMinecraftTask).get().classpath)) + this.classpath.filter { file -> + !file.path.contains("2.9.4-nightly-20150209") // Remove lwjgl2 + } + this.classpath(project.java17DependenciesCfg) + } + + public void setup(Project project) { + super.setup(project) + if (project.usesMixins.toBoolean()) { + this.extraJvmArgs.addAll(project.provider(() -> { + def mixinCfg = project.configurations.detachedConfiguration(project.dependencies.create(project.mixinProviderSpec)) + mixinCfg.canBeConsumed = false + mixinCfg.transitive = false + enableHotswap ? ["-javaagent:" + mixinCfg.singleFile.absolutePath] : [] + })) + } + } +} + +def runClient17Task = tasks.register("runClient17", RunHotswappableMinecraftTask, Distribution.CLIENT, "runClient") +runClient17Task.configure { + setup(project) + group = "Modded Minecraft" + description = "Runs the modded client using Java 17, lwjgl3ify and Hodgepodge" + dependsOn(setupHotswapAgentTask, mcpTasks.launcherSources.classesTaskName, minecraftTasks.taskDownloadVanillaAssets, mcpTasks.taskPackagePatchedMc, 'jar') + mainClass = "GradleStart" + username = minecraft.username + userUUID = minecraft.userUUID +} + +def runServer17Task = tasks.register("runServer17", RunHotswappableMinecraftTask, Distribution.DEDICATED_SERVER, "runServer") +runServer17Task.configure { + setup(project) + group = "Modded Minecraft" + description = "Runs the modded server using Java 17, lwjgl3ify and Hodgepodge" + dependsOn(setupHotswapAgentTask, mcpTasks.launcherSources.classesTaskName, minecraftTasks.taskDownloadVanillaAssets, mcpTasks.taskPackagePatchedMc, 'jar') + mainClass = "GradleStartServer" + extraArgs.add("nogui") +} + def getManifestAttributes() { def manifestAttributes = [:] if (!containsMixinsAndOrCoreModOnly.toBoolean() && (usesMixins.toBoolean() || coreModClass)) { @@ -667,11 +935,6 @@ tasks.named("jar", Jar).configure { } if (usesShadowedDependencies.toBoolean()) { - tasks.register('relocateShadowJar', ConfigureShadowRelocation) { - target = tasks.shadowJar - prefix = modGroup + ".shadow" - enabled = minimizeShadowedDependencies.toBoolean() - } tasks.named("shadowJar", ShadowJar).configure { manifest { attributes(getManifestAttributes()) @@ -686,8 +949,9 @@ if (usesShadowedDependencies.toBoolean()) { project.configurations.shadeCompile ] archiveClassifier.set('dev') - if (minimizeShadowedDependencies.toBoolean()) { - dependsOn(relocateShadowJar) + if (relocateShadowedDependencies.toBoolean()) { + relocationPrefix = modGroup + ".shadow" + enableRelocation = true } } configurations.runtimeElements.outgoing.artifacts.clear() @@ -705,7 +969,7 @@ if (usesShadowedDependencies.toBoolean()) { javaComponent.withVariantsFromConfiguration(configurations.shadowRuntimeElements) { skip() } - for (runTask in ["runClient", "runServer"]) { + for (runTask in ["runClient", "runServer", "runClient17", "runServer17"]) { tasks.named(runTask).configure { dependsOn("shadowJar") } @@ -743,16 +1007,47 @@ idea { module { downloadJavadoc = true downloadSources = true + inheritOutputDirs = true } project { settings { + if (ideaOverrideBuildType != "") { + delegateActions { + if ("gradle".equalsIgnoreCase(ideaOverrideBuildType)) { + delegateBuildRunToGradle = true + testRunner = org.jetbrains.gradle.ext.ActionDelegationConfig.TestRunner.GRADLE + } else if ("idea".equalsIgnoreCase(ideaOverrideBuildType)) { + delegateBuildRunToGradle = false + testRunner = org.jetbrains.gradle.ext.ActionDelegationConfig.TestRunner.PLATFORM + } else { + throw GradleScriptException('Accepted value for ideaOverrideBuildType is one of gradle or idea.') + } + } + } runConfigurations { + "0. Build and Test"(Gradle) { + taskNames = ["build"] + } "1. Run Client"(Gradle) { taskNames = ["runClient"] } "2. Run Server"(Gradle) { taskNames = ["runServer"] } + "1a. Run Client (Java 17)"(Gradle) { + taskNames = ["runClient17"] + } + "2a. Run Server (Java 17)"(Gradle) { + taskNames = ["runServer17"] + } + "1b. Run Client (Java 17, Hotswap)"(Gradle) { + taskNames = ["runClient17"] + envs = ["HOTSWAP": "true"] + } + "2b. Run Server (Java 17, Hotswap)"(Gradle) { + taskNames = ["runServer17"] + envs = ["HOTSWAP": "true"] + } "3. Run Obfuscated Client"(Gradle) { taskNames = ["runObfClient"] } @@ -770,7 +1065,7 @@ idea { } "Run Client (IJ Native)"(Application) { mainClass = "GradleStart" - moduleName = project.name + ".main" + moduleName = project.name + ".ideVirtualMain" afterEvaluate { workingDirectory = tasks.runClient.workingDir.absolutePath programParameters = tasks.runClient.calculateArgs(project).collect { '"' + it + '"' }.join(' ') @@ -781,7 +1076,7 @@ idea { } "Run Server (IJ Native)"(Application) { mainClass = "GradleStartServer" - moduleName = project.name + ".main" + moduleName = project.name + ".ideVirtualMain" afterEvaluate { workingDirectory = tasks.runServer.workingDir.absolutePath programParameters = tasks.runServer.calculateArgs(project).collect { '"' + it + '"' }.join(' ') @@ -793,11 +1088,57 @@ idea { } compiler.javac { afterEvaluate { + javacAdditionalOptions = "-encoding utf8" moduleJavacAdditionalOptions = [ (project.name + ".main"): tasks.compileJava.options.compilerArgs.collect { '"' + it + '"' }.join(' ') ] } } + withIDEADir { File ideaDir -> + if (!ideaDir.path.contains(".idea")) { + // If an .ipr file exists, the project root directory is passed here instead of the .idea subdirectory + ideaDir = new File(ideaDir, ".idea") + } + if (ideaDir.isDirectory()) { + def miscFile = new File(ideaDir, "misc.xml") + if (miscFile.isFile()) { + boolean dirty = false + def miscTransformer = new XmlTransformer() + miscTransformer.addAction { root -> + Node rootNode = root.asNode() + def rootManager = rootNode + .component.find { it.@name == 'ProjectRootManager' } + if (!rootManager) { + rootManager = rootNode.appendNode('component', ['name': 'ProjectRootManager', 'version': '2']) + dirty = true + } + def output = rootManager.output + if (!output) { + output = rootManager.appendNode('output') + dirty = true + } + if (!output.@url) { + // Only modify the output url if it doesn't yet have one, or if the existing one is blank somehow. + // This is a sensible default for most setups + output.@url = 'file://$PROJECT_DIR$/build/ideaBuild' + dirty = true + } + } + def result = miscTransformer.transform(miscFile.text) + if (dirty) { + miscFile.write(result) + } + } else { + miscFile.text = """ + + + + + +""" + } + } + } } } } @@ -806,6 +1147,14 @@ tasks.named("processIdeaSettings").configure { dependsOn("injectTags") } +tasks.named("ideVirtualMainClasses").configure { + // Make IntelliJ "Build project" build the mod jars + dependsOn("jar", "reobfJar") + if (!disableSpotless) { + dependsOn("spotlessCheck") + } +} + // workaround variable hiding in pom processing def projectConfigs = project.configurations @@ -826,12 +1175,14 @@ publishing { } repositories { - maven { - url = "http://jenkins.usrv.eu:8081/nexus/content/repositories/releases" - allowInsecureProtocol = true - credentials { - username = System.getenv("MAVEN_USER") ?: "NONE" - password = System.getenv("MAVEN_PASSWORD") ?: "NONE" + if (usesMavenPublishing.toBoolean()) { + maven { + url = mavenPublishUrl + allowInsecureProtocol = mavenPublishUrl.startsWith("http://") // Mostly for the GTNH maven + credentials { + username = System.getenv("MAVEN_USER") ?: "NONE" + password = System.getenv("MAVEN_PASSWORD") ?: "NONE" + } } } } @@ -867,7 +1218,7 @@ if (modrinthProjectId.size() != 0 && System.getenv("MODRINTH_TOKEN") != null) { } } if (usesMixins.toBoolean()) { - addModrinthDep("required", "project", "gtnhmixins") + addModrinthDep("required", "project", "unimixins") } tasks.modrinth.dependsOn(build) tasks.publish.dependsOn(tasks.modrinth) @@ -911,7 +1262,7 @@ if (curseForgeProjectId.size() != 0 && System.getenv("CURSEFORGE_TOKEN") != null } } if (usesMixins.toBoolean()) { - addCurseForgeRelation("requiredDependency", "gtnhmixins") + addCurseForgeRelation("requiredDependency", "unimixins") } tasks.curseforge.dependsOn(build) tasks.publish.dependsOn(tasks.curseforge) @@ -945,10 +1296,21 @@ def addCurseForgeRelation(String type, String name) { } // Updating + +def buildscriptGradleVersion = "8.2.1" + +tasks.named('wrapper', Wrapper).configure { + gradleVersion = buildscriptGradleVersion +} + tasks.register('updateBuildScript') { group = 'GTNH Buildscript' description = 'Updates the build script to the latest version' + if (gradle.gradleVersion != buildscriptGradleVersion && !Boolean.getBoolean('DISABLE_BUILDSCRIPT_GRADLE_UPDATE')) { + dependsOn('wrapper') + } + doLast { if (performBuildScriptUpdate()) return @@ -961,6 +1323,26 @@ if (!project.getGradle().startParameter.isOffline() && !Boolean.getBoolean('DISA performBuildScriptUpdate() } else { out.style(Style.SuccessHeader).println("Build script update available! Run 'gradle updateBuildScript'") + if (gradle.gradleVersion != buildscriptGradleVersion) { + out.style(Style.SuccessHeader).println("updateBuildScript can update gradle from ${gradle.gradleVersion} to ${buildscriptGradleVersion}\n") + } + } +} + +// If you want to add more cases to this task, implement them as arguments if total amount to print gets too large +tasks.register('faq') { + group = 'GTNH Buildscript' + description = 'Prints frequently asked questions about building a project' + + doLast { + print("If your build fails to fetch dependencies, run './gradlew updateDependencies'. " + + "Or you can manually check if the versions are still on the distributing sites - " + + "the links can be found in repositories.gradle and build.gradle:repositories, " + + "but not build.gradle:buildscript.repositories - those ones are for gradle plugin metadata.\n\n" + + "If your build fails to recognize the syntax of new Java versions, enable Jabel in your " + + "gradle.properties. See how it's done in GTNH ExampleMod/gradle.properties. " + + "However, keep in mind that Jabel enables only syntax features, but not APIs that were introduced in " + + "Java 9 or later.") } } @@ -1021,8 +1403,14 @@ boolean isNewBuildScriptVersionAvailable() { String currentBuildScript = getFile("build.gradle").getText() String currentBuildScriptHash = getVersionHash(currentBuildScript) - String availableBuildScript = availableBuildScriptUrl().newInputStream(parameters).getText() - String availableBuildScriptHash = getVersionHash(availableBuildScript) + String availableBuildScriptHash + try { + String availableBuildScript = availableBuildScriptUrl().newInputStream(parameters).getText() + availableBuildScriptHash = getVersionHash(availableBuildScript) + } catch (IOException e) { + logger.warn("Could not check for buildscript update availability: {}", e.message) + return false + } boolean isUpToDate = currentBuildScriptHash.empty || availableBuildScriptHash.empty || currentBuildScriptHash == availableBuildScriptHash return !isUpToDate @@ -1101,7 +1489,7 @@ static int replaceParams(File file, Map params) { return 0 } -// Dependency Deobfuscation +// Dependency Deobfuscation (Deprecated, use the new RFG API documented in dependencies.gradle) def deobf(String sourceURL) { try { @@ -1143,11 +1531,7 @@ def deobfMaven(String repoURL, String mavenDep) { } def deobfCurse(String curseDep) { - try { - return deobfMaven("https://www.cursemaven.com/", "curse.maven:$curseDep") - } catch (Exception ignored) { - out.style(Style.Failure).println("Failed to get $curseDep from cursemaven.") - } + return dependencies.rfg.deobf("curse.maven:$curseDep") } // The method above is to be preferred. Use this method if the filename is not at the end of the URL. @@ -1155,34 +1539,7 @@ def deobf(String sourceURL, String rawFileName) { String bon2Version = "2.5.1" String fileName = URLDecoder.decode(rawFileName, "UTF-8") String cacheDir = "$project.gradle.gradleUserHomeDir/caches" - String bon2Dir = "$cacheDir/forge_gradle/deobf" - String bon2File = "$bon2Dir/BON2-${bon2Version}.jar" String obfFile = "$cacheDir/modules-2/files-2.1/${fileName}.jar" - String deobfFile = "$cacheDir/modules-2/files-2.1/${fileName}-deobf.jar" - - if (file(deobfFile).exists()) { - return files(deobfFile) - } - - String mappingsVer - String remoteMappings = project.hasProperty('remoteMappings') ? project.remoteMappings : 'https://raw.githubusercontent.com/MinecraftForge/FML/1.7.10/conf/' - if (remoteMappings) { - String id = "${forgeVersion.split("\\.")[3]}-$minecraftVersion" - String mappingsZIP = "$cacheDir/forge_gradle/maven_downloader/de/oceanlabs/mcp/mcp_snapshot_nodoc/$id/mcp_snapshot_nodoc-${id}.zip" - - zipMappings(mappingsZIP, remoteMappings, bon2Dir) - - mappingsVer = "snapshot_$id" - } else { - mappingsVer = "${channel}_$mappingsVersion" - } - - download.run { - src "http://jenkins.usrv.eu:8081/nexus/content/repositories/releases/com/github/parker8283/BON2/$bon2Version-CUSTOM/BON2-$bon2Version-CUSTOM-all.jar" - dest bon2File - quiet true - overwrite false - } download.run { src sourceURL @@ -1190,50 +1547,8 @@ def deobf(String sourceURL, String rawFileName) { quiet true overwrite false } - - exec { - commandLine 'java', '-jar', bon2File, '--inputJar', obfFile, '--outputJar', deobfFile, '--mcVer', minecraftVersion, '--mappingsVer', mappingsVer, '--notch' - workingDir bon2Dir - standardOutput = new FileOutputStream("${deobfFile}.log") - } - - return files(deobfFile) -} - -def zipMappings(String zipPath, String url, String bon2Dir) { - File zipFile = new File(zipPath) - if (zipFile.exists()) { - return - } - - String fieldsCache = "$bon2Dir/data/fields.csv" - String methodsCache = "$bon2Dir/data/methods.csv" - - download.run { - src "${url}fields.csv" - dest fieldsCache - quiet true - } - download.run { - src "${url}methods.csv" - dest methodsCache - quiet true - } - - zipFile.getParentFile().mkdirs() - ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile)) - - zos.putNextEntry(new ZipEntry("fields.csv")) - Files.copy(Paths.get(fieldsCache), zos) - zos.closeEntry() - - zos.putNextEntry(new ZipEntry("methods.csv")) - Files.copy(Paths.get(methodsCache), zos) - zos.closeEntry() - - zos.close() + return dependencies.rfg.deobf(files(obfFile)) } - // Helper methods def checkPropertyExists(String propertyName) { @@ -1260,3 +1575,17 @@ def getSecondaryArtifacts() { if (apiPackage) secondaryArtifacts += [apiJar] return secondaryArtifacts } + +// For easier scripting of things that require variables defined earlier in the buildscript +if (file('addon.late.gradle.kts').exists()) { + apply from: 'addon.late.gradle.kts' +} else if (file('addon.late.gradle').exists()) { + apply from: 'addon.late.gradle' +} + +// File for local tweaks not commited to Git +if (file('addon.late.local.gradle.kts').exists()) { + apply from: 'addon.late.local.gradle.kts' +} else if (file('addon.late.local.gradle').exists()) { + apply from: 'addon.late.local.gradle' +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ccebba7710deaf9f98673a68957ea02138b60d0a..c1962a79e29d3e0ab67b14947c167a862655af9b 100644 GIT binary patch delta 8979 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 F{TFmrHLU;u delta 8469 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