Compare commits

..
19 Commits
Author SHA1 Message Date
selimaj-dev dd91f3844a Merge pull request 'Bump the version to 0.1.2-beta' (#37) from release-0.1.2-beta into master
Release / build (release) Successful in 4m34s
Reviewed-on: #37
2026-09-27 17:21:05 +00:00
selimaj-devandclaude b7cf3092da Bump the version to 0.1.2-beta
Check / compile (pull_request) Successful in 2m32s
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 19:18:29 +02:00
selimaj-dev 60afc12893 Merge pull request 'Bring in the UI fixes' (#36) from ui-fixes into master
Reviewed-on: #36
2026-09-27 17:16:14 +00:00
selimaj-devandclaude 8677837a8f Point common at the UI fixes
Check / compile (pull_request) Successful in 2m35s
common#7 fixes the emote wheel and restyles the buttons, title screen and
HUD editor, which the two commits before this one support.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 19:15:55 +02:00
selimaj-devandclaude 02f6cf7d71 Give common the player's skin texture
SaturnProvider.getPlayerSkin returns the signed-in player's skin texture,
or the default skin until it loads, for the title screen's player chip.
1.21.9 moved SkinTextures and renamed the skin provider's lookup, so each
side has its own version.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 19:01:42 +02:00
selimaj-devandclaude 85a46c0bba Close the emote wheel with its key
The emote wheel key only opened the wheel. It now closes it too while the
wheel is open. This is done in the key handler: closing from the screen
would reopen it on the next tick, since the key is still down by then.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 18:43:27 +02:00
selimaj-dev 30f65bd914 Merge pull request 'Bring in the UI redesign' (#35) from ui-redesign into master
Reviewed-on: #35
2026-09-27 16:32:20 +00:00
selimaj-devandclaude bedb45c8aa Point common at the UI redesign
Check / compile (pull_request) Successful in 2m39s
common#6 redesigns the mod menu, mod and client settings and cosmetics
screens, which the two commits before this one build on.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 18:28:20 +02:00
selimaj-devandclaude 1602520624 Blur behind Saturn's screens on 1.21.6+
From 1.21.6 vanilla's renderBackground drew its own panorama and blurred
at the vanilla setting, then Saturn's panorama was drawn unblurred on top.
SaturnScreenFabric now draws Saturn's panorama in renderBackground, blurs
it and keeps vanilla's darkening in a world, and GameRendererMixin gives
the blur shader Saturn's strength while a Saturn screen is open. Before
1.21.6 the existing blur reads the same setting.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 18:26:42 +02:00
selimaj-devandclaude f69c379ae1 Draw the player preview at a given yaw
EntityDrawerImpl follows common's new EntityDrawer contract: it draws the
player filling the preview box, turned by the yaw the cosmetics screen's
drag sets, and returns false when there's no player. The angle no longer
follows the cursor.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 17:33:25 +02:00
selimaj-dev fbd0e23fb3 Merge pull request 'Title panorama on 1.21.6+, and Fabric Loader 0.19.5 everywhere' (#34) from panorama-1.21.6 into master
Reviewed-on: #34
2026-09-27 05:34:32 +00:00
selimaj-devandclaude 8dbb1d84c1 Use Fabric Loader 0.19.5 for every version
Check / compile (pull_request) Successful in 2m42s
1.21.4-1.21.6 declared loader 0.18.x and the others 0.19.0, but the
bundled emotes-fabric pulls in loader 0.19.5, and its nested mod already
requires loader >=0.19. So the declared version was never what ran: in
dev, Loom's loader libraries came from the declared version while the
loader itself was 0.19.5, and 1.21.4/1.21.6 sometimes started with
sponge-mixin 0.17.0 and crashed (NoClassDefFoundError:
IAdviceProvider). Declaring 0.19.5 makes dev match what runs, and makes
fabric.mod.json's fabricloader requirement (>=0.19.5) honest.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 07:26:11 +02:00
selimaj-devandclaude ab70c67a4f Draw the title panorama behind Saturn screens on 1.21.6+
The call was commented out on 1.21.6+ (#30). From 1.21.6 CubeMapRenderer
draws from one cube map texture instead of six face textures, and it
crashes ("Texture view does not exist") if that texture isn't loaded.
Saturn's preload registered the six faces, and the vanilla
registerTextures only stores the cube map for the next resource reload,
which has already run by the time preload does.

- preload registers a CubemapTexture for the panorama through the
  TextureManager overload that loads it immediately, on 1.21.6+. Before
  1.21.6 it registers the six faces as before.
- The panorama is only drawn once preload has run, which also covers the
  splash overlay drawing the title menu on its first frames.
- 1.21.6+'s RotatingCubeMapRenderer.render has no opacity, so the
  panorama is fully opaque there. The background blur stays 1.21.5-only
  (TODO, #23).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 07:26:11 +02:00
selimaj-dev 0a6aa90e23 Merge pull request 'Custom nametags on every version (replace the name in the render state)' (#33) from nametags-render-state into master
Reviewed-on: #33
2026-09-27 05:14:42 +00:00
selimaj-devandclaude 22a7152f0d Replace nametags while the render state is built, on every version
Check / compile (pull_request) Successful in 2m49s
NametagsMixin hooked EntityRenderer.renderLabelIfPresent with a
VertexConsumerProvider, which 1.21.9 moved to the render command queue, so
custom nametags only worked before 1.21.9 (#30).

LivingEntityRendererMixin already captures health and entity type at the
end of LivingEntityRenderer.updateRenderState. It now also applies
NametagsMod's replacement to state.displayName there, the label the
renderer draws later. This works the same on every version, so
NametagsMixin and its features.mixins.json entry are gone.

As before, only entities that already show a label get a replacement. For
players, the role icon is added after this (PlayerEntityRendererMixin), so
it now comes before the replaced name and keeps its colour, instead of
being folded into the replacement text.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 07:13:54 +02:00
selimaj-dev b5ca9dbc8f Merge pull request 'Fill five version gaps from #30' (#32) from version-gaps-small into master
Reviewed-on: #32
2026-09-27 05:09:20 +00:00
selimaj-devandclaude 48118f6a84 Fill five version gaps from #30
Check / compile (pull_request) Successful in 2m31s
- World age on 1.21.11: World.getTime() was a shortcut for
  getLevelProperties().getTime(), which every version has, so
  getWorldAge uses that everywhere (the TPS feature and day counter got 0
  on 1.21.11).
- Screen resize on 1.21.11: override resize(int, int), the new signature.
- Splash loading-bar border on 1.21.9+: drawBorder was renamed
  drawStrokedRectangle.
- Drag deltas on 1.21.9+: mouseDragged(Click, offsetX, offsetY) gets the
  deltas as offsetX/offsetY; Saturn's screens got 0.0 before.
- Role icon in player names on 1.21.4-1.21.7: only storing the player in
  the render state needs Fabric API's render state data (1.21.8+). The
  updateRenderState hook now runs on every version and only the setData
  call is limited to 1.21.8+.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 06:59:18 +02:00
selimaj-dev 771ed93992 Merge pull request 'Build every Minecraft version from one shared src/ with Stonecutter' (#31) from stonecutter into master
Reviewed-on: #31
2026-09-27 04:50:14 +00:00
selimaj-devandclaude a8e6f14022 Fix the Release workflow's comment on how buildAll remaps
Check / compile (pull_request) Successful in 2m22s
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 06:48:57 +02:00
26 changed files with 195 additions and 192 deletions
+2 -1
View File
@@ -57,7 +57,8 @@ jobs:
- name: Build all versions - name: Build all versions
run: | run: |
chmod +x gradlew chmod +x gradlew
# buildAll remaps every versions/* project in parallel into build/allJars. # 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
- name: Upload jars to the release - name: Upload jars to the release
+2 -2
View File
@@ -36,9 +36,9 @@ import net.minecraft.client.render.RenderSetup;
Stonecutter rewrites inactive code into its own layout when switching versions (a single inactive line becomes `//code`), so write it that way to begin with, or run a switch and a reset before committing. Stonecutter rewrites inactive code into its own layout when switching versions (a single inactive line becomes `//code`), so write it that way to begin with, or run a switch and a reset before committing.
When a newer version leaves something out that an older one implements, keep the older version's code behind a condition and mark the newer branch with a `TODO`, rather than dropping it for every version. `WorldFeatureImpl.getWorldAge` and `SaturnScreenFabric.resize` are examples. Search for `TODO` to find what newer versions still lack. When a newer version leaves something out that an older one implements, keep the older version's code behind a condition and mark the newer branch with a `TODO`, rather than dropping it for every version. The panorama in `SaturnScreenFabric.render` is an example. Search for `TODO` to find what newer versions still lack (tracked in #30), and remove the condition once a version gap is fixed.
A whole class that only exists for some versions (such as `NametagsMixin`, which hooks a method that 1.21.9 changed) can keep an empty class body on the other versions. An empty mixin changes nothing, and it keeps the mixin configs the same for every version, since JSON can't hold version comments. 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 <condition> {` 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. For the same reason, don't nest multi-line version blocks; use `//?} else if <condition> {` for more than two version ranges, as in `PlayerEntityRendererMixin` and `SaturnRenderState`.
+1 -1
Submodule common updated: 15fa973553...ef0ead6bf8
+1 -1
View File
@@ -6,7 +6,7 @@ org.gradle.caching=true
# IntelliJ IDEA is not yet fully compatible with configuration cache, see: https://github.com/FabricMC/fabric-loom/issues/1349 # IntelliJ IDEA is not yet fully compatible with configuration cache, see: https://github.com/FabricMC/fabric-loom/issues/1349
org.gradle.configuration-cache=false org.gradle.configuration-cache=false
mod_version=0.1.1-beta mod_version=0.1.2-beta
# Saturn's emote library (git.selimaj.dev/saturnclientmc/emotes) # Saturn's emote library (git.selimaj.dev/saturnclientmc/emotes)
emotes_version=0.1.0 emotes_version=0.1.0
@@ -12,10 +12,9 @@ import net.minecraft.entity.LivingEntity;
* Fabric implementation of {@link EntityFeature}. * Fabric implementation of {@link EntityFeature}.
* *
* Crosshair targeting is resolved via {@link MinecraftClient#crosshairTarget}. * Crosshair targeting is resolved via {@link MinecraftClient#crosshairTarget}.
* Entity state for nametag rendering is supplied at render time by * Entity state for nametags is built by LivingEntityRendererMixin, which
* {@link org.saturnclient.modules.mixins.LivingEntityRendererMixin} which * writes health into the render state and then constructs a
* writes into the render state; {@link NametagsMixin} then constructs * {@link RenderStateEntityState} from that snapshot for NametagsMod.
* a {@link RenderStateEntityState} from the already-extracted snapshot.
*/ */
public class EntityFeatureImpl implements EntityFeature { public class EntityFeatureImpl implements EntityFeature {
@@ -43,7 +42,7 @@ public class EntityFeatureImpl implements EntityFeature {
* Minecraft render-state object) as a platform-neutral * Minecraft render-state object) as a platform-neutral
* {@link EntityState} for use by {@link NametagsFeature}. * {@link EntityState} for use by {@link NametagsFeature}.
* *
* This is constructed inside {@link NametagsMixin} where both the * This is constructed in LivingEntityRendererMixin, where both the
* render-state mixin interface and the entity name are available. * render-state mixin interface and the entity name are available.
*/ */
public static final class RenderStateEntityState implements EntityState { public static final class RenderStateEntityState implements EntityState {
@@ -7,8 +7,8 @@ import org.saturnclient.common.feature.EntityFeature;
* {@link net.minecraft.client.render.entity.state.LivingEntityRenderState}. * {@link net.minecraft.client.render.entity.state.LivingEntityRenderState}.
* *
* Carries the entity's health snapshot and classification into the render * Carries the entity's health snapshot and classification into the render
* pipeline so that {@link NametagsMixin} can read them without touching * render state, so NametagsMod's replacement nametag (applied in
* the live entity again. * LivingEntityRendererMixin) can use them without touching the live entity again.
*/ */
public interface HealthRenderState { public interface HealthRenderState {
@@ -1,101 +0,0 @@
package org.saturnclient.impl.features.mixins.misc;
import net.minecraft.client.render.entity.EntityRenderer;
import org.spongepowered.asm.mixin.Mixin;
//? if <1.21.9 {
/*import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.entity.state.EntityRenderState;
import net.minecraft.client.render.entity.state.LivingEntityRenderState;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.Text;
import org.saturnclient.impl.features.entity.EntityFeatureImpl;
import org.saturnclient.impl.features.entity.HealthRenderState;
import org.saturnclient.impl.features.mixins.render.LivingEntityRenderStateMixin;
import org.saturnclient.mod.mods.NametagsMod;
import org.spongepowered.asm.mixin.Shadow;
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;
*///?}
/**
* Intercepts nametag rendering and replaces the displayed text with
* the string produced by {@link NametagsMod#getNametagString}.
*
* Key changes from the original:
*
* <ul>
* <li>The old {@code NametagsFabric.EntityStateImpl} bridge class is
* replaced by {@link EntityFeatureImpl.RenderStateEntityState},
* which wraps the {@link HealthRenderState} already injected into
* the render state by {@link LivingEntityRenderStateMixin}.</li>
* <li>The feature import changes from {@code Nametags} to
* {@link NametagsMod}; the two static methods
* ({@code shouldReplaceName} / {@code getNametagString}) are
* unchanged in signature.</li>
* <li>The entity's display name is extracted from the render state's
* {@code nameVisible} / text fields where available, falling back
* to the plain text string passed to this method.</li>
* </ul>
*
* The re-entrancy guard ({@code saturn$rendering}) is kept intact to
* prevent infinite recursion when we call {@code renderLabelIfPresent}
* with the replacement text.
*/
@Mixin(EntityRenderer.class)
public abstract class NametagsMixin {
// TODO: 1.21.9 moved label rendering to the render queue (renderLabelIfPresent no longer takes a
// VertexConsumerProvider), so custom nametags only work on older versions. On 1.21.9+ this mixin is
// empty but stays registered, so features.mixins.json is the same for every version.
//? if <1.21.9 {
/*// Guards against the recursive call we make with the replacement text.
@Unique
private static final ThreadLocal<Boolean> saturn$rendering = ThreadLocal.withInitial(() -> false);
@Shadow
protected abstract <S extends EntityRenderState> void renderLabelIfPresent(
S state, Text text,
MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light);
@Inject(method = "renderLabelIfPresent", at = @At("HEAD"), cancellable = true)
private <S extends EntityRenderState> void saturn$replaceNametag(
S state, Text text,
MatrixStack matrices, VertexConsumerProvider vertexConsumers,
int light, CallbackInfo ci) {
// Skip our own recursive call
if (saturn$rendering.get())
return;
if (!NametagsMod.shouldReplaceName())
return;
// Only living entities carry health data
if (!(state instanceof LivingEntityRenderState living))
return;
if (!(living instanceof HealthRenderState hrs))
return;
// Build the platform-neutral EntityState from the render-state snapshot
// (health/type were written by LivingEntityRendererMixin at extract time).
String customName = text != null ? text.getString() : null;
EntityFeatureImpl.RenderStateEntityState entityState = new EntityFeatureImpl.RenderStateEntityState(
customName, hrs);
// Ask the feature for the replacement string
String replacement = NametagsMod.getNametagString(entityState);
if (replacement == null)
return; // feature decided not to replace this entity
ci.cancel();
saturn$rendering.set(true);
try {
renderLabelIfPresent(state, Text.literal(replacement), matrices, vertexConsumers, light);
} finally {
saturn$rendering.set(false);
}
}
*///?}
}
@@ -1,12 +1,20 @@
package org.saturnclient.impl.features.mixins.render; package org.saturnclient.impl.features.mixins.render;
//? if >=1.21.6
import net.minecraft.client.MinecraftClient;
//? if >=1.21.6
import net.minecraft.client.option.GameOptions;
import net.minecraft.client.render.GameRenderer; import net.minecraft.client.render.GameRenderer;
//? if >=1.21.6
import org.saturnclient.impl.ui.SaturnScreenFabric;
import org.saturnclient.mod.mods.FreelookMod; import org.saturnclient.mod.mods.FreelookMod;
import org.saturnclient.mod.mods.ZoomMod; import org.saturnclient.mod.mods.ZoomMod;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
//? if >=1.21.6
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
/** /**
@@ -38,4 +46,18 @@ public class GameRendererMixin {
cir.setReturnValue(zoomedFov); cir.setReturnValue(zoomedFov);
} }
} }
//? 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.
@Redirect(method = "render", at = @At(value = "INVOKE",
target = "Lnet/minecraft/client/option/GameOptions;getMenuBackgroundBlurrinessValue()I"))
private int useSaturnBlur(GameOptions options) {
if (MinecraftClient.getInstance().currentScreen instanceof SaturnScreenFabric saturnScreen) {
return saturnScreen.screen.getBackgroundBlur();
}
return options.getMenuBackgroundBlurrinessValue();
}
//?}
} }
@@ -4,7 +4,6 @@ import net.minecraft.client.render.entity.state.LivingEntityRenderState;
import org.saturnclient.common.feature.EntityFeature; import org.saturnclient.common.feature.EntityFeature;
import org.saturnclient.impl.features.entity.HealthRenderState; import org.saturnclient.impl.features.entity.HealthRenderState;
import org.saturnclient.impl.features.mixins.misc.NametagsMixin;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.Unique;
@@ -19,8 +18,8 @@ import org.spongepowered.asm.mixin.Unique;
* *
* The stored values are written by * The stored values are written by
* {@link LivingEntityRendererMixin#saturn$captureHealth} each time a * {@link LivingEntityRendererMixin#saturn$captureHealth} each time a
* render state is extracted from a live entity, and read by * render state is extracted from a live entity, and read there right away for
* {@link NametagsMixin} at render time. * NametagsMod's replacement nametag.
*/ */
@Mixin(LivingEntityRenderState.class) @Mixin(LivingEntityRenderState.class)
public class LivingEntityRenderStateMixin implements HealthRenderState { public class LivingEntityRenderStateMixin implements HealthRenderState {
@@ -6,24 +6,25 @@ import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.mob.HostileEntity; import net.minecraft.entity.mob.HostileEntity;
import net.minecraft.entity.passive.PassiveEntity; import net.minecraft.entity.passive.PassiveEntity;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.text.Text;
import org.saturnclient.common.feature.EntityFeature; import org.saturnclient.common.feature.EntityFeature;
import org.saturnclient.impl.features.entity.EntityFeatureImpl;
import org.saturnclient.impl.features.entity.HealthRenderState; import org.saturnclient.impl.features.entity.HealthRenderState;
import org.saturnclient.mod.mods.NametagsMod;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/** /**
* Captures each living entity's health and type into the render state * Captures each living entity's health and type into the render state during
* during {@code updateRenderState}, so that nametag rendering can read * {@code updateRenderState}, and applies {@link NametagsMod}'s replacement nametag to the state's
* them without touching the live entity again. * display name.
* *
* Previously imported {@code NametagsInterface.EntityType}; now uses * Replacing the name here, while the render state is built, works the same on every version. Hooking
* {@link EntityFeature.EntityType} — the canonical enum shared across * label rendering instead doesn't: 1.21.9 moved it to the render command queue. Player role icons are
* the whole module system. * added after this, in PlayerEntityRendererMixin.
*
* The injection target and strategy are unchanged from the original.
*/ */
@Mixin(LivingEntityRenderer.class) @Mixin(LivingEntityRenderer.class)
public abstract class LivingEntityRendererMixin<T extends LivingEntity, S extends LivingEntityRenderState> { public abstract class LivingEntityRendererMixin<T extends LivingEntity, S extends LivingEntityRenderState> {
@@ -47,5 +48,15 @@ public abstract class LivingEntityRendererMixin<T extends LivingEntity, S extend
type = EntityFeature.EntityType.OTHER; type = EntityFeature.EntityType.OTHER;
hrs.saturn$setEntityType(type); hrs.saturn$setEntityType(type);
// Only entities that already show a label get a replacement one, as when this hooked label
// rendering.
if (NametagsMod.shouldReplaceName() && state.displayName != null) {
String replacement = NametagsMod.getNametagString(
new EntityFeatureImpl.RenderStateEntityState(state.displayName.getString(), hrs));
if (replacement != null) {
state.displayName = Text.literal(replacement);
}
}
} }
} }
@@ -31,10 +31,7 @@ public class WorldFeatureImpl implements WorldFeature {
*/ */
@Override @Override
public long getWorldAge() { public long getWorldAge() {
//? if >=1.21.11 { // World.getTime() was a shortcut for this, and 1.21.11 removed it.
// TODO: not implemented on 1.21.11 yet. return hasWorld() ? mc.world.getLevelProperties().getTime() : 0L;
return 0L;
//?} else
//return hasWorld() ? mc.world.getTime() : 0L;
} }
} }
@@ -2,11 +2,17 @@ package org.saturnclient.impl.provider;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.util.UUID; import java.util.UUID;
import java.util.function.Supplier;
import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.toast.SystemToast; import net.minecraft.client.toast.SystemToast;
//? if >=1.21.9 {
import net.minecraft.entity.player.SkinTextures;
//?} else
//import net.minecraft.client.util.SkinTextures;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import org.saturnclient.common.provider.SaturnProvider; import org.saturnclient.common.provider.SaturnProvider;
import org.saturnclient.common.ref.asset.IdentifierRef; import org.saturnclient.common.ref.asset.IdentifierRef;
@@ -16,6 +22,9 @@ import org.saturnclient.impl.cosmetics.utils.IdentifierUtils;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
public class SaturnProviderImpl implements SaturnProvider { public class SaturnProviderImpl implements SaturnProvider {
/** Looks up the signed-in player's skin; made once, since the skin provider caches it */
private Supplier<Identifier> playerSkin;
@Override @Override
public MinecraftClientRef getClient() { public MinecraftClientRef getClient() {
return (MinecraftClientRef) SaturnClient.client; return (MinecraftClientRef) SaturnClient.client;
@@ -50,6 +59,23 @@ public class SaturnProviderImpl implements SaturnProvider {
/** Shown longer than the vanilla default, since these ask the player to act. */ /** Shown longer than the vanilla default, since these ask the player to act. */
private static final SystemToast.Type WARNING_TOAST = new SystemToast.Type(15_000L); private static final SystemToast.Type WARNING_TOAST = new SystemToast.Type(15_000L);
@Override
public IdentifierRef getPlayerSkin() {
if (playerSkin == null) {
MinecraftClient client = MinecraftClient.getInstance();
// 1.21.9 moved SkinTextures and renamed the skin provider's lookup.
//? if >=1.21.9 {
Supplier<SkinTextures> textures = client.getSkinProvider().supplySkinTextures(client.getGameProfile(), false);
playerSkin = () -> textures.get().body().texturePath();
//?} else {
/*Supplier<SkinTextures> textures = client.getSkinProvider().getSkinTexturesSupplier(client.getGameProfile());
playerSkin = () -> textures.get().texture();
*///?}
}
return (IdentifierRef) (Object) playerSkin.get();
}
@Override @Override
public String getModVersion() { public String getModVersion() {
return FabricLoader.getInstance().getModContainer(SaturnClient.MOD_ID) return FabricLoader.getInstance().getModContainer(SaturnClient.MOD_ID)
@@ -7,7 +7,6 @@ import org.jetbrains.annotations.Nullable;
import org.joml.Quaternionf; import org.joml.Quaternionf;
import org.joml.Vector3f; import org.joml.Vector3f;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.ui.ElementContext;
import org.saturnclient.ui.RenderScope; import org.saturnclient.ui.RenderScope;
import org.saturnclient.ui.components.SkinPreview.EntityDrawer; import org.saturnclient.ui.components.SkinPreview.EntityDrawer;
@@ -21,33 +20,32 @@ import net.minecraft.entity.LivingEntity;
public class EntityDrawerImpl implements EntityDrawer { public class EntityDrawerImpl implements EntityDrawer {
@Override @Override
public void render(RenderScope renderScope, boolean negativeAngle, float angle, float scale, ElementContext ctx) { public boolean render(RenderScope renderScope, int width, int height, float yaw) {
LivingEntity entity = SaturnClient.client.player; LivingEntity entity = SaturnClient.client.player;
if (entity != null) { if (entity == null) {
drawEntity(renderScope, 0, 0, 75, 78, 30, 0.0625F, ctx.mouseX / scale, ctx.mouseY / scale, return false;
SaturnClient.client.player);
} }
// The player is about 1.8 blocks tall; leave some room above and below.
drawEntity(renderScope, 0, 0, width, height, (int) (height / 2.2F), 0.0625F, yaw, entity);
return true;
} }
// Draws the entity in the box, turned by yaw degrees (0 faces the viewer) with its head straight.
public static void drawEntity(RenderScope renderScope, int x1, int y1, int x2, int y2, int size, float scale, public static void drawEntity(RenderScope renderScope, int x1, int y1, int x2, int y2, int size, float scale,
float mouseX, float mouseY, LivingEntity entity) { float yaw, LivingEntity entity) {
float f = (float) (x1 + x2) / 2.0F;
float g = (float) (y1 + y2) / 2.0F;
renderScope.enableScissor(x1, y1, x2, y2); renderScope.enableScissor(x1, y1, x2, y2);
float h = (float) Math.atan((double) ((f - mouseX) / 40.0F));
float i = (float) Math.atan((double) ((g - mouseY) / 40.0F));
Quaternionf quaternionf = (new Quaternionf()).rotateZ((float) Math.PI); Quaternionf quaternionf = (new Quaternionf()).rotateZ((float) Math.PI);
Quaternionf quaternionf2 = (new Quaternionf()).rotateX(i * 20.0F * ((float) Math.PI / 180F)); Quaternionf quaternionf2 = new Quaternionf();
quaternionf.mul(quaternionf2);
float j = entity.bodyYaw; float j = entity.bodyYaw;
float k = entity.getYaw(); float k = entity.getYaw();
float l = entity.getPitch(); float l = entity.getPitch();
float m = entity.lastHeadYaw; float m = entity.lastHeadYaw;
float n = entity.headYaw; float n = entity.headYaw;
entity.bodyYaw = 180.0F + h * 20.0F; entity.bodyYaw = 180.0F + yaw;
entity.setYaw(180.0F + h * 40.0F); entity.setYaw(180.0F + yaw);
entity.setPitch(-i * 20.0F); entity.setPitch(0.0F);
entity.headYaw = entity.getYaw(); entity.headYaw = entity.getYaw();
entity.lastHeadYaw = entity.getYaw(); entity.lastHeadYaw = entity.getYaw();
float o = entity.getScale(); float o = entity.getScale();
@@ -83,7 +81,6 @@ public class EntityDrawerImpl implements EntityDrawer {
import org.joml.Quaternionf; import org.joml.Quaternionf;
import org.joml.Vector3f; import org.joml.Vector3f;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.ui.ElementContext;
import org.saturnclient.ui.RenderScope; import org.saturnclient.ui.RenderScope;
import org.saturnclient.ui.components.SkinPreview.EntityDrawer; import org.saturnclient.ui.components.SkinPreview.EntityDrawer;
@@ -92,14 +89,12 @@ import net.minecraft.client.render.entity.EntityRenderDispatcher;
import net.minecraft.entity.LivingEntity; import net.minecraft.entity.LivingEntity;
public class EntityDrawerImpl implements EntityDrawer { public class EntityDrawerImpl implements EntityDrawer {
// Draws the entity in the box, turned by yaw degrees (0 faces the viewer) with its head straight.
public static void drawEntity(RenderScope renderScope, int x1, int y1, int x2, int y2, int size, float f, public static void drawEntity(RenderScope renderScope, int x1, int y1, int x2, int y2, int size, float f,
float mouseX, float yaw, LivingEntity entity) {
float mouseY, LivingEntity entity, float angle, boolean negativeAngle) {
float g = (float) (x1 + x2) / 2.0F; float g = (float) (x1 + x2) / 2.0F;
float h = (float) (y1 + y2) / 2.0F; float h = (float) (y1 + y2) / 2.0F;
renderScope.enableScissor(0, 6, x2, y2); renderScope.enableScissor(x1, y1, x2, y2);
float i = (float) Math.atan((double) ((g - mouseX) / 40.0F));
float j = (float) Math.atan((double) ((h - mouseY) / 40.0F));
Quaternionf quaternionf = (new Quaternionf()).rotateZ(3.1415927F); Quaternionf quaternionf = (new Quaternionf()).rotateZ(3.1415927F);
float k = entity.bodyYaw; float k = entity.bodyYaw;
float l = entity.getYaw(); float l = entity.getYaw();
@@ -109,10 +104,10 @@ public class EntityDrawerImpl implements EntityDrawer {
//? if <1.21.5 //? if <1.21.5
//float n = entity.prevHeadYaw; //float n = entity.prevHeadYaw;
float o = entity.headYaw; float o = entity.headYaw;
entity.setYaw(angle); entity.setYaw(180.0F + yaw);
entity.bodyYaw = angle; entity.bodyYaw = 180.0F + yaw;
entity.setPitch(-(j * 20.0F)); entity.setPitch(0.0F);
entity.headYaw = negativeAngle ? (i * 40.0F) + 180f : -(i * 40.0F); entity.headYaw = entity.getYaw();
//? if >=1.21.5 //? if >=1.21.5
entity.lastHeadYaw = entity.getHeadYaw(); entity.lastHeadYaw = entity.getHeadYaw();
//? if <1.21.5 //? if <1.21.5
@@ -166,12 +161,15 @@ public class EntityDrawerImpl implements EntityDrawer {
} }
@Override @Override
public void render(RenderScope renderScope, boolean negativeAngle, float angle, float scale, ElementContext ctx) { public boolean render(RenderScope renderScope, int width, int height, float yaw) {
LivingEntity entity = SaturnClient.client.player; LivingEntity entity = SaturnClient.client.player;
if (entity != null) { if (entity == null) {
drawEntity(renderScope, 0, 0, 75, 78, 30, 0.0625F, ctx.mouseX / scale, ctx.mouseY / scale, entity, angle, return false;
negativeAngle);
} }
// The player is about 1.8 blocks tall; leave some room above and below.
drawEntity(renderScope, 0, 0, width, height, (int) (height / 2.2F), 0.0625F, yaw, entity);
return true;
} }
} }
*///?} *///?}
@@ -14,6 +14,12 @@ import net.minecraft.client.gui.CubeMapRenderer;
import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.RotatingCubeMapRenderer; import net.minecraft.client.gui.RotatingCubeMapRenderer;
import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.Screen;
//? if >=1.21.6 {
import net.minecraft.client.texture.CubemapTexture;
//?} else {
/*import net.minecraft.client.texture.ResourceTexture;
import net.minecraft.client.texture.TextureManager;
*///?}
//? if >=1.21.9 //? if >=1.21.9
import net.minecraft.client.input.KeyInput; import net.minecraft.client.input.KeyInput;
//? if <1.21.6 { //? if <1.21.6 {
@@ -21,8 +27,6 @@ import net.minecraft.client.input.KeyInput;
import net.minecraft.client.render.DefaultFramebufferSet; import net.minecraft.client.render.DefaultFramebufferSet;
import net.minecraft.client.util.Pool; import net.minecraft.client.util.Pool;
*///?} *///?}
import net.minecraft.client.texture.ResourceTexture;
import net.minecraft.client.texture.TextureManager;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
@@ -41,14 +45,25 @@ public class SaturnScreenFabric extends Screen implements ScreenProvider {
//private final Pool pool = new Pool(3); //private final Pool pool = new Pool(3);
public final SaturnScreen screen; public final SaturnScreen screen;
/** Whether the panorama's textures are loaded; it isn't drawn before (e.g. under the splash). */
private static volatile boolean panoramaReady = false;
public static void preload(MinecraftClient client) { public static void preload(MinecraftClient client) {
TextureManager textureManager = client.getTextureManager(); //? if >=1.21.6 {
// From 1.21.6 CubeMapRenderer draws from one cube map texture made of panorama_0..5.png.
// CubeMapRenderer.registerTextures only stores it for the next resource reload, which has
// already happened by now, so register it through the overload that loads it right away.
client.getTextureManager().registerTexture(PANORAMA, new CubemapTexture(PANORAMA));
//?} else {
/*TextureManager textureManager = client.getTextureManager();
for (int i = 0; i < 6; i++) { for (int i = 0; i < 6; i++) {
String var10001 = PANORAMA.getPath(); String var10001 = PANORAMA.getPath();
Identifier tex = PANORAMA.withPath(var10001 + "_" + i + ".png"); Identifier tex = PANORAMA.withPath(var10001 + "_" + i + ".png");
ResourceTexture resourceTexture = new ResourceTexture(tex); ResourceTexture resourceTexture = new ResourceTexture(tex);
textureManager.registerTexture(tex, resourceTexture); textureManager.registerTexture(tex, resourceTexture);
} }
*///?}
panoramaReady = true;
} }
public SaturnScreenFabric(SaturnScreen screen) { public SaturnScreenFabric(SaturnScreen screen) {
@@ -74,20 +89,18 @@ public class SaturnScreenFabric extends Screen implements ScreenProvider {
mouseX *= 2; mouseX *= 2;
mouseY *= 2; mouseY *= 2;
// TODO: the panorama and the background blur (screen.backgroundBlur) are only drawn before // From 1.21.6 the background, blur included, is drawn in renderBackground.
// 1.21.6 so far. See also issue #23. //? if <1.21.6 {
if (client.world == null && client.getCurrentServerEntry() == null) { /*if (isShowingPanorama()) {
//? if <1.21.6 ROTATING_PANORAMA_RENDERER.render(context, this.width, this.height, screen.backgroundOpacity, delta);
//ROTATING_PANORAMA_RENDERER.render(context, this.width, this.height, screen.backgroundOpacity, delta);
} }
//? if <1.21.6 { PostEffectProcessor postEffectProcessor = this.client.getShaderLoader().loadPostEffect(
/*PostEffectProcessor postEffectProcessor = this.client.getShaderLoader().loadPostEffect(
Identifier.ofVanilla("blur"), Identifier.ofVanilla("blur"),
DefaultFramebufferSet.MAIN_ONLY); DefaultFramebufferSet.MAIN_ONLY);
if (postEffectProcessor != null) { if (postEffectProcessor != null) {
float radius = screen.backgroundBlur * Math.min((float) elapsed / 700, 1.0f); float radius = screen.getBackgroundBlur();
//? if >=1.21.5 //? if >=1.21.5
postEffectProcessor.render(this.client.getFramebuffer(), this.pool, pass -> pass.setUniform("Radius", radius)); postEffectProcessor.render(this.client.getFramebuffer(), this.pool, pass -> pass.setUniform("Radius", radius));
//? if <1.21.5 //? if <1.21.5
@@ -102,6 +115,32 @@ public class SaturnScreenFabric extends Screen implements ScreenProvider {
screen.render(renderScope, mouseX, mouseY, delta, elapsed); screen.render(renderScope, mouseX, mouseY, delta, elapsed);
} }
//? if >=1.21.6 {
// 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
public void renderBackground(DrawContext 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()) {
ROTATING_PANORAMA_RENDERER.render(context, this.width, this.height, true);
}
if (screen.getBackgroundBlur() >= 1) {
context.applyBlur();
}
if (client.world != null) {
renderDarkening(context);
}
}
//?}
/** Whether Saturn's panorama is behind the screen: outside a world, once it's loaded. */
private boolean isShowingPanorama() {
return panoramaReady && client.world == null && client.getCurrentServerEntry() == null;
}
// 1.21.9 moved mouse and key events to Click and KeyInput. // 1.21.9 moved mouse and key events to Click and KeyInput.
//? if >=1.21.9 { //? if >=1.21.9 {
@Override @Override
@@ -115,8 +154,8 @@ public class SaturnScreenFabric extends Screen implements ScreenProvider {
@Override @Override
public boolean mouseDragged(Click click, double offsetX, double offsetY) { public boolean mouseDragged(Click click, double offsetX, double offsetY) {
// TODO: 1.21.8 passes the drag deltas; check whether offsetX/offsetY are the same on 1.21.9+. // offsetX and offsetY are the drag deltas, as mouseDragged's deltaX and deltaY were before 1.21.9.
if (screen.mouseDragged(click.x(), click.y(), click.button(), 0.0, 0.0)) { if (screen.mouseDragged(click.x(), click.y(), click.button(), offsetX, offsetY)) {
return true; return true;
} }
@@ -184,8 +223,13 @@ public class SaturnScreenFabric extends Screen implements ScreenProvider {
} }
*///?} *///?}
// TODO: resize isn't hooked on 1.21.11 yet. //? if >=1.21.11 {
//? if <1.21.11 { @Override
public void resize(int width, int height) {
screen.resize(width, height);
super.resize(width, height);
}
//?} else {
/*@Override /*@Override
public void resize(MinecraftClient client, int width, int height) { public void resize(MinecraftClient client, int width, int height) {
screen.resize(width, height); screen.resize(width, height);
@@ -12,6 +12,7 @@ import org.saturnclient.saturnclient.SaturnClient;
import org.saturnclient.common.provider.Providers; import org.saturnclient.common.provider.Providers;
import org.saturnclient.client.ServiceClient; import org.saturnclient.client.ServiceClient;
import org.saturnclient.emotes.fabric.Emotes; import org.saturnclient.emotes.fabric.Emotes;
import org.saturnclient.impl.ui.SaturnScreenFabric;
import org.saturnclient.ui.screens.EmoteWheel; import org.saturnclient.ui.screens.EmoteWheel;
import org.saturnclient.ui.screens.ShiftMenu; import org.saturnclient.ui.screens.ShiftMenu;
@@ -51,12 +52,20 @@ public class KeyInputHandler {
Providers.saturn.getClient().setScreen(new ShiftMenu()); Providers.saturn.getClient().setScreen(new ShiftMenu());
} }
if (Config.openEmoteWheel.wasKeyPressed() && client.currentScreen == null // The emote wheel key opens the wheel, and closes it again while it's open. Closing it
// from the screen's own key handling would reopen it on the next tick, since the key is
// still down and no screen is open by then.
if (Config.openEmoteWheel.wasKeyPressed()
//? if >=1.21.9 { //? if >=1.21.9 {
&& !InputUtil.isKeyPressed(client.getWindow(), GLFW.GLFW_KEY_F3)) { && !InputUtil.isKeyPressed(client.getWindow(), GLFW.GLFW_KEY_F3)) {
//?} else //?} else
//&& !InputUtil.isKeyPressed(client.getWindow().getHandle(), GLFW.GLFW_KEY_F3)) { //&& !InputUtil.isKeyPressed(client.getWindow().getHandle(), GLFW.GLFW_KEY_F3)) {
Providers.saturn.getClient().setScreen(new EmoteWheel()); if (client.currentScreen == null) {
Providers.saturn.getClient().setScreen(new EmoteWheel());
} else if (client.currentScreen instanceof SaturnScreenFabric saturnScreen
&& saturnScreen.screen instanceof EmoteWheel) {
saturnScreen.close();
}
} }
// Sneaking cancels your own emote, for everyone. // Sneaking cancels your own emote, for everyone.
@@ -40,16 +40,15 @@ public abstract class PlayerEntityRendererMixin
this.addFeature(new CloakFeatureRenderer(this, ctx.getEquipmentModelLoader())); this.addFeature(new CloakFeatureRenderer(this, ctx.getEquipmentModelLoader()));
} }
// Stores the SaturnPlayer in the render state and adds the role icon to the name. Render state // Adds the role icon to the player's name, and stores the SaturnPlayer in the render state for the
// data only exists from 1.21.8. // cloak and hat (Fabric API only has render state data from 1.21.8).
// TODO: 1.21.6 and 1.21.7 don't show the role icon in player names.
//? if >=1.21.9 { //? if >=1.21.9 {
@Inject(method = "updateRenderState", at = @At("TAIL")) @Inject(method = "updateRenderState", at = @At("TAIL"))
public <A extends PlayerLikeEntity & ClientPlayerLikeEntity> void updateRenderState( public <A extends PlayerLikeEntity & ClientPlayerLikeEntity> void updateRenderState(
A entity, PlayerEntityRenderState state, float f, CallbackInfo ci) { A entity, PlayerEntityRenderState state, float f, CallbackInfo ci) {
saturn$applyPlayer(entity, state); saturn$applyPlayer(entity, state);
} }
//?} else if >=1.21.8 { //?} else {
/*@Inject(method = "updateRenderState", at = @At("TAIL")) /*@Inject(method = "updateRenderState", at = @At("TAIL"))
public void updateRenderState( public void updateRenderState(
AbstractClientPlayerEntity entity, PlayerEntityRenderState state, float f, CallbackInfo ci) { AbstractClientPlayerEntity entity, PlayerEntityRenderState state, float f, CallbackInfo ci) {
@@ -57,7 +56,6 @@ public abstract class PlayerEntityRendererMixin
} }
*///?} *///?}
//? if >=1.21.8 {
private void saturn$applyPlayer(Entity entity, PlayerEntityRenderState state) { private void saturn$applyPlayer(Entity entity, PlayerEntityRenderState state) {
if (entity == null) { if (entity == null) {
return; return;
@@ -65,6 +63,7 @@ public abstract class PlayerEntityRendererMixin
SaturnPlayer player = SaturnPlayer.get(entity.getUuid()); SaturnPlayer player = SaturnPlayer.get(entity.getUuid());
//? if >=1.21.8
state.setData(SaturnRenderState.saturnDataKey, player); state.setData(SaturnRenderState.saturnDataKey, player);
if (player != null && state.displayName != null) { if (player != null && state.displayName != null) {
@@ -77,5 +76,4 @@ public abstract class PlayerEntityRendererMixin
.append(state.displayName.copy()); .append(state.displayName.copy());
} }
} }
//?}
} }
@@ -126,8 +126,9 @@ public abstract class SplashOverlayMixin {
private void renderProgressBar(RenderScope scope, DrawContext context, int screenWidth, int screenHeight, private void renderProgressBar(RenderScope scope, DrawContext context, int screenWidth, int screenHeight,
int alpha) { int alpha) {
int progressY = ((screenHeight - 5) / 2) + 25; int progressY = ((screenHeight - 5) / 2) + 25;
// TODO: the loading bar's border isn't drawn on 1.21.9+ yet. //? if >=1.21.9 {
//? if <1.21.9 context.drawStrokedRectangle((screenWidth - 120) / 2, progressY, 120, 5, Theme.withAlpha(alpha, 0xFFFFFFFF));
//?} else
//context.drawBorder((screenWidth - 120) / 2, progressY, 120, 5, Theme.withAlpha(alpha, 0xFFFFFFFF)); //context.drawBorder((screenWidth - 120) / 2, progressY, 120, 5, Theme.withAlpha(alpha, 0xFFFFFFFF));
int progressWidth = Math.min((int) (progress * 120), 120); int progressWidth = Math.min((int) (progress * 120), 120);
-1
View File
@@ -9,7 +9,6 @@
"render.LivingEntityRendererMixin", "render.LivingEntityRendererMixin",
"render.LivingEntityRenderStateMixin", "render.LivingEntityRenderStateMixin",
"render.BackgroundRendererMixin", "render.BackgroundRendererMixin",
"misc.NametagsMixin",
"render.RenderMixin", "render.RenderMixin",
"world.TpsMixin", "world.TpsMixin",
"entity.CameraMixin", "entity.CameraMixin",
+1 -1
View File
@@ -9,7 +9,7 @@ org.gradle.configuration-cache=false
# check these on https://fabricmc.net/develop # check these on https://fabricmc.net/develop
minecraft_version=1.21.10 minecraft_version=1.21.10
yarn_mappings=1.21.10+build.3 yarn_mappings=1.21.10+build.3
loader_version=0.19.0 loader_version=0.19.5
fabric_api_version=0.138.4+1.21.10 fabric_api_version=0.138.4+1.21.10
+1 -1
View File
@@ -9,7 +9,7 @@ org.gradle.configuration-cache=false
# check these on https://fabricmc.net/develop # check these on https://fabricmc.net/develop
minecraft_version=1.21.11 minecraft_version=1.21.11
yarn_mappings=1.21.11+build.4 yarn_mappings=1.21.11+build.4
loader_version=0.19.0 loader_version=0.19.5
fabric_api_version=0.141.3+1.21.11 fabric_api_version=0.141.3+1.21.11
+1 -1
View File
@@ -9,7 +9,7 @@ org.gradle.configuration-cache=false
# check these on https://fabricmc.net/develop # check these on https://fabricmc.net/develop
minecraft_version=1.21.4 minecraft_version=1.21.4
yarn_mappings=1.21.4+build.8 yarn_mappings=1.21.4+build.8
loader_version=0.18.4 loader_version=0.19.5
# Mod Properties # Mod Properties
maven_group=org.saturnclient maven_group=org.saturnclient
+1 -1
View File
@@ -9,7 +9,7 @@ org.gradle.configuration-cache=false
# check these on https://fabricmc.net/develop # check these on https://fabricmc.net/develop
minecraft_version=1.21.5 minecraft_version=1.21.5
yarn_mappings=1.21.5+build.1 yarn_mappings=1.21.5+build.1
loader_version=0.18.4 loader_version=0.19.5
fabric_api_version=0.128.2+1.21.5 fabric_api_version=0.128.2+1.21.5
+1 -1
View File
@@ -9,7 +9,7 @@ org.gradle.configuration-cache=false
# check these on https://fabricmc.net/develop # check these on https://fabricmc.net/develop
minecraft_version=1.21.6 minecraft_version=1.21.6
yarn_mappings=1.21.6+build.1 yarn_mappings=1.21.6+build.1
loader_version=0.18.5 loader_version=0.19.5
fabric_api_version=0.128.2+1.21.6 fabric_api_version=0.128.2+1.21.6
+1 -1
View File
@@ -9,7 +9,7 @@ org.gradle.configuration-cache=false
# check these on https://fabricmc.net/develop # check these on https://fabricmc.net/develop
minecraft_version=1.21.7 minecraft_version=1.21.7
yarn_mappings=1.21.7+build.8 yarn_mappings=1.21.7+build.8
loader_version=0.19.0 loader_version=0.19.5
fabric_api_version=0.129.0+1.21.7 fabric_api_version=0.129.0+1.21.7
+1 -1
View File
@@ -9,7 +9,7 @@ org.gradle.configuration-cache=false
# check these on https://fabricmc.net/develop # check these on https://fabricmc.net/develop
minecraft_version=1.21.8 minecraft_version=1.21.8
yarn_mappings=1.21.8+build.1 yarn_mappings=1.21.8+build.1
loader_version=0.19.0 loader_version=0.19.5
fabric_api_version=0.136.1+1.21.8 fabric_api_version=0.136.1+1.21.8
+1 -1
View File
@@ -9,7 +9,7 @@ org.gradle.configuration-cache=false
# check these on https://fabricmc.net/develop # check these on https://fabricmc.net/develop
minecraft_version=1.21.9 minecraft_version=1.21.9
yarn_mappings=1.21.9+build.1 yarn_mappings=1.21.9+build.1
loader_version=0.19.0 loader_version=0.19.5
fabric_api_version=0.134.1+1.21.9 fabric_api_version=0.134.1+1.21.9