From 09bc223b8b7f0b5906233c39eef30c8adbc8a22e Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 30 Sep 2026 17:42:48 +0200 Subject: [PATCH] Implement the Ref interfaces explicitly in their mixins Several mixins implemented a Ref method only through a vanilla method of the same name. Released jars rename vanilla methods to intermediary names, so those Ref methods had no implementation there and threw AbstractMethodError: the armour display's durability, the status effects' icon and infinite checks, the SVG texture size and the pre-1.21.6 matrix stack's push and pop. With Mojang's names the same happens in development (1.21.4 crashed on the title screen). Each mixin now shadows the vanilla method and implements the Ref method by calling it. Points common at the Ref renames this needs. Co-Authored-By: Claude Opus 5.5 --- VERSION_GUIDE.md | 2 ++ common | 2 +- .../impl/mixins/ItemStackMixin.java | 16 ++++++++++++ .../impl/mixins/MatrixStackMixin.java | 11 ++++++++ .../impl/mixins/StatusEffectMixin.java | 11 ++++++++ .../saturnclient/impl/mixins/WindowMixin.java | 25 ++++++++++++++++++- 6 files changed, 65 insertions(+), 2 deletions(-) diff --git a/VERSION_GUIDE.md b/VERSION_GUIDE.md index 042561a..febd0d4 100644 --- a/VERSION_GUIDE.md +++ b/VERSION_GUIDE.md @@ -18,6 +18,8 @@ Every version is built with Mojang's official names (`loom.officialMojangMapping 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. +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. + ## Version-specific code with Stonecutter The code in `src/` is always plain Java for the **active** version (1.21.11, set in `stonecutter.gradle.kts`). Code for other versions sits in comments that Stonecutter swaps before building each version: diff --git a/common b/common index b0d75af..a6a324a 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit b0d75af8c4a587c7edf871cfa47a1fdf81e4ecfc +Subproject commit a6a324a61ade15ef41722769837bdbe31e899656 diff --git a/src/main/java/org/saturnclient/impl/mixins/ItemStackMixin.java b/src/main/java/org/saturnclient/impl/mixins/ItemStackMixin.java index 0ed61b4..7a2ca3e 100644 --- a/src/main/java/org/saturnclient/impl/mixins/ItemStackMixin.java +++ b/src/main/java/org/saturnclient/impl/mixins/ItemStackMixin.java @@ -16,4 +16,20 @@ public abstract class ItemStackMixin implements ItemStackRef { @Shadow public abstract int getDamageValue(); + + // ItemStackRef's methods delegate to ItemStack's, whose names production renames. + @Override + public boolean isEmptyStack() { + return isEmpty(); + } + + @Override + public int getMaxItemDamage() { + return getMaxDamage(); + } + + @Override + public int getItemDamage() { + return getDamageValue(); + } } diff --git a/src/main/java/org/saturnclient/impl/mixins/MatrixStackMixin.java b/src/main/java/org/saturnclient/impl/mixins/MatrixStackMixin.java index a6d8671..cec259b 100644 --- a/src/main/java/org/saturnclient/impl/mixins/MatrixStackMixin.java +++ b/src/main/java/org/saturnclient/impl/mixins/MatrixStackMixin.java @@ -44,6 +44,17 @@ import com.mojang.math.Axis; //? if <1.21.5 //public abstract void rotateAround(Quaternionf quaternion, float originX, float originY, float originZ); + // MatrixStackRef's methods delegate to PoseStack's, whose names production renames. + @Override + public void push() { + pushPose(); + } + + @Override + public void pop() { + popPose(); + } + @Override public void translate(float x, float y) { translate(x, y, 0); diff --git a/src/main/java/org/saturnclient/impl/mixins/StatusEffectMixin.java b/src/main/java/org/saturnclient/impl/mixins/StatusEffectMixin.java index 8dab800..1d06413 100644 --- a/src/main/java/org/saturnclient/impl/mixins/StatusEffectMixin.java +++ b/src/main/java/org/saturnclient/impl/mixins/StatusEffectMixin.java @@ -29,6 +29,17 @@ public abstract class StatusEffectMixin implements EffectRef { @Shadow public abstract Holder getEffect(); + // EffectRef's methods delegate to MobEffectInstance's, whose names production renames. + @Override + public boolean shouldShowIcon() { + return showIcon(); + } + + @Override + public boolean isInfinite() { + return isInfiniteDuration(); + } + @Override public SpriteRef getIcon() { // 1.21.9 changed the GUI atlas, so newer versions draw the effect's texture (getIconId) instead. diff --git a/src/main/java/org/saturnclient/impl/mixins/WindowMixin.java b/src/main/java/org/saturnclient/impl/mixins/WindowMixin.java index ab54ba2..4e07119 100644 --- a/src/main/java/org/saturnclient/impl/mixins/WindowMixin.java +++ b/src/main/java/org/saturnclient/impl/mixins/WindowMixin.java @@ -1,10 +1,11 @@ package org.saturnclient.impl.mixins; -import com.mojang.blaze3d.platform.Window; import org.saturnclient.common.ref.render.WindowRef; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; +import com.mojang.blaze3d.platform.Window; + @Mixin(Window.class) public abstract class WindowMixin implements WindowRef { @Shadow @@ -13,9 +14,31 @@ public abstract class WindowMixin implements WindowRef { @Shadow public abstract int getScreenHeight(); + // The framebuffer's size @Shadow public abstract int getWidth(); @Shadow public abstract int getHeight(); + + // WindowRef's methods delegate to Window's, whose names production renames. + @Override + public int getWindowWidth() { + return getScreenWidth(); + } + + @Override + public int getWindowHeight() { + return getScreenHeight(); + } + + @Override + public int getFramebufferWidth() { + return getWidth(); + } + + @Override + public int getFramebufferHeight() { + return getHeight(); + } }