From 28d0a2662d572d6c2d12dc72d44322133de9c5cf Mon Sep 17 00:00:00 2001 From: CC Date: Fri, 18 Sep 2026 16:01:27 -0700 Subject: [PATCH 1/2] 26.3: target release 26.3 and Fabric API 0.161.0 The branch was pinned to 26.3-snapshot-7. On release 26.3, Fabric API 0.156.2 fails to load: content-registries' LivingEntityMixin can no longer inject into isInCustomFluid, and the transitive-access-widener targets BlockTransformerMappings / ItemInHandRenderer are gone. 0.161.0 is the first build that loads cleanly, so raise the floor to match. Co-Authored-By: Claude Opus 5 --- versions/26.3/gradle.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/versions/26.3/gradle.properties b/versions/26.3/gradle.properties index 0616ac731..bc864a85d 100644 --- a/versions/26.3/gradle.properties +++ b/versions/26.3/gradle.properties @@ -1,10 +1,10 @@ -dep.minecraft=26.3-snapshot-7 +dep.minecraft=26.3 meta.minecraft-range=(26.2,26.4) #dep.neoforge=26.2.0.28-beta -dep.fapi=0.156.2+26.3 -meta.fapi-range=[0.156.2,) +dep.fapi=0.161.0+26.3 +meta.fapi-range=[0.161.0,) dep.yacl=3.9.6+26.3-fabric dep.yacl-neoforge=3.9.6+26.2-neoforge From 89b9032bb244d13c395924f2e77616e29d539aa4 Mon Sep 17 00:00:00 2001 From: CC Date: Fri, 18 Sep 2026 16:01:27 -0700 Subject: [PATCH 2/2] 26.3: follow openUri and item-drop moves in the release API Two signatures Controlify relies on moved between 26.3-snapshot-7 and release 26.3: - Util.getPlatform().openUri(String) is gone; link opening now lives in com.mojang.blaze3d.Blaze3D.openUri(URI). Five call sites in the settings screens updated. - LocalPlayer.drop(boolean) is gone, so the dropresult mixin's @Shadow failed to apply and the client crashed at startup. The equivalent is MultiPlayerGameMode.dropItem(LocalPlayer, boolean), which drops inventory.removeFromSelected(all) and swings, as drop() did. It returns void, so report the result the way the old method did: the drop happened iff the selected stack was non-empty beforehand. Co-Authored-By: Claude Opus 5 --- .../gui/screen/ControlifySettingsScreen.java | 11 +++--- .../screen/GlobalSettingsScreenFactory.java | 4 ++- .../patches/dropresult/LocalPlayerMixin.java | 34 +++++++++---------- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/main/java/dev/isxander/controlify/gui/screen/ControlifySettingsScreen.java b/src/main/java/dev/isxander/controlify/gui/screen/ControlifySettingsScreen.java index c09b1ac6e..57d2e1e58 100644 --- a/src/main/java/dev/isxander/controlify/gui/screen/ControlifySettingsScreen.java +++ b/src/main/java/dev/isxander/controlify/gui/screen/ControlifySettingsScreen.java @@ -6,6 +6,8 @@ */ package dev.isxander.controlify.gui.screen; +import com.mojang.blaze3d.Blaze3D; +import java.net.URI; import com.google.common.collect.ImmutableList; import dev.isxander.controlify.Controlify; import dev.isxander.controlify.api.buttonguide.ButtonGuideApi; @@ -37,7 +39,6 @@ import net.minecraft.network.chat.Component; import net.minecraft.resources.Identifier; import net.minecraft.util.Mth; -import net.minecraft.util.Util; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; @@ -67,7 +68,7 @@ protected void init() { Component donateText = Component.translatable("controlify.gui.carousel.donate") .withStyle(ChatFormatting.GOLD, ChatFormatting.BOLD); PlainTextButton donateBtn = this.addRenderableWidget(new PlainTextButton(3, 3, 100, 11, donateText, btn -> { - Util.getPlatform().openUri("https://patreon.com/isxander"); + Blaze3D.openUri(URI.create("https://patreon.com/isxander")); }, font)); donateBtn.setTabOrderGroup(2); @@ -75,7 +76,7 @@ protected void init() { .withStyle(ChatFormatting.DARK_GRAY); int artCreditTextWidth = font.width(artCreditText); PlainTextButton artCreditBtn = this.addRenderableWidget(new PlainTextButton(width - artCreditTextWidth - 3, 3, artCreditTextWidth, 11, artCreditText, btn -> { - Util.getPlatform().openUri("https://github.com/Andrew6rant"); + Blaze3D.openUri(URI.create("https://github.com/Andrew6rant")); }, font)); artCreditBtn.setTabOrderGroup(2); @@ -84,7 +85,7 @@ protected void init() { controllerNotDetectedButton = this.addRenderableWidget( Button.builder( Component.translatable("controlify.gui.carousel.controller_not_detected_btn"), - btn -> Util.getPlatform().openUri("https://docs.isxander.dev/controlify/users/controller-issues#my-controller-is-not-detected") + btn -> Blaze3D.openUri(URI.create("https://docs.isxander.dev/controlify/users/controller-issues#my-controller-is-not-detected")) ) .pos(width / 2 - 75, (this.height - 36) / 2 + 10) .tooltip(Tooltip.create(Component.translatable("controlify.gui.carousel.controller_not_detected_btn.tooltip"))) @@ -437,7 +438,7 @@ public SplitscreenAdvertisementSlotEntry( ); this.adButton = Button.builder( Component.literal("Donate on Patreon"), - btn -> Util.getPlatform().openUri("https://patreon.com/isxander") + btn -> Blaze3D.openUri(URI.create("https://patreon.com/isxander")) ).build(); Component disableAdText = Component.literal("Don't show this again") .withStyle(ChatFormatting.DARK_GRAY); diff --git a/src/main/java/dev/isxander/controlify/gui/screen/GlobalSettingsScreenFactory.java b/src/main/java/dev/isxander/controlify/gui/screen/GlobalSettingsScreenFactory.java index abb4a7da8..e2672312d 100644 --- a/src/main/java/dev/isxander/controlify/gui/screen/GlobalSettingsScreenFactory.java +++ b/src/main/java/dev/isxander/controlify/gui/screen/GlobalSettingsScreenFactory.java @@ -6,6 +6,8 @@ */ package dev.isxander.controlify.gui.screen; +import com.mojang.blaze3d.Blaze3D; +import java.net.URI; import dev.isxander.controlify.Controlify; import dev.isxander.controlify.api.ControlifyApi; import dev.isxander.controlify.config.settings.GlobalSettings; @@ -41,7 +43,7 @@ public static Screen createGlobalSettingsScreen(Screen parent) { .name(Component.translatable("controlify.gui.global_settings.title")) .option(ButtonOption.createBuilder() .name(Component.translatable("controlify.gui.open_issue_tracker")) - .action((screen, button) -> Util.getPlatform().openUri("https://github.com/isxander/controlify/issues")) + .action((screen, button) -> Blaze3D.openUri(URI.create("https://github.com/isxander/controlify/issues"))) .build()) .group(OptionGroup.createBuilder() .name(Component.translatable("controlify.gui.server_options")) diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/patches/dropresult/LocalPlayerMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/patches/dropresult/LocalPlayerMixin.java index f5224a05b..4335ef25a 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/patches/dropresult/LocalPlayerMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/patches/dropresult/LocalPlayerMixin.java @@ -12,32 +12,30 @@ import org.spongepowered.asm.mixin.Shadow; //? if >=26.3 { -import com.llamalad7.mixinextras.sugar.Local; -import net.minecraft.world.item.ItemStack; -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; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.MultiPlayerGameMode; //?} @Mixin(LocalPlayer.class) public abstract class LocalPlayerMixin implements DropWithResultInvoker { //? if >=26.3 { - @Unique private final ThreadLocal controlify$dropResult = ThreadLocal.withInitial(() -> false); - - @Shadow - public abstract void drop(boolean all); - - @Inject(method = "drop", at = @At("RETURN")) - private void setResultField(boolean all, CallbackInfo ci, @Local(name = "prediction") ItemStack prediction) { - controlify$dropResult.set(!prediction.isEmpty()); - } - + // 26.3 release moved LocalPlayer.drop(boolean) to + // MultiPlayerGameMode.dropItem(LocalPlayer, boolean), which drops the + // selected stack (and swings) exactly as LocalPlayer.drop used to. @Override public boolean controlify$drop(boolean all) { - this.drop(all); - return controlify$dropResult.get(); + LocalPlayer self = (LocalPlayer) (Object) this; + MultiPlayerGameMode gameMode = Minecraft.getInstance().gameMode; + if (gameMode == null) { + return false; + } + + // dropItem() drops inventory.removeFromSelected(all); it is a no-op + // when the selected slot is empty, which is what the result reports. + boolean dropped = !self.getInventory().getSelectedItem().isEmpty(); + gameMode.dropItem(self, all); + return dropped; } //?} else { /*@Shadow