diff --git a/.gitea/workflows/check.yml b/.gitea/workflows/check.yml index c884a0c..28c1857 100644 --- a/.gitea/workflows/check.yml +++ b/.gitea/workflows/check.yml @@ -19,15 +19,20 @@ jobs: # The JDK comes from the runner's tool cache. `cache: gradle` stores Gradle's # dependencies, Loom's Minecraft cache and the build cache in the runner's # actions cache. - - name: Set up Java 21 + # 1.21.x builds with Java 21 and 26.x with Java 25. The last version listed is the default + # (Gradle runs on it), and the Gradle commands below point toolchain detection at both. + - name: Set up Java 25 and 21 uses: actions/setup-java@v5 with: distribution: temurin - java-version: 21 + java-version: | + 25 + 21 cache: gradle - name: Compile all versions run: | chmod +x gradlew # --continue reports every version that fails, not just the first. - ./gradlew compileJava --no-daemon --stacktrace --continue + ./gradlew compileJava --no-daemon --stacktrace --continue \ + -Porg.gradle.java.installations.fromEnv=JAVA_HOME_21_X64,JAVA_HOME_25_X64 diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 1f1f194..12bbcee 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -47,11 +47,15 @@ jobs: # The JDK comes from the runner's tool cache. `cache: gradle` stores Gradle's # dependencies, Loom's Minecraft cache and the build cache in the runner's # actions cache. - - name: Set up Java 21 + # 1.21.x builds with Java 21 and 26.x with Java 25. The last version listed is the default + # (Gradle runs on it), and the Gradle commands below point toolchain detection at both. + - name: Set up Java 25 and 21 uses: actions/setup-java@v5 with: distribution: temurin - java-version: 21 + java-version: | + 25 + 21 cache: gradle - name: Build all versions @@ -59,7 +63,8 @@ jobs: chmod +x gradlew # buildAll remaps every Minecraft version (two at a time, to stay within the # Gradle heap) into build/allJars. - ./gradlew buildAll --no-daemon --stacktrace + ./gradlew buildAll --no-daemon --stacktrace \ + -Porg.gradle.java.installations.fromEnv=JAVA_HOME_21_X64,JAVA_HOME_25_X64 - name: Upload jars to the release env: diff --git a/README.MD b/README.MD index c0c8aab..a13c2d1 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**, with one build per version. 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 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. ## Features @@ -51,7 +51,7 @@ cd saturnclient ./gradlew :1.21.11:runClient # launch a development client ``` -Building needs Java 21. +Building needs Java 21. The 26.x versions compile with Java 25, which Gradle downloads when it isn't installed. ## Contributing diff --git a/VERSION_GUIDE.md b/VERSION_GUIDE.md index febd0d4..431f8e0 100644 --- a/VERSION_GUIDE.md +++ b/VERSION_GUIDE.md @@ -4,21 +4,23 @@ 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) 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, and 26.1) 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). - `versions//`: each version's `gradle.properties` (Minecraft, Fabric Loader and Fabric API versions), plus its `build/` and `run/` folders. Each version is the Gradle project `:`. -`settings.gradle.kts` lists the versions in `stonecutterVersions`. The root `build.gradle.kts` builds each version, and `stonecutter.gradle.kts` configures the root project (shared repositories, the `common` wiring and `buildAll`). Loom's version is `loom_version` in the root `gradle.properties`. +`settings.gradle.kts` lists the versions in `stonecutterVersions`. The root `build.gradle.kts` builds each version, and `stonecutter.gradle.kts` configures the root project (shared repositories, the `common` wiring and `buildAll`). Loom's version is `loomx.loom_version` in the root `gradle.properties`. + +Minecraft is obfuscated up to 1.21.11 and ships with Mojang's names from 26.1. The `loom-back-compat` plugin (`loomx`) applies the matching Loom for each version: the remapping one for 1.21.x, whose jar is `remapJar`'s output, and the plain one for 26.x, whose jar is `jar`'s. 26.x also compiles with Java 25 (21 before), set per version in `build.gradle.kts`; Gradle downloads a missing JDK through the foojay resolver, and CI installs both. An optional `minecraft_compat` in a version's `gradle.properties` widens the Minecraft versions its jar loads on, as 26.1's does for its hotfixes. ## Mappings 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 renamed a few classes within 1.21.x. `src/` uses the newest names, and `stonecutter.gradle.kts` lists replacements that swap in the older name when Stonecutter processes an older version (for example `Identifier`, which was `ResourceLocation` before 1.21.11, and `AvatarRenderState`, which was `PlayerRenderState` before 1.21.9). 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. 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 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. +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. ## Version-specific code with Stonecutter @@ -72,5 +74,5 @@ For bigger changes, `./gradlew buildAll` builds every version's jar into `build/ 1. Create `versions//gradle.properties` with `minecraft_version`, `loader_version` and `fabric_api_version` (copy 1.21.11's and change the values; they're listed at ). Loom's version is shared, in the root `gradle.properties`. 2. Add `` to `stonecutterVersions` in `settings.gradle.kts`. -3. Build it with `./gradlew ::compileJava` and put what Minecraft changed behind version comments. Check the mixin configs in `src/main/resources/*.mixins.json` as well: a mixin whose target changed fails at startup, not at compile time, so launch it with `./gradlew ::runClient`. +3. Build it with `./gradlew ::compileJava` and put what Minecraft changed behind version comments. Check the mixin configs in `src/main/resources/*.mixins.json` as well: a mixin whose target changed fails at startup (or when its class first loads, such as in a world), not at compile time, so launch it with `./gradlew ::runClient`. 4. The release workflow builds everything through `buildAll`, and "Publish to Modrinth" takes the game version from each jar's name, so neither needs changing. diff --git a/build.gradle.kts b/build.gradle.kts index afb70ae..90e9b88 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,11 +1,14 @@ // Builds one Minecraft version from the shared src/. Stonecutter runs this for each version in // stonecutterVersions (settings.gradle.kts), with that version's versions//gradle.properties. plugins { - id("net.fabricmc.fabric-loom-remap") + id("dev.kikugie.loom-back-compat") `maven-publish` } val minecraftVersion = property("minecraft_version") as String +// The Minecraft versions the jar loads on (fabric.mod.json); a range where hotfixes share a jar. +val minecraftCompat = findProperty("minecraft_compat") as String? ?: minecraftVersion +val javaVersion = if (sc.current.parsed >= "26.1") 25 else 21 val loaderVersion = property("loader_version") as String version = "${property("mod_version")}+$minecraftVersion" @@ -17,7 +20,7 @@ base { dependencies { minecraft("com.mojang:minecraft:$minecraftVersion") - mappings(loom.officialMojangMappings()) + loomx.applyMojangMappings() modImplementation("net.fabricmc:fabric-loader:$loaderVersion") modImplementation("net.fabricmc.fabric-api:fabric-api:${property("fabric_api_version")}") @@ -42,23 +45,23 @@ dependencies { tasks.processResources { val values = mapOf( "version" to project.version.toString(), - "minecraft_version" to minecraftVersion, + "minecraft_version" to minecraftCompat, "loader_version" to loaderVersion, + "java" to "JAVA_$javaVersion", ) inputs.properties(values) filteringCharset = "UTF-8" - filesMatching("fabric.mod.json") { expand(values) } + filesMatching(listOf("fabric.mod.json", "*.mixins.json")) { expand(values) } } tasks.withType().configureEach { - options.release = 21 + options.release = javaVersion } java { // Loom attaches the sources jar to remapSourcesJar and build. withSourcesJar() - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 + toolchain.languageVersion = JavaLanguageVersion.of(javaVersion) } tasks.jar { diff --git a/gradle.properties b/gradle.properties index 65fad62..1e1dc8d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ org.gradle.configuration-cache=false mod_version=0.1.4-beta # Saturn's emote library (git.selimaj.dev/saturnclientmc/emotes) -emotes_version=0.1.1 +emotes_version=0.1.2 -# Loom, for every Minecraft version (read in settings.gradle.kts). -loom_version=1.17.21 +# Loom, for every Minecraft version (read by loom-back-compat). +loomx.loom_version=1.17.21 diff --git a/settings.gradle.kts b/settings.gradle.kts index 8fcbb8a..26c4dc0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,16 +5,15 @@ pluginManagement { gradlePluginPortal() mavenCentral() } - - // Loom's version for build.gradle.kts, which can't read properties in its plugins block. - val loom_version: String by settings - plugins { - id("net.fabricmc.fabric-loom-remap") version loom_version - } } plugins { + // Downloads the Java toolchain a version needs (Java 25 for 26.1+) when it isn't installed. + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" id("dev.kikugie.stonecutter") version "0.9.8" + // Picks the Loom variant per version: remapping for 1.21.x, plain for the unobfuscated 26.1+. + // Its version is loomx.loom_version in gradle.properties. + id("dev.kikugie.loom-back-compat") version "0.4.2" } rootProject.name = "saturn-client" @@ -23,7 +22,10 @@ include("common") // Versions built by Stonecutter from the shared src/ (see stonecutter.gradle.kts). Each one's // 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") +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", +) stonecutter { create(rootProject) { diff --git a/src/main/java/org/saturnclient/impl/cosmetics/utils/ShaderUtils.java b/src/main/java/org/saturnclient/impl/cosmetics/utils/ShaderUtils.java index 4284c36..40bab50 100644 --- a/src/main/java/org/saturnclient/impl/cosmetics/utils/ShaderUtils.java +++ b/src/main/java/org/saturnclient/impl/cosmetics/utils/ShaderUtils.java @@ -26,6 +26,10 @@ import net.minecraft.client.renderer.blockentity.AbstractEndPortalRenderer; import net.minecraft.client.renderer.blockentity.TheEndPortalRenderer; *///?} import net.minecraft.resources.Identifier; +//? if >=26.1 { +/*import java.util.HashMap; +import java.util.Map; +*///?} public class ShaderUtils { // Render pipelines exist from 1.21.5; before that the layer names a shader program directly. @@ -87,8 +91,27 @@ public class ShaderUtils { } } + // Cosmetics are drawn unlit, without the lightmap or the directional shading, as the dragon's + // alpha pipeline drew them before 26.1. 26.1 dropped that pipeline, so this is the entity shader + // with its lighting and overlay turned off. + //? if >=26.1 { + /*public static final RenderPipeline UNLIT_CUTOUT_PIPELINE = RenderPipelines.register( + RenderPipeline.builder(new RenderPipeline.Snippet[] { + RenderPipelines.ENTITY_EMISSIVE_SNIPPET }) + .withLocation(Identifier.fromNamespaceAndPath("saturnclient", "pipeline/unlit_cutout")) + .withShaderDefine("ALPHA_CUTOUT", 0.1f) + .withShaderDefine("NO_CARDINAL_LIGHTING") + .withShaderDefine("NO_OVERLAY") + .withCull(false) + .build()); + private static final Map UNLIT_CUTOUT = new HashMap<>(); + *///?} + private static RenderType entityAlpha(Identifier texture) { - //? if >=1.21.11 { + //? if >=26.1 { + /*return UNLIT_CUTOUT.computeIfAbsent(texture, id -> RenderType.create("saturn_unlit_cutout", + RenderSetup.builder(UNLIT_CUTOUT_PIPELINE).withTexture("Sampler0", id).createRenderSetup())); + *///?} else if >=1.21.11 { return RenderTypes.dragonExplosionAlpha(texture); //?} else //return RenderType.dragonExplosionAlpha(texture); diff --git a/src/main/java/org/saturnclient/impl/features/mixins/entity/CameraMixin.java b/src/main/java/org/saturnclient/impl/features/mixins/entity/CameraMixin.java index 82d71ec..e0a8c5e 100644 --- a/src/main/java/org/saturnclient/impl/features/mixins/entity/CameraMixin.java +++ b/src/main/java/org/saturnclient/impl/features/mixins/entity/CameraMixin.java @@ -1,6 +1,7 @@ package org.saturnclient.impl.features.mixins.entity; import org.saturnclient.impl.features.entity.CameraOverriddenEntity; +import org.saturnclient.impl.features.render.ZoomFov; import org.saturnclient.mod.mods.FreelookMod; import org.saturnclient.saturnclient.SaturnClient; import org.spongepowered.asm.mixin.Mixin; @@ -9,6 +10,8 @@ import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +//? if >=26.1 +//import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import net.minecraft.client.player.LocalPlayer; import net.minecraft.client.Camera; @@ -26,17 +29,38 @@ public abstract class CameraMixin { @Shadow protected abstract void setRotation(float yaw, float pitch); + // Freelook keeps the camera's rotation after vanilla points it along the entity's view. From 26.1 + // that happens in alignWithEntity, which takes the camera's own entity. + //? if >=26.1 { + /*@Shadow + public abstract Entity entity(); + + @Inject(method = "alignWithEntity", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setRotation(FF)V", ordinal = 1, shift = At.Shift.AFTER)) + public void lockRotation(float tickDelta, CallbackInfo ci) { + saturn$lockRotation(entity()); + } + + // The camera works out the FOV from 26.1 (GameRenderer.getFov before). + @Inject(method = "calculateFov", at = @At("RETURN"), cancellable = true) + private void onCalculateFov(float tickDelta, CallbackInfoReturnable cir) { + cir.setReturnValue(ZoomFov.apply(cir.getReturnValue())); + } + *///?} else if >=1.21.11 { @Inject(method = "setup", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setRotation(FF)V", ordinal = 1, shift = At.Shift.AFTER)) - public void lockRotation( - //? if >=1.21.11 { - Level focusedBlock, - //?} else - //BlockGetter focusedBlock, - Entity cameraEntity, - boolean isThirdPerson, - boolean isFrontFacing, - float tickDelta, - CallbackInfo ci) { + public void lockRotation(Level focusedBlock, Entity cameraEntity, boolean isThirdPerson, boolean isFrontFacing, + float tickDelta, CallbackInfo ci) { + saturn$lockRotation(cameraEntity); + } + //?} else { + /*@Inject(method = "setup", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setRotation(FF)V", ordinal = 1, shift = At.Shift.AFTER)) + public void lockRotation(BlockGetter focusedBlock, Entity cameraEntity, boolean isThirdPerson, boolean isFrontFacing, + float tickDelta, CallbackInfo ci) { + saturn$lockRotation(cameraEntity); + } + *///?} + + @Unique + private void saturn$lockRotation(Entity cameraEntity) { if (FreelookMod.isFreeLooking() && cameraEntity instanceof LocalPlayer) { CameraOverriddenEntity cameraOverriddenEntity = (CameraOverriddenEntity) cameraEntity; 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 79c1eaf..a8f8bbe 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,8 +8,7 @@ import net.minecraft.client.renderer.GameRenderer; //? if >=1.21.6 import org.saturnclient.impl.ui.SaturnScreenFabric; -import org.saturnclient.mod.mods.FreelookMod; -import org.saturnclient.mod.mods.ZoomMod; +import org.saturnclient.impl.features.render.ZoomFov; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -18,11 +17,11 @@ import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; /** - * Overrides the game's field-of-view when {@link ZoomMod} is active. + * Overrides the game's field-of-view when {@link org.saturnclient.mod.mods.ZoomMod} is active. * * The old code checked both {@code Zoom.isZooming} and * {@code Zoom.shouldZoom()} separately. The refactored - * {@link ZoomMod#shouldZoom()} already ORs both conditions + * {@link org.saturnclient.mod.mods.ZoomMod#shouldZoom()} already ORs both conditions * ({@code enabled.value && isZooming}), so only one check is needed. * * No {@link org.saturnclient.common.module.ModProvider} is required @@ -36,20 +35,21 @@ public class GameRendererMixin { * After the vanilla FOV has been computed, divide it by the zoom * level to produce the narrowed field-of-view. */ + // From 26.1 the camera works out the FOV, so CameraMixin applies the zoom there. + //? if <26.1 { @Inject(method = "getFov", at = @At("RETURN"), cancellable = true) private void onGetFov(CallbackInfoReturnable cir) { - if (ZoomMod.shouldZoom()) { - float zoomedFov = cir.getReturnValue() / ZoomMod.getZoomLevel(); - cir.setReturnValue(zoomedFov); - } else if (FreelookMod.shouldZoomOut()) { - float zoomedFov = cir.getReturnValue() / FreelookMod.getZoomLevel(); - cir.setReturnValue(zoomedFov); - } + cir.setReturnValue(ZoomFov.apply(cir.getReturnValue())); } + //?} //? if >=1.21.6 { // From 1.21.6 the menu blur's strength is the vanilla setting, read here every frame. While a // Saturn screen is open, it's Saturn's background blur instead, faded in with the screen. + // From 26.1 the setting is read while extracting the options' render state. + //? if >=26.1 + //@Redirect(method = "extractOptions", at = @At(value = "INVOKE", + //? if <26.1 @Redirect(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Options;getMenuBackgroundBlurriness()I")) private int useSaturnBlur(Options options) { diff --git a/src/main/java/org/saturnclient/impl/features/mixins/render/LightmapTextureManagerMixin.java b/src/main/java/org/saturnclient/impl/features/mixins/render/LightmapTextureManagerMixin.java index d07ea86..466c32d 100644 --- a/src/main/java/org/saturnclient/impl/features/mixins/render/LightmapTextureManagerMixin.java +++ b/src/main/java/org/saturnclient/impl/features/mixins/render/LightmapTextureManagerMixin.java @@ -3,6 +3,9 @@ package org.saturnclient.impl.features.mixins.render; // Code approach adapted from Gamma Utils: https://github.com/Sjouwer/gamma-utils import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +//? if >=26.1 { +/*import net.minecraft.client.renderer.LightmapRenderStateExtractor; +*///?} else import net.minecraft.client.renderer.LightTexture; import org.saturnclient.mod.mods.FullbrightMod; @@ -18,6 +21,10 @@ import org.spongepowered.asm.mixin.injection.At; * it is now {@link FullbrightMod}. The injection target and * strategy are unchanged. */ +// From 26.1 the gamma is worked out while extracting the lightmap's render state. +//? if >=26.1 { +/*@Mixin(LightmapRenderStateExtractor.class) +*///?} else @Mixin(LightTexture.class) public class LightmapTextureManagerMixin { @@ -25,6 +32,9 @@ public class LightmapTextureManagerMixin { * If fullbright is active, substitute the feature's brightness * value for whatever vanilla calculated. */ + //? if >=26.1 { + /*@ModifyExpressionValue(method = "extract", at = @At(value = "INVOKE", target = "Ljava/lang/Math;max(FF)F", ordinal = 0)) + *///?} else @ModifyExpressionValue(method = "updateLightTexture", at = @At(value = "INVOKE", target = "Ljava/lang/Math;max(FF)F", ordinal = 0)) private float modifyGamma(float original) { if (FullbrightMod.shouldOverrideBrightness()) { 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 acec77b..b827d0d 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 @@ -40,6 +40,9 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(Gui.class) public class RenderMixin { + //? if >=26.1 { + /*@Inject(method = "extractChat", at = @At("TAIL")) + *///?} else @Inject(method = "renderChat", at = @At("TAIL")) public void renderMods(GuiGraphics context, DeltaTracker tickCounter, CallbackInfo ci) { Font textRenderer = SaturnClient.client.font; diff --git a/src/main/java/org/saturnclient/impl/features/render/ZoomFov.java b/src/main/java/org/saturnclient/impl/features/render/ZoomFov.java new file mode 100644 index 0000000..ae67f57 --- /dev/null +++ b/src/main/java/org/saturnclient/impl/features/render/ZoomFov.java @@ -0,0 +1,19 @@ +package org.saturnclient.impl.features.render; + +import org.saturnclient.mod.mods.FreelookMod; +import org.saturnclient.mod.mods.ZoomMod; + +/** The zoom mods' change to the field of view, applied where each version computes it. */ +public final class ZoomFov { + private ZoomFov() { + } + + public static float apply(float fov) { + if (ZoomMod.shouldZoom()) { + return fov / ZoomMod.getZoomLevel(); + } else if (FreelookMod.shouldZoomOut()) { + return fov / FreelookMod.getZoomLevel(); + } + return fov; + } +} diff --git a/src/main/java/org/saturnclient/impl/mixins/MinecraftClientMixin.java b/src/main/java/org/saturnclient/impl/mixins/MinecraftClientMixin.java index 8d4754f..b34a78e 100644 --- a/src/main/java/org/saturnclient/impl/mixins/MinecraftClientMixin.java +++ b/src/main/java/org/saturnclient/impl/mixins/MinecraftClientMixin.java @@ -92,6 +92,9 @@ public abstract class MinecraftClientMixin implements MinecraftClientRef { break; case Options: + //? if >=26.1 { + /*this.setScreen(new OptionsScreen(exitTarget, this.options, ((Minecraft) (Object) this).level != null)); + *///?} else this.setScreen(new OptionsScreen(exitTarget, this.options)); break; } diff --git a/src/main/java/org/saturnclient/impl/ui/GlassRenderer.java b/src/main/java/org/saturnclient/impl/ui/GlassRenderer.java index fc4149d..fbcd17a 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.1 { +/*import net.minecraft.client.renderer.Projection; +import net.minecraft.client.renderer.ProjectionMatrixBuffer; +*///?} else import net.minecraft.client.renderer.CachedOrthoProjectionMatrixBuffer; import net.minecraft.client.renderer.texture.AbstractTexture; import net.minecraft.client.gui.render.TextureSetup; @@ -54,6 +58,15 @@ public final class GlassRenderer { .build(); private static final Identifier BLUR = Identifier.withDefaultNamespace("post_effect/blur.json"); + //? if >=26.1 { + /*private static final Projection PROJECTION = new Projection(); + private static final ProjectionMatrixBuffer PROJECTION_BUFFER = new ProjectionMatrixBuffer("saturn glass"); + + static { + // As vanilla sets up its post chains' projection; the chain sizes it when it runs. + PROJECTION.setupOrtho(0.1f, 1000f, 1f, 1f, false); + } + *///?} else private static final CachedOrthoProjectionMatrixBuffer PROJECTION = new CachedOrthoProjectionMatrixBuffer("saturn glass", 0.1f, 1000f, false); // For the blurs' intermediate targets, freed once unused for a few frames private static final CrossFrameResourcePool POOL = new CrossFrameResourcePool(3); @@ -140,6 +153,9 @@ public final class GlassRenderer { PostChainConfig pipeline = PostChainConfig.CODEC.parse(JsonOps.INSTANCE, json).getOrThrow(); return PostChain.load(pipeline, SaturnClient.client.getTextureManager(), + //? if >=26.1 { + /*LevelTargetBundle.MAIN_TARGETS, Identifier.fromNamespaceAndPath("saturnclient", "glass_blur_" + blur), PROJECTION, PROJECTION_BUFFER); + *///?} else LevelTargetBundle.MAIN_TARGETS, Identifier.fromNamespaceAndPath("saturnclient", "glass_blur_" + blur), PROJECTION); } catch (Exception e) { SaturnClient.LOGGER.error("Couldn't load the glass blur", e); diff --git a/src/main/java/org/saturnclient/impl/ui/RenderScopeImpl.java b/src/main/java/org/saturnclient/impl/ui/RenderScopeImpl.java index 806cc20..5d30546 100644 --- a/src/main/java/org/saturnclient/impl/ui/RenderScopeImpl.java +++ b/src/main/java/org/saturnclient/impl/ui/RenderScopeImpl.java @@ -325,6 +325,9 @@ public class RenderScopeImpl implements RenderScope { entity, seed); try { + //? if >=26.1 { + /*this.state.submitItem(new GuiItemRenderState( + *///?} else this.state.submitItem(new GuiItemRenderState(stack.getItem().getName().toString(), new Matrix3x2f(this.matrices.stack), keyedItemRenderState, x, y, this.scissorStack.peekLast())); diff --git a/src/main/java/org/saturnclient/impl/ui/SaturnScreenFabric.java b/src/main/java/org/saturnclient/impl/ui/SaturnScreenFabric.java index 1cf7ee6..ede40e2 100644 --- a/src/main/java/org/saturnclient/impl/ui/SaturnScreenFabric.java +++ b/src/main/java/org/saturnclient/impl/ui/SaturnScreenFabric.java @@ -13,6 +13,9 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.input.MouseButtonEvent; import net.minecraft.client.renderer.CubeMap; import net.minecraft.client.gui.GuiGraphics; +//? if >=26.1 { +/*import net.minecraft.client.renderer.Panorama; +*///?} else import net.minecraft.client.renderer.PanoramaRenderer; import net.minecraft.client.gui.screens.Screen; //? if >=1.21.6 { @@ -34,13 +37,18 @@ import net.minecraft.resources.Identifier; public class SaturnScreenFabric extends Screen implements ScreenProvider { protected static final Identifier PANORAMA = Identifier.fromNamespaceAndPath("saturnclient", "textures/gui/title/background/panorama"); - protected static final CubeMap PANORAMA_RENDERER = new CubeMap(PANORAMA); + public static final CubeMap PANORAMA_RENDERER = new CubeMap(PANORAMA); + // From 26.1 vanilla draws the panorama from its own cube map; GuiRendererMixin swaps in Saturn's. + //? if >=26.1 { + /*public static final Panorama ROTATING_PANORAMA_RENDERER = new Panorama(); + *///?} else { public static final PanoramaRenderer ROTATING_PANORAMA_RENDERER; static { ROTATING_PANORAMA_RENDERER = new PanoramaRenderer(PANORAMA_RENDERER); } + //?} //? if <1.21.6 //private final CrossFrameResourcePool pool = new CrossFrameResourcePool(3); @@ -81,6 +89,9 @@ public class SaturnScreenFabric extends Screen implements ScreenProvider { //? if <1.21.6 //@SuppressWarnings("deprecation") @Override + //? if >=26.1 { + /*public void extractRenderState(GuiGraphics context, int mouseX, int mouseY, float delta) { + *///?} else public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { if (screen.start == null) { screen.start = Instant.now(); @@ -128,10 +139,16 @@ public class SaturnScreenFabric extends Screen implements ScreenProvider { // Vanilla's background would cover Saturn's panorama, and blurs by the vanilla setting. The // strength of this blur comes from GameRendererMixin, which gives Saturn's setting instead. @Override + //? if >=26.1 { + /*public void extractBackground(GuiGraphics context, int mouseX, int mouseY, float delta) { + *///?} else public void renderBackground(GuiGraphics context, int mouseX, int mouseY, float delta) { // 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 { + /*ROTATING_PANORAMA_RENDERER.extractRenderState(context, this.width, this.height, true); + *///?} else ROTATING_PANORAMA_RENDERER.render(context, this.width, this.height, true); } @@ -140,6 +157,9 @@ public class SaturnScreenFabric extends Screen implements ScreenProvider { } if (minecraft.level != null) { + //? if >=26.1 { + /*extractMenuBackground(context); + *///?} else renderMenuBackground(context); } } diff --git a/src/main/java/org/saturnclient/saturnclient/event/KeyInputHandler.java b/src/main/java/org/saturnclient/saturnclient/event/KeyInputHandler.java index c54d853..bd5bdbb 100644 --- a/src/main/java/org/saturnclient/saturnclient/event/KeyInputHandler.java +++ b/src/main/java/org/saturnclient/saturnclient/event/KeyInputHandler.java @@ -1,6 +1,9 @@ package org.saturnclient.saturnclient.event; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +//? if >=26.1 { +/*import net.fabricmc.fabric.api.client.keymapping.v1.KeyMappingHelper; +*///?} else import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.minecraft.client.KeyMapping; import com.mojang.blaze3d.platform.InputConstants; @@ -32,7 +35,13 @@ public class KeyInputHandler { private static KeyMapping mainMenuKeyBinding; public static void register() { - //? if >=1.21.9 { + //? if >=26.1 { + /*mainMenuKeyBinding = KeyMappingHelper.registerKeyMapping( + new KeyMapping( + MAIN_MENU_KEY_ID, + DEFAULT_MENU_KEY, + KeyMapping.Category.register(KEY_CATEGORY))); + *///?} else if >=1.21.9 { mainMenuKeyBinding = KeyBindingHelper.registerKeyBinding( new KeyMapping( MAIN_MENU_KEY_ID, diff --git a/src/main/java/org/saturnclient/saturnclient/mixin/GuiRendererMixin.java b/src/main/java/org/saturnclient/saturnclient/mixin/GuiRendererMixin.java new file mode 100644 index 0000000..2cdd373 --- /dev/null +++ b/src/main/java/org/saturnclient/saturnclient/mixin/GuiRendererMixin.java @@ -0,0 +1,35 @@ +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.spongepowered.asm.mixin.injection.At; +*///?} 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 + * 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. + */ +//? if >=26.1 { +/*@Mixin(GuiRenderer.class) +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; + original.call(saturnScreen ? SaturnScreenFabric.PANORAMA_RENDERER : cubeMap, xRot, yRot); + } +} +*///?} else { +@Mixin(Minecraft.class) +public class GuiRendererMixin { +} +//?} diff --git a/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java b/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java index 9772149..791a20f 100644 --- a/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java +++ b/src/main/java/org/saturnclient/saturnclient/mixin/SplashOverlayMixin.java @@ -54,6 +54,9 @@ public abstract class SplashOverlayMixin { * @reason Replace vanilla splash rendering completely but keep exit logic */ @Overwrite + //? if >=26.1 { + /*public void extractRenderState(GuiGraphics context, int mouseX, int mouseY, float delta) { + *///?} else public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { int screenWidth = context.guiWidth(); int screenHeight = context.guiHeight(); @@ -116,6 +119,9 @@ public abstract class SplashOverlayMixin { private void renderCurrentScreen(GuiGraphics context, int mouseX, int mouseY, float delta) { if (minecraft.screen != null) { + //? if >=26.1 { + /*minecraft.screen.extractRenderState(context, mouseX, mouseY, delta); + *///?} else minecraft.screen.render(context, mouseX, mouseY, delta); } } @@ -129,7 +135,9 @@ public abstract class SplashOverlayMixin { private void renderProgressBar(RenderScope scope, GuiGraphics context, int screenWidth, int screenHeight, int alpha) { int progressY = ((screenHeight - 5) / 2) + 25; - //? if >=1.21.11 { + //? if >=26.1 { + /*context.outline((screenWidth - 120) / 2, progressY, 120, 5, Theme.withAlpha(alpha, 0xFFFFFFFF)); + *///?} else if >=1.21.11 { context.renderOutline((screenWidth - 120) / 2, progressY, 120, 5, Theme.withAlpha(alpha, 0xFFFFFFFF)); //?} else if >=1.21.9 { /*context.submitOutline((screenWidth - 120) / 2, progressY, 120, 5, Theme.withAlpha(alpha, 0xFFFFFFFF)); diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index b5d0419..bb90202 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -22,7 +22,7 @@ ], "depends": { "fabricloader": ">=${loader_version}", - "fabric": "*", + "fabric-api": "*", "minecraft": "${minecraft_version}" } } diff --git a/src/main/resources/features.mixins.json b/src/main/resources/features.mixins.json index 4c74195..6a42779 100644 --- a/src/main/resources/features.mixins.json +++ b/src/main/resources/features.mixins.json @@ -2,7 +2,7 @@ "required": true, "minVersion": "0.8", "package": "org.saturnclient.impl.features.mixins", - "compatibilityLevel": "JAVA_21", + "compatibilityLevel": "${java}", "client": [ "render.GameRendererMixin", "render.LightmapTextureManagerMixin", diff --git a/src/main/resources/impl.mixins.json b/src/main/resources/impl.mixins.json index 094792b..5420903 100644 --- a/src/main/resources/impl.mixins.json +++ b/src/main/resources/impl.mixins.json @@ -2,7 +2,7 @@ "required": true, "minVersion": "0.8", "package": "org.saturnclient.impl.mixins", - "compatibilityLevel": "JAVA_21", + "compatibilityLevel": "${java}", "client": [ "IdentifierMixin", "ItemStackMixin", diff --git a/src/main/resources/saturnclient.mixins.json b/src/main/resources/saturnclient.mixins.json index d831845..94b4c34 100644 --- a/src/main/resources/saturnclient.mixins.json +++ b/src/main/resources/saturnclient.mixins.json @@ -2,14 +2,15 @@ "required": true, "minVersion": "0.8", "package": "org.saturnclient.saturnclient.mixin", - "compatibilityLevel": "JAVA_21", + "compatibilityLevel": "${java}", "client": [ "TabListMixin", "PlayerEntityRendererMixin", "WhoAmI", "DrawContextAccessor", "SplashOverlayMixin", - "SetScreenMixin" + "SetScreenMixin", + "GuiRendererMixin" ], "injectors": { "defaultRequire": 1 diff --git a/stonecutter.gradle.kts b/stonecutter.gradle.kts index 98b6560..f6092e1 100644 --- a/stonecutter.gradle.kts +++ b/stonecutter.gradle.kts @@ -6,16 +6,23 @@ plugins { // "Reset active project" before committing. stonecutter active "1.21.11" -// src/ uses the newest Mojang names. These classes were renamed in the versions given, so Stonecutter -// swaps in the older names when it processes an older version. +// src/ uses 1.21.11's Mojang names (the active version). These names changed in the versions given, +// so Stonecutter swaps in each version's name when it processes it. stonecutter parameters { for ((since, old, new) in listOf( Triple("1.21.11", "ResourceLocation", "Identifier"), Triple("1.21.9", "PlayerRenderer", "AvatarRenderer"), Triple("1.21.9", "PlayerRenderState", "AvatarRenderState"), + Triple("26.1", "GuiGraphics", "GuiGraphicsExtractor"), + Triple("26.1", "net.minecraft.client.gui.render.state", "net.minecraft.client.renderer.state.gui"), + // GuiRenderState's methods + Triple("26.1", "submitGuiElement", "addGuiElement"), + Triple("26.1", "submitText", "addText"), + Triple("26.1", "submitItem", "addItem"), + Triple("26.1", "submitPicturesInPictureState", "addPicturesInPictureState"), )) { replacements.regex(eval(current.version, ">=$since")) { - replace("\\b$old\\b", new, "\\b$new\\b", old) + replace("\\b${Regex.escape(old)}\\b", new, "\\b${Regex.escape(new)}\\b", old) } } } @@ -69,17 +76,22 @@ subprojects { tasks.register("buildAll") { group = "build" - description = "Builds every Minecraft version's remapped jar and collects them in build/allJars" + description = "Builds every Minecraft version's mod jar and collects them in build/allJars" - val remapJars = subprojects.flatMap { it.tasks.matching { task -> task.name == "remapJar" } } - dependsOn(remapJars) + // The mod jar is remapJar's output for 1.21.x, and jar's for 26.1+, which isn't obfuscated. + val modJars = subprojects.filter { it.name != "common" }.flatMap { project -> + val unobfuscated = project.name.substringBefore('.').toInt() >= 26 + val task = if (unobfuscated) "jar" else "remapJar" + project.tasks.matching { it.name == task } + } + dependsOn(modJars) doLast { val outputDir = layout.buildDirectory.dir("allJars").get().asFile outputDir.mkdirs() - remapJars.map { (it as AbstractArchiveTask).archiveFile.get().asFile } + modJars.map { (it as AbstractArchiveTask).archiveFile.get().asFile } .filter { it.exists() && !it.name.contains("sources") } .forEach { it.copyTo(outputDir.resolve(it.name), overwrite = true) } - println("All remapped jars collected in: $outputDir") + println("All mod jars collected in: $outputDir") } } diff --git a/versions/26.1/gradle.properties b/versions/26.1/gradle.properties new file mode 100644 index 0000000..01554f7 --- /dev/null +++ b/versions/26.1/gradle.properties @@ -0,0 +1,19 @@ +# 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.1 +# Built against 26.1 and allowed on its hotfixes (26.1.1, 26.1.2). +minecraft_compat=~26.1 +loader_version=0.19.5 + +fabric_api_version=0.145.1+26.1 + +# Mod Properties +maven_group=org.saturnclient +archives_base_name=saturn-client