From a4ed30111c6b93144566f324c87ba8cfa3053039 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 30 Sep 2026 21:31:32 +0200 Subject: [PATCH 1/4] Build for Minecraft 26.2 Adds 26.2 as a Stonecutter version (#39). 26.2 splits the HUD out of Gui into Hud and moves the open screen, the overlay and the toasts from Minecraft to Gui. A new Screens helper gets and sets the screen and clears the overlay on every version, and MinecraftClientMixin opens screens through it instead of shadowing Minecraft.setScreen. SetScreenMixin targets Gui.setScreen and the HUD hook Hud.extractChat on 26.2. 26.2's rendering API declares samplers through bind group layouts and gives render targets a GpuFormat, so the glass pipeline adds SAMPLER1's layout and its backdrops are RGBA8_UNORM like vanilla's targets. The main render target is the game renderer's, the panorama no longer takes a fade flag, and toasts are added with SystemToast.add. Co-Authored-By: Claude Opus 5.5 --- README.MD | 2 +- VERSION_GUIDE.md | 4 +-- settings.gradle.kts | 2 +- .../mixins/render/GameRendererMixin.java | 4 ++- .../features/mixins/render/RenderMixin.java | 10 +++++- .../impl/mixins/MinecraftClientMixin.java | 16 ++++----- .../impl/mixins/StatusEffectMixin.java | 6 ++++ .../impl/provider/GLFWProviderImpl.java | 3 +- .../impl/provider/SaturnProviderImpl.java | 5 +++ .../saturnclient/impl/ui/GlassRenderer.java | 13 ++++++++ .../impl/ui/SaturnScreenFabric.java | 4 ++- .../org/saturnclient/impl/ui/Screens.java | 33 +++++++++++++++++++ .../saturnclient/event/KeyInputHandler.java | 5 +-- .../saturnclient/mixin/GuiRendererMixin.java | 4 +-- .../saturnclient/mixin/SetScreenMixin.java | 8 ++++- .../mixin/SplashOverlayMixin.java | 16 +++++---- versions/26.2/gradle.properties | 17 ++++++++++ 17 files changed, 122 insertions(+), 30 deletions(-) create mode 100644 src/main/java/org/saturnclient/impl/ui/Screens.java create mode 100644 versions/26.2/gradle.properties diff --git a/README.MD b/README.MD index a13c2d1..725631c 100644 --- a/README.MD +++ b/README.MD @@ -18,7 +18,7 @@ Saturn Client is a free, open-source Minecraft client designed to enhance the gameplay experience while staying lightweight and customizable. Our goal is to build a clean, optimized client that the community can contribute to and improve together. -Saturn runs on Fabric and supports **Minecraft 1.21.4 to 1.21.11 and 26.1**, with one build per version (the 26.1 build also runs on 26.1.1 and 26.1.2). If something doesn't work on your version, please open an issue. +Saturn runs on Fabric and supports **Minecraft 1.21.4 to 1.21.11, 26.1 and 26.2**, with one build per version (the 26.1 build also runs on 26.1.1 and 26.1.2). If something doesn't work on your version, please open an issue. ## Features diff --git a/VERSION_GUIDE.md b/VERSION_GUIDE.md index 431f8e0..22eae91 100644 --- a/VERSION_GUIDE.md +++ b/VERSION_GUIDE.md @@ -4,7 +4,7 @@ How Saturn Client supports several Minecraft versions, and how to change or add ## Layout -Every supported Minecraft version (1.21.4 to 1.21.11, and 26.1) builds from one copy of the version-specific code with [Stonecutter](https://stonecutter.kikugie.dev/): +Every supported Minecraft version (1.21.4 to 1.21.11, 26.1 and 26.2) builds from one copy of the version-specific code with [Stonecutter](https://stonecutter.kikugie.dev/): - `common/`: all version-independent code (mods, UI, cosmetics, the server client). It has no Minecraft dependency and reaches the game only through the interfaces in `org.saturnclient.common`. - `src/`: the version-specific code (providers, refs, mixins and the three mixin configs), one copy for every version. Where Minecraft's API differs between versions, the code goes behind version comments (see below). @@ -18,7 +18,7 @@ Minecraft is obfuscated up to 1.21.11 and ships with Mojang's names from 26.1. T Every version is built with Mojang's official names (`loom.officialMojangMappings()`), the names Minecraft itself ships from 26.1 on. Yarn isn't used any more: 1.21.11 has the last Yarn release. -Mojang renames things between versions. `src/` uses the active version's (1.21.11's) names, and `stonecutter.gradle.kts` lists replacements that swap in each version's name when Stonecutter processes it: for example `Identifier`, which was `ResourceLocation` before 1.21.11, `AvatarRenderState`, which was `PlayerRenderState` before 1.21.9, and `GuiGraphics`, which is `GuiGraphicsExtractor` from 26.1. Add a replacement there when a rename touches many lines. A class that only moved package needs just a version comment on its import, and a renamed method a version comment where it's called. +Mojang renames things between versions. `src/` uses the active version's (1.21.11's) names, and `stonecutter.gradle.kts` lists replacements that swap in each version's name when Stonecutter processes it: for example `Identifier`, which was `ResourceLocation` before 1.21.11, `AvatarRenderState`, which was `PlayerRenderState` before 1.21.9, and `GuiGraphics`, which is `GuiGraphicsExtractor` from 26.1. Add a replacement there when a rename touches many lines. From 26.2 the open screen and overlay live on `Gui` rather than `Minecraft`, so code goes through `impl.ui.Screens` for them. A class that only moved package needs just a version comment on its import, and a renamed method a version comment where it's called. Released 1.21.x jars run on intermediary names (`method_1234`), not Mojang's, and Loom renames everything that refers to Minecraft when it builds them. So a mixin can't implement one of `common`'s Ref interfaces by relying on a vanilla method that happens to have the same name: the vanilla method is renamed in the jar, and the interface method is left without an implementation (`AbstractMethodError`). Shadow the vanilla method and implement the Ref method explicitly, as `WindowMixin` does. For the same reason, Ref methods mustn't share a name and parameters with a method of the class they're mixed into, or the mixin replaces the vanilla method in development. diff --git a/settings.gradle.kts b/settings.gradle.kts index 26c4dc0..2166843 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -24,7 +24,7 @@ include("common") // versions//gradle.properties holds its Minecraft, Fabric Loader and Fabric API versions. val stonecutterVersions = listOf( "1.21.4", "1.21.5", "1.21.6", "1.21.7", "1.21.8", "1.21.9", "1.21.10", "1.21.11", - "26.1", + "26.1", "26.2", ) stonecutter { diff --git a/src/main/java/org/saturnclient/impl/features/mixins/render/GameRendererMixin.java b/src/main/java/org/saturnclient/impl/features/mixins/render/GameRendererMixin.java index a8f8bbe..9b96307 100644 --- a/src/main/java/org/saturnclient/impl/features/mixins/render/GameRendererMixin.java +++ b/src/main/java/org/saturnclient/impl/features/mixins/render/GameRendererMixin.java @@ -8,6 +8,8 @@ import net.minecraft.client.renderer.GameRenderer; //? if >=1.21.6 import org.saturnclient.impl.ui.SaturnScreenFabric; +//? if >=1.21.6 +import org.saturnclient.impl.ui.Screens; import org.saturnclient.impl.features.render.ZoomFov; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -53,7 +55,7 @@ public class GameRendererMixin { @Redirect(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Options;getMenuBackgroundBlurriness()I")) private int useSaturnBlur(Options options) { - if (Minecraft.getInstance().screen instanceof SaturnScreenFabric saturnScreen) { + if (Screens.current() instanceof SaturnScreenFabric saturnScreen) { return saturnScreen.screen.getBackgroundBlur(); } diff --git a/src/main/java/org/saturnclient/impl/features/mixins/render/RenderMixin.java b/src/main/java/org/saturnclient/impl/features/mixins/render/RenderMixin.java index b827d0d..16953cb 100644 --- a/src/main/java/org/saturnclient/impl/features/mixins/render/RenderMixin.java +++ b/src/main/java/org/saturnclient/impl/features/mixins/render/RenderMixin.java @@ -2,11 +2,15 @@ package org.saturnclient.impl.features.mixins.render; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; +//? if >=26.2 { +/*import net.minecraft.client.gui.Hud; +*///?} else import net.minecraft.client.gui.Gui; import net.minecraft.client.DeltaTracker; import org.saturnclient.impl.ui.RenderScopeImpl; import org.saturnclient.impl.ui.SaturnScreenFabric; +import org.saturnclient.impl.ui.Screens; import org.saturnclient.mod.HudMod; import org.saturnclient.mod.Mod; import org.saturnclient.mod.ModLayout; @@ -37,6 +41,10 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; * {@link SaturnScreenFabric} / {@link HudEditor} pair; that is * unchanged infrastructure. */ +// The HUD is drawn by Hud from 26.2 (by Gui before). +//? if >=26.2 { +/*@Mixin(Hud.class) +*///?} else @Mixin(Gui.class) public class RenderMixin { @@ -51,7 +59,7 @@ public class RenderMixin { // can draw its own preview layout without double-drawing. if (textRenderer == null) return; - if (SaturnClient.client.screen instanceof SaturnScreenFabric f + if (Screens.current() instanceof SaturnScreenFabric f && f.screen instanceof HudEditor) return; diff --git a/src/main/java/org/saturnclient/impl/mixins/MinecraftClientMixin.java b/src/main/java/org/saturnclient/impl/mixins/MinecraftClientMixin.java index b34a78e..1d9017d 100644 --- a/src/main/java/org/saturnclient/impl/mixins/MinecraftClientMixin.java +++ b/src/main/java/org/saturnclient/impl/mixins/MinecraftClientMixin.java @@ -4,11 +4,11 @@ import java.io.File; import java.io.InputStream; import java.util.UUID; -import org.jetbrains.annotations.Nullable; import org.saturnclient.common.ref.asset.IdentifierRef; import org.saturnclient.common.ref.game.MinecraftClientRef; import org.saturnclient.common.ref.render.WindowRef; import org.saturnclient.impl.ui.SaturnScreenFabric; +import org.saturnclient.impl.ui.Screens; import org.saturnclient.ui.SaturnScreen; import org.saturnclient.ui.screens.TitleMenu; import org.spongepowered.asm.mixin.Mixin; @@ -16,7 +16,6 @@ import org.spongepowered.asm.mixin.Shadow; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen; import net.minecraft.client.gui.screens.options.OptionsScreen; import net.minecraft.client.gui.screens.worldselection.SelectWorldScreen; @@ -43,9 +42,6 @@ public abstract class MinecraftClientMixin implements MinecraftClientRef { @Shadow private User user; - @Shadow - public abstract void setScreen(@Nullable Screen screen); - @Shadow public abstract void stop(); @@ -75,7 +71,7 @@ public abstract class MinecraftClientMixin implements MinecraftClientRef { @Override public void setScreen(SaturnScreen screen) { - this.setScreen(new SaturnScreenFabric(screen)); + Screens.set(new SaturnScreenFabric(screen)); } @Override @@ -84,18 +80,18 @@ public abstract class MinecraftClientMixin implements MinecraftClientRef { switch (screen) { case SelectWorld: - this.setScreen(new SelectWorldScreen(exitTarget)); + Screens.set(new SelectWorldScreen(exitTarget)); break; case Multiplayer: - this.setScreen(new JoinMultiplayerScreen(exitTarget)); + Screens.set(new JoinMultiplayerScreen(exitTarget)); break; case Options: //? if >=26.1 { - /*this.setScreen(new OptionsScreen(exitTarget, this.options, ((Minecraft) (Object) this).level != null)); + /*Screens.set(new OptionsScreen(exitTarget, this.options, ((Minecraft) (Object) this).level != null)); *///?} else - this.setScreen(new OptionsScreen(exitTarget, this.options)); + Screens.set(new OptionsScreen(exitTarget, this.options)); break; } } diff --git a/src/main/java/org/saturnclient/impl/mixins/StatusEffectMixin.java b/src/main/java/org/saturnclient/impl/mixins/StatusEffectMixin.java index 1d06413..9c9f9b3 100644 --- a/src/main/java/org/saturnclient/impl/mixins/StatusEffectMixin.java +++ b/src/main/java/org/saturnclient/impl/mixins/StatusEffectMixin.java @@ -8,6 +8,9 @@ import org.saturnclient.common.ref.game.EffectRef; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; +//? if >=26.2 { +/*import net.minecraft.client.gui.Hud; +*///?} else import net.minecraft.client.gui.Gui; import net.minecraft.world.effect.MobEffect; import net.minecraft.world.effect.MobEffectInstance; @@ -55,6 +58,9 @@ public abstract class StatusEffectMixin implements EffectRef { //? if >=1.21.6 { @Override public IdentifierRef getIconId() { + //? if >=26.2 { + /*Identifier id = Hud.getMobEffectSprite(getEffect()); + *///?} else Identifier id = Gui.getMobEffectSprite(getEffect()); return IdentifierRef.of(id.getNamespace(), "textures/" + id.getPath() + ".png"); diff --git a/src/main/java/org/saturnclient/impl/provider/GLFWProviderImpl.java b/src/main/java/org/saturnclient/impl/provider/GLFWProviderImpl.java index 85eb7ba..a2756ed 100644 --- a/src/main/java/org/saturnclient/impl/provider/GLFWProviderImpl.java +++ b/src/main/java/org/saturnclient/impl/provider/GLFWProviderImpl.java @@ -2,6 +2,7 @@ package org.saturnclient.impl.provider; import org.lwjgl.glfw.GLFW; import org.saturnclient.common.provider.GLFWProvider; +import org.saturnclient.impl.ui.Screens; import org.saturnclient.saturnclient.SaturnClient; public class GLFWProviderImpl implements GLFWProvider { @@ -12,7 +13,7 @@ public class GLFWProviderImpl implements GLFWProvider { //?} else //long window = SaturnClient.client.getWindow().getWindow(); return GLFW.glfwGetKey(window, key) == GLFW.GLFW_PRESS - && SaturnClient.client.screen == null; + && Screens.current() == null; } @Override diff --git a/src/main/java/org/saturnclient/impl/provider/SaturnProviderImpl.java b/src/main/java/org/saturnclient/impl/provider/SaturnProviderImpl.java index e76c79d..a000892 100644 --- a/src/main/java/org/saturnclient/impl/provider/SaturnProviderImpl.java +++ b/src/main/java/org/saturnclient/impl/provider/SaturnProviderImpl.java @@ -86,8 +86,13 @@ public class SaturnProviderImpl implements SaturnProvider { @Override public void showWarning(String title, String message) { Minecraft client = Minecraft.getInstance(); + //? if >=26.2 { + /*client.execute(() -> SystemToast.add(client.gui.toastManager(), WARNING_TOAST, Component.literal(title), + Component.literal(message))); + *///?} else { client.execute(() -> client.getToastManager().addToast( SystemToast.multiline(client, WARNING_TOAST, Component.literal(title), Component.literal(message)))); + //?} } @Override diff --git a/src/main/java/org/saturnclient/impl/ui/GlassRenderer.java b/src/main/java/org/saturnclient/impl/ui/GlassRenderer.java index fbcd17a..8e8ccaa 100644 --- a/src/main/java/org/saturnclient/impl/ui/GlassRenderer.java +++ b/src/main/java/org/saturnclient/impl/ui/GlassRenderer.java @@ -37,6 +37,10 @@ import net.minecraft.client.renderer.PostChain; import net.minecraft.client.renderer.RenderPipelines; import com.mojang.blaze3d.pipeline.TextureTarget; import net.minecraft.client.renderer.LevelTargetBundle; +//? if >=26.2 { +/*import com.mojang.blaze3d.GpuFormat; +import net.minecraft.client.renderer.BindGroupLayouts; +*///?} //? if >=26.1 { /*import net.minecraft.client.renderer.Projection; import net.minecraft.client.renderer.ProjectionMatrixBuffer; @@ -51,6 +55,9 @@ public final class GlassRenderer { public static final RenderPipeline PIPELINE = RenderPipeline.builder(RenderPipelines.GUI_TEXTURED_SNIPPET) .withLocation(Identifier.fromNamespaceAndPath("saturnclient", "pipeline/glass")) .withFragmentShader(Identifier.fromNamespaceAndPath("saturnclient", "core/glass")) + //? if >=26.2 { + /*.withBindGroupLayout(BindGroupLayouts.SAMPLER1) + *///?} else .withSampler("Sampler1") // The transforms block ends with LineWidth before 1.21.11 (see glass.fsh). //? if <1.21.11 @@ -81,12 +88,18 @@ public final class GlassRenderer { // Copies the main framebuffer and blurs the copy by blur. Call once a frame for each blur, while // the screen builds its GUI, before drawing glass with it. public static void captureBackdrop(int blur) { + //? if >=26.2 { + /*RenderTarget main = SaturnClient.client.gameRenderer.mainRenderTarget(); + *///?} else RenderTarget main = SaturnClient.client.getMainRenderTarget(); int width = main.width; int height = main.height; TextureTarget backdrop = BACKDROPS.get(blur); if (backdrop == null) { + //? if >=26.2 { + /*backdrop = new TextureTarget("Saturn glass " + blur, width, height, false, GpuFormat.RGBA8_UNORM); + *///?} else backdrop = new TextureTarget("Saturn glass " + blur, width, height, false); BACKDROPS.put(blur, backdrop); } else if (backdrop.width != width || backdrop.height != height) { diff --git a/src/main/java/org/saturnclient/impl/ui/SaturnScreenFabric.java b/src/main/java/org/saturnclient/impl/ui/SaturnScreenFabric.java index ede40e2..68d15a7 100644 --- a/src/main/java/org/saturnclient/impl/ui/SaturnScreenFabric.java +++ b/src/main/java/org/saturnclient/impl/ui/SaturnScreenFabric.java @@ -146,7 +146,9 @@ public class SaturnScreenFabric extends Screen implements ScreenProvider { // The panorama renderer has no opacity (and takes no delta) from 1.21.6, so it's drawn fully // opaque. if (isShowingPanorama()) { - //? if >=26.1 { + //? if >=26.2 { + /*ROTATING_PANORAMA_RENDERER.extractRenderState(context, this.width, this.height); + *///?} else if >=26.1 { /*ROTATING_PANORAMA_RENDERER.extractRenderState(context, this.width, this.height, true); *///?} else ROTATING_PANORAMA_RENDERER.render(context, this.width, this.height, true); diff --git a/src/main/java/org/saturnclient/impl/ui/Screens.java b/src/main/java/org/saturnclient/impl/ui/Screens.java new file mode 100644 index 0000000..24ca159 --- /dev/null +++ b/src/main/java/org/saturnclient/impl/ui/Screens.java @@ -0,0 +1,33 @@ +package org.saturnclient.impl.ui; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; + +/** The open screen and overlay, which Minecraft holds before 26.2 and its Gui from 26.2. */ +public final class Screens { + private Screens() { + } + + public static @Nullable Screen current() { + //? if >=26.2 { + /*return Minecraft.getInstance().gui.screen(); + *///?} else + return Minecraft.getInstance().screen; + } + + public static void set(@Nullable Screen screen) { + //? if >=26.2 { + /*Minecraft.getInstance().gui.setScreen(screen); + *///?} else + Minecraft.getInstance().setScreen(screen); + } + + public static void clearOverlay() { + //? if >=26.2 { + /*Minecraft.getInstance().gui.setOverlay(null); + *///?} else + Minecraft.getInstance().setOverlay(null); + } +} diff --git a/src/main/java/org/saturnclient/saturnclient/event/KeyInputHandler.java b/src/main/java/org/saturnclient/saturnclient/event/KeyInputHandler.java index bd5bdbb..d304670 100644 --- a/src/main/java/org/saturnclient/saturnclient/event/KeyInputHandler.java +++ b/src/main/java/org/saturnclient/saturnclient/event/KeyInputHandler.java @@ -16,6 +16,7 @@ import org.saturnclient.common.provider.Providers; import org.saturnclient.client.ServiceClient; import org.saturnclient.emotes.fabric.Emotes; import org.saturnclient.impl.ui.SaturnScreenFabric; +import org.saturnclient.impl.ui.Screens; import org.saturnclient.ui.screens.EmoteWheel; import org.saturnclient.ui.screens.ShiftMenu; @@ -69,9 +70,9 @@ public class KeyInputHandler { && !InputConstants.isKeyDown(client.getWindow(), GLFW.GLFW_KEY_F3)) { //?} else //&& !InputConstants.isKeyDown(client.getWindow().getWindow(), GLFW.GLFW_KEY_F3)) { - if (client.screen == null) { + if (Screens.current() == null) { Providers.saturn.getClient().setScreen(new EmoteWheel()); - } else if (client.screen instanceof SaturnScreenFabric saturnScreen + } else if (Screens.current() instanceof SaturnScreenFabric saturnScreen && saturnScreen.screen instanceof EmoteWheel) { saturnScreen.onClose(); } diff --git a/src/main/java/org/saturnclient/saturnclient/mixin/GuiRendererMixin.java b/src/main/java/org/saturnclient/saturnclient/mixin/GuiRendererMixin.java index 2cdd373..a02eca4 100644 --- a/src/main/java/org/saturnclient/saturnclient/mixin/GuiRendererMixin.java +++ b/src/main/java/org/saturnclient/saturnclient/mixin/GuiRendererMixin.java @@ -3,10 +3,10 @@ package org.saturnclient.saturnclient.mixin; //? if >=26.1 { /*import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; -import net.minecraft.client.Minecraft; import net.minecraft.client.gui.render.GuiRenderer; import net.minecraft.client.renderer.CubeMap; import org.saturnclient.impl.ui.SaturnScreenFabric; +import org.saturnclient.impl.ui.Screens; import org.spongepowered.asm.mixin.injection.At; *///?} else import net.minecraft.client.Minecraft; @@ -24,7 +24,7 @@ import org.spongepowered.asm.mixin.Mixin; public class GuiRendererMixin { @WrapOperation(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/CubeMap;render(FF)V")) private void saturn$drawSaturnPanorama(CubeMap cubeMap, float xRot, float yRot, Operation original) { - boolean saturnScreen = Minecraft.getInstance().screen instanceof SaturnScreenFabric; + boolean saturnScreen = Screens.current() instanceof SaturnScreenFabric; original.call(saturnScreen ? SaturnScreenFabric.PANORAMA_RENDERER : cubeMap, xRot, yRot); } } diff --git a/src/main/java/org/saturnclient/saturnclient/mixin/SetScreenMixin.java b/src/main/java/org/saturnclient/saturnclient/mixin/SetScreenMixin.java index e5e5624..ab68586 100644 --- a/src/main/java/org/saturnclient/saturnclient/mixin/SetScreenMixin.java +++ b/src/main/java/org/saturnclient/saturnclient/mixin/SetScreenMixin.java @@ -8,9 +8,15 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.ModifyVariable; import net.minecraft.client.Minecraft; +//? if >=26.2 +//import net.minecraft.client.gui.Gui; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.TitleScreen; +// Screens are opened through Gui from 26.2 (through Minecraft before). +//? if >=26.2 { +/*@Mixin(Gui.class) +*///?} else @Mixin(Minecraft.class) public class SetScreenMixin { @ModifyVariable(method = "setScreen", at = @At("HEAD"), argsOnly = true) @@ -18,7 +24,7 @@ public class SetScreenMixin { if (Config.saturnTitleScreen.value) { if (screen instanceof TitleScreen) return new SaturnScreenFabric(new TitleMenu()); - if (screen == null && ((Minecraft) (Object) this).level == null) + if (screen == null && Minecraft.getInstance().level == null) return new SaturnScreenFabric(new TitleMenu()); } diff --git a/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java b/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java index 791a20f..340a1fd 100644 --- a/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java +++ b/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java @@ -1,5 +1,7 @@ package org.saturnclient.saturnclient.mixin; +import org.saturnclient.impl.ui.Screens; + import java.util.Optional; import java.util.function.Consumer; @@ -110,7 +112,7 @@ public abstract class SplashOverlayMixin { // Remove overlay after fade-out completes if (fadeOutProgress >= 2.0F) { - minecraft.setOverlay(null); + Screens.clearOverlay(); } // Handle reload completion and exceptions @@ -118,11 +120,11 @@ public abstract class SplashOverlayMixin { } private void renderCurrentScreen(GuiGraphics context, int mouseX, int mouseY, float delta) { - if (minecraft.screen != null) { + if (Screens.current() != null) { //? if >=26.1 { - /*minecraft.screen.extractRenderState(context, mouseX, mouseY, delta); + /*Screens.current().extractRenderState(context, mouseX, mouseY, delta); *///?} else - minecraft.screen.render(context, mouseX, mouseY, delta); + Screens.current().render(context, mouseX, mouseY, delta); } } @@ -173,11 +175,11 @@ public abstract class SplashOverlayMixin { //? if >=1.21.11 Window window = minecraft.getWindow(); - if (minecraft.screen != null) { + if (Screens.current() != null) { //? if >=1.21.11 { - minecraft.screen.init(window.getGuiScaledWidth(), window.getGuiScaledHeight()); + Screens.current().init(window.getGuiScaledWidth(), window.getGuiScaledHeight()); //?} else - //minecraft.screen.init(minecraft, context.guiWidth(), context.guiHeight()); + //Screens.current().init(minecraft, context.guiWidth(), context.guiHeight()); } } } diff --git a/versions/26.2/gradle.properties b/versions/26.2/gradle.properties new file mode 100644 index 0000000..9e3e1ba --- /dev/null +++ b/versions/26.2/gradle.properties @@ -0,0 +1,17 @@ +# Done to increase the memory available to gradle. +org.gradle.jvmargs=-Xmx1G +org.gradle.parallel=true + +# IntelliJ IDEA is not yet fully compatible with configuration cache, see: https://github.com/FabricMC/fabric-loom/issues/1349 +org.gradle.configuration-cache=false + +# Fabric Properties +# check these on https://fabricmc.net/develop +minecraft_version=26.2 +loader_version=0.19.5 + +fabric_api_version=0.161.0+26.2 + +# Mod Properties +maven_group=org.saturnclient +archives_base_name=saturn-client From 6b1b79062c52bc750186adf064a2a1b43d5500c3 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 30 Sep 2026 21:31:32 +0200 Subject: [PATCH 2/4] Note in the version guide that version blocks can nest Stonecutter 0.9 writes an inactive block inside another as /^ ... ^/, so nesting works (GlassRenderer relies on it since 26.1). else if is still clearer for several ranges of the same code. Co-Authored-By: Claude Opus 5.5 --- VERSION_GUIDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION_GUIDE.md b/VERSION_GUIDE.md index 22eae91..e2d0624 100644 --- a/VERSION_GUIDE.md +++ b/VERSION_GUIDE.md @@ -50,7 +50,7 @@ When a newer version leaves something out that an older one implements, keep the A whole class that only exists for some versions (such as `MatrixStackMixin`, which only makes sense before 1.21.6's GUI rewrite) can keep an empty class body on the other versions. Before splitting a class like that, look for a hook that works on every version: nametags used to hook label rendering, which 1.21.9 changed, and now replace the name while the render state is built (`LivingEntityRendererMixin`), with no version condition at all. An empty mixin changes nothing, and it keeps the mixin configs the same for every version, since JSON can't hold version comments. -Inactive code sits inside a `/* … */` comment, so it can't contain `/* … */` comments itself: use `//` comments in code that's only active for some versions. For the same reason, don't nest multi-line version blocks; use `//?} else if {` for more than two version ranges, as in `PlayerEntityRendererMixin` and `SaturnRenderState`. +Inactive code sits inside a `/* … */` comment, so it can't contain `/* … */` comments itself: use `//` comments in code that's only active for some versions. Version blocks can nest (Stonecutter writes an inactive block inside another as `/^ … ^/`, as in `GlassRenderer`), but for more than two ranges of the same thing, prefer `//?} else if {`, as in `PlayerEntityRendererMixin` and `SaturnRenderState`. - `./gradlew "Set active project to "` (or the Stonecutter IntelliJ plugin) rewrites `src/` so that version's code is the uncommented one, for editing it in the IDE. - `./gradlew "Reset active project"` switches back to 1.21.11. **Run it before committing**, so `src/` is always committed with 1.21.11 active. From 86f326a8b401590a9b8c3ab1621197dc4c43d3b5 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 30 Sep 2026 21:46:14 +0200 Subject: [PATCH 3/4] Blur the glass backdrop once the frame's world is drawn on 26.x From 26.1 the GUI is built before the frame is drawn. The glass copies the main framebuffer while the GUI is built, so it copied the last frame, glass included, and each frame blurred the previous blur until it bloomed. On 26.1+ captureBackdrop now only prepares the backdrop and queues its blur. GuiRendererMixin copies and blurs the queued backdrops as the GUI renderer is about to draw, with the world and panorama drawn and no GUI yet. Pending backdrops are never released before they're drawn. Co-Authored-By: Claude Opus 5.5 --- .../saturnclient/impl/ui/GlassRenderer.java | 52 +++++++++++++++++-- .../saturnclient/mixin/GuiRendererMixin.java | 13 ++++- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/saturnclient/impl/ui/GlassRenderer.java b/src/main/java/org/saturnclient/impl/ui/GlassRenderer.java index 8e8ccaa..d1264fd 100644 --- a/src/main/java/org/saturnclient/impl/ui/GlassRenderer.java +++ b/src/main/java/org/saturnclient/impl/ui/GlassRenderer.java @@ -17,7 +17,11 @@ package org.saturnclient.impl.ui; //? if >=1.21.6 { import java.io.Reader; import java.util.HashMap; +//? if >=26.1 +//import java.util.LinkedHashSet; import java.util.Map; +//? if >=26.1 +//import java.util.Set; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -85,13 +89,24 @@ public final class GlassRenderer { private GlassRenderer() { } + // From 26.1 the GUI is built before the frame's world is drawn, so the main framebuffer still + // holds the last frame, glass included. The copies wait until the GUI renderer is about to draw + // (GuiRendererMixin), when it holds this frame's world and panorama. + //? if >=26.1 { + /*private static final Set PENDING = new LinkedHashSet<>(); + *///?} + + private static RenderTarget mainTarget() { + //? if >=26.2 { + /*return SaturnClient.client.gameRenderer.mainRenderTarget(); + *///?} else + return SaturnClient.client.getMainRenderTarget(); + } + // Copies the main framebuffer and blurs the copy by blur. Call once a frame for each blur, while // the screen builds its GUI, before drawing glass with it. public static void captureBackdrop(int blur) { - //? if >=26.2 { - /*RenderTarget main = SaturnClient.client.gameRenderer.mainRenderTarget(); - *///?} else - RenderTarget main = SaturnClient.client.getMainRenderTarget(); + RenderTarget main = mainTarget(); int width = main.width; int height = main.height; @@ -108,6 +123,29 @@ public final class GlassRenderer { //? if <1.21.11 //backdrop.getColorTexture().setTextureFilter(FilterMode.LINEAR, false); + //? if >=26.1 { + /*PENDING.add(blur); + } + + // Copies and blurs the backdrops the GUI asked for this frame. GuiRendererMixin calls it once the + // world and the panorama are drawn, before the GUI. + public static void blurPendingBackdrops() { + RenderTarget main = mainTarget(); + for (int blur : PENDING) { + TextureTarget backdrop = BACKDROPS.get(blur); + // The GUI already refers to the backdrop's texture, so after a resize it keeps the last + // frame's until the next one. + if (backdrop != null && backdrop.width == main.width && backdrop.height == main.height) { + blurBackdrop(main, backdrop, blur); + } + } + PENDING.clear(); + } + + private static void blurBackdrop(RenderTarget main, TextureTarget backdrop, int blur) { + int width = main.width; + int height = main.height; + *///?} RenderSystem.getDevice().createCommandEncoder().copyTextureToTexture( main.getColorTexture(), backdrop.getColorTexture(), 0, 0, 0, 0, 0, width, height); @@ -132,6 +170,9 @@ public final class GlassRenderer { private static void release(int kept) { BACKDROPS.entrySet().removeIf(entry -> { boolean unused = entry.getKey() != kept && !isSet(entry.getKey()); + // The GUI already refers to this frame's pending backdrops. + //? if >=26.1 + //unused &= !PENDING.contains(entry.getKey()); if (unused) { entry.getValue().destroyBuffers(); } @@ -139,6 +180,9 @@ public final class GlassRenderer { }); BLURS.entrySet().removeIf(entry -> { boolean unused = entry.getKey() != kept && !isSet(entry.getKey()); + // The GUI already refers to this frame's pending backdrops. + //? if >=26.1 + //unused &= !PENDING.contains(entry.getKey()); if (unused && entry.getValue() != null) { entry.getValue().close(); } diff --git a/src/main/java/org/saturnclient/saturnclient/mixin/GuiRendererMixin.java b/src/main/java/org/saturnclient/saturnclient/mixin/GuiRendererMixin.java index a02eca4..3181893 100644 --- a/src/main/java/org/saturnclient/saturnclient/mixin/GuiRendererMixin.java +++ b/src/main/java/org/saturnclient/saturnclient/mixin/GuiRendererMixin.java @@ -5,16 +5,20 @@ package org.saturnclient.saturnclient.mixin; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import net.minecraft.client.gui.render.GuiRenderer; import net.minecraft.client.renderer.CubeMap; +import org.saturnclient.impl.ui.GlassRenderer; import org.saturnclient.impl.ui.SaturnScreenFabric; import org.saturnclient.impl.ui.Screens; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; *///?} else import net.minecraft.client.Minecraft; import org.spongepowered.asm.mixin.Mixin; /** * From 26.1 GuiRenderer draws the menu panorama from its own cube map, which is vanilla's. While a - * Saturn screen is open, this draws Saturn's panorama instead. Before 26.1 SaturnScreenFabric draws + * Saturn screen is open, this draws Saturn's panorama instead. It also blurs the glass backdrops + * once the panorama is drawn. Before 26.1 SaturnScreenFabric draws * the panorama itself, so this mixin is empty there, and it targets Minecraft because GuiRenderer * doesn't exist before 1.21.6. It stays registered, keeping saturnclient.mixins.json the same for * every version. @@ -27,6 +31,13 @@ public class GuiRendererMixin { boolean saturnScreen = Screens.current() instanceof SaturnScreenFabric; original.call(saturnScreen ? SaturnScreenFabric.PANORAMA_RENDERER : cubeMap, xRot, yRot); } + + // The glass backdrops are copied here, with the frame's world and panorama drawn and the GUI not + // yet: the GUI is built before the frame is drawn (see GlassRenderer). + @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/render/GuiRenderer;prepare()V")) + private void saturn$blurGlassBackdrops(CallbackInfo ci) { + GlassRenderer.blurPendingBackdrops(); + } } *///?} else { @Mixin(Minecraft.class) From 4b776663bb8482bdf75eb46bca7106011779d42c Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 30 Sep 2026 21:49:25 +0200 Subject: [PATCH 4/4] Point common at the removal of the old theme color upgrade Co-Authored-By: Claude Opus 5.5 --- common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common b/common index 6537385..e8f686b 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit 653738568a9c7b1f9ab76bd86eb01cedc166f057 +Subproject commit e8f686b2d3dfdb609cf5104927a7d0455c2214bb