Skip to content

Port Stats Ring from ChatTriggers module to 1.21.10 Fabric mod - #2

Merged
bubner merged 11 commits into
1.21.10-fabricfrom
copilot/port-stats-ring-to-fabric
Feb 9, 2026
Merged

Port Stats Ring from ChatTriggers module to 1.21.10 Fabric mod#2
bubner merged 11 commits into
1.21.10-fabricfrom
copilot/port-stats-ring-to-fabric

Conversation

CopilotAI commented Feb 8, 2026

Copy link
Copy Markdown
Contributor
  • Analyze ChatTriggers source code (main.js + config.js) from main branch
  • Understand scaffolded Fabric mod template on 1.21.10-fabric branch
  • Add Cloth Config dependency to build.gradle for config GUI (version 20.0.148)
  • Create ModConfig.java with Properties-based serialization (as specified)
  • Create ConfigScreen.java using Cloth Config for /ring settings GUI
  • Create StatsRingRenderer.java porting main.js logic (action bar parsing + HUD rendering)
  • Update StatsRing.java to register /ring command and wire components
  • Update fabric.mod.json with Cloth Config dependency
  • Address all PR review feedback:
    • Fix blit call to use graphics.blit(RenderPipelines.GUI_TEXTURED, ...) (Mojang mappings)
    • Fix Identifier to ResourceLocation (Mojang mappings)
    • Fix pose transforms to use Matrix3x2fStack with pushMatrix/popMatrix
    • Use HudElementRegistry instead of deprecated HudRenderCallback
    • Replace unicode escape sequences with actual characters
    • Move utility methods to Util.java
    • Add ManaReadStatus enum
    • Use local var for config, remove init log
    • Fix all @author tags
    • Extract magic numbers into named constants
    • Move scale to PERCENT_SCALE constant
    • Add NOT_ENOUGH_MANA 2-second latch for extended warning

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

CopilotAIand others added 3 commits February 8, 2026 10:05
- Add ModConfig with Properties-based serialization
- Add ConfigScreen using Cloth Config API
- Add StatsRingRenderer porting main.js logic
- Update StatsRing entry point with /ring command
- Add Cloth Config dependency
Co-authored-by: bubner <81782264+bubner@users.noreply.github.com>
Co-authored-by: bubner <81782264+bubner@users.noreply.github.com>
- Add deprecation comment for HudRenderCallback
- Fix parseStat to match original comma-stripping parseInt logic
- Use ChatFormatting enum instead of raw formatting codes in title
Co-authored-by: bubner <81782264+bubner@users.noreply.github.com>
CopilotAI changed the title [WIP] Port Stats Ring from ChatTriggers to Fabric modPort Stats Ring from ChatTriggers module to 1.21.10 Fabric modFeb 8, 2026
CopilotAI requested a review from bubnerFebruary 8, 2026 10:16
if (config.getBackingImage()) {
int ringX = xCenter - RING_SIZE / 2;
int ringY = yCenter - RING_SIZE / 2;
graphics.blit(RenderType::guiTextured, RING_TEXTURE, ringX, ringY, 0, 0, RING_SIZE, RING_SIZE, RING_SIZE, RING_SIZE);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

com.mojang.blaze3d.pipeline.RenderPipeline is not a functional interface

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in bf31df0 — replaced graphics.blit(RenderType::guiTextured, ...) with graphics.blitSprite(RenderPipelines.GUI_TEXTURED, RING_TEXTURE, ringX, ringY, RING_SIZE, RING_SIZE).

String hpText = Math.round(healthPercent) + "%";
int hpTextX = Math.round(healthPercent) >= 100 ? xCenter - 32 : xCenter - 28;
int hpTextY = yCenter - Math.round(mc.font.lineHeight * scale / 2f);
graphics.pose().pushPose();

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pose returns public org.joml.Matrix3x2fStack pose(), hence pushPose() does not exist and dest argument of pose().scale(scale, scale, dest) must be Matrix3x2f

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in bf31df0 — now uses Matrix3x2fStack pose = graphics.pose() with pushMatrix()/popMatrix() and translate()/scale() matching the 1.21.10 API, as seen in other mods like MEH's SkyBlockPrettyHudHudElement.

ClientReceiveMessageEvents.GAME.register(this::onGameMessage);
ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> resetState());
ClientTickEvents.END_CLIENT_TICK.register(client -> onTick());
HudRenderCallback.EVENT.register(this::onHudRender);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can HudElementRegistry be used instead?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in bf31df0StatsRingRenderer now implements HudElement and registers via HudElementRegistry.attachElementAfter(VanillaHudElements.CROSSHAIR, ...).

