cir) {
+ if (fogType == FogRenderer.FogType.WORLD && NoRender.INSTANCE.isEnabled() && NoRender.getNoTerrainFog())
+ cir.setReturnValue(emptyBuffer.slice(0L, FOG_UBO_SIZE));
+ }
+}
diff --git a/src/main/java/com/lambda/mixin/render/GameRendererMixin.java b/src/main/java/com/lambda/mixin/render/GameRendererMixin.java
index 42cb9ace5..98cf293e7 100644
--- a/src/main/java/com/lambda/mixin/render/GameRendererMixin.java
+++ b/src/main/java/com/lambda/mixin/render/GameRendererMixin.java
@@ -20,12 +20,14 @@
import com.lambda.event.EventFlow;
import com.lambda.event.events.RenderEvent;
import com.lambda.graphics.RenderMain;
+import com.lambda.gui.DearImGui;
import com.lambda.module.modules.render.NoRender;
import com.lambda.module.modules.render.Zoom;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
+import com.mojang.blaze3d.buffers.GpuBufferSlice;
import net.minecraft.client.render.Camera;
import net.minecraft.client.render.GameRenderer;
import net.minecraft.client.render.RenderTickCounter;
@@ -33,6 +35,7 @@
import net.minecraft.client.util.ObjectAllocator;
import net.minecraft.item.ItemStack;
import org.joml.Matrix4f;
+import org.joml.Vector4f;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@@ -47,21 +50,9 @@ private void updateTargetedEntityInvoke(float tickDelta, CallbackInfo info) {
}
}
- /**
- * Begins our 3d render after the game has rendered the world
- * {@code
- * float m = Math.max(h, (float)(Integer)this.client.options.getFov().getValue());
- * Matrix4f matrix4f2 = this.getBasicProjectionMatrix(m);
- * RenderSystem.setProjectionMatrix(matrix4f, ProjectionType.PERSPECTIVE);
- * Quaternionf quaternionf = camera.getRotation().conjugate(new Quaternionf());
- * Matrix4f matrix4f3 = (new Matrix4f()).rotation(quaternionf);
- * this.client.worldRenderer.setupFrustum(camera.getPos(), matrix4f3, matrix4f2);
- * this.client.worldRenderer.render(this.pool, renderTickCounter, bl, camera, this, matrix4f3, matrix4f);
- * }
- */
- @WrapOperation(method = "renderWorld", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/WorldRenderer;render(Lnet/minecraft/client/util/ObjectAllocator;Lnet/minecraft/client/render/RenderTickCounter;ZLnet/minecraft/client/render/Camera;Lnet/minecraft/client/render/GameRenderer;Lorg/joml/Matrix4f;Lorg/joml/Matrix4f;)V"))
- void onRenderWorld(WorldRenderer instance, ObjectAllocator allocator, RenderTickCounter tickCounter, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, Matrix4f positionMatrix, Matrix4f projectionMatrix, Operation original) {
- original.call(instance, allocator, tickCounter, renderBlockOutline, camera, gameRenderer, positionMatrix, projectionMatrix);
+ @WrapOperation(method = "renderWorld", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/WorldRenderer;render(Lnet/minecraft/client/util/ObjectAllocator;Lnet/minecraft/client/render/RenderTickCounter;ZLnet/minecraft/client/render/Camera;Lorg/joml/Matrix4f;Lorg/joml/Matrix4f;Lorg/joml/Matrix4f;Lcom/mojang/blaze3d/buffers/GpuBufferSlice;Lorg/joml/Vector4f;Z)V"))
+ void onRenderWorld(WorldRenderer instance, ObjectAllocator allocator, RenderTickCounter tickCounter, boolean renderBlockOutline, Camera camera, Matrix4f positionMatrix, Matrix4f basicProjectionMatrix, Matrix4f projectionMatrix, GpuBufferSlice fogBuffer, Vector4f fogColor, boolean renderSky, Operation original) {
+ original.call(instance, allocator, tickCounter, renderBlockOutline, camera, positionMatrix, basicProjectionMatrix, projectionMatrix, fogBuffer, fogColor, renderSky);
RenderMain.render3D(positionMatrix, projectionMatrix);
}
@@ -80,4 +71,9 @@ private void injectShowFloatingItem(ItemStack floatingItem, CallbackInfo ci) {
private float modifyGetFov(float original) {
return original / Zoom.getLerpedZoom();
}
+
+ @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/render/GuiRenderer;render(Lcom/mojang/blaze3d/buffers/GpuBufferSlice;)V", shift = At.Shift.AFTER))
+ private void onGuiRenderComplete(RenderTickCounter tickCounter, boolean tick, CallbackInfo ci) {
+ DearImGui.INSTANCE.render();
+ }
}
diff --git a/src/main/java/com/lambda/mixin/render/HandledScreenMixin.java b/src/main/java/com/lambda/mixin/render/HandledScreenMixin.java
index 193cdc516..3c66e9c53 100644
--- a/src/main/java/com/lambda/mixin/render/HandledScreenMixin.java
+++ b/src/main/java/com/lambda/mixin/render/HandledScreenMixin.java
@@ -18,6 +18,7 @@
package com.lambda.mixin.render;
import com.lambda.module.modules.render.ContainerPreview;
+import net.minecraft.client.gui.Click;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@@ -27,18 +28,18 @@
@Mixin(HandledScreen.class)
public class HandledScreenMixin {
@Inject(method = "mouseClicked", at = @At("HEAD"), cancellable = true)
- private void onMouseClicked(double mouseX, double mouseY, int button, CallbackInfoReturnable cir) {
+ private void onMouseClicked(Click click, boolean doubled, CallbackInfoReturnable cir) {
if (ContainerPreview.INSTANCE.isEnabled() && ContainerPreview.isLocked()) {
- if (ContainerPreview.isMouseOverLockedTooltip((int) mouseX, (int) mouseY)) {
+ if (ContainerPreview.isMouseOverLockedTooltip((int) click.x(), (int) click.y())) {
cir.setReturnValue(true);
}
}
}
@Inject(method = "mouseReleased", at = @At("HEAD"), cancellable = true)
- private void onMouseReleased(double mouseX, double mouseY, int button, CallbackInfoReturnable cir) {
+ private void onMouseReleased(Click click, CallbackInfoReturnable cir) {
if (ContainerPreview.INSTANCE.isEnabled() && ContainerPreview.isLocked()) {
- if (ContainerPreview.isMouseOverLockedTooltip((int) mouseX, (int) mouseY)) {
+ if (ContainerPreview.isMouseOverLockedTooltip((int) click.x(), (int) click.y())) {
cir.setReturnValue(true);
}
}
diff --git a/src/main/java/com/lambda/mixin/render/HeadFeatureRendererMixin.java b/src/main/java/com/lambda/mixin/render/HeadFeatureRendererMixin.java
index 38834b1ac..bb8416614 100644
--- a/src/main/java/com/lambda/mixin/render/HeadFeatureRendererMixin.java
+++ b/src/main/java/com/lambda/mixin/render/HeadFeatureRendererMixin.java
@@ -19,7 +19,9 @@
import com.lambda.module.modules.render.NoRender;
import net.minecraft.client.render.VertexConsumerProvider;
+import net.minecraft.client.render.command.OrderedRenderCommandQueue;
import net.minecraft.client.render.entity.feature.HeadFeatureRenderer;
+import net.minecraft.client.render.entity.state.EntityRenderState;
import net.minecraft.client.render.entity.state.LivingEntityRenderState;
import net.minecraft.client.util.math.MatrixStack;
import org.spongepowered.asm.mixin.Mixin;
@@ -29,8 +31,8 @@
@Mixin(HeadFeatureRenderer.class)
public class HeadFeatureRendererMixin {
- @Inject(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/LivingEntityRenderState;FF)V", at = @At("HEAD"), cancellable = true)
- private void injectRender(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, LivingEntityRenderState livingEntityRenderState, float f, float g, CallbackInfo ci) {
+ @Inject(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/command/OrderedRenderCommandQueue;ILnet/minecraft/client/render/entity/state/EntityRenderState;FF)V", at = @At("HEAD"), cancellable = true)
+ private void injectRender(MatrixStack matrices, OrderedRenderCommandQueue queue, int light, EntityRenderState state, float limbAngle, float limbDistance, CallbackInfo ci) {
if (NoRender.INSTANCE.isEnabled() && NoRender.getNoArmor() && NoRender.getIncludeNoOtherHeadItems()) ci.cancel();
}
}
diff --git a/src/main/java/com/lambda/mixin/render/HeldItemRendererMixin.java b/src/main/java/com/lambda/mixin/render/HeldItemRendererMixin.java
index 48d965c1a..0be8b99b8 100644
--- a/src/main/java/com/lambda/mixin/render/HeldItemRendererMixin.java
+++ b/src/main/java/com/lambda/mixin/render/HeldItemRendererMixin.java
@@ -20,9 +20,10 @@
import com.google.common.base.MoreObjects;
import com.lambda.Lambda;
import com.lambda.module.modules.render.ViewModel;
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.AbstractClientPlayerEntity;
-import net.minecraft.client.render.VertexConsumerProvider;
+import net.minecraft.client.render.command.OrderedRenderCommandQueue;
import net.minecraft.client.render.item.HeldItemRenderer;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.item.ItemStack;
@@ -33,7 +34,6 @@
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.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(HeldItemRenderer.class)
@@ -44,18 +44,14 @@ public class HeldItemRendererMixin {
@Shadow private float equipProgressMainHand;
@Shadow private float equipProgressOffHand;
- @Inject(method = "renderFirstPersonItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/item/HeldItemRenderer;renderArmHoldingItem(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IFFLnet/minecraft/util/Arm;)V"))
- private void onRenderArmHoldingItem(AbstractClientPlayerEntity player, float tickDelta, float pitch, Hand hand, float swingProgress, ItemStack itemStack, float equipProgress, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, CallbackInfo ci) {
- if (!ViewModel.INSTANCE.isEnabled()) return;
-
- ViewModel.INSTANCE.transform(itemStack, hand, matrices);
+ @Inject(method = "renderFirstPersonItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/item/HeldItemRenderer;renderItem(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/item/ItemStack;Lnet/minecraft/item/ItemDisplayContext;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/command/OrderedRenderCommandQueue;I)V"))
+ private void injectRenderFirstPersonItem(AbstractClientPlayerEntity player, float tickProgress, float pitch, Hand hand, float swingProgress, ItemStack item, float equipProgress, MatrixStack matrices, OrderedRenderCommandQueue orderedRenderCommandQueue, int light, CallbackInfo ci) {
+ if (ViewModel.INSTANCE.isEnabled()) ViewModel.INSTANCE.transform(item, hand, matrices);
}
- @Inject(method = "renderFirstPersonItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/item/HeldItemRenderer;renderItem(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/item/ItemStack;Lnet/minecraft/item/ItemDisplayContext;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V", ordinal = 1))
- private void onRenderFirstPersonItem(AbstractClientPlayerEntity player, float tickDelta, float pitch, Hand hand, float swingProgress, ItemStack itemStack, float equipProgress, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, CallbackInfo ci) {
- if (!ViewModel.INSTANCE.isEnabled()) return;
-
- ViewModel.INSTANCE.transform(itemStack, hand, matrices);
+ @Inject(method = "renderFirstPersonItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/item/HeldItemRenderer;renderArmHoldingItem(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/command/OrderedRenderCommandQueue;IFFLnet/minecraft/util/Arm;)V"))
+ private void injectRenderArmHoldingItem(AbstractClientPlayerEntity player, float tickProgress, float pitch, Hand hand, float swingProgress, ItemStack item, float equipProgress, MatrixStack matrices, OrderedRenderCommandQueue orderedRenderCommandQueue, int light, CallbackInfo ci) {
+ if (ViewModel.INSTANCE.isEnabled()) ViewModel.INSTANCE.transform(item, hand, matrices);
}
@ModifyArg(method = "updateHeldItems", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/math/MathHelper;clamp(FFF)F", ordinal = 2), index = 0)
@@ -68,7 +64,7 @@ private float modifyEquipProgressMainHand(float value) {
mainHand = currentStack;
}
- float progress = config.getOldAnimations() ? 1 : (float) Math.pow(client.player.getAttackCooldownProgress(1), 3);
+ float progress = config.getOldAnimations() ? 1 : (float) Math.pow(client.player.getHandEquippingProgress(1), 3);
return (ItemStack.areEqual(mainHand, currentStack) ? progress : 0) - equipProgressMainHand;
}
@@ -87,7 +83,7 @@ private float modifyEquipProgressOffHand(float value) {
return (ItemStack.areEqual(offHand, currentStack) ? 1 : 0) - equipProgressOffHand;
}
- @ModifyVariable(method = "renderItem(FLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider$Immediate;Lnet/minecraft/client/network/ClientPlayerEntity;I)V", at = @At(value = "STORE", ordinal = 0), index = 6)
+ @ModifyExpressionValue(method = "renderItem(FLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/command/OrderedRenderCommandQueue;Lnet/minecraft/client/network/ClientPlayerEntity;I)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;getHandSwingProgress(F)F"))
private float modifySwing(float swingProgress) {
ViewModel config = ViewModel.INSTANCE;
MinecraftClient mc = Lambda.getMc();
diff --git a/src/main/java/com/lambda/mixin/render/InGameHudMixin.java b/src/main/java/com/lambda/mixin/render/InGameHudMixin.java
index 407ef5998..d6ca4f2ca 100644
--- a/src/main/java/com/lambda/mixin/render/InGameHudMixin.java
+++ b/src/main/java/com/lambda/mixin/render/InGameHudMixin.java
@@ -35,13 +35,6 @@
@Mixin(InGameHud.class)
public class InGameHudMixin {
- /**
- * Begins our 2d render after the game has rendered all 2d elements
- */
- @Inject(method = "render", at = @At("TAIL"))
- private void onRender(DrawContext context, RenderTickCounter tickCounter, CallbackInfo ci) {
- DearImGui.INSTANCE.render();
- }
@Inject(method = "renderNauseaOverlay", at = @At("HEAD"), cancellable = true)
private void injectRenderNauseaOverlay(DrawContext context, float nauseaStrength, CallbackInfo ci) {
diff --git a/src/main/java/com/lambda/mixin/render/InGameOverlayRendererMixin.java b/src/main/java/com/lambda/mixin/render/InGameOverlayRendererMixin.java
index 49412089b..956abd82e 100644
--- a/src/main/java/com/lambda/mixin/render/InGameOverlayRendererMixin.java
+++ b/src/main/java/com/lambda/mixin/render/InGameOverlayRendererMixin.java
@@ -32,9 +32,9 @@
@Mixin(InGameOverlayRenderer.class)
public class InGameOverlayRendererMixin {
@WrapMethod(method = "renderFireOverlay")
- private static void wrapRenderFireOverlay(MatrixStack matrices, VertexConsumerProvider vertexConsumers, Operation original) {
+ private static void wrapRenderFireOverlay(MatrixStack matrices, VertexConsumerProvider vertexConsumers, Sprite sprite, Operation original) {
if (!(NoRender.INSTANCE.isEnabled() && NoRender.getNoFireOverlay())) {
- original.call(matrices, vertexConsumers);
+ original.call(matrices, vertexConsumers, sprite);
}
}
diff --git a/src/main/java/com/lambda/mixin/render/LightmapTextureManagerMixin.java b/src/main/java/com/lambda/mixin/render/LightmapTextureManagerMixin.java
index 849a9b297..48dec03ed 100644
--- a/src/main/java/com/lambda/mixin/render/LightmapTextureManagerMixin.java
+++ b/src/main/java/com/lambda/mixin/render/LightmapTextureManagerMixin.java
@@ -35,19 +35,25 @@
import java.util.OptionalInt;
+/**
+ * Mixin to override lightmap for Fullbright/XRay and disable darkness effect.
+ *
+ * Note: In 1.21.11, the lightmap rendering was rewritten to use RenderPass with shaders.
+ * We override the texture after normal rendering completes.
+ */
@Mixin(LightmapTextureManager.class)
public class LightmapTextureManagerMixin {
@Shadow @Final private GpuTexture glTexture;
@Inject(method = "update", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/profiler/Profiler;pop()V", shift = At.Shift.BEFORE))
- private void injectUpdate(CallbackInfo ci) {
+ private void injectUpdate(float tickProgress, CallbackInfo ci) {
if (Fullbright.INSTANCE.isEnabled() || XRay.INSTANCE.isEnabled()) {
- RenderSystem.getDevice().createCommandEncoder().createRenderPass(glTexture, OptionalInt.of(ColorHelper.getArgb(255, 255, 255, 255))).close();
+ RenderSystem.getDevice().createCommandEncoder().clearColorTexture(glTexture, ColorHelper.fullAlpha(ColorHelper.getWhite(1.0f)));
}
}
@ModifyReturnValue(method = "getDarkness", at = @At("RETURN"))
- private float modifyGetDarkness(float original) {
+ private float modifyGetDarkness(float original, LivingEntity entity, float factor, float tickProgress) {
if (NoRender.getNoDarkness() && NoRender.INSTANCE.isEnabled()) return 0.0f;
return original;
}
diff --git a/src/main/java/com/lambda/mixin/render/ParticleManagerMixin.java b/src/main/java/com/lambda/mixin/render/ParticleManagerMixin.java
deleted file mode 100644
index 654e32316..000000000
--- a/src/main/java/com/lambda/mixin/render/ParticleManagerMixin.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright 2025 Lambda
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package com.lambda.mixin.render;
-
-import com.lambda.module.modules.render.NoRender;
-import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
-import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
-import net.minecraft.client.particle.Particle;
-import net.minecraft.client.particle.ParticleManager;
-import net.minecraft.client.particle.ParticleTextureSheet;
-import net.minecraft.client.render.Camera;
-import net.minecraft.client.render.VertexConsumerProvider;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Unique;
-import org.spongepowered.asm.mixin.injection.At;
-
-import java.util.LinkedList;
-import java.util.Queue;
-import java.util.stream.Collectors;
-
-@Mixin(ParticleManager.class)
-public class ParticleManagerMixin {
- // prevents the particles from being stored and potential overhead. Downside being they need to spawn back in rather than just enabling rendering again
-// @Inject(method = "addParticle(Lnet/minecraft/client/particle/Particle;)V", at = @At("HEAD"), cancellable = true)
-// private void injectAddParticle(Particle particle, CallbackInfo ci) {
-// if (NoRender.shouldOmitParticle(particle)) ci.cancel();
-// }
-
- @WrapOperation(method = "renderParticles(Lnet/minecraft/client/render/Camera;FLnet/minecraft/client/render/VertexConsumerProvider$Immediate;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/ParticleManager;renderParticles(Lnet/minecraft/client/render/Camera;FLnet/minecraft/client/render/VertexConsumerProvider$Immediate;Lnet/minecraft/client/particle/ParticleTextureSheet;Ljava/util/Queue;)V"))
- private void wrapRenderParticles(Camera camera, float tickProgress, VertexConsumerProvider.Immediate vertexConsumers, ParticleTextureSheet sheet, Queue particles, Operation original) {
- original.call(camera, tickProgress, vertexConsumers, sheet, filterParticles(particles));
- }
-
- @WrapOperation(method = "renderParticles(Lnet/minecraft/client/render/Camera;FLnet/minecraft/client/render/VertexConsumerProvider$Immediate;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/ParticleManager;renderCustomParticles(Lnet/minecraft/client/render/Camera;FLnet/minecraft/client/render/VertexConsumerProvider$Immediate;Ljava/util/Queue;)V"))
- private void wrapRenderParticles(Camera camera, float tickProgress, VertexConsumerProvider.Immediate vertexConsumers, Queue particles, Operation original) {
- original.call(camera, tickProgress, vertexConsumers, filterParticles(particles));
- }
-
- @Unique
- private Queue filterParticles(Queue particles) {
- return particles.stream().filter(particle ->
- !NoRender.shouldOmitParticle(particle)).collect(Collectors.toCollection(LinkedList::new)
- );
- }
-}
diff --git a/src/main/java/com/lambda/mixin/render/RenderLayersMixin.java b/src/main/java/com/lambda/mixin/render/RenderLayersMixin.java
index 0b4d622dd..96e786e33 100644
--- a/src/main/java/com/lambda/mixin/render/RenderLayersMixin.java
+++ b/src/main/java/com/lambda/mixin/render/RenderLayersMixin.java
@@ -19,28 +19,34 @@
import com.lambda.module.modules.render.XRay;
import net.minecraft.block.BlockState;
-import net.minecraft.client.render.RenderLayer;
-import net.minecraft.client.render.RenderLayers;
+import net.minecraft.client.render.BlockRenderLayer;
+import net.minecraft.client.render.BlockRenderLayers;
import net.minecraft.fluid.FluidState;
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.callback.CallbackInfoReturnable;
-@Mixin(RenderLayers.class)
+/**
+ * Mixin to make blocks render as translucent for XRay functionality.
+ *
+ * Note: In 1.21.11, RenderLayers was split - BlockRenderLayers now handles
+ * block/fluid layer determination and returns BlockRenderLayer enum instead of RenderLayer.
+ */
+@Mixin(BlockRenderLayers.class)
public class RenderLayersMixin {
@Inject(method = "getBlockLayer", at = @At("HEAD"), cancellable = true)
- private static void injectGetBlockLayer(BlockState state, CallbackInfoReturnable cir) {
+ private static void injectGetBlockLayer(BlockState state, CallbackInfoReturnable cir) {
if (XRay.INSTANCE.isDisabled()) return;
final var opacity = XRay.getOpacity();
if (opacity <= 0 || opacity >= 100) return;
- if (!XRay.isSelected(state)) cir.setReturnValue(RenderLayer.getTranslucent());
+ if (!XRay.isSelected(state)) cir.setReturnValue(BlockRenderLayer.TRANSLUCENT);
}
@Inject(method = "getFluidLayer", at = @At("HEAD"), cancellable = true)
- private static void injectGetFluidLayer(FluidState state, CallbackInfoReturnable cir) {
+ private static void injectGetFluidLayer(FluidState state, CallbackInfoReturnable cir) {
if (XRay.INSTANCE.isDisabled()) return;
final var opacity = XRay.getOpacity();
- if (opacity > 0 && opacity < 100) cir.setReturnValue(RenderLayer.getTranslucent());
+ if (opacity > 0 && opacity < 100) cir.setReturnValue(BlockRenderLayer.TRANSLUCENT);
}
}
diff --git a/src/main/java/com/lambda/mixin/render/ScreenMixin.java b/src/main/java/com/lambda/mixin/render/ScreenMixin.java
index 9c4972653..6ed4f2c53 100644
--- a/src/main/java/com/lambda/mixin/render/ScreenMixin.java
+++ b/src/main/java/com/lambda/mixin/render/ScreenMixin.java
@@ -25,6 +25,7 @@
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.input.KeyInput;
import org.lwjgl.glfw.GLFW;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@@ -35,8 +36,8 @@
@Mixin(Screen.class)
public class ScreenMixin {
@Inject(method = "keyPressed", at = @At("HEAD"), cancellable = true)
- private void onKeyPressed(int keyCode, int scanCode, int modifiers, CallbackInfoReturnable cir) {
- if (keyCode == GLFW.GLFW_KEY_ESCAPE && QuickSearch.INSTANCE.isOpen()) {
+ private void onKeyPressed(KeyInput input, CallbackInfoReturnable cir) {
+ if (input.key() == GLFW.GLFW_KEY_ESCAPE && QuickSearch.INSTANCE.isOpen()) {
QuickSearch.INSTANCE.close();
cir.setReturnValue(true);
}
diff --git a/src/main/java/com/lambda/mixin/render/SodiumWorldRendererMixin.java b/src/main/java/com/lambda/mixin/render/SodiumWorldRendererMixin.java
new file mode 100644
index 000000000..892984062
--- /dev/null
+++ b/src/main/java/com/lambda/mixin/render/SodiumWorldRendererMixin.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.mixin.render;
+
+import com.lambda.module.modules.render.NoRender;
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import net.caffeinemc.mods.sodium.client.render.SodiumWorldRenderer;
+import net.caffeinemc.mods.sodium.client.util.FogParameters;
+import org.objectweb.asm.Opcodes;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Unique;
+import org.spongepowered.asm.mixin.injection.At;
+
+@Mixin(SodiumWorldRenderer.class)
+public class SodiumWorldRendererMixin {
+ @Unique
+ private final FogParameters NO_FOG = new FogParameters(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f);
+
+ @ModifyExpressionValue(method = "drawChunkLayer(Lnet/minecraft/client/render/BlockRenderLayerGroup;Lnet/caffeinemc/mods/sodium/client/render/chunk/ChunkRenderMatrices;DDDLnet/minecraft/client/gl/GpuSampler;)V", at = @At(value = "FIELD", target = "Lnet/caffeinemc/mods/sodium/client/render/SodiumWorldRenderer;lastFogParameters:Lnet/caffeinemc/mods/sodium/client/util/FogParameters;", opcode = Opcodes.GETFIELD))
+ private FogParameters modifyFogParameters(FogParameters original) {
+ if (NoRender.INSTANCE.isEnabled() && NoRender.getNoTerrainFog()) return NO_FOG;
+ return original;
+ }
+}
diff --git a/src/main/java/com/lambda/mixin/render/WorldBorderRenderingMixin.java b/src/main/java/com/lambda/mixin/render/WorldBorderRenderingMixin.java
index a5e6f9269..9553a39a5 100644
--- a/src/main/java/com/lambda/mixin/render/WorldBorderRenderingMixin.java
+++ b/src/main/java/com/lambda/mixin/render/WorldBorderRenderingMixin.java
@@ -19,6 +19,7 @@
import com.lambda.module.modules.render.NoRender;
import net.minecraft.client.render.WorldBorderRendering;
+import net.minecraft.client.render.state.WorldBorderRenderState;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.border.WorldBorder;
import org.spongepowered.asm.mixin.Mixin;
@@ -29,7 +30,7 @@
@Mixin(WorldBorderRendering.class)
public class WorldBorderRenderingMixin {
@Inject(method = "render", at = @At("HEAD"), cancellable = true)
- private void injectRender(WorldBorder border, Vec3d cameraPos, double viewDistanceBlocks, double farPlaneDistance, CallbackInfo ci) {
+ private void injectRender(WorldBorderRenderState state, Vec3d cameraPos, double viewDistanceBlocks, double farPlaneDistance, CallbackInfo ci) {
if (NoRender.INSTANCE.isEnabled() && NoRender.getNoWorldBorder()) ci.cancel();
}
}
diff --git a/src/main/java/com/lambda/mixin/render/WorldRendererMixin.java b/src/main/java/com/lambda/mixin/render/WorldRendererMixin.java
index d8eb33e08..313cb6ddd 100644
--- a/src/main/java/com/lambda/mixin/render/WorldRendererMixin.java
+++ b/src/main/java/com/lambda/mixin/render/WorldRendererMixin.java
@@ -34,23 +34,6 @@
@Mixin(WorldRenderer.class)
public class WorldRendererMixin {
- @ModifyExpressionValue(method = "getEntitiesToRender(Lnet/minecraft/client/render/Camera;Lnet/minecraft/client/render/Frustum;Ljava/util/List;)Z", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/Camera;isThirdPerson()Z"))
- private boolean renderIsThirdPerson(boolean original) {
- return Freecam.INSTANCE.isEnabled() || original;
- }
-
- @ModifyArg(method = "render(Lnet/minecraft/client/util/ObjectAllocator;Lnet/minecraft/client/render/RenderTickCounter;ZLnet/minecraft/client/render/Camera;Lnet/minecraft/client/render/GameRenderer;Lorg/joml/Matrix4f;Lorg/joml/Matrix4f;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/WorldRenderer;setupTerrain(Lnet/minecraft/client/render/Camera;Lnet/minecraft/client/render/Frustum;ZZ)V"), index = 3)
- private boolean renderSetupTerrainModifyArg(boolean hasForcedFrustum) {
- return Freecam.INSTANCE.isEnabled() || CameraTweaks.INSTANCE.isEnabled() || hasForcedFrustum;
- }
-
- @ModifyArg(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/BackgroundRenderer;applyFog(Lnet/minecraft/client/render/Camera;Lnet/minecraft/client/render/BackgroundRenderer$FogType;Lorg/joml/Vector4f;FZF)Lnet/minecraft/client/render/Fog;", ordinal = 0), index = 3)
- private float modifyApplyFogRenderDistance(float viewDistance) {
- return NoRender.INSTANCE.isEnabled() && NoRender.getNoTerrainFog()
- ? Float.MAX_VALUE
- : viewDistance;
- }
-
@Inject(method = "hasBlindnessOrDarkness(Lnet/minecraft/client/render/Camera;)Z", at = @At(value = "HEAD"), cancellable = true)
private void modifyEffectCheck(Camera camera, CallbackInfoReturnable cir) {
Entity entity = camera.getFocusedEntity();
@@ -60,4 +43,14 @@ private void modifyEffectCheck(Camera camera, CallbackInfoReturnable ci
cir.setReturnValue(blind || dark);
}
}
+
+ @ModifyArg(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/WorldRenderer;updateCamera(Lnet/minecraft/client/render/Camera;Lnet/minecraft/client/render/Frustum;Z)V"), index = 2)
+ private boolean renderSetupTerrainModifyArg(boolean spectator) {
+ return Freecam.INSTANCE.isEnabled() || CameraTweaks.INSTANCE.isEnabled() || spectator;
+ }
+
+ @ModifyExpressionValue(method = "fillEntityRenderStates", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/Camera;isThirdPerson()Z"))
+ private boolean modifyIsThirdPerson(boolean original) {
+ return Freecam.INSTANCE.isEnabled() || original;
+ }
}
diff --git a/src/main/java/com/lambda/mixin/render/AbstractSignBlockEntityRendererMixin.java b/src/main/java/com/lambda/mixin/render/blockentity/AbstractSignBlockEntityRendererMixin.java
similarity index 77%
rename from src/main/java/com/lambda/mixin/render/AbstractSignBlockEntityRendererMixin.java
rename to src/main/java/com/lambda/mixin/render/blockentity/AbstractSignBlockEntityRendererMixin.java
index 6f1d4c5c5..9d6232c03 100644
--- a/src/main/java/com/lambda/mixin/render/AbstractSignBlockEntityRendererMixin.java
+++ b/src/main/java/com/lambda/mixin/render/blockentity/AbstractSignBlockEntityRendererMixin.java
@@ -15,14 +15,13 @@
* along with this program. If not, see .
*/
-package com.lambda.mixin.render;
+package com.lambda.mixin.render.blockentity;
import com.lambda.module.modules.render.NoRender;
-import net.minecraft.block.entity.SignText;
-import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.block.entity.AbstractSignBlockEntityRenderer;
+import net.minecraft.client.render.block.entity.state.SignBlockEntityRenderState;
+import net.minecraft.client.render.command.OrderedRenderCommandQueue;
import net.minecraft.client.util.math.MatrixStack;
-import net.minecraft.util.math.BlockPos;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@@ -31,7 +30,7 @@
@Mixin(AbstractSignBlockEntityRenderer.class)
public class AbstractSignBlockEntityRendererMixin {
@Inject(method = "renderText", at = @At("HEAD"), cancellable = true)
- private void injectRenderText(BlockPos pos, SignText text, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int textLineHeight, int maxTextWidth, boolean front, CallbackInfo ci) {
+ private void injectRenderText(SignBlockEntityRenderState renderState, MatrixStack matrices, OrderedRenderCommandQueue queue, boolean front, CallbackInfo ci) {
if (NoRender.INSTANCE.isEnabled() && NoRender.getNoSignText()) ci.cancel();
}
}
diff --git a/src/main/java/com/lambda/mixin/render/BeaconBlockEntityRendererMixin.java b/src/main/java/com/lambda/mixin/render/blockentity/BeaconBlockEntityRendererMixin.java
similarity index 61%
rename from src/main/java/com/lambda/mixin/render/BeaconBlockEntityRendererMixin.java
rename to src/main/java/com/lambda/mixin/render/blockentity/BeaconBlockEntityRendererMixin.java
index a3d4b5e38..8ad9a6d83 100644
--- a/src/main/java/com/lambda/mixin/render/BeaconBlockEntityRendererMixin.java
+++ b/src/main/java/com/lambda/mixin/render/blockentity/BeaconBlockEntityRendererMixin.java
@@ -15,14 +15,14 @@
* along with this program. If not, see .
*/
-package com.lambda.mixin.render;
+package com.lambda.mixin.render.blockentity;
import com.lambda.module.modules.render.NoRender;
-import net.minecraft.block.entity.BlockEntity;
-import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.block.entity.BeaconBlockEntityRenderer;
+import net.minecraft.client.render.block.entity.state.BeaconBlockEntityRenderState;
+import net.minecraft.client.render.command.OrderedRenderCommandQueue;
+import net.minecraft.client.render.state.CameraRenderState;
import net.minecraft.client.util.math.MatrixStack;
-import net.minecraft.util.math.Vec3d;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@@ -30,8 +30,8 @@
@Mixin(BeaconBlockEntityRenderer.class)
public class BeaconBlockEntityRendererMixin {
- @Inject(method = "render", at = @At("HEAD"), cancellable = true)
- private void injectRender(BlockEntity entity, float tickProgress, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay, Vec3d cameraPos, CallbackInfo ci) {
+ @Inject(method = "render(Lnet/minecraft/client/render/block/entity/state/BeaconBlockEntityRenderState;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/command/OrderedRenderCommandQueue;Lnet/minecraft/client/render/state/CameraRenderState;)V", at = @At("HEAD"), cancellable = true)
+ private void injectRender(BeaconBlockEntityRenderState beaconBlockEntityRenderState, MatrixStack matrixStack, OrderedRenderCommandQueue orderedRenderCommandQueue, CameraRenderState cameraRenderState, CallbackInfo ci) {
if (NoRender.INSTANCE.isEnabled() && NoRender.getNoBeaconBeams()) ci.cancel();
}
}
diff --git a/src/main/java/com/lambda/mixin/render/BlockEntityRenderDispatcherMixin.java b/src/main/java/com/lambda/mixin/render/blockentity/BlockEntityRenderDispatcherMixin.java
similarity index 52%
rename from src/main/java/com/lambda/mixin/render/BlockEntityRenderDispatcherMixin.java
rename to src/main/java/com/lambda/mixin/render/blockentity/BlockEntityRenderDispatcherMixin.java
index 268b07043..784faeee0 100644
--- a/src/main/java/com/lambda/mixin/render/BlockEntityRenderDispatcherMixin.java
+++ b/src/main/java/com/lambda/mixin/render/blockentity/BlockEntityRenderDispatcherMixin.java
@@ -15,24 +15,33 @@
* along with this program. If not, see .
*/
-package com.lambda.mixin.render;
+package com.lambda.mixin.render.blockentity;
import com.lambda.module.modules.render.NoRender;
import net.minecraft.block.entity.BlockEntity;
-import net.minecraft.client.render.VertexConsumerProvider;
-import net.minecraft.client.render.block.entity.BlockEntityRenderDispatcher;
-import net.minecraft.client.render.block.entity.BlockEntityRenderer;
-import net.minecraft.client.util.math.MatrixStack;
-import net.minecraft.util.math.Vec3d;
+import net.minecraft.client.render.block.entity.BlockEntityRenderManager;
+import net.minecraft.client.render.block.entity.state.BlockEntityRenderState;
+import net.minecraft.client.render.command.ModelCommandRenderer;
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.callback.CallbackInfo;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+import org.jspecify.annotations.Nullable;
-@Mixin(BlockEntityRenderDispatcher.class)
+/**
+ * Mixin to disable block entity rendering when NoRender is enabled.
+ *
+ * Note: In 1.21.11, BlockEntityRenderDispatcher was renamed to BlockEntityRenderManager
+ * and uses a render state system. Returning null from getRenderState prevents rendering.
+ */
+@Mixin(BlockEntityRenderManager.class)
public class BlockEntityRenderDispatcherMixin {
- @Inject(method = "render(Lnet/minecraft/client/render/block/entity/BlockEntityRenderer;Lnet/minecraft/block/entity/BlockEntity;FLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;Lnet/minecraft/util/math/Vec3d;)V", at = @At("HEAD"), cancellable = true)
- private static void injectRender(BlockEntityRenderer renderer, BlockEntity blockEntity, float tickProgress, MatrixStack matrices, VertexConsumerProvider vertexConsumers, Vec3d cameraPos, CallbackInfo ci) {
- if (NoRender.shouldOmitBlockEntity(blockEntity)) ci.cancel();
+ @Inject(method = "getRenderState", at = @At("HEAD"), cancellable = true)
+ private void injectGetRenderState(
+ E blockEntity, float tickProgress, ModelCommandRenderer.@Nullable CrumblingOverlayCommand crumblingOverlay,
+ CallbackInfoReturnable cir) {
+ if (NoRender.shouldOmitBlockEntity(blockEntity)) {
+ cir.setReturnValue(null);
+ }
}
}
diff --git a/src/main/java/com/lambda/mixin/render/EnchantingTableBlockEntityRendererMixin.java b/src/main/java/com/lambda/mixin/render/blockentity/EnchantingTableBlockEntityRendererMixin.java
similarity index 51%
rename from src/main/java/com/lambda/mixin/render/EnchantingTableBlockEntityRendererMixin.java
rename to src/main/java/com/lambda/mixin/render/blockentity/EnchantingTableBlockEntityRendererMixin.java
index f80ca373d..f0e5b02dd 100644
--- a/src/main/java/com/lambda/mixin/render/EnchantingTableBlockEntityRendererMixin.java
+++ b/src/main/java/com/lambda/mixin/render/blockentity/EnchantingTableBlockEntityRendererMixin.java
@@ -15,21 +15,23 @@
* along with this program. If not, see .
*/
-package com.lambda.mixin.render;
+package com.lambda.mixin.render.blockentity;
import com.lambda.module.modules.render.NoRender;
-import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
-import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.block.entity.EnchantingTableBlockEntityRenderer;
-import net.minecraft.client.render.entity.model.BookModel;
+import net.minecraft.client.render.block.entity.state.EnchantingTableBlockEntityRenderState;
+import net.minecraft.client.render.command.OrderedRenderCommandQueue;
+import net.minecraft.client.render.state.CameraRenderState;
import net.minecraft.client.util.math.MatrixStack;
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.callback.CallbackInfo;
@Mixin(EnchantingTableBlockEntityRenderer.class)
public class EnchantingTableBlockEntityRendererMixin {
- @WrapWithCondition(method = "render(Lnet/minecraft/block/entity/EnchantingTableBlockEntity;FLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IILnet/minecraft/util/math/Vec3d;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/entity/model/BookModel;render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumer;II)V"))
- private boolean wrapRender(BookModel instance, MatrixStack matrixStack, VertexConsumer vertexConsumer, int i, int j) {
- return NoRender.INSTANCE.isDisabled() || !NoRender.getNoEnchantingTableBook();
+ @Inject(method = "render(Lnet/minecraft/client/render/block/entity/state/EnchantingTableBlockEntityRenderState;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/command/OrderedRenderCommandQueue;Lnet/minecraft/client/render/state/CameraRenderState;)V", at = @At("HEAD"), cancellable = true)
+ private void injectRender(EnchantingTableBlockEntityRenderState renderState, MatrixStack matrices, OrderedRenderCommandQueue queue, CameraRenderState cameraRenderState, CallbackInfo ci) {
+ if (NoRender.INSTANCE.isEnabled() && NoRender.getNoEnchantingTableBook()) ci.cancel();
}
}
diff --git a/src/main/java/com/lambda/mixin/render/MobSpawnerBlockEntityRendererMixin.java b/src/main/java/com/lambda/mixin/render/blockentity/MobSpawnerBlockEntityRendererMixin.java
similarity index 66%
rename from src/main/java/com/lambda/mixin/render/MobSpawnerBlockEntityRendererMixin.java
rename to src/main/java/com/lambda/mixin/render/blockentity/MobSpawnerBlockEntityRendererMixin.java
index e2233bf31..f47e9d2c0 100644
--- a/src/main/java/com/lambda/mixin/render/MobSpawnerBlockEntityRendererMixin.java
+++ b/src/main/java/com/lambda/mixin/render/blockentity/MobSpawnerBlockEntityRendererMixin.java
@@ -15,14 +15,15 @@
* along with this program. If not, see .
*/
-package com.lambda.mixin.render;
+package com.lambda.mixin.render.blockentity;
import com.lambda.module.modules.render.NoRender;
-import net.minecraft.block.entity.MobSpawnerBlockEntity;
-import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.block.entity.MobSpawnerBlockEntityRenderer;
+import net.minecraft.client.render.command.OrderedRenderCommandQueue;
+import net.minecraft.client.render.entity.EntityRenderManager;
+import net.minecraft.client.render.entity.state.EntityRenderState;
+import net.minecraft.client.render.state.CameraRenderState;
import net.minecraft.client.util.math.MatrixStack;
-import net.minecraft.util.math.Vec3d;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@@ -30,8 +31,8 @@
@Mixin(MobSpawnerBlockEntityRenderer.class)
public class MobSpawnerBlockEntityRendererMixin {
- @Inject(method = "render(Lnet/minecraft/block/entity/MobSpawnerBlockEntity;FLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IILnet/minecraft/util/math/Vec3d;)V", at = @At("HEAD"), cancellable = true)
- private void injectRender(MobSpawnerBlockEntity mobSpawnerBlockEntity, float f, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, int j, Vec3d vec3d, CallbackInfo ci) {
+ @Inject(method = "renderDisplayEntity", at = @At("HEAD"), cancellable = true)
+ private static void injectRender(MatrixStack matrices, OrderedRenderCommandQueue queue, EntityRenderState state, EntityRenderManager entityRenderDispatcher, float rotation, float scale, CameraRenderState cameraRenderState, CallbackInfo ci) {
if (NoRender.INSTANCE.isEnabled() && NoRender.getNoSpawnerMob()) ci.cancel();
}
}
diff --git a/src/main/java/com/lambda/mixin/render/particle/BillboardParticleMixin.java b/src/main/java/com/lambda/mixin/render/particle/BillboardParticleMixin.java
new file mode 100644
index 000000000..06faa1a5d
--- /dev/null
+++ b/src/main/java/com/lambda/mixin/render/particle/BillboardParticleMixin.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.mixin.render.particle;
+
+import com.lambda.module.modules.render.NoRender;
+import net.minecraft.client.particle.BillboardParticle;
+import net.minecraft.client.particle.BillboardParticleSubmittable;
+import net.minecraft.client.particle.Particle;
+import net.minecraft.client.render.Camera;
+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.callback.CallbackInfo;
+
+@Mixin(BillboardParticle.class)
+public class BillboardParticleMixin {
+ @Inject(method = "render(Lnet/minecraft/client/particle/BillboardParticleSubmittable;Lnet/minecraft/client/render/Camera;F)V", at = @At("HEAD"), cancellable = true)
+ private void injectRender(BillboardParticleSubmittable submittable, Camera camera, float tickDelta, CallbackInfo ci) {
+ if (NoRender.shouldOmitParticle((Particle) ((Object) this))) ci.cancel();
+ }
+}
diff --git a/src/main/java/com/lambda/mixin/render/particle/ElderGuardianParticleRendererMixin.java b/src/main/java/com/lambda/mixin/render/particle/ElderGuardianParticleRendererMixin.java
new file mode 100644
index 000000000..cc7b2e818
--- /dev/null
+++ b/src/main/java/com/lambda/mixin/render/particle/ElderGuardianParticleRendererMixin.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.mixin.render.particle;
+
+import com.lambda.module.modules.render.NoRender;
+import net.minecraft.client.particle.ElderGuardianParticle;
+import net.minecraft.client.particle.ElderGuardianParticleRenderer;
+import net.minecraft.client.particle.NoRenderParticleRenderer;
+import net.minecraft.client.render.Camera;
+import net.minecraft.client.render.Frustum;
+import net.minecraft.client.render.Submittable;
+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.callback.CallbackInfoReturnable;
+
+@Mixin(ElderGuardianParticleRenderer.class)
+public class ElderGuardianParticleRendererMixin {
+ @Inject(method = "render", at = @At("HEAD"), cancellable = true)
+ private void injectRender(Frustum frustum, Camera camera, float tickProgress, CallbackInfoReturnable cir) {
+ if (NoRender.shouldOmitParticle(ElderGuardianParticle.class)) cir.setReturnValue(NoRenderParticleRenderer.EMPTY);
+ }
+}
diff --git a/src/main/java/com/lambda/mixin/render/particle/ItemPickupParticleRendererMixin.java b/src/main/java/com/lambda/mixin/render/particle/ItemPickupParticleRendererMixin.java
new file mode 100644
index 000000000..509b3323c
--- /dev/null
+++ b/src/main/java/com/lambda/mixin/render/particle/ItemPickupParticleRendererMixin.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.mixin.render.particle;
+
+import com.lambda.module.modules.render.NoRender;
+import net.minecraft.client.particle.ItemPickupParticle;
+import net.minecraft.client.particle.ItemPickupParticleRenderer;
+import net.minecraft.client.particle.NoRenderParticleRenderer;
+import net.minecraft.client.render.Camera;
+import net.minecraft.client.render.Frustum;
+import net.minecraft.client.render.Submittable;
+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.callback.CallbackInfoReturnable;
+
+@Mixin(ItemPickupParticleRenderer.class)
+public class ItemPickupParticleRendererMixin {
+ @Inject(method = "render", at = @At("HEAD"), cancellable = true)
+ private void injectRender(Frustum frustum, Camera camera, float tickProgress, CallbackInfoReturnable cir) {
+ if (NoRender.shouldOmitParticle(ItemPickupParticle.class)) cir.setReturnValue(NoRenderParticleRenderer.EMPTY);
+ }
+}
diff --git a/src/main/java/com/lambda/mixin/world/ClientWorldMixin.java b/src/main/java/com/lambda/mixin/world/ClientWorldMixin.java
index d98e1efb3..5b86cf797 100644
--- a/src/main/java/com/lambda/mixin/world/ClientWorldMixin.java
+++ b/src/main/java/com/lambda/mixin/world/ClientWorldMixin.java
@@ -46,21 +46,21 @@ private void onRemoveEntity(int entityId, Entity.RemovalReason removalReason, Ca
EventFlow.post(new EntityEvent.Removal(entity, removalReason));
}
- @ModifyReturnValue(method = "getCloudsColor", at = @At("RETURN"))
- private int modifyGetCloudsColor(int original) {
- if (WorldColors.INSTANCE.isEnabled() && WorldColors.getCustomClouds()) {
- return WorldColors.getCloudColor().getRGB() & 0xFFFFFF;
- }
- return original;
- }
-
- @ModifyReturnValue(method = "getSkyColor", at = @At("RETURN"))
- private int modifyGetSkyColor(int original) {
- if (WorldColors.INSTANCE.isEnabled() && WorldColors.getCustomSky()) {
- return WorldColors.getSkyColor().getRGB() & 0xFFFFFF;
- }
- return original;
- }
+// @ModifyReturnValue(method = "getCloudsColor", at = @At("RETURN"))
+// private int modifyGetCloudsColor(int original) {
+// if (WorldColors.INSTANCE.isEnabled() && WorldColors.getCustomClouds()) {
+// return WorldColors.getCloudColor().getRGB() & 0xFFFFFF;
+// }
+// return original;
+// }
+//
+// @ModifyReturnValue(method = "getSkyColor", at = @At("RETURN"))
+// private int modifyGetSkyColor(int original) {
+// if (WorldColors.INSTANCE.isEnabled() && WorldColors.getCustomSky()) {
+// return WorldColors.getSkyColor().getRGB() & 0xFFFFFF;
+// }
+// return original;
+// }
@Inject(method = "handleBlockUpdate", at = @At("HEAD"), cancellable = true)
diff --git a/src/main/kotlin/com/lambda/Lambda.kt b/src/main/kotlin/com/lambda/Lambda.kt
index c8242977d..d90bbbaad 100644
--- a/src/main/kotlin/com/lambda/Lambda.kt
+++ b/src/main/kotlin/com/lambda/Lambda.kt
@@ -27,6 +27,7 @@ import com.lambda.config.serializer.ItemCodec
import com.lambda.config.serializer.ItemStackCodec
import com.lambda.config.serializer.KeyCodeCodec
import com.lambda.config.serializer.OptionalCodec
+import com.lambda.config.serializer.TextCodec
import com.lambda.core.Loader
import com.lambda.event.events.ClientEvent
import com.lambda.event.listener.UnsafeListener.Companion.listenOnceUnsafe
@@ -79,7 +80,7 @@ object Lambda : ClientModInitializer {
.registerTypeAdapter(GameProfile::class.java, GameProfileCodec)
.registerTypeAdapter(Optional::class.java, OptionalCodec)
.registerTypeAdapter(ItemStack::class.java, ItemStackCodec)
- .registerTypeAdapter(Text::class.java, Text.Serializer(DynamicRegistryManager.EMPTY))
+ .registerTypeAdapter(Text::class.java, TextCodec) // ToDo: Find out if needed
.registerTypeAdapter(Item::class.java, ItemCodec)
.registerTypeAdapter(BlockItem::class.java, ItemCodec)
.registerTypeAdapter(ArrowItem::class.java, ItemCodec)
diff --git a/src/main/kotlin/com/lambda/brigadier/argument/PlayerArguments.kt b/src/main/kotlin/com/lambda/brigadier/argument/PlayerArguments.kt
index 7c6ef926b..bdaae0d48 100644
--- a/src/main/kotlin/com/lambda/brigadier/argument/PlayerArguments.kt
+++ b/src/main/kotlin/com/lambda/brigadier/argument/PlayerArguments.kt
@@ -35,6 +35,7 @@ import net.minecraft.command.argument.EntityArgumentType
import net.minecraft.command.argument.GameProfileArgumentType
import net.minecraft.command.argument.TeamArgumentType
import net.minecraft.scoreboard.Team
+import net.minecraft.server.PlayerConfigEntry
import net.minecraft.server.command.ServerCommandSource
import net.minecraft.server.network.ServerPlayerEntity
@@ -69,7 +70,7 @@ fun ArgumentReader<
DefaultArgumentDescriptor<
GameProfileArgumentType
>
- >.value(): Collection {
+ >.value(): Collection {
return GameProfileArgumentType.getProfileArgument(context, name)
}
diff --git a/src/main/kotlin/com/lambda/command/CommandManager.kt b/src/main/kotlin/com/lambda/command/CommandManager.kt
index 2344c4259..960d83a92 100644
--- a/src/main/kotlin/com/lambda/command/CommandManager.kt
+++ b/src/main/kotlin/com/lambda/command/CommandManager.kt
@@ -73,7 +73,7 @@ object CommandManager {
canRead() && peek() == prefix
}
- fun currentDispatcher(message: String): CommandDispatcher {
+ fun currentDispatcher(message: String): CommandDispatcher {
return if (message.isLambdaCommand()) {
dispatcher
} else {
diff --git a/src/main/kotlin/com/lambda/command/commands/PrefixCommand.kt b/src/main/kotlin/com/lambda/command/commands/PrefixCommand.kt
new file mode 100644
index 000000000..57444d190
--- /dev/null
+++ b/src/main/kotlin/com/lambda/command/commands/PrefixCommand.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.command.commands
+
+import com.lambda.brigadier.CommandResult.Companion.failure
+import com.lambda.brigadier.CommandResult.Companion.success
+import com.lambda.brigadier.argument.greedyString
+import com.lambda.brigadier.argument.string
+import com.lambda.brigadier.argument.value
+import com.lambda.brigadier.execute
+import com.lambda.brigadier.executeWithResult
+import com.lambda.brigadier.required
+import com.lambda.command.CommandRegistry
+import com.lambda.command.LambdaCommand
+import com.lambda.config.Configuration
+import com.lambda.config.Setting
+import com.lambda.util.Communication.info
+import com.lambda.util.extension.CommandBuilder
+import com.lambda.util.text.buildText
+import com.lambda.util.text.literal
+
+object PrefixCommand : LambdaCommand(
+ "prefix",
+ usage = "prefix ",
+ description = "Sets the prefix for Lambda commands. If the prefix does not seem to work, try putting it in double quotes."
+) {
+ // i have no idea why someone would want to use some of these as a prefix
+ // but ig the people who run 20 clients at once could benefit from this
+ val ptrn = Regex("^[!\"#$%&'()*+,\\-./:;<=>?@\\[\\\\\\]^_`{|}~]$")
+
+ override fun CommandBuilder.create() {
+ required(greedyString("prefix")) { prefixStr ->
+ executeWithResult {
+ val prefix = prefixStr().value()
+ if (!ptrn.matches(prefix)) {
+ return@executeWithResult failure("Prefix must be a single non-alphanumeric ASCII character, excluding spaces.")
+ }
+ val prefixChar = prefix.first()
+ val configurable = Configuration.configurableByName("command") ?: return@executeWithResult failure("No command configurable found.")
+ val setting = configurable.settings.find { it.name == "prefix" } as? Setting<*, Char>
+ ?: return@executeWithResult failure("Prefix setting is not a Char or can not be found.")
+ setting.trySetValue(prefixChar)
+ return@executeWithResult success()
+ }
+ }
+
+ execute {
+ info(
+ buildText {
+ literal("The prefix is currently: ${CommandRegistry.prefix}")
+ }
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/config/AutomationConfig.kt b/src/main/kotlin/com/lambda/config/AutomationConfig.kt
index 56e986838..c32f0677f 100644
--- a/src/main/kotlin/com/lambda/config/AutomationConfig.kt
+++ b/src/main/kotlin/com/lambda/config/AutomationConfig.kt
@@ -85,9 +85,9 @@ open class AutomationConfig(
var drawables = listOf()
init {
- onStaticRender {
- if (renders)
- with(it) { drawables.forEach { with(it) { buildRenderer() } } }
+ onStaticRender { esp ->
+ if (renders)
+ drawables.forEach { it.render(esp) }
}
}
}
diff --git a/src/main/kotlin/com/lambda/config/ConfigEditor.kt b/src/main/kotlin/com/lambda/config/ConfigEditor.kt
index 4a9e168b9..2e74a71b1 100644
--- a/src/main/kotlin/com/lambda/config/ConfigEditor.kt
+++ b/src/main/kotlin/com/lambda/config/ConfigEditor.kt
@@ -38,11 +38,11 @@ open class SettingGroupEditor(open val c: T) {
throw IllegalStateException("Could not access delegate for property $name", e)
}
- fun KProperty0.setting() =
+ fun KProperty0.setting() =
this.delegate as? Setting, T>
?: throw IllegalStateException("Setting delegate did not match current value's type")
- fun KProperty0.settingCore() = setting().core
+ fun KProperty0.settingCore() = setting().core
@SettingEditorDsl
inline fun KProperty0.edit(edits: TypedEditBuilder.(SettingCore) -> Unit) {
@@ -60,14 +60,14 @@ open class SettingGroupEditor(open val c: T) {
fun edit(
vararg settings: KProperty0<*>,
edits: BasicEditBuilder.() -> Unit
- ) = BasicEditBuilder(this, settings.map { it.setting() }).apply(edits)
+ ) = BasicEditBuilder(this, settings.map { (it as KProperty0).setting() }).apply(edits)
@SettingEditorDsl
inline fun editWith(
vararg settings: KProperty0<*>,
other: KProperty0,
edits: BasicEditBuilder.(SettingCore) -> Unit
- ) = BasicEditBuilder(this, settings.map { it.setting() }).edits(other.settingCore())
+ ) = BasicEditBuilder(this, settings.map { (it as KProperty0).setting() }).edits(other.settingCore())
@SettingEditorDsl
inline fun editTyped(
@@ -89,7 +89,7 @@ open class SettingGroupEditor(open val c: T) {
@SettingEditorDsl
fun hide(vararg settings: KProperty0<*>) =
- hide(settings.map { it.setting() })
+ hide(settings.map { (it as KProperty0).setting() })
open class BasicEditBuilder(val c: SettingGroupEditor<*>, open val settings: Collection>) {
@SettingEditorDsl
@@ -123,8 +123,10 @@ class ConfigurableEditor(override val c: T) : SettingGroupEdit
fun hideGroup(settingGroup: ISettingGroup) = hide(settingGroup.settings)
@SettingEditorDsl
- fun hideGroupExcept(settingGroup: ISettingGroup, vararg except: KProperty0<*>) =
- hide(*((settingGroup.settings as List>) - except.toSet()).toTypedArray())
+ fun hideGroupExcept(settingGroup: ISettingGroup, vararg except: KProperty0<*>) {
+ val exceptSettings = except.map { (it as KProperty0).setting() }.toSet()
+ hide(settingGroup.settings.filter { it !in exceptSettings })
+ }
@SettingEditorDsl
fun hideGroups(vararg settingGroups: ISettingGroup) =
diff --git a/src/main/kotlin/com/lambda/config/Configurable.kt b/src/main/kotlin/com/lambda/config/Configurable.kt
index ba1cb14f7..e044162c8 100644
--- a/src/main/kotlin/com/lambda/config/Configurable.kt
+++ b/src/main/kotlin/com/lambda/config/Configurable.kt
@@ -36,7 +36,7 @@ import com.lambda.config.settings.complex.Bind
import com.lambda.config.settings.complex.BlockPosSetting
import com.lambda.config.settings.complex.BlockSetting
import com.lambda.config.settings.complex.ColorSetting
-import com.lambda.config.settings.complex.KeybindSettingCore
+import com.lambda.config.settings.complex.KeybindSetting
import com.lambda.config.settings.complex.Vec3dSetting
import com.lambda.config.settings.numeric.DoubleSetting
import com.lambda.config.settings.numeric.FloatSetting
@@ -151,33 +151,22 @@ abstract class Configurable(
) = Setting(name, description, ItemCollectionSetting(immutableCollection, defaultValue.toMutableList()), this, visibility).register()
@JvmName("collectionSetting3")
- inline fun > setting(
+ inline fun setting(
name: String,
defaultValue: Collection,
immutableList: Collection = defaultValue,
description: String = "",
+ displayClassName: Boolean = false,
noinline visibility: () -> Boolean = { true },
) = Setting(
name,
description,
- CollectionSetting(
- defaultValue.toMutableList(),
- immutableList,
- TypeToken.getParameterized(Collection::class.java, T::class.java).type
- ),
+ if (displayClassName) ClassCollectionSetting(immutableList, defaultValue.toMutableList())
+ else CollectionSetting(defaultValue.toMutableList(), immutableList, TypeToken.getParameterized(Collection::class.java, T::class.java).type),
this,
visibility
).register()
- @JvmName("collectionSetting4")
- inline fun setting(
- name: String,
- defaultValue: Collection,
- immutableList: Collection = defaultValue,
- description: String = "",
- noinline visibility: () -> Boolean = { true },
- ) = Setting(name, description, ClassCollectionSetting(immutableList, defaultValue.toMutableList()), this, visibility).register()
-
// ToDo: Actually implement maps
inline fun setting(
name: String,
@@ -240,14 +229,14 @@ abstract class Configurable(
defaultValue: Bind,
description: String = "",
visibility: () -> Boolean = { true },
- ) = Setting(name, description, KeybindSettingCore(defaultValue), this, visibility).register()
+ ) = Setting(name, description, KeybindSetting(defaultValue), this, visibility).register()
fun setting(
name: String,
defaultValue: KeyCode,
description: String = "",
visibility: () -> Boolean = { true },
- ) = Setting(name, description, KeybindSettingCore(defaultValue), this, visibility).register()
+ ) = Setting(name, description, KeybindSetting(defaultValue), this, visibility).register()
fun setting(
name: String,
diff --git a/src/main/kotlin/com/lambda/config/Setting.kt b/src/main/kotlin/com/lambda/config/Setting.kt
index 9f16d53c4..75f9ed688 100644
--- a/src/main/kotlin/com/lambda/config/Setting.kt
+++ b/src/main/kotlin/com/lambda/config/Setting.kt
@@ -94,7 +94,7 @@ import kotlin.reflect.KProperty
* @property type The type reflection of the setting.
* @property visibility A function that determines whether the setting is visible.
*/
-abstract class SettingCore(
+abstract class SettingCore(
var defaultValue: T,
val type: Type
) {
@@ -150,7 +150,7 @@ abstract class SettingCore(
}
}
-class Setting, R : Any>(
+class Setting, R>(
override val name: String,
override val description: String,
var core: T,
diff --git a/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt b/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt
index f7769bd3f..9ef2b2d41 100644
--- a/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt
+++ b/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt
@@ -57,7 +57,7 @@ open class BreakSettings(
override val breakDelay by c.setting("Break Delay", 0, 0..5, 1, "The delay between breaking blocks", " tick(s)").group(baseGroup, Group.General).index()
// Timing
- override val tickStageMask by c.setting("Break Stage Mask", setOf(TickEvent.Input.Post), ALL_STAGES.toSet(), description = "The sub-tick timing at which break actions can be performed").group(baseGroup, Group.General).index()
+ override val tickStageMask by c.setting("Break Stage Mask", setOf(TickEvent.Input.Post), ALL_STAGES.toSet(), "The sub-tick timing at which break actions can be performed", displayClassName = true).group(baseGroup, Group.General).index()
// Swap
override val swapMode by c.setting("Break Swap Mode", BreakConfig.SwapMode.End, "Decides when to swap to the best suited tool when breaking a block").group(baseGroup, Group.General).index()
diff --git a/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt b/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt
index 323161174..e4b2fab9d 100644
--- a/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt
+++ b/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt
@@ -23,7 +23,9 @@ import com.lambda.util.Describable
import com.lambda.util.NamedEnum
interface BuildConfig : ISettingGroup {
- // General
+ val breakBlocks: Boolean
+ val interactBlocks: Boolean
+
val pathing: Boolean
val stayInRange: Boolean
val collectDrops: Boolean
diff --git a/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt b/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt
index 1a7632bd7..0aa955dbb 100644
--- a/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt
+++ b/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt
@@ -33,7 +33,9 @@ class BuildSettings(
Scan("Scan")
}
- // General
+ override val breakBlocks by c.setting("Break", true, "Break blocks").group(baseGroup, Group.General).index()
+ override val interactBlocks by c.setting("Place / Interact", true, "Interact blocks").group(baseGroup, Group.General).index()
+
override val pathing by c.setting("Pathing", true, "Path to blocks").group(baseGroup, Group.General).index()
override val stayInRange by c.setting("Stay In Range", true, "Stay in range of blocks").group(baseGroup, Group.General).index()
override val collectDrops by c.setting("Collect All Drops", false, "Collect all drops when breaking blocks").group(baseGroup, Group.General).index()
diff --git a/src/main/kotlin/com/lambda/config/groups/FormatterSettings.kt b/src/main/kotlin/com/lambda/config/groups/FormatterSettings.kt
index 295c4db21..1d53a3d72 100644
--- a/src/main/kotlin/com/lambda/config/groups/FormatterSettings.kt
+++ b/src/main/kotlin/com/lambda/config/groups/FormatterSettings.kt
@@ -23,22 +23,22 @@ import com.lambda.util.NamedEnum
class FormatterSettings(
c: Configurable,
- baseGroup: NamedEnum,
+ vararg baseGroup: NamedEnum,
) : FormatterConfig, SettingGroup(c) {
- val localeEnum by c.setting("Locale", FormatterConfig.Locales.US, "The regional formatting used for numbers").group(baseGroup).index()
+ val localeEnum by c.setting("Locale", FormatterConfig.Locales.US, "The regional formatting used for numbers").group(*baseGroup).index()
override val locale get() = localeEnum.locale
- val sep by c.setting("Separator", FormatterConfig.TupleSeparator.Comma, "Separator for string serialization of tuple data structures").group(baseGroup).index()
- val customSep by c.setting("Custom Separator", "") { sep == FormatterConfig.TupleSeparator.Custom }.group(baseGroup).index()
+ val sep by c.setting("Separator", FormatterConfig.TupleSeparator.Comma, "Separator for string serialization of tuple data structures").group(*baseGroup).index()
+ val customSep by c.setting("Custom Separator", "") { sep == FormatterConfig.TupleSeparator.Custom }.group(*baseGroup).index()
override val separator get() = if (sep == FormatterConfig.TupleSeparator.Custom) customSep else sep.separator
- val group by c.setting("Tuple Prefix", FormatterConfig.TupleGrouping.Parentheses).group(baseGroup).index()
+ val group by c.setting("Tuple Prefix", FormatterConfig.TupleGrouping.Parentheses).group(*baseGroup).index()
override val prefix get() = group.prefix
override val postfix get() = group.postfix
- val floatingPrecision by c.setting("Floating Precision", 3, 0..6, 1, "Precision for floating point numbers").group(baseGroup).index()
+ val floatingPrecision by c.setting("Floating Precision", 3, 0..6, 1, "Precision for floating point numbers").group(*baseGroup).index()
override val precision get() = floatingPrecision
- val timeFormat by c.setting("Time Format", FormatterConfig.Time.IsoDateTime).group(baseGroup).index()
+ val timeFormat by c.setting("Time Format", FormatterConfig.Time.IsoDateTime).group(*baseGroup).index()
override val format get() = timeFormat.format
}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/config/groups/HotbarSettings.kt b/src/main/kotlin/com/lambda/config/groups/HotbarSettings.kt
index 618e6c10e..34baab4e8 100644
--- a/src/main/kotlin/com/lambda/config/groups/HotbarSettings.kt
+++ b/src/main/kotlin/com/lambda/config/groups/HotbarSettings.kt
@@ -33,5 +33,5 @@ class HotbarSettings(
override val swapDelay by c.setting("Swap Delay", 0, 0..3, 1, "The number of ticks delay before allowing another hotbar selection swap", " ticks").group(baseGroup).index()
override val swapsPerTick by c.setting("Swaps Per Tick", 3, 1..10, 1, "The number of hotbar selection swaps that can take place each tick") { swapDelay <= 0 }.group(baseGroup).index()
override val swapPause by c.setting("Swap Pause", 0, 0..20, 1, "The delay in ticks to pause actions after switching to the slot", " ticks").group(baseGroup).index()
- override val tickStageMask by c.setting("Hotbar Stage Mask", setOf(TickEvent.Input.Post), ALL_STAGES.toSet(), description = "The sub-tick timing at which hotbar actions are performed").group(baseGroup).index()
+ override val tickStageMask by c.setting("Hotbar Stage Mask", setOf(TickEvent.Input.Post), ALL_STAGES.toSet(), "The sub-tick timing at which hotbar actions are performed", displayClassName = true).group(baseGroup).index()
}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/config/groups/InteractSettings.kt b/src/main/kotlin/com/lambda/config/groups/InteractSettings.kt
index 9704a6d8b..90fc0b8be 100644
--- a/src/main/kotlin/com/lambda/config/groups/InteractSettings.kt
+++ b/src/main/kotlin/com/lambda/config/groups/InteractSettings.kt
@@ -34,7 +34,7 @@ class InteractSettings(
override val airPlace by c.setting("Air Place", AirPlaceMode.None, "Allows for placing blocks without adjacent faces").group(baseGroup).index()
override val axisRotateSetting by c.setting("Axis Rotate", true, "Overrides the Rotate For Place setting and rotates the player on each axis to air place rotational blocks") { airPlace.isEnabled }.group(baseGroup).index()
override val sorter by c.setting("Interaction Sorter", ActionConfig.SortMode.Tool, "The order in which placements are performed").group(baseGroup).index()
- override val tickStageMask by c.setting("Interaction Stage Mask", setOf(TickEvent.Input.Post), ALL_STAGES.toSet(), description = "The sub-tick timing at which place actions are performed").group(baseGroup).index()
+ override val tickStageMask by c.setting("Interaction Stage Mask", setOf(TickEvent.Input.Post), ALL_STAGES.toSet(), "The sub-tick timing at which place actions are performed", displayClassName = true).group(baseGroup).index()
override val interactConfirmationMode by c.setting("Interact Confirmation", InteractConfirmationMode.PlaceThenAwait, "Wait for block placement confirmation").group(baseGroup).index()
override val interactDelay by c.setting("Interact Delay", 0, 0..3, 1, "Tick delay between interacting with another block").group(baseGroup).index()
override val interactionsPerTick by c.setting("Interactions Per Tick", 1, 1..30, 1, "Maximum instant block places per tick").group(baseGroup).index()
diff --git a/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt b/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt
index 9d67ff369..33c56aec5 100644
--- a/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt
+++ b/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt
@@ -35,15 +35,15 @@ class InventorySettings(
}
override val actionsPerSecond by c.setting("Actions Per Second", 100, 0..100, 1, "How many inventory actions can be performed per tick").group(baseGroup, Group.General).index()
- override val tickStageMask by c.setting("Inventory Stage Mask", ALL_STAGES.toSet(), description = "The sub-tick timing at which inventory actions are performed").group(baseGroup, Group.General).index()
+ override val tickStageMask by c.setting("Inventory Stage Mask", ALL_STAGES.toSet(), description = "The sub-tick timing at which inventory actions are performed", displayClassName = true).group(baseGroup, Group.General).index()
override val disposables by c.setting("Disposables", ItemUtils.defaultDisposables, description = "Items that will be ignored when checking for a free slot").group(baseGroup, Group.Container).index()
override val swapWithDisposables by c.setting("Swap With Disposables", true, "Swap items with disposable ones").group(baseGroup, Group.Container).index()
override val providerPriority by c.setting("Provider Priority", InventoryConfig.Priority.WithMinItems, "What container to prefer when retrieving the item from").group(baseGroup, Group.Container).index()
override val storePriority by c.setting("Store Priority", InventoryConfig.Priority.WithMinItems, "What container to prefer when storing the item to").group(baseGroup, Group.Container).index()
override val immediateAccessOnly by c.setting("Immediate Access Only", false, "Only allow access to inventories that can be accessed immediately").group(baseGroup, Group.Access).index()
- override val accessShulkerBoxes by c.setting("Access Shulker Boxes", true, "Allow access to the player's shulker boxes").group(baseGroup, Group.Access).index()
- override val accessEnderChest by c.setting("Access Ender Chest", false, "Allow access to the player's ender chest").group(baseGroup, Group.Access).index()
- override val accessChests by c.setting("Access Chests", false, "Allow access to the player's normal chests").group(baseGroup, Group.Access).index()
- override val accessStashes by c.setting("Access Stashes", false, "Allow access to the player's stashes").group(baseGroup, Group.Access).index()
+ override val accessShulkerBoxes by c.setting("Access Shulker Boxes", true, "Allow access to the player's shulker boxes") { !immediateAccessOnly }.group(baseGroup, Group.Access).index()
+ override val accessEnderChest by c.setting("Access Ender Chest", false, "Allow access to the player's ender chest") { !immediateAccessOnly }.group(baseGroup, Group.Access).index()
+ override val accessChests by c.setting("Access Chests", false, "Allow access to the player's normal chests") { !immediateAccessOnly }.group(baseGroup, Group.Access).index()
+ override val accessStashes by c.setting("Access Stashes", false, "Allow access to the player's stashes") { !immediateAccessOnly }.group(baseGroup, Group.Access).index()
}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/config/groups/ReplaceConfig.kt b/src/main/kotlin/com/lambda/config/groups/ReplaceConfig.kt
new file mode 100644
index 000000000..f67407d17
--- /dev/null
+++ b/src/main/kotlin/com/lambda/config/groups/ReplaceConfig.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.config.groups
+
+import com.lambda.util.Describable
+
+interface ReplaceConfig {
+ val action: ActionStrategy
+ val replace: ReplaceStrategy
+
+ val enabled: Boolean get() = action != ActionStrategy.None
+
+ enum class ActionStrategy(override val description: String) : Describable {
+ Hide("Hides the message. Will override other strategies."),
+ Delete("Deletes the matching part off the message."),
+ Replace("Replace the matching string in the message with one of the following replace strategy."),
+ None("Don't do anything."),
+ }
+
+ enum class ReplaceStrategy(val block: (String) -> String) {
+ CensorAll({ it.replaceRange(0.. if (i % 2 == 0) acc + char else "$acc*" } }),
+ KeepFirst({ if (it.length <= 1) it else it.replaceRange(1, it.length, "*".repeat(it.length - 1)) }),
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/config/serializer/TextCodec.kt b/src/main/kotlin/com/lambda/config/serializer/TextCodec.kt
new file mode 100644
index 000000000..629e3ebb6
--- /dev/null
+++ b/src/main/kotlin/com/lambda/config/serializer/TextCodec.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.config.serializer
+
+import com.google.gson.JsonDeserializationContext
+import com.google.gson.JsonElement
+import com.google.gson.JsonSerializationContext
+import com.lambda.config.Codec
+import com.mojang.serialization.JsonOps
+import net.minecraft.text.Text
+import net.minecraft.text.TextCodecs
+import java.lang.reflect.Type
+import kotlin.jvm.optionals.getOrElse
+
+object TextCodec : Codec {
+ override fun serialize(
+ src: Text,
+ typeOfSrc: Type,
+ context: JsonSerializationContext,
+ ): JsonElement =
+ TextCodecs.CODEC.encodeStart(JsonOps.INSTANCE, src)
+ .orThrow
+
+ override fun deserialize(
+ json: JsonElement?,
+ typeOfT: Type?,
+ context: JsonDeserializationContext?,
+ ): Text =
+ TextCodecs.CODEC.parse(JsonOps.INSTANCE, json)
+ .result()
+ .getOrElse { Text.empty() }
+}
diff --git a/src/main/kotlin/com/lambda/config/settings/collections/CollectionSetting.kt b/src/main/kotlin/com/lambda/config/settings/collections/CollectionSetting.kt
index 812ccb90a..f8825d62b 100644
--- a/src/main/kotlin/com/lambda/config/settings/collections/CollectionSetting.kt
+++ b/src/main/kotlin/com/lambda/config/settings/collections/CollectionSetting.kt
@@ -17,6 +17,7 @@
package com.lambda.config.settings.collections
+import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.reflect.TypeToken
import com.lambda.Lambda.gson
@@ -106,7 +107,7 @@ open class CollectionSetting(
context(setting: Setting<*, MutableCollection>)
override fun toJson(): JsonElement =
- gson.toJsonTree(value.map { it.toString() })
+ gson.toJsonTree(value)
context(setting: Setting<*, MutableCollection>)
override fun loadFromJson(serialized: JsonElement) {
diff --git a/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt b/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt
index 1e8c11993..0d31ee129 100644
--- a/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt
+++ b/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt
@@ -48,7 +48,7 @@ import org.lwjgl.glfw.GLFW.GLFW_MOD_NUM_LOCK
import org.lwjgl.glfw.GLFW.GLFW_MOD_SHIFT
import org.lwjgl.glfw.GLFW.GLFW_MOD_SUPER
-class KeybindSettingCore(defaultValue: Bind) : SettingCore(
+class KeybindSetting(defaultValue: Bind) : SettingCore(
defaultValue,
TypeToken.get(Bind::class.java).type
) {
@@ -66,16 +66,18 @@ class KeybindSettingCore(defaultValue: Bind) : SettingCore(
if (listening) "Press any key…"
else bind.name
- if (listening) {
- withStyleColor(ImGuiCol.Button, 0.20f, 0.50f, 1.00f, 1.00f) {
- withStyleColor(ImGuiCol.ButtonHovered, 0.25f, 0.60f, 1.00f, 1.00f) {
- withStyleColor(ImGuiCol.ButtonActive, 0.20f, 0.50f, 0.95f, 1.00f) {
- button(preview)
+ withId("##Bind-${this@KeybindSetting.hashCode()}") {
+ if (listening) {
+ withStyleColor(ImGuiCol.Button, 0.20f, 0.50f, 1.00f, 1.00f) {
+ withStyleColor(ImGuiCol.ButtonHovered, 0.25f, 0.60f, 1.00f, 1.00f) {
+ withStyleColor(ImGuiCol.ButtonActive, 0.20f, 0.50f, 0.95f, 1.00f) {
+ button(preview)
+ }
}
}
+ } else {
+ button(preview) { listening = true }
}
- } else {
- button(preview) { listening = true }
}
lambdaTooltip {
@@ -88,9 +90,11 @@ class KeybindSettingCore(defaultValue: Bind) : SettingCore(
}
sameLine()
- smallButton("Unbind") {
- value = Bind.EMPTY
- listening = false
+ withId("##Unbind-${this@KeybindSetting.hashCode()}") {
+ smallButton("Unbind") {
+ value = Bind.EMPTY
+ listening = false
+ }
}
onItemHover(ImGuiHoveredFlags.Stationary) {
lambdaTooltip("Clear binding")
diff --git a/src/main/kotlin/com/lambda/event/events/ChatEvent.kt b/src/main/kotlin/com/lambda/event/events/ChatEvent.kt
new file mode 100644
index 000000000..aed5d57fe
--- /dev/null
+++ b/src/main/kotlin/com/lambda/event/events/ChatEvent.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.event.events
+
+import com.lambda.event.Event
+import com.lambda.event.callback.Cancellable
+import net.minecraft.client.gui.hud.MessageIndicator
+import net.minecraft.network.message.MessageSignatureData
+import net.minecraft.text.Text
+
+sealed class ChatEvent {
+ class Send(var message: String) : Event, Cancellable()
+
+ class Receive(
+ var message: Text,
+ var signature: MessageSignatureData?,
+ var indicator: MessageIndicator?,
+ ) : Event, Cancellable()
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/event/events/MouseEvent.kt b/src/main/kotlin/com/lambda/event/events/MouseEvent.kt
index 427969e39..3fcb9d3f7 100644
--- a/src/main/kotlin/com/lambda/event/events/MouseEvent.kt
+++ b/src/main/kotlin/com/lambda/event/events/MouseEvent.kt
@@ -21,6 +21,7 @@ import com.lambda.config.settings.complex.Bind
import com.lambda.event.callback.Cancellable
import com.lambda.event.callback.ICancellable
import com.lambda.util.math.Vec2d
+import net.minecraft.client.input.MouseInput
sealed class MouseEvent {
/**
diff --git a/src/main/kotlin/com/lambda/event/events/RenderEvent.kt b/src/main/kotlin/com/lambda/event/events/RenderEvent.kt
index 812979bfa..36e2dbe37 100644
--- a/src/main/kotlin/com/lambda/event/events/RenderEvent.kt
+++ b/src/main/kotlin/com/lambda/event/events/RenderEvent.kt
@@ -22,13 +22,14 @@ import com.lambda.event.Event
import com.lambda.event.callback.Cancellable
import com.lambda.event.callback.ICancellable
import com.lambda.event.listener.SafeListener.Companion.listen
-import com.lambda.graphics.renderer.esp.ShapeBuilder
-import com.lambda.graphics.renderer.esp.Treed
+import com.lambda.graphics.RenderMain
+import com.lambda.graphics.mc.TransientRegionESP
-fun Any.onStaticRender(block: SafeContext.(ShapeBuilder) -> Unit) =
- listen { block(ShapeBuilder(Treed.Static.faceBuilder, Treed.Static.edgeBuilder)) }
-fun Any.onDynamicRender(block: SafeContext.(ShapeBuilder) -> Unit) =
- listen { block(ShapeBuilder(Treed.Dynamic.faceBuilder, Treed.Dynamic.edgeBuilder)) }
+fun Any.onStaticRender(block: SafeContext.(TransientRegionESP) -> Unit) =
+ listen { block(RenderMain.StaticESP) }
+
+fun Any.onDynamicRender(block: SafeContext.(TransientRegionESP) -> Unit) =
+ listen { block(RenderMain.DynamicESP) }
sealed class RenderEvent {
object Upload : Event
diff --git a/src/main/kotlin/com/lambda/graphics/RenderMain.kt b/src/main/kotlin/com/lambda/graphics/RenderMain.kt
index 3668298f2..1353fec6e 100644
--- a/src/main/kotlin/com/lambda/graphics/RenderMain.kt
+++ b/src/main/kotlin/com/lambda/graphics/RenderMain.kt
@@ -22,55 +22,90 @@ import com.lambda.event.EventFlow.post
import com.lambda.event.events.RenderEvent
import com.lambda.event.events.TickEvent
import com.lambda.event.listener.SafeListener.Companion.listen
-import com.lambda.graphics.gl.GlStateUtils.setupGL
import com.lambda.graphics.gl.Matrices
import com.lambda.graphics.gl.Matrices.resetMatrices
-import com.lambda.graphics.renderer.esp.Treed
-import com.lambda.util.math.Vec2d
-import com.mojang.blaze3d.opengl.GlStateManager
-import com.mojang.blaze3d.systems.RenderSystem
-import net.minecraft.client.gl.GlBackend
-import net.minecraft.client.texture.GlTexture
+import com.lambda.graphics.mc.TransientRegionESP
+import net.minecraft.util.math.Vec3d
import org.joml.Matrix4f
-import org.lwjgl.opengl.GL30.GL_FRAMEBUFFER
+import org.joml.Vector2f
+import org.joml.Vector4f
object RenderMain {
+ @JvmStatic
+ val StaticESP = TransientRegionESP("Static")
+
+ @JvmStatic
+ val DynamicESP = TransientRegionESP("Dynamic")
+
val projectionMatrix = Matrix4f()
- val modelViewMatrix get() = Matrices.peek()
- val projModel: Matrix4f get() = Matrix4f(projectionMatrix).mul(modelViewMatrix)
+ val modelViewMatrix
+ get() = Matrices.peek()
+ val projModel: Matrix4f
+ get() = Matrix4f(projectionMatrix).mul(modelViewMatrix)
+
+ /**
+ * Project a world position to screen coordinates. Returns null if the position is behind the
+ * camera or off-screen.
+ *
+ * @param worldPos The world position to project
+ * @return Screen coordinates (x, y) in pixels, or null if not visible
+ */
+ fun worldToScreen(worldPos: Vec3d): Vector2f? {
+ val camera = mc.gameRenderer?.camera ?: return null
+ val cameraPos = camera.pos
+
+ // Camera-relative position
+ val relX = (worldPos.x - cameraPos.x).toFloat()
+ val relY = (worldPos.y - cameraPos.y).toFloat()
+ val relZ = (worldPos.z - cameraPos.z).toFloat()
+
+ // Apply projection * modelview matrix
+ val vec = Vector4f(relX, relY, relZ, 1f)
+ projModel.transform(vec)
+
+ // Behind camera check
+ if (vec.w <= 0) return null
- var screenSize = Vec2d.ZERO
+ // Perspective divide to get NDC
+ val ndcX = vec.x / vec.w
+ val ndcY = vec.y / vec.w
+ val ndcZ = vec.z / vec.w
+
+ // Off-screen check (NDC is -1 to 1)
+ if (ndcZ < -1 || ndcZ > 1) return null
+
+ // NDC to screen coordinates (Y is flipped in screen space)
+ val window = mc.window
+ val screenX = (ndcX + 1f) * 0.5f * window.framebufferWidth
+ val screenY = (1f - ndcY) * 0.5f * window.framebufferHeight
+
+ return Vector2f(screenX, screenY)
+ }
+
+ /** Check if a world position is visible on screen. */
+ fun isOnScreen(worldPos: Vec3d): Boolean = worldToScreen(worldPos) != null
@JvmStatic
fun render3D(positionMatrix: Matrix4f, projMatrix: Matrix4f) {
resetMatrices(positionMatrix)
projectionMatrix.set(projMatrix)
- setupGL {
- val framebuffer = mc.framebuffer
- val prevFramebuffer = (framebuffer.getColorAttachment() as GlTexture).getOrCreateFramebuffer(
- (RenderSystem.getDevice() as GlBackend).framebufferManager,
- null
- )
-
- GlStateManager._glBindFramebuffer(GL_FRAMEBUFFER, prevFramebuffer)
+ // Render transient ESPs using the new pipeline
+ StaticESP.render() // Uses internal depthTest flag (true)
+ DynamicESP.render() // Uses internal depthTest flag (false)
- Treed.Static.render()
- Treed.Dynamic.render()
-
- RenderEvent.Render.post()
- }
+ RenderEvent.Render.post()
}
init {
listen {
- Treed.Static.clear()
- Treed.Dynamic.clear()
+ StaticESP.clear()
+ DynamicESP.clear()
RenderEvent.Upload.post()
- Treed.Static.upload()
- Treed.Dynamic.upload()
+ StaticESP.upload()
+ DynamicESP.upload()
}
}
}
diff --git a/src/main/kotlin/com/lambda/graphics/renderer/gui/font/core/LambdaFont.kt b/src/main/kotlin/com/lambda/graphics/esp/EspDsl.kt
similarity index 64%
rename from src/main/kotlin/com/lambda/graphics/renderer/gui/font/core/LambdaFont.kt
rename to src/main/kotlin/com/lambda/graphics/esp/EspDsl.kt
index dd08deb2c..0c0a19a3f 100644
--- a/src/main/kotlin/com/lambda/graphics/renderer/gui/font/core/LambdaFont.kt
+++ b/src/main/kotlin/com/lambda/graphics/esp/EspDsl.kt
@@ -15,16 +15,18 @@
* along with this program. If not, see .
*/
-package com.lambda.graphics.renderer.gui.font.core
+package com.lambda.graphics.esp
-import com.lambda.graphics.renderer.gui.font.core.LambdaAtlas.buildBuffer
+import com.lambda.graphics.mc.ChunkedRegionESP
+import com.lambda.module.Module
-enum class LambdaFont(val fontName: String) {
- FiraSansRegular("FiraSans-Regular"),
- FiraSansBold("FiraSans-Bold");
+@DslMarker
+annotation class EspDsl
- fun load(): String {
- entries.forEach { it.buildBuffer() }
- return "Loaded ${entries.size} fonts"
- }
+fun Module.chunkedEsp(
+ name: String,
+ depthTest: Boolean = false,
+ update: ShapeScope.(net.minecraft.world.World, com.lambda.util.world.FastVector) -> Unit
+): ChunkedRegionESP {
+ return ChunkedRegionESP(this, name, depthTest, update)
}
diff --git a/src/main/kotlin/com/lambda/graphics/esp/RegionESP.kt b/src/main/kotlin/com/lambda/graphics/esp/RegionESP.kt
new file mode 100644
index 000000000..b32e03755
--- /dev/null
+++ b/src/main/kotlin/com/lambda/graphics/esp/RegionESP.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.graphics.esp
+
+import com.lambda.Lambda.mc
+import com.lambda.graphics.mc.LambdaRenderPipelines
+import com.lambda.graphics.mc.RegionRenderer
+import com.lambda.graphics.mc.RenderRegion
+import com.lambda.util.extension.tickDelta
+import com.mojang.blaze3d.systems.RenderSystem
+import java.util.concurrent.ConcurrentHashMap
+import kotlin.math.floor
+import org.joml.Matrix4f
+import org.joml.Vector3f
+import org.joml.Vector4f
+
+/**
+ * Base class for region-based ESP systems. Provides unified rendering logic and region management.
+ */
+abstract class RegionESP(val name: String, val depthTest: Boolean) {
+ protected val renderers = ConcurrentHashMap()
+
+ /** Get or create a ShapeScope for a specific world position. */
+ open fun shapes(x: Double, y: Double, z: Double, block: ShapeScope.() -> Unit) {}
+
+ /** Upload collected geometry to GPU. Must be called on main thread. */
+ open fun upload() {}
+
+ /** Clear all geometry data. */
+ abstract fun clear()
+
+ /** Close and release all GPU resources. */
+ open fun close() {
+ renderers.values.forEach { it.close() }
+ renderers.clear()
+ clear()
+ }
+
+ /**
+ * Render all active regions.
+ * @param tickDelta Progress within current tick (used for interpolation)
+ */
+ open fun render(tickDelta: Float = mc.tickDelta) {
+ val camera = mc.gameRenderer?.camera ?: return
+ val cameraPos = camera.pos
+
+ val activeRenderers = renderers.values.filter { it.hasData() }
+ if (activeRenderers.isEmpty()) return
+
+ val modelViewMatrix = com.lambda.graphics.RenderMain.modelViewMatrix
+ val transforms = activeRenderers.map { renderer ->
+ val offset = renderer.region.computeCameraRelativeOffset(cameraPos)
+ val modelView = Matrix4f(modelViewMatrix).translate(offset)
+
+ val dynamicTransform = RenderSystem.getDynamicUniforms()
+ .write(
+ modelView,
+ Vector4f(1f, 1f, 1f, 1f),
+ Vector3f(0f, 0f, 0f),
+ Matrix4f()
+ )
+ renderer to dynamicTransform
+ }
+
+ // Render Faces
+ RegionRenderer.createRenderPass("$name Faces")?.use { pass ->
+ val pipeline =
+ if (depthTest) LambdaRenderPipelines.ESP_QUADS
+ else LambdaRenderPipelines.ESP_QUADS_THROUGH
+ pass.setPipeline(pipeline)
+ RenderSystem.bindDefaultUniforms(pass)
+ transforms.forEach { (renderer, transform) ->
+ pass.setUniform("DynamicTransforms", transform)
+ renderer.renderFaces(pass)
+ }
+ }
+
+ // Render Edges
+ RegionRenderer.createRenderPass("$name Edges")?.use { pass ->
+ val pipeline =
+ if (depthTest) LambdaRenderPipelines.ESP_LINES
+ else LambdaRenderPipelines.ESP_LINES_THROUGH
+ pass.setPipeline(pipeline)
+ RenderSystem.bindDefaultUniforms(pass)
+ transforms.forEach { (renderer, transform) ->
+ pass.setUniform("DynamicTransforms", transform)
+ renderer.renderEdges(pass)
+ }
+ }
+ }
+
+ /**
+ * Compute a unique key for a region based on its coordinates. Prevents collisions between
+ * regions at different Y levels.
+ */
+ protected fun getRegionKey(x: Double, y: Double, z: Double): Long {
+ val rx = (RenderRegion.REGION_SIZE * floor(x / RenderRegion.REGION_SIZE)).toInt()
+ val ry = (RenderRegion.REGION_SIZE * floor(y / RenderRegion.REGION_SIZE)).toInt()
+ val rz = (RenderRegion.REGION_SIZE * floor(z / RenderRegion.REGION_SIZE)).toInt()
+
+ return getRegionKey(rx, ry, rz)
+ }
+
+ protected fun getRegionKey(rx: Int, ry: Int, rz: Int): Long {
+ // 20 bits for X, 20 bits for Z, 24 bits for Y (total 64)
+ // This supports +- 500k blocks in X/Z and full Y range
+ return (rx.toLong() and 0xFFFFF) or
+ ((rz.toLong() and 0xFFFFF) shl 20) or
+ ((ry.toLong() and 0xFFFFFF) shl 40)
+ }
+}
diff --git a/src/main/kotlin/com/lambda/graphics/esp/ShapeScope.kt b/src/main/kotlin/com/lambda/graphics/esp/ShapeScope.kt
new file mode 100644
index 000000000..14ab277f5
--- /dev/null
+++ b/src/main/kotlin/com/lambda/graphics/esp/ShapeScope.kt
@@ -0,0 +1,416 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.graphics.esp
+
+import com.lambda.graphics.mc.RegionShapeBuilder
+import com.lambda.graphics.mc.RegionVertexCollector
+import com.lambda.graphics.mc.RenderRegion
+import com.lambda.graphics.renderer.esp.DirectionMask
+import com.lambda.graphics.renderer.esp.DynamicAABB
+import net.minecraft.block.BlockState
+import net.minecraft.util.math.BlockPos
+import net.minecraft.util.math.Box
+import net.minecraft.util.math.MathHelper
+import net.minecraft.util.math.Vec3d
+import net.minecraft.util.shape.VoxelShape
+import java.awt.Color
+
+@EspDsl
+class ShapeScope(val region: RenderRegion, val collectShapes: Boolean = false) {
+ internal val builder = RegionShapeBuilder(region)
+ internal val shapes = if (collectShapes) mutableListOf() else null
+
+ /** Start building a box. */
+ fun box(box: Box, id: Any? = null, block: BoxScope.() -> Unit) {
+ val scope = BoxScope(box, this)
+ scope.apply(block)
+ if (collectShapes) {
+ shapes?.add(
+ EspShape.BoxShape(
+ id?.hashCode() ?: box.hashCode(),
+ box,
+ scope.filledColor,
+ scope.outlineColor,
+ scope.sides,
+ scope.outlineMode,
+ scope.thickness
+ )
+ )
+ }
+ }
+
+ /** Draw a line between two points. */
+ fun line(start: Vec3d, end: Vec3d, color: Color, width: Float = 1.0f, id: Any? = null) {
+ builder.line(start, end, color, width)
+ if (collectShapes) {
+ shapes?.add(
+ EspShape.LineShape(
+ id?.hashCode() ?: (start.hashCode() xor end.hashCode()),
+ start,
+ end,
+ color,
+ width
+ )
+ )
+ }
+ }
+
+ /** Draw a tracer. */
+ fun tracer(from: Vec3d, to: Vec3d, id: Any? = null, block: LineScope.() -> Unit = {}) {
+ val scope = LineScope(from, to, this)
+ scope.apply(block)
+ scope.draw()
+ if (collectShapes) {
+ shapes?.add(
+ EspShape.LineShape(
+ id?.hashCode() ?: (from.hashCode() xor to.hashCode()),
+ from,
+ to,
+ scope.lineColor,
+ scope.lineWidth,
+ scope.lineDashLength,
+ scope.lineGapLength
+ )
+ )
+ }
+ }
+
+ /** Draw a simple filled box. */
+ fun filled(box: Box, color: Color, sides: Int = DirectionMask.ALL) {
+ builder.filled(box, color, sides)
+ if (collectShapes) {
+ shapes?.add(EspShape.BoxShape(box.hashCode(), box, color, null, sides))
+ }
+ }
+
+ /** Draw a simple outlined box. */
+ fun outline(box: Box, color: Color, sides: Int = DirectionMask.ALL, thickness: Float = builder.lineWidth) {
+ builder.outline(box, color, sides, thickness = thickness)
+ if (collectShapes) {
+ shapes?.add(EspShape.BoxShape(box.hashCode(), box, null, color, sides, thickness = thickness))
+ }
+ }
+
+ fun filled(box: DynamicAABB, color: Color, sides: Int = DirectionMask.ALL) {
+ builder.filled(box, color, sides)
+ if (collectShapes) {
+ box.pair?.second?.let {
+ shapes?.add(EspShape.BoxShape(it.hashCode(), it, color, null, sides))
+ }
+ }
+ }
+
+ fun outline(box: DynamicAABB, color: Color, sides: Int = DirectionMask.ALL, thickness: Float = builder.lineWidth) {
+ builder.outline(box, color, sides, thickness = thickness)
+ if (collectShapes) {
+ box.pair?.second?.let {
+ shapes?.add(EspShape.BoxShape(it.hashCode(), it, null, color, sides, thickness = thickness))
+ }
+ }
+ }
+
+ fun filled(pos: BlockPos, color: Color, sides: Int = DirectionMask.ALL) {
+ builder.filled(pos, color, sides)
+ if (collectShapes) {
+ shapes?.add(EspShape.BoxShape(pos.hashCode(), Box(pos), color, null, sides))
+ }
+ }
+
+ fun outline(pos: BlockPos, color: Color, sides: Int = DirectionMask.ALL, thickness: Float = builder.lineWidth) {
+ builder.outline(pos, color, sides, thickness = thickness)
+ if (collectShapes) {
+ shapes?.add(EspShape.BoxShape(pos.hashCode(), Box(pos), null, color, sides, thickness = thickness))
+ }
+ }
+
+ fun filled(pos: BlockPos, state: BlockState, color: Color, sides: Int = DirectionMask.ALL) {
+ builder.filled(pos, state, color, sides)
+ if (collectShapes) {
+ shapes?.add(EspShape.BoxShape(pos.hashCode(), Box(pos), color, null, sides))
+ }
+ }
+
+ fun outline(pos: BlockPos, state: BlockState, color: Color, sides: Int = DirectionMask.ALL, thickness: Float = builder.lineWidth) {
+ builder.outline(pos, state, color, sides, thickness = thickness)
+ if (collectShapes) {
+ shapes?.add(EspShape.BoxShape(pos.hashCode(), Box(pos), null, color, sides, thickness = thickness))
+ }
+ }
+
+ fun filled(shape: VoxelShape, color: Color, sides: Int = DirectionMask.ALL) {
+ builder.filled(shape, color, sides)
+ if (collectShapes) {
+ shape.boundingBoxes.forEach {
+ shapes?.add(EspShape.BoxShape(it.hashCode(), it, color, null, sides))
+ }
+ }
+ }
+
+ fun outline(shape: VoxelShape, color: Color, sides: Int = DirectionMask.ALL, thickness: Float = builder.lineWidth) {
+ builder.outline(shape, color, sides, thickness = thickness)
+ if (collectShapes) {
+ shape.boundingBoxes.forEach {
+ shapes?.add(EspShape.BoxShape(it.hashCode(), it, null, color, sides, thickness = thickness))
+ }
+ }
+ }
+
+ fun box(
+ pos: BlockPos,
+ state: BlockState,
+ filled: Color,
+ outline: Color,
+ sides: Int = DirectionMask.ALL,
+ mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And,
+ thickness: Float = builder.lineWidth
+ ) {
+ builder.box(pos, state, filled, outline, sides, mode, thickness = thickness)
+ if (collectShapes) {
+ shapes?.add(EspShape.BoxShape(pos.hashCode(), Box(pos), filled, outline, sides, mode, thickness = thickness))
+ }
+ }
+
+ fun box(
+ pos: BlockPos,
+ filled: Color,
+ outline: Color,
+ sides: Int = DirectionMask.ALL,
+ mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And,
+ thickness: Float = builder.lineWidth
+ ) {
+ builder.box(pos, filled, outline, sides, mode, thickness = thickness)
+ if (collectShapes) {
+ shapes?.add(EspShape.BoxShape(pos.hashCode(), Box(pos), filled, outline, sides, mode, thickness = thickness))
+ }
+ }
+
+ fun box(
+ box: Box,
+ filledColor: Color,
+ outlineColor: Color,
+ sides: Int = DirectionMask.ALL,
+ mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And,
+ thickness: Float = builder.lineWidth
+ ) {
+ builder.box(box, filledColor, outlineColor, sides, mode, thickness = thickness)
+ if (collectShapes) {
+ shapes?.add(EspShape.BoxShape(box.hashCode(), box, filledColor, outlineColor, sides, mode, thickness = thickness))
+ }
+ }
+
+ fun box(
+ box: DynamicAABB,
+ filledColor: Color,
+ outlineColor: Color,
+ sides: Int = DirectionMask.ALL,
+ mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And,
+ thickness: Float = builder.lineWidth
+ ) {
+ builder.box(box, filledColor, outlineColor, sides, mode, thickness = thickness)
+ if (collectShapes) {
+ box.pair?.second?.let {
+ shapes?.add(
+ EspShape.BoxShape(it.hashCode(), it, filledColor, outlineColor, sides, mode, thickness = thickness)
+ )
+ }
+ }
+ }
+
+ fun box(
+ entity: net.minecraft.block.entity.BlockEntity,
+ filled: Color,
+ outline: Color,
+ sides: Int = DirectionMask.ALL,
+ mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And,
+ thickness: Float = builder.lineWidth
+ ) {
+ builder.box(entity, filled, outline, sides, mode, thickness = thickness)
+ if (collectShapes) {
+ shapes?.add(
+ EspShape.BoxShape(
+ entity.pos.hashCode(),
+ Box(entity.pos),
+ filled,
+ outline,
+ sides,
+ mode,
+ thickness = thickness
+ )
+ )
+ }
+ }
+
+ fun box(
+ entity: net.minecraft.entity.Entity,
+ filled: Color,
+ outline: Color,
+ sides: Int = DirectionMask.ALL,
+ mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And,
+ thickness: Float = builder.lineWidth
+ ) {
+ builder.box(entity, filled, outline, sides, mode, thickness = thickness)
+ if (collectShapes) {
+ shapes?.add(
+ EspShape.BoxShape(
+ entity.hashCode(),
+ entity.boundingBox,
+ filled,
+ outline,
+ sides,
+ mode,
+ thickness = thickness
+ )
+ )
+ }
+ }
+}
+
+@EspDsl
+class BoxScope(val box: Box, val parent: ShapeScope) {
+ internal var filledColor: Color? = null
+ internal var outlineColor: Color? = null
+ internal var sides: Int = DirectionMask.ALL
+ internal var outlineMode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And
+ internal var thickness: Float = parent.builder.lineWidth
+
+ fun filled(color: Color, sides: Int = DirectionMask.ALL) {
+ this.filledColor = color
+ this.sides = sides
+ parent.builder.filled(box, color, sides)
+ }
+
+ fun outline(
+ color: Color,
+ sides: Int = DirectionMask.ALL,
+ mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And,
+ thickness: Float = parent.builder.lineWidth
+ ) {
+ this.outlineColor = color
+ this.sides = sides
+ this.outlineMode = mode
+ this.thickness = thickness
+ parent.builder.outline(box, color, sides, mode, thickness = thickness)
+ }
+}
+
+@EspDsl
+class LineScope(val from: Vec3d, val to: Vec3d, val parent: ShapeScope) {
+ internal var lineColor: Color = Color.WHITE
+ internal var lineWidth: Float = 1.0f
+ internal var lineDashLength: Double? = null
+ internal var lineGapLength: Double? = null
+
+ fun color(color: Color) {
+ this.lineColor = color
+ }
+
+ fun width(width: Float) {
+ this.lineWidth = width
+ }
+
+ fun dashed(dashLength: Double = 0.5, gapLength: Double = 0.25) {
+ this.lineDashLength = dashLength
+ this.lineGapLength = gapLength
+ }
+
+ internal fun draw() {
+ val dLen = lineDashLength
+ val gLen = lineGapLength
+
+ if (dLen != null && gLen != null) {
+ parent.builder.dashedLine(from, to, lineColor, dLen, gLen, lineWidth)
+ } else {
+ parent.builder.line(from, to, lineColor, lineWidth)
+ }
+ }
+}
+
+sealed class EspShape(val id: Int) {
+ abstract fun renderInterpolated(
+ prev: EspShape,
+ tickDelta: Float,
+ collector: RegionVertexCollector,
+ region: RenderRegion
+ )
+
+ class BoxShape(
+ id: Int,
+ val box: Box,
+ val filledColor: Color?,
+ val outlineColor: Color?,
+ val sides: Int = DirectionMask.ALL,
+ val outlineMode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And,
+ val thickness: Float = 1.0f
+ ) : EspShape(id) {
+ override fun renderInterpolated(
+ prev: EspShape,
+ tickDelta: Float,
+ collector: RegionVertexCollector,
+ region: RenderRegion
+ ) {
+ val interpBox =
+ if (prev is BoxShape) {
+ Box(
+ MathHelper.lerp(tickDelta.toDouble(), prev.box.minX, box.minX),
+ MathHelper.lerp(tickDelta.toDouble(), prev.box.minY, box.minY),
+ MathHelper.lerp(tickDelta.toDouble(), prev.box.minZ, box.minZ),
+ MathHelper.lerp(tickDelta.toDouble(), prev.box.maxX, box.maxX),
+ MathHelper.lerp(tickDelta.toDouble(), prev.box.maxY, box.maxY),
+ MathHelper.lerp(tickDelta.toDouble(), prev.box.maxZ, box.maxZ)
+ )
+ } else box
+
+ val shapeBuilder = RegionShapeBuilder(region)
+ filledColor?.let { shapeBuilder.filled(interpBox, it, sides) }
+ outlineColor?.let { shapeBuilder.outline(interpBox, it, sides, outlineMode, thickness = thickness) }
+
+ collector.faceVertices.addAll(shapeBuilder.collector.faceVertices)
+ collector.edgeVertices.addAll(shapeBuilder.collector.edgeVertices)
+ }
+ }
+
+ class LineShape(
+ id: Int,
+ val from: Vec3d,
+ val to: Vec3d,
+ val color: Color,
+ val width: Float,
+ val dashLength: Double? = null,
+ val gapLength: Double? = null
+ ) : EspShape(id) {
+ override fun renderInterpolated(
+ prev: EspShape,
+ tickDelta: Float,
+ collector: RegionVertexCollector,
+ region: RenderRegion
+ ) {
+ val iFrom = if (prev is LineShape) prev.from.lerp(from, tickDelta.toDouble()) else from
+ val iTo = if (prev is LineShape) prev.to.lerp(to, tickDelta.toDouble()) else to
+
+ val shapeBuilder = RegionShapeBuilder(region)
+ if (dashLength != null && gapLength != null) {
+ shapeBuilder.dashedLine(iFrom, iTo, color, dashLength, gapLength, width)
+ } else {
+ shapeBuilder.line(iFrom, iTo, color, width)
+ }
+
+ collector.faceVertices.addAll(shapeBuilder.collector.faceVertices)
+ collector.edgeVertices.addAll(shapeBuilder.collector.edgeVertices)
+ }
+ }
+}
diff --git a/src/main/kotlin/com/lambda/graphics/gl/GlStateUtils.kt b/src/main/kotlin/com/lambda/graphics/gl/GlStateUtils.kt
index 77b830aca..c8e57ef5f 100644
--- a/src/main/kotlin/com/lambda/graphics/gl/GlStateUtils.kt
+++ b/src/main/kotlin/com/lambda/graphics/gl/GlStateUtils.kt
@@ -30,6 +30,7 @@ import org.lwjgl.opengl.GL30C.glDisable
import org.lwjgl.opengl.GL30C.glEnable
import org.lwjgl.opengl.GL30C.glLineWidth
+// ToDo: Migrate particle system so we can remove this
object GlStateUtils {
private var depthTestState = true
private var blendState = false
diff --git a/src/main/kotlin/com/lambda/graphics/mc/ChunkedRegionESP.kt b/src/main/kotlin/com/lambda/graphics/mc/ChunkedRegionESP.kt
new file mode 100644
index 000000000..d367694a5
--- /dev/null
+++ b/src/main/kotlin/com/lambda/graphics/mc/ChunkedRegionESP.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.graphics.mc
+
+import com.lambda.event.events.RenderEvent
+import com.lambda.event.events.TickEvent
+import com.lambda.event.events.WorldEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.event.listener.SafeListener.Companion.listenConcurrently
+import com.lambda.graphics.esp.RegionESP
+import com.lambda.graphics.esp.ShapeScope
+import com.lambda.module.Module
+import com.lambda.module.modules.client.StyleEditor
+import com.lambda.threading.runSafe
+import com.lambda.util.world.FastVector
+import com.lambda.util.world.fastVectorOf
+import net.minecraft.world.World
+import net.minecraft.world.chunk.WorldChunk
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.ConcurrentLinkedDeque
+
+/**
+ * Region-based chunked ESP system using MC 1.21.11's new render pipeline.
+ *
+ * This system:
+ * - Uses region-relative coordinates for precision-safe rendering
+ * - Maintains per-chunk geometry for efficient updates
+ *
+ * @param owner The module that owns this ESP system
+ * @param name The name of the ESP system
+ * @param depthTest Whether to use depth testing
+ * @param update The update function called for each block position
+ */
+class ChunkedRegionESP(
+ owner: Module,
+ name: String,
+ depthTest: Boolean = false,
+ private val update: ShapeScope.(World, FastVector) -> Unit
+) : RegionESP(name, depthTest) {
+ private val chunkMap = ConcurrentHashMap()
+
+ private val WorldChunk.regionChunk
+ get() = chunkMap.getOrPut(getRegionKey(pos.x shl 4, bottomY, pos.z shl 4)) {
+ RegionChunk(this)
+ }
+
+ private val uploadQueue = ConcurrentLinkedDeque<() -> Unit>()
+ private val rebuildQueue = ConcurrentLinkedDeque()
+
+ /** Mark all tracked chunks for rebuild. */
+ fun rebuild() {
+ rebuildQueue.clear()
+ rebuildQueue.addAll(chunkMap.values)
+ }
+
+ /**
+ * Load all currently loaded world chunks and mark them for rebuild. Call this when the module
+ * is enabled to populate initial chunks.
+ */
+ fun rebuildAll() {
+ runSafe {
+ val chunksArray = world.chunkManager.chunks.chunks
+ (0 until chunksArray.length()).forEach { i ->
+ chunksArray.get(i)?.regionChunk?.markDirty()
+ }
+ }
+ }
+
+ override fun clear() {
+ chunkMap.values.forEach { it.close() }
+ chunkMap.clear()
+ rebuildQueue.clear()
+ uploadQueue.clear()
+ }
+
+ init {
+ owner.listen { event ->
+ val pos = event.pos
+ world.getWorldChunk(pos)?.regionChunk?.markDirty()
+
+ val xInChunk = pos.x and 15
+ val zInChunk = pos.z and 15
+
+ if (xInChunk == 0) world.getWorldChunk(pos.west())?.regionChunk?.markDirty()
+ if (xInChunk == 15) world.getWorldChunk(pos.east())?.regionChunk?.markDirty()
+ if (zInChunk == 0) world.getWorldChunk(pos.north())?.regionChunk?.markDirty()
+ if (zInChunk == 15) world.getWorldChunk(pos.south())?.regionChunk?.markDirty()
+ }
+
+ owner.listen { event -> event.chunk.regionChunk.markDirty() }
+
+ owner.listen {
+ val pos = getRegionKey(it.chunk.pos.x shl 4, it.chunk.bottomY, it.chunk.pos.z shl 4)
+ chunkMap.remove(pos)?.close()
+ }
+
+ owner.listenConcurrently {
+ val queueSize = rebuildQueue.size
+ val polls = minOf(StyleEditor.rebuildsPerTick, queueSize)
+ repeat(polls) { rebuildQueue.poll()?.rebuild() }
+ }
+
+ owner.listen {
+ val polls = minOf(StyleEditor.uploadsPerTick, uploadQueue.size)
+ repeat(polls) { uploadQueue.poll()?.invoke() }
+ }
+
+ owner.listen { render() }
+ }
+
+ /** Per-chunk rendering data. */
+ private inner class RegionChunk(val chunk: WorldChunk) {
+ val region = RenderRegion.forChunk(chunk.pos.x, chunk.pos.z, chunk.bottomY)
+ private val key = getRegionKey(chunk.pos.x shl 4, chunk.bottomY, chunk.pos.z shl 4)
+
+ private var isDirty = false
+
+ fun markDirty() {
+ isDirty = true
+ if (!rebuildQueue.contains(this)) {
+ rebuildQueue.add(this)
+ }
+ }
+
+ fun rebuild() {
+ if (!isDirty) return
+ val scope = ShapeScope(region)
+
+ for (x in chunk.pos.startX..chunk.pos.endX) {
+ for (z in chunk.pos.startZ..chunk.pos.endZ) {
+ for (y in chunk.bottomY..chunk.height) {
+ update(scope, chunk.world, fastVectorOf(x, y, z))
+ }
+ }
+ }
+
+ uploadQueue.add {
+ val renderer = renderers.getOrPut(key) { RegionRenderer(region) }
+ renderer.upload(scope.builder.collector)
+ isDirty = false
+ }
+ }
+
+ fun close() {
+ renderers.remove(key)?.close()
+ }
+ }
+}
diff --git a/src/main/kotlin/com/lambda/graphics/mc/CurveUtils.kt b/src/main/kotlin/com/lambda/graphics/mc/CurveUtils.kt
new file mode 100644
index 000000000..2cf2b1ddc
--- /dev/null
+++ b/src/main/kotlin/com/lambda/graphics/mc/CurveUtils.kt
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.graphics.mc
+
+import net.minecraft.util.math.MathHelper
+import net.minecraft.util.math.Vec3d
+
+/**
+ * Utility functions for curve and spline calculations.
+ *
+ * Provides Bezier curves and Catmull-Rom splines for smooth
+ * trajectory rendering and path visualization.
+ */
+object CurveUtils {
+
+ /**
+ * Linear interpolation between two points.
+ */
+ fun lerp(t: Double, p0: Vec3d, p1: Vec3d): Vec3d {
+ return Vec3d(
+ MathHelper.lerp(t, p0.x, p1.x),
+ MathHelper.lerp(t, p0.y, p1.y),
+ MathHelper.lerp(t, p0.z, p1.z)
+ )
+ }
+
+ /**
+ * Quadratic Bezier curve.
+ *
+ * B(t) = (1-t)²P0 + 2(1-t)tP1 + t²P2
+ *
+ * @param t Parameter from 0.0 to 1.0
+ * @param p0 Start point
+ * @param p1 Control point
+ * @param p2 End point
+ */
+ fun quadraticBezier(t: Double, p0: Vec3d, p1: Vec3d, p2: Vec3d): Vec3d {
+ val mt = 1.0 - t
+ val mt2 = mt * mt
+ val t2 = t * t
+
+ return Vec3d(
+ mt2 * p0.x + 2 * mt * t * p1.x + t2 * p2.x,
+ mt2 * p0.y + 2 * mt * t * p1.y + t2 * p2.y,
+ mt2 * p0.z + 2 * mt * t * p1.z + t2 * p2.z
+ )
+ }
+
+ /**
+ * Cubic Bezier curve.
+ *
+ * B(t) = (1-t)³P0 + 3(1-t)²tP1 + 3(1-t)t²P2 + t³P3
+ *
+ * @param t Parameter from 0.0 to 1.0
+ * @param p0 Start point
+ * @param p1 First control point
+ * @param p2 Second control point
+ * @param p3 End point
+ */
+ fun cubicBezier(t: Double, p0: Vec3d, p1: Vec3d, p2: Vec3d, p3: Vec3d): Vec3d {
+ val mt = 1.0 - t
+ val mt2 = mt * mt
+ val mt3 = mt2 * mt
+ val t2 = t * t
+ val t3 = t2 * t
+
+ return Vec3d(
+ mt3 * p0.x + 3 * mt2 * t * p1.x + 3 * mt * t2 * p2.x + t3 * p3.x,
+ mt3 * p0.y + 3 * mt2 * t * p1.y + 3 * mt * t2 * p2.y + t3 * p3.y,
+ mt3 * p0.z + 3 * mt2 * t * p1.z + 3 * mt * t2 * p2.z + t3 * p3.z
+ )
+ }
+
+ /**
+ * Catmull-Rom spline interpolation.
+ *
+ * Unlike Bezier curves, Catmull-Rom splines pass through all control points.
+ * Uses MC's built-in catmullRom function.
+ *
+ * @param t Parameter from 0.0 to 1.0 (interpolates between p1 and p2)
+ * @param p0 Point before the segment
+ * @param p1 Start of segment
+ * @param p2 End of segment
+ * @param p3 Point after the segment
+ */
+ fun catmullRom(t: Float, p0: Vec3d, p1: Vec3d, p2: Vec3d, p3: Vec3d): Vec3d {
+ return Vec3d(
+ MathHelper.catmullRom(t, p0.x.toFloat(), p1.x.toFloat(), p2.x.toFloat(), p3.x.toFloat()).toDouble(),
+ MathHelper.catmullRom(t, p0.y.toFloat(), p1.y.toFloat(), p2.y.toFloat(), p3.y.toFloat()).toDouble(),
+ MathHelper.catmullRom(t, p0.z.toFloat(), p1.z.toFloat(), p2.z.toFloat(), p3.z.toFloat()).toDouble()
+ )
+ }
+
+ /**
+ * Generate points along a quadratic Bezier curve.
+ *
+ * @param p0 Start point
+ * @param p1 Control point
+ * @param p2 End point
+ * @param segments Number of line segments
+ * @return List of points along the curve
+ */
+ fun quadraticBezierPoints(p0: Vec3d, p1: Vec3d, p2: Vec3d, segments: Int): List {
+ return (0..segments).map { i ->
+ val t = i.toDouble() / segments
+ quadraticBezier(t, p0, p1, p2)
+ }
+ }
+
+ /**
+ * Generate points along a cubic Bezier curve.
+ *
+ * @param p0 Start point
+ * @param p1 First control point
+ * @param p2 Second control point
+ * @param p3 End point
+ * @param segments Number of line segments
+ * @return List of points along the curve
+ */
+ fun cubicBezierPoints(p0: Vec3d, p1: Vec3d, p2: Vec3d, p3: Vec3d, segments: Int): List {
+ return (0..segments).map { i ->
+ val t = i.toDouble() / segments
+ cubicBezier(t, p0, p1, p2, p3)
+ }
+ }
+
+ /**
+ * Generate points along a Catmull-Rom spline that passes through all control points.
+ *
+ * @param controlPoints List of points the spline should pass through (minimum 4)
+ * @param segmentsPerSection Number of segments between each pair of control points
+ * @return List of points along the spline
+ */
+ fun catmullRomSplinePoints(controlPoints: List, segmentsPerSection: Int): List {
+ if (controlPoints.size < 4) return controlPoints
+
+ val result = mutableListOf()
+
+ for (i in 1 until controlPoints.size - 2) {
+ val p0 = controlPoints[i - 1]
+ val p1 = controlPoints[i]
+ val p2 = controlPoints[i + 1]
+ val p3 = controlPoints[i + 2]
+
+ for (j in 0 until segmentsPerSection) {
+ val t = j.toFloat() / segmentsPerSection
+ result.add(catmullRom(t, p0, p1, p2, p3))
+ }
+ }
+
+ // Add the last point
+ result.add(controlPoints[controlPoints.size - 2])
+
+ return result
+ }
+
+ /**
+ * Estimate the arc length of a cubic Bezier curve using subdivision.
+ *
+ * @param p0 Start point
+ * @param p1 First control point
+ * @param p2 Second control point
+ * @param p3 End point
+ * @param subdivisions Number of subdivisions for estimation
+ */
+ fun cubicBezierLength(p0: Vec3d, p1: Vec3d, p2: Vec3d, p3: Vec3d, subdivisions: Int = 32): Double {
+ var length = 0.0
+ var prev = p0
+
+ for (i in 1..subdivisions) {
+ val t = i.toDouble() / subdivisions
+ val curr = cubicBezier(t, p0, p1, p2, p3)
+ length += prev.distanceTo(curr)
+ prev = curr
+ }
+
+ return length
+ }
+
+ /**
+ * Calculate the tangent (direction) at a point on a cubic Bezier curve.
+ *
+ * B'(t) = 3(1-t)²(P1-P0) + 6(1-t)t(P2-P1) + 3t²(P3-P2)
+ *
+ * @param t Parameter from 0.0 to 1.0
+ * @param p0 Start point
+ * @param p1 First control point
+ * @param p2 Second control point
+ * @param p3 End point
+ * @return Normalized tangent vector
+ */
+ fun cubicBezierTangent(t: Double, p0: Vec3d, p1: Vec3d, p2: Vec3d, p3: Vec3d): Vec3d {
+ val mt = 1.0 - t
+ val mt2 = mt * mt
+ val t2 = t * t
+
+ val d1 = p1.subtract(p0)
+ val d2 = p2.subtract(p1)
+ val d3 = p3.subtract(p2)
+
+ return Vec3d(
+ 3 * mt2 * d1.x + 6 * mt * t * d2.x + 3 * t2 * d3.x,
+ 3 * mt2 * d1.y + 6 * mt * t * d2.y + 3 * t2 * d3.y,
+ 3 * mt2 * d1.z + 6 * mt * t * d2.z + 3 * t2 * d3.z
+ ).normalize()
+ }
+
+ /**
+ * Create a smooth path through waypoints using Catmull-Rom splines.
+ * Automatically handles the first and last points by duplicating them.
+ *
+ * @param waypoints List of points to pass through (minimum 2)
+ * @param segmentsPerSection Smoothness (higher = smoother)
+ */
+ fun smoothPath(waypoints: List, segmentsPerSection: Int = 16): List {
+ if (waypoints.size < 2) return waypoints
+ if (waypoints.size == 2) return listOf(waypoints[0], waypoints[1])
+
+ // Extend with phantom points for natural curve at endpoints
+ val extended = buildList {
+ // Mirror first point
+ add(waypoints[0].add(waypoints[0].subtract(waypoints[1])))
+ addAll(waypoints)
+ // Mirror last point
+ val last = waypoints.last()
+ val secondLast = waypoints[waypoints.size - 2]
+ add(last.add(last.subtract(secondLast)))
+ }
+
+ return catmullRomSplinePoints(extended, segmentsPerSection)
+ }
+}
diff --git a/src/main/kotlin/com/lambda/graphics/mc/ImGuiWorldText.kt b/src/main/kotlin/com/lambda/graphics/mc/ImGuiWorldText.kt
new file mode 100644
index 000000000..735f17de1
--- /dev/null
+++ b/src/main/kotlin/com/lambda/graphics/mc/ImGuiWorldText.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.graphics.mc
+
+import com.lambda.graphics.RenderMain
+import imgui.ImGui
+import imgui.ImVec2
+import net.minecraft.util.math.Vec3d
+import java.awt.Color
+
+/**
+ * ImGUI-based world text renderer.
+ * Projects world coordinates to screen space and draws text using ImGUI.
+ *
+ * Usage:
+ * ```kotlin
+ * // In a GuiEvent.NewFrame listener
+ * ImGuiWorldText.drawText(entity.pos, "Label", Color.WHITE)
+ * ```
+ */
+object ImGuiWorldText {
+
+ /**
+ * Draw text at a world position using ImGUI.
+ *
+ * @param worldPos World position for the text
+ * @param text The text to render
+ * @param color Text color
+ * @param centered Whether to center the text horizontally
+ * @param offsetY Vertical offset in screen pixels (negative = up)
+ */
+ fun drawText(
+ worldPos: Vec3d,
+ text: String,
+ color: Color = Color.WHITE,
+ centered: Boolean = true,
+ offsetY: Float = 0f
+ ) {
+ val screen = RenderMain.worldToScreen(worldPos) ?: return
+
+ val drawList = ImGui.getBackgroundDrawList()
+ val colorInt = colorToImGui(color)
+
+ val x = if (centered) {
+ val textSize = ImVec2()
+ ImGui.calcTextSize(textSize, text)
+ screen.x - textSize.x / 2f
+ } else {
+ screen.x
+ }
+
+ drawList.addText(x, screen.y + offsetY, colorInt, text)
+ }
+
+ /**
+ * Draw text with a shadow/outline effect.
+ */
+ fun drawTextWithShadow(
+ worldPos: Vec3d,
+ text: String,
+ color: Color = Color.WHITE,
+ shadowColor: Color = Color.BLACK,
+ centered: Boolean = true,
+ offsetY: Float = 0f
+ ) {
+ val screen = RenderMain.worldToScreen(worldPos) ?: return
+
+ val drawList = ImGui.getBackgroundDrawList()
+ val textSize = ImVec2()
+ ImGui.calcTextSize(textSize, text)
+
+ val x = if (centered) screen.x - textSize.x / 2f else screen.x
+ val y = screen.y + offsetY
+
+ // Draw shadow (offset by 1 pixel)
+ val shadowInt = colorToImGui(shadowColor)
+ drawList.addText(x + 1f, y + 1f, shadowInt, text)
+
+ // Draw main text
+ val colorInt = colorToImGui(color)
+ drawList.addText(x, y, colorInt, text)
+ }
+
+ /**
+ * Draw multiple lines of text stacked vertically.
+ */
+ fun drawMultilineText(
+ worldPos: Vec3d,
+ lines: List,
+ color: Color = Color.WHITE,
+ centered: Boolean = true,
+ lineSpacing: Float = 12f,
+ offsetY: Float = 0f
+ ) {
+ val screen = RenderMain.worldToScreen(worldPos) ?: return
+
+ val drawList = ImGui.getBackgroundDrawList()
+ val colorInt = colorToImGui(color)
+
+ lines.forEachIndexed { index, line ->
+ val textSize = ImVec2()
+ ImGui.calcTextSize(textSize, line)
+
+ val x = if (centered) screen.x - textSize.x / 2f else screen.x
+ val y = screen.y + offsetY + (index * lineSpacing)
+
+ drawList.addText(x, y, colorInt, line)
+ }
+ }
+
+ /**
+ * Draw text with a background box.
+ */
+ fun drawTextWithBackground(
+ worldPos: Vec3d,
+ text: String,
+ textColor: Color = Color.WHITE,
+ backgroundColor: Color = Color(0, 0, 0, 128),
+ centered: Boolean = true,
+ padding: Float = 4f,
+ offsetY: Float = 0f
+ ) {
+ val screen = RenderMain.worldToScreen(worldPos) ?: return
+
+ val drawList = ImGui.getBackgroundDrawList()
+ val textSize = ImVec2()
+ ImGui.calcTextSize(textSize, text)
+
+ val x = if (centered) screen.x - textSize.x / 2f else screen.x
+ val y = screen.y + offsetY
+
+ // Draw background
+ val bgInt = colorToImGui(backgroundColor)
+ drawList.addRectFilled(
+ x - padding,
+ y - padding,
+ x + textSize.x + padding,
+ y + textSize.y + padding,
+ bgInt,
+ 2f // corner rounding
+ )
+
+ // Draw text
+ val colorInt = colorToImGui(textColor)
+ drawList.addText(x, y, colorInt, text)
+ }
+
+ /**
+ * Convert java.awt.Color to ImGui color format (ABGR)
+ */
+ private fun colorToImGui(color: Color): Int {
+ return (color.alpha shl 24) or
+ (color.blue shl 16) or
+ (color.green shl 8) or
+ color.red
+ }
+}
diff --git a/src/main/kotlin/com/lambda/graphics/mc/InterpolatedRegionESP.kt b/src/main/kotlin/com/lambda/graphics/mc/InterpolatedRegionESP.kt
new file mode 100644
index 000000000..8b2b0b4b7
--- /dev/null
+++ b/src/main/kotlin/com/lambda/graphics/mc/InterpolatedRegionESP.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.graphics.mc
+
+import com.lambda.graphics.esp.RegionESP
+import com.lambda.graphics.esp.ShapeScope
+import java.util.concurrent.ConcurrentHashMap
+import kotlin.math.floor
+
+/**
+ * Interpolated region-based ESP system for smooth entity rendering.
+ *
+ * Unlike TransientRegionESP which rebuilds every tick, this system stores both previous and current
+ * frame data and interpolates between them during rendering for smooth movement at any framerate.
+ */
+class InterpolatedRegionESP(name: String, depthTest: Boolean = false) : RegionESP(name, depthTest) {
+ // Current frame builders (being populated this tick)
+ private val currBuilders = ConcurrentHashMap()
+
+ // Previous frame data (uploaded last tick)
+ private val prevBuilders = ConcurrentHashMap()
+
+ // Interpolated collectors for rendering (computed each frame)
+ private val interpolatedCollectors =
+ ConcurrentHashMap()
+
+ // Track if we need to re-interpolate
+ private var lastTickDelta = -1f
+ private var needsInterpolation = true
+
+ override fun shapes(x: Double, y: Double, z: Double, block: ShapeScope.() -> Unit) {
+ val key = getRegionKey(x, y, z)
+ val scope =
+ currBuilders.getOrPut(key) {
+ val size = RenderRegion.REGION_SIZE
+ val rx = (size * floor(x / size)).toInt()
+ val ry = (size * floor(y / size)).toInt()
+ val rz = (size * floor(z / size)).toInt()
+ ShapeScope(RenderRegion(rx, ry, rz), collectShapes = true)
+ }
+ scope.apply(block)
+ }
+
+ override fun clear() {
+ prevBuilders.clear()
+ currBuilders.clear()
+ interpolatedCollectors.clear()
+ }
+
+ fun tick() {
+ prevBuilders.clear()
+ prevBuilders.putAll(currBuilders)
+ currBuilders.clear()
+ needsInterpolation = true
+ }
+
+ override fun upload() {
+ needsInterpolation = true
+ }
+
+ override fun render(tickDelta: Float) {
+ if (needsInterpolation || lastTickDelta != tickDelta) {
+ interpolate(tickDelta)
+ uploadInterpolated()
+ lastTickDelta = tickDelta
+ needsInterpolation = false
+ }
+ super.render(tickDelta)
+ }
+
+ private fun interpolate(tickDelta: Float) {
+ interpolatedCollectors.clear()
+ (prevBuilders.keys + currBuilders.keys).toSet().forEach { key ->
+ val prevScope = prevBuilders[key]
+ val currScope = currBuilders[key]
+ val collector = RegionVertexCollector()
+ val region = currScope?.region ?: prevScope?.region ?: return@forEach
+
+ val prevShapes = prevScope?.shapes?.associateBy { it.id } ?: emptyMap()
+ val currShapes = currScope?.shapes?.associateBy { it.id } ?: emptyMap()
+
+ val allIds = (prevShapes.keys + currShapes.keys).toSet()
+
+ for (id in allIds) {
+ val prev = prevShapes[id]
+ val curr = currShapes[id]
+
+ when {
+ prev != null && curr != null -> {
+ curr.renderInterpolated(prev, tickDelta, collector, region)
+ }
+ curr != null -> {
+ // New shape - just render
+ curr.renderInterpolated(curr, 1.0f, collector, region)
+ }
+ prev != null -> {
+ // Disappeared - render at previous position
+ prev.renderInterpolated(prev, 1.0f, collector, region)
+ }
+ }
+ }
+
+ if (collector.faceVertices.isNotEmpty() || collector.edgeVertices.isNotEmpty()) {
+ interpolatedCollectors[key] = collector
+ }
+ }
+ }
+
+ private fun uploadInterpolated() {
+ val activeKeys = interpolatedCollectors.keys.toSet()
+ interpolatedCollectors.forEach { (key, collector) ->
+ val region = currBuilders[key]?.region ?: prevBuilders[key]?.region ?: return@forEach
+
+ val renderer = renderers.getOrPut(key) { RegionRenderer(region) }
+ renderer.upload(collector)
+ }
+
+ renderers.forEach { (key, renderer) ->
+ if (key !in activeKeys) {
+ renderer.clearData()
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/com/lambda/graphics/mc/LambdaRenderPipelines.kt b/src/main/kotlin/com/lambda/graphics/mc/LambdaRenderPipelines.kt
new file mode 100644
index 000000000..350a40a77
--- /dev/null
+++ b/src/main/kotlin/com/lambda/graphics/mc/LambdaRenderPipelines.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.graphics.mc
+
+import com.lambda.core.Loadable
+import com.mojang.blaze3d.pipeline.BlendFunction
+import com.mojang.blaze3d.pipeline.RenderPipeline
+import com.mojang.blaze3d.platform.DepthTestFunction
+import com.mojang.blaze3d.vertex.VertexFormat
+import net.minecraft.client.gl.RenderPipelines
+import net.minecraft.client.render.VertexFormats
+import net.minecraft.util.Identifier
+
+object LambdaRenderPipelines : Loadable {
+ override val priority: Int
+ get() = 100 // High priority to ensure pipelines are ready early
+
+ /**
+ * Base snippet for Lambda ESP rendering. Includes transforms, projection, and a custom
+ * per-region uniform.
+ */
+ private val LAMBDA_ESP_SNIPPET =
+ RenderPipeline.builder(RenderPipelines.TRANSFORMS_AND_PROJECTION_SNIPPET).buildSnippet()
+
+ /**
+ * Pipeline for ESP lines/outlines.
+ * - Uses MC's line rendering with per-vertex line width
+ * - No depth write for overlapping
+ * - No culling
+ */
+ val ESP_LINES: RenderPipeline =
+ RenderPipelines.register(
+ RenderPipeline.builder(LAMBDA_ESP_SNIPPET, RenderPipelines.GLOBALS_SNIPPET)
+ .withLocation(Identifier.of("lambda", "pipeline/esp_lines"))
+ .withVertexShader(Identifier.of("lambda", "core/advanced_lines"))
+ .withFragmentShader(Identifier.of("lambda", "core/advanced_lines"))
+ .withBlend(BlendFunction.TRANSLUCENT)
+ .withDepthWrite(false)
+ .withDepthTestFunction(DepthTestFunction.LEQUAL_DEPTH_TEST)
+ .withCull(false)
+ .withVertexFormat(
+ VertexFormats.POSITION_COLOR_NORMAL_LINE_WIDTH,
+ VertexFormat.DrawMode.QUADS
+ )
+ .build()
+ )
+
+ /** Pipeline for ESP lines that render through walls. */
+ val ESP_LINES_THROUGH: RenderPipeline =
+ RenderPipelines.register(
+ RenderPipeline.builder(LAMBDA_ESP_SNIPPET, RenderPipelines.GLOBALS_SNIPPET)
+ .withLocation(Identifier.of("lambda", "pipeline/esp_lines_through"))
+ .withVertexShader(Identifier.of("lambda", "core/advanced_lines"))
+ .withFragmentShader(Identifier.of("lambda", "core/advanced_lines"))
+ .withBlend(BlendFunction.TRANSLUCENT)
+ .withDepthWrite(false)
+ .withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST)
+ .withCull(false)
+ .withVertexFormat(
+ VertexFormats.POSITION_COLOR_NORMAL_LINE_WIDTH,
+ VertexFormat.DrawMode.QUADS
+ )
+ .build()
+ )
+
+ /**
+ * Pipeline for quad-based ESP (compatible with existing shape building). Uses QUADS draw mode
+ * which MC converts to triangles internally.
+ */
+ val ESP_QUADS: RenderPipeline =
+ RenderPipelines.register(
+ RenderPipeline.builder(LAMBDA_ESP_SNIPPET)
+ .withLocation(Identifier.of("lambda", "pipeline/esp_quads"))
+ .withVertexShader(Identifier.ofVanilla("core/position_color"))
+ .withFragmentShader(Identifier.ofVanilla("core/position_color"))
+ .withBlend(BlendFunction.TRANSLUCENT)
+ .withDepthWrite(false)
+ .withDepthTestFunction(DepthTestFunction.LEQUAL_DEPTH_TEST)
+ .withCull(false)
+ .withVertexFormat(
+ VertexFormats.POSITION_COLOR,
+ VertexFormat.DrawMode.QUADS
+ )
+ .build()
+ )
+
+ /** Pipeline for quad-based ESP that renders through walls. */
+ val ESP_QUADS_THROUGH: RenderPipeline =
+ RenderPipelines.register(
+ RenderPipeline.builder(LAMBDA_ESP_SNIPPET)
+ .withLocation(Identifier.of("lambda", "pipeline/esp_quads_through"))
+ .withVertexShader(Identifier.ofVanilla("core/position_color"))
+ .withFragmentShader(Identifier.ofVanilla("core/position_color"))
+ .withBlend(BlendFunction.TRANSLUCENT)
+ .withDepthWrite(false)
+ .withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST)
+ .withCull(false)
+ .withVertexFormat(
+ VertexFormats.POSITION_COLOR,
+ VertexFormat.DrawMode.QUADS
+ )
+ .build()
+ )
+}
diff --git a/src/main/kotlin/com/lambda/graphics/mc/RegionRenderer.kt b/src/main/kotlin/com/lambda/graphics/mc/RegionRenderer.kt
new file mode 100644
index 000000000..8d7f36f4a
--- /dev/null
+++ b/src/main/kotlin/com/lambda/graphics/mc/RegionRenderer.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.graphics.mc
+
+import com.lambda.Lambda.mc
+import com.mojang.blaze3d.buffers.GpuBuffer
+import com.mojang.blaze3d.systems.RenderPass
+import com.mojang.blaze3d.systems.RenderSystem
+import com.mojang.blaze3d.vertex.VertexFormat
+import java.util.*
+
+/**
+ * Region-based renderer for ESP rendering using MC 1.21.11's new render pipeline.
+ *
+ * This renderer manages the lifecycle of dedicated GPU buffers for a specific region and provides
+ * methods to render them within a RenderPass.
+ *
+ * @param region The render region this renderer is associated with
+ */
+class RegionRenderer(val region: RenderRegion) {
+
+ // Dedicated GPU buffers for faces and edges
+ private var faceVertexBuffer: GpuBuffer? = null
+ private var edgeVertexBuffer: GpuBuffer? = null
+
+ // Index counts for draw calls
+ private var faceIndexCount = 0
+ private var edgeIndexCount = 0
+
+ // State tracking
+ private var hasData = false
+
+ /**
+ * Upload collected vertices from an external collector. This must be called on the main/render
+ * thread.
+ *
+ * @param collector The collector containing the geometry to upload
+ */
+ fun upload(collector: RegionVertexCollector) {
+ val result = collector.upload()
+
+ // Cleanup old buffers
+ faceVertexBuffer?.close()
+ edgeVertexBuffer?.close()
+
+ // Assign new buffers and counts
+ faceVertexBuffer = result.faces?.buffer
+ faceIndexCount = result.faces?.indexCount ?: 0
+
+ edgeVertexBuffer = result.edges?.buffer
+ edgeIndexCount = result.edges?.indexCount ?: 0
+
+ hasData = faceVertexBuffer != null || edgeVertexBuffer != null
+ }
+
+ /**
+ * Render faces using the given render pass.
+ *
+ * @param renderPass The active RenderPass to record commands into
+ */
+ fun renderFaces(renderPass: RenderPass) {
+ val vb = faceVertexBuffer ?: return
+ if (faceIndexCount == 0) return
+
+ renderPass.setVertexBuffer(0, vb)
+ // Use vanilla's sequential index buffer for quads
+ val shapeIndexBuffer = RenderSystem.getSequentialBuffer(VertexFormat.DrawMode.QUADS)
+ val indexBuffer = shapeIndexBuffer.getIndexBuffer(faceIndexCount)
+
+ renderPass.setIndexBuffer(indexBuffer, shapeIndexBuffer.indexType)
+ renderPass.drawIndexed(0, 0, faceIndexCount, 1)
+ }
+
+ /**
+ * Render edges using the given render pass.
+ *
+ * @param renderPass The active RenderPass to record commands into
+ */
+ fun renderEdges(renderPass: RenderPass) {
+ val vb = edgeVertexBuffer ?: return
+ if (edgeIndexCount == 0) return
+
+ renderPass.setVertexBuffer(0, vb)
+ // Use vanilla's sequential index buffer for quads
+ val shapeIndexBuffer = RenderSystem.getSequentialBuffer(VertexFormat.DrawMode.QUADS)
+ val indexBuffer = shapeIndexBuffer.getIndexBuffer(edgeIndexCount)
+
+ renderPass.setIndexBuffer(indexBuffer, shapeIndexBuffer.indexType)
+ renderPass.drawIndexed(0, 0, edgeIndexCount, 1)
+ }
+
+ /** Clear all geometry data and release GPU resources. */
+ fun clearData() {
+ faceVertexBuffer?.close()
+ edgeVertexBuffer?.close()
+ faceVertexBuffer = null
+ edgeVertexBuffer = null
+ faceIndexCount = 0
+ edgeIndexCount = 0
+ hasData = false
+ }
+
+ /** Check if this renderer has any data to render. */
+ fun hasData(): Boolean = hasData
+
+ /** Clean up all resources. */
+ fun close() {
+ clearData()
+ }
+
+ companion object {
+ /** Helper to create a render pass targeting the main framebuffer. */
+ fun createRenderPass(label: String): RenderPass? {
+ val framebuffer = mc.framebuffer ?: return null
+ return RenderSystem.getDevice()
+ .createCommandEncoder()
+ .createRenderPass(
+ { label },
+ framebuffer.colorAttachmentView,
+ OptionalInt.empty(),
+ framebuffer.depthAttachmentView,
+ OptionalDouble.empty()
+ )
+ }
+ }
+}
diff --git a/src/main/kotlin/com/lambda/graphics/mc/RegionShapeBuilder.kt b/src/main/kotlin/com/lambda/graphics/mc/RegionShapeBuilder.kt
new file mode 100644
index 000000000..8b33498be
--- /dev/null
+++ b/src/main/kotlin/com/lambda/graphics/mc/RegionShapeBuilder.kt
@@ -0,0 +1,758 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.graphics.mc
+
+import com.lambda.Lambda.mc
+import com.lambda.graphics.renderer.esp.DirectionMask
+import com.lambda.graphics.renderer.esp.DirectionMask.hasDirection
+import com.lambda.graphics.renderer.esp.DynamicAABB
+import com.lambda.module.modules.client.StyleEditor
+import com.lambda.threading.runSafe
+import com.lambda.util.BlockUtils.blockState
+import com.lambda.util.extension.partialTicks
+import net.minecraft.block.BlockState
+import net.minecraft.block.entity.BlockEntity
+import net.minecraft.entity.Entity
+import net.minecraft.util.math.BlockPos
+import net.minecraft.util.math.Box
+import net.minecraft.util.math.MathHelper.lerp
+import net.minecraft.util.math.Vec3d
+import net.minecraft.util.shape.VoxelShape
+import java.awt.Color
+import kotlin.math.min
+import kotlin.math.sqrt
+
+/**
+ * Shape builder for region-based rendering. All coordinates are automatically converted to
+ * region-relative positions.
+ *
+ * This class provides drawing primitives for region-based rendering and collects vertex data in thread-safe collections
+ * for later upload to MC's BufferBuilder.
+ *
+ * @param region The render region (provides origin for coordinate conversion)
+ */
+class RegionShapeBuilder(val region: RenderRegion) {
+ val collector = RegionVertexCollector()
+
+ val lineWidth: Float
+ get() = StyleEditor.outlineWidth.toFloat()
+
+ fun box(
+ entity: BlockEntity,
+ filled: Color,
+ outline: Color,
+ sides: Int = DirectionMask.ALL,
+ mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And
+ ) = box(entity.pos, entity.cachedState, filled, outline, sides, mode)
+
+ fun box(
+ entity: Entity,
+ filled: Color,
+ outline: Color,
+ sides: Int = DirectionMask.ALL,
+ mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And
+ ) = box(entity.boundingBox, filled, outline, sides, mode)
+
+ /** Convert world coordinates to region-relative. */
+ private fun toRelative(x: Double, y: Double, z: Double) =
+ Triple(
+ (x - region.originX).toFloat(),
+ (y - region.originY).toFloat(),
+ (z - region.originZ).toFloat()
+ )
+
+ /** Add a colored quad face (filled rectangle). */
+ fun filled(
+ box: Box,
+ bottomColor: Color,
+ topColor: Color = bottomColor,
+ sides: Int = DirectionMask.ALL
+ ) {
+ val (x1, y1, z1) = toRelative(box.minX, box.minY, box.minZ)
+ val (x2, y2, z2) = toRelative(box.maxX, box.maxY, box.maxZ)
+
+ // Bottom-left-back, bottom-left-front, etc.
+ if (sides.hasDirection(DirectionMask.EAST)) {
+ // East face (+X)
+ faceVertex(x2, y1, z1, bottomColor)
+ faceVertex(x2, y2, z1, topColor)
+ faceVertex(x2, y2, z2, topColor)
+ faceVertex(x2, y1, z2, bottomColor)
+ }
+ if (sides.hasDirection(DirectionMask.WEST)) {
+ // West face (-X)
+ faceVertex(x1, y1, z1, bottomColor)
+ faceVertex(x1, y1, z2, bottomColor)
+ faceVertex(x1, y2, z2, topColor)
+ faceVertex(x1, y2, z1, topColor)
+ }
+ if (sides.hasDirection(DirectionMask.UP)) {
+ // Top face (+Y)
+ faceVertex(x1, y2, z1, topColor)
+ faceVertex(x1, y2, z2, topColor)
+ faceVertex(x2, y2, z2, topColor)
+ faceVertex(x2, y2, z1, topColor)
+ }
+ if (sides.hasDirection(DirectionMask.DOWN)) {
+ // Bottom face (-Y)
+ faceVertex(x1, y1, z1, bottomColor)
+ faceVertex(x2, y1, z1, bottomColor)
+ faceVertex(x2, y1, z2, bottomColor)
+ faceVertex(x1, y1, z2, bottomColor)
+ }
+ if (sides.hasDirection(DirectionMask.SOUTH)) {
+ // South face (+Z)
+ faceVertex(x1, y1, z2, bottomColor)
+ faceVertex(x2, y1, z2, bottomColor)
+ faceVertex(x2, y2, z2, topColor)
+ faceVertex(x1, y2, z2, topColor)
+ }
+ if (sides.hasDirection(DirectionMask.NORTH)) {
+ // North face (-Z)
+ faceVertex(x1, y1, z1, bottomColor)
+ faceVertex(x1, y2, z1, topColor)
+ faceVertex(x2, y2, z1, topColor)
+ faceVertex(x2, y1, z1, bottomColor)
+ }
+ }
+
+ fun filled(box: Box, color: Color, sides: Int = DirectionMask.ALL) =
+ filled(box, color, color, sides)
+
+ fun filled(box: DynamicAABB, color: Color, sides: Int = DirectionMask.ALL) {
+ val pair = box.pair ?: return
+ val prev = pair.first
+ val curr = pair.second
+ val tickDelta = mc.partialTicks
+ val interpolated = Box(
+ lerp(tickDelta, prev.minX, curr.minX),
+ lerp(tickDelta, prev.minY, curr.minY),
+ lerp(tickDelta, prev.minZ, curr.minZ),
+ lerp(tickDelta, prev.maxX, curr.maxX),
+ lerp(tickDelta, prev.maxY, curr.maxY),
+ lerp(tickDelta, prev.maxZ, curr.maxZ)
+ )
+ filled(interpolated, color, sides)
+ }
+
+ fun filled(
+ pos: BlockPos,
+ state: BlockState,
+ color: Color,
+ sides: Int = DirectionMask.ALL
+ ) = runSafe {
+ val shape = state.getOutlineShape(world, pos)
+ if (shape.isEmpty) {
+ filled(Box(pos), color, sides)
+ } else {
+ filled(shape.offset(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()), color, sides)
+ }
+ }
+
+ fun filled(pos: BlockPos, color: Color, sides: Int = DirectionMask.ALL) = runSafe {
+ filled(pos, blockState(pos), color, sides)
+ }
+
+ fun filled(pos: BlockPos, entity: BlockEntity, color: Color, sides: Int = DirectionMask.ALL) =
+ filled(pos, entity.cachedState, color, sides)
+
+ fun filled(shape: VoxelShape, color: Color, sides: Int = DirectionMask.ALL) {
+ shape.boundingBoxes.forEach { filled(it, color, color, sides) }
+ }
+
+ /** Add outline (lines) for a box. */
+ fun outline(
+ box: Box,
+ bottomColor: Color,
+ topColor: Color = bottomColor,
+ sides: Int = DirectionMask.ALL,
+ mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And,
+ thickness: Float = lineWidth
+ ) {
+ val (x1, y1, z1) = toRelative(box.minX, box.minY, box.minZ)
+ val (x2, y2, z2) = toRelative(box.maxX, box.maxY, box.maxZ)
+
+ val hasEast = sides.hasDirection(DirectionMask.EAST)
+ val hasWest = sides.hasDirection(DirectionMask.WEST)
+ val hasUp = sides.hasDirection(DirectionMask.UP)
+ val hasDown = sides.hasDirection(DirectionMask.DOWN)
+ val hasSouth = sides.hasDirection(DirectionMask.SOUTH)
+ val hasNorth = sides.hasDirection(DirectionMask.NORTH)
+
+ // Top edges
+ if (mode.check(hasUp, hasNorth)) line(x1, y2, z1, x2, y2, z1, topColor, topColor, thickness)
+ if (mode.check(hasUp, hasSouth)) line(x1, y2, z2, x2, y2, z2, topColor, topColor, thickness)
+ if (mode.check(hasUp, hasWest)) line(x1, y2, z1, x1, y2, z2, topColor, topColor, thickness)
+ if (mode.check(hasUp, hasEast)) line(x2, y2, z2, x2, y2, z1, topColor, topColor, thickness)
+
+ // Bottom edges
+ if (mode.check(hasDown, hasNorth)) line(x1, y1, z1, x2, y1, z1, bottomColor, bottomColor, thickness)
+ if (mode.check(hasDown, hasSouth)) line(x1, y1, z2, x2, y1, z2, bottomColor, bottomColor, thickness)
+ if (mode.check(hasDown, hasWest)) line(x1, y1, z1, x1, y1, z2, bottomColor, bottomColor, thickness)
+ if (mode.check(hasDown, hasEast)) line(x2, y1, z1, x2, y1, z2, bottomColor, bottomColor, thickness)
+
+ // Vertical edges
+ if (mode.check(hasWest, hasNorth)) line(x1, y2, z1, x1, y1, z1, topColor, bottomColor, thickness)
+ if (mode.check(hasNorth, hasEast)) line(x2, y2, z1, x2, y1, z1, topColor, bottomColor, thickness)
+ if (mode.check(hasEast, hasSouth)) line(x2, y2, z2, x2, y1, z2, topColor, bottomColor, thickness)
+ if (mode.check(hasSouth, hasWest)) line(x1, y2, z2, x1, y1, z2, topColor, bottomColor, thickness)
+ }
+
+ fun outline(
+ box: Box,
+ color: Color,
+ sides: Int = DirectionMask.ALL,
+ mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And,
+ thickness: Float = lineWidth
+ ) = outline(box, color, color, sides, mode, thickness)
+
+ fun outline(
+ box: DynamicAABB,
+ color: Color,
+ sides: Int = DirectionMask.ALL,
+ mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And,
+ thickness: Float = lineWidth
+ ) {
+ val pair = box.pair ?: return
+ val prev = pair.first
+ val curr = pair.second
+ val tickDelta = mc.partialTicks
+ val interpolated = Box(
+ lerp(tickDelta, prev.minX, curr.minX),
+ lerp(tickDelta, prev.minY, curr.minY),
+ lerp(tickDelta, prev.minZ, curr.minZ),
+ lerp(tickDelta, prev.maxX, curr.maxX),
+ lerp(tickDelta, prev.maxY, curr.maxY),
+ lerp(tickDelta, prev.maxZ, curr.maxZ)
+ )
+ outline(interpolated, color, sides, mode, thickness)
+ }
+
+ fun outline(
+ pos: BlockPos,
+ state: BlockState,
+ color: Color,
+ sides: Int = DirectionMask.ALL,
+ mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And,
+ thickness: Float = lineWidth
+ ) = runSafe {
+ val shape = state.getOutlineShape(world, pos)
+ if (shape.isEmpty) {
+ outline(Box(pos), color, sides, mode, thickness)
+ } else {
+ outline(shape.offset(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()), color, sides, mode, thickness)
+ }
+ }
+
+ fun outline(
+ pos: BlockPos,
+ color: Color,
+ sides: Int = DirectionMask.ALL,
+ mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And,
+ thickness: Float = lineWidth
+ ) = runSafe { outline(pos, blockState(pos), color, sides, mode, thickness) }
+
+ fun outline(
+ pos: BlockPos,
+ entity: BlockEntity,
+ color: Color,
+ sides: Int = DirectionMask.ALL,
+ mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And,
+ thickness: Float = lineWidth
+ ) = runSafe { outline(pos, entity.cachedState, color, sides, mode, thickness) }
+
+ fun outline(
+ shape: VoxelShape,
+ color: Color,
+ sides: Int = DirectionMask.ALL,
+ mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And,
+ thickness: Float = lineWidth
+ ) {
+ shape.boundingBoxes.forEach { outline(it, color, sides, mode, thickness) }
+ }
+
+ /** Add both filled and outline for a box. */
+ fun box(
+ pos: BlockPos,
+ state: BlockState,
+ filledColor: Color,
+ outlineColor: Color,
+ sides: Int = DirectionMask.ALL,
+ mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And,
+ thickness: Float = lineWidth
+ ) = runSafe {
+ filled(pos, state, filledColor, sides)
+ outline(pos, state, outlineColor, sides, mode, thickness)
+ }
+
+ fun box(
+ pos: BlockPos,
+ filledColor: Color,
+ outlineColor: Color,
+ sides: Int = DirectionMask.ALL,
+ mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And,
+ thickness: Float = lineWidth
+ ) = runSafe {
+ filled(pos, filledColor, sides)
+ outline(pos, outlineColor, sides, mode, thickness)
+ }
+
+ fun box(
+ box: Box,
+ filledColor: Color,
+ outlineColor: Color,
+ sides: Int = DirectionMask.ALL,
+ mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And,
+ thickness: Float = lineWidth
+ ) {
+ filled(box, filledColor, sides)
+ outline(box, outlineColor, sides, mode, thickness)
+ }
+
+ fun box(
+ box: DynamicAABB,
+ filledColor: Color,
+ outlineColor: Color,
+ sides: Int = DirectionMask.ALL,
+ mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And,
+ thickness: Float = lineWidth
+ ) {
+ filled(box, filledColor, sides)
+ outline(box, outlineColor, sides, mode, thickness)
+ }
+
+ fun box(
+ entity: BlockEntity,
+ filled: Color,
+ outlineColor: Color,
+ sides: Int = DirectionMask.ALL,
+ mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And,
+ thickness: Float = lineWidth
+ ) = runSafe {
+ filled(entity.pos, entity, filled, sides)
+ outline(entity.pos, entity, outlineColor, sides, mode, thickness)
+ }
+
+ fun box(
+ entity: Entity,
+ filled: Color,
+ outlineColor: Color,
+ sides: Int = DirectionMask.ALL,
+ mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And,
+ thickness: Float = lineWidth
+ ) = runSafe {
+ filled(entity.boundingBox, filled, sides)
+ outline(entity.boundingBox, outlineColor, sides, mode, thickness)
+ }
+
+ private fun faceVertex(x: Float, y: Float, z: Float, color: Color) {
+ collector.addFaceVertex(x, y, z, color)
+ }
+
+ private fun line(
+ x1: Float,
+ y1: Float,
+ z1: Float,
+ x2: Float,
+ y2: Float,
+ z2: Float,
+ color: Color,
+ width: Float = lineWidth
+ ) {
+ line(x1, y1, z1, x2, y2, z2, color, color, width)
+ }
+
+ private fun line(
+ x1: Float,
+ y1: Float,
+ z1: Float,
+ x2: Float,
+ y2: Float,
+ z2: Float,
+ color1: Color,
+ color2: Color,
+ width: Float = lineWidth
+ ) {
+ // Calculate segment vector (dx, dy, dz)
+ val dx = x2 - x1
+ val dy = y2 - y1
+ val dz = z2 - z1
+
+ // Quad-based lines need 4 vertices per segment
+ // We pass the full vector as 'Normal' so the shader knows where the other end is
+ collector.addEdgeVertex(x1, y1, z1, color1, dx, dy, dz, width)
+ collector.addEdgeVertex(x1, y1, z1, color1, dx, dy, dz, width)
+ collector.addEdgeVertex(x2, y2, z2, color2, dx, dy, dz, width)
+ collector.addEdgeVertex(x2, y2, z2, color2, dx, dy, dz, width)
+ }
+
+ /**
+ * Draw a dashed line between two world positions.
+ *
+ * @param start Start position in world coordinates
+ * @param end End position in world coordinates
+ * @param color Line color
+ * @param dashLength Length of each dash in blocks
+ * @param gapLength Length of each gap in blocks
+ * @param width Line width (uses default if null)
+ */
+ fun dashedLine(
+ start: Vec3d,
+ end: Vec3d,
+ color: Color,
+ dashLength: Double = 0.5,
+ gapLength: Double = 0.25,
+ width: Float = lineWidth
+ ) {
+ val direction = end.subtract(start)
+ val totalLength = direction.length()
+ if (totalLength < 0.001) return
+
+ val normalizedDir = direction.normalize()
+ var pos = 0.0
+ var isDash = true
+
+ while (pos < totalLength) {
+ val segmentLength = if (isDash) dashLength else gapLength
+ val segmentEnd = min(pos + segmentLength, totalLength)
+
+ if (isDash) {
+ val segStart = start.add(normalizedDir.multiply(pos))
+ val segEnd = start.add(normalizedDir.multiply(segmentEnd))
+
+ val (x1, y1, z1) = toRelative(segStart.x, segStart.y, segStart.z)
+ val (x2, y2, z2) = toRelative(segEnd.x, segEnd.y, segEnd.z)
+
+ lineWithWidth(x1, y1, z1, x2, y2, z2, color, width)
+ }
+
+ pos = segmentEnd
+ isDash = !isDash
+ }
+ }
+
+ /** Draw a dashed outline for a box. */
+ fun dashedOutline(
+ box: Box,
+ color: Color,
+ dashLength: Double = 0.5,
+ gapLength: Double = 0.25,
+ sides: Int = DirectionMask.ALL,
+ mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And
+ ) {
+ val hasEast = sides.hasDirection(DirectionMask.EAST)
+ val hasWest = sides.hasDirection(DirectionMask.WEST)
+ val hasUp = sides.hasDirection(DirectionMask.UP)
+ val hasDown = sides.hasDirection(DirectionMask.DOWN)
+ val hasSouth = sides.hasDirection(DirectionMask.SOUTH)
+ val hasNorth = sides.hasDirection(DirectionMask.NORTH)
+
+ // Top edges
+ if (mode.check(hasUp, hasNorth))
+ dashedLine(
+ Vec3d(box.minX, box.maxY, box.minZ),
+ Vec3d(box.maxX, box.maxY, box.minZ),
+ color,
+ dashLength,
+ gapLength
+ )
+ if (mode.check(hasUp, hasSouth))
+ dashedLine(
+ Vec3d(box.minX, box.maxY, box.maxZ),
+ Vec3d(box.maxX, box.maxY, box.maxZ),
+ color,
+ dashLength,
+ gapLength
+ )
+ if (mode.check(hasUp, hasWest))
+ dashedLine(
+ Vec3d(box.minX, box.maxY, box.minZ),
+ Vec3d(box.minX, box.maxY, box.maxZ),
+ color,
+ dashLength,
+ gapLength
+ )
+ if (mode.check(hasUp, hasEast))
+ dashedLine(
+ Vec3d(box.maxX, box.maxY, box.maxZ),
+ Vec3d(box.maxX, box.maxY, box.minZ),
+ color,
+ dashLength,
+ gapLength
+ )
+
+ // Bottom edges
+ if (mode.check(hasDown, hasNorth))
+ dashedLine(
+ Vec3d(box.minX, box.minY, box.minZ),
+ Vec3d(box.maxX, box.minY, box.minZ),
+ color,
+ dashLength,
+ gapLength
+ )
+ if (mode.check(hasDown, hasSouth))
+ dashedLine(
+ Vec3d(box.minX, box.minY, box.maxZ),
+ Vec3d(box.maxX, box.minY, box.maxZ),
+ color,
+ dashLength,
+ gapLength
+ )
+ if (mode.check(hasDown, hasWest))
+ dashedLine(
+ Vec3d(box.minX, box.minY, box.minZ),
+ Vec3d(box.minX, box.minY, box.maxZ),
+ color,
+ dashLength,
+ gapLength
+ )
+ if (mode.check(hasDown, hasEast))
+ dashedLine(
+ Vec3d(box.maxX, box.minY, box.minZ),
+ Vec3d(box.maxX, box.minY, box.maxZ),
+ color,
+ dashLength,
+ gapLength
+ )
+
+ // Vertical edges
+ if (mode.check(hasWest, hasNorth))
+ dashedLine(
+ Vec3d(box.minX, box.maxY, box.minZ),
+ Vec3d(box.minX, box.minY, box.minZ),
+ color,
+ dashLength,
+ gapLength
+ )
+ if (mode.check(hasNorth, hasEast))
+ dashedLine(
+ Vec3d(box.maxX, box.maxY, box.minZ),
+ Vec3d(box.maxX, box.minY, box.minZ),
+ color,
+ dashLength,
+ gapLength
+ )
+ if (mode.check(hasEast, hasSouth))
+ dashedLine(
+ Vec3d(box.maxX, box.maxY, box.maxZ),
+ Vec3d(box.maxX, box.minY, box.maxZ),
+ color,
+ dashLength,
+ gapLength
+ )
+ if (mode.check(hasSouth, hasWest))
+ dashedLine(
+ Vec3d(box.minX, box.maxY, box.maxZ),
+ Vec3d(box.minX, box.minY, box.maxZ),
+ color,
+ dashLength,
+ gapLength
+ )
+ }
+
+ /** Draw a line between two world positions. */
+ fun line(start: Vec3d, end: Vec3d, color: Color, width: Float = lineWidth) {
+ val (x1, y1, z1) = toRelative(start.x, start.y, start.z)
+ val (x2, y2, z2) = toRelative(end.x, end.y, end.z)
+ lineWithWidth(x1, y1, z1, x2, y2, z2, color, width)
+ }
+
+ /** Draw a polyline through a list of points. */
+ fun polyline(points: List, color: Color, width: Float = lineWidth) {
+ if (points.size < 2) return
+ for (i in 0 until points.size - 1) {
+ line(points[i], points[i + 1], color, width)
+ }
+ }
+
+ /** Draw a dashed polyline through a list of points. */
+ fun dashedPolyline(
+ points: List,
+ color: Color,
+ dashLength: Double = 0.5,
+ gapLength: Double = 0.25,
+ width: Float = lineWidth
+ ) {
+ if (points.size < 2) return
+ for (i in 0 until points.size - 1) {
+ dashedLine(points[i], points[i + 1], color, dashLength, gapLength, width)
+ }
+ }
+
+ /**
+ * Draw a quadratic Bezier curve.
+ *
+ * @param p0 Start point
+ * @param p1 Control point
+ * @param p2 End point
+ * @param color Line color
+ * @param segments Number of line segments (higher = smoother)
+ */
+ fun quadraticBezier(
+ p0: Vec3d,
+ p1: Vec3d,
+ p2: Vec3d,
+ color: Color,
+ segments: Int = 16,
+ width: Float = lineWidth
+ ) {
+ val points = CurveUtils.quadraticBezierPoints(p0, p1, p2, segments)
+ polyline(points, color, width)
+ }
+
+ /**
+ * Draw a cubic Bezier curve.
+ *
+ * @param p0 Start point
+ * @param p1 First control point
+ * @param p2 Second control point
+ * @param p3 End point
+ * @param color Line color
+ * @param segments Number of line segments (higher = smoother)
+ */
+ fun cubicBezier(
+ p0: Vec3d,
+ p1: Vec3d,
+ p2: Vec3d,
+ p3: Vec3d,
+ color: Color,
+ segments: Int = 32,
+ width: Float = lineWidth
+ ) {
+ val points = CurveUtils.cubicBezierPoints(p0, p1, p2, p3, segments)
+ polyline(points, color, width)
+ }
+
+ /**
+ * Draw a Catmull-Rom spline that passes through all control points.
+ *
+ * @param controlPoints List of points the spline should pass through (minimum 4)
+ * @param color Line color
+ * @param segmentsPerSection Segments between each pair of control points
+ */
+ fun catmullRomSpline(
+ controlPoints: List,
+ color: Color,
+ segmentsPerSection: Int = 16,
+ width: Float = lineWidth
+ ) {
+ val points = CurveUtils.catmullRomSplinePoints(controlPoints, segmentsPerSection)
+ polyline(points, color, width)
+ }
+
+ /**
+ * Draw a smooth path through waypoints using Catmull-Rom splines. Handles endpoints
+ * naturally by mirroring.
+ *
+ * @param waypoints List of points to pass through (minimum 2)
+ * @param color Line color
+ * @param segmentsPerSection Smoothness (higher = smoother)
+ */
+ fun smoothPath(
+ waypoints: List,
+ color: Color,
+ segmentsPerSection: Int = 16,
+ width: Float = lineWidth
+ ) {
+ val points = CurveUtils.smoothPath(waypoints, segmentsPerSection)
+ polyline(points, color, width)
+ }
+
+ /** Draw a dashed Bezier curve. */
+ fun dashedCubicBezier(
+ p0: Vec3d,
+ p1: Vec3d,
+ p2: Vec3d,
+ p3: Vec3d,
+ color: Color,
+ segments: Int = 32,
+ dashLength: Double = 0.5,
+ gapLength: Double = 0.25,
+ width: Float = lineWidth
+ ) {
+ val points = CurveUtils.cubicBezierPoints(p0, p1, p2, p3, segments)
+ dashedPolyline(points, color, dashLength, gapLength, width)
+ }
+
+ /** Draw a dashed smooth path. */
+ fun dashedSmoothPath(
+ waypoints: List,
+ color: Color,
+ segmentsPerSection: Int = 16,
+ dashLength: Double = 0.5,
+ gapLength: Double = 0.25,
+ width: Float = lineWidth
+ ) {
+ val points = CurveUtils.smoothPath(waypoints, segmentsPerSection)
+ dashedPolyline(points, color, dashLength, gapLength, width)
+ }
+
+ /**
+ * Draw a circle in a plane.
+ *
+ * @param center Center of the circle
+ * @param radius Radius of the circle
+ * @param normal Normal vector of the plane (determines orientation)
+ * @param color Line color
+ * @param segments Number of segments
+ */
+ fun circle(
+ center: Vec3d,
+ radius: Double,
+ normal: Vec3d = Vec3d(0.0, 1.0, 0.0),
+ color: Color,
+ segments: Int = 32,
+ width: Float = lineWidth
+ ) {
+ // Create basis vectors perpendicular to normal
+ val up =
+ if (kotlin.math.abs(normal.y) < 0.99) Vec3d(0.0, 1.0, 0.0)
+ else Vec3d(1.0, 0.0, 0.0)
+ val u = normal.crossProduct(up).normalize()
+ val v = u.crossProduct(normal).normalize()
+
+ val points =
+ (0..segments).map { i ->
+ val angle = 2.0 * Math.PI * i / segments
+ val x = kotlin.math.cos(angle) * radius
+ val y = kotlin.math.sin(angle) * radius
+ center.add(u.multiply(x)).add(v.multiply(y))
+ }
+
+ polyline(points, color, width)
+ }
+
+ private fun lineWithWidth(
+ x1: Float,
+ y1: Float,
+ z1: Float,
+ x2: Float,
+ y2: Float,
+ z2: Float,
+ color: Color,
+ width: Float
+ ) {
+ val dx = x2 - x1
+ val dy = y2 - y1
+ val dz = z2 - z1
+ collector.addEdgeVertex(x1, y1, z1, color, dx, dy, dz, width)
+ collector.addEdgeVertex(x1, y1, z1, color, dx, dy, dz, width)
+ collector.addEdgeVertex(x2, y2, z2, color, dx, dy, dz, width)
+ collector.addEdgeVertex(x2, y2, z2, color, dx, dy, dz, width)
+ }
+}
diff --git a/src/main/kotlin/com/lambda/graphics/mc/RegionVertexCollector.kt b/src/main/kotlin/com/lambda/graphics/mc/RegionVertexCollector.kt
new file mode 100644
index 000000000..c82347817
--- /dev/null
+++ b/src/main/kotlin/com/lambda/graphics/mc/RegionVertexCollector.kt
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.graphics.mc
+
+import com.mojang.blaze3d.buffers.GpuBuffer
+import com.mojang.blaze3d.systems.RenderSystem
+import com.mojang.blaze3d.vertex.VertexFormat
+import net.minecraft.client.render.BufferBuilder
+import net.minecraft.client.render.VertexFormats
+import net.minecraft.client.util.BufferAllocator
+import java.awt.Color
+import java.util.concurrent.ConcurrentLinkedDeque
+
+/**
+ * Thread-safe vertex collector for region-based rendering.
+ *
+ * Collects vertex data on background threads using thread-safe collections, then writes to MC's
+ * BufferBuilder and uploads on the main thread.
+ */
+class RegionVertexCollector {
+ val faceVertices = ConcurrentLinkedDeque()
+ val edgeVertices = ConcurrentLinkedDeque()
+
+ /** Face vertex data (position + color). */
+ data class FaceVertex(
+ val x: Float,
+ val y: Float,
+ val z: Float,
+ val r: Int,
+ val g: Int,
+ val b: Int,
+ val a: Int
+ )
+
+ /** Edge vertex data (position + color + normal + line width). */
+ data class EdgeVertex(
+ val x: Float,
+ val y: Float,
+ val z: Float,
+ val r: Int,
+ val g: Int,
+ val b: Int,
+ val a: Int,
+ val nx: Float,
+ val ny: Float,
+ val nz: Float,
+ val lineWidth: Float
+ )
+
+ /** Add a face vertex. */
+ fun addFaceVertex(x: Float, y: Float, z: Float, color: Color) {
+ faceVertices.add(FaceVertex(x, y, z, color.red, color.green, color.blue, color.alpha))
+ }
+
+ /** Add an edge vertex. */
+ fun addEdgeVertex(
+ x: Float,
+ y: Float,
+ z: Float,
+ color: Color,
+ nx: Float,
+ ny: Float,
+ nz: Float,
+ lineWidth: Float
+ ) {
+ edgeVertices.add(
+ EdgeVertex(x, y, z, color.red, color.green, color.blue, color.alpha, nx, ny, nz, lineWidth)
+ )
+ }
+
+ /**
+ * Upload collected data to GPU buffers. Must be called on the main/render thread.
+ *
+ * @return Pair of (faceBuffer, edgeBuffer) and their index counts, or null if no data
+ */
+ fun upload(): UploadResult {
+ val faces = uploadFaces()
+ val edges = uploadEdges()
+ return UploadResult(faces, edges)
+ }
+
+ private fun uploadFaces(): BufferResult {
+ if (faceVertices.isEmpty()) return BufferResult(null, 0)
+
+ val vertices = faceVertices.toList()
+ faceVertices.clear()
+
+ var result: BufferResult? = null
+ BufferAllocator(vertices.size * 16).use { allocator ->
+ val builder =
+ BufferBuilder(
+ allocator,
+ VertexFormat.DrawMode.QUADS,
+ VertexFormats.POSITION_COLOR
+ )
+
+ vertices.forEach { v -> builder.vertex(v.x, v.y, v.z).color(v.r, v.g, v.b, v.a) }
+
+ builder.endNullable()?.let { built ->
+ val gpuDevice = RenderSystem.getDevice()
+ val buffer =
+ gpuDevice.createBuffer(
+ { "Lambda ESP Face Buffer" },
+ GpuBuffer.USAGE_VERTEX,
+ built.buffer
+ )
+ result = BufferResult(buffer, built.drawParameters.indexCount())
+ built.close()
+ }
+ }
+ return result ?: BufferResult(null, 0)
+ }
+
+ private fun uploadEdges(): BufferResult {
+ if (edgeVertices.isEmpty()) return BufferResult(null, 0)
+
+ val vertices = edgeVertices.toList()
+ edgeVertices.clear()
+
+ var result: BufferResult? = null
+ BufferAllocator(vertices.size * 32).use { allocator ->
+ val builder =
+ BufferBuilder(
+ allocator,
+ VertexFormat.DrawMode.QUADS,
+ VertexFormats.POSITION_COLOR_NORMAL_LINE_WIDTH
+ )
+
+ vertices.forEach { v ->
+ builder.vertex(v.x, v.y, v.z)
+ .color(v.r, v.g, v.b, v.a)
+ .normal(v.nx, v.ny, v.nz)
+ .lineWidth(v.lineWidth)
+ }
+
+ builder.endNullable()?.let { built ->
+ val gpuDevice = RenderSystem.getDevice()
+ val buffer =
+ gpuDevice.createBuffer(
+ { "Lambda ESP Edge Buffer" },
+ GpuBuffer.USAGE_VERTEX,
+ built.buffer
+ )
+ result = BufferResult(buffer, built.drawParameters.indexCount())
+ built.close()
+ }
+ }
+ return result ?: BufferResult(null, 0)
+ }
+
+ data class BufferResult(val buffer: GpuBuffer?, val indexCount: Int)
+ data class UploadResult(val faces: BufferResult?, val edges: BufferResult?)
+}
diff --git a/src/main/kotlin/com/lambda/graphics/mc/RenderRegion.kt b/src/main/kotlin/com/lambda/graphics/mc/RenderRegion.kt
new file mode 100644
index 000000000..6687aa44e
--- /dev/null
+++ b/src/main/kotlin/com/lambda/graphics/mc/RenderRegion.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.graphics.mc
+
+import net.minecraft.util.math.Vec3d
+import org.joml.Vector3f
+
+/**
+ * A render region represents a chunk-sized area in the world where vertices are stored relative to
+ * the region's origin. This solves floating-point precision issues at large world coordinates.
+ *
+ * @param originX The X coordinate of the region's origin (typically chunk corner)
+ * @param originY The Y coordinate of the region's origin
+ * @param originZ The Z coordinate of the region's origin
+ */
+class RenderRegion(val originX: Int, val originY: Int, val originZ: Int) {
+ /**
+ * Compute the camera-relative offset for this region. This is done in double precision to
+ * maintain accuracy at large coordinates.
+ *
+ * @param cameraPos The camera's world position (double precision)
+ * @return The offset from camera to region origin (small float, high precision)
+ */
+ fun computeCameraRelativeOffset(cameraPos: Vec3d): Vector3f {
+ val offsetX = originX.toDouble() - cameraPos.x
+ val offsetY = originY.toDouble() - cameraPos.y
+ val offsetZ = originZ.toDouble() - cameraPos.z
+ return Vector3f(offsetX.toFloat(), offsetY.toFloat(), offsetZ.toFloat())
+ }
+
+ companion object {
+ /** Standard size of a render region (matches Minecraft chunk size). */
+ const val REGION_SIZE = 16
+
+ /**
+ * Create a region for a chunk position.
+ *
+ * @param chunkX Chunk X coordinate
+ * @param chunkZ Chunk Z coordinate
+ * @param bottomY World bottom Y coordinate (typically -64)
+ */
+ fun forChunk(chunkX: Int, chunkZ: Int, bottomY: Int) =
+ RenderRegion(chunkX * 16, bottomY, chunkZ * 16)
+ }
+}
diff --git a/src/main/kotlin/com/lambda/graphics/mc/TransientRegionESP.kt b/src/main/kotlin/com/lambda/graphics/mc/TransientRegionESP.kt
new file mode 100644
index 000000000..bc1cd812c
--- /dev/null
+++ b/src/main/kotlin/com/lambda/graphics/mc/TransientRegionESP.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.graphics.mc
+
+import com.lambda.graphics.esp.RegionESP
+import com.lambda.graphics.esp.ShapeScope
+import java.util.concurrent.ConcurrentHashMap
+import kotlin.math.floor
+
+/**
+ * Modern replacement for the legacy Treed system. Handles geometry that is cleared and rebuilt
+ * every tick. Uses region-based rendering for precision.
+ */
+class TransientRegionESP(name: String, depthTest: Boolean = false) : RegionESP(name, depthTest) {
+ private val builders = ConcurrentHashMap()
+
+ /** Get or create a builder for a specific region. */
+ override fun shapes(x: Double, y: Double, z: Double, block: ShapeScope.() -> Unit) {
+ val key = getRegionKey(x, y, z)
+ val scope =
+ builders.getOrPut(key) {
+ val size = RenderRegion.REGION_SIZE
+ val rx = (size * floor(x / size)).toInt()
+ val ry = (size * floor(y / size)).toInt()
+ val rz = (size * floor(z / size)).toInt()
+ ShapeScope(RenderRegion(rx, ry, rz))
+ }
+ scope.apply(block)
+ }
+
+ /** Clear all current builders. Call this at the end of every tick. */
+ override fun clear() {
+ builders.clear()
+ }
+
+ /** Upload collected geometry to GPU. Must be called on main thread. */
+ override fun upload() {
+ val activeKeys = builders.keys().asSequence().toSet()
+
+ builders.forEach { (key, scope) ->
+ val renderer = renderers.getOrPut(key) { RegionRenderer(scope.region) }
+ renderer.upload(scope.builder.collector)
+ }
+
+ renderers.forEach { (key, renderer) ->
+ if (key !in activeKeys) {
+ renderer.clearData()
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/com/lambda/graphics/mc/WorldTextRenderer.kt b/src/main/kotlin/com/lambda/graphics/mc/WorldTextRenderer.kt
new file mode 100644
index 000000000..9aa34034d
--- /dev/null
+++ b/src/main/kotlin/com/lambda/graphics/mc/WorldTextRenderer.kt
@@ -0,0 +1,307 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.graphics.mc
+
+import com.lambda.Lambda.mc
+import net.minecraft.client.font.TextRenderer
+import net.minecraft.client.render.LightmapTextureManager
+import net.minecraft.client.util.math.MatrixStack
+import net.minecraft.text.Text
+import net.minecraft.util.math.Vec3d
+import java.awt.Color
+
+/**
+ * Utility for rendering text in 3D world space.
+ *
+ * Uses Minecraft's TextRenderer to draw text that faces the camera (billboard style) at any world
+ * position. Handles Unicode, formatting codes, and integrates with MC's rendering system.
+ *
+ * Usage:
+ * ```kotlin
+ * // In your render event
+ * WorldTextRenderer.drawText(
+ * pos = entity.pos.add(0.0, entity.height + 0.5, 0.0),
+ * text = entity.name,
+ * color = Color.WHITE,
+ * scale = 0.025f
+ * )
+ * ```
+ */
+object WorldTextRenderer {
+
+ /** Default scale for world text (MC uses 0.025f for name tags) */
+ const val DEFAULT_SCALE = 0.025f
+
+ /** Maximum light level for full brightness */
+ private const val FULL_BRIGHT = LightmapTextureManager.MAX_LIGHT_COORDINATE
+
+ /**
+ * Draw text at a world position, facing the camera.
+ *
+ * @param pos World position for the text
+ * @param text The text to render
+ * @param color Text color (ARGB)
+ * @param scale Text scale (0.025f is default name tag size)
+ * @param shadow Whether to draw drop shadow
+ * @param seeThrough Whether text should be visible through blocks
+ * @param centered Whether to center the text horizontally
+ * @param backgroundColor Background color (0 for no background)
+ * @param light Light level (uses full bright by default)
+ */
+ fun drawText(
+ pos: Vec3d,
+ text: Text,
+ color: Color = Color.WHITE,
+ scale: Float = DEFAULT_SCALE,
+ shadow: Boolean = true,
+ seeThrough: Boolean = false,
+ centered: Boolean = true,
+ backgroundColor: Int = 0,
+ light: Int = FULL_BRIGHT
+ ) {
+ val client = mc
+ val camera = client.gameRenderer?.camera ?: return
+ val textRenderer = client.textRenderer ?: return
+ val immediate = client.bufferBuilders?.entityVertexConsumers ?: return
+
+ val cameraPos = camera.pos
+
+ val matrices = MatrixStack()
+ matrices.push()
+
+ // Translate to world position relative to camera
+ matrices.translate(pos.x - cameraPos.x, pos.y - cameraPos.y, pos.z - cameraPos.z)
+
+ // Billboard - face camera using camera rotation directly (same as MC's LabelCommandRenderer)
+ matrices.multiply(camera.rotation)
+
+ // Scale with negative Y to flip text vertically (matches MC's 0.025, -0.025, 0.025)
+ matrices.scale(scale, -scale, scale)
+
+ // Calculate text position
+ val textWidth = textRenderer.getWidth(text)
+ val x = if (centered) -textWidth / 2f else 0f
+
+ val layerType =
+ if (seeThrough) TextRenderer.TextLayerType.SEE_THROUGH
+ else TextRenderer.TextLayerType.NORMAL
+
+ // Draw text
+ textRenderer.draw(
+ text,
+ x,
+ 0f,
+ color.rgb,
+ shadow,
+ matrices.peek().positionMatrix,
+ immediate,
+ layerType,
+ backgroundColor,
+ light
+ )
+
+ matrices.pop()
+
+ // Flush immediately for world rendering
+ immediate.draw()
+ }
+
+ /**
+ * Draw text at a world position with an outline effect.
+ *
+ * @param pos World position for the text
+ * @param text The text to render
+ * @param color Text color
+ * @param outlineColor Outline color
+ * @param scale Text scale
+ * @param centered Whether to center the text horizontally
+ * @param light Light level
+ */
+ fun drawTextWithOutline(
+ pos: Vec3d,
+ text: Text,
+ color: Color = Color.WHITE,
+ outlineColor: Color = Color.BLACK,
+ scale: Float = DEFAULT_SCALE,
+ centered: Boolean = true,
+ light: Int = FULL_BRIGHT
+ ) {
+ val client = mc
+ val camera = client.gameRenderer?.camera ?: return
+ val textRenderer = client.textRenderer ?: return
+ val immediate = client.bufferBuilders?.entityVertexConsumers ?: return
+
+ val cameraPos = camera.pos
+
+ val matrices = MatrixStack()
+ matrices.push()
+
+ matrices.translate(pos.x - cameraPos.x, pos.y - cameraPos.y, pos.z - cameraPos.z)
+
+ // Billboard - face camera using camera rotation directly (same as MC's LabelCommandRenderer)
+ matrices.multiply(camera.rotation)
+ matrices.scale(scale, -scale, scale)
+
+ val textWidth = textRenderer.getWidth(text)
+ val x = if (centered) -textWidth / 2f else 0f
+
+ textRenderer.drawWithOutline(
+ text.asOrderedText(),
+ x,
+ 0f,
+ color.rgb,
+ outlineColor.rgb,
+ matrices.peek().positionMatrix,
+ immediate,
+ light
+ )
+
+ matrices.pop()
+ immediate.draw()
+ }
+
+ /** Draw a simple string at a world position. */
+ fun drawString(
+ pos: Vec3d,
+ text: String,
+ color: Color = Color.WHITE,
+ scale: Float = DEFAULT_SCALE,
+ shadow: Boolean = true,
+ seeThrough: Boolean = false,
+ centered: Boolean = true
+ ) {
+ drawText(pos, Text.literal(text), color, scale, shadow, seeThrough, centered)
+ }
+
+ /**
+ * Draw multiple lines of text stacked vertically.
+ *
+ * @param pos World position for the top line
+ * @param lines List of text lines to render
+ * @param color Text color
+ * @param scale Text scale
+ * @param lineSpacing Spacing between lines in scaled units (default 10)
+ */
+ fun drawMultilineText(
+ pos: Vec3d,
+ lines: List,
+ color: Color = Color.WHITE,
+ scale: Float = DEFAULT_SCALE,
+ lineSpacing: Float = 10f,
+ shadow: Boolean = true,
+ seeThrough: Boolean = false,
+ centered: Boolean = true
+ ) {
+ val client = mc
+ val camera = client.gameRenderer?.camera ?: return
+ val textRenderer = client.textRenderer ?: return
+ val immediate = client.bufferBuilders?.entityVertexConsumers ?: return
+
+ val cameraPos = camera.pos
+
+ val matrices = MatrixStack()
+ matrices.push()
+
+ matrices.translate(pos.x - cameraPos.x, pos.y - cameraPos.y, pos.z - cameraPos.z)
+
+ // Billboard - face camera using camera rotation directly (same as MC's LabelCommandRenderer)
+ matrices.multiply(camera.rotation)
+ matrices.scale(scale, -scale, scale)
+
+ val layerType =
+ if (seeThrough) TextRenderer.TextLayerType.SEE_THROUGH
+ else TextRenderer.TextLayerType.NORMAL
+
+ lines.forEachIndexed { index, text ->
+ val textWidth = textRenderer.getWidth(text)
+ val x = if (centered) -textWidth / 2f else 0f
+ val y = index * lineSpacing
+
+ textRenderer.draw(
+ text,
+ x,
+ y,
+ color.rgb,
+ shadow,
+ matrices.peek().positionMatrix,
+ immediate,
+ layerType,
+ 0,
+ FULL_BRIGHT
+ )
+ }
+
+ matrices.pop()
+ immediate.draw()
+ }
+
+ /**
+ * Draw text with a background box.
+ *
+ * @param pos World position
+ * @param text Text to render
+ * @param textColor Text color
+ * @param backgroundColor Background color (with alpha)
+ * @param scale Text scale
+ * @param padding Padding around text in pixels
+ */
+ fun drawTextWithBackground(
+ pos: Vec3d,
+ text: Text,
+ textColor: Color = Color.WHITE,
+ backgroundColor: Color = Color(0, 0, 0, 128),
+ scale: Float = DEFAULT_SCALE,
+ padding: Int = 2,
+ shadow: Boolean = false,
+ seeThrough: Boolean = false,
+ centered: Boolean = true
+ ) {
+ val client = mc
+ client.textRenderer ?: return
+
+ // Calculate background color as ARGB int
+ val bgColorInt =
+ (backgroundColor.alpha shl 24) or
+ (backgroundColor.red shl 16) or
+ (backgroundColor.green shl 8) or
+ backgroundColor.blue
+
+ drawText(
+ pos = pos,
+ text = text,
+ color = textColor,
+ scale = scale,
+ shadow = shadow,
+ seeThrough = seeThrough,
+ centered = centered,
+ backgroundColor = bgColorInt
+ )
+ }
+
+ /** Calculate the width of text in world units at a given scale. */
+ fun getTextWidth(text: Text, scale: Float = DEFAULT_SCALE): Float {
+ val textRenderer = mc.textRenderer ?: return 0f
+ return textRenderer.getWidth(text) * scale
+ }
+
+ /** Calculate the height of text in world units at a given scale. */
+ fun getTextHeight(scale: Float = DEFAULT_SCALE): Float {
+ val textRenderer = mc.textRenderer ?: return 0f
+ return textRenderer.fontHeight * scale
+ }
+}
diff --git a/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt b/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt
deleted file mode 100644
index 03cf28c85..000000000
--- a/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright 2025 Lambda
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package com.lambda.graphics.renderer.esp
-
-import com.lambda.event.events.RenderEvent
-import com.lambda.event.events.TickEvent
-import com.lambda.event.events.WorldEvent
-import com.lambda.event.listener.SafeListener.Companion.listen
-import com.lambda.event.listener.SafeListener.Companion.listenConcurrently
-import com.lambda.module.Module
-import com.lambda.module.modules.client.StyleEditor
-import com.lambda.threading.awaitMainThread
-import com.lambda.util.world.FastVector
-import com.lambda.util.world.fastVectorOf
-import net.minecraft.world.World
-import net.minecraft.world.chunk.WorldChunk
-import java.util.concurrent.ConcurrentHashMap
-import java.util.concurrent.ConcurrentLinkedDeque
-
-class ChunkedESP private constructor(
- owner: Module,
- private val update: ShapeBuilder.(World, FastVector) -> Unit
-) {
- private val rendererMap = ConcurrentHashMap()
- private val WorldChunk.renderer
- get() = rendererMap.getOrPut(pos.toLong()) { EspChunk(this, this@ChunkedESP) }
-
- private val uploadQueue = ConcurrentLinkedDeque<() -> Unit>()
- private val rebuildQueue = ConcurrentLinkedDeque()
-
- private var ticks = 0
-
- fun rebuild() {
- rebuildQueue.clear()
- rebuildQueue.addAll(rendererMap.values)
- }
-
- init {
- listen { event ->
- world.getWorldChunk(event.pos).renderer.notifyChunks()
- }
-
- listen { event ->
- event.chunk.renderer.notifyChunks()
- }
-
- listen {
- val pos = it.chunk.pos.toLong()
- rendererMap.remove(pos)?.notifyChunks()
- }
-
- owner.listenConcurrently {
- val polls = minOf(StyleEditor.rebuildsPerTick, rebuildQueue.size)
- repeat(polls) { rebuildQueue.poll()?.rebuild() }
- }
-
- owner.listen {
- val polls = minOf(StyleEditor.uploadsPerTick, uploadQueue.size)
- repeat(polls) { uploadQueue.poll()?.invoke() }
- }
-
- owner.listen {
- rendererMap.values.forEach { it.renderer.render() }
- }
- }
-
- companion object {
- fun Module.newChunkedESP(
- update: ShapeBuilder.(World, FastVector) -> Unit
- ) = ChunkedESP(this@newChunkedESP, update)
- }
-
- private class EspChunk(val chunk: WorldChunk, val owner: ChunkedESP) {
- var renderer = Treed(static = true)
- private val builder: ShapeBuilder
- get() = ShapeBuilder(renderer.faceBuilder, renderer.edgeBuilder)
-
- /*val neighbors = listOf(1 to 0, 0 to 1, -1 to 0, 0 to -1)
- .map { ChunkPos(chunk.pos.x + it.first, chunk.pos.z + it.second) }*/
-
- fun notifyChunks() {
- owner.rendererMap[chunk.pos.toLong()]?.let {
- if (!owner.rebuildQueue.contains(it))
- owner.rebuildQueue.add(it)
- }
- }
-
- suspend fun rebuild() {
- renderer.clear()
- renderer = awaitMainThread { Treed(static = true) }
-
- for (x in chunk.pos.startX..chunk.pos.endX)
- for (z in chunk.pos.startZ..chunk.pos.endZ)
- for (y in chunk.bottomY..chunk.height)
- owner.update(builder, chunk.world, fastVectorOf(x, y, z))
-
- owner.uploadQueue.add { renderer.upload() }
- }
- }
-}
diff --git a/src/main/kotlin/com/lambda/graphics/renderer/esp/ShapeDsl.kt b/src/main/kotlin/com/lambda/graphics/renderer/esp/ShapeDsl.kt
deleted file mode 100644
index eaf223370..000000000
--- a/src/main/kotlin/com/lambda/graphics/renderer/esp/ShapeDsl.kt
+++ /dev/null
@@ -1,360 +0,0 @@
-/*
- * Copyright 2025 Lambda
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package com.lambda.graphics.renderer.esp
-
-import com.lambda.graphics.pipeline.VertexBuilder
-import com.lambda.graphics.renderer.esp.DirectionMask.hasDirection
-import com.lambda.threading.runSafe
-import com.lambda.util.BlockUtils.blockState
-import com.lambda.util.extension.max
-import com.lambda.util.extension.min
-import com.lambda.util.extension.outlineShape
-import net.minecraft.block.BlockState
-import net.minecraft.block.entity.BlockEntity
-import net.minecraft.entity.Entity
-import net.minecraft.util.math.BlockPos
-import net.minecraft.util.math.Box
-import net.minecraft.util.shape.VoxelShape
-import java.awt.Color
-
-@DslMarker
-annotation class ShapeDsl
-
-class ShapeBuilder(
- val faces: VertexBuilder = VertexBuilder(),
- val edges: VertexBuilder = VertexBuilder(),
-) {
- @ShapeDsl
- fun filled(
- box : DynamicAABB,
- color : Color,
- sides : Int = DirectionMask.ALL,
- ) = faces.apply {
- val boxes = box.pair ?: return@apply
-
- val pos11 = boxes.first.min
- val pos12 = boxes.first.max
- val pos21 = boxes.second.min
- val pos22 = boxes.second.max
-
- val blb by lazy { vertex { vec3(pos11.x, pos11.y, pos11.z).vec3(pos21.x, pos21.y, pos21.z).color(color) } }
- val blf by lazy { vertex { vec3(pos11.x, pos11.y, pos12.z).vec3(pos21.x, pos21.y, pos22.z).color(color) } }
- val brb by lazy { vertex { vec3(pos12.x, pos11.y, pos11.z).vec3(pos22.x, pos21.y, pos21.z).color(color) } }
- val brf by lazy { vertex { vec3(pos12.x, pos11.y, pos12.z).vec3(pos22.x, pos21.y, pos22.z).color(color) } }
- val tlb by lazy { vertex { vec3(pos11.x, pos12.y, pos11.z).vec3(pos21.x, pos22.y, pos21.z).color(color) } }
- val tlf by lazy { vertex { vec3(pos11.x, pos12.y, pos12.z).vec3(pos21.x, pos22.y, pos22.z).color(color) } }
- val trb by lazy { vertex { vec3(pos12.x, pos12.y, pos11.z).vec3(pos22.x, pos22.y, pos21.z).color(color) } }
- val trf by lazy { vertex { vec3(pos12.x, pos12.y, pos12.z).vec3(pos22.x, pos22.y, pos22.z).color(color) } }
-
- if (sides.hasDirection(DirectionMask.EAST)) buildQuad(brb, trb, trf, brf)
- if (sides.hasDirection(DirectionMask.WEST)) buildQuad(blb, blf, tlf, tlb)
- if (sides.hasDirection(DirectionMask.UP)) buildQuad(tlb, tlf, trf, trb)
- if (sides.hasDirection(DirectionMask.DOWN)) buildQuad(blb, brb, brf, blf)
- if (sides.hasDirection(DirectionMask.SOUTH)) buildQuad(blf, brf, trf, tlf)
- if (sides.hasDirection(DirectionMask.NORTH)) buildQuad(blb, tlb, trb, brb)
- }
-
- @ShapeDsl
- fun filled(
- box : Box,
- bottomColor : Color,
- topColor : Color = bottomColor,
- sides : Int = DirectionMask.ALL
- ) = faces.apply {
- val pos1 = box.min
- val pos2 = box.max
-
- val blb by lazy { vertex { vec3(pos1.x, pos1.y, pos1.z).color(bottomColor) } }
- val blf by lazy { vertex { vec3(pos1.x, pos1.y, pos2.z).color(bottomColor) } }
- val brb by lazy { vertex { vec3(pos2.x, pos1.y, pos1.z).color(bottomColor) } }
- val brf by lazy { vertex { vec3(pos2.x, pos1.y, pos2.z).color(bottomColor) } }
-
- val tlb by lazy { vertex { vec3(pos1.x, pos2.y, pos1.z).color(topColor) } }
- val tlf by lazy { vertex { vec3(pos1.x, pos2.y, pos2.z).color(topColor) } }
- val trb by lazy { vertex { vec3(pos2.x, pos2.y, pos1.z).color(topColor) } }
- val trf by lazy { vertex { vec3(pos2.x, pos2.y, pos2.z).color(topColor) } }
-
- if (sides.hasDirection(DirectionMask.EAST)) buildQuad(brb, trb, trf, brf)
- if (sides.hasDirection(DirectionMask.WEST)) buildQuad(blb, blf, tlf, tlb)
- if (sides.hasDirection(DirectionMask.UP)) buildQuad(tlb, tlf, trf, trb)
- if (sides.hasDirection(DirectionMask.DOWN)) buildQuad(blb, brb, brf, blf)
- if (sides.hasDirection(DirectionMask.SOUTH)) buildQuad(blf, brf, trf, tlf)
- if (sides.hasDirection(DirectionMask.NORTH)) buildQuad(blb, tlb, trb, brb)
- }
-
- @ShapeDsl
- fun filled(
- pos : BlockPos,
- state : BlockState,
- color : Color,
- sides : Int = DirectionMask.ALL,
- ) = runSafe { faces.apply {
- val shape = outlineShape(state, pos)
- if (shape.isEmpty) {
- filled(Box(pos), color, sides)
- } else {
- filled(shape, color, sides)
- }
- } }
-
- @ShapeDsl
- fun filled(
- pos : BlockPos,
- color : Color,
- sides : Int = DirectionMask.ALL,
- ) = runSafe { faces.apply { filled(pos, blockState(pos), color, sides) } }
-
- @ShapeDsl
- fun filled(
- pos : BlockPos,
- entity : BlockEntity,
- color : Color,
- sides : Int = DirectionMask.ALL,
- ) = filled(pos, entity.cachedState, color, sides)
-
- @ShapeDsl
- fun filled(
- shape: VoxelShape,
- color: Color,
- sides: Int = DirectionMask.ALL,
- ) {
- shape.boundingBoxes
- .forEach { filled(it, color, color, sides) }
- }
-
- @ShapeDsl
- fun filled(
- box : Box,
- color : Color,
- sides : Int = DirectionMask.ALL,
- ) = filled(box, color, color, sides)
-
- @ShapeDsl
- fun outline(
- box : DynamicAABB,
- color : Color,
- sides : Int = DirectionMask.ALL,
- mode : DirectionMask.OutlineMode = DirectionMask.OutlineMode.Or,
- ) = edges.apply {
- val boxes = box.pair ?: return@apply
-
- val pos11 = boxes.first.min
- val pos12 = boxes.first.max
- val pos21 = boxes.second.min
- val pos22 = boxes.second.max
-
- val blb by lazy { vertex { vec3(pos11.x, pos11.y, pos11.z).vec3(pos21.x, pos21.y, pos21.z).color(color) } }
- val blf by lazy { vertex { vec3(pos11.x, pos11.y, pos12.z).vec3(pos21.x, pos21.y, pos22.z).color(color) } }
- val brb by lazy { vertex { vec3(pos12.x, pos11.y, pos11.z).vec3(pos22.x, pos21.y, pos21.z).color(color) } }
- val brf by lazy { vertex { vec3(pos12.x, pos11.y, pos12.z).vec3(pos22.x, pos21.y, pos22.z).color(color) } }
- val tlb by lazy { vertex { vec3(pos11.x, pos12.y, pos11.z).vec3(pos21.x, pos22.y, pos21.z).color(color) } }
- val tlf by lazy { vertex { vec3(pos11.x, pos12.y, pos12.z).vec3(pos21.x, pos22.y, pos22.z).color(color) } }
- val trb by lazy { vertex { vec3(pos12.x, pos12.y, pos11.z).vec3(pos22.x, pos22.y, pos21.z).color(color) } }
- val trf by lazy { vertex { vec3(pos12.x, pos12.y, pos12.z).vec3(pos22.x, pos22.y, pos22.z).color(color) } }
-
- val hasEast = sides.hasDirection(DirectionMask.EAST)
- val hasWest = sides.hasDirection(DirectionMask.WEST)
- val hasUp = sides.hasDirection(DirectionMask.UP)
- val hasDown = sides.hasDirection(DirectionMask.DOWN)
- val hasSouth = sides.hasDirection(DirectionMask.SOUTH)
- val hasNorth = sides.hasDirection(DirectionMask.NORTH)
-
- if (mode.check(hasUp, hasNorth)) buildLine(tlb, trb)
- if (mode.check(hasUp, hasSouth)) buildLine(tlf, trf)
- if (mode.check(hasUp, hasWest)) buildLine(tlb, tlf)
- if (mode.check(hasUp, hasEast)) buildLine(trf, trb)
-
- if (mode.check(hasDown, hasNorth)) buildLine(blb, brb)
- if (mode.check(hasDown, hasSouth)) buildLine(blf, brf)
- if (mode.check(hasDown, hasWest)) buildLine(blb, blf)
- if (mode.check(hasDown, hasEast)) buildLine(brb, brf)
-
- if (mode.check(hasWest, hasNorth)) buildLine(tlb, blb)
- if (mode.check(hasNorth, hasEast)) buildLine(trb, brb)
- if (mode.check(hasEast, hasSouth)) buildLine(trf, brf)
- if (mode.check(hasSouth, hasWest)) buildLine(tlf, blf)
- }
-
- @ShapeDsl
- fun outline(
- box : Box,
- bottomColor : Color,
- topColor : Color = bottomColor,
- sides : Int = DirectionMask.ALL,
- mode : DirectionMask.OutlineMode = DirectionMask.OutlineMode.Or,
- ) = edges.apply {
- val pos1 = box.min
- val pos2 = box.max
-
- val blb by lazy { vertex { vec3(pos1.x, pos1.y, pos1.z).color(bottomColor) } }
- val blf by lazy { vertex { vec3(pos1.x, pos1.y, pos2.z).color(bottomColor) } }
- val brb by lazy { vertex { vec3(pos2.x, pos1.y, pos1.z).color(bottomColor) } }
- val brf by lazy { vertex { vec3(pos2.x, pos1.y, pos2.z).color(bottomColor) } }
- val tlb by lazy { vertex { vec3(pos1.x, pos2.y, pos1.z).color(topColor) } }
- val tlf by lazy { vertex { vec3(pos1.x, pos2.y, pos2.z).color(topColor) } }
- val trb by lazy { vertex { vec3(pos2.x, pos2.y, pos1.z).color(topColor) } }
- val trf by lazy { vertex { vec3(pos2.x, pos2.y, pos2.z).color(topColor) } }
-
- val hasEast = sides.hasDirection(DirectionMask.EAST)
- val hasWest = sides.hasDirection(DirectionMask.WEST)
- val hasUp = sides.hasDirection(DirectionMask.UP)
- val hasDown = sides.hasDirection(DirectionMask.DOWN)
- val hasSouth = sides.hasDirection(DirectionMask.SOUTH)
- val hasNorth = sides.hasDirection(DirectionMask.NORTH)
-
- if (mode.check(hasUp, hasNorth)) buildLine(tlb, trb)
- if (mode.check(hasUp, hasSouth)) buildLine(tlf, trf)
- if (mode.check(hasUp, hasWest)) buildLine(tlb, tlf)
- if (mode.check(hasUp, hasEast)) buildLine(trf, trb)
-
- if (mode.check(hasDown, hasNorth)) buildLine(blb, brb)
- if (mode.check(hasDown, hasSouth)) buildLine(blf, brf)
- if (mode.check(hasDown, hasWest)) buildLine(blb, blf)
- if (mode.check(hasDown, hasEast)) buildLine(brb, brf)
-
- if (mode.check(hasWest, hasNorth)) buildLine(tlb, blb)
- if (mode.check(hasNorth, hasEast)) buildLine(trb, brb)
- if (mode.check(hasEast, hasSouth)) buildLine(trf, brf)
- if (mode.check(hasSouth, hasWest)) buildLine(tlf, blf)
- }
-
- @ShapeDsl
- fun outline(
- pos : BlockPos,
- state : BlockState,
- color : Color,
- sides : Int = DirectionMask.ALL,
- mode : DirectionMask.OutlineMode = DirectionMask.OutlineMode.Or,
- ) = runSafe {
- val shape = outlineShape(state, pos)
- if (shape.isEmpty) {
- outline(Box(pos), color, sides, mode)
- } else {
- outline(shape, color, sides, mode)
- }
- }
-
- @ShapeDsl
- fun outline(
- pos : BlockPos,
- color : Color,
- sides : Int = DirectionMask.ALL,
- mode : DirectionMask.OutlineMode = DirectionMask.OutlineMode.Or,
- ) = runSafe { outline(pos, blockState(pos), color, sides, mode) }
-
- @ShapeDsl
- fun outline(
- pos : BlockPos,
- entity : BlockEntity,
- color : Color,
- sides : Int = DirectionMask.ALL,
- mode : DirectionMask.OutlineMode = DirectionMask.OutlineMode.Or,
- ) = runSafe { outline(pos, entity.cachedState, color, sides, mode) }
-
- @ShapeDsl
- fun outline(
- shape : VoxelShape,
- color : Color,
- sides : Int = DirectionMask.ALL,
- mode : DirectionMask.OutlineMode = DirectionMask.OutlineMode.Or,
- ) {
- shape.boundingBoxes
- .forEach { outline(it, color, sides, mode) }
- }
-
- @ShapeDsl
- fun outline(
- box : Box,
- color : Color,
- sides : Int = DirectionMask.ALL,
- mode : DirectionMask.OutlineMode = DirectionMask.OutlineMode.Or,
- ) {
- outline(box, color, color, sides, mode)
- }
-
- @ShapeDsl
- fun box(
- pos : BlockPos,
- state : BlockState,
- filled : Color,
- outline : Color,
- sides : Int = DirectionMask.ALL,
- mode : DirectionMask.OutlineMode = DirectionMask.OutlineMode.Or,
- ) = runSafe {
- filled(pos, state, filled, sides)
- outline(pos, state, outline, sides, mode)
- }
-
- @ShapeDsl
- fun box(
- pos : BlockPos,
- filled : Color,
- outline : Color,
- sides : Int = DirectionMask.ALL,
- mode : DirectionMask.OutlineMode = DirectionMask.OutlineMode.Or,
- ) = runSafe {
- filled(pos, filled, sides)
- outline(pos, outline, sides, mode)
- }
-
- @ShapeDsl
- fun box(
- box : DynamicAABB,
- filled : Color,
- outline : Color,
- sides : Int = DirectionMask.ALL,
- mode : DirectionMask.OutlineMode = DirectionMask.OutlineMode.Or,
- ) {
- filled(box, filled, sides)
- outline(box, outline, sides, mode)
- }
-
- @ShapeDsl
- fun box(
- box : Box,
- filled : Color,
- outline : Color,
- sides : Int = DirectionMask.ALL,
- mode : DirectionMask.OutlineMode = DirectionMask.OutlineMode.Or,
- ) {
- filled(box, filled, sides)
- outline(box, outline, sides, mode)
- }
-
- @ShapeDsl
- fun box(
- entity : BlockEntity,
- color : Color,
- sides : Int = DirectionMask.ALL,
- mode : DirectionMask.OutlineMode = DirectionMask.OutlineMode.Or,
- ) = runSafe {
- filled(entity.pos, entity, color, sides)
- outline(entity.pos, entity, color, sides, mode)
- }
-
- @ShapeDsl
- fun box(
- entity : Entity,
- color : Color,
- sides : Int = DirectionMask.ALL,
- mode : DirectionMask.OutlineMode = DirectionMask.OutlineMode.Or,
- ) = runSafe {
- filled(entity.boundingBox, color, sides)
- outline(entity.boundingBox, color, sides, mode)
- }
-}
diff --git a/src/main/kotlin/com/lambda/graphics/renderer/esp/Treed.kt b/src/main/kotlin/com/lambda/graphics/renderer/esp/Treed.kt
deleted file mode 100644
index 42394ed93..000000000
--- a/src/main/kotlin/com/lambda/graphics/renderer/esp/Treed.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2025 Lambda
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package com.lambda.graphics.renderer.esp
-
-import com.lambda.Lambda.mc
-import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib
-import com.lambda.graphics.buffer.vertex.attributes.VertexMode
-import com.lambda.graphics.gl.GlStateUtils
-import com.lambda.graphics.pipeline.VertexBuilder
-import com.lambda.graphics.pipeline.VertexPipeline
-import com.lambda.graphics.shader.Shader
-import com.lambda.module.modules.client.StyleEditor
-import com.lambda.util.extension.partialTicks
-
-/**
- * Open class for 3d rendering. It contains two pipelines, one for edges and the other for faces.
- */
-open class Treed(private val static: Boolean) {
- val shader = if (static) staticMode.first else dynamicMode.first
-
- val faces = VertexPipeline(VertexMode.Triangles, if (static) staticMode.second else dynamicMode.second)
- val edges = VertexPipeline(VertexMode.Lines, if (static) staticMode.second else dynamicMode.second)
-
- var faceBuilder = VertexBuilder(); private set
- var edgeBuilder = VertexBuilder(); private set
-
- fun upload() {
- faces.upload(faceBuilder)
- edges.upload(edgeBuilder)
- }
-
- fun render() {
- shader.use()
-
- if (!static)
- shader["u_TickDelta"] = mc.partialTicks
-
- GlStateUtils.withFaceCulling(faces::render)
- GlStateUtils.withLineWidth(StyleEditor.outlineWidth, edges::render)
- }
-
- fun clear() {
- faces.clear()
- edges.clear()
-
- faceBuilder = VertexBuilder()
- edgeBuilder = VertexBuilder()
- }
-
- /**
- * Public object for static rendering. Shapes rendering by this are not interpolated.
- * That means that if the shape is frequently moving, its movement will saccade.
- */
- object Static : Treed(true)
-
- /**
- * Public object for dynamic rendering. Its position will be interpolated between ticks, allowing
- * for smooth movement at the cost of duplicate position and slightly higher memory consumption.
- */
- object Dynamic : Treed(false)
-
- companion object {
- private val staticMode = Shader("shaders/vertex/box_static.glsl", "shaders/fragment/pos_color.glsl") to VertexAttrib.Group.STATIC_RENDERER
- private val dynamicMode = Shader("shaders/vertex/box_dynamic.glsl", "shaders/fragment/pos_color.glsl") to VertexAttrib.Group.DYNAMIC_RENDERER
- }
-}
diff --git a/src/main/kotlin/com/lambda/graphics/renderer/gui/AbstractGUIRenderer.kt b/src/main/kotlin/com/lambda/graphics/renderer/gui/AbstractGUIRenderer.kt
deleted file mode 100644
index 271614ff5..000000000
--- a/src/main/kotlin/com/lambda/graphics/renderer/gui/AbstractGUIRenderer.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2025 Lambda
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package com.lambda.graphics.renderer.gui
-
-import com.lambda.event.events.TickEvent
-import com.lambda.event.listener.SafeListener.Companion.listen
-import com.lambda.graphics.RenderMain
-import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib
-import com.lambda.graphics.buffer.vertex.attributes.VertexMode
-import com.lambda.graphics.pipeline.VertexPipeline
-import com.lambda.graphics.shader.Shader
-import com.lambda.gui.components.ClickGuiLayout
-import com.lambda.module.modules.client.StyleEditor
-import com.lambda.util.math.MathUtils.toInt
-import com.lambda.util.math.Vec2d
-import org.lwjgl.glfw.GLFW
-
-open class AbstractGUIRenderer(
- attribGroup: VertexAttrib.Group,
- val shader: Shader
-) {
- private val pipeline = VertexPipeline(VertexMode.Triangles, attribGroup)
- private var memoryMapping = true
-
- init {
- listen(alwaysListen = true) {
- memoryMapping = StyleEditor.useMemoryMapping
- }
-
- listen(alwaysListen = true) {
- if (memoryMapping) pipeline.sync()
- }
- }
-
- fun render(
- shade: Boolean = false,
- block: VertexPipeline.(Shader) -> Unit
- ) {
- shader.use()
-
- block(pipeline, shader)
-
- shader["u_Shade"] = shade.toInt().toDouble()
- if (shade) {
- shader["u_ShadeTime"] = GLFW.glfwGetTime() * ClickGuiLayout.colorSpeed * 5.0
- shader["u_ShadeColor1"] = ClickGuiLayout.primaryColor
- shader["u_ShadeColor2"] = ClickGuiLayout.secondaryColor
-
- shader["u_ShadeSize"] = RenderMain.screenSize / Vec2d(ClickGuiLayout.colorWidth, ClickGuiLayout.colorHeight)
- }
-
- pipeline.apply {
- render()
- if (memoryMapping) end() else clear()
- }
- }
-}
diff --git a/src/main/kotlin/com/lambda/graphics/renderer/gui/FontRenderer.kt b/src/main/kotlin/com/lambda/graphics/renderer/gui/FontRenderer.kt
deleted file mode 100644
index 69dc6bf3b..000000000
--- a/src/main/kotlin/com/lambda/graphics/renderer/gui/FontRenderer.kt
+++ /dev/null
@@ -1,280 +0,0 @@
-/*
- * Copyright 2025 Lambda
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package com.lambda.graphics.renderer.gui
-
-import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib
-import com.lambda.graphics.pipeline.VertexBuilder
-import com.lambda.graphics.renderer.gui.font.core.GlyphInfo
-import com.lambda.graphics.renderer.gui.font.core.LambdaAtlas.get
-import com.lambda.graphics.renderer.gui.font.core.LambdaAtlas.height
-import com.lambda.graphics.texture.TextureOwner.bind
-import com.lambda.gui.components.ClickGuiLayout
-import com.lambda.module.modules.client.LambdaMoji
-import com.lambda.module.modules.client.StyleEditor
-import com.lambda.util.math.MathUtils.toInt
-import com.lambda.util.math.Vec2d
-import com.lambda.util.math.a
-import com.lambda.util.math.setAlpha
-import net.minecraft.client.gui.hud.ChatHud.getHeight
-import sun.java2d.SunGraphicsEnvironment.getScaleFactor
-import java.awt.Color
-
-/**
- * Renders text and emoji glyphs using a shader-based font rendering system.
- * This class handles text and emoji rendering, shadow effects, and text scaling.
- */
-/*object FontRenderer : AbstractGUIRenderer(VertexAttrib.Group.FONT, shader("renderer/font")) {
- private val chars get() = StyleEditor.textFont
- private val emojis get() = StyleEditor.emojiFont
-
- private val shadowShift get() = StyleEditor.shadowShift * 10.0
- private val baselineOffset get() = StyleEditor.baselineOffset * 2.0f - 16f
- private val gap get() = StyleEditor.gap * 0.5f - 0.8f
-
- /**
- * Renders a text string at a specified position with configurable color, scale, shadow, and emoji parsing
- *
- * @param text The text to render.
- * @param position The position to render the text.
- * @param color The color of the text.
- * @param scale The scale factor of the text.
- * @param shadow Whether to render a shadow for the text.
- * @param parseEmoji Whether to parse and render emojis in the text.
- */
- fun drawString(
- text: String,
- position: Vec2d = Vec2d.ZERO,
- color: Color = Color.WHITE,
- scale: Double = ClickGuiLayout.fontScale,
- shadow: Boolean = true,
- parseEmoji: Boolean = LambdaMoji.isEnabled
- ) = render {
- shader["u_FontTexture"] = 0
- shader["u_EmojiTexture"] = 1
- shader["u_SDFMin"] = StyleEditor.sdfMin
- shader["u_SDFMax"] = StyleEditor.sdfMax
-
- bind(chars, emojis)
-
- upload {
- processText(text, color, scale, shadow, parseEmoji) { char, pos1, pos2, col, _ ->
- buildGlyph(char, position, pos1, pos2, col)
- }
- }
- }
-
- /**
- * Renders a single glyph at the specified position with the given scale and color
- *
- * @param glyph The glyph information
- * @param position The rendering position where the glyph will be drawn
- * @param color The color of the glyph
- * @param scale The scale factor of the glyph
- */
- fun drawGlyph(
- glyph: GlyphInfo,
- position: Vec2d,
- color: Color = Color.WHITE,
- scale: Double = ClickGuiLayout.fontScale,
- ) = render {
- shader["u_FontTexture"] = 0
- shader["u_EmojiTexture"] = 1
- shader["u_SDFMin"] = StyleEditor.sdfMin
- shader["u_SDFMax"] = StyleEditor.sdfMax
-
- bind(chars, emojis)
-
- val actualScale = getScaleFactor(scale)
- val scaledSize = glyph.size * actualScale
-
- val posY = getHeight(scale) * -0.5 + baselineOffset * actualScale
- val pos1 = Vec2d(0.0, posY) * actualScale
- val pos2 = pos1 + scaledSize
-
- upload { buildGlyph(glyph, position, pos1, pos2, color) }
- }
-
- /**
- * Renders a single glyph at a given position.
- *
- * @param glyph The glyph information to render.
- * @param origin The position to start from
- * @param pos1 The starting position of the glyph.
- * @param pos2 The end position of the glyph
- * @param color The color of the glyph.
- */
- private fun VertexBuilder.buildGlyph(
- glyph: GlyphInfo,
- origin: Vec2d = Vec2d.ZERO,
- pos1: Vec2d,
- pos2: Vec2d,
- color: Color,
- ) {
- val x1 = pos1.x + origin.x
- val y1 = pos1.y + origin.y
- val x2 = pos2.x + origin.x
- val y2 = pos2.y + origin.y
-
- val upLeft = vertex { vec3m(x1, y1).vec2(glyph.uv1.x, glyph.uv1.y).color(color) }
- val downLeft = vertex { vec3m(x1, y2).vec2(glyph.uv1.x, glyph.uv2.y).color(color) }
- val upRight = vertex { vec3m(x2, y2).vec2(glyph.uv2.x, glyph.uv2.y).color(color) }
- val downRight = vertex { vec3m(x2, y1).vec2(glyph.uv2.x, glyph.uv1.y).color(color) }
-
- buildQuad(upLeft, downLeft, upRight, downRight)
- }
-
- /**
- * Calculates the width of the specified text.
- *
- * @param text The text to measure.
- * @param scale The scale factor for the width calculation.
- * @param parseEmoji Whether to include emojis in the width calculation.
- * @return The width of the text at the specified scale.
- */
- fun getWidth(
- text: String,
- scale: Double = ClickGuiLayout.fontScale,
- parseEmoji: Boolean = LambdaMoji.isEnabled,
- ): Double {
- var width = 0.0
- var gaps = -1
-
- processText(text, scale = scale, parseEmoji = parseEmoji) { char, _, _, _, isShadow ->
- if (isShadow) return@processText
- width += char.width; gaps++
- }
-
- return (width + gaps.coerceAtLeast(0) * gap) * getScaleFactor(scale)
- }
-
- /**
- * Computes the effective height of the rendered text
- *
- * The height is derived from the current font's base height, adjusted by a scaling factor
- * that ensures consistent visual proportions
- *
- * @param scale The scale factor for the height calculation.
- * @return The height of the text at the specified scale.
- */
- fun getHeight(scale: Double = 1.0) = chars.height * getScaleFactor(scale) * 0.7
-
- /**
- * Processes a text string by iterating over its characters and emojis, computing rendering positions, and invoking a block for each glyph
- *
- * @param text The text to iterate over.
- * @param color The color of the text.
- * @param scale The scale of the text.
- * @param shadow Whether to render a shadow.
- * @param parseEmoji Whether to parse and include emojis.
- * @param block The function to apply to each character or emoji glyph.
- */
- private fun processText(
- text: String,
- color: Color = Color.WHITE,
- scale: Double = 1.0,
- shadow: Boolean = StyleEditor.shadow,
- parseEmoji: Boolean = LambdaMoji.isEnabled,
- block: (GlyphInfo, Vec2d, Vec2d, Color, Boolean) -> Unit
- ) {
- val actualScale = getScaleFactor(scale)
- val scaledGap = gap * actualScale
-
- val shadowColor = getShadowColor(color)
- val emojiColor = color.setAlpha(color.a)
-
- var posX = 0.0
- var posY = getHeight(scale) * -0.5 + baselineOffset * actualScale
-
- fun drawGlyph(info: GlyphInfo?, color: Color, isShadow: Boolean = false) {
- if (info == null) return
-
- val scaledSize = info.size * actualScale
- val pos1 = Vec2d(posX, posY) + shadowShift * actualScale * isShadow.toInt()
- val pos2 = pos1 + scaledSize
-
- block(info, pos1, pos2, color, isShadow)
- if (!isShadow) posX += scaledSize.x + scaledGap
- }
-
- val parsed = if (parseEmoji) emojis.parse(text) else mutableListOf()
-
- fun processTextSection(section: String, hasEmojis: Boolean) {
- if (section.isEmpty()) return
- if (!parseEmoji || parsed.isEmpty() || !hasEmojis) {
- // Draw simple characters if no emojis are present
- section.forEach { char ->
- // Logic for control characters
- when (char) {
- '\n', '\r' -> { posX = 0.0; posY += chars.height * actualScale; return@forEach }
- }
-
- val glyph = chars[char] ?: return@forEach
-
- if (shadow && StyleEditor.shadow) drawGlyph(glyph, shadowColor, true)
- drawGlyph(glyph, color)
- }
- } else {
- // Only compute the first parsed emoji to avoid duplication
- // This is important in order to keep the parsed ranges valid
- // If you do not this, you will get out of bounds positions
- // due to slicing
- val emoji = parsed.removeFirstOrNull() ?: return
-
- // Iterate the emojis from left to right
- val start = section.indexOf(emoji)
- val end = start + emoji.length
-
- val preEmojiText = section.substring(0, start)
- val postEmojiText = section.substring(end)
-
- // Draw the text without emoji
- processTextSection(preEmojiText, hasEmojis = false)
-
- // Draw the emoji
- drawGlyph(emojis[emoji], emojiColor)
-
- // Process the rest of the text after the emoji
- processTextSection(postEmojiText, hasEmojis = true)
- }
- }
-
- // Start processing the full text
- processTextSection(text, hasEmojis = parsed.isNotEmpty())
- }
-
- /**
- * Calculates the scale factor for the text based on the provided scale.
- *
- * @param scale The base scale factor.
- * @return The adjusted scale factor.
- */
- fun getScaleFactor(scale: Double): Double = scale * 8.5 / chars.height
-
- /**
- * Calculates the shadow color by adjusting the brightness of the input color.
- *
- * @param color The original color.
- * @return The modified shadow color.
- */
- fun getShadowColor(color: Color): Color = Color(
- (color.red * StyleEditor.shadowBrightness).toInt(),
- (color.green * StyleEditor.shadowBrightness).toInt(),
- (color.blue * StyleEditor.shadowBrightness).toInt(),
- color.alpha
- )
-}*/
diff --git a/src/main/kotlin/com/lambda/graphics/renderer/gui/font/core/GlyphInfo.kt b/src/main/kotlin/com/lambda/graphics/renderer/gui/font/core/GlyphInfo.kt
deleted file mode 100644
index 88dab1a59..000000000
--- a/src/main/kotlin/com/lambda/graphics/renderer/gui/font/core/GlyphInfo.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2025 Lambda
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package com.lambda.graphics.renderer.gui.font.core
-
-import com.lambda.util.math.Vec2d
-
-/**
- * Represents information about a character (glyph) in a font.
- *
- * @property size The size of the character in a 2D vector.
- * @property uv1 The top-left UV coordinates of the character texture.
- * @property uv2 The bottom-right UV coordinates of the character texture.
- */
-data class GlyphInfo(
- val size: Vec2d,
- val uv1: Vec2d,
- val uv2: Vec2d
-) {
- /**
- * The width of the character.
- */
- val width get() = size.x
-
- /**
- * The height of the character.
- */
- val height get() = size.y
-
- /**
- * The U coordinate of the top-left corner of the character texture.
- */
- val u1 get() = uv1.x
-
- /**
- * The V coordinate of the top-left corner of the character texture.
- */
- val v1 get() = uv1.y
-
- /**
- * The U coordinate of the bottom-right corner of the character texture.
- */
- val u2 get() = uv2.x
-
- /**
- * The V coordinate of the bottom-right corner of the character texture.
- */
- val v2 get() = uv2.y
-}
diff --git a/src/main/kotlin/com/lambda/graphics/renderer/gui/font/core/LambdaAtlas.kt b/src/main/kotlin/com/lambda/graphics/renderer/gui/font/core/LambdaAtlas.kt
deleted file mode 100644
index bc4004394..000000000
--- a/src/main/kotlin/com/lambda/graphics/renderer/gui/font/core/LambdaAtlas.kt
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * Copyright 2025 Lambda
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package com.lambda.graphics.renderer.gui.font.core
-
-import com.google.common.math.IntMath
-import com.lambda.core.Loadable
-import com.lambda.graphics.texture.TextureOwner.upload
-import com.lambda.threading.runGameScheduled
-import com.lambda.util.math.Vec2d
-import com.lambda.util.stream
-import it.unimi.dsi.fastutil.objects.Object2DoubleArrayMap
-import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
-import java.awt.Color
-import java.awt.Font
-import java.awt.FontMetrics
-import java.awt.Graphics2D
-import java.awt.RenderingHints
-import java.awt.image.BufferedImage
-import java.io.File
-import java.util.zip.ZipFile
-import javax.imageio.ImageIO
-import kotlin.math.ceil
-import kotlin.math.log2
-import kotlin.math.max
-import kotlin.math.sqrt
-
-/**
- * The [LambdaAtlas] manages the creation and binding of texture atlases for fonts, emojis and user defined atlases
- * It stores glyph information, manages texture uploads, and provides functionality to build texture buffers for fonts and emoji sets
- *
- * It caches font information and emoji data for efficient rendering and includes mechanisms for uploading and binding texture atlases
- *
- * It's also possible to upload custom atlases and bind them with no hassle
- * ```kt
- * enum class ExampleFont {
- * CoolFont("Cool-Font");
- * }
- *
- * fun loadFont(...) = BufferedImage
- *
- * // Function extension from [TexturePipeline]
- * ExampleFont.CoolFont.upload(loadFont(...)) // The extension keeps a reference to the font owner
- *
- * ...
- *
- * onRender {
- * ExampleFont.CoolFont.bind()
- * }
- * ```
- */
-object LambdaAtlas : Loadable {
- private val fontMap = mutableMapOf>()
- private val emojiMap = mutableMapOf>()
-
- private val bufferPool =
- mutableMapOf() // This array is nuked once the data is dispatched to OpenGL
-
- private val fontCache = mutableMapOf()
- private val metricCache = mutableMapOf()
- private val heightCache = Object2DoubleArrayMap()
-
- operator fun LambdaFont.get(char: Char): GlyphInfo? = fontMap.getValue(this)[char]
- operator fun LambdaEmoji.get(string: String): GlyphInfo? = emojiMap.getValue(this)[string.removeSurrounding(":")]
-
- val LambdaFont.height: Double
- get() = heightCache.getDouble(fontCache[this@height])
-
- val LambdaEmoji.keys
- get() = emojiMap.getValue(this)
-
- private const val CHAR_SPACE = 8
-
- /**
- * Builds the buffer for an emoji set by reading a ZIP file containing emoji images.
- * The images are arranged into a texture atlas, and their UV coordinates are computed for later rendering.
- *
- * @throws IllegalStateException If the texture size is too small to fit the emojis.
- */
- fun LambdaEmoji.buildBuffer() {
- var image: BufferedImage
- val file = File.createTempFile("emoji", "zip")
- url.stream.copyTo(file.outputStream())
- ZipFile(file).use { zip ->
- val firstImage = ImageIO.read(zip.getInputStream(zip.entries().nextElement()))
- val length = zip.size().toDouble()
-
- val textureDimensionLength: (Int) -> Int = { dimLength ->
- IntMath.pow(2, ceil(log2((dimLength + CHAR_SPACE) * sqrt(length))).toInt())
- }
-
- val width = textureDimensionLength(firstImage.width)
- val height = textureDimensionLength(firstImage.height)
- val texelSize = Vec2d.ONE / Vec2d(width, height)
-
- image = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB)
- val graphics = image.graphics as Graphics2D
- graphics.color = Color(0, 0, 0, 0)
-
- var x = CHAR_SPACE
- var y = CHAR_SPACE
-
- val constructed = Object2ObjectOpenHashMap()
- for (entry in zip.entries()) {
- val name = entry.name.substringAfterLast("/").substringBeforeLast(".")
- val emoji = ImageIO.read(zip.getInputStream(entry))
-
- val charWidth = emoji.width + CHAR_SPACE
- val charHeight = emoji.height + CHAR_SPACE
-
- if (x + charWidth >= image.width) {
- check(y + charHeight < image.height) { "Can't load emoji glyphs. Texture size is too small" }
-
- y += charHeight
- x = 0
- }
-
- graphics.drawImage(emoji, x, y, null)
-
- val size = Vec2d(emoji.width, emoji.height)
- val uv1 = Vec2d(x, y) * texelSize
- val uv2 = Vec2d(x, y).plus(size) * texelSize
-
- val normalized = 128.0 / size.y
- constructed[name] = GlyphInfo(size * normalized, -uv1, -uv2)
-
- x += emoji.width + 2
- }
-
- emojiMap[this@buildBuffer] = constructed
- }
-
- bufferPool[this@buildBuffer] = image
- }
-
- fun LambdaFont.buildBuffer(
- characters: Int = 2048 // How many characters from that font should be used for the generation
- ) {
- val font = fontCache.computeIfAbsent(this) {
- Font.createFont(Font.TRUETYPE_FONT, "fonts/$fontName.ttf".stream).deriveFont(128.0f)
- }
-
- val textureSize = characters * 2
- val oneTexelSize = 1.0 / textureSize
-
- val image = BufferedImage(textureSize, textureSize, BufferedImage.TYPE_INT_ARGB)
-
- val graphics = image.graphics as Graphics2D
- graphics.background = Color(0, 0, 0, 0)
-
- var x = CHAR_SPACE
- var y = CHAR_SPACE
- var rowHeight = 0
-
- val constructed = mutableMapOf()
- (Char.MIN_VALUE..
- val charImage = getCharImage(font, char) ?: return@forEach
-
- rowHeight = max(rowHeight, charImage.height + CHAR_SPACE)
- val charWidth = charImage.width + CHAR_SPACE
-
- if (x + charWidth >= textureSize) {
- // Check if possible to step to the next row
- check(y + rowHeight <= textureSize) { "Can't load font glyphs. Texture size is too small" }
-
- y += rowHeight
- x = 0
- rowHeight = 0
- }
-
- graphics.drawImage(charImage, x, y, null)
-
- val size = Vec2d(charImage.width, charImage.height)
- val uv1 = Vec2d(x, y) * oneTexelSize
- val uv2 = Vec2d(x, y).plus(size) * oneTexelSize
-
- constructed[char] = GlyphInfo(size, uv1, uv2)
- heightCache[font] = max(heightCache.getDouble(font), size.y) // No compare set unfortunately
-
- x += charWidth
- }
-
- fontMap[this@buildBuffer] = constructed
- bufferPool[this@buildBuffer] = image
- }
-
- // TODO: Change this when we've refactored the loadables
- override fun load(): String {
- LambdaFont.entries.forEach(LambdaFont::load)
- LambdaEmoji.entries.forEach(LambdaEmoji::load)
-
- val str = "Loaded ${bufferPool.size} fonts" // avoid race condition
-
- runGameScheduled {
- bufferPool.forEach { (owner, image) -> owner.upload(image) }
- bufferPool.clear()
- }
-
- return str
- }
-
- private fun getCharImage(font: Font, codePoint: Char): BufferedImage? {
- if (!font.canDisplay(codePoint)) return null
-
- val fontMetrics = metricCache.getOrPut(font) {
- val image = BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB)
- val graphics2D = image.createGraphics()
-
- graphics2D.font = font
- graphics2D.dispose()
-
- image.graphics.getFontMetrics(font)
- }
-
- val charWidth = if (fontMetrics.charWidth(codePoint) > 0) fontMetrics.charWidth(codePoint) else 8
- val charHeight = if (fontMetrics.height > 0) fontMetrics.height else font.size
-
- val charImage = BufferedImage(charWidth, charHeight, BufferedImage.TYPE_INT_ARGB)
- val graphics2D = charImage.createGraphics()
-
- graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
- graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_DEFAULT)
-
- graphics2D.font = font
- graphics2D.color = Color.WHITE
- graphics2D.drawString(codePoint.toString(), 0, fontMetrics.ascent)
- graphics2D.dispose()
-
- return charImage
- }
-}
diff --git a/src/main/kotlin/com/lambda/graphics/renderer/gui/font/core/LambdaEmoji.kt b/src/main/kotlin/com/lambda/graphics/renderer/gui/font/core/LambdaEmoji.kt
deleted file mode 100644
index 78b778ed0..000000000
--- a/src/main/kotlin/com/lambda/graphics/renderer/gui/font/core/LambdaEmoji.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2025 Lambda
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package com.lambda.graphics.renderer.gui.font.core
-
-import com.lambda.graphics.renderer.gui.font.core.LambdaAtlas.buildBuffer
-
-enum class LambdaEmoji(val url: String) {
- Twemoji("fonts/emojis.zip");
-
- private val emojiRegex = Regex(":[a-zA-Z0-9_]+:")
-
- /**
- * Extracts emoji names from the provided text
- *
- * The function scans the input text for patterns matching emojis in the `:name:` format and
- * returns a mutable list of the emoji names
- *
- * @param text The text to parse.
- * @return A list of extract emoji names
- */
- fun parse(text: String): MutableList =
- emojiRegex.findAll(text).map { it.value }.toMutableList()
-
- fun load(): String {
- entries.forEach { it.buildBuffer() }
- return "Loaded ${entries.size} emoji sets"
- }
-}
diff --git a/src/main/kotlin/com/lambda/gui/DearImGui.kt b/src/main/kotlin/com/lambda/gui/DearImGui.kt
index 13b4f24fa..770468d52 100644
--- a/src/main/kotlin/com/lambda/gui/DearImGui.kt
+++ b/src/main/kotlin/com/lambda/gui/DearImGui.kt
@@ -36,6 +36,7 @@ import imgui.glfw.ImGuiImplGlfw
import net.minecraft.client.gl.GlBackend
import net.minecraft.client.texture.GlTexture
import org.lwjgl.opengl.GL30.GL_FRAMEBUFFER
+import org.lwjgl.opengl.GL32C
import kotlin.math.abs
object DearImGui : Loadable {
@@ -103,7 +104,7 @@ object DearImGui : Loadable {
val framebuffer = mc.framebuffer
val prevFramebuffer = (framebuffer.getColorAttachment() as GlTexture).getOrCreateFramebuffer(
- (RenderSystem.getDevice() as GlBackend).framebufferManager,
+ (RenderSystem.getDevice() as GlBackend).bufferManager,
null
)
@@ -120,6 +121,8 @@ object DearImGui : Loadable {
GuiEvent.EndFrame.post()
implGl3.renderDrawData(ImGui.getDrawData())
+
+ GlStateManager._glBindFramebuffer(GL_FRAMEBUFFER, 0)
}
fun destroy() {
diff --git a/src/main/kotlin/com/lambda/gui/LambdaScreen.kt b/src/main/kotlin/com/lambda/gui/LambdaScreen.kt
index cb35fc93f..bb01781ea 100644
--- a/src/main/kotlin/com/lambda/gui/LambdaScreen.kt
+++ b/src/main/kotlin/com/lambda/gui/LambdaScreen.kt
@@ -26,4 +26,14 @@ object LambdaScreen : Screen(Text.of("Lambda")) {
override fun shouldPause() = false
override fun removed() = ClickGuiLayout.close()
override fun render(context: DrawContext?, mouseX: Int, mouseY: Int, deltaTicks: Float) {}
+
+ override fun applyBlur(context: DrawContext?) {
+ if (!ClickGuiLayout.backgroundBlur) return
+ super.applyBlur(context)
+ }
+
+ override fun renderDarkening(context: DrawContext?) {
+ if (!ClickGuiLayout.backgroundDarkening) return
+ super.renderDarkening(context)
+ }
}
diff --git a/src/main/kotlin/com/lambda/gui/MenuBar.kt b/src/main/kotlin/com/lambda/gui/MenuBar.kt
index 3856aea6f..bfef5ac0f 100644
--- a/src/main/kotlin/com/lambda/gui/MenuBar.kt
+++ b/src/main/kotlin/com/lambda/gui/MenuBar.kt
@@ -52,6 +52,11 @@ import imgui.flag.ImGuiCol
import imgui.flag.ImGuiStyleVar
import imgui.flag.ImGuiWindowFlags
import net.fabricmc.loader.api.FabricLoader
+import net.minecraft.client.gui.hud.debug.DebugHudEntries
+import net.minecraft.client.gui.screen.DebugOptionsScreen
+import net.minecraft.command.permission.Permission
+import net.minecraft.network.packet.c2s.play.ChangeGameModeC2SPacket
+import net.minecraft.server.command.GameModeCommand
import net.minecraft.util.Util
import net.minecraft.world.GameMode
import java.util.*
@@ -346,18 +351,18 @@ object MenuBar {
}
separator()
runSafe {
- menu("Gamemode", enabled = player.hasPermissionLevel(2)) {
+ menu("Gamemode", enabled = GameModeCommand.PERMISSION_CHECK.allows(player.permissions)) {
menuItem("Survival", selected = interaction.gameMode == GameMode.SURVIVAL) {
- connection.sendCommand("gamemode survival")
+ connection.sendPacket(ChangeGameModeC2SPacket(GameMode.SURVIVAL))
}
menuItem("Creative", selected = interaction.gameMode == GameMode.CREATIVE) {
- connection.sendCommand("gamemode creative")
+ connection.sendPacket(ChangeGameModeC2SPacket(GameMode.CREATIVE))
}
menuItem("Adventure", selected = interaction.gameMode == GameMode.ADVENTURE) {
- connection.sendCommand("gamemode adventure")
+ connection.sendPacket(ChangeGameModeC2SPacket(GameMode.ADVENTURE))
}
menuItem("Spectator", selected = interaction.gameMode == GameMode.SPECTATOR) {
- connection.sendCommand("gamemode spectator")
+ connection.sendPacket(ChangeGameModeC2SPacket(GameMode.SPECTATOR))
}
}
menu("Debug Menu") {
@@ -365,16 +370,6 @@ object MenuBar {
mc.options.advancedItemTooltips = !mc.options.advancedItemTooltips
mc.options.write()
}
- menuItem("Show Chunk Borders", "F3+G", mc.debugRenderer.showChunkBorder) {
- mc.debugRenderer.toggleShowChunkBorder()
- }
- menuItem("Show Octree", selected = mc.debugRenderer.showOctree) {
- mc.debugRenderer.toggleShowOctree()
- }
- menuItem("Show Hitboxes", "F3+B", mc.entityRenderDispatcher.shouldRenderHitboxes()) {
- val now = !mc.entityRenderDispatcher.shouldRenderHitboxes()
- mc.entityRenderDispatcher.setRenderHitboxes(now)
- }
menuItem("Copy Location (as command)", "F3+C") {
val cmd = String.format(
Locale.ROOT,
@@ -388,6 +383,9 @@ object MenuBar {
menuItem("Clear Chat", "F3+D") {
mc.inGameHud?.chatHud?.clear(false)
}
+ menuItem("Open Debug Entry Menu", "(new)") { // ToDo: Put actual keybind in
+ mc.setScreen(DebugOptionsScreen())
+ }
separator()
@@ -414,8 +412,8 @@ object MenuBar {
separator()
- menuItem("Show Debug Menu", "F3", mc.debugHud.showDebugHud) {
- mc.debugHud.toggleDebugHud()
+ menuItem("Show Debug Menu", "F3", mc.debugHudEntryList.isF3Enabled) {
+ mc.debugHudEntryList.toggleF3Enabled()
}
menuItem("Rendering Chart", "F3+1", mc.debugHud.renderingChartVisible) {
mc.debugHud.toggleRenderingChart()
diff --git a/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt b/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt
index b54bad9d4..89729a4e3 100644
--- a/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt
+++ b/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt
@@ -115,13 +115,15 @@ object ClickGuiLayout : Loadable, Configurable(GuiConfig) {
if (to) {
setLambdaWindowIcon()
} else {
- val icon = if (SharedConstants.getGameVersion().isStable) Icons.RELEASE else Icons.SNAPSHOT
+ val icon = if (SharedConstants.getGameVersion().stable()) Icons.RELEASE else Icons.SNAPSHOT
mc.window.setIcon(mc.defaultResourcePack, icon)
}
}
@JvmStatic
val setLambdaWindowTitle by setting("Set Lambda Window Title", true).onValueChange { _, _ -> mc.updateWindowTitle() }.group(Group.General)
val lambdaTitleAppendixName by setting("Append Username", true) { setLambdaWindowTitle }.onValueChange { _, _ -> mc.updateWindowTitle() }.group(Group.General)
+ val backgroundBlur by setting("Background Blur", true).group(Group.General)
+ val backgroundDarkening by setting("Background Darkening", true).group(Group.General)
// Snapping
val snapEnabled by setting("Enable Snapping", true, "Master toggle for GUI/HUD snapping").group(Group.Snapping)
diff --git a/src/main/kotlin/com/lambda/gui/components/QuickSearch.kt b/src/main/kotlin/com/lambda/gui/components/QuickSearch.kt
index 396a9b826..f4815152b 100644
--- a/src/main/kotlin/com/lambda/gui/components/QuickSearch.kt
+++ b/src/main/kotlin/com/lambda/gui/components/QuickSearch.kt
@@ -91,7 +91,7 @@ object QuickSearch {
override fun ImGuiBuilder.buildLayout() {
text(command.name.capitalize())
sameLine()
- smallButton("Insert") { mc.setScreen(ChatScreen("${CommandRegistry.prefix}${command.name} ")) }
+ smallButton("Insert") { mc.setScreen(ChatScreen("${CommandRegistry.prefix}${command.name} ", true)) }
if (command.description.isNotBlank()) {
sameLine()
textDisabled(command.description)
diff --git a/src/main/kotlin/com/lambda/interaction/BaritoneManager.kt b/src/main/kotlin/com/lambda/interaction/BaritoneManager.kt
index 43f36c52c..97a448780 100644
--- a/src/main/kotlin/com/lambda/interaction/BaritoneManager.kt
+++ b/src/main/kotlin/com/lambda/interaction/BaritoneManager.kt
@@ -28,15 +28,18 @@ import com.lambda.context.Automated
import com.lambda.config.AutomationConfig
import com.lambda.util.BlockUtils.blockPos
import com.lambda.util.NamedEnum
+import net.fabricmc.loader.api.FabricLoader
object BaritoneManager : Configurable(LambdaConfig), Automated by AutomationConfig.Companion.DEFAULT {
override val name = "baritone"
- private val baritone = BaritoneAPI.getProvider()
- val baritoneSettings: Settings = BaritoneAPI.getSettings()
+ val isBaritoneLoaded = FabricLoader.getInstance().isModLoaded("baritone")
+
+ private val baritone = if (isBaritoneLoaded) BaritoneAPI.getProvider() else null
+ val baritoneSettings: Settings? = if (isBaritoneLoaded) BaritoneAPI.getSettings() else null
@JvmStatic
- val primary: IBaritone = baritone.primaryBaritone
+ val primary: IBaritone? = baritone?.primaryBaritone
private enum class Group(override val displayName: String) : NamedEnum {
General("General"),
@@ -72,7 +75,8 @@ object BaritoneManager : Configurable(LambdaConfig), Automated by AutomationConf
init {
// ToDo: Dont actually save the settings as its duplicate data
- with(baritoneSettings) {
+ if (isBaritoneLoaded) {
+ with(baritoneSettings!!) {
// GENERAL
setting("Log As Toast", logAsToast.value).group(Group.General, SubGroup.ChatAndControl).onValueChange { _, it -> logAsToast.value = it }
@@ -334,6 +338,7 @@ object BaritoneManager : Configurable(LambdaConfig), Automated by AutomationConf
setting("Allow Land On Nether Fortress", elytraAllowLandOnNetherFortress.value).group(Group.Elytra).onValueChange { _, it -> elytraAllowLandOnNetherFortress.value = it }
setting("Terms Accepted", elytraTermsAccepted.value).group(Group.Elytra).onValueChange { _, it -> elytraTermsAccepted.value = it }
setting("Chat Spam", elytraChatSpam.value).group(Group.Elytra).onValueChange { _, it -> elytraChatSpam.value = it }
+ }
}
}
@@ -341,22 +346,28 @@ object BaritoneManager : Configurable(LambdaConfig), Automated by AutomationConf
* Whether Baritone is currently pathing
*/
val isPathing: Boolean
- get() = primary.pathingBehavior.isPathing
+ get() = isBaritoneLoaded && primary?.pathingBehavior?.isPathing == true
/**
* Whether Baritone is active (pathing, calculating goal, etc.)
*/
val isActive: Boolean
- get() = primary.customGoalProcess.isActive || primary.pathingBehavior.isPathing || primary.pathingControlManager.mostRecentInControl()
- .orElse(null)?.isActive == true
+ get() = isBaritoneLoaded && (primary?.customGoalProcess?.isActive == true || primary?.pathingBehavior?.isPathing == true || primary?.pathingControlManager?.mostRecentInControl()
+ ?.orElse(null)?.isActive == true)
/**
* Sets the current Baritone goal and starts pathing
*/
- fun setGoalAndPath(goal: Goal) = primary.customGoalProcess.setGoalAndPath(goal)
+ fun setGoalAndPath(goal: Goal) {
+ if (!isBaritoneLoaded) return
+ primary?.customGoalProcess?.setGoalAndPath(goal)
+ }
/**
* Force cancel Baritone
*/
- fun cancel() = primary.pathingBehavior.cancelEverything()
+ fun cancel() {
+ if (!isBaritoneLoaded) return
+ primary?.pathingBehavior?.cancelEverything()
+ }
}
diff --git a/src/main/kotlin/com/lambda/interaction/PlayerPacketHandler.kt b/src/main/kotlin/com/lambda/interaction/PlayerPacketHandler.kt
index 5cda2e509..dca27c164 100644
--- a/src/main/kotlin/com/lambda/interaction/PlayerPacketHandler.kt
+++ b/src/main/kotlin/com/lambda/interaction/PlayerPacketHandler.kt
@@ -39,7 +39,6 @@ object PlayerPacketHandler {
var lastPosition: Vec3d = Vec3d.ZERO
var lastRotation = Rotation.ZERO
var lastSprint = false
- var lastSneak = false
var lastOnGround = false
var lastHorizontalCollision = false
@@ -60,21 +59,6 @@ object PlayerPacketHandler {
}
}
- @JvmStatic
- fun sendSneakPackets() {
- runSafe {
- val sneaking = player.isSneaking
- if (sneaking == lastSneak) return@runSafe
- val mode = if (sneaking) {
- ClientCommandC2SPacket.Mode.PRESS_SHIFT_KEY
- } else {
- ClientCommandC2SPacket.Mode.RELEASE_SHIFT_KEY
- }
- connection.sendPacket(ClientCommandC2SPacket(player, mode))
- lastSneak = sneaking
- }
- }
-
private fun SafeContext.updatePlayerPackets(new: PlayerPacketEvent.Pre) {
configurations.add(new)
diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/SimInfo.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/SimInfo.kt
index 202799110..5609660cb 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/SimInfo.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/SimInfo.kt
@@ -19,12 +19,12 @@ package com.lambda.interaction.construction.simulation
import com.lambda.context.Automated
import com.lambda.context.AutomatedSafeContext
-import com.lambda.interaction.construction.simulation.processing.PreProcessingData
-import com.lambda.interaction.construction.simulation.processing.ProcessorRegistry.getProcessingInfo
-import com.lambda.interaction.construction.simulation.result.BuildResult
import com.lambda.interaction.construction.simulation.checks.BasicChecker.hasBasicRequirements
import com.lambda.interaction.construction.simulation.checks.BreakSim.Companion.simBreak
import com.lambda.interaction.construction.simulation.checks.InteractSim.Companion.simInteraction
+import com.lambda.interaction.construction.simulation.processing.PreProcessingData
+import com.lambda.interaction.construction.simulation.processing.ProcessorRegistry.getProcessingInfo
+import com.lambda.interaction.construction.simulation.result.BuildResult
import com.lambda.interaction.construction.verify.TargetState
import com.lambda.util.BlockUtils.matches
import net.minecraft.block.BlockState
@@ -99,29 +99,29 @@ interface SimInfo : Automated {
pov: Vec3d,
dependencyStack: Stack>,
concurrentResults: MutableSet
- ): SimInfo {
- if (!targetState.isEmpty()) {
- getProcessingInfo(state, targetState, pos)?.let { preProcessing ->
- return object : InteractSimInfo, Automated by this {
- override val pos = pos
- override val state = state
- override val targetState = targetState
- override val pov = pov
- override val dependencyStack = dependencyStack
- override val concurrentResults = concurrentResults
- override val preProcessing = preProcessing
- override val expectedState = preProcessing.info.expectedState
- override val item = preProcessing.info.item
- override val placing = preProcessing.info.placing
+ ): SimInfo {
+ if (!targetState.isEmpty()) {
+ getProcessingInfo(state, targetState, pos)?.let { preProcessing ->
+ return object : InteractSimInfo, Automated by this {
+ override val pos = pos
+ override val state = state
+ override val targetState = targetState
+ override val pov = pov
+ override val dependencyStack = dependencyStack
+ override val concurrentResults = concurrentResults
+ override val preProcessing = preProcessing
+ override val expectedState = preProcessing.info.expectedState
+ override val item = preProcessing.info.item
+ override val placing = preProcessing.info.placing
- context(_: AutomatedSafeContext, _: Sim<*>)
- override suspend fun sim() = simInteraction()
+ context(_: AutomatedSafeContext, _: Sim<*>)
+ override suspend fun sim() = simInteraction()
- override fun AutomatedSafeContext.matchesTarget(state: BlockState, completely: Boolean) =
- expectedState.matches(state, if (!completely) preProcessing.info.ignore else emptySet())
- }
- }
- }
+ override fun AutomatedSafeContext.matchesTarget(state: BlockState, completely: Boolean) =
+ expectedState.matches(state, if (!completely) preProcessing.info.ignore else emptySet())
+ }
+ }
+ }
return object : BreakSimInfo, Automated by this {
override val pos = pos
@@ -134,7 +134,7 @@ interface SimInfo : Automated {
context(_: AutomatedSafeContext, _: Sim<*>)
override suspend fun sim() = simBreak()
}
- }
+ }
}
}
diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt
index 98e5f6c4c..7785c578e 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt
@@ -19,7 +19,8 @@ package com.lambda.interaction.construction.simulation
import com.lambda.context.Automated
import com.lambda.context.SafeContext
-import com.lambda.graphics.renderer.esp.ShapeBuilder
+import com.lambda.graphics.esp.ShapeScope
+import com.lambda.graphics.mc.TransientRegionESP
import com.lambda.interaction.construction.blueprint.Blueprint
import com.lambda.interaction.construction.simulation.result.BuildResult
import com.lambda.interaction.construction.simulation.result.Drawable
@@ -41,7 +42,7 @@ data class Simulation(
private val automated: Automated
) : Automated by automated {
private val cache: MutableMap> = mutableMapOf()
- private fun FastVector.toView(): Vec3d = toVec3d().add(0.5, ClientPlayerEntity.DEFAULT_EYE_HEIGHT.toDouble(), 0.5)
+ private fun FastVector.toView(): Vec3d = toVec3d().add(0.5, ClientPlayerEntity.EYE_HEIGHT.toDouble(), 0.5)
fun simulate(pos: FastVector) =
cache.getOrPut(pos) {
@@ -63,8 +64,10 @@ data class Simulation(
.map { PossiblePos(it.key.toBlockPos(), it.value.count { it.rank.ordinal < 4 }) }
class PossiblePos(val pos: BlockPos, val interactions: Int) : Drawable {
- override fun ShapeBuilder.buildRenderer() {
- box(Vec3d.ofBottomCenter(pos).playerBox(), Color(0, 255, 0, 50), Color(0, 255, 0, 50))
+ override fun render(esp: TransientRegionESP) {
+ esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) {
+ box(Vec3d.ofBottomCenter(pos).playerBox(), Color(0, 255, 0, 50), Color(0, 255, 0, 50))
+ }
}
}
diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt
index 3c4a88f19..5d48ef331 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt
@@ -29,8 +29,8 @@ import com.lambda.interaction.construction.simulation.result.results.BreakResult
import com.lambda.interaction.construction.simulation.result.results.GenericResult
import com.lambda.interaction.construction.verify.TargetState
import com.lambda.interaction.managers.hotbar.HotbarManager
+import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest
import com.lambda.interaction.managers.rotating.RotationManager
-import com.lambda.interaction.managers.rotating.RotationRequest
import com.lambda.interaction.managers.rotating.visibilty.lookAtBlock
import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer
import com.lambda.interaction.material.StackSelection
@@ -108,7 +108,7 @@ class BreakSim private constructor(simInfo: SimInfo)
if (shape.boundingBoxes.map { it.offset(pos) }.any { it.contains(pov) }) {
val currentCast = RotationManager.activeRotation.rayCast(buildConfig.blockReach, pov)
currentCast?.blockResult?.let { blockHit ->
- val rotationRequest = RotationRequest(lookAtBlock(pos)?.rotation ?: return, this)
+ val rotationRequest = lookAtBlock(pos)?.rotation?.let { rotationRequest { rotation(it) } } ?: return
val breakContext = BreakContext(
blockHit,
rotationRequest,
@@ -127,7 +127,7 @@ class BreakSim private constructor(simInfo: SimInfo)
val validHits = scanShape(pov, shape, pos, Direction.entries.toSet(), null) ?: return
val bestHit = buildConfig.pointSelection.select(validHits) ?: return
- val rotationRequest = RotationRequest(bestHit.rotation, this)
+ val rotationRequest = rotationRequest { rotation(bestHit.rotation) }
val breakContext = BreakContext(
bestHit.hit.blockResult ?: return,
diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/InteractSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/InteractSim.kt
index 258156fb1..f09186529 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/InteractSim.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/InteractSim.kt
@@ -27,10 +27,10 @@ import com.lambda.interaction.construction.simulation.result.BuildResult
import com.lambda.interaction.construction.simulation.result.results.GenericResult
import com.lambda.interaction.construction.simulation.result.results.InteractResult
import com.lambda.interaction.construction.verify.TargetState
+import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest
import com.lambda.interaction.managers.rotating.Rotation
import com.lambda.interaction.managers.rotating.Rotation.Companion.rotation
import com.lambda.interaction.managers.rotating.RotationManager
-import com.lambda.interaction.managers.rotating.RotationRequest
import com.lambda.interaction.managers.rotating.visibilty.PlaceDirection
import com.lambda.interaction.managers.rotating.visibilty.VisibilityChecker.CheckedHit
import com.lambda.interaction.managers.rotating.visibilty.lookInDirection
@@ -130,19 +130,15 @@ class InteractSim private constructor(simInfo: InteractSimInfo)
buildConfig.pointSelection.select(validHits)?.let { checkedHit ->
val hitResult = checkedHit.hit.blockResult ?: return
- val swapStack = getSwapStack(item)
-
if (!placing) {
- if (swapStack == null) return
-
val interactContext = InteractContext(
hitResult,
- RotationRequest(checkedHit.rotation, this@InteractSim),
- swapStack.inventoryIndex,
+ rotationRequest { rotation(checkedHit.rotation) },
+ getSwapStack(item, supervisorScope)?.inventoryIndex ?: return,
pos,
state,
expectedState,
- false,
+ preProcessing.info,
fakePlayer.isSneaking,
true,
this@InteractSim
@@ -188,21 +184,14 @@ class InteractSim private constructor(simInfo: InteractSimInfo)
lookInDirection(PlaceDirection.fromRotation(rotatePlaceTest.rotation))
else rotatePlaceTest.rotation
- if (swapStack == null) return
- if (!swapStack.item.isEnabled(world.enabledFeatures)) {
- result(InteractResult.BlockFeatureDisabled(pos, swapStack))
- supervisorScope.cancel()
- return
- }
-
val interactContext = InteractContext(
hitResult,
- RotationRequest(rotationRequest, this@InteractSim),
- swapStack.inventoryIndex,
+ rotationRequest { rotation(rotationRequest) },
+ getSwapStack(item, supervisorScope)?.inventoryIndex ?: return,
pos,
state,
rotatePlaceTest.resultState,
- true,
+ preProcessing.info,
fakePlayer.isSneaking,
rotatePlaceTest.currentDirIsValid,
this@InteractSim
@@ -214,7 +203,12 @@ class InteractSim private constructor(simInfo: InteractSimInfo)
return
}
- private fun AutomatedSafeContext.getSwapStack(item: Item?): ItemStack? {
+ private fun AutomatedSafeContext.getSwapStack(item: Item?, supervisorScope: CoroutineScope): ItemStack? {
+ if (item?.isEnabled(world.enabledFeatures) == false) {
+ result(InteractResult.BlockFeatureDisabled(pos, item))
+ supervisorScope.cancel()
+ return null
+ }
val stackSelection = item?.select()
?: StackSelection.selectStack(0, sorter = compareByDescending { it.inventoryIndex == player.inventory.selectedSlot })
val containerSelection = selectContainer { ofAnyType(MaterialContainer.Rank.Hotbar) }
@@ -309,9 +303,8 @@ class InteractSim private constructor(simInfo: InteractSimInfo)
(pos.y - (hitbox.maxY - hitbox.minY)).floorToInt(),
pos.y
)
- }
- .flatten()
- .forEach { support ->
+ }.flatten()
+ .forEach { support ->
sim(support, blockState(support), TargetState.Empty)
}
}
diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt
index 7d2a273a0..17ecce3ef 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt
@@ -18,19 +18,15 @@
package com.lambda.interaction.construction.simulation.context
import com.lambda.context.Automated
-import com.lambda.graphics.renderer.esp.ShapeBuilder
-import com.lambda.interaction.material.StackSelection
-import com.lambda.interaction.managers.LogContext
-import com.lambda.interaction.managers.LogContext.Companion.LogContextBuilder
-import com.lambda.interaction.managers.LogContext.Companion.getLogContextBuilder
+import com.lambda.graphics.mc.TransientRegionESP
import com.lambda.interaction.managers.rotating.RotationRequest
+import com.lambda.interaction.material.StackSelection
import com.lambda.threading.runSafe
import com.lambda.util.BlockUtils.emptyState
import net.minecraft.block.BlockState
import net.minecraft.block.FallingBlock
import net.minecraft.util.hit.BlockHitResult
import net.minecraft.util.math.BlockPos
-import net.minecraft.util.math.Box
import java.awt.Color
import kotlin.math.sqrt
@@ -43,7 +39,7 @@ data class BreakContext(
val insideBlock: Boolean,
override var cachedState: BlockState,
private val automated: Automated
-) : BuildContext(), LogContext, Automated by automated {
+) : BuildContext(), Automated by automated {
private val baseColor = Color(222, 0, 0, 25)
private val sideColor = Color(222, 0, 0, 100)
@@ -63,25 +59,9 @@ data class BreakContext(
override val sorter get() = breakConfig.sorter
- override fun ShapeBuilder.buildRenderer() {
- val box = with(hitResult.pos) {
- Box(
- x - 0.05, y - 0.05, z - 0.05,
- x + 0.05, y + 0.05, z + 0.05,
- ).offset(hitResult.side.doubleVector.multiply(0.05))
- }
- box(box, baseColor, sideColor)
- }
-
- override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = {
- group("Break Context") {
- text(blockPos.getLogContextBuilder())
- text(hitResult.getLogContextBuilder())
- text(rotationRequest.getLogContextBuilder())
- value("Hotbar Index", hotbarIndex)
- value("Instant Break", instantBreak)
- value("Cached State", cachedState)
- value("Expected State", expectedState)
+ override fun render(esp: TransientRegionESP) {
+ esp.shapes(blockPos.x.toDouble(), blockPos.y.toDouble(), blockPos.z.toDouble()) {
+ box(blockPos, baseColor, sideColor)
}
}
}
diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/context/InteractContext.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/context/InteractContext.kt
index 0ccc754e6..cd78d3f52 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/context/InteractContext.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/context/InteractContext.kt
@@ -18,11 +18,8 @@
package com.lambda.interaction.construction.simulation.context
import com.lambda.context.Automated
-import com.lambda.graphics.renderer.esp.ShapeBuilder
-import com.lambda.interaction.managers.LogContext
-import com.lambda.interaction.managers.LogContext.Companion.LogContextBuilder
-import com.lambda.interaction.managers.LogContext.Companion.getLogContextBuilder
-import com.lambda.interaction.managers.Request.Companion.submit
+import com.lambda.graphics.mc.TransientRegionESP
+import com.lambda.interaction.construction.simulation.processing.PreProcessingInfo
import com.lambda.interaction.managers.hotbar.HotbarRequest
import com.lambda.interaction.managers.interacting.InteractRequest
import com.lambda.interaction.managers.rotating.RotationRequest
@@ -39,44 +36,33 @@ data class InteractContext(
override val blockPos: BlockPos,
override var cachedState: BlockState,
override val expectedState: BlockState,
- val placing: Boolean,
+ val preProcessingInfo: PreProcessingInfo,
val sneak: Boolean,
val currentDirIsValid: Boolean = false,
private val automated: Automated
-) : BuildContext(), LogContext, Automated by automated {
+) : BuildContext(), Automated by automated {
private val baseColor = Color(35, 188, 254, 50)
private val sideColor = Color(35, 188, 254, 100)
override val sorter get() = interactConfig.sorter
- override fun ShapeBuilder.buildRenderer() {
- val box = with(hitResult.pos) {
- Box(
- x - 0.05, y - 0.05, z - 0.05,
- x + 0.05, y + 0.05, z + 0.05,
- ).offset(hitResult.side.doubleVector.multiply(0.05))
+ override fun render(esp: TransientRegionESP) {
+ esp.shapes(hitResult.pos.x, hitResult.pos.y, hitResult.pos.z) {
+ val box = with(hitResult.pos) {
+ Box(
+ x - 0.05, y - 0.05, z - 0.05,
+ x + 0.05, y + 0.05, z + 0.05,
+ ).offset(hitResult.side.doubleVector.multiply(0.05))
+ }
+ box(box, baseColor, sideColor)
}
- box(box, baseColor, sideColor)
}
fun requestDependencies(request: InteractRequest): Boolean {
- val hotbarRequest = submit(HotbarRequest(hotbarIndex, this), false)
+ val hotbarRequest = HotbarRequest(hotbarIndex, this).submit(queueIfMismatchedStage = false)
val validRotation = if (request.interactConfig.rotate) {
- submit(rotationRequest, false).done && currentDirIsValid
+ rotationRequest.submit(queueIfMismatchedStage = false).done && currentDirIsValid
} else true
return hotbarRequest.done && validRotation
}
-
- override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = {
- group("Place Context") {
- text(blockPos.getLogContextBuilder())
- text(hitResult.getLogContextBuilder())
- text(rotationRequest.getLogContextBuilder())
- value("Hotbar Index", hotbarIndex)
- value("Cached State", cachedState)
- value("Expected State", expectedState)
- value("Sneak", sneak)
- value("Current Dir Is Valid", currentDirIsValid)
- }
- }
}
diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/PreProcessingInfo.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/PreProcessingInfo.kt
index a3957e185..099f7c900 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/PreProcessingInfo.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/PreProcessingInfo.kt
@@ -57,7 +57,7 @@ class PreProcessingInfoAccumulator(
override val sides: MutableSet = Direction.entries.toMutableSet(),
override var placing: Boolean = true,
override var noCaching: Boolean = false,
- var omitPlacement: Boolean = false
+ var omitInteraction: Boolean = false
) : PreProcessingInfo {
@InfoAccumulator
fun offerSurfaceScan(scan: SurfaceScan) {
@@ -106,7 +106,7 @@ class PreProcessingInfoAccumulator(
@InfoAccumulator
fun omitPlacement() {
- omitPlacement = true
+ omitInteraction = true
}
@InfoAccumulator
diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/ProcessorRegistry.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/ProcessorRegistry.kt
index edfe5143c..2efe12d79 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/ProcessorRegistry.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/ProcessorRegistry.kt
@@ -22,6 +22,7 @@ import com.lambda.context.SafeContext
import com.lambda.core.Loadable
import com.lambda.interaction.construction.simulation.SimDsl
import com.lambda.interaction.construction.verify.TargetState
+import com.lambda.util.BlockUtils.matches
import com.lambda.util.reflections.getInstances
import net.minecraft.block.BlockState
import net.minecraft.item.ItemStack
@@ -153,15 +154,21 @@ object ProcessorRegistry : Loadable {
with(processor) { preProcess(state, targetState, pos) }
}
}
- if (omitPlacement) return@run complete()
+ if (omitInteraction) return@run complete()
if (!stateProcessing) {
- if (!state.isReplaceable && state.block != expectedState.block) return@run null
- if (state.block != expectedState.block) propertyPreProcessors.forEach { processor ->
- if (processor.acceptsState(targetState))
- with(processor) { preProcess(state, expectedState) }
- } else propertyPostProcessors.forEach { processor ->
- if (processor.acceptsState(state, expectedState))
- with(processor) { preProcess(state, expectedState) }
+ if (state.block != expectedState.block) {
+ if (!state.isReplaceable) return@run null
+ propertyPreProcessors.forEach { processor ->
+ if (processor.acceptsState(targetState))
+ with(processor) { preProcess(state, expectedState) }
+ }
+ } else {
+ propertyPostProcessors.forEach { processor ->
+ if (processor.acceptsState(state, expectedState)) {
+ with(processor) { preProcess(state, expectedState) }
+ }
+ }
+ if (!state.matches(targetState, ignore)) return@run null
}
}
complete()
diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/post/StandardInteractPreProcessor.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/post/StandardInteractPostProcessor.kt
similarity index 89%
rename from src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/post/StandardInteractPreProcessor.kt
rename to src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/post/StandardInteractPostProcessor.kt
index 9c134f9a3..77e349353 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/post/StandardInteractPreProcessor.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/post/StandardInteractPostProcessor.kt
@@ -22,7 +22,9 @@ import com.lambda.interaction.construction.simulation.processing.ProcessorRegist
import com.lambda.interaction.construction.simulation.processing.PropertyPostProcessor
import net.minecraft.block.BlockState
-object StandardInteractPreProcessor : PropertyPostProcessor {
+// Collected using reflections and then accessed from a collection in ProcessorRegistry
+@Suppress("unused")
+object StandardInteractPostProcessor : PropertyPostProcessor {
override fun acceptsState(state: BlockState, targetState: BlockState) =
standardInteractProperties.any {
it in targetState && state.get(it) != targetState.get(it)
diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Contextual.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Contextual.kt
index 2fbae62ae..73accade9 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Contextual.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Contextual.kt
@@ -22,7 +22,6 @@ import com.lambda.interaction.construction.simulation.context.BreakContext
import com.lambda.interaction.construction.simulation.context.BuildContext
import com.lambda.interaction.construction.simulation.context.InteractContext
import com.lambda.interaction.managers.hotbar.HotbarManager
-import com.lambda.interaction.managers.rotating.Rotation.Companion.dist
import com.lambda.interaction.managers.rotating.RotationManager
import com.lambda.threading.runSafe
import com.lambda.util.BlockUtils
@@ -51,7 +50,7 @@ interface Contextual : ComparableResult {
ActionConfig.SortMode.Tool,
ActionConfig.SortMode.Closest -> it.sortDistance
ActionConfig.SortMode.Farthest -> -it.sortDistance
- ActionConfig.SortMode.Rotation -> it.rotationRequest.rotation.value?.dist(RotationManager.activeRotation)
+ ActionConfig.SortMode.Rotation -> it.rotationRequest dist RotationManager.activeRotation
ActionConfig.SortMode.Random -> it.random
}
}.thenByDescending {
diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Drawable.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Drawable.kt
index cd51bd728..ac339712a 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Drawable.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Drawable.kt
@@ -17,11 +17,11 @@
package com.lambda.interaction.construction.simulation.result
-import com.lambda.graphics.renderer.esp.ShapeBuilder
+import com.lambda.graphics.mc.TransientRegionESP
/**
* Represents a [BuildResult] that can be rendered in-game.
*/
interface Drawable {
- fun ShapeBuilder.buildRenderer()
+ fun render(esp: TransientRegionESP)
}
diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt
index 100724f93..4356dc53d 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt
@@ -22,7 +22,8 @@ import baritone.api.pathing.goals.GoalInverted
import com.lambda.context.Automated
import com.lambda.context.SafeContext
import com.lambda.graphics.renderer.esp.DirectionMask.mask
-import com.lambda.graphics.renderer.esp.ShapeBuilder
+import com.lambda.graphics.esp.ShapeScope
+import com.lambda.graphics.mc.TransientRegionESP
import com.lambda.interaction.construction.simulation.context.BreakContext
import com.lambda.interaction.construction.simulation.result.BuildResult
import com.lambda.interaction.construction.simulation.result.ComparableResult
@@ -55,8 +56,8 @@ sealed class BreakResult : BuildResult() {
) : Contextual, Drawable, BreakResult() {
override val rank = Rank.BreakSuccess
- override fun ShapeBuilder.buildRenderer() {
- with(context) { buildRenderer() }
+ override fun render(esp: TransientRegionESP) {
+ context.render(esp)
}
}
@@ -72,8 +73,10 @@ sealed class BreakResult : BuildResult() {
override val rank = Rank.BreakNotExposed
private val color = Color(46, 0, 0, 30)
- override fun ShapeBuilder.buildRenderer() {
- box(pos, color, color, side.mask)
+ override fun render(esp: TransientRegionESP) {
+ esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) {
+ box(pos, color, color, side.mask)
+ }
}
override fun compareResult(other: ComparableResult) =
@@ -119,8 +122,10 @@ sealed class BreakResult : BuildResult() {
override val rank = Rank.BreakSubmerge
private val color = Color(114, 27, 255, 100)
- override fun ShapeBuilder.buildRenderer() {
- box(pos, color, color)
+ override fun render(esp: TransientRegionESP) {
+ esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) {
+ box(pos, color, color)
+ }
}
}
@@ -135,13 +140,15 @@ sealed class BreakResult : BuildResult() {
override val rank = Rank.BreakIsBlockedByFluid
private val color = Color(50, 12, 112, 100)
- override fun ShapeBuilder.buildRenderer() {
- val center = pos.toCenterPos()
- val box = Box(
- center.x - 0.1, center.y - 0.1, center.z - 0.1,
- center.x + 0.1, center.y + 0.1, center.z + 0.1
- )
- box(box, color, color)
+ override fun render(esp: TransientRegionESP) {
+ esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) {
+ val center = pos.toCenterPos()
+ val box = Box(
+ center.x - 0.1, center.y - 0.1, center.z - 0.1,
+ center.x + 0.1, center.y + 0.1, center.z + 0.1
+ )
+ box(box, color, color)
+ }
}
}
@@ -157,8 +164,10 @@ sealed class BreakResult : BuildResult() {
override val goal = GoalInverted(GoalBlock(pos))
- override fun ShapeBuilder.buildRenderer() {
- box(pos, color, color)
+ override fun render(esp: TransientRegionESP) {
+ esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) {
+ box(pos, color, color)
+ }
}
}
diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt
index 6009ebaf4..dc8716f3f 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt
@@ -20,7 +20,8 @@ package com.lambda.interaction.construction.simulation.result.results
import baritone.api.pathing.goals.GoalNear
import com.lambda.context.Automated
import com.lambda.context.SafeContext
-import com.lambda.graphics.renderer.esp.ShapeBuilder
+import com.lambda.graphics.esp.ShapeScope
+import com.lambda.graphics.mc.TransientRegionESP
import com.lambda.interaction.construction.simulation.result.BuildResult
import com.lambda.interaction.construction.simulation.result.ComparableResult
import com.lambda.interaction.construction.simulation.result.Drawable
@@ -53,14 +54,16 @@ sealed class GenericResult : BuildResult() {
override val rank = Rank.NotVisible
private val color = Color(46, 0, 0, 80)
- override fun ShapeBuilder.buildRenderer() {
- val box = with(pos) {
- Box(
- x - 0.05, y - 0.05, z - 0.05,
- x + 0.05, y + 0.05, z + 0.05,
- ).offset(pos)
+ override fun render(esp: TransientRegionESP) {
+ esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) {
+ val box = with(pos) {
+ Box(
+ x - 0.05, y - 0.05, z - 0.05,
+ x + 0.05, y + 0.05, z + 0.05,
+ ).offset(pos)
+ }
+ box(box, color, color)
}
- box(box, color, color)
}
override fun compareResult(other: ComparableResult): Int {
@@ -98,13 +101,15 @@ sealed class GenericResult : BuildResult() {
context(automated: Automated, safeContext: SafeContext)
override fun resolve() = neededSelection.transfer(MainHandContainer)
- override fun ShapeBuilder.buildRenderer() {
- val center = pos.toCenterPos()
- val box = Box(
- center.x - 0.1, center.y - 0.1, center.z - 0.1,
- center.x + 0.1, center.y + 0.1, center.z + 0.1
- )
- box(box, color, color)
+ override fun render(esp: TransientRegionESP) {
+ esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) {
+ val center = pos.toCenterPos()
+ val box = Box(
+ center.x - 0.1, center.y - 0.1, center.z - 0.1,
+ center.x + 0.1, center.y + 0.1, center.z + 0.1
+ )
+ box(box, color, color)
+ }
}
}
@@ -129,13 +134,15 @@ sealed class GenericResult : BuildResult() {
override val goal = GoalNear(pos, 3)
- override fun ShapeBuilder.buildRenderer() {
- val center = pos.toCenterPos()
- val box = Box(
- center.x - 0.1, center.y - 0.1, center.z - 0.1,
- center.x + 0.1, center.y + 0.1, center.z + 0.1
- )
- box(box, color, color)
+ override fun render(esp: TransientRegionESP) {
+ esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) {
+ val center = pos.toCenterPos()
+ val box = Box(
+ center.x - 0.1, center.y - 0.1, center.z - 0.1,
+ center.x + 0.1, center.y + 0.1, center.z + 0.1
+ )
+ box(box, color, color)
+ }
}
override fun compareResult(other: ComparableResult): Int {
diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/InteractResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/InteractResult.kt
index 653fa75ff..b4537bcec 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/InteractResult.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/InteractResult.kt
@@ -19,7 +19,7 @@ package com.lambda.interaction.construction.simulation.result.results
import baritone.api.pathing.goals.GoalBlock
import baritone.api.pathing.goals.GoalInverted
-import com.lambda.graphics.renderer.esp.ShapeBuilder
+import com.lambda.graphics.mc.TransientRegionESP
import com.lambda.interaction.construction.simulation.context.InteractContext
import com.lambda.interaction.construction.simulation.result.BuildResult
import com.lambda.interaction.construction.simulation.result.Contextual
@@ -29,8 +29,8 @@ import com.lambda.interaction.construction.simulation.result.Navigable
import com.lambda.interaction.construction.simulation.result.Rank
import net.minecraft.block.BlockState
import net.minecraft.entity.Entity
+import net.minecraft.item.Item
import net.minecraft.item.ItemPlacementContext
-import net.minecraft.item.ItemStack
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Box
import net.minecraft.util.math.Direction
@@ -57,8 +57,8 @@ sealed class InteractResult : BuildResult() {
) : Contextual, Drawable, InteractResult() {
override val rank = Rank.PlaceSuccess
- override fun ShapeBuilder.buildRenderer() {
- with(context) { buildRenderer() }
+ override fun render(esp: TransientRegionESP) {
+ context.render(esp)
}
}
@@ -82,14 +82,16 @@ sealed class InteractResult : BuildResult() {
override val rank = Rank.PlaceNoIntegrity
private val color = Color(252, 3, 3, 100)
- override fun ShapeBuilder.buildRenderer() {
- val box = with(simulated.hitPos) {
- Box(
- x - 0.05, y - 0.05, z - 0.05,
- x + 0.05, y + 0.05, z + 0.05,
- ).offset(simulated.side.doubleVector.multiply(0.05))
+ override fun render(esp: TransientRegionESP) {
+ esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) {
+ val box = with(simulated.hitPos) {
+ Box(
+ x - 0.05, y - 0.05, z - 0.05,
+ x + 0.05, y + 0.05, z + 0.05,
+ ).offset(simulated.side.doubleVector.multiply(0.05))
+ }
+ box(box, color, color)
}
- box(box, color, color)
}
}
@@ -119,14 +121,16 @@ sealed class InteractResult : BuildResult() {
override val rank = Rank.PlaceBlockedByEntity
private val color = Color(252, 3, 3, 100)
- override fun ShapeBuilder.buildRenderer() {
- val box = with(hitPos) {
- Box(
- x - 0.05, y - 0.05, z - 0.05,
- x + 0.05, y + 0.05, z + 0.05,
- ).offset(side.doubleVector.multiply(0.05))
+ override fun render(esp: TransientRegionESP) {
+ esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) {
+ val box = with(hitPos) {
+ Box(
+ x - 0.05, y - 0.05, z - 0.05,
+ x + 0.05, y + 0.05, z + 0.05,
+ ).offset(side.doubleVector.multiply(0.05))
+ }
+ box(box, color, color)
}
- box(box, color, color)
}
}
@@ -163,7 +167,7 @@ sealed class InteractResult : BuildResult() {
*/
data class BlockFeatureDisabled(
override val pos: BlockPos,
- val itemStack: ItemStack,
+ val item: Item,
) : InteractResult() {
override val rank = Rank.PlaceBlockFeatureDisabled
}
diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/PreSimResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/PreSimResult.kt
index c92bda7b1..baa2a2513 100644
--- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/PreSimResult.kt
+++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/PreSimResult.kt
@@ -18,7 +18,8 @@
package com.lambda.interaction.construction.simulation.result.results
import baritone.api.pathing.goals.GoalBlock
-import com.lambda.graphics.renderer.esp.ShapeBuilder
+import com.lambda.graphics.esp.ShapeScope
+import com.lambda.graphics.mc.TransientRegionESP
import com.lambda.interaction.construction.simulation.result.BuildResult
import com.lambda.interaction.construction.simulation.result.ComparableResult
import com.lambda.interaction.construction.simulation.result.Drawable
@@ -55,8 +56,10 @@ sealed class PreSimResult : BuildResult() {
override val goal = GoalBlock(pos)
- override fun ShapeBuilder.buildRenderer() {
- box(pos, color, color)
+ override fun render(esp: TransientRegionESP) {
+ esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) {
+ box(pos, color, color)
+ }
}
override fun compareResult(other: ComparableResult) =
@@ -77,8 +80,10 @@ sealed class PreSimResult : BuildResult() {
override val rank = Rank.BreakRestricted
private val color = Color(255, 0, 0, 100)
- override fun ShapeBuilder.buildRenderer() {
- box(pos, color, color)
+ override fun render(esp: TransientRegionESP) {
+ esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) {
+ box(pos, color, color)
+ }
}
}
@@ -95,8 +100,10 @@ sealed class PreSimResult : BuildResult() {
override val rank get() = Rank.BreakNoPermission
private val color = Color(255, 0, 0, 100)
- override fun ShapeBuilder.buildRenderer() {
- box(pos, color, color)
+ override fun render(esp: TransientRegionESP) {
+ esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) {
+ box(pos, color, color)
+ }
}
}
@@ -111,8 +118,10 @@ sealed class PreSimResult : BuildResult() {
override val rank = Rank.OutOfWorld
private val color = Color(3, 148, 252, 100)
- override fun ShapeBuilder.buildRenderer() {
- box(pos, color, color)
+ override fun render(esp: TransientRegionESP) {
+ esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) {
+ box(pos, color, color)
+ }
}
}
@@ -129,8 +138,10 @@ sealed class PreSimResult : BuildResult() {
override val rank = Rank.Unbreakable
private val color = Color(11, 11, 11, 100)
- override fun ShapeBuilder.buildRenderer() {
- box(pos, color, color)
+ override fun render(esp: TransientRegionESP) {
+ esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) {
+ box(pos, color, color)
+ }
}
}
}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/managers/DebugLogger.kt b/src/main/kotlin/com/lambda/interaction/managers/DebugLogger.kt
deleted file mode 100644
index 0e6f85b67..000000000
--- a/src/main/kotlin/com/lambda/interaction/managers/DebugLogger.kt
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright 2025 Lambda
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package com.lambda.interaction.managers
-
-import com.lambda.gui.components.ClickGuiLayout
-import com.lambda.gui.dsl.ImGuiBuilder
-import com.lambda.interaction.managers.LogContext.Companion.buildLogContext
-import com.lambda.module.hud.ManagerDebugLoggers.autoScroll
-import com.lambda.module.hud.ManagerDebugLoggers.maxLogEntries
-import com.lambda.module.hud.ManagerDebugLoggers.showDebug
-import com.lambda.module.hud.ManagerDebugLoggers.showError
-import com.lambda.module.hud.ManagerDebugLoggers.showSuccess
-import com.lambda.module.hud.ManagerDebugLoggers.showSystem
-import com.lambda.module.hud.ManagerDebugLoggers.showWarning
-import com.lambda.module.hud.ManagerDebugLoggers.wrapText
-import com.lambda.util.math.a
-import imgui.ImGui
-import imgui.flag.ImGuiCol
-import imgui.flag.ImGuiWindowFlags
-import java.awt.Color
-import java.util.*
-
-/**
- * A simple logger that can be used to display information about what is happening within the managers.
- */
-class DebugLogger(
- val name: String
-) {
- val logs = LinkedList()
-
- private fun log(message: String, logColor: LogType, extraContext: List) {
- if (logs.size + 1 > maxLogEntries) {
- logs.removeFirst()
- }
- logs.add(LogEntry(message, logColor, extraContext.filterNotNull()))
- }
-
- fun debug(message: String) = log(message, LogType.Debug, emptyList())
- fun debug(message: String, vararg extraContext: String?) = log(message, LogType.Debug, extraContext.toList())
- fun debug(message: String, vararg extraContext: LogContext?) =
- log(message, LogType.Debug, extraContext.filterNotNull().map { buildLogContext(builder = it.getLogContextBuilder()) })
- fun success(message: String) = log(message, LogType.Success, emptyList())
- fun success(message: String, vararg extraContext: String?) = log(message, LogType.Success, extraContext.toList())
- fun success(message: String, vararg extraContext: LogContext?) =
- log(message, LogType.Success, extraContext.filterNotNull().map { buildLogContext(builder = it.getLogContextBuilder()) })
- fun warning(message: String) = log(message, LogType.Warning, emptyList())
- fun warning(message: String, vararg extraContext: String?) = log(message, LogType.Warning, extraContext.toList())
- fun warning(message: String, vararg extraContext: LogContext?) =
- log(message, LogType.Warning, extraContext.filterNotNull().map { buildLogContext(builder = it.getLogContextBuilder()) })
- fun error(message: String) = log(message, LogType.Error, emptyList())
- fun error(message: String, vararg extraContext: String?) = log(message, LogType.Error, extraContext.toList())
- fun error(message: String, vararg extraContext: LogContext?) =
- log(message, LogType.Error, extraContext.filterNotNull().map { buildLogContext(builder = it.getLogContextBuilder()) })
- fun system(message: String) = log(message, LogType.System, emptyList())
- fun system(message: String, vararg extraContext: String?) = log(message, LogType.System, extraContext.toList())
- fun system(message: String, vararg extraContext: LogContext?) =
- log(message, LogType.System, extraContext.filterNotNull().map { buildLogContext(builder = it.getLogContextBuilder()) })
-
- fun ImGuiBuilder.buildLayout() {
- ImGui.setNextWindowSizeConstraints(300f, 400f, windowViewport.workSizeX, windowViewport.workSizeY)
- var flags = if (autoScroll) ImGuiWindowFlags.NoScrollbar or ImGuiWindowFlags.NoScrollWithMouse else 0
- flags = flags or ImGuiWindowFlags.NoBackground
- if (!ClickGuiLayout.open) flags = flags or ImGuiWindowFlags.NoInputs
- child("Log Content", border = false, windowFlags = flags) {
- if (wrapText) ImGui.pushTextWrapPos()
-
- logs.forEach { logEntry ->
- if (shouldDisplay(logEntry)) {
- val type = logEntry.type
- val (logTypeStr, color) = when (type) {
- LogType.Debug -> Pair("[DEBUG]", type.color)
- LogType.Success -> Pair("[SUCCESS]", type.color)
- LogType.Warning -> Pair("[WARNING]", type.color)
- LogType.Error -> Pair("[ERROR]", type.color)
- LogType.System -> Pair("[SYSTEM]", type.color)
- }
-
- val floats = floatArrayOf(0f, 0f, 0f)
- val (r, g, b) = color.getColorComponents(floats)
- ImGui.pushStyleColor(ImGuiCol.Text, r, g, b, color.a.toFloat())
- if (logEntry.type == LogType.System) {
- text("$logTypeStr ${logEntry.message}")
- } else {
- treeNode("$logTypeStr ${logEntry.message}", logEntry.uuid) {
- logEntry.extraContext
- .filterNotNull()
- .forEach {
- text(it)
- }
- }
- }
- ImGui.popStyleColor()
- }
- }
-
- if (wrapText) ImGui.popTextWrapPos()
-
- if (autoScroll) {
- ImGui.setScrollHereY(1f)
- }
- }
- }
-
- fun shouldDisplay(logEntry: LogEntry) =
- when (logEntry.type) {
- LogType.Debug -> showDebug
- LogType.Success -> showSuccess
- LogType.Warning -> showWarning
- LogType.Error -> showError
- LogType.System -> showSystem
- }
-
- fun clear() = logs.clear()
-
- class LogEntry(
- val message: String,
- val type: LogType,
- val extraContext: Collection
- ) {
- val uuid = UUID.randomUUID().toString()
- }
-
- enum class LogType(val color: Color) {
- Debug(Color(1.0f, 1.0f, 1.0f, 1f)),
- Success(Color(0.28f, 1.0f, 0.28f, 1f)),
- Warning(Color(1.0f, 1.0f, 0.28f, 1f)),
- Error(Color(1.0f, 0.28f, 0.28f, 1f)),
- System(Color(0.28f, 0.28f, 1.0f, 1f))
- }
-}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/managers/LogContext.kt b/src/main/kotlin/com/lambda/interaction/managers/LogContext.kt
deleted file mode 100644
index a2df2d272..000000000
--- a/src/main/kotlin/com/lambda/interaction/managers/LogContext.kt
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright 2025 Lambda
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package com.lambda.interaction.managers
-
-import net.minecraft.util.hit.BlockHitResult
-import net.minecraft.util.math.BlockPos
-
-interface LogContext {
- fun getLogContextBuilder(): LogContextBuilder.() -> Unit
-
- companion object {
- @DslMarker
- private annotation class LogContextDsl
-
- fun BlockPos.getLogContextBuilder(): LogContextBuilder.() -> Unit {
- val pos = if (this is BlockPos.Mutable) toImmutable() else this
- return { value("Block Pos", pos.toShortString()) }
- }
-
- fun BlockHitResult.getLogContextBuilder(): LogContextBuilder.() -> Unit = {
- group("Block Hit Result") {
- value("Side", side)
- text(blockPos.getLogContextBuilder())
- value("Pos", pos)
- }
- }
-
- @LogContextDsl
- fun buildLogContext(tabMin: Int = 0, builder: LogContextBuilder.() -> Unit): String =
- LogContextBuilder(tabMin).apply(builder).build()
-
- @LogContextDsl
- private fun LogContextBuilder.build() = logContext
-
- @LogContextDsl
- class LogContextBuilder(val tabMin: Int = 0) {
- var logContext = ""
-
- private var tabs = tabMin
-
- @LogContextDsl
- fun sameLine() {
- val length = logContext.length
- val first = logContext[length - 2]
- val second = logContext[length - 1]
- if (first != '\\' || second != 'n') throw IllegalStateException("String does not end in a new line")
- logContext = logContext.removeRange(length - 2, length - 1)
- }
-
- @LogContextDsl
- fun text(text: String) {
- repeat(tabs) {
- logContext += "----"
- }
- logContext += "$text\n"
- }
-
- @LogContextDsl
- fun text(builder: LogContextBuilder.() -> Unit) {
- logContext += LogContextBuilder(tabs).apply(builder).build()
- }
-
- @LogContextDsl
- fun value(name: String, value: Any) {
- text("$name: $value")
- }
-
- @LogContextDsl
- fun value(name: String, value: String) {
- text("$name: $value")
- }
-
- @LogContextDsl
- fun group(name: String, builder: LogContextBuilder.() -> Unit) {
- text("$name {")
- logContext += LogContextBuilder(tabs + 1).apply(builder).build()
- text("}")
- }
-
- @LogContextDsl
- fun pushTab() {
- tabs++
- }
-
- @LogContextDsl
- fun popTab() {
- tabs--
- if (tabs < tabMin) throw IllegalStateException("Cannot reduce tabs beneath the minimum tab count")
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/managers/ManagerUtils.kt b/src/main/kotlin/com/lambda/interaction/managers/ManagerUtils.kt
index ef21c2a4b..ec86a2392 100644
--- a/src/main/kotlin/com/lambda/interaction/managers/ManagerUtils.kt
+++ b/src/main/kotlin/com/lambda/interaction/managers/ManagerUtils.kt
@@ -17,7 +17,6 @@
package com.lambda.interaction.managers
-import com.lambda.event.Event
import com.lambda.util.reflections.getInstances
import net.minecraft.util.math.BlockPos
@@ -26,14 +25,6 @@ object ManagerUtils {
val accumulatedManagerPriority = managers.map { it.stagePriority }.reduce { acc, priority -> acc + priority }
val positionBlockingManagers = getInstances()
- fun DebugLogger.newTick() =
- system("------------- New Tick -------------")
-
- fun DebugLogger.newStage(tickStage: Event?) =
- system("Tick stage ${tickStage?.run { this.toLogContext() }}")
-
- fun Event.toLogContext() = this::class.qualifiedName?.substringAfter("com.lambda.event.events.")
-
fun isPosBlocked(pos: BlockPos) =
positionBlockingManagers.any { pos in it.blockedPositions }
}
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/managers/Request.kt b/src/main/kotlin/com/lambda/interaction/managers/Request.kt
index 731ba6ac0..1400274a8 100644
--- a/src/main/kotlin/com/lambda/interaction/managers/Request.kt
+++ b/src/main/kotlin/com/lambda/interaction/managers/Request.kt
@@ -37,9 +37,4 @@ abstract class Request : Automated {
abstract val done: Boolean
abstract fun submit(queueIfMismatchedStage: Boolean = true): Request
-
- companion object {
- fun submit(request: Request, queueIfClosed: Boolean = true) =
- request.submit(queueIfClosed)
- }
}
diff --git a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakInfo.kt b/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakInfo.kt
index 0c216040b..0a0c81804 100644
--- a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakInfo.kt
+++ b/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakInfo.kt
@@ -20,8 +20,6 @@ package com.lambda.interaction.managers.breaking
import com.lambda.context.SafeContext
import com.lambda.interaction.construction.simulation.context.BreakContext
import com.lambda.interaction.managers.ActionInfo
-import com.lambda.interaction.managers.LogContext
-import com.lambda.interaction.managers.LogContext.Companion.LogContextBuilder
import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.Primary
import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.Rebreak
import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.RedundantSecondary
@@ -44,7 +42,7 @@ data class BreakInfo(
override var context: BreakContext,
var type: BreakType,
var request: BreakRequest
-) : ActionInfo, LogContext {
+) : ActionInfo {
// Delegates
val breakConfig get() = request.breakConfig
override val pendingInteractionsList get() = request.pendingInteractions
@@ -164,27 +162,5 @@ data class BreakInfo(
}
}
- override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = {
- group("Break Info") {
- value("Type", type)
- text(context.getLogContextBuilder())
- group("Details") {
- value("Should Progress", shouldProgress)
- value("Rebreak Potential", rebreakPotential)
- text(swapInfo.getLogContextBuilder())
- value("Swap Stack", swapStack)
- value("Updated This Tick", updatedThisTick)
- value("Updated Pre-Processing This Tick", updatedPreProcessingThisTick)
- value("Progressed This Tick", progressedThisTick)
- value("Breaking", breaking)
- value("Abandoned", abandoned)
- value("Breaking Ticks", breakingTicks)
- value("Sounds Cooldown", soundsCooldown)
- value("Vanilla Instant Breakable", vanillaInstantBreakable)
- value("Rebreakable", rebreakable)
- }
- }
- }
-
override fun toString() = "$type, ${context.cachedState}, ${context.blockPos}"
}
diff --git a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakManager.kt b/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakManager.kt
index e66f188c3..252bad051 100644
--- a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakManager.kt
+++ b/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakManager.kt
@@ -19,8 +19,6 @@ package com.lambda.interaction.managers.breaking
import com.lambda.context.AutomatedSafeContext
import com.lambda.context.SafeContext
-import com.lambda.event.Event
-import com.lambda.event.EventFlow.post
import com.lambda.event.events.ConnectionEvent
import com.lambda.event.events.EntityEvent
import com.lambda.event.events.TickEvent
@@ -34,11 +32,8 @@ import com.lambda.interaction.construction.simulation.BuildSimulator.simulate
import com.lambda.interaction.construction.simulation.context.BreakContext
import com.lambda.interaction.construction.simulation.result.results.BreakResult
import com.lambda.interaction.construction.verify.TargetState
-import com.lambda.interaction.managers.Logger
import com.lambda.interaction.managers.Manager
import com.lambda.interaction.managers.ManagerUtils.isPosBlocked
-import com.lambda.interaction.managers.ManagerUtils.newStage
-import com.lambda.interaction.managers.ManagerUtils.newTick
import com.lambda.interaction.managers.PositionBlocking
import com.lambda.interaction.managers.breaking.BreakConfig.BreakConfirmationMode
import com.lambda.interaction.managers.breaking.BreakConfig.BreakMode
@@ -76,7 +71,6 @@ import com.lambda.interaction.managers.interacting.InteractManager
import com.lambda.interaction.managers.rotating.RotationRequest
import com.lambda.interaction.material.StackSelection
import com.lambda.interaction.material.StackSelection.Companion.select
-import com.lambda.module.hud.ManagerDebugLoggers.breakManagerLogger
import com.lambda.threading.runSafeAutomated
import com.lambda.util.BlockUtils.blockState
import com.lambda.util.BlockUtils.calcItemBlockBreakingDelta
@@ -110,13 +104,11 @@ import kotlin.math.max
object BreakManager : Manager(
0,
onOpen = {
- if (activeInfos.isNotEmpty() || breaks.isNotEmpty())
- BreakManager.logger.newStage(BreakManager.tickStage)
processRequest(activeRequest)
simulateAbandoned()
},
onClose = { checkForCancels() }
-), PositionBlocking, Logger {
+), PositionBlocking {
private val breakInfos = arrayOfNulls(2)
private val activeInfos
@@ -145,7 +137,6 @@ object BreakManager : Manager(
}?.context?.itemSelection
?: StackSelection.EVERYTHING.select()
- private val pendingBreakCount get() = activeInfos.count() + pendingActions.size
override val blockedPositions
get() = activeInfos.map { it.context.blockPos } + pendingActions.map { it.context.blockPos }
@@ -169,14 +160,11 @@ object BreakManager : Manager(
field = value
}
- override val logger = breakManagerLogger
-
override fun load(): String {
super.load()
listen(priority = Int.MAX_VALUE) {
if (activeInfos.isEmpty() && breaks.isEmpty()) return@listen
- logger.newTick()
}
listen(priority = Int.MIN_VALUE) {
@@ -230,7 +218,7 @@ object BreakManager : Manager(
?.internalOnItemDrop(it.entity)
}
- onDynamicRender { render ->
+ onDynamicRender { esp ->
val activeStack = breakInfos
.filterNotNull()
.firstOrNull()?.swapStack ?: return@onDynamicRender
@@ -275,15 +263,18 @@ object BreakManager : Manager(
)
else config.staticOutlineColor
- info.context.cachedState.getOutlineShape(world, info.context.blockPos).boundingBoxes.map {
- it.offset(info.context.blockPos)
- }.forEach boxes@ { box ->
- val animationMode = info.breakConfig.animation
- val currentProgress = interpolateBox(box, currentProgress, animationMode)
- val nextProgress = interpolateBox(box, nextTicksProgress, animationMode)
- val dynamicAABB = DynamicAABB().update(currentProgress).update(nextProgress)
- if (config.fill) render.filled(dynamicAABB, fillColor)
- if (config.outline) render.outline(dynamicAABB, outlineColor)
+ val pos = info.context.blockPos
+ esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) {
+ info.context.cachedState.getOutlineShape(world, pos).boundingBoxes.map {
+ it.offset(pos)
+ }.forEach boxes@{ box ->
+ val animationMode = info.breakConfig.animation
+ val currentProgressBox = interpolateBox(box, currentProgress, animationMode)
+ val nextProgressBox = interpolateBox(box, nextTicksProgress, animationMode)
+ val dynamicAABB = DynamicAABB().update(currentProgressBox).update(nextProgressBox)
+ if (config.fill) filled(dynamicAABB, fillColor)
+ if (config.outline) outline(dynamicAABB, outlineColor)
+ }
}
}
}
@@ -305,7 +296,7 @@ object BreakManager : Manager(
* @see processRequest
*/
override fun AutomatedSafeContext.handleRequest(request: BreakRequest) {
- if (activeRequest != null || request.contexts.isEmpty()) return
+ if (!request.buildConfig.breakBlocks || activeRequest != null || request.contexts.isEmpty()) return
if (InteractManager.activeThisTick) return
activeRequest = request
@@ -327,7 +318,6 @@ object BreakManager : Manager(
*/
private fun SafeContext.processRequest(request: BreakRequest?) {
request?.let { request ->
- logger.debug("Processing request", request)
if (request.fresh) populateFrom(request)
}
@@ -357,10 +347,7 @@ object BreakManager : Manager(
if (noNew && noProgression) break
}
- if (breaks.isEmpty()) {
- if (activeRequest != null) logger.debug("Clearing active request", activeRequest)
- activeRequest = null
- }
+ if (breaks.isEmpty()) activeRequest = null
if (breaksThisTick > 0 || activeInfos.isNotEmpty()) {
activeThisTick = true
}
@@ -376,8 +363,6 @@ object BreakManager : Manager(
* @see BreakInfo.updateInfo
*/
private fun SafeContext.populateFrom(request: BreakRequest) = request.runSafeAutomated {
- logger.debug("Populating from request", request)
-
// Sanitize the new breaks
val newBreaks = request.contexts
.distinctBy { it.blockPos }
@@ -396,7 +381,6 @@ object BreakManager : Manager(
if (info.updatedThisTick && info.type != RedundantSecondary && !info.abandoned) return@forEach
- logger.debug("Updating info", info, ctx)
when {
info.type == RedundantSecondary -> info.request.onStart?.invoke(this, info.context.blockPos)
info.abandoned -> {
@@ -413,8 +397,6 @@ object BreakManager : Manager(
.take(buildConfig.maxPendingActions - request.pendingInteractions.size.coerceAtLeast(0))
.toMutableList()
- logger.debug("${breaks.size} unprocessed breaks")
-
maxBreaksThisTick = breakConfig.breaksPerTick
}
@@ -445,7 +427,6 @@ object BreakManager : Manager(
info.breakConfig.rotate
}?.let { info ->
val rotation = info.context.rotationRequest
- logger.debug("Requesting rotation", rotation)
rotation.submit(false)
}
@@ -466,7 +447,6 @@ object BreakManager : Manager(
).submit(false)
}
- logger.debug("Submitted hotbar request", hotbarRequest)
return
}
@@ -526,7 +506,6 @@ object BreakManager : Manager(
if (!primaryInfo.breaking) return null
- logger.debug("Transforming primary to secondary", primaryInfo)
secondaryBreak = primaryInfo.apply { type = Secondary }
secondaryBreak?.stopBreakPacket()
return@let
@@ -534,7 +513,6 @@ object BreakManager : Manager(
primaryBreak = breakInfo
setPendingConfigs()
- logger.success("Initialized break info", breakInfo)
return primaryBreak
}
@@ -624,13 +602,11 @@ object BreakManager : Manager(
context(_: SafeContext)
private fun BreakInfo.updatePreProcessing() = request.runSafeAutomated {
- logger.debug("Updating pre-processing", this@updatePreProcessing)
-
shouldProgress = !progressedThisTick
&& tickStage in breakConfig.tickStageMask
&& (rotated || type != Primary)
- if (updatedPreProcessingThisTick) return
+ if (updatedPreProcessingThisTick) return@runSafeAutomated
updatedPreProcessingThisTick = true
swapStack = player.inventory.getStack(context.hotbarIndex)
@@ -653,7 +629,6 @@ object BreakManager : Manager(
if (type == RedundantSecondary || abandoned) return@with
when (type) {
Primary -> {
- logger.warning("Cancelling break", this@cancelBreak)
nullify()
setBreakingTextureStage(player, world, -1)
abortBreakPacket()
@@ -661,14 +636,10 @@ object BreakManager : Manager(
}
Secondary -> {
if (breakConfig.unsafeCancels) {
- logger.warning("Making break redundant", this@cancelBreak)
type = RedundantSecondary
setBreakingTextureStage(player, world, -1)
request.onCancel?.invoke(this, context.blockPos)
- } else {
- logger.warning("Abandoning break", this@cancelBreak)
- abandoned = true
- }
+ } else abandoned = true
}
else -> {}
}
@@ -716,7 +687,6 @@ object BreakManager : Manager(
if (blockState.isEmpty) {
info.nullify()
info.request.onCancel?.invoke(this, ctx.blockPos)
- logger.warning("Block state was unexpectedly empty", info)
return
}
@@ -725,7 +695,6 @@ object BreakManager : Manager(
info.breakingTicks++
val breakDelta = blockState.calcBreakDelta(ctx.blockPos)
val progress = breakDelta * (info.breakingTicks - breakConfig.fudgeFactor)
- logger.debug("${info.type} progress: $progress, breaking ticks: ${info.breakingTicks}", info)
if (breakConfig.sounds) {
if (info.soundsCooldown % 4.0f == 0.0f) {
@@ -745,7 +714,7 @@ object BreakManager : Manager(
}
if (breakConfig.particles) {
- mc.particleManager.addBlockBreakingParticles(ctx.blockPos, hitResult.side)
+ world.spawnBlockBreakingParticle(ctx.blockPos, hitResult.side)
}
if (breakConfig.breakingTexture) {
@@ -756,7 +725,6 @@ object BreakManager : Manager(
if (progress >= info.getBreakThreshold()) {
if (info.swapInfo.swap && !swapped) return
- logger.success("Breaking", info)
onBlockBreak(info)
if (info.type == Primary) info.stopBreakPacket()
if (swing.isEnabled() && swing != BreakConfig.SwingMode.Start)
@@ -779,10 +747,8 @@ object BreakManager : Manager(
val ctx = info.context
if (info.rebreakPotential.isPossible()) {
- logger.debug("Handling potential rebreak")
when (val rebreakResult = RebreakHandler.handleUpdate(info.context, info.request)) {
is RebreakResult.StillBreaking -> {
- logger.debug("Rebreak not complete", info)
primaryBreak = rebreakResult.breakInfo.apply {
type = Primary
RebreakHandler.clearRebreak()
@@ -796,7 +762,6 @@ object BreakManager : Manager(
return true
}
is RebreakResult.Rebroke -> {
- logger.debug("Rebroke", info)
info.type = Rebreak
info.nullify()
info.request.onReBreak?.invoke(this, ctx.blockPos)
@@ -832,12 +797,11 @@ object BreakManager : Manager(
val instantBreakable = progress >= info.getBreakThreshold()
if (instantBreakable) {
- logger.success("Instant breaking", info)
info.vanillaInstantBreakable = progress >= 1
onBlockBreak(info)
- if (!info.vanillaInstantBreakable) breakCooldown = breakConfig.breakDelay + 1
+ if (!info.vanillaInstantBreakable)
+ breakCooldown = if (breakConfig.breakDelay == 0) 0 else breakConfig.breakDelay + 1
} else {
- logger.debug("Starting break", info)
info.apply {
breaking = true
breakingTicks = 1
diff --git a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakRequest.kt b/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakRequest.kt
index 32a50031d..daa9b321f 100644
--- a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakRequest.kt
+++ b/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakRequest.kt
@@ -27,8 +27,6 @@ import com.lambda.interaction.construction.simulation.result.BuildResult
import com.lambda.interaction.construction.simulation.result.Dependent
import com.lambda.interaction.construction.simulation.result.results.BreakResult
import com.lambda.interaction.construction.verify.TargetState
-import com.lambda.interaction.managers.LogContext
-import com.lambda.interaction.managers.LogContext.Companion.LogContextBuilder
import com.lambda.interaction.managers.Request
import com.lambda.interaction.managers.breaking.BreakRequest.Companion.breakRequest
import com.lambda.threading.runSafe
@@ -55,7 +53,7 @@ data class BreakRequest private constructor(
val pendingInteractions: MutableCollection,
private val automated: Automated,
override val nowOrNothing: Boolean = false
-) : Request(), LogContext, Automated by automated {
+) : Request(), Automated by automated {
override val requestId = ++requestCount
override val tickStageMask get() = breakConfig.tickStageMask
@@ -70,22 +68,6 @@ data class BreakRequest private constructor(
override val done: Boolean
get() = runSafe { contexts.all { blockState(it.blockPos).isEmpty } } == true
- override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = {
- group("Break Request") {
- value("Request ID", requestId)
- value("Contexts", contexts.size)
- group("Callbacks") {
- value("onStart", onStart != null)
- value("onUpdate", onUpdate != null)
- value("onStop", onStop != null)
- value("onCancel", onCancel != null)
- value("onItemDrop", onItemDrop != null)
- value("onReBreakStart", onReBreakStart != null)
- value("onReBreak", onReBreak != null)
- }
- }
- }
-
@DslMarker
annotation class BreakRequestDsl
diff --git a/src/main/kotlin/com/lambda/interaction/managers/breaking/BrokenBlockHandler.kt b/src/main/kotlin/com/lambda/interaction/managers/breaking/BrokenBlockHandler.kt
index 5188dcfee..454524216 100644
--- a/src/main/kotlin/com/lambda/interaction/managers/breaking/BrokenBlockHandler.kt
+++ b/src/main/kotlin/com/lambda/interaction/managers/breaking/BrokenBlockHandler.kt
@@ -60,11 +60,9 @@ object BrokenBlockHandler : PostActionHandler() {
if (!info.broken) {
val message = "${info.type} ${info::class.simpleName} at ${info.context.blockPos.toShortString()} timed out with cached state ${info.context.cachedState}"
- BreakManager.logger.error(message)
if (managerDebugLogs) this@BrokenBlockHandler.warn(message)
} else if (!DEFAULT.ignoreItemDropWarnings) {
val message = "${info.type} ${info::class.simpleName}'s item drop at ${info.context.blockPos.toShortString()} timed out"
- BreakManager.logger.warning(message)
if (managerDebugLogs) this@BrokenBlockHandler.warn(message)
}
@@ -97,7 +95,6 @@ object BrokenBlockHandler : PostActionHandler() {
pending.context.cachedState = event.newState
} else {
val message = "Broken block at ${event.pos.toShortString()} was rejected with ${event.newState} instead of ${pending.context.cachedState.emptyState}"
- BreakManager.logger.error(message)
if (managerDebugLogs) this@BrokenBlockHandler.warn(message)
pending.stopPending()
}
diff --git a/src/main/kotlin/com/lambda/interaction/managers/breaking/SwapInfo.kt b/src/main/kotlin/com/lambda/interaction/managers/breaking/SwapInfo.kt
index 89bb61e16..0632f190e 100644
--- a/src/main/kotlin/com/lambda/interaction/managers/breaking/SwapInfo.kt
+++ b/src/main/kotlin/com/lambda/interaction/managers/breaking/SwapInfo.kt
@@ -17,11 +17,9 @@
package com.lambda.interaction.managers.breaking
-import com.lambda.context.Automated
import com.lambda.config.AutomationConfig.Companion.DEFAULT
+import com.lambda.context.Automated
import com.lambda.context.SafeContext
-import com.lambda.interaction.managers.LogContext
-import com.lambda.interaction.managers.LogContext.Companion.LogContextBuilder
import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.Primary
import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.Secondary
import com.lambda.interaction.managers.breaking.BreakManager.calcBreakDelta
@@ -35,14 +33,7 @@ data class SwapInfo(
private val automated: Automated = DEFAULT,
val swap: Boolean = false,
val longSwap: Boolean = false
-) : LogContext, Automated by automated {
- override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = {
- group("Swap Info") {
- value("Type", type)
- value("Swap", swap)
- }
- }
-
+) : Automated by automated {
companion object {
val EMPTY = SwapInfo(Primary)
diff --git a/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarManager.kt b/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarManager.kt
index d8bfe17d0..945e808f7 100644
--- a/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarManager.kt
+++ b/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarManager.kt
@@ -19,21 +19,15 @@ package com.lambda.interaction.managers.hotbar
import com.lambda.context.AutomatedSafeContext
import com.lambda.context.SafeContext
-import com.lambda.event.Event
-import com.lambda.event.EventFlow.post
import com.lambda.event.events.TickEvent
import com.lambda.event.listener.SafeListener.Companion.listen
-import com.lambda.interaction.managers.Logger
import com.lambda.interaction.managers.Manager
-import com.lambda.interaction.managers.ManagerUtils.newStage
-import com.lambda.interaction.managers.ManagerUtils.newTick
import com.lambda.interaction.managers.hotbar.HotbarConfig.SwapMode
import com.lambda.interaction.managers.hotbar.HotbarManager.activeRequest
import com.lambda.interaction.managers.hotbar.HotbarManager.activeSlot
import com.lambda.interaction.managers.hotbar.HotbarManager.checkResetSwap
import com.lambda.interaction.managers.hotbar.HotbarManager.setActiveRequest
import com.lambda.interaction.managers.hotbar.HotbarManager.setActiveSlot
-import com.lambda.module.hud.ManagerDebugLoggers.hotbarManagerLogger
import com.lambda.threading.runSafe
import net.minecraft.item.ItemStack
@@ -50,12 +44,11 @@ object HotbarManager : Manager(
onOpen = {
if (activeRequest != null) {
setActiveSlot()
- HotbarManager.logger.newStage(HotbarManager.tickStage)
}
},
onClose = { checkResetSwap() }
-), Logger {
- var activeRequest: HotbarRequest? = null
+) {
+ private var activeRequest: HotbarRequest? = null
@JvmStatic var activeSlot: Int = -1
val serverSlot get() = runSafe {
@@ -69,16 +62,9 @@ object HotbarManager : Manager(
private var maxSwapsThisTick = 0
private var swapDelay = 0
- override val logger = hotbarManagerLogger
-
override fun load(): String {
super.load()
- listen(priority = Int.MAX_VALUE) {
- if (activeRequest != null)
- logger.newTick()
- }
-
listen(priority = Int.MIN_VALUE) {
swapsThisTick = 0
if (swapDelay > 0) swapDelay--
@@ -107,7 +93,6 @@ object HotbarManager : Manager(
* @see setActiveSlot
*/
override fun AutomatedSafeContext.handleRequest(request: HotbarRequest) {
- logger.debug("Handling request:", request)
if (request.nowOrNothing && tickStage !in hotbarConfig.tickStageMask) return
@@ -128,7 +113,6 @@ object HotbarManager : Manager(
private fun AutomatedSafeContext.setActiveRequest(request: HotbarRequest) {
maxSwapsThisTick = hotbarConfig.swapsPerTick
activeRequest = request
- logger.success("Set active request", request)
}
/**
@@ -172,7 +156,6 @@ object HotbarManager : Manager(
}
val canStopSwap = swapsThisTick < maxSwapsThisTick
if (tickStage in active.hotbarConfig.tickStageMask && canStopSwap) {
- logger.debug("Clearing request and syncing slot", activeRequest)
val prevSlot = activeSlot
activeRequest = null
activeSlot = -1
diff --git a/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarRequest.kt b/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarRequest.kt
index 2c23715bb..1b47082ba 100644
--- a/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarRequest.kt
+++ b/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarRequest.kt
@@ -18,17 +18,15 @@
package com.lambda.interaction.managers.hotbar
import com.lambda.context.Automated
-import com.lambda.interaction.managers.LogContext
-import com.lambda.interaction.managers.LogContext.Companion.LogContextBuilder
import com.lambda.interaction.managers.Request
class HotbarRequest(
val slot: Int,
automated: Automated,
var keepTicks: Int = automated.hotbarConfig.keepTicks,
- var swapPause: Int = automated.hotbarConfig.swapPause,
+ val swapPause: Int = automated.hotbarConfig.swapPause,
override val nowOrNothing: Boolean = true
-) : Request(), LogContext, Automated by automated {
+) : Request(), Automated by automated {
override val requestId = ++requestCount
override val tickStageMask get() = hotbarConfig.tickStageMask
@@ -41,17 +39,6 @@ class HotbarRequest(
override fun submit(queueIfMismatchedStage: Boolean) =
HotbarManager.request(this, queueIfMismatchedStage)
- override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = {
- group("Hotbar Request") {
- value("Request ID", requestId)
- value("Slot", slot)
- value("Keep Ticks", keepTicks)
- value("Swap Pause", swapPause)
- value("Swap Pause Age", swapPauseAge)
- value("Active Request Age", activeRequestAge)
- }
- }
-
companion object {
var requestCount = 0
}
diff --git a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractInfo.kt b/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractInfo.kt
index e9826d895..782a71e7a 100644
--- a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractInfo.kt
+++ b/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractInfo.kt
@@ -21,8 +21,6 @@ import com.lambda.context.SafeContext
import com.lambda.interaction.construction.simulation.context.BuildContext
import com.lambda.interaction.construction.simulation.context.InteractContext
import com.lambda.interaction.managers.ActionInfo
-import com.lambda.interaction.managers.LogContext
-import com.lambda.interaction.managers.LogContext.Companion.LogContextBuilder
import net.minecraft.util.math.BlockPos
data class InteractInfo(
@@ -30,13 +28,4 @@ data class InteractInfo(
override val pendingInteractionsList: MutableCollection,
val onPlace: (SafeContext.(BlockPos) -> Unit)?,
val interactConfig: InteractConfig
-) : ActionInfo, LogContext {
- override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = {
- group("Place Info") {
- text(context.getLogContextBuilder())
- group("Callbacks") {
- value("onPlace", onPlace != null)
- }
- }
- }
-}
\ No newline at end of file
+) : ActionInfo
\ No newline at end of file
diff --git a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractManager.kt b/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractManager.kt
index d1940fe39..ed21a4784 100644
--- a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractManager.kt
+++ b/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractManager.kt
@@ -26,11 +26,8 @@ import com.lambda.event.events.TickEvent
import com.lambda.event.listener.SafeListener.Companion.listen
import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe
import com.lambda.interaction.construction.simulation.context.InteractContext
-import com.lambda.interaction.managers.Logger
import com.lambda.interaction.managers.Manager
import com.lambda.interaction.managers.ManagerUtils.isPosBlocked
-import com.lambda.interaction.managers.ManagerUtils.newStage
-import com.lambda.interaction.managers.ManagerUtils.newTick
import com.lambda.interaction.managers.PositionBlocking
import com.lambda.interaction.managers.breaking.BreakManager
import com.lambda.interaction.managers.interacting.InteractManager.activeRequest
@@ -42,7 +39,6 @@ import com.lambda.interaction.managers.interacting.InteractedBlockHandler.pendin
import com.lambda.interaction.managers.interacting.InteractedBlockHandler.setPendingConfigs
import com.lambda.interaction.managers.interacting.InteractedBlockHandler.startPending
import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest
-import com.lambda.module.hud.ManagerDebugLoggers.placeManagerLogger
import com.lambda.threading.runSafeAutomated
import com.lambda.util.BlockUtils.blockState
import com.lambda.util.item.ItemUtils.blockItem
@@ -68,12 +64,8 @@ import net.minecraft.world.GameMode
object InteractManager : Manager(
0,
- onOpen = {
- if (potentialPlacements.isNotEmpty())
- InteractManager.logger.newStage(InteractManager.tickStage)
- activeRequest?.let { it.runSafeAutomated { processRequest(it) } }
- }
-), PositionBlocking, Logger {
+ onOpen = { activeRequest?.let { it.runSafeAutomated { processRequest(it) } } }
+), PositionBlocking {
private var activeRequest: InteractRequest? = null
private var potentialPlacements = mutableListOf()
@@ -88,16 +80,9 @@ object InteractManager : Manager(
override val blockedPositions
get() = pendingActions.map { it.context.blockPos }
- override val logger = placeManagerLogger
-
override fun load(): String {
super.load()
- listen(priority = Int.MAX_VALUE) {
- if (potentialPlacements.isNotEmpty())
- logger.newTick()
- }
-
listen(priority = Int.MIN_VALUE) {
activeRequest = null
placementsThisTick = 0
@@ -128,7 +113,7 @@ object InteractManager : Manager(
* @see processRequest
*/
override fun AutomatedSafeContext.handleRequest(request: InteractRequest) {
- if (activeRequest != null || request.contexts.isEmpty()) return
+ if (!request.buildConfig.interactBlocks || activeRequest != null || request.contexts.isEmpty()) return
if (BreakManager.activeThisTick) return
activeRequest = request
@@ -150,8 +135,6 @@ object InteractManager : Manager(
* @see interactBlock
*/
fun AutomatedSafeContext.processRequest(request: InteractRequest) {
- logger.debug("Processing request", request)
-
if (request.fresh) populateFrom(request)
val iterator = potentialPlacements.iterator()
@@ -161,35 +144,27 @@ object InteractManager : Manager(
val ctx = iterator.next()
if (ctx.sneak) shouldSneak = true
- if (!ctx.requestDependencies(request)) {
- logger.warning("Dependencies failed for context", ctx, request)
- return
- }
+ if (!ctx.requestDependencies(request)) return
if (!validSneak(player)) return
if (tickStage !in interactConfig.tickStageMask) return
- val actionResult = if (ctx.placing) placeBlock(ctx, request, Hand.MAIN_HAND)
+ val actionResult = if (ctx.preProcessingInfo.placing) placeBlock(ctx, request, Hand.MAIN_HAND)
else interaction.interactBlock(player, Hand.MAIN_HAND, ctx.hitResult)
- if (!actionResult.isAccepted) {
- logger.warning("Placement interaction failed with $actionResult", ctx, request)
- } else if (interactConfig.swing) {
- swingHand(interactConfig.swingType, Hand.MAIN_HAND)
-
- val stackInHand = player.getStackInHand(Hand.MAIN_HAND)
- val stackCountPre = stackInHand.count
- if (!stackInHand.isEmpty && (stackInHand.count != stackCountPre || player.isInCreativeMode)) {
- mc.gameRenderer.firstPersonRenderer.resetEquipProgress(Hand.MAIN_HAND)
- }
+ if (actionResult.isAccepted && interactConfig.swing) {
+ swingHand(interactConfig.swingType, Hand.MAIN_HAND)
+
+ val stackInHand = player.getStackInHand(Hand.MAIN_HAND)
+ val stackCountPre = stackInHand.count
+ if (!stackInHand.isEmpty && (stackInHand.count != stackCountPre || player.isInCreativeMode)) {
+ mc.gameRenderer.firstPersonRenderer.resetEquipProgress(Hand.MAIN_HAND)
+ }
}
interactCooldown = ctx.interactConfig.interactDelay + 1
placementsThisTick++
iterator.remove()
}
if (potentialPlacements.isEmpty()) {
- if (activeRequest != null) {
- logger.debug("Clearing active request", activeRequest)
- activeRequest = null
- }
+ if (activeRequest != null) activeRequest = null
}
}
@@ -200,14 +175,12 @@ object InteractManager : Manager(
* @see isPosBlocked
*/
private fun Automated.populateFrom(request: InteractRequest) {
- logger.debug("Populating from request", request)
setPendingConfigs()
potentialPlacements = request.contexts
.distinctBy { it.blockPos }
.filter { !isPosBlocked(it.blockPos) }
.take(buildConfig.maxPendingActions - request.pendingInteractions.size.coerceAtLeast(0))
.toMutableList()
- logger.debug("${potentialPlacements.size} potential placements")
maxPlacementsThisTick = interactConfig.interactionsPerTick
}
@@ -221,14 +194,8 @@ object InteractManager : Manager(
private fun AutomatedSafeContext.placeBlock(interactContext: InteractContext, request: InteractRequest, hand: Hand): ActionResult {
interaction.syncSelectedSlot()
val hitResult = interactContext.hitResult
- if (!world.worldBorder.contains(hitResult.blockPos)) {
- logger.error("Placement position outside the world border", interactContext, request)
- return ActionResult.FAIL
- }
- if (gamemode == GameMode.SPECTATOR) {
- logger.error("Player is in spectator mode", interactContext, request)
- return ActionResult.PASS
- }
+ if (!world.worldBorder.contains(hitResult.blockPos)) return ActionResult.FAIL
+ if (gamemode == GameMode.SPECTATOR) return ActionResult.PASS
return interactBlockInternal(interactContext, request, hand, hitResult)
}
@@ -247,10 +214,7 @@ object InteractManager : Manager(
val cantInteract = player.shouldCancelInteraction() && handNotEmpty
if (!cantInteract) {
val blockState = blockState(hitResult.blockPos)
- if (!connection.hasFeature(blockState.block.requiredFeatures)) {
- logger.error("Required features not met for $blockState", interactContext, request)
- return ActionResult.FAIL
- }
+ if (!connection.hasFeature(blockState.block.requiredFeatures)) return ActionResult.FAIL
val actionResult = blockState.onUseWithItem(player.getStackInHand(hand), world, player, hand, hitResult)
if (actionResult.isAccepted) return actionResult
@@ -311,23 +275,11 @@ object InteractManager : Manager(
item: BlockItem,
context: ItemPlacementContext
): ActionResult {
- if (!item.block.isEnabled(world.enabledFeatures)) {
- logger.error("Block ${item.block.name} is not enabled", interactContext, request)
- return ActionResult.FAIL
- }
- if (!context.canPlace()) {
- logger.error("Cannot place at ${interactContext.blockPos} with current state ${interactContext.cachedState}", interactContext, request)
- return ActionResult.FAIL
- }
+ if (!item.block.isEnabled(world.enabledFeatures)) return ActionResult.FAIL
+ if (!context.canPlace()) return ActionResult.FAIL
- val itemPlacementContext = item.getPlacementContext(context) ?: run {
- logger.error("Could not retrieve item placement context", interactContext, request)
- return ActionResult.FAIL
- }
- val blockState = item.getPlacementState(itemPlacementContext) ?: run {
- logger.error("Could not retrieve placement state", interactContext, request)
- return ActionResult.FAIL
- }
+ val itemPlacementContext = item.getPlacementContext(context) ?: return ActionResult.FAIL
+ val blockState = item.getPlacementState(itemPlacementContext) ?: return ActionResult.FAIL
if (interactConfig.airPlace == InteractConfig.AirPlaceMode.Grim) {
val placeHand = if (hand == Hand.MAIN_HAND) Hand.OFF_HAND else Hand.MAIN_HAND
@@ -354,10 +306,7 @@ object InteractManager : Manager(
// TODO: Implement restriction checks (e.g., world height) to prevent unnecessary server requests when the
// "AwaitThenPlace" confirmation setting is enabled, as the block state setting methods that validate these
// rules are not called.
- if (!item.place(itemPlacementContext, blockState)) {
- logger.error("Could not place block client side at ${interactContext.blockPos} with placement state ${interactContext.expectedState}", interactContext, request)
- return ActionResult.FAIL
- }
+ if (!item.place(itemPlacementContext, blockState)) return ActionResult.FAIL
val blockPos = itemPlacementContext.blockPos
var state = world.getBlockState(blockPos)
@@ -373,8 +322,6 @@ object InteractManager : Manager(
request.onPlace?.invoke(this, interactContext.blockPos)
}
- logger.success("Placed ${interactContext.expectedState} at ${interactContext.blockPos}", interactContext, request)
-
return ActionResult.SUCCESS
}
diff --git a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractRequest.kt b/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractRequest.kt
index 5e5289c99..fe3269827 100644
--- a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractRequest.kt
+++ b/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractRequest.kt
@@ -24,8 +24,6 @@ import com.lambda.interaction.construction.simulation.context.InteractContext
import com.lambda.interaction.construction.simulation.result.BuildResult
import com.lambda.interaction.construction.simulation.result.Dependent
import com.lambda.interaction.construction.simulation.result.results.InteractResult
-import com.lambda.interaction.managers.LogContext
-import com.lambda.interaction.managers.LogContext.Companion.LogContextBuilder
import com.lambda.interaction.managers.Request
import com.lambda.threading.runSafe
import com.lambda.util.BlockUtils.blockState
@@ -37,7 +35,7 @@ data class InteractRequest private constructor(
val pendingInteractions: MutableCollection