diff --git a/src/main/java/meteordevelopment/meteorclient/MixinPlugin.java b/src/main/java/meteordevelopment/meteorclient/MixinPlugin.java index d04e014f0af..c8fcc0fc32a 100644 --- a/src/main/java/meteordevelopment/meteorclient/MixinPlugin.java +++ b/src/main/java/meteordevelopment/meteorclient/MixinPlugin.java @@ -26,6 +26,8 @@ public class MixinPlugin implements IMixinConfigPlugin { private static boolean isBaritonePresent; private static boolean isMetallumPresent; + private static boolean isModernSodium; + @Override public void onLoad(String mixinPackage) { if (loaded) return; @@ -38,9 +40,57 @@ public void onLoad(String mixinPackage) { isBaritonePresent = FabricLoader.getInstance().isModLoaded("baritone") || FabricLoader.getInstance().isModLoaded("baritone-meteor"); isMetallumPresent = FabricLoader.getInstance().isModLoaded("metallum"); + isModernSodium = detectModernSodium(); + loaded = true; } + private static boolean detectModernSodium() { + var sodium = FabricLoader.getInstance().getModContainer("sodium"); + if (sodium.isEmpty()) { + return false; + } + + String version = sodium.get().getMetadata().getVersion().getFriendlyString(); + + String versionMatch = version.replaceFirst(".*?(0\\.\\d+\\.\\d+).*", "$1"); + + if (versionMatch.isEmpty() || versionMatch.equals(version)) { + String normalized = version.toLowerCase(); + int idx = normalized.indexOf("0."); + if (idx >= 0) { + versionMatch = normalized.substring(idx); + int end = versionMatch.indexOf('-'); + if (end >= 0) versionMatch = versionMatch.substring(0, end); + } + } + + if (versionMatch.isEmpty()) { + return false; + } + + String[] parts = versionMatch.split("\\."); + if (parts.length < 2) { + return false; + } + + try { + int major = Integer.parseInt(parts[0]); + int minor = Integer.parseInt(parts[1]); + int patch = parts.length > 2 ? Integer.parseInt(parts[2]) : 0; + + // 0.9.2 and below -> old + // 0.9.3 and above -> new + if (major == 0 && minor == 9) { + return patch >= 3; + } + + return major > 0 || minor > 9; + } catch (NumberFormatException e) { + return false; + } + } + @Override public String getRefMapperConfig() { return null; @@ -53,7 +103,17 @@ public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { } else if (mixinClassName.endsWith("PlayerEntityRendererMixin")) { return !isOriginsPresent; } else if (mixinClassName.startsWith(mixinPackage + ".sodium")) { - return isSodiumPresent; + if (!isSodiumPresent) return false; + + if (mixinClassName.endsWith("SodiumDefaultFluidRendererOldMixin")) { + return !isModernSodium; + } + + if (mixinClassName.endsWith("SodiumDefaultFluidRendererNewMixin")) { + return isModernSodium; + } + + return true; } else if (mixinClassName.startsWith(mixinPackage + ".indigo")) { return isIndigoPresent; } else if (mixinClassName.startsWith(mixinPackage + ".lithium")) { @@ -64,7 +124,6 @@ public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { return isMetallumPresent; } - return true; } diff --git a/src/main/java/meteordevelopment/meteorclient/mixin/sodium/SodiumDefaultFluidRendererNewMixin.java b/src/main/java/meteordevelopment/meteorclient/mixin/sodium/SodiumDefaultFluidRendererNewMixin.java new file mode 100644 index 00000000000..040474471ff --- /dev/null +++ b/src/main/java/meteordevelopment/meteorclient/mixin/sodium/SodiumDefaultFluidRendererNewMixin.java @@ -0,0 +1,124 @@ +/* + * This file is part of the Meteor Client distribution (https://github.com/MeteorDevelopment/meteor-client). + * Copyright (c) Meteor Development. + */ + +package meteordevelopment.meteorclient.mixin.sodium; + +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import com.llamalad7.mixinextras.sugar.Local; +import meteordevelopment.meteorclient.systems.modules.Modules; +import meteordevelopment.meteorclient.systems.modules.render.Xray; +import net.caffeinemc.mods.sodium.client.model.color.ColorProvider; +import net.caffeinemc.mods.sodium.client.model.light.LightPipeline; +import net.caffeinemc.mods.sodium.client.model.quad.ModelQuadViewMutable; +import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing; +import net.caffeinemc.mods.sodium.client.render.chunk.compile.buffers.ChunkModelBuilder; +import net.caffeinemc.mods.sodium.client.render.chunk.compile.pipeline.DefaultFluidRenderer; +import net.caffeinemc.mods.sodium.client.render.chunk.terrain.material.DefaultMaterials; +import net.caffeinemc.mods.sodium.client.render.chunk.terrain.material.Material; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TranslucentGeometryCollector; +import net.caffeinemc.mods.sodium.client.world.LevelSlice; +import net.minecraft.client.renderer.block.BlockAndTintGetter; +import net.minecraft.client.renderer.block.FluidModel; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.FluidState; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +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.ModifyArg; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(value = DefaultFluidRenderer.class, remap = false) +public abstract class SodiumDefaultFluidRendererNewMixin { + @Final + @Shadow + private int[] quadColors; + + @Unique + private int xrayAlpha; + @Unique + private boolean forceXrayFluidSides; + @Unique + private Xray xray; + + @Inject(method = "", at = @At("TAIL")) + private void onInit(CallbackInfo ci) { + xray = Modules.get().get(Xray.class); + } + + @Inject(method = "render", at = @At("HEAD"), cancellable = true) + private void onRender(LevelSlice level, BlockState blockState, FluidState fluidState, BlockPos blockPos, BlockPos offset, TranslucentGeometryCollector collector, ChunkModelBuilder meshBuilder, Material material, ColorProvider colorProvider, FluidModel sprites, CallbackInfo ci) { + xrayAlpha = Xray.getFluidAlpha(fluidState, blockPos); + forceXrayFluidSides = xray.isActive(); + + // Cancel block rendering when alpha is 0, required for Iris support but unnecessary to check for shaders, we already force be disabled when Xray is enabled + if (xrayAlpha == 0) { + forceXrayFluidSides = false; + ci.cancel(); + } + } + + @ModifyReturnValue(method = "isFullBlockFluidSideVisible", at = @At("RETURN")) + private boolean onIsFullBlockFluidSideVisible(boolean original, BlockGetter view, BlockPos selfPos, Direction facing, FluidState fluid, @Local(name = "otherState") BlockState otherState) { + if (original || !forceXrayFluidSides || facing.getAxis().isVertical()) return original; + if (!xray.isBlocked(otherState.getBlock(), null)) return false; + + return !otherState.getFluidState().getType().isSame(fluid.getType()); + } + + @ModifyReturnValue(method = "isFullBlockFluidSelfVisible(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/core/Direction;)Z", at = @At("RETURN")) + private boolean onIsFullBlockFluidSelfVisible(boolean original, BlockState selfBlockState, Direction facing) { + if (!forceXrayFluidSides || facing.getAxis().isVertical()) return original; + + // Xray should ignore the block's own occlusion for fluid faces we are intentionally exposing. + return true; + } + + @ModifyReturnValue(method = "isSliceFluidSelfVisible(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/core/Direction;FF)Z", at = @At("RETURN")) + private boolean onIsSliceFluidSelfVisible(boolean original, BlockState selfBlockState, Direction facing, float minThickness, float maxThickness) { + if (!forceXrayFluidSides || facing.getAxis().isVertical()) return original; + + // Xray should ignore self-occlusion when the fluid face is being forced visible. + return true; + } + + @ModifyReturnValue(method = "isFluidSideExposed(Lnet/minecraft/client/renderer/block/BlockAndTintGetter;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/core/BlockPos;Lnet/minecraft/core/Direction;F)Z", at = @At("RETURN")) + private boolean onIsFluidSideExposed(boolean original, BlockAndTintGetter world, BlockState ownBlockState, BlockPos neighborPos, Direction facing, float height) { + BlockState neighborBlockState = world.getBlockState(neighborPos); + + if (original || !forceXrayFluidSides || facing.getAxis().isVertical()) return original; + if (!xray.isBlocked(neighborBlockState.getBlock(), null)) return false; + + return !neighborBlockState.getFluidState().getType().isSame(ownBlockState.getFluidState().getType()); + } + + @Inject(method = "getUpFaceExposureByNeighbors", at = @At("HEAD"), cancellable = true) + private void onGetUpFaceExposureByNeighbors(BlockAndTintGetter level, BlockPos origin, FluidState fluidState, CallbackInfoReturnable cir) { + if (forceXrayFluidSides) cir.setReturnValue(3); + } + + @Inject(method = "updateQuad", at = @At("TAIL")) + private void onUpdateQuad(ModelQuadViewMutable quad, LevelSlice level, BlockPos pos, LightPipeline lighter, Direction dir, ModelQuadFacing facing, float brightness, ColorProvider colorProvider, FluidState fluidState, CallbackInfo ci) { + if (xrayAlpha != -1) { + for (int i = 0; i < 4; i++) { + quadColors[i] = (quadColors[i] & 0x00FFFFFF) | (xrayAlpha << 24); + } + } + } + + @ModifyArg(method = "render", at = @At(value = "INVOKE", target = "Lnet/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/DefaultFluidRenderer;writeQuad(Lnet/caffeinemc/mods/sodium/client/render/chunk/compile/buffers/ChunkModelBuilder;Lnet/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector;Lnet/caffeinemc/mods/sodium/client/render/chunk/terrain/material/Material;Lnet/minecraft/core/BlockPos;Lnet/caffeinemc/mods/sodium/client/model/quad/ModelQuadView;Lnet/caffeinemc/mods/sodium/client/model/quad/properties/ModelQuadFacing;Z)V"), index = 2) + private Material modifyMaterial(Material material) { + if (xrayAlpha != -1) { + return DefaultMaterials.TRANSLUCENT; + } + return material; + } +} diff --git a/src/main/java/meteordevelopment/meteorclient/mixin/sodium/SodiumDefaultFluidRendererMixin.java b/src/main/java/meteordevelopment/meteorclient/mixin/sodium/SodiumDefaultFluidRendererOldMixin.java similarity index 99% rename from src/main/java/meteordevelopment/meteorclient/mixin/sodium/SodiumDefaultFluidRendererMixin.java rename to src/main/java/meteordevelopment/meteorclient/mixin/sodium/SodiumDefaultFluidRendererOldMixin.java index 674bde9913d..4f4f455b471 100644 --- a/src/main/java/meteordevelopment/meteorclient/mixin/sodium/SodiumDefaultFluidRendererMixin.java +++ b/src/main/java/meteordevelopment/meteorclient/mixin/sodium/SodiumDefaultFluidRendererOldMixin.java @@ -37,7 +37,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(value = DefaultFluidRenderer.class, remap = false) -public abstract class SodiumDefaultFluidRendererMixin { +public abstract class SodiumDefaultFluidRendererOldMixin { @Final @Shadow private int[] quadColors; diff --git a/src/main/resources/meteor-client-sodium.mixins.json b/src/main/resources/meteor-client-sodium.mixins.json index d7744492e73..28271409591 100644 --- a/src/main/resources/meteor-client-sodium.mixins.json +++ b/src/main/resources/meteor-client-sodium.mixins.json @@ -7,7 +7,8 @@ "AbstractBlockRenderContextMixin", "SodiumBlockRendererMixin", "SodiumChunkBuilderMeshingTaskMixin", - "SodiumDefaultFluidRendererMixin", + "SodiumDefaultFluidRendererOldMixin", + "SodiumDefaultFluidRendererNewMixin", "SodiumFluidRendererImplDefaultRenderContextMixin", "SodiumFluidRendererImplMixin", "SodiumLightDataAccessMixin",