From 5187d39fe06f0e53ce3b84bcd077c93565c9d462 Mon Sep 17 00:00:00 2001 From: PepperCode1 <44146161+PepperCode1@users.noreply.github.com> Date: Thu, 21 Jul 2022 12:07:20 -0700 Subject: [PATCH 1/5] Fix Indigo AO calculation (#2344) * Fix AO calculation - Ensure calcFastVanilla and computeFace have parity with vanilla - Ensure BlockStates are not queried more than once for the same position - Replace deprecated FabricLoader#getConfigDirectory call with FabricLoader#getConfigDir * Revert opaque sides fix * More AO fixes and optimizations --- .../fabric/impl/client/indigo/Indigo.java | 4 +- .../indigo/renderer/aocalc/AoCalculator.java | 142 +++++++++++------- .../renderer/aocalc/AoLuminanceFix.java | 9 +- .../renderer/render/AbstractMeshConsumer.java | 16 +- .../renderer/render/AbstractQuadRenderer.java | 10 +- .../renderer/render/BlockRenderContext.java | 8 +- .../renderer/render/ChunkRenderInfo.java | 10 +- 7 files changed, 119 insertions(+), 80 deletions(-) diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/Indigo.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/Indigo.java index 6a49ebe7d..a974bc781 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/Indigo.java +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/Indigo.java @@ -23,8 +23,8 @@ import java.io.IOException; import java.util.Locale; import java.util.Properties; -import org.slf4j.LoggerFactory; import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.renderer.v1.RendererAccess; @@ -89,7 +89,7 @@ public class Indigo implements ClientModInitializer { } static { - File configDir = new File(FabricLoader.getInstance().getConfigDirectory(), "fabric"); + File configDir = FabricLoader.getInstance().getConfigDir().resolve("fabric").toFile(); if (!configDir.exists()) { if (!configDir.mkdir()) { diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/aocalc/AoCalculator.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/aocalc/AoCalculator.java index 6069b941c..016b4aff7 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/aocalc/AoCalculator.java +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/aocalc/AoCalculator.java @@ -28,12 +28,11 @@ import static net.minecraft.util.math.Direction.UP; import static net.minecraft.util.math.Direction.WEST; import java.util.BitSet; -import java.util.function.ToIntFunction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import net.minecraft.block.Block; +import net.minecraft.block.BlockState; import net.minecraft.client.render.block.BlockModelRenderer; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; @@ -55,10 +54,15 @@ import net.fabricmc.fabric.impl.client.indigo.renderer.render.BlockRenderInfo; */ @Environment(EnvType.CLIENT) public class AoCalculator { + @FunctionalInterface + public interface BrightnessFunc { + int apply(BlockPos pos, BlockState state); + } + /** Used to receive a method reference in constructor for ao value lookup. */ @FunctionalInterface public interface AoFunc { - float apply(BlockPos pos); + float apply(BlockPos pos, BlockState state); } /** @@ -81,10 +85,10 @@ public class AoCalculator { private final BlockPos.Mutable lightPos = new BlockPos.Mutable(); private final BlockPos.Mutable searchPos = new BlockPos.Mutable(); private final BlockRenderInfo blockInfo; - private final ToIntFunction brightnessFunc; + private final BrightnessFunc brightnessFunc; private final AoFunc aoFunc; - /** caches results of {@link #computeFace(Direction, boolean)} for the current block. */ + /** caches results of {@link #computeFace(Direction, boolean, boolean)} for the current block. */ private final AoFaceData[] faceData = new AoFaceData[12]; /** indicates which elements of {@link #faceData} have been computed for the current block. */ @@ -97,7 +101,7 @@ public class AoCalculator { public final float[] ao = new float[4]; public final int[] light = new int[4]; - public AoCalculator(BlockRenderInfo blockInfo, ToIntFunction brightnessFunc, AoFunc aoFunc) { + public AoCalculator(BlockRenderInfo blockInfo, BrightnessFunc brightnessFunc, AoFunc aoFunc) { this.blockInfo = blockInfo; this.brightnessFunc = brightnessFunc; this.aoFunc = aoFunc; @@ -193,7 +197,7 @@ public class AoCalculator { int flags = quad.geometryFlags(); // force to block face if shape is full cube - matches vanilla logic - if ((flags & LIGHT_FACE_FLAG) == 0 && (flags & AXIS_ALIGNED_FLAG) == AXIS_ALIGNED_FLAG && Block.isShapeFullCube(blockInfo.blockState.getCollisionShape(blockInfo.blockView, blockInfo.blockPos))) { + if ((flags & LIGHT_FACE_FLAG) == 0 && (flags & AXIS_ALIGNED_FLAG) != 0 && blockInfo.blockState.isFullCube(blockInfo.blockView, blockInfo.blockPos)) { flags |= LIGHT_FACE_FLAG; } @@ -355,7 +359,7 @@ public class AoCalculator { /** * Computes smoothed brightness and Ao shading for four corners of a block face. - * Outer block face is what you normally see and what you get get when second + * Outer block face is what you normally see and what you get when the second * parameter is true. Inner is light *within* the block and usually darker. * It is blended with the outer face for inset surfaces, but is also used directly * in vanilla logic for some blocks that aren't full opaque cubes. @@ -371,45 +375,76 @@ public class AoCalculator { final BlockRenderView world = blockInfo.blockView; final BlockPos pos = blockInfo.blockPos; + final BlockState blockState = blockInfo.blockState; final BlockPos.Mutable lightPos = this.lightPos; final BlockPos.Mutable searchPos = this.searchPos; + BlockState searchState; + + if (isOnBlockFace) { + lightPos.set(pos, lightFace); + } else { + lightPos.set(pos); + } - lightPos.set(isOnBlockFace ? pos.offset(lightFace) : pos); AoFace aoFace = AoFace.get(lightFace); - searchPos.set(lightPos).move(aoFace.neighbors[0]); - final int light0 = brightnessFunc.applyAsInt(searchPos); - final float ao0 = aoFunc.apply(searchPos); - searchPos.set(lightPos).move(aoFace.neighbors[1]); - final int light1 = brightnessFunc.applyAsInt(searchPos); - final float ao1 = aoFunc.apply(searchPos); - searchPos.set(lightPos).move(aoFace.neighbors[2]); - final int light2 = brightnessFunc.applyAsInt(searchPos); - final float ao2 = aoFunc.apply(searchPos); - searchPos.set(lightPos).move(aoFace.neighbors[3]); - final int light3 = brightnessFunc.applyAsInt(searchPos); - final float ao3 = aoFunc.apply(searchPos); + // Vanilla was further offsetting the positions for opaque block checks in the + // direction of the light face, but it was actually mis-sampling and causing + // visible artifacts in certain situations - // vanilla was further offsetting these in the direction of the light face - // but it was actually mis-sampling and causing visible artifacts in certain situation - searchPos.set(lightPos).move(aoFace.neighbors[0]); //.setOffset(lightFace); - if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) searchPos.move(lightFace); - final boolean isClear0 = world.getBlockState(searchPos).getOpacity(world, searchPos) == 0; - searchPos.set(lightPos).move(aoFace.neighbors[1]); //.setOffset(lightFace); - if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) searchPos.move(lightFace); - final boolean isClear1 = world.getBlockState(searchPos).getOpacity(world, searchPos) == 0; - searchPos.set(lightPos).move(aoFace.neighbors[2]); //.setOffset(lightFace); - if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) searchPos.move(lightFace); - final boolean isClear2 = world.getBlockState(searchPos).getOpacity(world, searchPos) == 0; - searchPos.set(lightPos).move(aoFace.neighbors[3]); //.setOffset(lightFace); - if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) searchPos.move(lightFace); - final boolean isClear3 = world.getBlockState(searchPos).getOpacity(world, searchPos) == 0; + searchPos.set(lightPos, aoFace.neighbors[0]); + searchState = world.getBlockState(searchPos); + final int light0 = brightnessFunc.apply(searchPos, searchState); + final float ao0 = aoFunc.apply(searchPos, searchState); + + if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) { + searchPos.move(lightFace); + searchState = world.getBlockState(searchPos); + } + + final boolean isClear0 = !searchState.shouldBlockVision(world, searchPos) || searchState.getOpacity(world, searchPos) == 0; + + searchPos.set(lightPos, aoFace.neighbors[1]); + searchState = world.getBlockState(searchPos); + final int light1 = brightnessFunc.apply(searchPos, searchState); + final float ao1 = aoFunc.apply(searchPos, searchState); + + if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) { + searchPos.move(lightFace); + searchState = world.getBlockState(searchPos); + } + + final boolean isClear1 = !searchState.shouldBlockVision(world, searchPos) || searchState.getOpacity(world, searchPos) == 0; + + searchPos.set(lightPos, aoFace.neighbors[2]); + searchState = world.getBlockState(searchPos); + final int light2 = brightnessFunc.apply(searchPos, searchState); + final float ao2 = aoFunc.apply(searchPos, searchState); + + if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) { + searchPos.move(lightFace); + searchState = world.getBlockState(searchPos); + } + + final boolean isClear2 = !searchState.shouldBlockVision(world, searchPos) || searchState.getOpacity(world, searchPos) == 0; + + searchPos.set(lightPos, aoFace.neighbors[3]); + searchState = world.getBlockState(searchPos); + final int light3 = brightnessFunc.apply(searchPos, searchState); + final float ao3 = aoFunc.apply(searchPos, searchState); + + if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) { + searchPos.move(lightFace); + searchState = world.getBlockState(searchPos); + } + + final boolean isClear3 = !searchState.shouldBlockVision(world, searchPos) || searchState.getOpacity(world, searchPos) == 0; // c = corner - values at corners of face int cLight0, cLight1, cLight2, cLight3; float cAo0, cAo1, cAo2, cAo3; - // If neighbors on both side of the corner are opaque, then apparently we use the light/shade + // If neighbors on both sides of the corner are opaque, then apparently we use the light/shade // from one of the sides adjacent to the corner. If either neighbor is clear (no light subtraction) // then we use values from the outwardly diagonal corner. (outwardly = position is one more away from light face) if (!isClear2 && !isClear0) { @@ -417,8 +452,9 @@ public class AoCalculator { cLight0 = light0; } else { searchPos.set(lightPos).move(aoFace.neighbors[0]).move(aoFace.neighbors[2]); - cAo0 = aoFunc.apply(searchPos); - cLight0 = brightnessFunc.applyAsInt(searchPos); + searchState = world.getBlockState(searchPos); + cAo0 = aoFunc.apply(searchPos, searchState); + cLight0 = brightnessFunc.apply(searchPos, searchState); } if (!isClear3 && !isClear0) { @@ -426,8 +462,9 @@ public class AoCalculator { cLight1 = light0; } else { searchPos.set(lightPos).move(aoFace.neighbors[0]).move(aoFace.neighbors[3]); - cAo1 = aoFunc.apply(searchPos); - cLight1 = brightnessFunc.applyAsInt(searchPos); + searchState = world.getBlockState(searchPos); + cAo1 = aoFunc.apply(searchPos, searchState); + cLight1 = brightnessFunc.apply(searchPos, searchState); } if (!isClear2 && !isClear1) { @@ -435,8 +472,9 @@ public class AoCalculator { cLight2 = light1; } else { searchPos.set(lightPos).move(aoFace.neighbors[1]).move(aoFace.neighbors[2]); - cAo2 = aoFunc.apply(searchPos); - cLight2 = brightnessFunc.applyAsInt(searchPos); + searchState = world.getBlockState(searchPos); + cAo2 = aoFunc.apply(searchPos, searchState); + cLight2 = brightnessFunc.apply(searchPos, searchState); } if (!isClear3 && !isClear1) { @@ -444,22 +482,24 @@ public class AoCalculator { cLight3 = light1; } else { searchPos.set(lightPos).move(aoFace.neighbors[1]).move(aoFace.neighbors[3]); - cAo3 = aoFunc.apply(searchPos); - cLight3 = brightnessFunc.applyAsInt(searchPos); + searchState = world.getBlockState(searchPos); + cAo3 = aoFunc.apply(searchPos, searchState); + cLight3 = brightnessFunc.apply(searchPos, searchState); } // If on block face or neighbor isn't occluding, "center" will be neighbor brightness // Doesn't use light pos because logic not based solely on this block's geometry int lightCenter; - searchPos.set(pos).move(lightFace); + searchPos.set(pos, lightFace); + searchState = world.getBlockState(searchPos); - if (isOnBlockFace || !world.getBlockState(searchPos).isOpaqueFullCube(world, searchPos)) { - lightCenter = brightnessFunc.applyAsInt(searchPos); + if (isOnBlockFace || !searchState.isOpaqueFullCube(world, searchPos)) { + lightCenter = brightnessFunc.apply(searchPos, searchState); } else { - lightCenter = brightnessFunc.applyAsInt(pos); + lightCenter = brightnessFunc.apply(pos, blockState); } - float aoCenter = aoFunc.apply(isOnBlockFace ? lightPos : pos); + float aoCenter = aoFunc.apply(lightPos, world.getBlockState(lightPos)); float worldBrightness = world.getBrightness(lightFace, shade); result.a0 = ((ao3 + ao0 + cAo1 + aoCenter) * 0.25F) * worldBrightness; @@ -495,12 +535,12 @@ public class AoCalculator { if (b == 0) b = d; if (c == 0) c = d; // bitwise divide by 4, clamp to expected (positive) range - return a + b + c + d >> 2 & 16711935; + return a + b + c + d >> 2 & 0xFF00FF; } private static int meanInnerBrightness(int a, int b, int c, int d) { // bitwise divide by 4, clamp to expected (positive) range - return a + b + c + d >> 2 & 16711935; + return a + b + c + d >> 2 & 0xFF00FF; } private static int nonZeroMin(int a, int b) { diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/aocalc/AoLuminanceFix.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/aocalc/AoLuminanceFix.java index aa19ebc9c..27b46eee9 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/aocalc/AoLuminanceFix.java +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/aocalc/AoLuminanceFix.java @@ -28,16 +28,15 @@ import net.fabricmc.fabric.impl.client.indigo.Indigo; */ @FunctionalInterface public interface AoLuminanceFix { - float apply(BlockView view, BlockPos pos); + float apply(BlockView view, BlockPos pos, BlockState state); AoLuminanceFix INSTANCE = Indigo.FIX_LUMINOUS_AO_SHADE ? AoLuminanceFix::fixed : AoLuminanceFix::vanilla; - static float vanilla(BlockView view, BlockPos pos) { - return view.getBlockState(pos).getAmbientOcclusionLightLevel(view, pos); + static float vanilla(BlockView view, BlockPos pos, BlockState state) { + return state.getAmbientOcclusionLightLevel(view, pos); } - static float fixed(BlockView view, BlockPos pos) { - final BlockState state = view.getBlockState(pos); + static float fixed(BlockView view, BlockPos pos, BlockState state) { return state.getLuminance() == 0 ? state.getAmbientOcclusionLightLevel(view, pos) : 1f; } } diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractMeshConsumer.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractMeshConsumer.java index d62654c65..d0853abbf 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractMeshConsumer.java +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractMeshConsumer.java @@ -19,7 +19,6 @@ package net.fabricmc.fabric.impl.client.indigo.renderer.render; import java.util.function.Consumer; import java.util.function.Function; -import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.RenderLayer; import net.minecraft.client.render.VertexConsumer; @@ -93,25 +92,22 @@ public abstract class AbstractMeshConsumer extends AbstractQuadRenderer implemen return; } - final RenderMaterialImpl.Value mat = quad.material(); - - if (!mat.disableAo(0) && MinecraftClient.isAmbientOcclusionEnabled()) { - // needs to happen before offsets are applied - aoCalc.compute(quad, false); - } - - tessellateQuad(quad, mat, 0); + tessellateQuad(quad, 0); } /** * Determines color index and render layer, then routes to appropriate * tessellate routine based on material properties. */ - private void tessellateQuad(MutableQuadViewImpl quad, RenderMaterialImpl.Value mat, int textureIndex) { + private void tessellateQuad(MutableQuadViewImpl quad, int textureIndex) { + final RenderMaterialImpl.Value mat = quad.material(); final int colorIndex = mat.disableColorIndex(textureIndex) ? -1 : quad.colorIndex(); final RenderLayer renderLayer = blockInfo.effectiveRenderLayer(mat.blendMode(textureIndex)); if (blockInfo.defaultAo && !mat.disableAo(textureIndex)) { + // needs to happen before offsets are applied + aoCalc.compute(quad, false); + if (mat.emissive(textureIndex)) { tessellateSmoothEmissive(quad, renderLayer, colorIndex); } else { diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractQuadRenderer.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractQuadRenderer.java index 409dde259..0a7e9c27b 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractQuadRenderer.java +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractQuadRenderer.java @@ -16,11 +16,11 @@ package net.fabricmc.fabric.impl.client.indigo.renderer.render; +import static net.fabricmc.fabric.impl.client.indigo.renderer.helper.GeometryHelper.AXIS_ALIGNED_FLAG; import static net.fabricmc.fabric.impl.client.indigo.renderer.helper.GeometryHelper.LIGHT_FACE_FLAG; import java.util.function.Function; -import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.client.render.LightmapTextureManager; import net.minecraft.client.render.RenderLayer; @@ -177,8 +177,12 @@ public abstract class AbstractQuadRenderer { // for reference. if (quad.cullFace() != null) { mpos.move(quad.cullFace()); - } else if ((quad.geometryFlags() & LIGHT_FACE_FLAG) != 0 || Block.isShapeFullCube(blockState.getCollisionShape(blockInfo.blockView, pos))) { - mpos.move(quad.lightFace()); + } else { + final int flags = quad.geometryFlags(); + + if ((flags & LIGHT_FACE_FLAG) != 0 || ((flags & AXIS_ALIGNED_FLAG) != 0 && blockState.isFullCube(blockInfo.blockView, pos))) { + mpos.move(quad.lightFace()); + } } // Unfortunately cannot use brightness cache here unless we implement one specifically for flat lighting. See #329 diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/BlockRenderContext.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/BlockRenderContext.java index 279d65335..e35b9ce7f 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/BlockRenderContext.java +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/BlockRenderContext.java @@ -94,17 +94,17 @@ public class BlockRenderContext extends AbstractRenderContext { } }; - private int brightness(BlockPos pos) { + private int brightness(BlockPos pos, BlockState state) { if (blockInfo.blockView == null) { return LightmapTextureManager.MAX_LIGHT_COORDINATE; } - return WorldRenderer.getLightmapCoordinates(blockInfo.blockView, blockInfo.blockView.getBlockState(pos), pos); + return WorldRenderer.getLightmapCoordinates(blockInfo.blockView, state, pos); } - private float aoLevel(BlockPos pos) { + private float aoLevel(BlockPos pos, BlockState state) { final BlockRenderView blockView = blockInfo.blockView; - return blockView == null ? 1f : AoLuminanceFix.INSTANCE.apply(blockView, pos); + return blockView == null ? 1f : AoLuminanceFix.INSTANCE.apply(blockView, pos, state); } private VertexConsumer outputBuffer(RenderLayer renderLayer) { diff --git a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/ChunkRenderInfo.java b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/ChunkRenderInfo.java index 80e7134d4..b1bab0449 100644 --- a/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/ChunkRenderInfo.java +++ b/fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/ChunkRenderInfo.java @@ -119,27 +119,27 @@ public class ChunkRenderInfo { } /** - * Cached values for {@link BlockState#getBlockBrightness(BlockRenderView, BlockPos)}. + * Cached values for {@link WorldRenderer#getLightmapCoordinates(BlockRenderView, BlockState, BlockPos)}. * See also the comments for {@link #brightnessCache}. */ - int cachedBrightness(BlockPos pos) { + int cachedBrightness(BlockPos pos, BlockState state) { long key = pos.asLong(); int result = brightnessCache.get(key); if (result == Integer.MAX_VALUE) { - result = WorldRenderer.getLightmapCoordinates(blockView, blockView.getBlockState(pos), pos); + result = WorldRenderer.getLightmapCoordinates(blockView, state, pos); brightnessCache.put(key, result); } return result; } - float cachedAoLevel(BlockPos pos) { + float cachedAoLevel(BlockPos pos, BlockState state) { long key = pos.asLong(); float result = aoLevelCache.get(key); if (result == Float.MAX_VALUE) { - result = AoLuminanceFix.INSTANCE.apply(blockView, pos); + result = AoLuminanceFix.INSTANCE.apply(blockView, pos, state); aoLevelCache.put(key, result); } From c54bb47e4e5595dfb4290c4603000d68eee0f035 Mon Sep 17 00:00:00 2001 From: deirn Date: Fri, 22 Jul 2022 02:07:37 +0700 Subject: [PATCH 2/5] Make disconnected screen reason text scrollable (#2349) * Make disconnected screen reason text scrollable * Remove redundant mixin. * wrong unique * fix max scroll having additional line-sized gap * turns out it's the testmod fault for having an extra new line Co-authored-by: modmuss50 --- .../client/DisconnectedScreenMixin.java | 94 +++++++++++++++++++ ...abric-networking-api-v1.client.mixins.json | 3 +- .../DisconnectScreenTest.java | 40 ++++++++ .../src/testmod/resources/fabric.mod.json | 1 + 4 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/mixin/networking/client/DisconnectedScreenMixin.java create mode 100644 fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/disconnectscreen/DisconnectScreenTest.java diff --git a/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/mixin/networking/client/DisconnectedScreenMixin.java b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/mixin/networking/client/DisconnectedScreenMixin.java new file mode 100644 index 000000000..cc770b064 --- /dev/null +++ b/fabric-networking-api-v1/src/client/java/net/fabricmc/fabric/mixin/networking/client/DisconnectedScreenMixin.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.networking.client; + +import com.mojang.blaze3d.systems.RenderSystem; +import org.objectweb.asm.Opcodes; +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.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.client.font.MultilineText; +import net.minecraft.client.gui.screen.DisconnectedScreen; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.util.math.MathHelper; + +/** + * This mixin makes disconnect reason text scrollable. + */ +@Mixin(DisconnectedScreen.class) +public abstract class DisconnectedScreenMixin extends Screen { + @Shadow + private int reasonHeight; + + @Unique + private int actualReasonHeight; + + @Unique + private int scroll; + + @Unique + private int maxScroll; + + private DisconnectedScreenMixin() { + super(null); + } + + // Inject to right after reasonHeight is stored, to make sure the back button have correct position. + @Inject(method = "init", at = @At(value = "FIELD", target = "Lnet/minecraft/client/gui/screen/DisconnectedScreen;reasonHeight:I", opcode = Opcodes.PUTFIELD, shift = At.Shift.AFTER)) + private void init(CallbackInfo ci) { + actualReasonHeight = reasonHeight; + reasonHeight = Math.min(reasonHeight, height - 100); + scroll = 0; + maxScroll = actualReasonHeight - reasonHeight; + } + + @Redirect(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/font/MultilineText;drawCenterWithShadow(Lnet/minecraft/client/util/math/MatrixStack;II)I")) + private int render(MultilineText instance, MatrixStack matrixStack, int x, int y) { + double scale = client.getWindow().getScaleFactor(); + RenderSystem.enableScissor(0, (int) (y * scale), (int) (width * scale), (int) (reasonHeight * scale)); + instance.drawCenterWithShadow(matrixStack, x, y - scroll); + RenderSystem.disableScissor(); + + // Draw gradient at the top/bottom to indicate that the text is scrollable. + if (actualReasonHeight > reasonHeight) { + int startX = (width - instance.getMaxWidth()) / 2; + int endX = (width + instance.getMaxWidth()) / 2; + + if (scroll > 0) { + fillGradient(matrixStack, startX, y, endX, y + 10, 0xFF000000, 0); + } + + if (scroll < maxScroll) { + fillGradient(matrixStack, startX, y + reasonHeight - 10, endX, y + reasonHeight, 0, 0xFF000000); + } + } + + return y + reasonHeight; + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double amount) { + scroll = MathHelper.clamp(scroll - (MathHelper.sign(amount) * client.textRenderer.fontHeight * 10), 0, maxScroll); + return super.mouseScrolled(mouseX, mouseY, amount); + } +} diff --git a/fabric-networking-api-v1/src/client/resources/fabric-networking-api-v1.client.mixins.json b/fabric-networking-api-v1/src/client/resources/fabric-networking-api-v1.client.mixins.json index 356826f22..539b9bef7 100644 --- a/fabric-networking-api-v1/src/client/resources/fabric-networking-api-v1.client.mixins.json +++ b/fabric-networking-api-v1/src/client/resources/fabric-networking-api-v1.client.mixins.json @@ -6,7 +6,8 @@ "accessor.ConnectScreenAccessor", "accessor.MinecraftClientAccessor", "client.ClientLoginNetworkHandlerMixin", - "client.ClientPlayNetworkHandlerMixin" + "client.ClientPlayNetworkHandlerMixin", + "client.DisconnectedScreenMixin" ], "injectors": { "defaultRequire": 1 diff --git a/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/disconnectscreen/DisconnectScreenTest.java b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/disconnectscreen/DisconnectScreenTest.java new file mode 100644 index 000000000..1892061cb --- /dev/null +++ b/fabric-networking-api-v1/src/testmod/java/net/fabricmc/fabric/test/networking/disconnectscreen/DisconnectScreenTest.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.test.networking.disconnectscreen; + +import net.minecraft.text.Text; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; + +public class DisconnectScreenTest implements ClientModInitializer { + @Override + public void onInitializeClient() { + ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> + dispatcher.register(ClientCommandManager.literal("disconnect_screen_test").executes(context -> { + StringBuilder builder = new StringBuilder("A very long disconnect reason:"); + + for (int i = 0; i < 100; i++) { + builder.append("\nLine ").append(i + 1); + } + + context.getSource().getPlayer().networkHandler.getConnection().disconnect(Text.of(builder.toString())); + return 1; + }))); + } +} diff --git a/fabric-networking-api-v1/src/testmod/resources/fabric.mod.json b/fabric-networking-api-v1/src/testmod/resources/fabric.mod.json index 664aee76b..3a7472728 100644 --- a/fabric-networking-api-v1/src/testmod/resources/fabric.mod.json +++ b/fabric-networking-api-v1/src/testmod/resources/fabric.mod.json @@ -17,6 +17,7 @@ ], "client": [ "net.fabricmc.fabric.test.networking.channeltest.NetworkingChannelClientTest", + "net.fabricmc.fabric.test.networking.disconnectscreen.DisconnectScreenTest", "net.fabricmc.fabric.test.networking.keybindreciever.NetworkingKeybindClientPacketTest", "net.fabricmc.fabric.test.networking.login.NetworkingLoginQueryClientTest", "net.fabricmc.fabric.test.networking.play.NetworkingPlayPacketClientTest" From 442de8b8625915d8503533f039df686644b45ca5 Mon Sep 17 00:00:00 2001 From: apple502j <33279053+apple502j@users.noreply.github.com> Date: Fri, 22 Jul 2022 04:07:48 +0900 Subject: [PATCH 3/5] Fix incorrect check in GlobalReceiverRegistry (#2363) --- .../fabricmc/fabric/impl/networking/GlobalReceiverRegistry.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/GlobalReceiverRegistry.java b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/GlobalReceiverRegistry.java index a93fca984..c2518cc03 100644 --- a/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/GlobalReceiverRegistry.java +++ b/fabric-networking-api-v1/src/main/java/net/fabricmc/fabric/impl/networking/GlobalReceiverRegistry.java @@ -68,7 +68,7 @@ public final class GlobalReceiverRegistry { try { final boolean replaced = this.handlers.putIfAbsent(channelName, handler) == null; - if (!replaced) { + if (replaced) { this.handleRegistration(channelName, handler); } From 35a03c43c2b913659a489eab29868ea841c7e80e Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Thu, 21 Jul 2022 21:08:00 +0200 Subject: [PATCH 4/5] Fix inconsistent ordering of item attribute modifiers by using a linked hashmap (#2380) --- .../java/net/fabricmc/fabric/mixin/item/ItemStackMixin.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/ItemStackMixin.java b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/ItemStackMixin.java index d56c807b7..8b2b009c5 100644 --- a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/ItemStackMixin.java +++ b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/ItemStackMixin.java @@ -18,7 +18,7 @@ package net.fabricmc.fabric.mixin.item; import java.util.function.Consumer; -import com.google.common.collect.HashMultimap; +import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Multimap; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -83,8 +83,8 @@ public abstract class ItemStackMixin { ) public Multimap hookGetAttributeModifiers(Item item, EquipmentSlot slot) { ItemStack stack = (ItemStack) (Object) this; - //we need to ensure it is modifiable for the callback - Multimap attributeModifiers = HashMultimap.create(item.getAttributeModifiers(stack, slot)); + //we need to ensure it is modifiable for the callback, use linked map to preserve ordering + Multimap attributeModifiers = LinkedHashMultimap.create(item.getAttributeModifiers(stack, slot)); ModifyItemAttributeModifiersCallback.EVENT.invoker().modifyAttributeModifiers(stack, slot, attributeModifiers); return attributeModifiers; } From 5f1a85e0654c83f58631230427d466e2e34e980b Mon Sep 17 00:00:00 2001 From: deirn Date: Fri, 22 Jul 2022 02:08:20 +0700 Subject: [PATCH 5/5] fix custom dimension not loaded on world preset other than default (#2387) --- .../loader/client/CreateWorldScreenMixin.java | 63 +++++++++++++++---- .../client/MoreOptionsDialogAccessor.java | 29 +++++++++ ...bric-resource-loader-v0.client.mixins.json | 3 +- 3 files changed, 81 insertions(+), 14 deletions(-) create mode 100644 fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/MoreOptionsDialogAccessor.java diff --git a/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/CreateWorldScreenMixin.java b/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/CreateWorldScreenMixin.java index e4595fcf0..a84d34d9f 100644 --- a/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/CreateWorldScreenMixin.java +++ b/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/CreateWorldScreenMixin.java @@ -17,10 +17,14 @@ package net.fabricmc.fabric.mixin.resource.loader.client; import java.io.File; +import java.util.concurrent.CompletableFuture; import com.google.gson.JsonElement; import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; import com.mojang.serialization.JsonOps; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -31,25 +35,29 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyArg; import org.spongepowered.asm.mixin.injection.ModifyVariable; import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.world.CreateWorldScreen; +import net.minecraft.client.gui.screen.world.MoreOptionsDialog; +import net.minecraft.client.world.GeneratorOptionsHolder; import net.minecraft.resource.DataPackSettings; import net.minecraft.resource.ResourceManager; import net.minecraft.resource.ResourcePackManager; import net.minecraft.resource.ResourceType; +import net.minecraft.server.SaveLoading; import net.minecraft.util.Util; import net.minecraft.util.dynamic.RegistryOps; import net.minecraft.util.registry.DynamicRegistryManager; import net.minecraft.world.gen.GeneratorOptions; -import net.minecraft.world.gen.WorldPresets; import net.fabricmc.fabric.impl.resource.loader.ModResourcePackCreator; import net.fabricmc.fabric.impl.resource.loader.ModResourcePackUtil; import net.fabricmc.fabric.mixin.resource.loader.ResourcePackManagerAccessor; @Mixin(CreateWorldScreen.class) -public abstract class CreateWorldScreenMixin { +public abstract class CreateWorldScreenMixin extends Screen { @Unique private static DataPackSettings defaultDataPackSettings; @@ -60,8 +68,25 @@ public abstract class CreateWorldScreenMixin { @Final private static Logger LOGGER; - @Unique - private static RegistryOps loadedOps; + @Shadow + @Final + public MoreOptionsDialog moreOptionsDialog; + + @Shadow + protected DataPackSettings dataPackSettings; + + @Shadow + private static SaveLoading.ServerConfig createServerConfig(ResourcePackManager resourcePackManager, DataPackSettings dataPackSettings) { + return null; + } + + @Shadow + @Nullable + protected abstract Pair getScannedPack(); + + private CreateWorldScreenMixin() { + super(null); + } @ModifyVariable(method = "create(Lnet/minecraft/client/MinecraftClient;Lnet/minecraft/client/gui/screen/Screen;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/world/CreateWorldScreen;createServerConfig(Lnet/minecraft/resource/ResourcePackManager;Lnet/minecraft/resource/DataPackSettings;)Lnet/minecraft/server/SaveLoading$ServerConfig;")) @@ -84,22 +109,34 @@ public abstract class CreateWorldScreenMixin { @Redirect(method = "method_41854", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/registry/DynamicRegistryManager$Mutable;toImmutable()Lnet/minecraft/util/registry/DynamicRegistryManager$Immutable;")) private static DynamicRegistryManager.Immutable loadDynamicRegistry(DynamicRegistryManager.Mutable mutableRegistryManager, ResourceManager dataPackManager) { // This loads the dynamic registry from the data pack - loadedOps = RegistryOps.ofLoaded(JsonOps.INSTANCE, mutableRegistryManager, dataPackManager); + RegistryOps.ofLoaded(JsonOps.INSTANCE, mutableRegistryManager, dataPackManager); return mutableRegistryManager.toImmutable(); } /** - * Fix GeneratorOptions not having custom dimensions. + * Load the DynamicRegistryManager again to fix GeneratorOptions not having custom dimensions. * Taken from {@link CreateWorldScreen#applyDataPacks(ResourcePackManager)}. */ @SuppressWarnings("JavadocReference") - @Redirect(method = "method_41854", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/gen/WorldPresets;createDefaultOptions(Lnet/minecraft/util/registry/DynamicRegistryManager;)Lnet/minecraft/world/gen/GeneratorOptions;")) - private static GeneratorOptions loadDatapackDimensions(DynamicRegistryManager dynamicRegistryManager) { - GeneratorOptions defaultGen = WorldPresets.createDefaultOptions(dynamicRegistryManager); - RegistryOps registryOps = RegistryOps.of(JsonOps.INSTANCE, dynamicRegistryManager); - return GeneratorOptions.CODEC.encodeStart(registryOps, defaultGen) - .flatMap(json -> GeneratorOptions.CODEC.parse(loadedOps, json)) - .getOrThrow(false, Util.addPrefix("Error parsing worldgen settings after loading data packs: ", LOGGER::error)); + @Inject(method = "startServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/world/CreateWorldScreen;clearDataPackTempDir()V")) + private void loadDatapackDimensions(CallbackInfo ci) { + CompletableFuture future = SaveLoading.load(createServerConfig(getScannedPack().getSecond(), dataPackSettings), (resourceManager, dataPackSettings1) -> { + GeneratorOptionsHolder holder = moreOptionsDialog.getGeneratorOptionsHolder(); + DynamicRegistryManager.Mutable newDrm = DynamicRegistryManager.createAndLoad(); + DynamicOps heldOps = RegistryOps.of(JsonOps.INSTANCE, holder.dynamicRegistryManager()); + DynamicOps newOps = RegistryOps.ofLoaded(JsonOps.INSTANCE, newDrm, resourceManager); + DataResult result = GeneratorOptions.CODEC.encodeStart(heldOps, holder.generatorOptions()) + .flatMap(json -> GeneratorOptions.CODEC.parse(newOps, json)); + return Pair.of(result, newDrm.toImmutable()); + }, (resourceManager, dataPackContents, drm, result) -> { + resourceManager.close(); + GeneratorOptions options = result.getOrThrow(false, Util.addPrefix("Error parsing worldgen settings after loading data packs: ", LOGGER::error)); + GeneratorOptionsHolder holder = new GeneratorOptionsHolder(options, result.lifecycle().add(drm.getRegistryLifecycle()), drm, dataPackContents); + ((MoreOptionsDialogAccessor) moreOptionsDialog).callSetGeneratorOptionsHolder(holder); + return null; + }, Util.getMainWorkerExecutor(), client); + + client.runTasks(future::isDone); } @Inject(method = "getScannedPack", diff --git a/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/MoreOptionsDialogAccessor.java b/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/MoreOptionsDialogAccessor.java new file mode 100644 index 000000000..9b056bda6 --- /dev/null +++ b/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/MoreOptionsDialogAccessor.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.resource.loader.client; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +import net.minecraft.client.gui.screen.world.MoreOptionsDialog; +import net.minecraft.client.world.GeneratorOptionsHolder; + +@Mixin(MoreOptionsDialog.class) +public interface MoreOptionsDialogAccessor { + @Invoker + void callSetGeneratorOptionsHolder(GeneratorOptionsHolder generatorOptionsHolder); +} diff --git a/fabric-resource-loader-v0/src/client/resources/fabric-resource-loader-v0.client.mixins.json b/fabric-resource-loader-v0/src/client/resources/fabric-resource-loader-v0.client.mixins.json index f2cc70818..5bed17030 100644 --- a/fabric-resource-loader-v0/src/client/resources/fabric-resource-loader-v0.client.mixins.json +++ b/fabric-resource-loader-v0/src/client/resources/fabric-resource-loader-v0.client.mixins.json @@ -7,7 +7,8 @@ "CreateWorldScreenMixin", "FontManagerResourceReloadListenerMixin", "GameOptionsMixin", - "KeyedResourceReloadListenerClientMixin" + "KeyedResourceReloadListenerClientMixin", + "MoreOptionsDialogAccessor" ], "injectors": { "defaultRequire": 1