diff --git a/common/src/main/java/com/lambda/mixin/render/CameraMixin.java b/common/src/main/java/com/lambda/mixin/render/CameraMixin.java index 108af5ef5..393adc7d6 100644 --- a/common/src/main/java/com/lambda/mixin/render/CameraMixin.java +++ b/common/src/main/java/com/lambda/mixin/render/CameraMixin.java @@ -42,7 +42,7 @@ private void injectQuickPerspectiveSwap(BlockView area, Entity focusedEntity, bo @Inject(method = "clipToSpace", at = @At("HEAD"), cancellable = true) private void onClipToSpace(double desiredCameraDistance, CallbackInfoReturnable info) { - if (CameraTweaks.getNoClipCam()) { + if (CameraTweaks.INSTANCE.isEnabled() && CameraTweaks.getNoClipCam()) { info.setReturnValue(desiredCameraDistance); } } diff --git a/common/src/main/java/com/lambda/mixin/render/ChatScreenMixin.java b/common/src/main/java/com/lambda/mixin/render/ChatScreenMixin.java index 545db5c37..874a45dd2 100644 --- a/common/src/main/java/com/lambda/mixin/render/ChatScreenMixin.java +++ b/common/src/main/java/com/lambda/mixin/render/ChatScreenMixin.java @@ -1,14 +1,44 @@ package com.lambda.mixin.render; import com.lambda.command.CommandManager; +import com.lambda.graphics.renderer.gui.font.FontRenderer; +import com.lambda.graphics.renderer.gui.font.LambdaEmoji; +import com.lambda.graphics.renderer.gui.font.glyph.GlyphInfo; +import kotlin.Pair; +import kotlin.ranges.IntRange; import net.minecraft.client.gui.screen.ChatScreen; 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.ModifyArg; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import java.util.Collections; +import java.util.List; + @Mixin(ChatScreen.class) public abstract class ChatScreenMixin { + @ModifyArg(method = "sendMessage", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayNetworkHandler;sendChatMessage(Ljava/lang/String;)V"), index = 0) + private String modifyChatText(String chatText) { + List> emojis = FontRenderer.Companion.parseEmojis(chatText, LambdaEmoji.Twemoji); + Collections.reverse(emojis); + + for (Pair emoji : emojis) { + String emojiString = chatText.substring(emoji.getSecond().getStart() + 1, emoji.getSecond().getEndInclusive()); + if (LambdaEmoji.Twemoji.get(emojiString) == null) + continue; + + // Because the width of a char is bigger than an emoji + // we can simply replace the matches string by a space + // and render it after the text + chatText = chatText.substring(0, emoji.getSecond().getStart()) + " " + chatText.substring(emoji.getSecond().getEndInclusive() + 1); + + // TODO: Build a renderer for the emojis + // TODO: Render the emojis at their correct position + } + + return chatText; + } @Inject(method = "sendMessage", at = @At("HEAD"), cancellable = true) void sendMessageInject(String chatText, boolean addToHistory, CallbackInfoReturnable cir) { diff --git a/common/src/main/kotlin/com/lambda/Lambda.kt b/common/src/main/kotlin/com/lambda/Lambda.kt index 4cd45128a..9571b5fa2 100644 --- a/common/src/main/kotlin/com/lambda/Lambda.kt +++ b/common/src/main/kotlin/com/lambda/Lambda.kt @@ -10,8 +10,10 @@ import com.lambda.core.Loader import com.lambda.gui.impl.clickgui.windows.tag.CustomModuleWindow import com.lambda.gui.impl.clickgui.windows.tag.TagWindow import com.lambda.module.tag.ModuleTag +import com.lambda.threading.runGameScheduled import com.lambda.util.KeyCode import com.mojang.authlib.GameProfile +import com.mojang.blaze3d.systems.RenderSystem.recordRenderCall import net.minecraft.block.Block import net.minecraft.client.MinecraftClient import net.minecraft.util.math.BlockPos diff --git a/common/src/main/kotlin/com/lambda/config/AbstractSetting.kt b/common/src/main/kotlin/com/lambda/config/AbstractSetting.kt index d00f307d9..dd7251357 100644 --- a/common/src/main/kotlin/com/lambda/config/AbstractSetting.kt +++ b/common/src/main/kotlin/com/lambda/config/AbstractSetting.kt @@ -5,6 +5,7 @@ import com.lambda.Lambda.gson import com.lambda.context.SafeContext import com.lambda.threading.runSafe import com.lambda.util.Nameable +import java.lang.reflect.Type import kotlin.properties.Delegates import kotlin.reflect.KProperty @@ -49,10 +50,12 @@ import kotlin.reflect.KProperty * * @property defaultValue The default value of the setting. * @property description A description of the setting. + * @property type The type reflection of the setting. * @property visibility A function that determines whether the setting is visible. */ abstract class AbstractSetting( private val defaultValue: T, + private val type: Type, val description: String, val visibility: () -> Boolean, ) : Jsonable, Nameable { @@ -74,10 +77,10 @@ abstract class AbstractSetting( } override fun toJson(): JsonElement = - gson.toJsonTree(value) + gson.toJsonTree(value, type) override fun loadFromJson(serialized: JsonElement) { - value = gson.fromJson(serialized, value::class.java) + value = gson.fromJson(serialized, type) } fun onValueChange(block: SafeContext.(from: T, to: T) -> Unit) { @@ -101,4 +104,4 @@ abstract class AbstractSetting( } class ValueListener(val requiresValueChange: Boolean, val execute: (from: T, to: T) -> Unit) -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/config/Configuration.kt b/common/src/main/kotlin/com/lambda/config/Configuration.kt index a58e24769..eaa64b2c3 100644 --- a/common/src/main/kotlin/com/lambda/config/Configuration.kt +++ b/common/src/main/kotlin/com/lambda/config/Configuration.kt @@ -131,6 +131,7 @@ abstract class Configuration : Jsonable { } .onFailure { val message = "Failed to save ${configName.capitalize()} config" + LOG.error(message, it) logError(message) } } diff --git a/common/src/main/kotlin/com/lambda/config/serializer/gui/TagWindowSerializer.kt b/common/src/main/kotlin/com/lambda/config/serializer/gui/TagWindowSerializer.kt index 8a6108945..45b288cad 100644 --- a/common/src/main/kotlin/com/lambda/config/serializer/gui/TagWindowSerializer.kt +++ b/common/src/main/kotlin/com/lambda/config/serializer/gui/TagWindowSerializer.kt @@ -3,6 +3,7 @@ package com.lambda.config.serializer.gui import com.google.gson.* import com.lambda.gui.impl.clickgui.LambdaClickGui import com.lambda.gui.impl.clickgui.windows.tag.TagWindow +import com.lambda.gui.impl.hudgui.LambdaHudGui import com.lambda.module.tag.ModuleTag import com.lambda.util.math.Vec2d import java.lang.reflect.Type @@ -29,8 +30,16 @@ object TagWindowSerializer : JsonSerializer, JsonDeserializer LambdaClickGui + in ModuleTag.hudDefaults -> LambdaHudGui + else -> return@let null + } + + TagWindow(tag, gui).apply { width = it["width"].asDouble height = it["height"].asDouble isOpen = it["isOpen"].asBoolean diff --git a/common/src/main/kotlin/com/lambda/config/settings/CharSetting.kt b/common/src/main/kotlin/com/lambda/config/settings/CharSetting.kt index f4c0e4c2a..f068bc7ac 100644 --- a/common/src/main/kotlin/com/lambda/config/settings/CharSetting.kt +++ b/common/src/main/kotlin/com/lambda/config/settings/CharSetting.kt @@ -1,5 +1,6 @@ package com.lambda.config.settings +import com.google.gson.reflect.TypeToken import com.lambda.config.AbstractSetting /** @@ -17,6 +18,7 @@ class CharSetting( visibility: () -> Boolean, ) : AbstractSetting( defaultValue, + TypeToken.get(Char::class.java).type, description, visibility -) \ No newline at end of file +) diff --git a/common/src/main/kotlin/com/lambda/config/settings/NumericSetting.kt b/common/src/main/kotlin/com/lambda/config/settings/NumericSetting.kt index 4bef2aaa0..9aa53e0cf 100644 --- a/common/src/main/kotlin/com/lambda/config/settings/NumericSetting.kt +++ b/common/src/main/kotlin/com/lambda/config/settings/NumericSetting.kt @@ -1,5 +1,6 @@ package com.lambda.config.settings +import com.google.gson.reflect.TypeToken import com.lambda.config.AbstractSetting import java.text.NumberFormat import java.util.* @@ -26,6 +27,7 @@ abstract class NumericSetting( val unit: String, ) : AbstractSetting( value, + TypeToken.get(value::class.java).type, description, visibility ) where T : Number, T : Comparable { @@ -36,4 +38,4 @@ abstract class NumericSetting( override operator fun setValue(thisRef: Any?, property: KProperty<*>, valueIn: T) { value = valueIn.coerceIn(range) } -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/config/settings/StringSetting.kt b/common/src/main/kotlin/com/lambda/config/settings/StringSetting.kt index 7c9e45cdf..5bd47e7df 100644 --- a/common/src/main/kotlin/com/lambda/config/settings/StringSetting.kt +++ b/common/src/main/kotlin/com/lambda/config/settings/StringSetting.kt @@ -1,5 +1,6 @@ package com.lambda.config.settings +import com.google.gson.reflect.TypeToken import com.lambda.config.AbstractSetting /** @@ -17,6 +18,7 @@ class StringSetting( visibility: () -> Boolean, ) : AbstractSetting( defaultValue, + TypeToken.get(String::class.java).type, description, visibility -) \ No newline at end of file +) diff --git a/common/src/main/kotlin/com/lambda/config/settings/collections/ListSetting.kt b/common/src/main/kotlin/com/lambda/config/settings/collections/ListSetting.kt index 2d7d7494d..1c002df86 100644 --- a/common/src/main/kotlin/com/lambda/config/settings/collections/ListSetting.kt +++ b/common/src/main/kotlin/com/lambda/config/settings/collections/ListSetting.kt @@ -1,6 +1,7 @@ package com.lambda.config.settings.collections import com.google.gson.JsonElement +import com.google.gson.reflect.TypeToken import com.lambda.Lambda.gson import com.lambda.config.AbstractSetting import java.lang.reflect.Type @@ -13,6 +14,7 @@ class ListSetting( visibility: () -> Boolean, ) : AbstractSetting>( defaultValue, + type, description, visibility ) { @@ -21,7 +23,6 @@ class ListSetting( } override fun toJson(): JsonElement { - value = defaultValue // Hack the Delegates.observable return gson.toJsonTree(value) } } diff --git a/common/src/main/kotlin/com/lambda/config/settings/collections/MapSetting.kt b/common/src/main/kotlin/com/lambda/config/settings/collections/MapSetting.kt index 08fb931c3..b8c36c63f 100644 --- a/common/src/main/kotlin/com/lambda/config/settings/collections/MapSetting.kt +++ b/common/src/main/kotlin/com/lambda/config/settings/collections/MapSetting.kt @@ -1,27 +1,17 @@ package com.lambda.config.settings.collections -import com.google.gson.JsonElement -import com.lambda.Lambda.gson import com.lambda.config.AbstractSetting import java.lang.reflect.Type class MapSetting( override val name: String, - private val defaultValue: MutableMap, - private val type: Type, + defaultValue: MutableMap, + type: Type, description: String, visibility: () -> Boolean, ) : AbstractSetting>( defaultValue, + type, description, visibility -) { - override fun loadFromJson(serialized: JsonElement) { - value = gson.fromJson(serialized, type) - } - - override fun toJson(): JsonElement { - value = defaultValue // Hack the Delegates.observable - return gson.toJsonTree(value) - } -} +) diff --git a/common/src/main/kotlin/com/lambda/config/settings/collections/SetSetting.kt b/common/src/main/kotlin/com/lambda/config/settings/collections/SetSetting.kt index bbce58e21..d4da0afd8 100644 --- a/common/src/main/kotlin/com/lambda/config/settings/collections/SetSetting.kt +++ b/common/src/main/kotlin/com/lambda/config/settings/collections/SetSetting.kt @@ -1,27 +1,17 @@ package com.lambda.config.settings.collections -import com.google.gson.JsonElement -import com.lambda.Lambda.gson import com.lambda.config.AbstractSetting import java.lang.reflect.Type class SetSetting( override val name: String, - private val defaultValue: MutableSet, - private val type: Type, + defaultValue: MutableSet, + type: Type, description: String, visibility: () -> Boolean, ) : AbstractSetting>( defaultValue, + type, description, visibility -) { - override fun loadFromJson(serialized: JsonElement) { - value = gson.fromJson(serialized, type) - } - - override fun toJson(): JsonElement { - value = defaultValue // Hack the Delegates.observable - return gson.toJsonTree(value) - } -} +) diff --git a/common/src/main/kotlin/com/lambda/config/settings/comparable/BooleanSetting.kt b/common/src/main/kotlin/com/lambda/config/settings/comparable/BooleanSetting.kt index 6ada06326..272680124 100644 --- a/common/src/main/kotlin/com/lambda/config/settings/comparable/BooleanSetting.kt +++ b/common/src/main/kotlin/com/lambda/config/settings/comparable/BooleanSetting.kt @@ -1,5 +1,6 @@ package com.lambda.config.settings.comparable +import com.google.gson.reflect.TypeToken import com.lambda.config.AbstractSetting class BooleanSetting( @@ -9,6 +10,7 @@ class BooleanSetting( visibility: () -> Boolean, ) : AbstractSetting( defaultValue, + TypeToken.get(Boolean::class.java).type, description, visibility -) \ No newline at end of file +) diff --git a/common/src/main/kotlin/com/lambda/config/settings/comparable/EnumSetting.kt b/common/src/main/kotlin/com/lambda/config/settings/comparable/EnumSetting.kt index b52bef99a..aa48a162b 100644 --- a/common/src/main/kotlin/com/lambda/config/settings/comparable/EnumSetting.kt +++ b/common/src/main/kotlin/com/lambda/config/settings/comparable/EnumSetting.kt @@ -1,5 +1,6 @@ package com.lambda.config.settings.comparable +import com.google.gson.reflect.TypeToken import com.lambda.config.AbstractSetting class EnumSetting>( @@ -9,6 +10,7 @@ class EnumSetting>( visibility: () -> Boolean, ) : AbstractSetting( defaultValue, + TypeToken.get(defaultValue.declaringJavaClass).type, description, visibility, ) { @@ -17,4 +19,4 @@ class EnumSetting>( fun next() { value = enumValues[((value.ordinal + 1) % enumValues.size)] } -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/config/settings/complex/BlockPosSetting.kt b/common/src/main/kotlin/com/lambda/config/settings/complex/BlockPosSetting.kt index 46ece0360..7c21776a6 100644 --- a/common/src/main/kotlin/com/lambda/config/settings/complex/BlockPosSetting.kt +++ b/common/src/main/kotlin/com/lambda/config/settings/complex/BlockPosSetting.kt @@ -1,5 +1,6 @@ package com.lambda.config.settings.complex +import com.google.gson.reflect.TypeToken import com.lambda.config.AbstractSetting import net.minecraft.util.math.BlockPos @@ -10,6 +11,7 @@ class BlockPosSetting( visibility: () -> Boolean, ) : AbstractSetting( defaultValue, + TypeToken.get(BlockPos::class.java).type, description, visibility -) \ No newline at end of file +) diff --git a/common/src/main/kotlin/com/lambda/config/settings/complex/BlockSetting.kt b/common/src/main/kotlin/com/lambda/config/settings/complex/BlockSetting.kt index 5fc9cd1f6..ac5b9e365 100644 --- a/common/src/main/kotlin/com/lambda/config/settings/complex/BlockSetting.kt +++ b/common/src/main/kotlin/com/lambda/config/settings/complex/BlockSetting.kt @@ -1,5 +1,6 @@ package com.lambda.config.settings.complex +import com.google.gson.reflect.TypeToken import com.lambda.config.AbstractSetting import net.minecraft.block.Block @@ -10,6 +11,7 @@ class BlockSetting( visibility: () -> Boolean, ) : AbstractSetting( defaultValue, + TypeToken.get(Block::class.java).type, description, visibility -) \ No newline at end of file +) diff --git a/common/src/main/kotlin/com/lambda/config/settings/complex/ColorSetting.kt b/common/src/main/kotlin/com/lambda/config/settings/complex/ColorSetting.kt index 179b88596..caef2e0ae 100644 --- a/common/src/main/kotlin/com/lambda/config/settings/complex/ColorSetting.kt +++ b/common/src/main/kotlin/com/lambda/config/settings/complex/ColorSetting.kt @@ -1,5 +1,6 @@ package com.lambda.config.settings.complex +import com.google.gson.reflect.TypeToken import com.lambda.config.AbstractSetting import java.awt.Color @@ -10,6 +11,7 @@ class ColorSetting( visibility: () -> Boolean, ) : AbstractSetting( defaultValue, + TypeToken.get(Color::class.java).type, description, visibility ) diff --git a/common/src/main/kotlin/com/lambda/config/settings/complex/KeyBindSetting.kt b/common/src/main/kotlin/com/lambda/config/settings/complex/KeyBindSetting.kt index fccd4fec0..df19f8668 100644 --- a/common/src/main/kotlin/com/lambda/config/settings/complex/KeyBindSetting.kt +++ b/common/src/main/kotlin/com/lambda/config/settings/complex/KeyBindSetting.kt @@ -1,5 +1,6 @@ package com.lambda.config.settings.complex +import com.google.gson.reflect.TypeToken import com.lambda.config.AbstractSetting import com.lambda.util.KeyCode @@ -10,6 +11,7 @@ class KeyBindSetting( visibility: () -> Boolean, ) : AbstractSetting( defaultValue, + TypeToken.get(KeyCode::class.java).type, description, visibility -) \ No newline at end of file +) diff --git a/common/src/main/kotlin/com/lambda/core/Loader.kt b/common/src/main/kotlin/com/lambda/core/Loader.kt index b0d684eed..3adc51e43 100644 --- a/common/src/main/kotlin/com/lambda/core/Loader.kt +++ b/common/src/main/kotlin/com/lambda/core/Loader.kt @@ -5,7 +5,9 @@ import com.lambda.Lambda.LOG import com.lambda.command.CommandRegistry import com.lambda.friend.FriendRegistry import com.lambda.graphics.renderer.gui.font.LambdaFont +import com.lambda.graphics.renderer.gui.font.LambdaEmoji import com.lambda.gui.GuiConfigurable +import com.lambda.gui.HudGuiConfigurable import com.lambda.interaction.PlayerPacketManager import com.lambda.interaction.RotationManager import com.lambda.interaction.material.ContainerManager @@ -21,7 +23,9 @@ object Loader { RotationManager, PlayerPacketManager, LambdaFont.Loader, + LambdaEmoji.Loader, GuiConfigurable, + HudGuiConfigurable, FriendRegistry, SoundRegistry, ContainerManager diff --git a/common/src/main/kotlin/com/lambda/event/EventFlow.kt b/common/src/main/kotlin/com/lambda/event/EventFlow.kt index f9f5f616b..b14290673 100644 --- a/common/src/main/kotlin/com/lambda/event/EventFlow.kt +++ b/common/src/main/kotlin/com/lambda/event/EventFlow.kt @@ -159,9 +159,7 @@ object EventFlow { private fun Event.executeListenerConcurrently() { concurrentListeners[this::class]?.forEach { listener -> if (shouldNotNotify(listener, this)) return@forEach - runConcurrent { - listener.execute(this) - } + listener.execute(this) } } diff --git a/common/src/main/kotlin/com/lambda/event/events/RenderEvent.kt b/common/src/main/kotlin/com/lambda/event/events/RenderEvent.kt index ddd037c10..0b4bb423a 100644 --- a/common/src/main/kotlin/com/lambda/event/events/RenderEvent.kt +++ b/common/src/main/kotlin/com/lambda/event/events/RenderEvent.kt @@ -4,12 +4,44 @@ import com.lambda.Lambda.mc import com.lambda.event.Event import com.lambda.event.callback.Cancellable import com.lambda.event.callback.ICancellable +import com.lambda.graphics.renderer.esp.DirectionMask +import com.lambda.graphics.renderer.esp.EntityEspRenderer import com.lambda.util.math.Vec2d +import net.minecraft.entity.Entity +import java.awt.Color abstract class RenderEvent : Event { class World : RenderEvent() + class EntityESP : RenderEvent() { + fun build( + entity: Entity, + filledColor: Color, + outlineColor: Color, + sides: Int = DirectionMask.ALL, + outlineMode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.OR + ) { + buildFilled(entity, filledColor, sides) + buildOutline(entity, outlineColor, sides, outlineMode) + } + + fun buildFilled( + entity: Entity, + color: Color, + sides: Int = DirectionMask.ALL + ) = EntityEspRenderer.buildFilled(entity, color, sides) + + fun buildOutline( + entity: Entity, + color: Color, + sides: Int = DirectionMask.ALL, + outlineMode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.OR + ) = EntityEspRenderer.buildOutline(entity, color, sides, outlineMode) + } + abstract class GUI(val scale: Double) : RenderEvent() { + class HUD(scaleFactor: Double) : GUI(scaleFactor) + class Scaled(scaleFactor: Double) : GUI(scaleFactor) class Fixed : GUI(1.0) diff --git a/common/src/main/kotlin/com/lambda/event/listener/SafeListener.kt b/common/src/main/kotlin/com/lambda/event/listener/SafeListener.kt index 7f24f3430..20ccca517 100644 --- a/common/src/main/kotlin/com/lambda/event/listener/SafeListener.kt +++ b/common/src/main/kotlin/com/lambda/event/listener/SafeListener.kt @@ -5,6 +5,7 @@ import com.lambda.event.Event import com.lambda.event.EventFlow import com.lambda.event.Muteable import com.lambda.task.Task +import com.lambda.threading.runConcurrent import com.lambda.threading.runSafe @@ -162,10 +163,12 @@ class SafeListener( inline fun Any.concurrentListener( priority: Int = 0, alwaysListen: Boolean = false, - noinline function: SafeContext.(T) -> Unit, + noinline function: suspend SafeContext.(T) -> Unit, ): SafeListener { val listener = SafeListener(priority, this, alwaysListen) { event -> - function(event as T) + runConcurrent { + function(event as T) + } } EventFlow.concurrentListeners.subscribe(listener) diff --git a/common/src/main/kotlin/com/lambda/friend/FriendRegistry.kt b/common/src/main/kotlin/com/lambda/friend/FriendRegistry.kt index d4ed87a82..aefeaac44 100644 --- a/common/src/main/kotlin/com/lambda/friend/FriendRegistry.kt +++ b/common/src/main/kotlin/com/lambda/friend/FriendRegistry.kt @@ -8,7 +8,7 @@ import com.mojang.authlib.GameProfile object FriendRegistry : Configurable(FriendConfig), Loadable { override val name = "friends" - val friends by setting("friends", listOf()) + val friends by setting("friends", listOf()) // Todo: Fix the fucking delegates override fun load(): String { return "Loaded ${friends.size} friends" diff --git a/common/src/main/kotlin/com/lambda/graphics/RenderMain.kt b/common/src/main/kotlin/com/lambda/graphics/RenderMain.kt index 24594a991..e7dd001f0 100644 --- a/common/src/main/kotlin/com/lambda/graphics/RenderMain.kt +++ b/common/src/main/kotlin/com/lambda/graphics/RenderMain.kt @@ -7,6 +7,7 @@ import com.lambda.graphics.gl.GlStateUtils.setupGL import com.lambda.graphics.gl.Matrices import com.lambda.graphics.gl.Matrices.resetMatrix import com.lambda.graphics.gl.Matrices.translate +import com.lambda.graphics.renderer.esp.EntityEspRenderer import com.lambda.module.modules.client.GuiSettings import com.lambda.util.math.Vec2d import com.mojang.blaze3d.systems.RenderSystem.getProjectionMatrix @@ -24,11 +25,12 @@ object RenderMain { translate(0.0, 0.0, -3000.0) setupGL { - rescale(GuiSettings.scale) - RenderEvent.GUI.Scaled(GuiSettings.scale).post() - rescale(1.0) RenderEvent.GUI.Fixed().post() + + rescale(GuiSettings.scale) + RenderEvent.GUI.HUD(GuiSettings.scale).post() + RenderEvent.GUI.Scaled(GuiSettings.scale).post() } } @@ -39,6 +41,7 @@ object RenderMain { setupGL { RenderEvent.World().post() + EntityEspRenderer.render() } } diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vao/VAO.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vao/VAO.kt index 8793136ee..d822e3f94 100644 --- a/common/src/main/kotlin/com/lambda/graphics/buffer/vao/VAO.kt +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vao/VAO.kt @@ -1,5 +1,6 @@ package com.lambda.graphics.buffer.vao +import com.lambda.graphics.buffer.vao.vertex.BufferUsage import com.lambda.graphics.buffer.vao.vertex.VertexAttrib import com.lambda.graphics.buffer.vao.vertex.VertexMode import com.lambda.graphics.gl.Memory.address @@ -20,14 +21,15 @@ import com.lambda.graphics.gl.VaoUtils.unbindIndexBuffer import com.lambda.graphics.gl.VaoUtils.unbindVertexArray import com.lambda.graphics.gl.VaoUtils.unbindVertexBuffer import com.lambda.threading.runGameScheduled -import com.mojang.blaze3d.systems.RenderSystem.drawElements import org.lwjgl.opengl.GL30C.* import java.awt.Color import java.nio.ByteBuffer class VAO( - private val drawMode: VertexMode, + private val vertexMode: VertexMode, attribGroup: VertexAttrib.Group, + private val bufferUsage: BufferUsage = BufferUsage.DYNAMIC, + initializeInstantly: Boolean = false ) : IRenderContext { private var vao = 0 private var vbo = 0 @@ -42,41 +44,50 @@ class VAO( private lateinit var indices: ByteBuffer private var indicesPointer = 0L private var indicesCount = 0 + private var uploadedIndices = 0 private var vertexIndex = 0 init { val stride = attribGroup.stride - objectSize = stride * drawMode.indicesCount + objectSize = stride * vertexMode.indicesCount - runGameScheduled { - vertices = byteBuffer(objectSize * 256 * 4) - verticesPointer = address(vertices) - verticesPosition = verticesPointer + if (initializeInstantly) { + initialize(attribGroup, stride) + } else { + runGameScheduled { + initialize(attribGroup, stride) + } + } + } - indices = byteBuffer(drawMode.indicesCount * 512 * 4) - indicesPointer = address(indices) + private fun initialize(attribGroup: VertexAttrib.Group, stride: Int) { + vertices = byteBuffer(objectSize * 256 * 4) + verticesPointer = address(vertices) + verticesPosition = verticesPointer - vao = glGenVertexArrays() - bindVertexArray(vao) + indices = byteBuffer(vertexMode.indicesCount * 512 * 4) + indicesPointer = address(indices) - vbo = glGenBuffers() - bindVertexBuffer(vbo) + vao = glGenVertexArrays() + bindVertexArray(vao) - ibo = glGenBuffers() - bindIndexBuffer(ibo) + vbo = glGenBuffers() + bindVertexBuffer(vbo) - var pointer = 0L - attribGroup.attributes.forEachIndexed { index, attrib -> - VaoUtils.enableVertexAttribute(index) - VaoUtils.vertexAttribute(index, attrib.componentCount, attrib.gl, attrib.normalized, stride, pointer) - pointer += attrib.size - } + ibo = glGenBuffers() + bindIndexBuffer(ibo) - unbindVertexArray() - unbindVertexBuffer() - unbindIndexBuffer() + var pointer = 0L + attribGroup.attributes.forEachIndexed { index, attrib -> + VaoUtils.enableVertexAttribute(index) + VaoUtils.vertexAttribute(index, attrib.componentCount, attrib.gl, attrib.normalized, stride, pointer) + pointer += attrib.size } + + unbindVertexArray() + unbindVertexBuffer() + unbindIndexBuffer() } override fun vec3(x: Double, y: Double, z: Double): VAO { @@ -156,7 +167,7 @@ class VAO( if ((indicesCount + amount) * 4 < cap) return var newSize = cap * 2 - if (newSize % drawMode.indicesCount != 0) newSize += newSize % (drawMode.indicesCount * 4) + if (newSize % vertexMode.indicesCount != 0) newSize += newSize % (vertexMode.indicesCount * 4) val newIndices = byteBuffer(newSize) val from = address(indices) @@ -168,10 +179,10 @@ class VAO( } override fun render() { - if (indicesCount <= 0) return + if (uploadedIndices <= 0) return bindVertexArray(vao) - drawElements(drawMode.gl, indicesCount, GL_UNSIGNED_INT) + glDrawElements(vertexMode.gl, uploadedIndices, GL_UNSIGNED_INT, 0) unbindVertexArray() } @@ -182,18 +193,21 @@ class VAO( val iboData = indices.limit(indicesCount * 4) bindVertexBuffer(vbo) - bufferData(GL_ARRAY_BUFFER, vboData, GL_DYNAMIC_DRAW) + bufferData(GL_ARRAY_BUFFER, vboData, bufferUsage.gl) unbindVertexBuffer() bindIndexBuffer(ibo) - bufferData(GL_ELEMENT_ARRAY_BUFFER, iboData, GL_DYNAMIC_DRAW) + bufferData(GL_ELEMENT_ARRAY_BUFFER, iboData, bufferUsage.gl) unbindIndexBuffer() + + uploadedIndices = indicesCount } override fun clear() { verticesPosition = verticesPointer vertexIndex = 0 indicesCount = 0 + uploadedIndices = 0 } protected fun finalize() { diff --git a/common/src/main/kotlin/com/lambda/graphics/buffer/vao/vertex/BufferUsage.kt b/common/src/main/kotlin/com/lambda/graphics/buffer/vao/vertex/BufferUsage.kt new file mode 100644 index 000000000..1cd56273d --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/buffer/vao/vertex/BufferUsage.kt @@ -0,0 +1,10 @@ +package com.lambda.graphics.buffer.vao.vertex + +import com.lambda.graphics.gl.GLObject +import org.lwjgl.opengl.GL30C.GL_DYNAMIC_DRAW +import org.lwjgl.opengl.GL30C.GL_STATIC_DRAW + +enum class BufferUsage(override val gl: Int) : GLObject { + STATIC(GL_STATIC_DRAW), + DYNAMIC(GL_DYNAMIC_DRAW) +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/graphics/gl/GlStateUtils.kt b/common/src/main/kotlin/com/lambda/graphics/gl/GlStateUtils.kt index 71b463f83..919d90610 100644 --- a/common/src/main/kotlin/com/lambda/graphics/gl/GlStateUtils.kt +++ b/common/src/main/kotlin/com/lambda/graphics/gl/GlStateUtils.kt @@ -19,7 +19,6 @@ object GlStateUtils { blend(true) cull(false) - block() glDepthMask(true) @@ -36,6 +35,18 @@ object GlStateUtils { depthTest(false) } + fun withFaceCulling(block: () -> Unit) { + cull(true) + block() + cull(false) + } + + fun withLineWidth(width: Double, block: () -> Unit) { + glLineWidth(width.toFloat()) + block() + glLineWidth(1f) + } + @JvmStatic fun capSet(id: Int, flag: Boolean) { val field = when (id) { diff --git a/common/src/main/kotlin/com/lambda/graphics/gl/Matrices.kt b/common/src/main/kotlin/com/lambda/graphics/gl/Matrices.kt index 7428cde2c..245d9dc35 100644 --- a/common/src/main/kotlin/com/lambda/graphics/gl/Matrices.kt +++ b/common/src/main/kotlin/com/lambda/graphics/gl/Matrices.kt @@ -4,7 +4,7 @@ import org.joml.Matrix4f import org.joml.Quaternionf object Matrices { - private val stack = ArrayDeque(listOf(Matrix4f())) + private val stack = ArrayDeque(1) fun translate(x: Double, y: Double, z: Double) { translate(x.toFloat(), y.toFloat(), z.toFloat()) @@ -51,4 +51,4 @@ object Matrices { stack.clear() stack.add(entry) } -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/graphics/gl/Scissor.kt b/common/src/main/kotlin/com/lambda/graphics/gl/Scissor.kt index 5098b93fb..4093ff959 100644 --- a/common/src/main/kotlin/com/lambda/graphics/gl/Scissor.kt +++ b/common/src/main/kotlin/com/lambda/graphics/gl/Scissor.kt @@ -12,7 +12,7 @@ object Scissor { private var stack = ArrayDeque() fun scissor(rect: Rect, block: () -> Unit) { - // clamp corners so children scissor box can't overlap parent + // clamp corners so children scissor boxes can't overlap parent val processed = stack.lastOrNull()?.let(rect::clamp) ?: rect registerScissor(processed, block) } @@ -24,7 +24,7 @@ object Scissor { block() stack.removeLast() - stack.lastOrNull().apply(::scissor) + scissor(stack.lastOrNull()) } private fun scissor(entry: Rect?) { diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/Renderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/Renderer.kt deleted file mode 100644 index 4151bd754..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/Renderer.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.lambda.graphics.renderer - -import com.lambda.graphics.buffer.vao.VAO -import com.lambda.graphics.buffer.vao.vertex.VertexAttrib -import com.lambda.graphics.buffer.vao.vertex.VertexMode - -abstract class Renderer(drawMode: VertexMode, attribGroup: VertexAttrib.Group) { - protected val vao = VAO(drawMode, attribGroup) - - open fun render() { - vao.upload() - vao.render() - vao.clear() - } -} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkStorage.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkStorage.kt new file mode 100644 index 000000000..1f7352d5b --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkStorage.kt @@ -0,0 +1,29 @@ +package com.lambda.graphics.renderer.esp + +import com.lambda.core.Loadable +import com.lambda.event.events.ConnectionEvent +import com.lambda.event.events.WorldEvent +import com.lambda.event.listener.SafeListener.Companion.concurrentListener +import net.minecraft.util.math.ChunkPos +import net.minecraft.world.chunk.WorldChunk +import java.util.concurrent.ConcurrentHashMap + +object ChunkStorage : Loadable { + val chunkMap = ChunkMap() + + init { + concurrentListener { event -> + chunkMap[event.chunk.pos.toLong()] = event.chunk + } + + concurrentListener { event -> + chunkMap.remove(event.chunk.pos.toLong()) + } + + concurrentListener { + chunkMap.clear() + } + } +} + +typealias ChunkMap = ConcurrentHashMap \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt new file mode 100644 index 000000000..2894a2664 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt @@ -0,0 +1,145 @@ +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.concurrentListener +import com.lambda.event.listener.SafeListener.Companion.listener +import com.lambda.module.modules.client.RenderSettings +import com.lambda.threading.runGameBlocking +import net.minecraft.util.math.ChunkPos +import net.minecraft.world.WorldView +import net.minecraft.world.chunk.WorldChunk +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentLinkedDeque + +class ChunkedESP private constructor( + owner: Any, + private val update: EspRenderer.(WorldView, Int, Int, Int) -> Unit +) { + private val rendererMap = ConcurrentHashMap() + private val WorldChunk.renderer get() = rendererMap.getOrPut(pos.toLong()) { + EspChunk(this, this@ChunkedESP) + } + private var ticks = 0 + + private val uploadQueue = ConcurrentLinkedDeque<() -> Unit>() + private val rebuildQueue = ConcurrentLinkedDeque() + + // i completely dont like to listen to enable events + + fun rebuild() { + rebuildQueue.clear() + rebuildQueue.addAll(rendererMap.values) + } + + init { + concurrentListener { event -> + world.getWorldChunk(event.pos).renderer.apply { + queueRebuild() + notifyNeighbors() + } + } + + concurrentListener { event -> + event.chunk.renderer.notifyNeighbors() + } + + concurrentListener { event -> + rendererMap.remove(event.chunk.pos.toLong())?.notifyNeighbors() + } + + owner.concurrentListener { + if (++ticks % RenderSettings.updateFrequency == 0) { + val polls = minOf(RenderSettings.rebuildsPerTick, rebuildQueue.size) + + repeat(polls) { + rebuildQueue.poll()?.rebuild() + } + ticks = 0 + } + } + + owner.listener { + if (uploadQueue.isEmpty()) return@listener + + val polls = minOf(RenderSettings.uploadsPerTick, uploadQueue.size) + + repeat(polls) { + uploadQueue.poll()?.invoke() + } + } + + owner.listener { + rendererMap.values.forEach { + it.renderer?.render() + } + } + } + + companion object { + fun Any.newChunkedESP( + update: EspRenderer.(WorldView, Int, Int, Int) -> Unit + ) = ChunkedESP(this, update) + } + + private class EspChunk(val chunk: WorldChunk, val owner: ChunkedESP) { + var renderer: EspRenderer? = null + + private val chunkOffsets = listOf(1 to 0, 0 to 1, -1 to 0, 0 to -1) + val neighbors = chunkOffsets.map { + ChunkPos(chunk.pos.x + it.first, chunk.pos.z + it.second) + }.toTypedArray() + val neighborsLoaded get() = neighbors.all { it.isLoaded() } + + fun ChunkPos.isLoaded() = chunk.world.chunkManager.isChunkLoaded(x, z) + + fun queueRebuild() { + if (owner.rebuildQueue.contains(this)) return + owner.rebuildQueue.add(this) + } + + fun notifyNeighbors() { + neighbors.forEach { + owner.rendererMap[it.toLong()]?.queueRebuild() + } + } + + suspend fun rebuild() { + val newRenderer = runGameBlocking { + EspRenderer() + } + + iterateChunk { x, y, z -> + owner.update(newRenderer, chunk.world, x, y, z) + } + + val upload = { + newRenderer.upload() + renderer?.clear() + renderer = newRenderer + } + + when (RenderSettings.uploadScheduler) { + RenderSettings.UploadScheduler.Instant -> { + runGameBlocking { + upload() + } + } + RenderSettings.UploadScheduler.Delayed -> { + owner.uploadQueue.add(upload) + } + } + } + + private fun iterateChunk(block: (Int, Int, Int) -> Unit) = chunk.apply { + for (x in pos.startX..pos.endX) { + for (z in pos.startZ..pos.endZ) { + for (y in bottomY..topY) { + block(x, y, z) + } + } + } + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/DirectionMask.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/DirectionMask.kt new file mode 100644 index 000000000..31d338b6a --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/DirectionMask.kt @@ -0,0 +1,37 @@ +package com.lambda.graphics.renderer.esp + +import net.minecraft.util.math.Direction + +object DirectionMask { + const val EAST = 1 // X + + const val WEST = 2 // X - + + const val UP = 4 // Y + + const val DOWN = 8 // Y - + + const val SOUTH = 16 // Z + + const val NORTH = 32 // Z - + + const val ALL = EAST or WEST or UP or DOWN or SOUTH or NORTH + const val NONE = 0 + + fun Int.exclude(dir: Int) = this xor dir + fun Int.hasDirection(dir: Int) = (this and dir) != 0 + + val Direction.mask get() = when (this) { + Direction.DOWN -> DOWN + Direction.UP -> UP + Direction.NORTH -> NORTH + Direction.SOUTH -> SOUTH + Direction.WEST -> WEST + Direction.EAST -> EAST + } + + enum class OutlineMode(val check: (Boolean, Boolean) -> Boolean) { + // Render engine will add a line if BOTH touching sides are included into the mask + AND(Boolean::and), + + // Render engine will add a line if ANY OF touching sides is included into the mask + OR(Boolean::or) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/EntityEspRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/EntityEspRenderer.kt new file mode 100644 index 000000000..2c4f3adba --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/EntityEspRenderer.kt @@ -0,0 +1,127 @@ +package com.lambda.graphics.renderer.esp + +import com.lambda.Lambda.mc +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.listener +import com.lambda.graphics.buffer.vao.VAO +import com.lambda.graphics.buffer.vao.vertex.VertexAttrib +import com.lambda.graphics.buffer.vao.vertex.VertexMode +import com.lambda.graphics.renderer.esp.DirectionMask.DOWN +import com.lambda.graphics.renderer.esp.DirectionMask.EAST +import com.lambda.graphics.renderer.esp.DirectionMask.NORTH +import com.lambda.graphics.renderer.esp.DirectionMask.SOUTH +import com.lambda.graphics.renderer.esp.DirectionMask.UP +import com.lambda.graphics.renderer.esp.DirectionMask.WEST +import com.lambda.graphics.renderer.esp.DirectionMask.hasDirection +import com.lambda.graphics.shader.Shader +import com.lambda.util.primitives.extension.max +import com.lambda.util.primitives.extension.min +import com.lambda.util.primitives.extension.partialTicks +import com.lambda.util.primitives.extension.prevPos +import net.minecraft.entity.Entity +import net.minecraft.util.math.Box +import java.awt.Color + +object EntityEspRenderer { + private val filled = VAO(VertexMode.TRIANGLES, VertexAttrib.Group.DYNAMIC_RENDERER) + private val outline = VAO(VertexMode.LINES, VertexAttrib.Group.DYNAMIC_RENDERER) + private val shader = Shader("renderer/pos_color", "renderer/box_dynamic") + + fun buildFilled(entity: Entity, color: Color, sides: Int) = filled.use { + val box = entity.boundingBox + + val delta = entity.prevPos.subtract(entity.pos) + val prevBox = Box(box.min.add(delta), box.max.add(delta)) + + val pos11 = prevBox.min + val pos12 = prevBox.max + val pos21 = box.min + val pos22 = box.max + + grow(8) + + val blb by lazy { vec3(pos11.x, pos11.y, pos11.z).vec3(pos21.x, pos21.y, pos21.z).color(color).end() } + val blf by lazy { vec3(pos11.x, pos11.y, pos12.z).vec3(pos21.x, pos21.y, pos22.z).color(color).end() } + val brb by lazy { vec3(pos12.x, pos11.y, pos11.z).vec3(pos22.x, pos21.y, pos21.z).color(color).end() } + val brf by lazy { vec3(pos12.x, pos11.y, pos12.z).vec3(pos22.x, pos21.y, pos22.z).color(color).end() } + val tlb by lazy { vec3(pos11.x, pos12.y, pos11.z).vec3(pos21.x, pos22.y, pos21.z).color(color).end() } + val tlf by lazy { vec3(pos11.x, pos12.y, pos12.z).vec3(pos21.x, pos22.y, pos22.z).color(color).end() } + val trb by lazy { vec3(pos12.x, pos12.y, pos11.z).vec3(pos22.x, pos22.y, pos21.z).color(color).end() } + val trf by lazy { vec3(pos12.x, pos12.y, pos12.z).vec3(pos22.x, pos22.y, pos22.z).color(color).end() } + + if (sides.hasDirection(EAST)) putQuad(brb, brf, trf, trb) + if (sides.hasDirection(WEST)) putQuad(blb, blf, tlf, tlb) + if (sides.hasDirection(UP)) putQuad(tlb, tlf, trf, trb) + if (sides.hasDirection(DOWN)) putQuad(blb, brb, brf, blf) + if (sides.hasDirection(SOUTH)) putQuad(blf, brf, trf, tlf) + if (sides.hasDirection(NORTH)) putQuad(blb, brb, trb, tlb) + } + + fun buildOutline(entity: Entity, color: Color, sides: Int, outlineMode: DirectionMask.OutlineMode) = outline.use { + val box = entity.boundingBox + + val delta = entity.prevPos.subtract(entity.pos) + val prevBox = Box(box.min.add(delta), box.max.add(delta)) + + val pos11 = prevBox.min + val pos12 = prevBox.max + val pos21 = box.min + val pos22 = box.max + + grow(8) + + val blb by lazy { vec3(pos11.x, pos11.y, pos11.z).vec3(pos21.x, pos21.y, pos21.z).color(color).end() } + val blf by lazy { vec3(pos11.x, pos11.y, pos12.z).vec3(pos21.x, pos21.y, pos22.z).color(color).end() } + val brb by lazy { vec3(pos12.x, pos11.y, pos11.z).vec3(pos22.x, pos21.y, pos21.z).color(color).end() } + val brf by lazy { vec3(pos12.x, pos11.y, pos12.z).vec3(pos22.x, pos21.y, pos22.z).color(color).end() } + val tlb by lazy { vec3(pos11.x, pos12.y, pos11.z).vec3(pos21.x, pos22.y, pos21.z).color(color).end() } + val tlf by lazy { vec3(pos11.x, pos12.y, pos12.z).vec3(pos21.x, pos22.y, pos22.z).color(color).end() } + val trb by lazy { vec3(pos12.x, pos12.y, pos11.z).vec3(pos22.x, pos22.y, pos21.z).color(color).end() } + val trf by lazy { vec3(pos12.x, pos12.y, pos12.z).vec3(pos22.x, pos22.y, pos22.z).color(color).end() } + + val hasEast = sides.hasDirection(EAST) + val hasWest = sides.hasDirection(WEST) + val hasUp = sides.hasDirection(UP) + val hasDown = sides.hasDirection(DOWN) + val hasSouth = sides.hasDirection(SOUTH) + val hasNorth = sides.hasDirection(NORTH) + + if (outlineMode.check(hasUp, hasNorth)) putLine(tlb, trb) + if (outlineMode.check(hasUp, hasSouth)) putLine(tlf, trf) + if (outlineMode.check(hasUp, hasWest)) putLine(tlb, tlf) + if (outlineMode.check(hasUp, hasEast)) putLine(trf, trb) + + if (outlineMode.check(hasDown, hasNorth)) putLine(blb, brb) + if (outlineMode.check(hasDown, hasSouth)) putLine(blf, brf) + if (outlineMode.check(hasDown, hasWest)) putLine(blb, blf) + if (outlineMode.check(hasDown, hasEast)) putLine(brb, brf) + + if (outlineMode.check(hasWest, hasNorth)) putLine(tlb, blb) + if (outlineMode.check(hasNorth, hasEast)) putLine(trb, brb) + if (outlineMode.check(hasEast, hasSouth)) putLine(trf, brf) + if (outlineMode.check(hasSouth, hasWest)) putLine(tlf, blf) + } + + fun render() { + shader.use() + shader["u_TickDelta"] = mc.partialTicks + shader["u_CameraPosition"] = mc.gameRenderer.camera.pos + + filled.render() + outline.render() + } + + init { + listener { + filled.clear() + outline.clear() + + RenderEvent.EntityESP().post() + + filled.upload() + outline.upload() + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/EspRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/EspRenderer.kt new file mode 100644 index 000000000..b0ef61969 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/EspRenderer.kt @@ -0,0 +1,246 @@ +package com.lambda.graphics.renderer.esp + +import com.lambda.Lambda.mc +import com.lambda.graphics.buffer.vao.IRenderContext +import com.lambda.graphics.buffer.vao.VAO +import com.lambda.graphics.buffer.vao.vertex.BufferUsage +import com.lambda.graphics.buffer.vao.vertex.VertexAttrib +import com.lambda.graphics.buffer.vao.vertex.VertexMode +import com.lambda.graphics.gl.GlStateUtils.withFaceCulling +import com.lambda.graphics.gl.GlStateUtils.withLineWidth +import com.lambda.graphics.renderer.esp.DirectionMask.DOWN +import com.lambda.graphics.renderer.esp.DirectionMask.EAST +import com.lambda.graphics.renderer.esp.DirectionMask.NORTH +import com.lambda.graphics.renderer.esp.DirectionMask.SOUTH +import com.lambda.graphics.renderer.esp.DirectionMask.UP +import com.lambda.graphics.renderer.esp.DirectionMask.WEST +import com.lambda.graphics.renderer.esp.DirectionMask.hasDirection +import com.lambda.graphics.shader.Shader +import com.lambda.module.modules.client.RenderSettings +import com.lambda.util.primitives.extension.max +import com.lambda.util.primitives.extension.min +import net.minecraft.util.math.Box +import net.minecraft.util.math.Vec3d +import java.awt.Color +import java.util.concurrent.ConcurrentHashMap + +class EspRenderer( + usage: BufferUsage = BufferUsage.STATIC +) { + private val faces = VAO(VertexMode.TRIANGLES, VertexAttrib.Group.STATIC_RENDERER, usage, true) + private val faceVertices = ConcurrentHashMap() + + private val outlines = VAO(VertexMode.LINES, VertexAttrib.Group.STATIC_RENDERER, usage, true) + private val outlineVertices = ConcurrentHashMap() + + private var updateFaces = false + private var updateOutline = false + + fun build( + box: Box, + filledColor: Color, + outlineColor: Color, + sides: Int = DirectionMask.ALL, + outlineMode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.OR + ) { + buildFilled(box, filledColor, sides) + buildOutline(box, outlineColor, sides, outlineMode) + } + + fun buildFilled(box: Box, color: Color, sides: Int = DirectionMask.ALL) = faces.use { + updateFaces = true + val pos1 = box.min + val pos2 = box.max + + grow(8) + + val blb by vertex(pos1.x, pos1.y, pos1.z, color) + val blf by vertex(pos1.x, pos1.y, pos2.z, color) + val brb by vertex(pos2.x, pos1.y, pos1.z, color) + val brf by vertex(pos2.x, pos1.y, pos2.z, color) + val tlb by vertex(pos1.x, pos2.y, pos1.z, color) + val tlf by vertex(pos1.x, pos2.y, pos2.z, color) + val trb by vertex(pos2.x, pos2.y, pos1.z, color) + val trf by vertex(pos2.x, pos2.y, pos2.z, color) + + if (sides.hasDirection(EAST)) putQuad(brb, trb, trf, brf) + if (sides.hasDirection(WEST)) putQuad(blb, blf, tlf, tlb) + if (sides.hasDirection(UP)) putQuad(tlb, tlf, trf, trb) + if (sides.hasDirection(DOWN)) putQuad(blb, brb, brf, blf) + if (sides.hasDirection(SOUTH)) putQuad(blf, brf, trf, tlf) + if (sides.hasDirection(NORTH)) putQuad(blb, tlb, trb, brb) + } + + fun buildOutline( + box: Box, + color: Color, + sides: Int = DirectionMask.ALL, + outlineMode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.OR + ) = outlines.use { + updateOutline = true + val pos1 = box.min + val pos2 = box.max + + grow(8) + + val blb by vertex(pos1.x, pos1.y, pos1.z, color) + val blf by vertex(pos1.x, pos1.y, pos2.z, color) + val brb by vertex(pos2.x, pos1.y, pos1.z, color) + val brf by vertex(pos2.x, pos1.y, pos2.z, color) + val tlb by vertex(pos1.x, pos2.y, pos1.z, color) + val tlf by vertex(pos1.x, pos2.y, pos2.z, color) + val trb by vertex(pos2.x, pos2.y, pos1.z, color) + val trf by vertex(pos2.x, pos2.y, pos2.z, color) + + val hasEast = sides.hasDirection(EAST) + val hasWest = sides.hasDirection(WEST) + val hasUp = sides.hasDirection(UP) + val hasDown = sides.hasDirection(DOWN) + val hasSouth = sides.hasDirection(SOUTH) + val hasNorth = sides.hasDirection(NORTH) + + if (outlineMode.check(hasUp, hasNorth)) putLine(tlb, trb) + if (outlineMode.check(hasUp, hasSouth)) putLine(tlf, trf) + if (outlineMode.check(hasUp, hasWest)) putLine(tlb, tlf) + if (outlineMode.check(hasUp, hasEast)) putLine(trf, trb) + + if (outlineMode.check(hasDown, hasNorth)) putLine(blb, brb) + if (outlineMode.check(hasDown, hasSouth)) putLine(blf, brf) + if (outlineMode.check(hasDown, hasWest)) putLine(blb, blf) + if (outlineMode.check(hasDown, hasEast)) putLine(brb, brf) + + if (outlineMode.check(hasWest, hasNorth)) putLine(tlb, blb) + if (outlineMode.check(hasNorth, hasEast)) putLine(trb, brb) + if (outlineMode.check(hasEast, hasSouth)) putLine(trf, brf) + if (outlineMode.check(hasSouth, hasWest)) putLine(tlf, blf) + } + + infix operator fun Vec3d.compareTo(other: Vec3d) = + lengthSquared().compareTo(other.lengthSquared()) + + fun buildMesh(boxes: Set, color: Color) { + val edges = hashMapOf() + val faces = hashMapOf() + + fun addFace(v1: Vec3d, v2: Vec3d, v3: Vec3d, v4: Vec3d) { + val face = Face(v1, v2, v3, v4) + faces[face] = faces.getOrDefault(face, 0) + 1 + } + + fun addEdge(v1: Vec3d, v2: Vec3d) { + val edge = if (v1 < v2) Edge(v1, v2) else Edge(v2, v1) + edges[edge] = edges.getOrDefault(edge, 0) + 1 + } + + boxes.forEach { box -> + val pos1 = box.min + val pos2 = box.max + + val blb = Vec3d(pos1.x, pos1.y, pos1.z) + val blf = Vec3d(pos1.x, pos1.y, pos2.z) + val brb = Vec3d(pos2.x, pos1.y, pos1.z) + val brf = Vec3d(pos2.x, pos1.y, pos2.z) + val tlb = Vec3d(pos1.x, pos2.y, pos1.z) + val tlf = Vec3d(pos1.x, pos2.y, pos2.z) + val trb = Vec3d(pos2.x, pos2.y, pos1.z) + val trf = Vec3d(pos2.x, pos2.y, pos2.z) + + addFace(blb, blf, brf, brb) + addFace(tlb, tlf, trf, trb) + addFace(blb, brb, trb, tlb) + addFace(blf, brf, trf, tlf) + addFace(blb, blf, tlf, tlb) + addFace(brb, brf, trf, trb) + + addEdge(tlb, trb) + addEdge(tlf, trf) + addEdge(tlb, tlf) + addEdge(trf, trb) + + addEdge(blb, brb) + addEdge(blf, brf) + addEdge(blb, blf) + addEdge(brb, brf) + + addEdge(tlb, blb) + addEdge(trb, brb) + addEdge(trf, brf) + addEdge(tlf, blf) + } + + this.faces.use { + updateFaces = true + faces.forEach { (face, count) -> + if (count % 2 == 0) return@forEach + grow(1) + putQuad( + vec3(face.v1.x, face.v1.y, face.v1.z).color(color).end(), + vec3(face.v2.x, face.v2.y, face.v2.z).color(color).end(), + vec3(face.v3.x, face.v3.y, face.v3.z).color(color).end(), + vec3(face.v4.x, face.v4.y, face.v4.z).color(color).end() + ) + } + } + + outlines.use { + updateOutline = true + edges.forEach { (edge, count) -> + if (count % 2 == 0) return@forEach + grow(1) + putLine( + vec3(edge.start.x, edge.start.y, edge.start.z).color(color).end(), + vec3(edge.end.x, edge.end.y, edge.end.z).color(color).end() + ) + } + } + } + + fun upload() { + if (updateFaces) { + updateFaces = false + faces.upload() + } + + if (updateOutline) { + updateOutline = false + outlines.upload() + } + } + + fun render() { + shader.use() + shader["u_CameraPosition"] = mc.gameRenderer.camera.pos + + withFaceCulling(faces::render) + withLineWidth(RenderSettings.outlineWidth, outlines::render) + } + + fun clear() { + faceVertices.clear() + outlineVertices.clear() + + faces.clear() + outlines.clear() + } + + private fun IRenderContext.vertex( + x: Double, y: Double, z: Double, + color: Color + ) = lazy { + val newVertex = { + vec3(x, y, z).color(color).end() + } + + if (RenderSettings.vertexMapping) { + faceVertices.getOrPut(Vertex(x, y, z, color), newVertex) + } else newVertex() + } + + data class Vertex(val x: Double, val y: Double, val z: Double, val color: Color) + data class Edge(val start: Vec3d, val end: Vec3d) + data class Face(val v1: Vec3d, val v2: Vec3d, val v3: Vec3d, val v4: Vec3d) + + companion object { + private val shader = Shader("renderer/pos_color", "renderer/box_static") + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/FontRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/FontRenderer.kt index 1b8e904ff..a70638a70 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/FontRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/FontRenderer.kt @@ -1,18 +1,28 @@ package com.lambda.graphics.renderer.gui.font -import com.lambda.graphics.buffer.vao.IRenderContext +import com.lambda.graphics.buffer.vao.VAO import com.lambda.graphics.buffer.vao.vertex.VertexAttrib import com.lambda.graphics.buffer.vao.vertex.VertexMode -import com.lambda.graphics.renderer.Renderer -import com.lambda.graphics.renderer.gui.font.glyph.CharInfo +import com.lambda.graphics.renderer.gui.font.glyph.GlyphInfo import com.lambda.graphics.shader.Shader -import com.lambda.module.modules.client.FontSettings +import com.lambda.module.modules.client.LambdaMoji +import com.lambda.module.modules.client.RenderSettings +import com.lambda.util.math.ColorUtils.a +import com.lambda.util.math.ColorUtils.setAlpha import com.lambda.util.math.Vec2d import java.awt.Color -class FontRenderer(private val font: LambdaFont) : Renderer(VertexMode.TRIANGLES, VertexAttrib.Group.FONT) { - var scaleMultiplier = 1.0 +class FontRenderer( + private val font: LambdaFont, + private val emojis: LambdaEmoji +) { + private val vao = VAO(VertexMode.TRIANGLES, VertexAttrib.Group.FONT) + private val scaleMultiplier = 1.0 + + /** + * Builds the vertex array for rendering the text. + */ fun build( text: String, position: Vec2d, @@ -20,82 +30,149 @@ class FontRenderer(private val font: LambdaFont) : Renderer(VertexMode.TRIANGLES scale: Double = 1.0, shadow: Boolean = true, ) = vao.use { + iterateText(text, scale, shadow, color) { char, pos1, pos2, color -> + grow(4) + putQuad( + vec2(pos1.x + position.x, pos1.y + position.y).vec2(char.uv1.x, char.uv1.y).color(color).end(), + vec2(pos1.x + position.x, pos2.y + position.y).vec2(char.uv1.x, char.uv2.y).color(color).end(), + vec2(pos2.x + position.x, pos2.y + position.y).vec2(char.uv2.x, char.uv2.y).color(color).end(), + vec2(pos2.x + position.x, pos1.y + position.y).vec2(char.uv2.x, char.uv1.y).color(color).end() + ) + } + } + + /** + * Calculates the width of the given text. + */ + fun getWidth(text: String, scale: Double = 1.0): Double { + var width = 0.0 + iterateText(text, scale, false) { char, _, _, _ -> width += char.width + gap } + return width * getScaleFactor(scale) + } + + /** + * Calculates the height of the text. + * + * The values are hardcoded + * We do not need to ask the emoji font since the height is smaller + */ + private fun getHeight(scale: Double = 1.0) = font.glyphs.fontHeight * getScaleFactor(scale) * 0.7 + + /** + * Iterates over each character and emoji in the text. + * + * @param text The text to iterate over. + * @param scale The scale of the text. + * @param shadow Whether to render a shadow. + * @param color The color of the text. + * @param block The block to execute for each character. + * + * @see GlyphInfo + */ + private fun iterateText( + text: String, + scale: Double, + shadow: Boolean, + color: Color = Color.WHITE, + block: (GlyphInfo, Vec2d, Vec2d, Color) -> Unit + ) { val actualScale = getScaleFactor(scale) - val scaledShadowShift = shadowShift * actualScale val scaledGap = gap * actualScale + val shadowColor = getShadowColor(color) + val emojiColor = Color.WHITE.setAlpha(color.a) var posX = 0.0 val posY = getHeight(scale) * -0.5 + baselineOffset * actualScale - text.toCharArray().forEach { char -> - val charInfo = font[char] ?: return@forEach - val scaledSize = charInfo.size * actualScale + val emojis = parseEmojis(text, emojis) - val pos1 = Vec2d(posX, posY) + fun draw(info: GlyphInfo, color: Color, offset: Double = 0.0) { + val scaledSize = info.size * actualScale + val pos1 = Vec2d(posX, posY) + offset * actualScale val pos2 = pos1 + scaledSize - if (shadow && FontSettings.shadow) { - val shadowPos1 = pos1 + scaledShadowShift - val shadowPos2 = shadowPos1 + scaledSize - putChar(position, shadowPos1, shadowPos2, shadowColor, charInfo) - } - - putChar(position, pos1, pos2, color, charInfo) - - posX += scaledSize.x + scaledGap + block(info, pos1, pos2, color) + if (offset == 0.0) posX += scaledSize.x + scaledGap } - } - fun getWidth(text: String, scale: Double = 1.0): Double { - var width = 0.0 + var index = 0 + textProcessor@ while (index < text.length) { + var innerLoopContact = false // Instead of using BreakContinueInInlineLambdas, we use this - text.forEach { char -> - val glyph = font[char] ?: return@forEach - width += glyph.size.x + gap - } + if (LambdaMoji.isEnabled) { + // Check if there are emojis to render + emojis.firstOrNull { index in it.second }?.let { emoji -> + if (index == emoji.second.first) draw(emoji.first, emojiColor) - return width * getScaleFactor(scale) - } + // Skip the emoji + index = emoji.second.last + 1 + innerLoopContact = true + } + } - fun getHeight(scale: Double = 1.0) = - font.glyphs.fontHeight * getScaleFactor(scale) * 0.7 + if (innerLoopContact) continue@textProcessor - private fun IRenderContext.putChar(pos: Vec2d, lt: Vec2d, rb: Vec2d, color: Color, ci: CharInfo) { - val x = pos.x - val y = pos.y + // Render chars + val charInfo = font[text[index]] ?: continue@textProcessor - grow(4) + // Draw a shadow before + if (shadow && RenderSettings.shadow && shadowShift > 0.0) { + draw(charInfo, shadowColor, shadowShift) + } - putQuad( - vec2(lt.x + x, lt.y + y).vec2(ci.uv1.x, ci.uv1.y).color(color).end(), - vec2(lt.x + x, rb.y + y).vec2(ci.uv1.x, ci.uv2.y).color(color).end(), - vec2(rb.x + x, rb.y + y).vec2(ci.uv2.x, ci.uv2.y).color(color).end(), - vec2(rb.x + x, lt.y + y).vec2(ci.uv2.x, ci.uv1.y).color(color).end() - ) + // Draw actual char over the shadow + draw(charInfo, color) + + index++ + } } - private fun getScaleFactor(scale: Double) = - scaleMultiplier * scale * 0.12 + private fun getScaleFactor(scale: Double) = scaleMultiplier * scale * 0.12 - private fun getShadowColor(color: Color) = Color( - (color.red * FontSettings.shadowBrightness).toInt(), - (color.green * FontSettings.shadowBrightness).toInt(), - (color.blue * FontSettings.shadowBrightness).toInt(), - color.alpha - ) + private fun getShadowColor(color: Color): Color { + return Color( + (color.red * RenderSettings.shadowBrightness).toInt(), + (color.green * RenderSettings.shadowBrightness).toInt(), + (color.blue * RenderSettings.shadowBrightness).toInt(), + color.alpha + ) + } - override fun render() { + fun render() { shader.use() + shader["u_EmojiTexture"] = 1 + font.glyphs.bind() - super.render() + emojis.glyphs.bind() + + vao.upload() + vao.render() + vao.clear() } companion object { private val shader = Shader("renderer/font") - private val shadowShift get() = FontSettings.shadowShift * 4.0 - private val baselineOffset get() = FontSettings.baselineOffset * 2.0f - 10f - private val gap get() = FontSettings.gapSetting * 0.5f - 0.8f + val shadowShift get() = RenderSettings.shadowShift * 5.0 + val baselineOffset get() = RenderSettings.baselineOffset * 2.0f - 10f + val gap get() = RenderSettings.gap * 0.5f - 0.8f + + private val emojiRegex = Regex(":[a-zA-Z0-9_]+:") + + /** + * Parses the emojis in the given text. + * + * @param text The text to parse. + * @return A list of pairs containing the glyph info and the range of the emoji in the text. + */ + fun parseEmojis(text: String, emojis: LambdaEmoji) = + mutableListOf>().apply { + emojiRegex.findAll(text).forEach { match -> + val emojiKey = match.value.substring(1, match.value.length - 1) + val charInfo = emojis[emojiKey] ?: return@forEach + add(charInfo to match.range) + } + } } -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/LambdaEmoji.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/LambdaEmoji.kt new file mode 100644 index 000000000..99ae00c4b --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/LambdaEmoji.kt @@ -0,0 +1,23 @@ +package com.lambda.graphics.renderer.gui.font + +import com.lambda.core.Loadable +import com.lambda.graphics.renderer.gui.font.glyph.EmojiGlyphs + +enum class LambdaEmoji(private val zipUrl: String) { + Twemoji("https://github.com/Edouard127/emoji-generator/releases/latest/download/emojis.zip"); + + lateinit var glyphs: EmojiGlyphs + + operator fun get(emoji: String) = glyphs.getEmoji(emoji) + + fun loadGlyphs() { + glyphs = EmojiGlyphs(zipUrl) + } + + object Loader : Loadable { + override fun load(): String { + entries.forEach(LambdaEmoji::loadGlyphs) + return "Loaded ${entries.size} emoji pools" + } + } +} diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/CharInfo.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/CharInfo.kt deleted file mode 100644 index 894a776e4..000000000 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/CharInfo.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.lambda.graphics.renderer.gui.font.glyph - -import com.lambda.util.math.Vec2d - -class CharInfo( - val size: Vec2d, - val uv1: Vec2d, - val uv2: Vec2d, -) \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/EmojiGlyphs.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/EmojiGlyphs.kt new file mode 100644 index 000000000..97cf936b9 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/EmojiGlyphs.kt @@ -0,0 +1,104 @@ +package com.lambda.graphics.renderer.gui.font.glyph + +import com.google.common.math.IntMath.pow +import com.lambda.Lambda.LOG +import com.lambda.graphics.texture.MipmapTexture +import com.lambda.module.modules.client.RenderSettings +import com.lambda.util.math.Vec2d +import java.awt.Color +import java.awt.Graphics2D +import java.awt.image.BufferedImage +import java.io.File +import java.net.URL +import java.util.zip.ZipFile +import javax.imageio.ImageIO +import kotlin.math.ceil +import kotlin.math.log2 +import kotlin.math.sqrt +import kotlin.system.measureTimeMillis + +// TODO: AbstractGlyphs to use for both Font & Emoji glyphs? +class EmojiGlyphs(zipUrl: String) { + private val emojiMap = mutableMapOf() + private val fontTexture: MipmapTexture + + private val image: BufferedImage + private val graphics: Graphics2D + + init { + var x = 0 + var y = 0 + + val time = measureTimeMillis { + val file = File.createTempFile("emoji", ".zip") + file.deleteOnExit() + + file.outputStream().use { output -> + URL(zipUrl).openStream().use { input -> + input.copyTo(output) + } + } + + ZipFile(file).use { zip -> + val firstImage = ImageIO.read(zip.getInputStream(zip.entries().nextElement())) + + val length = zip.size().toDouble() + + fun getTextureDimensionLength(dimLength: Int) = + pow(2, ceil(log2((dimLength + STEP) * sqrt(length))).toInt()) + + val width = getTextureDimensionLength(firstImage.width) + val height = getTextureDimensionLength(firstImage.height) + val texelSize = Vec2d.ONE / Vec2d(width, height) + + image = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) + graphics = image.graphics as Graphics2D + graphics.color = Color(0, 0, 0, 0) + + for (entry in zip.entries()) { + val name = entry.name.substringAfterLast("/").substringBeforeLast(".") + val emoji = ImageIO.read(zip.getInputStream(entry)) + + if (x + emoji.width >= image.width) { + y += emoji.height + STEP + x = 0 + } + + check(y + emoji.height < image.height) { "Can't load emoji glyphs. Texture size is too small" } + + 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 + + emojiMap[name] = GlyphInfo(size, -uv1, -uv2) + + x += emoji.width + STEP + } + } + + //ImageIO.write(image, "png", File("emoji.png")) + + fontTexture = MipmapTexture(image) + } + + LOG.info("Loaded ${emojiMap.size} emojis in $time ms") + } + + fun bind() { + with(fontTexture) { + bind(GL_TEXTURE_SLOT) + setLOD(RenderSettings.lodBias.toFloat()) + } + } + + fun getEmoji(emoji: String): GlyphInfo? = + emojiMap[emoji] + + companion object { + private const val STEP = 2 + + private const val GL_TEXTURE_SLOT = 1 // TODO: Texture slot borrowing + } +} diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/FontGlyphs.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/FontGlyphs.kt index b5ab9b94e..e00cbb284 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/FontGlyphs.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/FontGlyphs.kt @@ -3,7 +3,7 @@ package com.lambda.graphics.renderer.gui.font.glyph import com.lambda.Lambda import com.lambda.graphics.texture.MipmapTexture import com.lambda.graphics.texture.TextureUtils.getCharImage -import com.lambda.module.modules.client.FontSettings +import com.lambda.module.modules.client.RenderSettings import com.lambda.util.math.Vec2d import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap import java.awt.Color @@ -14,7 +14,7 @@ import kotlin.math.max import kotlin.system.measureTimeMillis class FontGlyphs(font: Font) { - private val charMap = Int2ObjectOpenHashMap() + private val charMap = Int2ObjectOpenHashMap() private val fontTexture: MipmapTexture var fontHeight = 0.0; private set @@ -35,13 +35,13 @@ class FontGlyphs(font: Font) { rowHeight = max(rowHeight, charImage.height + STEP) - if (x + charImage.width + STEP >= TEXTURE_SIZE) { + if (x + charImage.width >= TEXTURE_SIZE) { y += rowHeight x = 0 rowHeight = 0 } - check(y + charImage.height < TEXTURE_SIZE) { "Can't load font glyphs. Texture size is too small" } + check(y + charImage.height <= TEXTURE_SIZE) { "Can't load font glyphs. Texture size is too small" } graphics.drawImage(charImage, x, y, null) @@ -49,7 +49,7 @@ class FontGlyphs(font: Font) { val uv1 = Vec2d(x, y) * ONE_TEXEL_SIZE val uv2 = Vec2d(x, y).plus(size) * ONE_TEXEL_SIZE - charMap[char.code] = CharInfo(size, uv1, uv2) + charMap[char.code] = GlyphInfo(size, uv1, uv2) fontHeight = max(fontHeight, size.y) x += charImage.width + STEP @@ -58,27 +58,34 @@ class FontGlyphs(font: Font) { fontTexture = MipmapTexture(image) } - Lambda.LOG.info("Font ${font.fontName} loaded with ${charMap.size} characters (${time}ms)") + Lambda.LOG.info("Font ${font.fontName} loaded with ${charMap.size} characters in $time ms") } fun bind() { with(fontTexture) { - bind() - setLOD(FontSettings.lodBias.toFloat()) + bind(GL_TEXTURE_SLOT) + setLOD(RenderSettings.lodBias.toFloat()) } } - fun getChar(char: Char): CharInfo? = + fun getChar(char: Char): GlyphInfo? = charMap[char.code] companion object { + // The allocated texture slot + private const val GL_TEXTURE_SLOT = 0 + + // The space between glyphs is necessary to prevent artifacts from appearing when the font texture is blurred + private const val STEP = 2 + // Since most Lambda users probably have bad pc, the default size is 2048, which includes latin, cyrillic, greek and arabic // and in the future we could grow the textures when needed private const val CHAR_AMOUNT = 2048 - private const val TEXTURE_SIZE = 4096 - private const val ONE_TEXEL_SIZE = 1.0 / TEXTURE_SIZE - // The space between glyphs is necessary to prevent artifacts from appearing when the font texture is blurred - private const val STEP = 2 + // The size of the texture in pixels + private const val TEXTURE_SIZE = CHAR_AMOUNT * 2 + + // The size of one texel in UV coordinates + private const val ONE_TEXEL_SIZE = 1.0 / TEXTURE_SIZE } } diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/GlyphInfo.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/GlyphInfo.kt new file mode 100644 index 000000000..281bc8a3a --- /dev/null +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/font/glyph/GlyphInfo.kt @@ -0,0 +1,46 @@ +package com.lambda.graphics.renderer.gui.font.glyph + +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/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/AbstractRectRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/AbstractRectRenderer.kt index dd6f87941..9e893e36a 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/AbstractRectRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/AbstractRectRenderer.kt @@ -1,9 +1,9 @@ package com.lambda.graphics.renderer.gui.rect -import com.lambda.Lambda.mc +import com.lambda.graphics.RenderMain +import com.lambda.graphics.buffer.vao.VAO import com.lambda.graphics.buffer.vao.vertex.VertexAttrib import com.lambda.graphics.buffer.vao.vertex.VertexMode -import com.lambda.graphics.renderer.Renderer import com.lambda.graphics.shader.Shader import com.lambda.module.modules.client.GuiSettings import com.lambda.util.math.Vec2d @@ -11,17 +11,20 @@ import org.lwjgl.glfw.GLFW.glfwGetTime abstract class AbstractRectRenderer( attribGroup: VertexAttrib.Group, - val shader: Shader, -) : Renderer(VertexMode.TRIANGLES, attribGroup) { - override fun render() { + val shader: Shader +) { + protected val vao = VAO(VertexMode.TRIANGLES, attribGroup) + + open fun render() { shader.use() shader["u_Time"] = glfwGetTime() * GuiSettings.colorSpeed * 5.0 shader["u_Color1"] = GuiSettings.shadeColor1 shader["u_Color2"] = GuiSettings.shadeColor2 - val screen = Vec2d(mc.window.framebufferWidth, mc.window.framebufferHeight) - val size = Vec2d(GuiSettings.colorWidth, GuiSettings.colorHeight) - shader["u_Size"] = screen / size - super.render() + shader["u_Size"] = RenderMain.screenSize /Vec2d(GuiSettings.colorWidth, GuiSettings.colorHeight) + + vao.upload() + vao.render() + vao.clear() } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/FilledRectRenderer.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/FilledRectRenderer.kt index 3cb991b59..36fc7f991 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/FilledRectRenderer.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/FilledRectRenderer.kt @@ -59,7 +59,6 @@ class FilledRectRenderer : AbstractRectRenderer( override fun render() { shader.use() - super.render() } diff --git a/common/src/main/kotlin/com/lambda/graphics/shader/Shader.kt b/common/src/main/kotlin/com/lambda/graphics/shader/Shader.kt index 69c15e1d5..45cd06ed1 100644 --- a/common/src/main/kotlin/com/lambda/graphics/shader/Shader.kt +++ b/common/src/main/kotlin/com/lambda/graphics/shader/Shader.kt @@ -28,8 +28,7 @@ class Shader(fragmentPath: String, vertexPath: String) { fun use() { glUseProgram(id) - set("u_Projection", RenderMain.projectionMatrix) - set("u_ModelView", RenderMain.modelViewMatrix) + set("u_ProjModel", Matrix4f(RenderMain.projectionMatrix).mul(RenderMain.modelViewMatrix)) } private fun loc(name: String) = diff --git a/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt b/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt index 0ff1b10f2..b32302118 100644 --- a/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt +++ b/common/src/main/kotlin/com/lambda/graphics/texture/Texture.kt @@ -14,16 +14,5 @@ abstract class Texture { protected abstract fun init() - open fun bind() = bindTexture(id) - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Texture - - return id == other.id - } - - override fun hashCode() = id -} \ No newline at end of file + open fun bind(slot: Int = 0) = bindTexture(id, slot) +} diff --git a/common/src/main/kotlin/com/lambda/graphics/texture/TextureUtils.kt b/common/src/main/kotlin/com/lambda/graphics/texture/TextureUtils.kt index 1142dec70..5bb814180 100644 --- a/common/src/main/kotlin/com/lambda/graphics/texture/TextureUtils.kt +++ b/common/src/main/kotlin/com/lambda/graphics/texture/TextureUtils.kt @@ -4,10 +4,7 @@ import com.mojang.blaze3d.systems.RenderSystem import net.minecraft.client.texture.NativeImage import org.lwjgl.BufferUtils import org.lwjgl.opengl.GL13C.* -import java.awt.Color -import java.awt.Font -import java.awt.RenderingHints -import java.awt.Transparency +import java.awt.* import java.awt.image.BufferedImage import java.io.ByteArrayOutputStream import javax.imageio.ImageIO @@ -15,6 +12,9 @@ import kotlin.math.roundToInt import kotlin.math.sqrt object TextureUtils { + private val metricCache = mutableMapOf() + + fun bindTexture(id: Int, slot: Int = 0) { RenderSystem.activeTexture(GL_TEXTURE0 + slot) RenderSystem.bindTexture(id) @@ -85,24 +85,31 @@ object TextureUtils { return NativeImage.read(buffer).pointer } - fun getCharImage(font: Font, char: Char): BufferedImage? { - if (!font.canDisplay(char)) return null + 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() - val tempGraphics2D = BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB).createGraphics() - tempGraphics2D.font = font - val fontMetrics = tempGraphics2D.fontMetrics - tempGraphics2D.dispose() + graphics2D.font = font + graphics2D.dispose() + + image.graphics.getFontMetrics(font) + } - val charWidth = if (fontMetrics.charWidth(char) > 0) fontMetrics.charWidth(char) else 8 + 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(char.toString(), 0, fontMetrics.ascent) + graphics2D.drawString(codePoint.toString(), 0, fontMetrics.ascent) graphics2D.dispose() return charImage diff --git a/common/src/main/kotlin/com/lambda/gui/AbstractGuiConfigurable.kt b/common/src/main/kotlin/com/lambda/gui/AbstractGuiConfigurable.kt new file mode 100644 index 000000000..878b57ee9 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/gui/AbstractGuiConfigurable.kt @@ -0,0 +1,27 @@ +package com.lambda.gui + +import com.lambda.config.Configurable +import com.lambda.config.configurations.GuiConfig +import com.lambda.core.Loadable +import com.lambda.gui.impl.AbstractClickGui +import com.lambda.gui.impl.clickgui.windows.tag.CustomModuleWindow +import com.lambda.gui.impl.clickgui.windows.tag.TagWindow +import com.lambda.module.tag.ModuleTag +import com.lambda.util.math.Vec2d + +abstract class AbstractGuiConfigurable( + private val ownerGui: AbstractClickGui, + private val tags: Set, + override val name: String +) : Configurable(GuiConfig), Loadable { + var mainWindows by setting("windows", defaultWindows) + open var customWindows = mutableListOf() + + private val defaultWindows get() = + tags.mapIndexed { index, tag -> + TagWindow(tag, ownerGui).apply { + val step = 5.0 + position = Vec2d((width + step) * index, 0.0) + step + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/gui/GuiConfigurable.kt b/common/src/main/kotlin/com/lambda/gui/GuiConfigurable.kt index 024a847af..81c09a471 100644 --- a/common/src/main/kotlin/com/lambda/gui/GuiConfigurable.kt +++ b/common/src/main/kotlin/com/lambda/gui/GuiConfigurable.kt @@ -1,36 +1,11 @@ package com.lambda.gui -import com.lambda.config.Configurable -import com.lambda.config.configurations.GuiConfig -import com.lambda.core.Loadable import com.lambda.gui.impl.clickgui.LambdaClickGui import com.lambda.gui.impl.clickgui.windows.tag.CustomModuleWindow -import com.lambda.gui.impl.clickgui.windows.tag.TagWindow import com.lambda.module.tag.ModuleTag -import com.lambda.util.math.Vec2d -object GuiConfigurable : Configurable(GuiConfig), Loadable { - override val name = "gui" - private val ownerGui = LambdaClickGui - - val mainWindows by setting("windows", defaultWindows).apply { - onValueChange { _, _ -> - ownerGui.updateWindows() - } - } - - val customWindows by setting("custom windows", listOf()).apply { - onValueChange { _, _ -> - ownerGui.updateWindows() - } - } - - private val defaultWindows - get() = - ModuleTag.defaults.mapIndexed { index, tag -> - TagWindow(tag, ownerGui).apply { - val step = 5.0 - position = Vec2d((width + step) * index, 0.0) + step - } - } +object GuiConfigurable : AbstractGuiConfigurable( + LambdaClickGui, ModuleTag.defaults, "gui" +) { + override var customWindows by setting("custom windows", listOf()) } diff --git a/common/src/main/kotlin/com/lambda/gui/HudGuiConfigurable.kt b/common/src/main/kotlin/com/lambda/gui/HudGuiConfigurable.kt new file mode 100644 index 000000000..d69ac70d2 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/gui/HudGuiConfigurable.kt @@ -0,0 +1,8 @@ +package com.lambda.gui + +import com.lambda.gui.impl.hudgui.LambdaHudGui +import com.lambda.module.tag.ModuleTag + +object HudGuiConfigurable : AbstractGuiConfigurable( + LambdaHudGui, ModuleTag.hudDefaults, "hudgui" +) \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/gui/api/LambdaGui.kt b/common/src/main/kotlin/com/lambda/gui/api/LambdaGui.kt index 1d7ff80a7..c8dad409e 100644 --- a/common/src/main/kotlin/com/lambda/gui/api/LambdaGui.kt +++ b/common/src/main/kotlin/com/lambda/gui/api/LambdaGui.kt @@ -1,13 +1,15 @@ package com.lambda.gui.api import com.lambda.Lambda.mc -import com.lambda.event.EventFlow.syncListeners +import com.lambda.event.Muteable import com.lambda.event.events.RenderEvent import com.lambda.event.events.TickEvent -import com.lambda.event.listener.UnsafeListener +import com.lambda.event.listener.SafeListener.Companion.listener import com.lambda.graphics.animation.AnimationTicker import com.lambda.gui.api.component.core.IComponent +import com.lambda.gui.impl.AbstractClickGui import com.lambda.module.Module +import com.lambda.threading.runSafe import com.lambda.util.KeyCode import com.lambda.util.Mouse import com.lambda.util.Nameable @@ -18,27 +20,29 @@ import net.minecraft.client.gui.DrawContext import net.minecraft.client.gui.screen.Screen import net.minecraft.text.Text -@Suppress("LeakingThis") abstract class LambdaGui( override val name: String, - private val owner: Module? = null, -) : Screen(Text.of(name)), IComponent, Nameable { - private var screenSize = Vec2d.ZERO + private val owner: Module? = null +) : Screen(Text.of(name)), IComponent, Nameable, Muteable { + protected var screenSize = Vec2d.ZERO override val rect get() = Rect(Vec2d.ZERO, screenSize) val isOpen get() = mc.currentScreen == this + override val isMuted: Boolean get() = !isOpen + private var closingAction: (() -> Unit)? = null val animation = AnimationTicker() - private val renderListener = UnsafeListener(0, this, false) { event -> - event as RenderEvent.GUI.Scaled - screenSize = event.screenSize - onEvent(GuiEvent.Render()) - } + init { + listener { event -> + screenSize = event.screenSize + onEvent(GuiEvent.Render()) + } - private val tickListener = UnsafeListener(0, this, false) { - animation.tick() - onEvent(GuiEvent.Tick()) + listener { + animation.tick() + onEvent(GuiEvent.Tick()) + } } /** @@ -47,37 +51,52 @@ abstract class LambdaGui( * No safe context required (TODO: let user open clickgui via main menu) */ fun show() { + owner?.enable() if (isOpen) return - mc.currentScreen?.close() - recordRenderCall { // wait for the previous screen to be closed - mc.setScreen(this) + when (val screen = mc.currentScreen) { + is AbstractClickGui -> { + screen.close() + + screen.setCloseTask { + mc.setScreen(this) + } + } + + else -> { + screen?.close() + + recordRenderCall { + mc.setScreen(this) + } + } } } final override fun onDisplayed() { onEvent(GuiEvent.Show()) - - with(syncListeners) { - subscribe(renderListener) - subscribe(tickListener) - } } - final override fun removed() { + override fun removed() { onEvent(GuiEvent.Hide()) - // quick crashfix (is there any other way to prevent gui being closed twice?) - mc.currentScreen = null - owner?.disable() - mc.currentScreen = this + runSafe { + // quick crashfix (is there any other way to prevent gui being closed twice?) + mc.currentScreen = null + owner?.disable() + mc.currentScreen = this@LambdaGui - with(syncListeners) { - unsubscribe(renderListener) - unsubscribe(tickListener) + closingAction?.let { + recordRenderCall(it) + closingAction = null + } } } + fun setCloseTask(block: () -> Unit) { + closingAction = block + } + final override fun render(context: DrawContext?, mouseX: Int, mouseY: Int, delta: Float) { // Let's remove background tint } @@ -93,7 +112,6 @@ abstract class LambdaGui( return true } - final override fun charTyped(chr: Char, modifiers: Int): Boolean { onEvent(GuiEvent.CharTyped(chr)) return true diff --git a/common/src/main/kotlin/com/lambda/gui/api/RenderLayer.kt b/common/src/main/kotlin/com/lambda/gui/api/RenderLayer.kt index 7ca93d3c8..034df4ff7 100644 --- a/common/src/main/kotlin/com/lambda/gui/api/RenderLayer.kt +++ b/common/src/main/kotlin/com/lambda/gui/api/RenderLayer.kt @@ -2,17 +2,21 @@ package com.lambda.gui.api import com.lambda.graphics.renderer.gui.font.FontRenderer import com.lambda.graphics.renderer.gui.font.LambdaFont +import com.lambda.graphics.renderer.gui.font.LambdaEmoji import com.lambda.graphics.renderer.gui.rect.FilledRectRenderer import com.lambda.graphics.renderer.gui.rect.OutlineRectRenderer class RenderLayer { val filled = FilledRectRenderer() val outline = OutlineRectRenderer() - val font = FontRenderer(LambdaFont.FiraSansRegular) + val font = FontRenderer( + LambdaFont.FiraSansRegular, + LambdaEmoji.Twemoji, + ) fun render() { filled.render() outline.render() font.render() } -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/gui/api/component/ListWindow.kt b/common/src/main/kotlin/com/lambda/gui/api/component/ListWindow.kt index 6e9b976c5..3128a9190 100644 --- a/common/src/main/kotlin/com/lambda/gui/api/component/ListWindow.kt +++ b/common/src/main/kotlin/com/lambda/gui/api/component/ListWindow.kt @@ -2,7 +2,7 @@ package com.lambda.gui.api.component import com.lambda.gui.api.GuiEvent import com.lambda.gui.api.component.button.ListButton -import com.lambda.gui.impl.clickgui.AbstractClickGui +import com.lambda.gui.impl.AbstractClickGui abstract class ListWindow( owner: AbstractClickGui, diff --git a/common/src/main/kotlin/com/lambda/gui/api/component/WindowComponent.kt b/common/src/main/kotlin/com/lambda/gui/api/component/WindowComponent.kt index 58f074ba9..e795b482f 100644 --- a/common/src/main/kotlin/com/lambda/gui/api/component/WindowComponent.kt +++ b/common/src/main/kotlin/com/lambda/gui/api/component/WindowComponent.kt @@ -6,7 +6,7 @@ import com.lambda.gui.api.GuiEvent import com.lambda.gui.api.RenderLayer import com.lambda.gui.api.component.core.list.ChildComponent import com.lambda.gui.api.component.core.list.ChildLayer -import com.lambda.gui.impl.clickgui.AbstractClickGui +import com.lambda.gui.impl.AbstractClickGui import com.lambda.module.modules.client.ClickGui import com.lambda.module.modules.client.GuiSettings import com.lambda.util.Mouse @@ -145,12 +145,4 @@ abstract class WindowComponent( } } } - - fun destroy() { - gui.apply { - scheduleAction { - windows.children.remove(this@WindowComponent) - } - } - } } diff --git a/common/src/main/kotlin/com/lambda/gui/api/component/button/ButtonComponent.kt b/common/src/main/kotlin/com/lambda/gui/api/component/button/ButtonComponent.kt index 82e4bbf7b..6cd18936b 100644 --- a/common/src/main/kotlin/com/lambda/gui/api/component/button/ButtonComponent.kt +++ b/common/src/main/kotlin/com/lambda/gui/api/component/button/ButtonComponent.kt @@ -22,10 +22,9 @@ abstract class ButtonComponent( abstract val size: Vec2d abstract val text: String - protected open val textColor - get() = lerp(Color.WHITE, GuiSettings.mainColor, activeAnimation).multAlpha( - showAnimation - ) + protected open val textColor get() = lerp(Color.WHITE, GuiSettings.mainColor, activeAnimation).multAlpha(showAnimation) + protected open val centerText = false + protected abstract var activeAnimation: Double private val actualSize get() = Vec2d(if (size.x == FILL_PARENT) owner.rect.size.x else size.x, size.y) @@ -72,12 +71,14 @@ abstract class ButtonComponent( ) // Text + val textScale = 1.0 - pressAnimation * 0.08 val textX = ClickGui.windowPadding + interactAnimation + hoverFontAnimation + val textXCentered = rect.size.x * 0.5 - renderer.font.getWidth(text, textScale) * 0.5 renderer.font.build( text = text, - position = Vec2d(rect.left + textX, rect.center.y), + position = Vec2d(rect.left + if (!centerText) textX else textXCentered, rect.center.y), color = textColor, - scale = 1.0 - pressAnimation * 0.08 + scale = textScale ) } diff --git a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/AbstractClickGui.kt b/common/src/main/kotlin/com/lambda/gui/impl/AbstractClickGui.kt similarity index 51% rename from common/src/main/kotlin/com/lambda/gui/impl/clickgui/AbstractClickGui.kt rename to common/src/main/kotlin/com/lambda/gui/impl/AbstractClickGui.kt index 23aa80a27..cd2208426 100644 --- a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/AbstractClickGui.kt +++ b/common/src/main/kotlin/com/lambda/gui/impl/AbstractClickGui.kt @@ -1,29 +1,39 @@ -package com.lambda.gui.impl.clickgui +package com.lambda.gui.impl import com.lambda.Lambda.mc import com.lambda.graphics.animation.Animation.Companion.exp +import com.lambda.gui.AbstractGuiConfigurable import com.lambda.gui.api.GuiEvent import com.lambda.gui.api.LambdaGui import com.lambda.gui.api.component.WindowComponent import com.lambda.gui.api.component.core.list.ChildLayer import com.lambda.gui.impl.clickgui.buttons.SettingButton import com.lambda.gui.impl.clickgui.windows.ModuleWindow +import com.lambda.gui.impl.clickgui.windows.tag.CustomModuleWindow +import com.lambda.gui.impl.clickgui.windows.tag.TagWindow +import com.lambda.gui.impl.hudgui.LambdaHudGui +import com.lambda.module.Module import com.lambda.module.modules.client.ClickGui import com.lambda.util.Mouse import com.mojang.blaze3d.systems.RenderSystem.recordRenderCall -abstract class AbstractClickGui(name: String = "ClickGui") : LambdaGui(name, ClickGui) { - private var activeWindow: WindowComponent<*>? = null - private var closing = false +abstract class AbstractClickGui(name: String, owner: Module? = null) : LambdaGui(name, owner) { + protected var hoveredWindow: WindowComponent<*>? = null + protected var closing = false final override var childShowAnimation by animation.exp(0.0, 1.0, { if (closing) ClickGui.closeSpeed else ClickGui.openSpeed }) { !closing }; private set val windows = ChildLayer, AbstractClickGui>(this, this, ::rect) { child -> - child == activeWindow && !closing + child == hoveredWindow && !closing } + abstract val moduleFilter: (Module) -> Boolean + abstract val configurable: AbstractGuiConfigurable + + private var lastTickedUpdate = 0L + private val actionPool = ArrayDeque<() -> Unit>() fun scheduleAction(block: () -> Unit) = actionPool.add(block) @@ -32,22 +42,29 @@ abstract class AbstractClickGui(name: String = "ClickGui") : LambdaGui(name, Cli when (e) { is GuiEvent.Show -> { - activeWindow = null + hoveredWindow = null closing = false childShowAnimation = 0.0 + updateWindows() } is GuiEvent.Tick -> { + val time = System.currentTimeMillis() + if (time - lastTickedUpdate > 1000L) { + lastTickedUpdate = time + updateWindows() + } + if (closing && childShowAnimation < 0.01) mc.setScreen(null) } is GuiEvent.MouseClick -> { - if (e.action == Mouse.Action.Click) activeWindow?.focus() + if (e.action == Mouse.Action.Click) hoveredWindow?.focus() } is GuiEvent.MouseMove -> { - activeWindow = windows.children.lastOrNull { child -> - e.mouse in child.rect + hoveredWindow = windows.children.lastOrNull { child -> + e.mouse in child.rect } } } @@ -55,13 +72,6 @@ abstract class AbstractClickGui(name: String = "ClickGui") : LambdaGui(name, Cli windows.onEvent(e) } - fun showWindow(window: WindowComponent<*>) { - // we have to wait some time to place this window over other ones - recordRenderCall { - windows.children.add(window) - } - } - fun unfocusSettings() { windows.children.filterIsInstance().forEach { moduleWindow -> moduleWindow.contentComponents.children.forEach { moduleButton -> @@ -70,6 +80,28 @@ abstract class AbstractClickGui(name: String = "ClickGui") : LambdaGui(name, Cli } } + private inline fun syncWindows(configWindows: MutableList) = windows.apply { + // Add windows from config + configWindows.filter { it !in children }.forEach(children::add) + + // Remove outdated/deleted windows + children.removeIf { + it is T && it !in configWindows + } + + // Update config + configWindows.clear() + configWindows.addAll(children.filterIsInstance()) + } + + fun updateWindows() { + syncWindows(configurable.mainWindows) + + if (this != LambdaHudGui) { + syncWindows(configurable.customWindows) + } + } + override fun close() { if (!isOpen) return closing = true diff --git a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/LambdaClickGui.kt b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/LambdaClickGui.kt index 2fbad35ad..38fe0c278 100644 --- a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/LambdaClickGui.kt +++ b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/LambdaClickGui.kt @@ -2,22 +2,75 @@ package com.lambda.gui.impl.clickgui import com.lambda.gui.GuiConfigurable import com.lambda.gui.api.GuiEvent -import com.lambda.gui.api.component.WindowComponent -import com.lambda.gui.impl.clickgui.windows.tag.CustomModuleWindow +import com.lambda.gui.api.RenderLayer +import com.lambda.gui.api.component.button.ButtonComponent +import com.lambda.gui.api.component.core.list.ChildLayer +import com.lambda.gui.impl.AbstractClickGui +import com.lambda.gui.impl.hudgui.LambdaHudGui +import com.lambda.module.HudModule +import com.lambda.module.Module +import com.lambda.module.modules.client.ClickGui +import com.lambda.module.modules.client.GuiSettings +import com.lambda.util.math.ColorUtils.multAlpha +import com.lambda.util.math.Vec2d + +object LambdaClickGui : AbstractClickGui("ClickGui", ClickGui) { + override val moduleFilter: (Module) -> Boolean = { + it !is HudModule + } + + override val configurable get() = GuiConfigurable + + private val buttonRenderer = RenderLayer() + private val buttons = ChildLayer.Drawable(this, this, buttonRenderer, ::rect) { + hoveredWindow == null && !closing + } -object LambdaClickGui : AbstractClickGui() { override fun onEvent(e: GuiEvent) { - if (e is GuiEvent.Show || e is GuiEvent.Tick) updateWindows() + buttons.onEvent(e) + if (e is GuiEvent.Render) buttonRenderer.render() + super.onEvent(e) } - fun updateWindows() { - windows.apply { - val windows = GuiConfigurable.mainWindows + GuiConfigurable.customWindows - windows.subtract(children.toSet()).forEach(::showWindow) + init { + buttons.children.add(object : ButtonComponent(buttons) { + override val position: Vec2d get() = screenSize - size - Vec2d.ONE * 5.0 + override val size = Vec2d(30.0, 15.0) + override val text = "HUD" + override val centerText = true + + override var activeAnimation; get() = pressAnimation; set(_) {} + + override fun onEvent(e: GuiEvent) { + super.onEvent(e) + + if (e is GuiEvent.Render) { + val rect = rect.shrink(interactAnimation) + + // Background + renderer.filled.build( + rect = rect, + roundRadius = ClickGui.windowRadius, + color = GuiSettings.backgroundColor.multAlpha(childShowAnimation), + shade = GuiSettings.shadeBackground + ) + + // Outline + renderer.outline.build( + rect = rect, + roundRadius = ClickGui.windowRadius, + innerGlow = ClickGui.windowRadius.coerceAtMost(1.0), + outerGlow = ClickGui.windowRadius, + color = GuiSettings.mainColor.multAlpha(childShowAnimation), + shade = GuiSettings.shadeBackground + ) + } + } - children.filter { it !in windows && it is CustomModuleWindow } - .forEach(WindowComponent<*>::destroy) - } + override fun performClickAction(e: GuiEvent.MouseClick) { + LambdaHudGui.show() + } + }) } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/buttons/setting/BindButton.kt b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/buttons/setting/BindButton.kt index b5304b2b2..c86e0d533 100644 --- a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/buttons/setting/BindButton.kt +++ b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/buttons/setting/BindButton.kt @@ -4,7 +4,7 @@ import com.lambda.config.settings.complex.KeyBindSetting import com.lambda.gui.api.GuiEvent import com.lambda.gui.api.component.button.InputBarOverlay import com.lambda.gui.api.component.core.list.ChildLayer -import com.lambda.gui.impl.clickgui.AbstractClickGui +import com.lambda.gui.impl.AbstractClickGui import com.lambda.gui.impl.clickgui.buttons.ModuleButton import com.lambda.gui.impl.clickgui.buttons.SettingButton import com.lambda.util.KeyCode diff --git a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/buttons/setting/NumberSlider.kt b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/buttons/setting/NumberSlider.kt index 24e68d42c..859464aac 100644 --- a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/buttons/setting/NumberSlider.kt +++ b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/buttons/setting/NumberSlider.kt @@ -4,7 +4,7 @@ import com.lambda.config.settings.NumericSetting import com.lambda.gui.api.GuiEvent import com.lambda.gui.api.component.button.InputBarOverlay import com.lambda.gui.api.component.core.list.ChildLayer -import com.lambda.gui.impl.clickgui.AbstractClickGui +import com.lambda.gui.impl.AbstractClickGui import com.lambda.gui.impl.clickgui.buttons.ModuleButton import com.lambda.gui.impl.clickgui.buttons.SettingButton import com.lambda.util.Mouse diff --git a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/buttons/setting/StringButton.kt b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/buttons/setting/StringButton.kt index c96eb0845..5226e4278 100644 --- a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/buttons/setting/StringButton.kt +++ b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/buttons/setting/StringButton.kt @@ -4,7 +4,7 @@ import com.lambda.config.settings.StringSetting import com.lambda.gui.api.GuiEvent import com.lambda.gui.api.component.button.InputBarOverlay import com.lambda.gui.api.component.core.list.ChildLayer -import com.lambda.gui.impl.clickgui.AbstractClickGui +import com.lambda.gui.impl.AbstractClickGui import com.lambda.gui.impl.clickgui.buttons.ModuleButton import com.lambda.gui.impl.clickgui.buttons.SettingButton import com.lambda.util.math.ColorUtils.multAlpha diff --git a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/windows/ModuleWindow.kt b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/windows/ModuleWindow.kt index fd518c234..f1ef7eaf7 100644 --- a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/windows/ModuleWindow.kt +++ b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/windows/ModuleWindow.kt @@ -2,7 +2,7 @@ package com.lambda.gui.impl.clickgui.windows import com.lambda.gui.api.GuiEvent import com.lambda.gui.api.component.ListWindow -import com.lambda.gui.impl.clickgui.AbstractClickGui +import com.lambda.gui.impl.AbstractClickGui import com.lambda.gui.impl.clickgui.buttons.ModuleButton import com.lambda.module.Module @@ -22,7 +22,7 @@ abstract class ModuleWindow( lastUpdate = time contentComponents.apply { - val modules = getModuleList() + val modules = getModuleList().filter((gui as AbstractClickGui).moduleFilter) // Add missing module buttons modules.filter { module -> @@ -33,12 +33,8 @@ abstract class ModuleWindow( .forEach(contentComponents.children::add) // Remove deleted modules - children.forEach { button -> - if (button.module !in modules) { - this@ModuleWindow.gui.scheduleAction { - children.remove(button) - } - } + children.removeIf { + it.module !in modules } } } diff --git a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/windows/tag/CustomModuleWindow.kt b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/windows/tag/CustomModuleWindow.kt index 38f50e4fd..9dea09470 100644 --- a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/windows/tag/CustomModuleWindow.kt +++ b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/windows/tag/CustomModuleWindow.kt @@ -1,6 +1,6 @@ package com.lambda.gui.impl.clickgui.windows.tag -import com.lambda.gui.impl.clickgui.AbstractClickGui +import com.lambda.gui.impl.AbstractClickGui import com.lambda.gui.impl.clickgui.windows.ModuleWindow import com.lambda.module.Module diff --git a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/windows/tag/TagWindow.kt b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/windows/tag/TagWindow.kt index 56681a56f..cf5433d95 100644 --- a/common/src/main/kotlin/com/lambda/gui/impl/clickgui/windows/tag/TagWindow.kt +++ b/common/src/main/kotlin/com/lambda/gui/impl/clickgui/windows/tag/TagWindow.kt @@ -1,6 +1,6 @@ package com.lambda.gui.impl.clickgui.windows.tag -import com.lambda.gui.impl.clickgui.AbstractClickGui +import com.lambda.gui.impl.AbstractClickGui import com.lambda.gui.impl.clickgui.windows.ModuleWindow import com.lambda.module.ModuleRegistry import com.lambda.module.tag.ModuleTag diff --git a/common/src/main/kotlin/com/lambda/gui/impl/hudgui/LambdaHudGui.kt b/common/src/main/kotlin/com/lambda/gui/impl/hudgui/LambdaHudGui.kt new file mode 100644 index 000000000..83484eec5 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/gui/impl/hudgui/LambdaHudGui.kt @@ -0,0 +1,55 @@ +package com.lambda.gui.impl.hudgui + +import com.lambda.gui.HudGuiConfigurable +import com.lambda.gui.api.GuiEvent +import com.lambda.gui.impl.AbstractClickGui +import com.lambda.gui.impl.clickgui.LambdaClickGui +import com.lambda.module.HudModule +import com.lambda.module.Module +import com.lambda.module.ModuleRegistry +import com.lambda.util.Mouse +import com.lambda.util.math.Vec2d + +object LambdaHudGui : AbstractClickGui("HudGui") { + override val moduleFilter: (Module) -> Boolean = { + it is HudModule + } + + override val configurable get() = HudGuiConfigurable + private val hudModules get() = ModuleRegistry.modules.filterIsInstance() + + private var dragInfo: Pair? = null + + override fun onEvent(e: GuiEvent) { + super.onEvent(e) + + when (e) { + is GuiEvent.Show -> { + dragInfo = null + + setCloseTask { + LambdaClickGui.show() + } + } + + is GuiEvent.MouseMove -> { + if (closing) dragInfo = null + + dragInfo?.let { + it.second.position = e.mouse - it.first + } + } + + is GuiEvent.MouseClick -> { + dragInfo = null + + if (hoveredWindow == null && + e.action == Mouse.Action.Click && + e.button == Mouse.Button.Left + ) hudModules.firstOrNull { e.mouse in it.rect }?.let { + dragInfo = e.mouse - it.position to it + } + } + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/module/HudModule.kt b/common/src/main/kotlin/com/lambda/module/HudModule.kt new file mode 100644 index 000000000..e7424390b --- /dev/null +++ b/common/src/main/kotlin/com/lambda/module/HudModule.kt @@ -0,0 +1,88 @@ +package com.lambda.module + +import com.lambda.event.events.RenderEvent +import com.lambda.event.listener.SafeListener.Companion.listener +import com.lambda.gui.api.RenderLayer +import com.lambda.module.tag.ModuleTag +import com.lambda.util.KeyCode +import com.lambda.util.math.Rect +import com.lambda.util.math.Vec2d + +abstract class HudModule( + name: String, + description: String = "", + alwaysListening: Boolean = false, + enabledByDefault: Boolean = false, + defaultKeybind: KeyCode = KeyCode.UNBOUND, +) : Module(name, description, setOf(ModuleTag.HUD), alwaysListening, enabledByDefault, defaultKeybind) { + private val renderCallables = mutableListOf Unit>() + + protected abstract val width: Double + protected abstract val height: Double + private val size get() = Vec2d(width, height) + + private var relativePosX by setting("Position X", 0.0, -10000.0..10000.0, 0.1) { false } + private var relativePosY by setting("Position Y", 0.0, -10000.0..10000.0, 0.1) { false } + private var relativePos get() = Vec2d(relativePosX, relativePosY); set(value) { + val vec = absToRelative(relativeToAbs(value)) + relativePosX = vec.x; relativePosY = vec.y + } + + var position get() = relativeToAbs(relativePos).let { + val x = it.x.coerceIn(0.0, screenSize.x - width) + val y = it.y.coerceIn(0.0, screenSize.y - height) + Vec2d(x, y) + }; set(value) { relativePos = absToRelative(value) } + + private fun relativeToAbs(posIn: Vec2d) = posIn + (screenSize - size) * dockingMultiplier + private fun absToRelative(posIn: Vec2d) = posIn - (screenSize - size) * dockingMultiplier + + private val dockingH by setting("Docking H", HAlign.LEFT).apply { + onValueChange { from, to -> + val delta = to.multiplier - from.multiplier + relativePosX += delta * (size.x - screenSize.x) + } + } + private val dockingV by setting("Docking V", VAlign.TOP).apply { + onValueChange { from, to -> + val delta = to.multiplier - from.multiplier + relativePosY += delta * (size.y - screenSize.y) + } + } + + private val dockingMultiplier get() = Vec2d(dockingH.multiplier, dockingV.multiplier) + + val rect get() = Rect.basedOn(position, width, height) + + private var screenSize = Vec2d.ZERO + private val renderer = RenderLayer() + + protected fun onRender(block: RenderLayer.() -> Unit) = + renderCallables.add(block) + + init { + listener { event -> + screenSize = event.screenSize + + renderCallables.forEach { function -> + function.invoke(renderer) + } + + renderer.render() + } + } + + @Suppress("UNUSED") + enum class HAlign(val multiplier: Float) { + LEFT(0.0f), + CENTER(0.5f), + RIGHT(1.0f) + } + + @Suppress("UNUSED") + enum class VAlign(val multiplier: Float) { + TOP(0.0f), + CENTER(0.5f), + BOTTOM(1.0f) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/module/hud/TestHudModule.kt b/common/src/main/kotlin/com/lambda/module/hud/TestHudModule.kt new file mode 100644 index 000000000..c1355b932 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/module/hud/TestHudModule.kt @@ -0,0 +1,19 @@ +package com.lambda.module.hud + +import com.lambda.module.HudModule +import java.awt.Color + +object TestHudModule : HudModule( + "TestHudModule", + enabledByDefault = true +) { + override val width = 100.0 + override val height = 30.0 + + init { + onRender { + filled.build(rect, 2.0, Color(180, 180, 180, 180), true) + outline.build(rect, 2.0, shade = true) + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/ClickGui.kt b/common/src/main/kotlin/com/lambda/module/modules/client/ClickGui.kt index 5b0ea7481..80864eaa7 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/ClickGui.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/ClickGui.kt @@ -1,8 +1,11 @@ package com.lambda.module.modules.client import com.lambda.event.events.ClientEvent +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listener import com.lambda.event.listener.UnsafeListener.Companion.unsafeListener import com.lambda.gui.impl.clickgui.LambdaClickGui +import com.lambda.gui.impl.hudgui.LambdaHudGui import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.KeyCode @@ -30,6 +33,7 @@ object ClickGui : Module( onDisable { LambdaClickGui.close() + LambdaHudGui.close() } unsafeListener { diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/FontSettings.kt b/common/src/main/kotlin/com/lambda/module/modules/client/FontSettings.kt deleted file mode 100644 index e552225b5..000000000 --- a/common/src/main/kotlin/com/lambda/module/modules/client/FontSettings.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.lambda.module.modules.client - -import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag - -object FontSettings : Module( - name = "FontSettings", - description = "Font renderer configuration", - defaultTags = setOf(ModuleTag.CLIENT) -) { - val shadow by setting("Shadow", true) - val shadowBrightness by setting("Shadow Brightness", 0.35, 0.0..0.5, 0.01, visibility = { shadow }) - val shadowShift by setting("Shadow Shift", 1.0, 0.0..2.0, 0.05, visibility = { shadow }) - val gapSetting by setting("Gap", 1.5, -10.0..10.0, 0.5) - val baselineOffset by setting("Vertical Offset", 0.0, -10.0..10.0, 0.5) - private val lodBiasSetting by setting("Smoothing", 0.0, -10.0..10.0, 0.5) - - val lodBias get() = lodBiasSetting * 0.25f - 0.75f -} diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/LambdaMoji.kt b/common/src/main/kotlin/com/lambda/module/modules/client/LambdaMoji.kt new file mode 100644 index 000000000..36b90b28c --- /dev/null +++ b/common/src/main/kotlin/com/lambda/module/modules/client/LambdaMoji.kt @@ -0,0 +1,11 @@ +package com.lambda.module.modules.client + +import com.lambda.module.Module +import com.lambda.module.tag.ModuleTag + +object LambdaMoji : Module( + name = "LambdaMoji", + description = "", + defaultTags = setOf(ModuleTag.CLIENT, ModuleTag.RENDER), + enabledByDefault = true, +) diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/RenderSettings.kt b/common/src/main/kotlin/com/lambda/module/modules/client/RenderSettings.kt new file mode 100644 index 000000000..11a15a235 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/module/modules/client/RenderSettings.kt @@ -0,0 +1,40 @@ +package com.lambda.module.modules.client + +import com.lambda.module.Module +import com.lambda.module.tag.ModuleTag + +object RenderSettings : Module( + name = "RenderSettings", + description = "Renderer configuration", + defaultTags = setOf(ModuleTag.CLIENT) +) { + private val page by setting("Page", Page.Font) + + // Font + val shadow by setting("Shadow", true) { page == Page.Font } + val shadowBrightness by setting("Shadow Brightness", 0.35, 0.0..0.5, 0.01) { page == Page.Font && shadow } + val shadowShift by setting("Shadow Shift", 1.0, 0.0..2.0, 0.05) { page == Page.Font && shadow } + val gap by setting("Gap", 1.5, -10.0..10.0, 0.5) { page == Page.Font } + val baselineOffset by setting("Vertical Offset", 0.0, -10.0..10.0, 0.5) { page == Page.Font } + private val lodBiasSetting by setting("Smoothing", 0.0, -10.0..10.0, 0.5) { page == Page.Font } + + // ESP + val uploadScheduler by setting("Upload Scheduler", UploadScheduler.Instant) { page == Page.ESP } + val uploadsPerTick by setting("Uploads", 16, 1..256, 1, unit = " chunk/tick") { page == Page.ESP && uploadScheduler == UploadScheduler.Delayed } + val rebuildsPerTick by setting("Rebuilds", 64, 1..256, 1, unit = " chunk/tick") { page == Page.ESP } + val vertexMapping by setting("Vertex Mapping", true) { page == Page.ESP } + val updateFrequency by setting("Update Frequency", 2, 1..10, 1, "Frequency of block updates", unit = " ticks") { page == Page.ESP } + val outlineWidth by setting("Outline Width", 1.0, 0.1..5.0, 0.1, "Width of block outlines", unit = "px") { page == Page.ESP } + + val lodBias get() = lodBiasSetting * 0.25f - 0.75f + + private enum class Page { + Font, + ESP + } + + enum class UploadScheduler { + Instant, + Delayed + } +} diff --git a/common/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt b/common/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt index c6c73c6eb..4344def28 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt @@ -1,10 +1,21 @@ package com.lambda.module.modules.debug +import com.lambda.event.events.RenderEvent +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listener +import com.lambda.graphics.renderer.esp.EspRenderer import com.lambda.module.Module import com.lambda.module.tag.ModuleTag +import com.lambda.threading.mainThread +import com.lambda.util.math.ColorUtils.setAlpha +import com.lambda.util.world.WorldUtils.getClosestEntity +import com.mojang.blaze3d.systems.RenderSystem.recordRenderCall +import net.minecraft.entity.LivingEntity +import net.minecraft.util.math.Box +import java.awt.Color object RenderTest : Module( - name = "RenderTest", + name = "Render:shrimp:Test:canned_food:", description = "RenderTest", defaultTags = setOf(ModuleTag.DEBUG) ) { @@ -15,31 +26,29 @@ object RenderTest : Module( private val test31 by setting("Holla huh 1", true, visibility = { !test1 }) private val test32 by setting("Holla buh 2", true, visibility = { !test1 }) -// private val filled = DynamicFilledRenderer() -// private val outline = DynamicOutlineRenderer() -// private val color = Color(60, 200, 60) -// -// init { -// filled.build { -// val flag = mc.crosshairTarget?.blockResult?.blockPos?.let { box = Box(it) } != null -// color = color.setAlpha((color.a + flag.toIntSign() * 0.05).coerceAtMost(0.2)) -// } -// -// outline.build { -// val block = mc.crosshairTarget?.blockResult -// val flag = block?.blockPos?.let { box = Box(it) } != null -// color = color.setAlpha(color.a + flag.toIntSign() * 0.2) -// sides = block?.side?.mask ?: sides -// } -// -// listener { -// filled.update() -// outline.update() -// } -// -// listener { -// filled.render() -// outline.render() -// } -// } + private val outlineColor = Color(100, 150, 255).setAlpha(0.5) + private val filledColor = outlineColor.setAlpha(0.2) + + private val rendeer by mainThread { + EspRenderer() + } + + init { + listener { + val entity = getClosestEntity(player.pos, 8.0) ?: return@listener + it.build(entity, filledColor, outlineColor) + } + + listener { + rendeer.build(Box.of(player.pos, 0.3, 0.3, 0.3), filledColor, outlineColor) + } + + listener { + rendeer.render() + } + + onEnable { + rendeer.clear() + } + } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/module/modules/debug/UpdateTest.kt b/common/src/main/kotlin/com/lambda/module/modules/debug/UpdateTest.kt deleted file mode 100644 index f1af171ab..000000000 --- a/common/src/main/kotlin/com/lambda/module/modules/debug/UpdateTest.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.lambda.module.modules.debug - -import com.lambda.event.events.WorldEvent -import com.lambda.event.listener.SafeListener.Companion.listener -import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag -import com.lambda.util.Communication.info - -object UpdateTest : Module( - name = "UpdateTest", - description = "A module for testing updates", - defaultTags = setOf(ModuleTag.DEBUG) -) { - init { - listener { - info("Block update at ${it.pos} with state ${it.state} and flags ${it.flags}") - } - - listener { - info("Chunk load at ${it.chunk.pos}") - } - - listener { - info("Chunk unload at ${it.chunk.pos}") - } - } -} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/module/modules/movement/Timer.kt b/common/src/main/kotlin/com/lambda/module/modules/movement/Timer.kt index 5aa0670fc..4168bc82f 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/movement/Timer.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/movement/Timer.kt @@ -10,11 +10,11 @@ object Timer : Module( description = "Modify client tick speed.", defaultTags = setOf(ModuleTag.MOVEMENT, ModuleTag.WORLD) ) { - private val timer by setting("Timer", 50, 0..1000, 5, unit = "ms/tick") + private val timer by setting("Timer", 1.0, 0.0..10.0, 0.01) init { listener { - it.speed = timer / 50.0 + it.speed = timer.coerceAtLeast(0.05) } } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt b/common/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt index cb55bf893..6627129ff 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt @@ -1,15 +1,63 @@ package com.lambda.module.modules.render import com.lambda.Lambda.mc +import com.lambda.graphics.renderer.esp.ChunkedESP.Companion.newChunkedESP +import com.lambda.graphics.renderer.esp.DirectionMask +import com.lambda.graphics.renderer.esp.DirectionMask.exclude +import com.lambda.graphics.renderer.esp.DirectionMask.mask +import com.lambda.graphics.renderer.esp.EspRenderer import com.lambda.module.Module import com.lambda.module.tag.ModuleTag +import com.lambda.util.BlockUtils.blockState +import net.minecraft.block.Block +import net.minecraft.block.Blocks import net.minecraft.client.render.model.BakedModel +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Box +import net.minecraft.util.math.Direction +import net.minecraft.world.WorldView +import java.awt.Color object BlockESP : Module( name = "BlockESP", description = "Render block ESP", defaultTags = setOf(ModuleTag.RENDER) ) { + private var drawFaces: Boolean by setting("Draw Faces", true, "Draw faces of blocks").apply { + onValueSet { _, to -> + esp.rebuild() + if (!to) drawOutlines = true + } + } + private val faceColor: Color by setting("Face Color", Color(100, 150, 255, 51), "Color of the surfaces") { + drawFaces + }.apply { + onValueSet { _, _ -> esp.rebuild() } + } + private var drawOutlines: Boolean by setting("Draw Outlines", true, "Draw outlines of blocks").apply { + onValueSet { _, to -> + esp.rebuild() + if (!to) drawFaces = true + } + } + private val outlineColor: Color by setting("Outline Color", Color(100, 150, 255, 128), "Color of the outlines") { + drawOutlines + }.apply { + onValueSet { _, _ -> esp.rebuild() } + } + private val outlineMode: DirectionMask.OutlineMode by setting("Outline Mode", DirectionMask.OutlineMode.AND, "Outline mode").apply { + onValueSet { _, _ -> esp.rebuild() } + } + private val mesh: Boolean by setting("Mesh", true, "Connect similar adjacent blocks").apply { + onValueSet { _, _ -> esp.rebuild() } + } + private val shaped: Boolean by setting("Shaped", false, "Render outline shape").apply { + onValueSet { _, _ -> esp.rebuild() } + } + private val blocks: Set by setting("Blocks", setOf(Blocks.BEDROCK), "Render blocks").apply { + onValueSet { _, _ -> esp.rebuild() } + } + @JvmStatic val barrier by setting("Solid Barrier Block", true, "Render barrier blocks") @@ -22,7 +70,46 @@ object BlockESP : Module( init { onToggle { - mc.worldRenderer.reload() + if (barrier) mc.worldRenderer.reload() + } + } + + private val esp = newChunkedESP { view, x, y, z -> + val blockPos = BlockPos(x, y, z) + val state = view.getBlockState(blockPos) + if (state.block !in blocks) return@newChunkedESP + + if (shaped) { + val shape = state.getOutlineShape(view, blockPos) + if (shape.isEmpty) return@newChunkedESP + val boxes = shape.boundingBoxes + .map { it.offset(blockPos) } + .toSet() + + buildMesh(boxes, outlineColor) + return@newChunkedESP + } + + var sides = DirectionMask.ALL + + if (mesh) { + Direction.entries + .filter { blockPos.offset(it).blockState(view).block in blocks } + .forEach { sides = sides.exclude(it.mask) } + } + + build(Box(blockPos), sides) + } + + private fun EspRenderer.build( + box: Box, + sides: Int, + ) { + if (drawFaces) { + buildFilled(box, faceColor, sides) + } + if (drawOutlines) { + buildOutline(box, outlineColor, sides, outlineMode) } } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/module/modules/render/XRay.kt b/common/src/main/kotlin/com/lambda/module/modules/render/XRay.kt index 975d7aee7..c1597ead0 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/render/XRay.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/render/XRay.kt @@ -23,15 +23,15 @@ object XRay : Module( Blocks.ANCIENT_DEBRIS ) - // private val selection by setting("Block Selection", defaultBlocks, "Block selection that will be shown (whitelist) or hidden (blacklist)") + private val selection by setting("Block Selection", defaultBlocks, "Block selection that will be shown (whitelist) or hidden (blacklist)") private val mode by setting("Selection Mode", Selection.WHITELIST, "The mode of the block selection") @JvmStatic fun isSelected(blockState: BlockState) = mode.select(blockState) enum class Selection(val select: (BlockState) -> Boolean) { - WHITELIST({ it.block in defaultBlocks }), - BLACKLIST({ it.block !in defaultBlocks }) + WHITELIST({ it.block in selection }), + BLACKLIST({ it.block !in selection }) } init { diff --git a/common/src/main/kotlin/com/lambda/module/tag/ModuleTag.kt b/common/src/main/kotlin/com/lambda/module/tag/ModuleTag.kt index 502330c02..c60a01a20 100644 --- a/common/src/main/kotlin/com/lambda/module/tag/ModuleTag.kt +++ b/common/src/main/kotlin/com/lambda/module/tag/ModuleTag.kt @@ -24,12 +24,14 @@ data class ModuleTag(override val name: String) : Nameable { val CLIENT = ModuleTag("Client") val NETWORK = ModuleTag("Network") val DEBUG = ModuleTag("Debug") + val defaults = setOf(COMBAT, MOVEMENT, RENDER, PLAYER, NETWORK, DEBUG, CLIENT) + + val HUD = ModuleTag("Hud") // omg + val hudDefaults = setOf(HUD) // currently secondary tags val WORLD = ModuleTag("World") val AUTOMATION = ModuleTag("Automation") val GRIM = ModuleTag("Grim") - - val defaults = listOf(COMBAT, MOVEMENT, RENDER, PLAYER, NETWORK, DEBUG, CLIENT) } } diff --git a/common/src/main/kotlin/com/lambda/util/BlockUtils.kt b/common/src/main/kotlin/com/lambda/util/BlockUtils.kt index 581e60c7a..f81bfdfa0 100644 --- a/common/src/main/kotlin/com/lambda/util/BlockUtils.kt +++ b/common/src/main/kotlin/com/lambda/util/BlockUtils.kt @@ -11,6 +11,7 @@ import net.minecraft.fluid.FluidState import net.minecraft.fluid.Fluids import net.minecraft.item.Item import net.minecraft.util.math.* +import net.minecraft.world.BlockView import kotlin.math.floor object BlockUtils { @@ -97,9 +98,9 @@ object BlockUtils { val allSigns = signs + wallSigns + hangingSigns + hangingWallSigns - fun BlockPos.blockState(world: ClientWorld): BlockState = world.getBlockState(this) - fun BlockPos.fluidState(world: ClientWorld): FluidState = world.getFluidState(this) - fun BlockPos.blockEntity(world: ClientWorld) = world.getBlockEntity(this) + fun BlockPos.blockState(world: BlockView): BlockState = world.getBlockState(this) + fun BlockPos.fluidState(world: BlockView): FluidState = world.getFluidState(this) + fun BlockPos.blockEntity(world: BlockView) = world.getBlockEntity(this) fun SafeContext.instantBreakable(blockState: BlockState, blockPos: BlockPos): Boolean { val ticksNeeded = 1 / blockState.calcBlockBreakingDelta(player, world, blockPos) return (ticksNeeded <= 1 && ticksNeeded != 0f) || player.isCreative diff --git a/common/src/main/kotlin/com/lambda/util/math/Vec2d.kt b/common/src/main/kotlin/com/lambda/util/math/Vec2d.kt index 31f85bf9b..966d59c95 100644 --- a/common/src/main/kotlin/com/lambda/util/math/Vec2d.kt +++ b/common/src/main/kotlin/com/lambda/util/math/Vec2d.kt @@ -4,10 +4,12 @@ data class Vec2d(val x: Double, val y: Double) { constructor(x: Int, y: Int) : this(x.toDouble(), y.toDouble()) constructor(x: Float, y: Float) : this(x.toDouble(), y.toDouble()) + operator fun unaryPlus() = this operator fun plus(vec2d: Vec2d) = plus(vec2d.x, vec2d.y) operator fun plus(add: Double) = plus(add, add) fun plus(x: Double, y: Double) = Vec2d(this.x + x, this.y + y) + operator fun unaryMinus() = Vec2d(-x, -y) operator fun minus(vec2d: Vec2d) = minus(vec2d.x, vec2d.y) operator fun minus(sub: Double) = minus(sub, sub) fun minus(x: Double, y: Double) = plus(-x, -y) @@ -29,4 +31,4 @@ data class Vec2d(val x: Double, val y: Double) { val TOP = Vec2d(0.0, 1.0) val BOTTOM = Vec2d(0.0, -1.0) } -} \ No newline at end of file +} diff --git a/common/src/main/resources/assets/lambda/shaders/fragment/renderer/font.frag b/common/src/main/resources/assets/lambda/shaders/fragment/renderer/font.frag index 3522a98c5..25cd3d0f7 100644 --- a/common/src/main/resources/assets/lambda/shaders/fragment/renderer/font.frag +++ b/common/src/main/resources/assets/lambda/shaders/fragment/renderer/font.frag @@ -1,6 +1,7 @@ #version 330 core -uniform sampler2D u_Texture; +uniform sampler2D u_FontTexture; +uniform sampler2D u_EmojiTexture; in vec2 v_TexCoord; in vec4 v_Color; @@ -8,6 +9,13 @@ in vec4 v_Color; out vec4 color; void main() { - float alpha = texture(u_Texture, v_TexCoord).a; - color = vec4(v_Color.rgb, v_Color.a * alpha); -} \ No newline at end of file + vec4 tex; + + if (v_TexCoord.x > 0.0) { + tex = texture(u_FontTexture, v_TexCoord); + } else { + tex = texture(u_EmojiTexture, -v_TexCoord); + } + + color = tex * v_Color; +} diff --git a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/box_dynamic.vert b/common/src/main/resources/assets/lambda/shaders/vertex/renderer/box_dynamic.vert index 7061f3df9..d33b24566 100644 --- a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/box_dynamic.vert +++ b/common/src/main/resources/assets/lambda/shaders/vertex/renderer/box_dynamic.vert @@ -4,8 +4,7 @@ layout (location = 0) in vec3 pos1; layout (location = 1) in vec3 pos2; layout (location = 2) in vec4 color; -uniform mat4 u_Projection; -uniform mat4 u_ModelView; +uniform mat4 u_ProjModel; uniform float u_TickDelta; uniform vec3 u_CameraPosition; @@ -15,6 +14,6 @@ out vec4 v_Color; #define VERTEX_POSITION mix(pos1, pos2, u_TickDelta) - u_CameraPosition void main() { - gl_Position = u_Projection * u_ModelView * vec4(VERTEX_POSITION, 1.0); + gl_Position = u_ProjModel * vec4(VERTEX_POSITION, 1.0); v_Color = color; } diff --git a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/box_static.vert b/common/src/main/resources/assets/lambda/shaders/vertex/renderer/box_static.vert index 8ab8e8660..197c14ca5 100644 --- a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/box_static.vert +++ b/common/src/main/resources/assets/lambda/shaders/vertex/renderer/box_static.vert @@ -3,9 +3,7 @@ layout (location = 0) in vec3 pos; layout (location = 1) in vec4 color; -uniform mat4 u_Projection; -uniform mat4 u_ModelView; - +uniform mat4 u_ProjModel; uniform vec3 u_CameraPosition; out vec4 v_Color; @@ -13,6 +11,6 @@ out vec4 v_Color; #define VERTEX_POSITION pos - u_CameraPosition void main() { - gl_Position = u_Projection * u_ModelView * vec4(VERTEX_POSITION, 1.0); + gl_Position = u_ProjModel * vec4(VERTEX_POSITION, 1.0); v_Color = color; } diff --git a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/font.vert b/common/src/main/resources/assets/lambda/shaders/vertex/renderer/font.vert index 96ad268b9..14d1bb437 100644 --- a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/font.vert +++ b/common/src/main/resources/assets/lambda/shaders/vertex/renderer/font.vert @@ -4,14 +4,13 @@ layout (location = 0) in vec4 pos; layout (location = 1) in vec2 uv; layout (location = 2) in vec4 color; -uniform mat4 u_Projection; -uniform mat4 u_ModelView; +uniform mat4 u_ProjModel; out vec2 v_TexCoord; out vec4 v_Color; void main() { - gl_Position = u_Projection * u_ModelView * pos; + gl_Position = u_ProjModel * pos; v_TexCoord = uv; v_Color = color; diff --git a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/rect_filled.vert b/common/src/main/resources/assets/lambda/shaders/vertex/renderer/rect_filled.vert index 0e3891429..e52b0bda8 100644 --- a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/rect_filled.vert +++ b/common/src/main/resources/assets/lambda/shaders/vertex/renderer/rect_filled.vert @@ -7,8 +7,7 @@ layout (location = 3) in float round; layout (location = 4) in float shade; layout (location = 5) in vec4 color; -uniform mat4 u_Projection; -uniform mat4 u_ModelView; +uniform mat4 u_ProjModel; out vec2 v_Position; out vec2 v_TexCoord; @@ -18,7 +17,7 @@ out float v_RoundRadius; out float v_Shade; void main() { - gl_Position = u_Projection * u_ModelView * pos; + gl_Position = u_ProjModel * pos; v_Position = gl_Position.xy * 0.5 + 0.5; v_TexCoord = uv; diff --git a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/rect_outline.vert b/common/src/main/resources/assets/lambda/shaders/vertex/renderer/rect_outline.vert index 192864a29..03650690f 100644 --- a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/rect_outline.vert +++ b/common/src/main/resources/assets/lambda/shaders/vertex/renderer/rect_outline.vert @@ -5,8 +5,7 @@ layout (location = 1) in float alpha; layout (location = 2) in float shade; layout (location = 3) in vec4 color; -uniform mat4 u_Projection; -uniform mat4 u_ModelView; +uniform mat4 u_ProjModel; out vec2 v_Position; out float v_Alpha; @@ -14,7 +13,7 @@ out vec4 v_Color; out float v_Shade; void main() { - gl_Position = u_Projection * u_ModelView * pos; + gl_Position = u_ProjModel * pos; v_Position = gl_Position.xy * 0.5 + 0.5; v_Alpha = alpha; diff --git a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/tracer_dynamic.vert b/common/src/main/resources/assets/lambda/shaders/vertex/renderer/tracer_dynamic.vert index 2ec823a2e..57c3accbd 100644 --- a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/tracer_dynamic.vert +++ b/common/src/main/resources/assets/lambda/shaders/vertex/renderer/tracer_dynamic.vert @@ -4,8 +4,7 @@ layout (location = 0) in vec3 pos1; layout (location = 1) in vec3 pos2; layout (location = 2) in vec4 color; -uniform mat4 u_Projection; -uniform mat4 u_ModelView; +uniform mat4 u_ProjModel; uniform float u_TickDelta; uniform vec3 u_CameraPosition; @@ -15,7 +14,7 @@ out vec4 v_Color; #define VERTEX_POSITION mix(pos1, pos2, u_TickDelta) - u_CameraPosition #define SCREEN_CENTER vec4(0.0, 0.0, 0.0, 1.0) -#define PROJECTED u_Projection * u_ModelView * vec4(VERTEX_POSITION, 1.0) +#define PROJECTED u_ProjModel * vec4(VERTEX_POSITION, 1.0) void main() { gl_Position = gl_VertexID % 2 == 0 ? SCREEN_CENTER : PROJECTED; diff --git a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/tracer_static.vert b/common/src/main/resources/assets/lambda/shaders/vertex/renderer/tracer_static.vert index 838117d38..68b534c64 100644 --- a/common/src/main/resources/assets/lambda/shaders/vertex/renderer/tracer_static.vert +++ b/common/src/main/resources/assets/lambda/shaders/vertex/renderer/tracer_static.vert @@ -3,8 +3,7 @@ layout (location = 0) in vec3 pos; layout (location = 1) in vec4 color; -uniform mat4 u_Projection; -uniform mat4 u_ModelView; +uniform mat4 u_ProjModel; uniform vec3 u_CameraPosition; @@ -13,7 +12,7 @@ out vec4 v_Color; #define VERTEX_POSITION pos - u_CameraPosition #define SCREEN_CENTER vec4(0.0, 0.0, 0.0, 1.0) -#define PROJECTED u_Projection * u_ModelView * vec4(VERTEX_POSITION, 1.0) +#define PROJECTED u_ProjModel * vec4(VERTEX_POSITION, 1.0) void main() { gl_Position = gl_VertexID % 2 == 0 ? SCREEN_CENTER : PROJECTED; diff --git a/common/src/main/resources/lambda.accesswidener b/common/src/main/resources/lambda.accesswidener index cd9d8a090..69ab51fc2 100644 --- a/common/src/main/resources/lambda.accesswidener +++ b/common/src/main/resources/lambda.accesswidener @@ -10,6 +10,8 @@ accessible field net/minecraft/client/MinecraftClient thread Ljava/lang/Thread; accessible field net/minecraft/client/world/ClientWorld entityManager Lnet/minecraft/client/world/ClientEntityManager; accessible field net/minecraft/client/world/ClientEntityManager cache Lnet/minecraft/world/entity/SectionedEntityCache; accessible field net/minecraft/world/entity/EntityTrackingSection collection Lnet/minecraft/util/collection/TypeFilterableList; +accessible field net/minecraft/client/world/ClientChunkManager chunks Lnet/minecraft/client/world/ClientChunkManager$ClientChunkMap; +accessible field net/minecraft/client/world/ClientChunkManager$ClientChunkMap chunks Ljava/util/concurrent/atomic/AtomicReferenceArray; # Entity accessible field net/minecraft/entity/projectile/FireworkRocketEntity shooter Lnet/minecraft/entity/LivingEntity;