From 8a968ac2cf3fcf3a6e5c36f104f791785f2d641e Mon Sep 17 00:00:00 2001 From: Ukiyograin Date: Sun, 2 Aug 2026 18:45:33 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(font):=20=E4=BF=AE=E5=A4=8D=20betterFon?= =?UTF-8?q?t=20=E4=B8=8B=E8=87=AA=E5=AE=9A=E4=B9=89=E8=AE=A1=E5=88=86?= =?UTF-8?q?=E6=9D=BF=E6=96=87=E5=AD=97=E9=80=8F=E6=98=8E=E4=B8=8D=E5=8F=AF?= =?UTF-8?q?=E8=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UFontRenderer.drawString 未按 vanilla 规则把无 alpha 位的颜色强制为不透明, ScoreboardComponent 以 0xFFFFFF 传色,经自定义字体路径渲染后 alpha=0 全透明, 导致开启"更好的字体"后自定义计分板整块消失(背景默认透明)。 按 FontRenderer 的规则在 edge$drawStringInternal 中补齐。 --- src/main/java/top/fpsmaster/font/impl/UFontRenderer.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/top/fpsmaster/font/impl/UFontRenderer.java b/src/main/java/top/fpsmaster/font/impl/UFontRenderer.java index 0b41a061..cfd41d0b 100644 --- a/src/main/java/top/fpsmaster/font/impl/UFontRenderer.java +++ b/src/main/java/top/fpsmaster/font/impl/UFontRenderer.java @@ -172,6 +172,14 @@ private int drawStringInternal(String text, float x, float y, int color, boolean } private int edge$drawStringInternal(String text, float x, float y, int color, boolean dropShadow, float shadowOffset) { + // Vanilla treats a colour with no alpha bits as opaque — its drawString does + // `if ((color & 0xFC000000) == 0) color |= 0xFF000000` and the custom-font mixin keeps + // the rule for the vanilla-replacement path. Without it here, callers that pass opaque + // colours the vanilla way (the custom scoreboard passes 0xFFFFFF) draw fully transparent + // once routed through this renderer. + if ((color & 0xFC000000) == 0) { + color |= 0xFF000000; + } color = apply(color); int i; if (dropShadow) { From 2313e5f4c1c756b11fdd4b18954eb66ad1cd8d0d Mon Sep 17 00:00:00 2001 From: gaoyu06 Date: Sun, 2 Aug 2026 06:01:06 -0700 Subject: [PATCH 2/2] fix(interface): guard the fades the vanilla colour rule would have made solid Forcing a colour with no alpha bits to opaque is right for UFontRenderer - it extends FontRenderer and is handed around as one, LevelTag picks between it and fontRendererObj at runtime, so a vanilla-shaped colour has to mean there what it means everywhere else. But it costs the alpha 1-3 band, and the client was running fades straight through it. The OOBE greeting crossfade is the one that shows. previousAlpha is cubic, so it sits at or below three for the last thirty-odd frames of the half-second fade, and the greeting rotates every 2.2s: the outgoing text would have held full opacity for half a second and then vanished. The tutorial slide is smaller - updateTutorialAutoplay resets the transition after updateAnimations has advanced it, so the frame a slide flips renders at exactly zero and would pop in solid. Both stop at four now, which is what MixinGuiNewChat already does and what every other fade in the client already did by guarding above the band. Component colours come from ColorSetting, whose alpha slider reaches zero and whose Wave mode scales what it returns, so Component.drawString takes alpha at its word and draws nothing below four. That is the opposite dialect to the renderer's, which is fine - they are different layers - and it leaves shapes alone: backgrounds go through drawRect and never come past here, so a fully transparent background stays transparent. The custom scoreboard was the one component writing vanilla's spelling of opaque white; it passes -1 now, like the other twelve. Co-Authored-By: Claude --- .../fpsmaster/font/impl/UFontRenderer.java | 10 +++++- .../top/fpsmaster/ui/custom/Component.java | 15 +++++++++ .../ui/custom/impl/ScoreboardComponent.java | 6 ++-- .../fpsmaster/ui/screens/oobe/OobeScreen.java | 31 ++++++++++++++----- 4 files changed, 51 insertions(+), 11 deletions(-) diff --git a/src/main/java/top/fpsmaster/font/impl/UFontRenderer.java b/src/main/java/top/fpsmaster/font/impl/UFontRenderer.java index cfd41d0b..2d02e560 100644 --- a/src/main/java/top/fpsmaster/font/impl/UFontRenderer.java +++ b/src/main/java/top/fpsmaster/font/impl/UFontRenderer.java @@ -176,7 +176,15 @@ private int drawStringInternal(String text, float x, float y, int color, boolean // `if ((color & 0xFC000000) == 0) color |= 0xFF000000` and the custom-font mixin keeps // the rule for the vanilla-replacement path. Without it here, callers that pass opaque // colours the vanilla way (the custom scoreboard passes 0xFFFFFF) draw fully transparent - // once routed through this renderer. + // once routed through this renderer. This class extends FontRenderer and is handed around + // as one — LevelTag picks between it and fontRendererObj at runtime — so a vanilla-shaped + // colour has to mean what it means everywhere else. + // + // The cost is that alpha 1-3 can no longer be asked for: a caller fading its own text out + // must stop above three rather than run to zero, which is what MixinGuiNewChat, the OOBE + // screen and every other fade in the client do. The global fade is unaffected — Alpha.apply + // runs after this, so Alpha.set(0f) still reaches zero and stays invisible. Do not move + // this below that call; a0860d7 removed the rule from TextRenderer for exactly that reason. if ((color & 0xFC000000) == 0) { color |= 0xFF000000; } diff --git a/src/main/java/top/fpsmaster/ui/custom/Component.java b/src/main/java/top/fpsmaster/ui/custom/Component.java index 241d6054..f25b954a 100644 --- a/src/main/java/top/fpsmaster/ui/custom/Component.java +++ b/src/main/java/top/fpsmaster/ui/custom/Component.java @@ -406,7 +406,22 @@ public void drawString(int fontSize, String text, float x, float y, int color) { drawString(fontSize, false, text, x, y, color); } + /** + * Draws a component's text. + * + *

Colours here come from {@link top.fpsmaster.features.settings.impl.ColorSetting}, whose + * alpha slider reaches zero and whose Wave mode scales what it returns, so alpha means what it + * says: below four there is nothing to draw. Both renderers underneath read it vanilla's way + * instead — no alpha bits set means opaque — and would turn a string the user hid into a solid + * one. Components must therefore pass a real alpha, not a bare {@code 0xRRGGBB}. + * + *

Only text is affected. Backgrounds and shapes go through {@code drawRect} and {@code Rects} + * and never come through here, so a fully transparent background stays transparent. + */ public void drawString(int fontSize, boolean bold, String text, float x, float y, int color) { + if (((color >>> 24) & 0xFF) <= 3) { + return; + } double scaled = (int) (scale * 100) / 100.0; fontSize = (int) (fontSize * scale); UFontRenderer font = FPSMaster.fontManager.getFont(fontSize); diff --git a/src/main/java/top/fpsmaster/ui/custom/impl/ScoreboardComponent.java b/src/main/java/top/fpsmaster/ui/custom/impl/ScoreboardComponent.java index 6ffe5df1..57979553 100644 --- a/src/main/java/top/fpsmaster/ui/custom/impl/ScoreboardComponent.java +++ b/src/main/java/top/fpsmaster/ui/custom/impl/ScoreboardComponent.java @@ -66,11 +66,13 @@ public void draw(float x, float y) { width = maxWidth + 6; height = (lines.size() + 1) * lineHeight + 4; drawRect(x, y, width, height, mod.backgroundColor.getColor()); - drawString(FONT_SIZE, title, x + 3 * scale, y + 2 * scale, 0xFFFFFF); + // Opaque white as -1, the way every other component writes it. A bare 0xFFFFFF is vanilla's + // spelling of the same colour, and Component.drawString does not read alpha vanilla's way. + drawString(FONT_SIZE, title, x + 3 * scale, y + 2 * scale, -1); // Row offsets are positions, so they scale here. float offsetY = y + (2 + lineHeight) * scale; for (String line : lines) { - drawString(FONT_SIZE, line, x + 3 * scale, offsetY, 0xFFFFFF); + drawString(FONT_SIZE, line, x + 3 * scale, offsetY, -1); offsetY += lineHeight * scale; } } diff --git a/src/main/java/top/fpsmaster/ui/screens/oobe/OobeScreen.java b/src/main/java/top/fpsmaster/ui/screens/oobe/OobeScreen.java index d52def3b..273b7a23 100644 --- a/src/main/java/top/fpsmaster/ui/screens/oobe/OobeScreen.java +++ b/src/main/java/top/fpsmaster/ui/screens/oobe/OobeScreen.java @@ -372,11 +372,17 @@ private void renderTutorialPage(int mouseX, int mouseY) { Color titleColor = new Color(24, 32, 54, alpha); Color descColor = new Color(92, 101, 118, alpha); - GL11.glPushMatrix(); - GL11.glTranslatef(0f, (1f - tutorialSlideTransition) * 8f, 0f); - FPSMaster.fontManager.s18.drawString(slides[tutorialIndex][0], x + 24f, cardY + 54f, titleColor.getRGB()); - drawMultilineBodyTextWithAlpha(extendTutorialDescription(slides[tutorialIndex][1], tutorialIndex), x + 24f, cardY + 94f, width - 48f, 4, alpha); - GL11.glPopMatrix(); + // Below four the renderer reads the colour as vanilla does - no alpha bits set means opaque - + // so a fade this shallow comes out solid rather than invisible. updateTutorialAutoplay resets + // the transition after updateAnimations has advanced it, which puts the frame a slide flips on + // at exactly zero: without this the new slide pops in at full opacity before its fade starts. + if (alpha > 3) { + GL11.glPushMatrix(); + GL11.glTranslatef(0f, (1f - tutorialSlideTransition) * 8f, 0f); + FPSMaster.fontManager.s18.drawString(slides[tutorialIndex][0], x + 24f, cardY + 54f, titleColor.getRGB()); + drawMultilineBodyTextWithAlpha(extendTutorialDescription(slides[tutorialIndex][1], tutorialIndex), x + 24f, cardY + 94f, width - 48f, 4, alpha); + GL11.glPopMatrix(); + } } private void renderFeaturesPage() { @@ -987,14 +993,23 @@ private void renderAnimatedGreeting(float x, float y) { int currentAlpha = Math.min(255, Math.max(0, Math.round(eased * 255f))); Color currentColor = new Color(accentText().getRed(), accentText().getGreen(), accentText().getBlue(), currentAlpha); + // Both passes stop at four rather than zero: the renderer reads a colour with no alpha bits + // as opaque, the way vanilla's does, so anything below that comes out solid. The fade-out is + // the one that matters - it is cubic, so previousAlpha sits in that band for the last 30-odd + // frames of the half-second crossfade, and the outgoing greeting would hold full opacity + // there and then vanish. Neither pass has anything left to show by four. if (greetingTransition < 1f && greetingPreviousText != null && !greetingPreviousText.isEmpty()) { float previousProgress = 1f - eased; int previousAlpha = Math.min(255, Math.max(0, Math.round(previousProgress * 255f))); - Color previousColor = new Color(accentText().getRed(), accentText().getGreen(), accentText().getBlue(), previousAlpha); - FPSMaster.fontManager.s18.drawString(greetingPreviousText, x, y - eased * 8f, previousColor.getRGB()); + if (previousAlpha > 3) { + Color previousColor = new Color(accentText().getRed(), accentText().getGreen(), accentText().getBlue(), previousAlpha); + FPSMaster.fontManager.s18.drawString(greetingPreviousText, x, y - eased * 8f, previousColor.getRGB()); + } } - FPSMaster.fontManager.s18.drawString(greetingCurrentText, x, y + (1f - eased) * 8f, currentColor.getRGB()); + if (currentAlpha > 3) { + FPSMaster.fontManager.s18.drawString(greetingCurrentText, x, y + (1f - eased) * 8f, currentColor.getRGB()); + } } private void switchLanguage(int newLanguage) {