String msg = message.getString();

// Extract health information
if (msg.contains("\u2764")) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont use unicode escape sequences

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in bf31df0 — replaced \u2764 and \u270E with literal and .

Comment on lines +227 to +264
// --- Utility methods ---

private static boolean isInSkyBlock() {
Minecraft mc = Minecraft.getInstance();
if (mc.level == null) return false;
Scoreboard scoreboard = mc.level.getScoreboard();
Objective objective = scoreboard.getDisplayObjective(DisplaySlot.SIDEBAR);
if (objective == null) return false;
return objective.getDisplayName().getString().contains("SKYBLOCK");
}

private static float parseStat(String stat) {
try {
return Integer.parseInt(stat.replaceAll(",", "").trim());
} catch (NumberFormatException e) {
return Float.NaN;
}
}

private static float lerp(float start, float end, float t) {
return start + (end - start) * t;
}

private static int argb(int a, int r, int g, int b) {
return (a << 24) | (r << 16) | (g << 8) | b;
}

private static int darkenRgb(int colour, float factor) {
int r = Math.round(((colour >> 16) & 0xFF) * factor);
int g = Math.round(((colour >> 8) & 0xFF) * factor);
int b = Math.round((colour & 0xFF) * factor);
return argb(255, r, g, b);
}

private static void drawBoldText(GuiGraphics graphics, Minecraft mc, String text, int x, int y, int color) {
Component component = Component.literal(text).withStyle(Style.EMPTY.withBold(true));
graphics.drawString(mc.font, component, x, y, color, true);
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move to a separate Util.java class

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in bf31df0 — moved isInSkyBlock, parseStat, lerp, argb, darkenRgb, and drawBoldText to Util.java. Imported via static me.bubner.statsring.Util.*.

private float maxMana = Float.NaN;

// 0 = mana read OK, 1 = mana frozen/missing, 2 = NOT ENOUGH MANA
private int manaReadStatus = 0;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use an enum instead of integer and comment

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in bf31df0 — added ManaReadStatus enum with OK, FROZEN, NOT_ENOUGH_MANA values.

}))
);

LOGGER.info("StatsRing initialised");

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

init message remove, use local var for config final ModConfig CONFIG = new ModConfig(); instead of instance field

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in bf31df0 — removed LOGGER.info("StatsRing initialised"), changed CONFIG from a public static final field to a local final ModConfig config variable in onInitializeClient(), and passed it as a parameter to ConfigScreen.create().

* StatsRing - Display Health and Mana in a ring around the crosshair on Hypixel SkyBlock.
* Ported from a 1.8.9 ChatTriggers module into a 1.21.10 Fabric mod.
*
* @author Lucas Bubner, 2023-2025

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

years only 2023, should be @author Lucas Bubner, 2023 (Original CT module)
do this for all author tags

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in bf31df0 — all @author tags updated to @author Lucas Bubner, 2023 (Original CT module).

