diff --git a/README.md b/README.md index 8095644e..9dc6a619 100644 --- a/README.md +++ b/README.md @@ -253,6 +253,7 @@ Available in flavors [**Cleanroom**](https://www.curseforge.com/minecraft/modpac * **Mob Despawning Improvement:** Mobs carrying picked up items will despawn properly (and optionally drop their equipment) * **Mob Griefing:** Controls mob griefing through customizable lists * **Mob Spawning Light Level:** Sets the maximum light level for hostile mobs to spawn +* * **Modern Debug Render:** Aligns the chunk border (F3+G) and hitbox (F3+B) overlays with Minecraft 1.21.1+ * **Modern Knockback:** Backports 1.16+ knockback to 1.12: Knockback resistance is now a scale instead of a probability * **More Banner Layers:** Sets the amount of applicable pattern layers for banners * **Music Control:** Enables various music playback control tweaks diff --git a/src/main/java/mod/acgaming/universaltweaks/config/UTConfigTweaks.java b/src/main/java/mod/acgaming/universaltweaks/config/UTConfigTweaks.java index 8ff33476..5617a4a5 100644 --- a/src/main/java/mod/acgaming/universaltweaks/config/UTConfigTweaks.java +++ b/src/main/java/mod/acgaming/universaltweaks/config/UTConfigTweaks.java @@ -2047,6 +2047,11 @@ public static class MiscCategory }) public int utLinearXP = 0; + @Config.RequiresMcRestart + @Config.Name("Modern Debug Render") + @Config.Comment("Aligns the chunk border (F3+G) and hitbox (F3+B) overlays with Minecraft 1.21.1+") + public boolean utModernDebugRenderToggle = true; + @Config.RequiresMcRestart @Config.Name("More Banner Layers") @Config.Comment diff --git a/src/main/java/mod/acgaming/universaltweaks/core/UTLoadingPlugin.java b/src/main/java/mod/acgaming/universaltweaks/core/UTLoadingPlugin.java index 7a5cbf81..ed457e8a 100644 --- a/src/main/java/mod/acgaming/universaltweaks/core/UTLoadingPlugin.java +++ b/src/main/java/mod/acgaming/universaltweaks/core/UTLoadingPlugin.java @@ -286,6 +286,7 @@ public class UTLoadingPlugin implements IFMLLoadingPlugin, IEarlyMixinLoader put("mixins/tweaks/mixins.misc.hotbarscroll.json", c -> UTConfigTweaks.MISC.utDisableHotbarScrollWrapping); put("mixins/tweaks/mixins.misc.lightning.flash.json", c -> UTConfigTweaks.MISC.LIGHTNING.utLightningFlashToggle); put("mixins/tweaks/mixins.misc.gui.mainmenu.json", c -> UTConfigTweaks.MISC.utReturnToMainMenu); + put("mixins/tweaks/mixins.misc.moderndebugrender.json", c -> UTConfigTweaks.MISC.utModernDebugRenderToggle); put("mixins/tweaks/mixins.misc.music.json", c -> UTConfigTweaks.MISC.MUSIC.utMusicControlToggle); put("mixins/tweaks/mixins.misc.narrator.json", c -> UTConfigTweaks.MISC.utDisableNarratorToggle); put("mixins/tweaks/mixins.misc.narratorkeybind.json", c -> UTConfigTweaks.MISC.utUseCustomNarratorKeybind); diff --git a/src/main/java/mod/acgaming/universaltweaks/tweaks/misc/moderndebugrender/UTModernDebugRender.java b/src/main/java/mod/acgaming/universaltweaks/tweaks/misc/moderndebugrender/UTModernDebugRender.java new file mode 100644 index 00000000..f8f8e5c9 --- /dev/null +++ b/src/main/java/mod/acgaming/universaltweaks/tweaks/misc/moderndebugrender/UTModernDebugRender.java @@ -0,0 +1,78 @@ +package mod.acgaming.universaltweaks.tweaks.misc.moderndebugrender; + +import javax.annotation.Nullable; + +import org.lwjgl.opengl.GL11; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.entity.Entity; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; + +public class UTModernDebugRender +{ + public static final float STROKE_WIDTH = 2.5F; + public static final float THICK_WIDTH = 4.0F; + public static final float THIN_WIDTH = 1.0F; + + @Nullable + public static Vec3d cameraPos(float partialTicks) + { + Entity camera = Minecraft.getMinecraft().getRenderViewEntity(); + if (camera == null) return null; + return new Vec3d( + camera.lastTickPosX + (camera.posX - camera.lastTickPosX) * partialTicks, + camera.lastTickPosY + (camera.posY - camera.lastTickPosY) * partialTicks, + camera.lastTickPosZ + (camera.posZ - camera.lastTickPosZ) * partialTicks); + } + + // Start of the 16 block cell the coordinate falls into, relative to the coordinate itself + public static double sectionOrigin(double coord) + { + return (MathHelper.floor(coord) >> 4 << 4) - coord; + } + + public static void drawArrowHead(Vec3d start, Vec3d end, float red, float green, float blue) + { + Vec3d shaft = end.subtract(start); + double length = shaft.length(); + if (length < 1.0E-5D) return; + + Vec3d forward = shaft.scale(1.0D / length); + double barb = MathHelper.clamp(length * 0.1D, 0.1D, 1.0D); + // The seed only has to be non-parallel to the shaft to yield a usable perpendicular + Vec3d seed = Math.abs(forward.y) > 0.999D ? new Vec3d(1.0D, 0.0D, 0.0D) : new Vec3d(0.0D, 1.0D, 0.0D); + Vec3d sideA = forward.crossProduct(seed).normalize().scale(barb); + // forward is unit length and perpendicular to sideA, so this product is already barb long + Vec3d sideB = forward.crossProduct(sideA); + Vec3d base = end.subtract(forward.scale(barb)); + + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder buffer = tessellator.getBuffer(); + buffer.begin(GL11.GL_LINES, DefaultVertexFormats.POSITION_COLOR); + addLine(buffer, base.add(sideA), end, red, green, blue); + addLine(buffer, base.subtract(sideA), end, red, green, blue); + addLine(buffer, base.add(sideB), end, red, green, blue); + addLine(buffer, base.subtract(sideB), end, red, green, blue); + tessellator.draw(); + } + + public static void drawPoint(double x, double y, double z, float red, float green, float blue) + { + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder buffer = tessellator.getBuffer(); + GL11.glPointSize(2.0F); + buffer.begin(GL11.GL_POINTS, DefaultVertexFormats.POSITION_COLOR); + buffer.pos(x, y, z).color(red, green, blue, 1.0F).endVertex(); + tessellator.draw(); + GL11.glPointSize(1.0F); + } + + private static void addLine(BufferBuilder buffer, Vec3d start, Vec3d end, float red, float green, float blue) + { + buffer.pos(start.x, start.y, start.z).color(red, green, blue, 1.0F).endVertex(); + buffer.pos(end.x, end.y, end.z).color(red, green, blue, 1.0F).endVertex(); + } +} \ No newline at end of file diff --git a/src/main/java/mod/acgaming/universaltweaks/tweaks/misc/moderndebugrender/mixin/UTChunkBorderMixin.java b/src/main/java/mod/acgaming/universaltweaks/tweaks/misc/moderndebugrender/mixin/UTChunkBorderMixin.java new file mode 100644 index 00000000..114b2437 --- /dev/null +++ b/src/main/java/mod/acgaming/universaltweaks/tweaks/misc/moderndebugrender/mixin/UTChunkBorderMixin.java @@ -0,0 +1,115 @@ +package mod.acgaming.universaltweaks.tweaks.misc.moderndebugrender.mixin; + +import org.lwjgl.opengl.GL11; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.RenderGlobal; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.debug.DebugRendererChunkBorder; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.Vec3d; + +import com.llamalad7.mixinextras.sugar.Local; +import com.llamalad7.mixinextras.sugar.Share; +import com.llamalad7.mixinextras.sugar.ref.LocalDoubleRef; +import mod.acgaming.universaltweaks.tweaks.misc.moderndebugrender.UTModernDebugRender; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.*; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.invoke.arg.Args; + +@Mixin(DebugRendererChunkBorder.class) +public abstract class UTChunkBorderMixin +{ + // Interpolated camera Y. The X and Z it is derived from are dead once the two origins below are stored. + @ModifyVariable(method = "render", at = @At("STORE"), ordinal = 1) + private double utCameraY(double original, @Local(argsOnly = true) float partialTicks, @Share("utCameraY") LocalDoubleRef shared) + { + Vec3d camera = UTModernDebugRender.cameraPos(partialTicks); + double value = camera == null ? original : camera.y; + shared.set(value); + return value; + } + + @ModifyVariable(method = "render", at = @At("STORE"), ordinal = 5) + private double utOriginX(double original, @Local(argsOnly = true) float partialTicks, @Share("utOriginX") LocalDoubleRef shared) + { + Vec3d camera = UTModernDebugRender.cameraPos(partialTicks); + double value = camera == null ? original : UTModernDebugRender.sectionOrigin(camera.x); + shared.set(value); + return value; + } + + @ModifyVariable(method = "render", at = @At("STORE"), ordinal = 6) + private double utOriginZ(double original, @Local(argsOnly = true) float partialTicks, @Share("utOriginZ") LocalDoubleRef shared) + { + Vec3d camera = UTModernDebugRender.cameraPos(partialTicks); + double value = camera == null ? original : UTModernDebugRender.sectionOrigin(camera.z); + shared.set(value); + return value; + } + + @Redirect(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GlStateManager;disableBlend()V")) + private void utKeepBlend() + { + GlStateManager.enableBlend(); + GlStateManager.tryBlendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO); + } + + // Colour calls 0-19 belong to the vertical grids, 20 onwards to the rings and the major lines. + // Only the yellow ones are ours to recolour, so the red and blue passes need no further slicing. + @ModifyArgs(method = "render", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/BufferBuilder;color(FFFF)Lnet/minecraft/client/renderer/BufferBuilder;"), + slice = @Slice(to = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/BufferBuilder;color(FFFF)Lnet/minecraft/client/renderer/BufferBuilder;", ordinal = 19))) + private void utGridCell(Args args, @Local(ordinal = 0) int coord) + { + if (coord % 4 == 0) utCellColor(args); + } + + @ModifyArgs(method = "render", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/BufferBuilder;color(FFFF)Lnet/minecraft/client/renderer/BufferBuilder;"), + slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/BufferBuilder;color(FFFF)Lnet/minecraft/client/renderer/BufferBuilder;", ordinal = 20))) + private void utRingCell(Args args, @Local(ordinal = 0) int coord) + { + if (coord % 8 == 0) utCellColor(args); + } + + @ModifyArg(method = "render", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GlStateManager;glLineWidth(F)V"), + slice = @Slice(to = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GlStateManager;glLineWidth(F)V", ordinal = 1))) + private float utPassWidth(float width) + { + return UTModernDebugRender.THICK_WIDTH; + } + + @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/BufferBuilder;pos(DDD)Lnet/minecraft/client/renderer/BufferBuilder;", ordinal = 4), require = 1) + private void utSplitGridBatch(float partialTicks, long finishTimeNano, CallbackInfo ci, @Local(ordinal = 0) Tessellator tess, @Local(ordinal = 0) BufferBuilder buffer) + { + tess.draw(); + GlStateManager.glLineWidth(UTModernDebugRender.THIN_WIDTH); + buffer.begin(GL11.GL_LINE_STRIP, DefaultVertexFormats.POSITION_COLOR); + } + + @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Tessellator;draw()V", ordinal = 1, shift = At.Shift.AFTER), require = 1) + private void utCameraSection(float partialTicks, long finishTimeNano, CallbackInfo ci, @Share("utCameraY") LocalDoubleRef cameraY, @Share("utOriginX") LocalDoubleRef originX, @Share("utOriginZ") LocalDoubleRef originZ) + { + double x = originX.get(); + double z = originZ.get(); + double y = UTModernDebugRender.sectionOrigin(cameraY.get()); + GlStateManager.glLineWidth(UTModernDebugRender.THIN_WIDTH); + GlStateManager.disableDepth(); + RenderGlobal.drawSelectionBoundingBox(new AxisAlignedBB(x, y, z, x + 16.0D, y + 16.0D, z + 16.0D), 0.25F, 0.25F, 1.0F, 1.0F); + GlStateManager.enableDepth(); + } + + @Unique + private static void utCellColor(Args args) + { + if ((float) args.get(0) != 1.0F || (float) args.get(1) != 1.0F || (float) args.get(2) != 0.0F) return; + args.set(0, 0.0F); + args.set(1, 155.0F / 255.0F); + args.set(2, 155.0F / 255.0F); + } +} \ No newline at end of file diff --git a/src/main/java/mod/acgaming/universaltweaks/tweaks/misc/moderndebugrender/mixin/UTCollisionBoxMixin.java b/src/main/java/mod/acgaming/universaltweaks/tweaks/misc/moderndebugrender/mixin/UTCollisionBoxMixin.java new file mode 100644 index 00000000..627d6468 --- /dev/null +++ b/src/main/java/mod/acgaming/universaltweaks/tweaks/misc/moderndebugrender/mixin/UTCollisionBoxMixin.java @@ -0,0 +1,65 @@ +package mod.acgaming.universaltweaks.tweaks.misc.moderndebugrender.mixin; + +import java.util.List; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.debug.DebugRendererCollisionBox; +import net.minecraft.entity.Entity; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import mod.acgaming.universaltweaks.tweaks.misc.moderndebugrender.UTModernDebugRender; +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.ModifyArg; + +@Mixin(DebugRendererCollisionBox.class) +public abstract class UTCollisionBoxMixin +{ + @Shadow + private double renderPosX; + @Shadow + private double renderPosY; + @Shadow + private double renderPosZ; + + @Unique + private long utLastUpdate; + + @Unique + private List utShapes; + + @WrapOperation(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;getCollisionBoxes(Lnet/minecraft/entity/Entity;Lnet/minecraft/util/math/AxisAlignedBB;)Ljava/util/List;")) + private List utCacheShapes(World world, Entity entity, AxisAlignedBB box, Operation> original) + { + long now = System.nanoTime(); + // null rather than a timestamp sentinel, because nanoTime has no defined origin to compare against + if (this.utShapes == null || now - this.utLastUpdate > 100_000_000L) + { + this.utLastUpdate = now; + Entity camera = Minecraft.getMinecraft().getRenderViewEntity(); + this.utShapes = camera == null ? original.call(world, entity, box) : original.call(world, camera, camera.getEntityBoundingBox().grow(6.0D)); + } + return this.utShapes; + } + + // The shapes arrive offset by the player position, which is not the camera while spectating + @ModifyArg(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderGlobal;drawSelectionBoundingBox(Lnet/minecraft/util/math/AxisAlignedBB;FFFF)V"), index = 0) + private AxisAlignedBB utCameraOffset(AxisAlignedBB box, @Local(argsOnly = true) float partialTicks) + { + Vec3d camera = UTModernDebugRender.cameraPos(partialTicks); + return camera == null ? box : box.offset(this.renderPosX - camera.x, this.renderPosY - camera.y, this.renderPosZ - camera.z); + } + + @ModifyArg(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GlStateManager;glLineWidth(F)V")) + private float utStrokeWidth(float width) + { + return UTModernDebugRender.STROKE_WIDTH; + } +} \ No newline at end of file diff --git a/src/main/java/mod/acgaming/universaltweaks/tweaks/misc/moderndebugrender/mixin/UTEntityHitboxMixin.java b/src/main/java/mod/acgaming/universaltweaks/tweaks/misc/moderndebugrender/mixin/UTEntityHitboxMixin.java new file mode 100644 index 00000000..3c418704 --- /dev/null +++ b/src/main/java/mod/acgaming/universaltweaks/tweaks/misc/moderndebugrender/mixin/UTEntityHitboxMixin.java @@ -0,0 +1,58 @@ +package mod.acgaming.universaltweaks.tweaks.misc.moderndebugrender.mixin; + +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.renderer.entity.RenderManager; +import net.minecraft.entity.Entity; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.Vec3d; + +import com.llamalad7.mixinextras.sugar.Local; +import com.llamalad7.mixinextras.sugar.Share; +import com.llamalad7.mixinextras.sugar.ref.LocalFloatRef; +import mod.acgaming.universaltweaks.tweaks.misc.moderndebugrender.UTModernDebugRender; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyArgs; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.invoke.arg.Args; + +@Mixin(RenderManager.class) +public abstract class UTEntityHitboxMixin +{ + // The lines are unlit, so the entity brightness is swapped for full bright and restored afterwards + @Inject(method = "renderDebugBoundingBox", at = @At("HEAD")) + private void utStrokeWidth(Entity entity, double x, double y, double z, float entityYaw, float partialTicks, CallbackInfo ci, @Share("utBrightnessX") LocalFloatRef brightnessX, @Share("utBrightnessY") LocalFloatRef brightnessY) + { + GlStateManager.glLineWidth(UTModernDebugRender.STROKE_WIDTH); + brightnessX.set(OpenGlHelper.lastBrightnessX); + brightnessY.set(OpenGlHelper.lastBrightnessY); + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240.0F, 240.0F); + } + + // Use the actual box for eye pos calculation + @ModifyArgs(method = "renderDebugBoundingBox", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderGlobal;drawBoundingBox(DDDDDDFFFF)V", ordinal = 2)) + private void utEyeBox(Args args, @Local(argsOnly = true, ordinal = 0) Entity entity, @Local(argsOnly = true, ordinal = 0) double x, @Local(argsOnly = true, ordinal = 1) double y, @Local(argsOnly = true, ordinal = 2) double z, @Local(ordinal = 0) AxisAlignedBB box) + { + double eyeY = y + box.minY - entity.posY + entity.getEyeHeight(); + args.set(0, box.minX - entity.posX + x); + args.set(1, eyeY - 0.01D); + args.set(2, box.minZ - entity.posZ + z); + args.set(3, box.maxX - entity.posX + x); + args.set(4, eyeY + 0.01D); + args.set(5, box.maxZ - entity.posZ + z); + } + + // The only draw in this method is the view vector, so afterwards the arrow head can be appended + // while texturing and depth writes are still off. + @Inject(method = "renderDebugBoundingBox", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Tessellator;draw()V", shift = At.Shift.AFTER)) + private void utArrowHeadAndCentre(Entity entity, double x, double y, double z, float entityYaw, float partialTicks, CallbackInfo ci, @Share("utBrightnessX") LocalFloatRef brightnessX, @Share("utBrightnessY") LocalFloatRef brightnessY) + { + Vec3d eye = new Vec3d(x, y + entity.getEyeHeight(), z); + UTModernDebugRender.drawArrowHead(eye, eye.add(entity.getLook(partialTicks).scale(2.0D)), 0.0F, 0.0F, 1.0F); + UTModernDebugRender.drawPoint(x, y, z, 1.0F, 1.0F, 1.0F); + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, brightnessX.get(), brightnessY.get()); + GlStateManager.glLineWidth(UTModernDebugRender.THIN_WIDTH); + } +} \ No newline at end of file diff --git a/src/main/resources/mixins/tweaks/mixins.misc.moderndebugrender.json b/src/main/resources/mixins/tweaks/mixins.misc.moderndebugrender.json new file mode 100644 index 00000000..960e5593 --- /dev/null +++ b/src/main/resources/mixins/tweaks/mixins.misc.moderndebugrender.json @@ -0,0 +1,7 @@ +{ + "package": "mod.acgaming.universaltweaks.tweaks.misc.moderndebugrender.mixin", + "refmap": "universaltweaks.refmap.json", + "minVersion": "0.8", + "compatibilityLevel": "JAVA_8", + "client": ["UTChunkBorderMixin", "UTCollisionBoxMixin", "UTEntityHitboxMixin"] +} \ No newline at end of file