- Fix blit call: use graphics.blitSprite(RenderPipelines.GUI_TEXTURED, ...) instead of RenderType::guiTextured
- Fix pose transforms: use Matrix3x2fStack with pushMatrix/popMatrix/translate/scale
- Use HudElementRegistry.attachElementAfter(CROSSHAIR) instead of deprecated HudRenderCallback
- Replace unicode escape sequences with actual characters (❤, ✎)
- Extract utility methods into Util.java
- Add ManaReadStatus enum to replace integer + comment
- Use local var for config in StatsRing instead of static field
- Remove init log message
- Fix all @author tags to "2023 (Original CT module)"
- Pass config as parameter to ConfigScreen.create()
- Use Identifier instead of ResourceLocation for 1.21.10 Mojang mappings
Co-authored-by: bubner <81782264+bubner@users.noreply.github.com>
if (config.getBackingImage()) {
int ringX = xCenter - RING_SIZE / 2;
int ringY = yCenter - RING_SIZE / 2;
graphics.blitSprite(RenderPipelines.GUI_TEXTURED, RING_TEXTURE, ringX, ringY, RING_SIZE, RING_SIZE);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

graphics.blitSprite does not exist on type GuiGraphics with official mojang mappings

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ba317eb — replaced graphics.blitSprite(RenderPipelines.GUI_TEXTURED, ...) with graphics.blit(RenderPipelines.GUI_TEXTURED, RING_TEXTURE, ringX, ringY, 0, 0, RING_SIZE, RING_SIZE, RING_SIZE, RING_SIZE) matching the Mojang mappings blit signature.

import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Identifier does not exist

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ba317eb — replaced all Identifier usages with ResourceLocation from net.minecraft.resources.ResourceLocation to match Official Mojang Mappings.

- Replace net.minecraft.resources.Identifier with ResourceLocation
- Replace graphics.blitSprite() with graphics.blit() matching Mojang mappings API
Co-authored-by: bubner <81782264+bubner@users.noreply.github.com>

CopilotAI commented Feb 8, 2026

Copy link
Copy Markdown
ContributorAuthor

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • maven.fabricmc.net
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED -Xmx1G -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en -Duser.variant (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

CopilotAI requested a review from bubnerFebruary 8, 2026 10:51
@bubnerbubner linked an issue Feb 9, 2026 that may be closed by this pull request
Comment on lines +141 to +192

float currentHpScale = Math.min(HEIGHT_SCALE, HEIGHT_SCALE * (hp / maxHp));
hpScale = config.getInterpolateBars() ? Util.lerp(hpScale, currentHpScale, 0.1f) : currentHpScale;
int hpBarHeight = Math.round(hpScale);

float lowHpPercent = config.getAlertLowHpPercent();
if (lowHpPercent >= 0 && healthPercent <= lowHpPercent && cycle) {
Util.drawBoldText(graphics, mc, "!!!", xCenter - 14, yCenter - 22, hpColour);
hpColour = config.getInterpolateColour() ? COLOR_RED : COLOR_WHITE;
graphics.fill(xCenter - 11, yCenter + 10 - HEIGHT_SCALE, xCenter - 11 + BAR_WIDTH, yCenter + 10 - hpBarHeight, Util.darkenRgb(hpColour, 0.25f));
}

graphics.fill(xCenter - 11, yCenter + 10 - hpBarHeight, xCenter - 11 + BAR_WIDTH, yCenter + 10, hpColour);

// === Mana bar ===
float manaPercentage = Math.min(100f, (mana / maxMana) * 100f);

int manaColour;
if (config.getInterpolateColour()) {
int r = Math.round(Util.lerp(200, 0, manaPercentage / 100f));
int g = Math.round(Util.lerp(100, 255, manaPercentage / 100f));
manaColour = Util.argb(255, r, g, 255);
} else {
manaColour = COLOR_AQUA;
}

float currentManaScale = Math.min(HEIGHT_SCALE, HEIGHT_SCALE * (mana / maxMana));
manaScale = config.getInterpolateBars() ? Util.lerp(manaScale, currentManaScale, 0.1f) : currentManaScale;
int manaBarHeight = Math.round(manaScale);

float lowManaPercent = config.getAlertLowManaPercent();
secInterval = manaReadStatus == ManaReadStatus.NOT_ENOUGH_MANA ? 0.2f : 0.4f;

if (manaReadStatus == ManaReadStatus.FROZEN) {
graphics.fill(xCenter + 9, yCenter + 10 - HEIGHT_SCALE, xCenter + 9 + BAR_WIDTH, yCenter + 10 - manaBarHeight, Util.darkenRgb(COLOR_GRAY, 0.25f));
} else if (((lowManaPercent >= 0 && manaPercentage <= lowManaPercent) || manaReadStatus == ManaReadStatus.NOT_ENOUGH_MANA) && !cycle) {
int foreColour = manaReadStatus == ManaReadStatus.NOT_ENOUGH_MANA ? COLOR_RED : (config.getInterpolateColour() ? COLOR_AQUA : COLOR_WHITE);
Util.drawBoldText(graphics, mc, "!!!", xCenter + 7, yCenter + 13, foreColour);
manaColour = foreColour;
graphics.fill(xCenter + 9, yCenter + 10 - HEIGHT_SCALE, xCenter + 9 + BAR_WIDTH, yCenter + 10 - manaBarHeight, Util.darkenRgb(manaColour, 0.25f));
}

int manaBarColour = manaReadStatus != ManaReadStatus.FROZEN ? manaColour : COLOR_GRAY;
graphics.fill(xCenter + 9, yCenter + 10 - manaBarHeight, xCenter + 9 + BAR_WIDTH, yCenter + 10, manaBarColour);

// === Percentages ===
if (!config.getPercentage()) return;

float scale = 0.75f;
int lineHeight = Math.round(mc.font.lineHeight * scale / 2f);

// HP percentage

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

too many magic numbers from the original javascript being used, e.g. xCenter + 9, yCenter + 10, xCenter - 32, xCenter - 28, xCenter + 15. move these and consolidate their uses as private static final integers as done with height scale, etc. also float scale = 0.75f can be moved out too. finally, making a functionality there if manaReadStatus reads NOT_ENOUGH_MANA -> it is desired for the not enough mana status to "latch" for two seconds, allowing the extended warning to take effect

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 121820b:

  • Extracted all magic numbers into named private static final constants: HP_BAR_X_OFFSET, MANA_BAR_X_OFFSET, BAR_BOTTOM_Y_OFFSET, HP_ALERT_X/Y_OFFSET, MANA_ALERT_X/Y_OFFSET, HP_PERCENT_X_OFFSET(_WIDE), MANA_PERCENT_X_OFFSET
  • Moved float scale = 0.75f to PERCENT_SCALE constant
  • Added NOT_ENOUGH_MANA latch: notEnoughManaLatchTicks counter set to NOT_ENOUGH_MANA_LATCH_TICKS (40 ticks = 2 seconds) when the status is detected, counting down in onTick(). During the latch window, neither OK nor FROZEN overwrites the status, allowing the extended warning to take full effect.

… latch
- Move all positional magic numbers from render() into private static final
constants: HP_BAR_X_OFFSET, MANA_BAR_X_OFFSET, BAR_BOTTOM_Y_OFFSET,
HP_ALERT_X/Y_OFFSET, MANA_ALERT_X/Y_OFFSET, HP_PERCENT_X_OFFSET(_WIDE),
MANA_PERCENT_X_OFFSET
- Move float scale = 0.75f to PERCENT_SCALE constant
- Add NOT_ENOUGH_MANA latch: when mana status reads NOT_ENOUGH_MANA, the
warning state persists for 2 seconds (40 ticks) via notEnoughManaLatchTicks
counter, preventing premature reset when new action bar messages arrive
Co-authored-by: bubner <81782264+bubner@users.noreply.github.com>
@bubner
bubner marked this pull request as ready for review February 9, 2026 09:02
CopilotAI review requested due to automatic review settings February 9, 2026 09:02

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Ports the “Stats Ring” HUD overlay from a 1.8.9 ChatTriggers module into a 1.21.10 Fabric client mod, including persistent configuration and an in-game settings screen.

Changes:

  • Added a HUD renderer that parses SkyBlock action bar stats and draws the ring overlay (HP/Mana bars, alerts, percentages).
  • Introduced a Properties-backed config + Cloth Config GUI, wired to a /ring client command.
  • Added Cloth Config dependency + mod metadata updates and included the ring texture asset.

Reviewed changes

Copilot reviewed 9 out of 12 changed files in this pull request and generated 6 comments.

Show a summary per file
FileDescription
src/main/resources/fabric.mod.jsonDeclares Cloth Config as a dependency in mod metadata.
src/main/resources/assets/statsring/ring-2.pngAdds the ring backing texture asset.
src/main/java/me/bubner/statsring/Util.javaAdds shared helpers for parsing and rendering.
src/main/java/me/bubner/statsring/StatsRingRenderer.javaImplements action bar parsing + HUD rendering via HudElementRegistry.
src/main/java/me/bubner/statsring/StatsRing.javaInitializes config/renderer and registers /ring to open the config screen.
src/main/java/me/bubner/statsring/ModConfig.javaAdds Properties-based persistence for settings.
src/main/java/me/bubner/statsring/ManaReadStatus.javaAdds enum to track mana read state (OK/FROZEN/NOT_ENOUGH_MANA).
src/main/java/me/bubner/statsring/ConfigScreen.javaAdds Cloth Config-based settings UI and save wiring.
build.gradleAdds Shedaniel Maven + Cloth Config dependency.
gradle.propertiesAdds cloth_config_version.
gradlewAdds the Gradle wrapper script.
README.mdDocuments the Cloth Config dependency.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadsrc/main/java/me/bubner/statsring/StatsRingRenderer.java
Comment threadsrc/main/java/me/bubner/statsring/StatsRingRenderer.java
Comment threadsrc/main/java/me/bubner/statsring/Util.java Outdated
Comment threadsrc/main/java/me/bubner/statsring/StatsRingRenderer.java
Comment threadsrc/main/java/me/bubner/statsring/ConfigScreen.java
Comment threadsrc/main/java/me/bubner/statsring/ModConfig.java
@bubner
bubner merged commit f50d72e into 1.21.10-fabricFeb 9, 2026
2 checks passed
@bubner
bubner deleted the copilot/port-stats-ring-to-fabric branch February 9, 2026 09:56
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Update/port to latest version

3 participants

@bubner