diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 0930a882c..000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: Build Lambda - -on: - push: - branches: - - '1.**' - pull_request: - -jobs: - build: - concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - - name: Build Lambda - runs-on: ubuntu-latest - - permissions: - contents: write - - env: - SEGMENT_DOWNLOAD_TIMEOUT_MINS: '5' - - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - - - name: Get short commit hash - id: vars - run: echo "COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV - - - name: Set-Up JDK - uses: actions/setup-java@v4 - with: - distribution: 'oracle' - java-version: '21' - architecture: x64 - cache: 'gradle' - - - name: Read Gradle Properties - uses: BrycensRanch/read-properties-action@v1.0.4 - id: all - with: - file: gradle.properties - all: true - - - name: Publish snapshot to maven -# if: github.event_name == 'push' - id: upload - run: ./gradlew publish -PmavenType=snapshots -PmavenUsername=${{ secrets.MAVEN_USER }} -PmavenPassword=${{ secrets.MAVEN_TOKEN }} - -# - name: Generate Summary -# if: steps.upload.conclusion == 'success' && github.event_name == 'push' -# run: | -# cat << EOF >> $GITHUB_STEP_SUMMARY -# ### [Lambda ${{ steps.all.outputs.modVersion }} ${{ steps.all.outputs.minecraftVersion }} (${{ env.COMMIT_HASH }})](https://maven.lambda-client.org/snapshots/com/lambda/lambda/${{ steps.all.outputs.modVersion }}+${{ steps.all.outputs.minecraftVersion }}-${{ env.COMMIT_HASH }}/lambda-${{ steps.all.outputs.modVersion }}+${{ steps.all.outputs.minecraftVersion }}-${{ env.COMMIT_HASH }}.jar) -# EOF - - - name: Rename jar -# if: steps.upload.conclusion == 'failure' || github.event_name == 'pull_request' - run: mv build/libs/lambda-${{ steps.all.outputs.modVersion }}+${{ steps.all.outputs.minecraftVersion }}.jar build/libs/lambda-${{ steps.all.outputs.modVersion }}+${{ steps.all.outputs.minecraftVersion }}-${{ env.COMMIT_HASH }}.jar - - - name: Upload -# if: steps.upload.conclusion == 'failure' || github.event_name == 'pull_request' - uses: actions/upload-artifact@v4 - with: - name: lambda-nightly - path: | - build/libs/lambda-${{ steps.all.outputs.modVersion }}+${{ steps.all.outputs.minecraftVersion }}-${{ env.COMMIT_HASH }}.jar diff --git a/.github/workflows/nightly_build.yml b/.github/workflows/nightly_build.yml new file mode 100644 index 000000000..a53ca5714 --- /dev/null +++ b/.github/workflows/nightly_build.yml @@ -0,0 +1,89 @@ +name: Nightly Build + +on: + push: + branches: + - '*.*' + +jobs: + build: + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + + name: Build Lambda + runs-on: ubuntu-latest + + permissions: + contents: write + + env: + SEGMENT_DOWNLOAD_TIMEOUT_MINS: '5' + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Get short commit hash + id: vars + run: echo "COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV + + - name: Set-Up JDK + uses: actions/setup-java@v4 + with: + distribution: 'oracle' + java-version: '21' + architecture: x64 + cache: 'gradle' + + - name: Read Gradle Properties + uses: BrycensRanch/read-properties-action@v1.0.4 + id: all + with: + file: gradle.properties + all: true + + - name: Publish snapshot to maven + # if: github.event_name == 'push' + id: upload-maven + run: ./gradlew publish -PmavenType=snapshots -PmavenUsername=${{ secrets.MAVEN_USER }} -PmavenPassword=${{ secrets.MAVEN_TOKEN }} + + # - name: Generate Summary + # if: steps.upload.conclusion == 'success' && github.event_name == 'push' + # run: | + # cat << EOF >> $GITHUB_STEP_SUMMARY + # ### [Lambda ${{ steps.all.outputs.modVersion }} ${{ steps.all.outputs.minecraftVersion }} (${{ env.COMMIT_HASH }})](https://maven.lambda-client.org/snapshots/com/lambda/lambda/${{ steps.all.outputs.modVersion }}+${{ steps.all.outputs.minecraftVersion }}-${{ env.COMMIT_HASH }}/lambda-${{ steps.all.outputs.modVersion }}+${{ steps.all.outputs.minecraftVersion }}-${{ env.COMMIT_HASH }}.jar) + # EOF + + - name: Rename jar + # if: steps.upload.conclusion == 'failure' || github.event_name == 'pull_request' + run: mv build/libs/lambda-${{ steps.all.outputs.modVersion }}+${{ steps.all.outputs.minecraftVersion }}.jar build/libs/lambda-${{ steps.all.outputs.modVersion }}+${{ steps.all.outputs.minecraftVersion }}-${{ env.COMMIT_HASH }}.jar + + - name: Upload + # if: steps.upload.conclusion == 'failure' || github.event_name == 'pull_request' + uses: actions/upload-artifact@v4 + id: upload-git + with: + name: lambda-nightly + path: | + build/libs/lambda-${{ steps.all.outputs.modVersion }}+${{ steps.all.outputs.minecraftVersion }}-${{ env.COMMIT_HASH }}.jar + + - name: Get latest version + if: steps.upload-maven.conclusion == 'success' || steps.upload-git.conclusion == 'success' + run: | + COMMIT_MESSAGE=$(git log --pretty=format:'- \`%h\` %s' -1 --reverse) + LATEST_VERSION=$(curl -s "https://maven.lambda-client.org/snapshots/com/lambda/lambda/maven-metadata.xml" | grep -oP '(?<=).*?(?=)') + LATEST_MAVEN_METADATA=$(curl -s "https://maven.lambda-client.org/snapshots/com/lambda/lambda/${LATEST_VERSION}/maven-metadata.xml") + TIMESTAMP=$(echo "$LATEST_MAVEN_METADATA" | grep -oP '(?<=).*?(?=)') + BUILD_NUMBER=$(echo "$LATEST_MAVEN_METADATA" | grep -oP '(?<=).*?(?=)') + LATEST_SNAPSHOT="lambda-${{ steps.all.outputs.modVersion }}+${{ steps.all.outputs.minecraftVersion }}-${TIMESTAMP}-${BUILD_NUMBER}" + + echo "COMMIT_MESSAGE=${COMMIT_MESSAGE}" >> $GITHUB_ENV + echo "LATEST_VERSION=${LATEST_VERSION}" >> $GITHUB_ENV + echo "LATEST_SNAPSHOT=lambda-${{ steps.all.outputs.modVersion }}+${{ steps.all.outputs.minecraftVersion }}-${TIMESTAMP}-${BUILD_NUMBER}" >> $GITHUB_ENV + + - name: Send Discord build message + if: steps.upload-maven.conclusion == 'success' || steps.upload-git.conclusion == 'success' + run: | + curl "${{ secrets.WEBHOOK }}" -sS -H "Content-Type:application/json" -X POST -d "{\"content\":null,\"embeds\":[{\"title\":\"Build ${{ github.run_number }}\",\"description\":\"**Branch:** ${{ github.ref_name }}\\n**Changes:**\\n${{ env.COMMIT_MESSAGE }}\",\"url\":\"${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}\",\"color\":1487872,\"fields\":[{\"name\":\"Artifacts:\",\"value\":\"- [${{ env.LATEST_SNAPSHOT }}.jar](https://maven.lambda-client.org/snapshots/com/lambda/lambda/${{ env.LATEST_VERSION }}/${{ env.LATEST_SNAPSHOT }}.jar)\"}],\"footer\":{\"text\":\"$GITHUB_REPOSITORY\"},\"thumbnail\":{\"url\":\"https://raw.githubusercontent.com/lambda-client/assets/refs/heads/main/lambda%20logo%20banner%20transparent.png\"}}],\"username\":\"Github Actions\",\"avatar_url\":\"https://raw.githubusercontent.com/lambda-client/assets/refs/heads/main/lambda.png\"}" + diff --git a/README.md b/README.md index 797271906..518098c0e 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,12 @@ logo

-![Minecraft](https://img.shields.io/badge/minecraft-1.21.5-green?link=https%3A%2F%2Fwww.minecraft.net%2F) +![Minecraft](https://img.shields.io/badge/minecraft-1.21.11-green?link=https%3A%2F%2Fwww.minecraft.net%2F) +![Minecraft](https://img.shields.io/badge/minecraft-1.21.5-red?link=https%3A%2F%2Fwww.minecraft.net%2F) ![GitHub Downloads](https://img.shields.io/github/downloads/lambda-client/lambda/total) ![Discord](https://img.shields.io/discord/834570721070022687?logo=Discord&logoColor=white&link=https%3A%2F%2Fdiscord.gg%2FMBAEzyFn) ![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/lambda-client/lambda?color=royalblue) -![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/lambda-client/lambda/build.yml?branch=1.21.5&logo=gradle) +![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/lambda-client/lambda/nightly_build.yml?logo=gradle) ![GitHub Contributors](https://img.shields.io/github/contributors/lambda-client/lambda) ![GitHub Repo Stars](https://img.shields.io/github/stars/lambda-client/lambda) ![GitHub License](https://img.shields.io/github/license/lambda-client/lambda?logo=gplv3&link=https%3A%2F%2Fwww.gnu.org%2Flicenses%2Fgpl-3.0.en.html) @@ -32,7 +33,7 @@ Find our backup matrix space at [\#lambda-client:matrix.org](https://app.element ### Automation Engine * **Build Engine:** Full integration with **Litematica** and schematic files for seamless automated building. -* **Block State Handling:** The build engine natively supports special block states including rotations, attachments (doors, signs, bells), half-slabs, stairs, repeater delay, and even edge cases like flower pots with plants. +* **Block State Handling:** The build engine natively supports special block states including rotations, attachments (doors, signs, bells), slabs, stairs, repeater delay, and even edge cases like flower pots with plants. * **Conflict-Free Orchestration:** A centralized manager system handles all core interactions (placing, breaking, rotating, inventory) to ensure zero conflicts between concurrently running modules. ### Unmatched Performance @@ -55,10 +56,13 @@ Find our backup matrix space at [\#lambda-client:matrix.org](https://app.element ## Installation Fabric Supported -1. Install Minecraft 1.21.5 [(download)](https://www.minecraft.net/) +1. Install the Minecraft version corresponding to the mod release(download)](https://www.minecraft.net/) 2. Install Fabric [(download)](https://fabricmc.net/use/installer/) 3. Get the latest Lambda version here [(download)](https://github.com/lambda-client/lambda/releases/download/0.0.2%2B1.21.5/lambda-0.0.2+1.21.5.jar) -4. Put the file in your `.minecraft/mods` folder +4. Get the corresponding [Baritone](https://github.com/cabaletta/baritone/releases) api fabric build +5. Get [Kotlin For Fabric](https://modrinth.com/mod/fabric-language-kotlin) +6. Get the latest [Fabric API](https://modrinth.com/mod/fabric-api/) release +7. Put the files in your `.minecraft/mods` folder ## Getting Started @@ -99,4 +103,4 @@ you can visit our [official Discord server](https://discord.gg/MBAEzyFn). > ### Disclaimer > Lambda is not affiliated with Mojang Studios. Minecraft is a registered trademark of Mojang Studios. -Use of the Lambda software is subject to the terms outlined in the license agreement [GNU General Public License v3.0](https://github.com/lambda-client/lambda/blob/master/LICENSE.md). \ No newline at end of file +Use of the Lambda software is subject to the terms outlined in the license agreement [GNU General Public License v3.0](https://github.com/lambda-client/lambda/blob/master/LICENSE.md). diff --git a/build.gradle.kts b/build.gradle.kts index f2fd2347f..571c52b14 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -30,8 +30,6 @@ val discordIPCVersion: String by project val classGraphVersion: String by project val kotlinVersion: String by project val ktorVersion: String by project -val mockitoKotlin: String by project -val mockitoInline: String by project val mockkVersion: String by project val spairVersion: String by project val lwjglVersion: String by project @@ -46,10 +44,10 @@ val replacements = file("gradle.properties").inputStream().use { stream -> }.map { (k, v) -> k.toString() to v.toString() }.toMap() plugins { - kotlin("jvm") version "2.2.0" - id("org.jetbrains.dokka") version "2.0.0" - id("fabric-loom") version "1.10-SNAPSHOT" - id("com.gradleup.shadow") version "9.0.0-rc1" + kotlin("jvm") version "2.3.0" + id("org.jetbrains.dokka") version "2.1.0" + id("fabric-loom") version "1.14-SNAPSHOT" + id("com.gradleup.shadow") version "9.3.0" id("maven-publish") } @@ -151,6 +149,8 @@ dependencies { // Fabric modImplementation("net.fabricmc:fabric-loader:$fabricLoaderVersion") modImplementation("net.fabricmc.fabric-api:fabric-api:$fabricApiVersion+$minecraftVersion") + // Explicit dependency on fabric-renderer-indigo for mixin access to internal classes + modCompileOnly("net.fabricmc.fabric-api:fabric-renderer-indigo:4.1.4+1af5c5a75f") modImplementation("net.fabricmc:fabric-language-kotlin:$kotlinFabricVersion.$kotlinVersion") // Add dependencies on the required Kotlin modules. @@ -175,7 +175,7 @@ dependencies { includeLib("io.ktor:ktor-serialization-gson:$ktorVersion") // Add mods - modImplementation("com.github.rfresh2:baritone-fabric:$minecraftVersion") + modImplementation("com.github.rfresh2:baritone-fabric:$minecraftVersion-SNAPSHOT") modCompileOnly("maven.modrinth:sodium:$sodiumVersion") modCompileOnly("maven.modrinth:malilib:$maLiLibVersion") modCompileOnly("maven.modrinth:litematica:$litematicaVersion") @@ -185,8 +185,6 @@ dependencies { // Test implementations testImplementation(kotlin("test")) - testImplementation("org.mockito.kotlin:mockito-kotlin:$mockitoKotlin") - testImplementation("org.mockito:mockito-inline:$mockitoInline") testImplementation("io.mockk:mockk:${mockkVersion}") // Finish the configuration diff --git a/gradle.properties b/gradle.properties index 1cc5bebd7..693129c7a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -25,31 +25,27 @@ mavenGroup=com.lambda modAuthors=Constructor (Avanatiker), Blade, beanbag44, Emy # General -minecraftVersion=1.21.5 -minecraftVersionMin=1.21.5 -minecraftVersionMax=1.21.6 +minecraftVersion=1.21.11 lwjglVersion=3.3.3 -mixinExtrasVersion=0.5.0-rc.2 -kotlinVersion=2.2.0 +mixinExtrasVersion=0.5.0 +kotlinVersion=2.3.0 pngEncoderVersion=0.16.0 javaVersion=21 baritoneVersion=1.14.0 discordIPCVersion=6f6b6cce17 -classGraphVersion=4.8.179 -ktorVersion=3.1.2 -mockitoKotlin=5.4.0 -mockitoInline=5.2.0 -mockkVersion=1.13.17 +classGraphVersion=4.8.184 +ktorVersion=3.3.3 +mockkVersion=1.14.7 spairVersion=1.90.0 # Fabric https://fabricmc.net/develop/ -fabricLoaderVersion=0.16.13 -yarnMappings=build.1 -fabricApiVersion=0.124.0 -kotlinFabricVersion=1.13.4+kotlin -sodiumVersion=mc1.21.5-0.6.13-fabric -litematicaVersion=0.22.2 -maLiLibVersion=0.24.2 +fabricLoaderVersion=0.18.3 +yarnMappings=build.3 +fabricApiVersion=0.139.5 +kotlinFabricVersion=1.13.8+kotlin +sodiumVersion=mc1.21.11-0.8.0-fabric +litematicaVersion=0.25.1 +maLiLibVersion=0.27.1 # Kotlin https://kotlinlang.org/ kotlin.code.style=official @@ -57,5 +53,3 @@ kotlin.code.style=official # Gradle https://gradle.org/ org.gradle.jvmargs=-Xmx4096M org.gradle.parallel=true - -org.jetbrains.dokka.experimental.gradle.pluginMode.nowarn=true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c42ddc0c7..3d7f8165b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -17,7 +17,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/src/main/java/com/lambda/mixin/MinecraftClientMixin.java b/src/main/java/com/lambda/mixin/MinecraftClientMixin.java index 83ec7b6ef..a0e5c5b47 100644 --- a/src/main/java/com/lambda/mixin/MinecraftClientMixin.java +++ b/src/main/java/com/lambda/mixin/MinecraftClientMixin.java @@ -29,6 +29,8 @@ import com.lambda.module.modules.player.InventoryMove; import com.lambda.module.modules.player.PacketMine; import com.lambda.util.WindowUtils; +import com.llamalad7.mixinextras.expression.Definition; +import com.llamalad7.mixinextras.expression.Expression; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; @@ -40,15 +42,14 @@ import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.client.network.ClientPlayerInteractionManager; import net.minecraft.client.option.KeyBinding; -import net.minecraft.client.render.WorldRenderer; import net.minecraft.client.sound.SoundManager; import net.minecraft.util.Hand; import net.minecraft.util.hit.HitResult; import net.minecraft.util.thread.ThreadExecutor; import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Opcodes; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -66,9 +67,6 @@ public class MinecraftClientMixin { @Shadow public int itemUseCooldown; - @Unique - private boolean lambda$inputHandledThisTick; - @Inject(method = "close", at = @At("HEAD")) void closeImGui(CallbackInfo ci) { DearImGui.INSTANCE.destroy(); @@ -83,8 +81,6 @@ void onLoopTick(boolean tick, Operation original) { @WrapMethod(method = "tick") void onTick(Operation original) { - this.lambda$inputHandledThisTick = false; - EventFlow.post(TickEvent.Pre.INSTANCE); original.call(); EventFlow.post(TickEvent.Post.INSTANCE); @@ -97,26 +93,22 @@ void onNetwork(ClientPlayerInteractionManager instance, Operation original EventFlow.post(TickEvent.Network.Post.INSTANCE); } - @WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MinecraftClient;handleInputEvents()V")) - void onInput(MinecraftClient instance, Operation original) { - EventFlow.post(TickEvent.Input.Pre.INSTANCE); - original.call(instance); - EventFlow.post(TickEvent.Input.Post.INSTANCE); - - this.lambda$inputHandledThisTick = true; - } - - @WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/WorldRenderer;tick()V")) - void onWorldRenderer(WorldRenderer instance, Operation original) { - if (!this.lambda$inputHandledThisTick) { + @Definition(id = "overlay", field = "Lnet/minecraft/client/MinecraftClient;overlay:Lnet/minecraft/client/gui/screen/Overlay;") + @Expression("this.overlay == null") + @ModifyExpressionValue(method = "tick", at = @At("MIXINEXTRAS:EXPRESSION")) + private boolean modifyCurrentScreenNullCheck(boolean original) { + if (!original || this.currentScreen != null) { EventFlow.post(TickEvent.Input.Pre.INSTANCE); EventFlow.post(TickEvent.Input.Post.INSTANCE); - this.lambda$inputHandledThisTick = true; } + return original; + } - EventFlow.post(TickEvent.WorldRender.Pre.INSTANCE); + @WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MinecraftClient;handleInputEvents()V")) + void onInput(MinecraftClient instance, Operation original) { + EventFlow.post(TickEvent.Input.Pre.INSTANCE); original.call(instance); - EventFlow.post(TickEvent.WorldRender.Post.INSTANCE); + EventFlow.post(TickEvent.Input.Post.INSTANCE); } @WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/sound/SoundManager;tick(Z)V")) @@ -134,7 +126,7 @@ private void onShutdown(CallbackInfo ci) { /** * Inject after the thread field is set so that {@link ThreadExecutor#getThread} is available */ - @Inject(at = @At(value = "FIELD", target = "Lnet/minecraft/client/MinecraftClient;thread:Ljava/lang/Thread;", shift = At.Shift.AFTER, ordinal = 0), method = "run") + @Inject(at = @At(value = "FIELD", target = "Lnet/minecraft/client/MinecraftClient;thread:Ljava/lang/Thread;", shift = At.Shift.AFTER, ordinal = 0, opcode = Opcodes.PUTFIELD), method = "run") private void onStartup(CallbackInfo ci) { EventFlow.post(new ClientEvent.Startup()); } diff --git a/src/main/java/com/lambda/mixin/client/sound/SoundSystemMixin.java b/src/main/java/com/lambda/mixin/client/sound/SoundSystemMixin.java index abdbb841d..0df20004e 100644 --- a/src/main/java/com/lambda/mixin/client/sound/SoundSystemMixin.java +++ b/src/main/java/com/lambda/mixin/client/sound/SoundSystemMixin.java @@ -25,13 +25,14 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(SoundSystem.class) public class SoundSystemMixin { - @Inject(method = "play(Lnet/minecraft/client/sound/SoundInstance;)V", at = @At("HEAD"), cancellable = true) - public void onPlay(SoundInstance sound, CallbackInfo ci) { + @Inject(method = "play(Lnet/minecraft/client/sound/SoundInstance;)Lnet/minecraft/client/sound/SoundSystem$PlayResult;", at = @At("HEAD"), cancellable = true) + public void onPlay(SoundInstance sound, CallbackInfoReturnable cir) { if (EventFlow.post(new ClientEvent.Sound(sound)).isCanceled()) { - ci.cancel(); + cir.setReturnValue(SoundSystem.PlayResult.NOT_STARTED); } } } diff --git a/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java b/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java index 985115054..7a660a139 100644 --- a/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java +++ b/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java @@ -21,7 +21,6 @@ import com.lambda.event.events.InventoryEvent; import com.lambda.event.events.PlayerEvent; import com.lambda.interaction.managers.inventory.InventoryManager; -import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import net.minecraft.client.network.ClientPlayerEntity; @@ -30,7 +29,6 @@ import net.minecraft.client.world.ClientWorld; import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.entity.player.PlayerInventory; import net.minecraft.screen.slot.SlotActionType; import net.minecraft.stat.StatHandler; import net.minecraft.util.ActionResult; @@ -51,6 +49,9 @@ public class ClientPlayInteractionManagerMixin { @Shadow public float currentBreakingProgress; + @Shadow + public int lastSelectedSlot; + @Inject(method = "interactBlock", at = @At("HEAD"), cancellable = true) public void interactBlockHead(final ClientPlayerEntity player, final Hand hand, final BlockHitResult hitResult, final CallbackInfoReturnable cir) { if (EventFlow.post(new PlayerEvent.Interact.Block(hand, hitResult)).isCanceled()) { @@ -103,9 +104,9 @@ public void clickSlotHead(int syncId, int slotId, int button, SlotActionType act * } * } */ - @ModifyExpressionValue(method = "syncSelectedSlot", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/PlayerInventory;getSelectedSlot()I")) - public int overrideSelectedSlotSync(int original) { - return EventFlow.post(new InventoryEvent.HotbarSlot.Update(original)).getSlot(); + @Inject(method = "syncSelectedSlot", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayNetworkHandler;sendPacket(Lnet/minecraft/network/packet/Packet;)V", shift = At.Shift.BEFORE)) + public void overrideSelectedSlotSync(CallbackInfo ci) { + EventFlow.post(new InventoryEvent.HotbarSlot.Update(lastSelectedSlot)); } @Inject(method = "updateBlockBreakingProgress", at = @At("HEAD"), cancellable = true) @@ -121,9 +122,9 @@ private void cancelBlockBreakingPre(CallbackInfo ci) { if (EventFlow.post(new PlayerEvent.Breaking.Cancel(currentBreakingProgress)).isCanceled()) ci.cancel(); } - @WrapMethod(method = "createPlayer(Lnet/minecraft/client/world/ClientWorld;Lnet/minecraft/stat/StatHandler;Lnet/minecraft/client/recipebook/ClientRecipeBook;ZZ)Lnet/minecraft/client/network/ClientPlayerEntity;") - private ClientPlayerEntity injectCreatePlayer(ClientWorld world, StatHandler statHandler, ClientRecipeBook recipeBook, boolean lastSneaking, boolean lastSprinting, Operation original) { - var player = original.call(world, statHandler, recipeBook, lastSneaking, lastSprinting); + @WrapMethod(method = "createPlayer(Lnet/minecraft/client/world/ClientWorld;Lnet/minecraft/stat/StatHandler;Lnet/minecraft/client/recipebook/ClientRecipeBook;)Lnet/minecraft/client/network/ClientPlayerEntity;") + private ClientPlayerEntity wrapCreatePlayer(ClientWorld world, StatHandler statHandler, ClientRecipeBook recipeBook, Operation original) { + var player = original.call(world, statHandler, recipeBook); InventoryManager.INSTANCE.setScreenHandler(player.playerScreenHandler); return player; } diff --git a/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java b/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java index 935ebecdb..752cd235a 100644 --- a/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java +++ b/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java @@ -21,80 +21,122 @@ import com.lambda.event.EventFlow; import com.lambda.event.events.MovementEvent; import com.lambda.event.events.PlayerEvent; +import com.lambda.event.events.PlayerPacketEvent; import com.lambda.event.events.TickEvent; -import com.lambda.interaction.PlayerPacketHandler; +import com.lambda.interaction.managers.rotating.Rotation; import com.lambda.interaction.managers.rotating.RotationManager; +import com.lambda.module.modules.movement.ElytraFly; +import com.lambda.module.modules.movement.NoJumpCooldown; import com.lambda.module.modules.player.PortalGui; import com.lambda.module.modules.render.ViewModel; +import com.llamalad7.mixinextras.expression.Definition; +import com.llamalad7.mixinextras.expression.Expression; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; -import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; import com.mojang.authlib.GameProfile; -import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.input.Input; import net.minecraft.client.network.AbstractClientPlayerEntity; import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.client.world.ClientWorld; import net.minecraft.entity.MovementType; +import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket; import net.minecraft.util.Hand; import net.minecraft.util.math.Vec3d; -import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; import java.util.Objects; @Mixin(value = ClientPlayerEntity.class, priority = Integer.MAX_VALUE) public abstract class ClientPlayerEntityMixin extends AbstractClientPlayerEntity { - @Shadow public Input input; - @Shadow @Final protected MinecraftClient client; - @Shadow private boolean autoJumpEnabled; + @Shadow + private float lastYawClient; + @Shadow + private float lastPitchClient; + @Unique + private PlayerPacketEvent.Pre moveEvent; public ClientPlayerEntityMixin(ClientWorld world, GameProfile profile) { super(world, profile); } @WrapOperation(method = "move", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/AbstractClientPlayerEntity;move(Lnet/minecraft/entity/MovementType;Lnet/minecraft/util/math/Vec3d;)V")) - private void emitMovementEvents(ClientPlayerEntity instance, MovementType movementType, Vec3d vec3d, Operation original) { + private void wrapMove(ClientPlayerEntity instance, MovementType movementType, Vec3d vec3d, Operation original) { EventFlow.post(new MovementEvent.Player.Pre(movementType, vec3d)); original.call(instance, movementType, vec3d); EventFlow.post(new MovementEvent.Player.Post(movementType, vec3d)); } @WrapOperation(method = "tickMovement", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/input/Input;tick()V")) - void processMovement(Input input, Operation original) { + void wrapTick(Input input, Operation original) { original.call(input); RotationManager.processRotations(); RotationManager.redirectStrafeInputs(input); EventFlow.post(new MovementEvent.InputUpdate(input)); } + @Inject(method = "tickMovement", at = @At("RETURN")) + private void injectTickMovement(CallbackInfo ci) { + if (NoJumpCooldown.INSTANCE.isEnabled() || (ElytraFly.INSTANCE.isEnabled() && ElytraFly.getMode() == ElytraFly.FlyMode.Bounce)) jumpingCooldown = 0; + } + @Inject(method = "sendMovementPackets", at = @At("HEAD")) - private void injectSendMovementPackets(CallbackInfo ci) { - PlayerPacketHandler.sendPlayerPackets(); - autoJumpEnabled = Lambda.getMc().options.getAutoJump().getValue(); + private void injectSendMovementPacketsHead(CallbackInfo ci) { + moveEvent = EventFlow.post(new PlayerPacketEvent.Pre(pos, RotationManager.getActiveRotation(), isOnGround(), isSprinting(), horizontalCollision)); + } + + @Definition(id = "g", local = @Local(type = double.class, ordinal = 3)) + @Expression("g != 0.0") + @ModifyExpressionValue(method = "sendMovementPackets", at = @At("MIXINEXTRAS:EXPRESSION")) + private boolean modifyHasRotated(boolean original) { + return !RotationManager.getActiveRotation().equalFloat(RotationManager.getServerRotation()) || original; + } + + @Inject(method = "sendMovementPackets", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayNetworkHandler;sendPacket(Lnet/minecraft/network/packet/Packet;)V", shift = At.Shift.BEFORE), locals = LocalCapture.CAPTURE_FAILEXCEPTION) + private void injectSendPacket(CallbackInfo ci, double d, double e, double f, double g, double h, boolean bl, boolean bl2) { + if (RotationManager.getRequests().stream().allMatch(Objects::isNull)) { + moveEvent.setRotation(new Rotation(g + lastYawClient, h + lastPitchClient)); + } } - - @WrapWithCondition(method = "sendMovementPackets", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;sendSprintingPacket()V")) - private boolean wrapSendSprintingPackets(ClientPlayerEntity instance) { return false; } - @ModifyExpressionValue(method = "sendMovementPackets", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;isCamera()Z")) - private boolean wrapIsCamera(boolean original) { return false; } + @WrapOperation(method = "sendMovementPackets", at = @At(value = "NEW", target = "net/minecraft/network/packet/c2s/play/PlayerMoveC2SPacket$Full")) + private PlayerMoveC2SPacket.Full wrapFullPacket(Vec3d pos, float yaw, float pitch, boolean onGround, boolean horizontalCollision, Operation original) { + return original.call(moveEvent.getPosition(), moveEvent.getRotation().getYawF(), moveEvent.getRotation().getPitchF(), moveEvent.getOnGround(), moveEvent.isCollidingHorizontally()); + } + + @WrapOperation(method = "sendMovementPackets", at = @At(value = "NEW", target = "net/minecraft/network/packet/c2s/play/PlayerMoveC2SPacket$PositionAndOnGround")) + private PlayerMoveC2SPacket.PositionAndOnGround wrapPositionAndOnGround(Vec3d pos, boolean onGround, boolean horizontalCollision, Operation original) { + return original.call(moveEvent.getPosition(), moveEvent.getOnGround(), moveEvent.isCollidingHorizontally()); + } + + @WrapOperation(method = "sendMovementPackets", at = @At(value = "NEW", target = "net/minecraft/network/packet/c2s/play/PlayerMoveC2SPacket$LookAndOnGround")) + private PlayerMoveC2SPacket.LookAndOnGround wrapLookAndOnGround(float yaw, float pitch, boolean onGround, boolean horizontalCollision, Operation original) { + return original.call(moveEvent.getRotation().getYawF(), moveEvent.getRotation().getPitchF(), moveEvent.getOnGround(), moveEvent.isCollidingHorizontally()); + } + + @WrapOperation(method = "sendMovementPackets", at = @At(value = "NEW", target = "net/minecraft/network/packet/c2s/play/PlayerMoveC2SPacket$OnGroundOnly")) + private PlayerMoveC2SPacket.OnGroundOnly wrapOnGroundOnly(boolean onGround, boolean horizontalCollision, Operation original) { + return original.call(moveEvent.getOnGround(), moveEvent.isCollidingHorizontally()); + } - @WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;sendSneakingPacket()V")) - void sendSneakingPacket(ClientPlayerEntity entity, Operation original) { - PlayerPacketHandler.sendSneakPackets(); + @Inject(method = "sendMovementPackets", at = @At("TAIL")) + private void injectSendMovementPacketsReturn(CallbackInfo ci) { + RotationManager.onRotationSend(); + EventFlow.post(new PlayerPacketEvent.Post()); } @ModifyExpressionValue(method = "tickMovement", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;isSprinting()Z")) - boolean isSprinting(boolean original) { + boolean modifyIsSprinting(boolean original) { return EventFlow.post(new MovementEvent.Sprint(original)).getSprint(); } @@ -115,22 +157,22 @@ void onTick(Operation original) { } @WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;getYaw()F")) - float fixHeldItemYaw(ClientPlayerEntity instance, Operation original) { + float wrapGetYaw(ClientPlayerEntity instance, Operation original) { return Objects.requireNonNullElse(RotationManager.getHandYaw(), original.call(instance)); } @WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;getPitch()F")) - float fixHeldItemPitch(ClientPlayerEntity instance, Operation original) { + float wrapGetPitch(ClientPlayerEntity instance, Operation original) { return Objects.requireNonNullElse(RotationManager.getHandPitch(), original.call(instance)); } @Inject(method = "swingHand", at = @At("HEAD"), cancellable = true) - void onSwing(Hand hand, CallbackInfo ci) { + void injectSwingHand(Hand hand, CallbackInfo ci) { if (EventFlow.post(new PlayerEvent.SwingHand(hand)).isCanceled()) ci.cancel(); } @WrapOperation(method = "swingHand", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/AbstractClientPlayerEntity;swingHand(Lnet/minecraft/util/Hand;)V")) - private void adjustSwing(ClientPlayerEntity instance, Hand hand, Operation original) { + private void wrapSwingHand(ClientPlayerEntity instance, Hand hand, Operation original) { ViewModel viewModel = ViewModel.INSTANCE; if (!viewModel.isEnabled()) { @@ -142,12 +184,13 @@ private void adjustSwing(ClientPlayerEntity instance, Hand hand, Operation } @Inject(method = "updateHealth", at = @At("HEAD")) - public void damage(float health, CallbackInfo ci) { + public void injectUpdateHealth(float health, CallbackInfo ci) { EventFlow.post(new PlayerEvent.Health(health)); } /** * Prevents the game from closing Guis when the player is in a nether portal + * *
{@code
      * if (this.client.currentScreen != null
      *         && !this.client.currentScreen.shouldPause()
@@ -162,7 +205,7 @@ public void damage(float health, CallbackInfo ci) {
      * }
*/ @ModifyExpressionValue(method = "tickNausea", at = @At(value = "FIELD", target = "Lnet/minecraft/client/MinecraftClient;currentScreen:Lnet/minecraft/client/gui/screen/Screen;")) - Screen keepScreensInPortal(Screen original) { + Screen modifyCurrentScreen(Screen original) { if (PortalGui.INSTANCE.isEnabled()) return null; else return original; } diff --git a/src/main/java/com/lambda/mixin/entity/EntityMixin.java b/src/main/java/com/lambda/mixin/entity/EntityMixin.java index 6959557b1..7b1e48152 100644 --- a/src/main/java/com/lambda/mixin/entity/EntityMixin.java +++ b/src/main/java/com/lambda/mixin/entity/EntityMixin.java @@ -17,20 +17,20 @@ package com.lambda.mixin.entity; -import com.lambda.Lambda; import com.lambda.event.EventFlow; import com.lambda.event.events.EntityEvent; import com.lambda.event.events.PlayerEvent; import com.lambda.interaction.managers.rotating.RotationManager; -import com.lambda.interaction.managers.rotating.RotationMode; -import com.lambda.module.modules.player.RotationLock; +import com.lambda.module.modules.movement.ElytraFly; import com.lambda.module.modules.render.NoRender; import com.lambda.util.math.Vec2d; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityPose; import net.minecraft.entity.MovementType; import net.minecraft.entity.data.TrackedData; import net.minecraft.util.math.Vec3d; @@ -39,6 +39,9 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import static com.lambda.Lambda.getMc; @Mixin(Entity.class) public abstract class EntityMixin { @@ -54,7 +57,7 @@ public void move(MovementType movementType, Vec3d movement) { */ @WrapOperation(method = "updateVelocity", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;getYaw()F")) public float velocityYaw(Entity entity, Operation original) { - if ((Object) this != Lambda.getMc().player) return original.call(entity); + if ((Object) this != getMc().player) return original.call(entity); Float y = RotationManager.getMovementYaw(); if (y == null) return original.call(entity); @@ -73,7 +76,7 @@ public float velocityYaw(Entity entity, Operation original) { @WrapOperation(method = "getRotationVec", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;getYaw(F)F")) float fixDirectionYaw(Entity entity, float tickDelta, Operation original) { Vec2d rot = RotationManager.getRotationForVector(tickDelta); - if (entity != Lambda.getMc().player || rot == null) return original.call(entity, tickDelta); + if (entity != getMc().player || rot == null) return original.call(entity, tickDelta); return (float) rot.getX(); } @@ -89,7 +92,7 @@ float fixDirectionYaw(Entity entity, float tickDelta, Operation original) @WrapOperation(method = "getRotationVec", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;getPitch(F)F")) float fixDirectionPitch(Entity entity, float tickDelta, Operation original) { Vec2d rot = RotationManager.getRotationForVector(tickDelta); - if (entity != Lambda.getMc().player || rot == null) return original.call(entity, tickDelta); + if (entity != getMc().player || rot == null) return original.call(entity, tickDelta); return (float) rot.getY(); } @@ -105,7 +108,7 @@ float fixDirectionPitch(Entity entity, float tickDelta, Operation origina @WrapOperation(method = "getRotationVector()Lnet/minecraft/util/math/Vec3d;", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;getYaw()F")) float fixDirectionYaw2(Entity entity, Operation original) { Vec2d rot = RotationManager.getRotationForVector(1.0); - if (entity != Lambda.getMc().player || rot == null) return original.call(entity); + if (entity != getMc().player || rot == null) return original.call(entity); return (float) rot.getX(); } @@ -121,7 +124,7 @@ float fixDirectionYaw2(Entity entity, Operation original) { @WrapOperation(method = "getRotationVector()Lnet/minecraft/util/math/Vec3d;", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;getPitch()F")) float fixDirectionPitch2(Entity entity, Operation original) { Vec2d rot = RotationManager.getRotationForVector(1.0); - if (entity != Lambda.getMc().player || rot == null) return original.call(entity); + if (entity != getMc().player || rot == null) return original.call(entity); return (float) rot.getY(); } @@ -149,17 +152,30 @@ private boolean modifyGetFlagGlowing(boolean original) { @WrapWithCondition(method = "changeLookDirection", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;setYaw(F)V")) private boolean wrapSetYaw(Entity instance, float yaw) { - return (instance != Lambda.getMc().player || - RotationLock.INSTANCE.isDisabled() || - RotationLock.INSTANCE.getRotationConfig().getRotationMode() != RotationMode.Lock || - RotationLock.getYawMode() == RotationLock.Mode.None); + return RotationManager.getLockYaw() == null; } @WrapWithCondition(method = "changeLookDirection", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;setPitch(F)V")) private boolean wrapSetPitch(Entity instance, float yaw) { - return (instance != Lambda.getMc().player || - RotationLock.INSTANCE.isDisabled() || - RotationLock.INSTANCE.getRotationConfig().getRotationMode() != RotationMode.Lock || - RotationLock.getPitchMode() == RotationLock.Mode.None); + return RotationManager.getLockPitch() == null; + } + + @Inject(method = "isSprinting()Z", at = @At("HEAD"), cancellable = true) + private void injectIsSprinting(CallbackInfoReturnable cir) { + var player = getMc().player; + if ((Object) this != getMc().player) return; + if (ElytraFly.INSTANCE.isEnabled() && ElytraFly.getMode() == ElytraFly.FlyMode.Bounce && player.isGliding()) cir.setReturnValue(true); + } + + @Inject(method = "getPose", at = @At("HEAD"), cancellable = true) + private void injectGetPose(CallbackInfoReturnable cir) { + var entity = (Entity) (Object) this; + if (!(entity instanceof ClientPlayerEntity player)) return; + if (ElytraFly.INSTANCE.isEnabled() && ElytraFly.getMode() == ElytraFly.FlyMode.Bounce && player.isGliding()) cir.setReturnValue(EntityPose.GLIDING); + } + + @ModifyExpressionValue(method = "getHorizontalFacing", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;getYaw()F")) + private float modifyGetYaw(float original) { + return (Object) this == getMc().player ? RotationManager.getServerRotation().getYawF() : original; } } diff --git a/src/main/java/com/lambda/mixin/entity/FireworkRocketEntityMixin.java b/src/main/java/com/lambda/mixin/entity/FireworkRocketEntityMixin.java index 0b1090840..960235b64 100644 --- a/src/main/java/com/lambda/mixin/entity/FireworkRocketEntityMixin.java +++ b/src/main/java/com/lambda/mixin/entity/FireworkRocketEntityMixin.java @@ -30,7 +30,7 @@ public class FireworkRocketEntityMixin { @WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/LivingEntity;setVelocity(Lnet/minecraft/util/math/Vec3d;)V")) private void wrapSetVelocity(LivingEntity shooter, Vec3d vec3d, Operation original) { - if (ElytraFly.getDoBoost()) { + if (ElytraFly.INSTANCE.isEnabled()) { ElytraFly.boostRocket(); } else original.call(shooter, vec3d); } diff --git a/src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java b/src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java index 74ececd16..02ba9ed72 100644 --- a/src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java +++ b/src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java @@ -21,6 +21,7 @@ import com.lambda.event.EventFlow; import com.lambda.event.events.MovementEvent; import com.lambda.interaction.managers.rotating.RotationManager; +import com.lambda.module.modules.movement.ElytraFly; import com.lambda.module.modules.movement.Velocity; import com.lambda.module.modules.render.ViewModel; import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; @@ -34,11 +35,10 @@ import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Constant; import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.ModifyConstant; import org.spongepowered.asm.mixin.injection.Slice; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(LivingEntity.class) public abstract class LivingEntityMixin extends EntityMixin { @@ -170,9 +170,9 @@ private float rotHead(LivingEntity entity, Operation original) { return (yaw == null) ? original.call(entity) : yaw; } - @ModifyConstant(method = "getHandSwingDuration", constant = @Constant(intValue = 6)) - private int getHandSwingDuration(int constant) { - if (lambda$instance != Lambda.getMc().player || ViewModel.INSTANCE.isDisabled()) return constant; + @WrapMethod(method = "getHandSwingDuration") + private int getHandSwingDuration(Operation original) { + if (lambda$instance != Lambda.getMc().player || ViewModel.INSTANCE.isDisabled()) return original.call(); return ViewModel.INSTANCE.getSwingDuration(); } @@ -184,4 +184,10 @@ private void wrapPushAwayFrom(Entity entity, Operation original) { Velocity.getPushed()) return; original.call(entity); } + + @Inject(method = "isGliding", at = @At("HEAD"), cancellable = true) + private void injectIsGliding(CallbackInfoReturnable cir) { + if (lambda$instance != Lambda.getMc().player) return; + cir.setReturnValue(ElytraFly.isGliding()); + } } diff --git a/src/main/java/com/lambda/mixin/entity/PlayerEntityMixin.java b/src/main/java/com/lambda/mixin/entity/PlayerEntityMixin.java index f6b7eeddc..15acd2497 100644 --- a/src/main/java/com/lambda/mixin/entity/PlayerEntityMixin.java +++ b/src/main/java/com/lambda/mixin/entity/PlayerEntityMixin.java @@ -47,13 +47,13 @@ private float wrapHeadYaw(PlayerEntity instance, Operation original) { return (yaw != null) ? yaw : original.call(instance); } - @WrapOperation(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/PlayerEntity;getYaw()F")) - private float wrapAttackYaw(PlayerEntity instance, Operation original) { - if ((Object) this != Lambda.getMc().player) { - return original.call(instance); - } - - Float yaw = RotationManager.getMovementYaw(); - return (yaw != null) ? yaw : original.call(instance); - } +// @WrapOperation(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/PlayerEntity;getYaw()F")) +// private float wrapAttackYaw(PlayerEntity instance, Operation original) { +// if ((Object) this != Lambda.getMc().player) { +// return original.call(instance); +// } +// +// Float yaw = RotationManager.getMovementYaw(); +// return (yaw != null) ? yaw : original.call(instance); +// } } diff --git a/src/main/java/com/lambda/mixin/input/KeyBindingMixin.java b/src/main/java/com/lambda/mixin/input/KeyBindingMixin.java index 87b8a524c..1ba526d2a 100644 --- a/src/main/java/com/lambda/mixin/input/KeyBindingMixin.java +++ b/src/main/java/com/lambda/mixin/input/KeyBindingMixin.java @@ -32,7 +32,7 @@ public class KeyBindingMixin { @ModifyReturnValue(method = "isPressed", at = @At("RETURN")) boolean modifyIsPressed(boolean original) { KeyBinding instance = (KeyBinding) (Object) this; - if (!Objects.equals(instance.getTranslationKey(), "key.sprint")) return original; + if (!Objects.equals(instance.getId(), "key.sprint")) return original; if (Sprint.INSTANCE.isEnabled()) return true; if (Speed.INSTANCE.isEnabled() && Speed.getMode() == Speed.Mode.GrimStrafe) return true; diff --git a/src/main/java/com/lambda/mixin/input/KeyboardMixin.java b/src/main/java/com/lambda/mixin/input/KeyboardMixin.java index 3c2368116..8e913469d 100644 --- a/src/main/java/com/lambda/mixin/input/KeyboardMixin.java +++ b/src/main/java/com/lambda/mixin/input/KeyboardMixin.java @@ -18,11 +18,13 @@ package com.lambda.mixin.input; import com.lambda.event.EventFlow; -import com.lambda.event.events.KeyboardEvent; +import com.lambda.event.events.ButtonEvent; import com.lambda.module.modules.player.InventoryMove; import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import net.minecraft.client.Keyboard; +import net.minecraft.client.input.CharInput; +import net.minecraft.client.input.KeyInput; import net.minecraft.client.option.KeyBinding; import net.minecraft.client.util.InputUtil; import org.spongepowered.asm.mixin.Mixin; @@ -30,28 +32,36 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +/** + * Mixin to intercept keyboard input events. + * + * Note: In 1.21.11, onKey/onChar methods were refactored to use KeyInput/CharInput records. + * - onKey(long window, int action, KeyInput input) where KeyInput has key, scancode, modifiers + * - onChar(long window, CharInput input) where CharInput has codepoint, modifiers + */ @Mixin(Keyboard.class) public class KeyboardMixin { @WrapMethod(method = "onKey") - private void onKey(long window, int key, int scancode, int action, int modifiers, Operation original) { - EventFlow.post(new KeyboardEvent.Press(key, scancode, action, modifiers)); - original.call(window, key, scancode, action, modifiers); + private void onKey(long window, int action, KeyInput input, Operation original) { + EventFlow.post(new ButtonEvent.Keyboard.Press(input.key(), input.scancode(), action, input.modifiers())); + original.call(window, action, input); } @Inject(method = "onKey", at = @At("RETURN")) - private void onKeyTail(long window, int key, int scancode, int action, int modifiers, CallbackInfo ci) { + private void onKeyTail(long window, int action, KeyInput input, CallbackInfo ci) { + int key = input.key(); if (!InventoryMove.getShouldMove() || !InventoryMove.isKeyMovementRelated(key)) return; - InputUtil.Key fromCode = InputUtil.fromKeyCode(key, scancode); + InputUtil.Key fromCode = InputUtil.fromKeyCode(input); KeyBinding.setKeyPressed(fromCode, action != 0); } @WrapMethod(method = "onChar") - private void onChar(long window, int codePoint, int modifiers, Operation original) { - char[] chars = Character.toChars(codePoint); + private void onChar(long window, CharInput input, Operation original) { + char[] chars = Character.toChars(input.codepoint()); for (char c : chars) - EventFlow.post(new KeyboardEvent.Char(c)); + EventFlow.post(new ButtonEvent.Keyboard.Char(c)); - original.call(window, codePoint, modifiers); + original.call(window, input); } } diff --git a/src/main/java/com/lambda/mixin/input/MouseMixin.java b/src/main/java/com/lambda/mixin/input/MouseMixin.java index 6fb6b6f31..c78c2aa73 100644 --- a/src/main/java/com/lambda/mixin/input/MouseMixin.java +++ b/src/main/java/com/lambda/mixin/input/MouseMixin.java @@ -18,14 +18,14 @@ package com.lambda.mixin.input; import com.lambda.event.EventFlow; -import com.lambda.event.events.MouseEvent; +import com.lambda.event.events.ButtonEvent; import com.lambda.module.modules.render.Zoom; import com.lambda.util.math.Vec2d; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import net.minecraft.client.Mouse; -import net.minecraft.client.option.SimpleOption; +import net.minecraft.client.input.MouseInput; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -36,17 +36,17 @@ public class MouseMixin { @Shadow private double y; - @WrapMethod(method = "onMouseButton(JIII)V") - private void onMouseButton(long window, int button, int action, int mods, Operation original) { - if (!EventFlow.post(new MouseEvent.Click(button, action, mods)).isCanceled()) - original.call(window, button, action, mods); + @WrapMethod(method = "onMouseButton") + private void onMouseButton(long window, MouseInput input, int action, Operation original) { + if (!EventFlow.post(new ButtonEvent.Mouse.Click(input.button(), action, input.modifiers())).isCanceled()) + original.call(window, input, action); } @WrapMethod(method = "onMouseScroll(JDD)V") private void onMouseScroll(long window, double horizontal, double vertical, Operation original) { Vec2d delta = new Vec2d(horizontal, vertical); - if (!EventFlow.post(new MouseEvent.Scroll(delta)).isCanceled()) + if (!EventFlow.post(new ButtonEvent.Mouse.Scroll(delta)).isCanceled()) original.call(window, horizontal, vertical); } @@ -56,7 +56,7 @@ private void onCursorPos(long window, double x, double y, Operation origin Vec2d position = new Vec2d(x, y); - if (!EventFlow.post(new MouseEvent.Move(position)).isCanceled()) + if (!EventFlow.post(new ButtonEvent.Mouse.Move(position)).isCanceled()) original.call(window, x, y); } diff --git a/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java b/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java index 49da330b8..758a17444 100644 --- a/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java +++ b/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java @@ -18,6 +18,7 @@ package com.lambda.mixin.network; import com.lambda.event.EventFlow; +import com.lambda.event.events.ChatEvent; import com.lambda.event.events.InventoryEvent; import com.lambda.event.events.WorldEvent; import com.lambda.interaction.managers.inventory.InventoryManager; @@ -45,15 +46,15 @@ void injectJoinPacket(GameJoinS2CPacket packet, CallbackInfo ci) { void injectPlayerList(PlayerListS2CPacket.Action action, PlayerListS2CPacket.Entry receivedEntry, PlayerListEntry currentEntry, CallbackInfo ci) { if (action != PlayerListS2CPacket.Action.UPDATE_LISTED) return; - var name = currentEntry.getProfile().getName(); - var uuid = currentEntry.getProfile().getId(); + var name = currentEntry.getProfile().name(); + var uuid = currentEntry.getProfile().id(); if (receivedEntry.listed()) { EventFlow.post(new WorldEvent.Player.Join(name, uuid, currentEntry)); } else EventFlow.post(new WorldEvent.Player.Leave(name, uuid, currentEntry)); } - @Inject(method = "onUpdateSelectedSlot", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/NetworkThreadUtils;forceMainThread(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/listener/PacketListener;Lnet/minecraft/util/thread/ThreadExecutor;)V", shift = At.Shift.AFTER), cancellable = true) + @Inject(method = "onUpdateSelectedSlot", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/NetworkThreadUtils;forceMainThread(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/listener/PacketListener;Lnet/minecraft/network/PacketApplyBatcher;)V", shift = At.Shift.AFTER), cancellable = true) private void onUpdateSelectedSlot(UpdateSelectedSlotS2CPacket packet, CallbackInfo ci) { if (EventFlow.post(new InventoryEvent.HotbarSlot.Sync(packet.slot())).isCanceled()) ci.cancel(); } @@ -64,17 +65,19 @@ private void onScreenHandlerSlotUpdate(ScreenHandlerSlotUpdateS2CPacket packet, } /** - * Sets displayedUnsecureChatWarning to {@link NoRender#getNoChatVerificationToast()} + * Sets seenInsecureChatWarning to {@link NoRender#getNoChatVerificationToast()} *
{@code
      * this.secureChatEnforced = packet.enforcesSecureChat();
-     * if (this.serverInfo != null && !this.displayedUnsecureChatWarning && !this.isSecureChatEnforced()) {
+     * if (this.serverInfo != null && !this.seenInsecureChatWarning && !this.isSecureChatEnforced()) {
      * SystemToast systemToast = SystemToast.create(this.client, SystemToast.Type.UNSECURE_SERVER_WARNING, UNSECURE_SERVER_TOAST_TITLE, UNSECURE_SERVER_TOAST_TEXT);
      * this.client.getToastManager().add(systemToast);
-     * this.displayedUnsecureChatWarning = true;
+     * this.seenInsecureChatWarning = true;
      * }
      * }
+ * + * Note: In 1.21.11, displayedUnsecureChatWarning was renamed to seenInsecureChatWarning. */ - @ModifyExpressionValue(method = "onGameJoin(Lnet/minecraft/network/packet/s2c/play/GameJoinS2CPacket;)V", at = @At(value = "FIELD", target = "Lnet/minecraft/client/network/ClientPlayNetworkHandler;displayedUnsecureChatWarning:Z", ordinal = 0)) + @ModifyExpressionValue(method = "onGameJoin(Lnet/minecraft/network/packet/s2c/play/GameJoinS2CPacket;)V", at = @At(value = "FIELD", target = "Lnet/minecraft/client/network/ClientPlayNetworkHandler;seenInsecureChatWarning:Z", ordinal = 0)) public boolean onServerMetadata(boolean original) { return (NoRender.getNoChatVerificationToast() && NoRender.INSTANCE.isEnabled()) || original; } @@ -116,4 +119,12 @@ private void wrapOnScreenHandlerSlotUpdate(ScreenHandlerSlotUpdateS2CPacket pack private void wrapOnInventory(InventoryS2CPacket packet, Operation original) { InventoryManager.onInventoryUpdate(packet, original); } + + @WrapMethod(method = "sendChatMessage(Ljava/lang/String;)V") + void onSendMessage(String content, Operation original) { + var event = new ChatEvent.Send(content); + + if (!EventFlow.post(event).isCanceled()) + original.call(event.getMessage()); + } } \ No newline at end of file diff --git a/src/main/java/com/lambda/mixin/render/AbstractTerrainRenderContextMixin.java b/src/main/java/com/lambda/mixin/render/AbstractTerrainRenderContextMixin.java index f09e674bc..f19cda820 100644 --- a/src/main/java/com/lambda/mixin/render/AbstractTerrainRenderContextMixin.java +++ b/src/main/java/com/lambda/mixin/render/AbstractTerrainRenderContextMixin.java @@ -28,7 +28,6 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -@SuppressWarnings("UnstableApiUsage") @Mixin(AbstractTerrainRenderContext.class) public class AbstractTerrainRenderContextMixin { @Final diff --git a/src/main/java/com/lambda/mixin/render/ArmorFeatureRendererMixin.java b/src/main/java/com/lambda/mixin/render/ArmorFeatureRendererMixin.java index cc5ee9e77..77ca02496 100644 --- a/src/main/java/com/lambda/mixin/render/ArmorFeatureRendererMixin.java +++ b/src/main/java/com/lambda/mixin/render/ArmorFeatureRendererMixin.java @@ -19,8 +19,10 @@ import com.lambda.module.modules.render.NoRender; import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.command.OrderedRenderCommandQueue; import net.minecraft.client.render.entity.feature.ArmorFeatureRenderer; import net.minecraft.client.render.entity.state.BipedEntityRenderState; +import net.minecraft.client.render.entity.state.EntityRenderState; import net.minecraft.client.util.math.MatrixStack; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -29,8 +31,8 @@ @Mixin(ArmorFeatureRenderer.class) public class ArmorFeatureRendererMixin { - @Inject(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/BipedEntityRenderState;FF)V", at = @At("HEAD"), cancellable = true) - private void injectRender(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, BipedEntityRenderState bipedEntityRenderState, float f, float g, CallbackInfo ci) { + @Inject(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/command/OrderedRenderCommandQueue;ILnet/minecraft/client/render/entity/state/EntityRenderState;FF)V", at = @At("HEAD"), cancellable = true) + private void injectRender(MatrixStack matrices, OrderedRenderCommandQueue queue, int light, EntityRenderState state, float limbAngle, float limbDistance, CallbackInfo ci) { if (NoRender.INSTANCE.isEnabled() && NoRender.getNoArmor()) ci.cancel(); } } diff --git a/src/main/java/com/lambda/mixin/render/BackgroundRendererMixin.java b/src/main/java/com/lambda/mixin/render/BackgroundRendererMixin.java index f0f02d1d2..887ca3b6b 100644 --- a/src/main/java/com/lambda/mixin/render/BackgroundRendererMixin.java +++ b/src/main/java/com/lambda/mixin/render/BackgroundRendererMixin.java @@ -18,60 +18,29 @@ package com.lambda.mixin.render; import com.lambda.module.modules.render.NoRender; -import com.lambda.module.modules.render.WorldColors; -import com.llamalad7.mixinextras.injector.ModifyExpressionValue; -import net.minecraft.client.render.BackgroundRenderer; +import net.minecraft.client.render.fog.BlindnessEffectFogModifier; +import net.minecraft.client.render.fog.DarknessEffectFogModifier; +import net.minecraft.block.enums.CameraSubmersionType; import net.minecraft.entity.Entity; import net.minecraft.entity.LivingEntity; -import net.minecraft.util.math.Vec3d; -import net.minecraft.world.biome.Biome; -import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import java.util.List; -import java.util.stream.Stream; - /** - *
{@code
- * Vec3d vec3d2 = camera.getPos().subtract(2.0, 2.0, 2.0).multiply(0.25);
- * Vec3d vec3d3 = CubicSampler.sampleColor(
- *         vec3d2, (x, y, z) -> world.getDimensionEffects().adjustFogColor(Vec3d.unpackRgb(biomeAccess.getBiomeForNoiseGen(x, y, z).value().getFogColor()), v)
- * );
- * red = (float)vec3d3.getX();
- * green = (float)vec3d3.getY();
- * blue = (float)vec3d3.getZ();
- * }
+ * Mixins to disable blindness and darkness fog effects when NoRender is enabled. + * + * Note: The fog system was completely rewritten in 1.21.11. + * BackgroundRenderer was replaced with FogRenderer and fog modifiers are now separate classes. */ -@Mixin(BackgroundRenderer.class) +@Mixin(BlindnessEffectFogModifier.class) public class BackgroundRendererMixin { - @Shadow @Final private static List FOG_MODIFIERS; - - /** - * Modifies the fog color returned from CubicSampler.sampleColor - */ - @ModifyExpressionValue(method = "getFogColor", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/CubicSampler;sampleColor(Lnet/minecraft/util/math/Vec3d;Lnet/minecraft/util/CubicSampler$RgbFetcher;)Lnet/minecraft/util/math/Vec3d;")) - private static Vec3d modifyFogColor(Vec3d original) { - return WorldColors.fogOfWarColor(original); - } - - @ModifyExpressionValue(method = "getFogColor", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/biome/Biome;getWaterFogColor()I")) - private static int modifyWaterFogColor(int original) { - return WorldColors.waterFogColor(original); - } - @Inject(method = "getFogModifier(Lnet/minecraft/entity/Entity;F)Lnet/minecraft/client/render/BackgroundRenderer$StatusEffectFogModifier;", at = @At("HEAD"), cancellable = true) - private static void injectFogModifier(Entity entity, float tickProgress, CallbackInfoReturnable cir){ - if (entity instanceof LivingEntity livingEntity) { - Stream modifiers = FOG_MODIFIERS - .stream() - .filter((modifier) -> - modifier.shouldApply(livingEntity, tickProgress) && NoRender.shouldAcceptFog(modifier) - ); - cir.setReturnValue(modifiers.findFirst().orElse(null)); + @Inject(method = "applyDarknessModifier", at = @At("HEAD"), cancellable = true) + private void injectShouldApplyBlindness(LivingEntity cameraEntity, float darkness, float tickProgress, CallbackInfoReturnable cir) { + if (NoRender.getNoBlindness() && NoRender.INSTANCE.isEnabled()) { + cir.setReturnValue(0.0f); } } } diff --git a/src/main/java/com/lambda/mixin/render/CameraMixin.java b/src/main/java/com/lambda/mixin/render/CameraMixin.java index 7b618d8be..13ee986f3 100644 --- a/src/main/java/com/lambda/mixin/render/CameraMixin.java +++ b/src/main/java/com/lambda/mixin/render/CameraMixin.java @@ -25,7 +25,7 @@ import net.minecraft.block.enums.CameraSubmersionType; import net.minecraft.client.render.Camera; import net.minecraft.entity.Entity; -import net.minecraft.world.BlockView; +import net.minecraft.world.World; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -41,15 +41,20 @@ public abstract class CameraMixin { @Shadow public abstract void setRotation(float yaw, float pitch); + @Shadow + public abstract float getPitch(); + + @Shadow + public abstract float getYaw(); + + @Shadow + public float yaw; + + @Shadow + public float pitch; + @Inject(method = "update", at = @At("TAIL")) - private void onUpdate( - BlockView area, - Entity focusedEntity, - boolean thirdPerson, - boolean inverseView, - float tickDelta, - CallbackInfo ci - ) { + private void onUpdate(World area, Entity focusedEntity, boolean thirdPerson, boolean inverseView, float tickProgress, CallbackInfo ci) { if (!Freecam.INSTANCE.isEnabled()) return; Freecam.updateCam(); @@ -66,21 +71,25 @@ private void onUpdate( * } */ @Inject(method = "update", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/Camera;setPos(DDD)V", shift = At.Shift.AFTER)) - private void injectQuickPerspectiveSwap(BlockView area, Entity focusedEntity, boolean thirdPerson, boolean inverseView, float tickDelta, CallbackInfo ci) { + private void injectQuickPerspectiveSwap(World area, Entity focusedEntity, boolean thirdPerson, boolean inverseView, float tickProgress, CallbackInfo ci) { var rot = RotationManager.getLockRotation(); if (rot == null) return; - setRotation(rot.getYawF(), rot.getPitchF()); + if (FreeLook.INSTANCE.isEnabled()) { + var newYaw = yaw; + var newPitch = pitch; + if (FreeLook.getEnableYaw()) newYaw = rot.getYawF(); + if (FreeLook.getEnablePitch()) newPitch = rot.getPitchF(); + setRotation(newYaw, newPitch); + } else setRotation(rot.getYawF(), rot.getPitchF()); } /** - * Allows camera to clip through blocks in third person + * Allows the camera to clip through blocks in third person */ @Inject(method = "clipToSpace", at = @At("HEAD"), cancellable = true) - private void onClipToSpace(float desiredCameraDistance, CallbackInfoReturnable info) { + private void onClipToSpace(float distance, CallbackInfoReturnable cir) { if (CameraTweaks.INSTANCE.isEnabled() && CameraTweaks.getNoClipCam()) { - info.setReturnValue(desiredCameraDistance); - } else if (FreeLook.INSTANCE.isEnabled()) { - info.setReturnValue(desiredCameraDistance); + cir.setReturnValue(distance); } } @@ -97,14 +106,12 @@ private void onClipToSpace(float desiredCameraDistance, CallbackInfoReturnable */ @ModifyArg(method = "update", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/Camera;clipToSpace(F)F")) - private float onDistanceUpdate(float desiredCameraDistance) { + private float onDistanceUpdate(float distance) { if (CameraTweaks.INSTANCE.isEnabled()) { return CameraTweaks.getCamDistance(); - } else if (FreeLook.INSTANCE.isEnabled()) { - return 4.0F; } - return desiredCameraDistance; + return distance; } /** diff --git a/src/main/java/com/lambda/mixin/render/CapeFeatureRendererMixin.java b/src/main/java/com/lambda/mixin/render/CapeFeatureRendererMixin.java index c1a319f3d..07be67e4a 100644 --- a/src/main/java/com/lambda/mixin/render/CapeFeatureRendererMixin.java +++ b/src/main/java/com/lambda/mixin/render/CapeFeatureRendererMixin.java @@ -21,7 +21,7 @@ import com.lambda.module.modules.client.Capes; import com.lambda.network.CapeManager; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; -import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.command.OrderedRenderCommandQueue; import net.minecraft.client.render.entity.feature.CapeFeatureRenderer; import net.minecraft.client.render.entity.state.PlayerEntityRenderState; import net.minecraft.client.util.math.MatrixStack; @@ -29,16 +29,25 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; +/** + * Mixin to override cape textures with Lambda capes. + * + * Note: In 1.21.11, render method uses OrderedRenderCommandQueue instead of VertexConsumerProvider. + * Cape texture is now accessed via skinTextures.cape().texturePath() instead of capeTexture(). + */ @Mixin(CapeFeatureRenderer.class) public class CapeFeatureRendererMixin { - @ModifyExpressionValue(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/PlayerEntityRenderState;FF)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/util/SkinTextures;capeTexture()Lnet/minecraft/util/Identifier;")) - Identifier renderCape(Identifier original, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, PlayerEntityRenderState player, float f, float g) { - var entry = Lambda.getMc().getNetworkHandler().getPlayerListEntry(player.name); // this will cause issues if we try to render while not in game + @ModifyExpressionValue(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/command/OrderedRenderCommandQueue;ILnet/minecraft/client/render/entity/state/PlayerEntityRenderState;FF)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/AssetInfo$TextureAsset;texturePath()Lnet/minecraft/util/Identifier;")) + Identifier renderCape(Identifier original, MatrixStack matrixStack, OrderedRenderCommandQueue commandQueue, int i, PlayerEntityRenderState player, float f, float g) { + var networkHandler = Lambda.getMc().getNetworkHandler(); + if (networkHandler == null) return original; + + var entry = player.playerName != null ? networkHandler.getPlayerListEntry(player.playerName.getString()) : null; if (entry == null) return original; var profile = entry.getProfile(); - if (!Capes.INSTANCE.isEnabled() || !CapeManager.INSTANCE.getCache().containsKey(profile.getId())) return original; + if (!Capes.INSTANCE.isEnabled() || !CapeManager.INSTANCE.getCache().containsKey(profile.id())) return original; - return Identifier.of("lambda", CapeManager.INSTANCE.getCache().get(profile.getId())); + return Identifier.of("lambda", CapeManager.INSTANCE.getCache().get(profile.id())); } } diff --git a/src/main/java/com/lambda/mixin/render/ChatHudMixin.java b/src/main/java/com/lambda/mixin/render/ChatHudMixin.java index 025b9f849..2b6790df0 100644 --- a/src/main/java/com/lambda/mixin/render/ChatHudMixin.java +++ b/src/main/java/com/lambda/mixin/render/ChatHudMixin.java @@ -17,15 +17,15 @@ package com.lambda.mixin.render; -import com.lambda.module.modules.client.LambdaMoji; +import com.lambda.event.EventFlow; +import com.lambda.event.events.ChatEvent; +import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; -import net.minecraft.client.font.TextRenderer; -import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.hud.ChatHud; -import net.minecraft.text.OrderedText; +import net.minecraft.client.gui.hud.MessageIndicator; +import net.minecraft.network.message.MessageSignatureData; +import net.minecraft.text.Text; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; @Mixin(ChatHud.class) public class ChatHudMixin { @@ -41,4 +41,12 @@ public class ChatHudMixin { // int wrapRenderCall(DrawContext instance, TextRenderer textRenderer, OrderedText text, int x, int y, int color, Operation original) { // return original.call(instance, textRenderer, LambdaMoji.INSTANCE.parse(text, x, y, color), 0, y, 16777215 + (color << 24)); // } + + @WrapMethod(method = "addMessage(Lnet/minecraft/text/Text;Lnet/minecraft/network/message/MessageSignatureData;Lnet/minecraft/client/gui/hud/MessageIndicator;)V") + void wrapAddMessage(Text message, MessageSignatureData signatureData, MessageIndicator indicator, Operation original) { + var event = new ChatEvent.Receive(message, signatureData, indicator); + + if (!EventFlow.post(event).isCanceled()) + original.call(event.getMessage(), event.getSignature(), event.getIndicator()); + } } diff --git a/src/main/java/com/lambda/mixin/render/ChatInputSuggestorMixin.java b/src/main/java/com/lambda/mixin/render/ChatInputSuggestorMixin.java index ae9578474..1978e42a7 100644 --- a/src/main/java/com/lambda/mixin/render/ChatInputSuggestorMixin.java +++ b/src/main/java/com/lambda/mixin/render/ChatInputSuggestorMixin.java @@ -17,42 +17,23 @@ package com.lambda.mixin.render; -import com.google.common.base.Strings; import com.lambda.command.CommandManager; -import com.lambda.graphics.renderer.gui.font.core.LambdaAtlas; -import com.lambda.module.modules.client.LambdaMoji; -import com.lambda.module.modules.client.StyleEditor; import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.suggestion.Suggestions; -import com.mojang.brigadier.suggestion.SuggestionsBuilder; import net.minecraft.client.gui.screen.ChatInputSuggestor; import net.minecraft.client.gui.widget.TextFieldWidget; import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.command.CommandSource; -import org.jetbrains.annotations.Nullable; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyVariable; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Stream; @Mixin(ChatInputSuggestor.class) public abstract class ChatInputSuggestorMixin { - @Unique private static final Pattern COLON_PATTERN = Pattern.compile("(:[a-zA-Z0-9_]+)"); - @Unique private static final Pattern EMOJI_PATTERN = Pattern.compile("(:)([a-zA-Z0-9_]+)(:)"); @Shadow @Final TextFieldWidget textField; - @Shadow private @Nullable CompletableFuture pendingSuggestions; @Shadow public abstract void show(boolean narrateFirstSuggestion); @@ -62,68 +43,9 @@ private boolean refreshModify(boolean showCompletions) { return CommandManager.INSTANCE.isCommand(textField.getText()); } + @SuppressWarnings("unchecked") @WrapOperation(method = "refresh", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayNetworkHandler;getCommandDispatcher()Lcom/mojang/brigadier/CommandDispatcher;")) private CommandDispatcher wrapRefresh(ClientPlayNetworkHandler instance, Operation> original) { - return CommandManager.INSTANCE.currentDispatcher(textField.getText()); - } - - @Inject(method = "refresh", at = @At("TAIL")) - private void refreshEmojiSuggestion(CallbackInfo ci) { - if (!LambdaMoji.INSTANCE.isEnabled() || - !LambdaMoji.INSTANCE.getSuggestions()) return; - - String typing = textField.getText(); - - // Don't suggest emojis in commands - if (CommandManager.INSTANCE.isCommand(typing) || - CommandManager.INSTANCE.isLambdaCommand(typing)) return; - - int cursor = textField.getCursor(); - String textToCursor = typing.substring(0, cursor); - if (textToCursor.isEmpty()) return; - - // Most right index at the left of the regex expression - int start = neoLambda$getLastColon(textToCursor); - if (start == -1) return; - - Matcher emojiMatcher = EMOJI_PATTERN.matcher(textToCursor); - Map emojiKeys = LambdaAtlas.INSTANCE.getKeys(StyleEditor.INSTANCE.getEmojiFont()); - while (emojiMatcher.find()) { - int openingColon = emojiMatcher.start(1); - String key = emojiMatcher.group(2); - int closingColon = emojiMatcher.end(3); - - if (emojiKeys.containsKey(key) && start >= openingColon && start < closingColon) { - // If the colon is part of a previous valid emoji, return - return; - } - } - - String emojiString = textToCursor.substring(start + 1); - - Stream results = emojiKeys.keySet().stream() - .filter(s -> s.startsWith(emojiString)) - .map(s -> s + ":"); - - pendingSuggestions = CommandSource.suggestMatching(results, new SuggestionsBuilder(textToCursor, start + 1)); - pendingSuggestions.thenRun(() -> { - if (!pendingSuggestions.isDone()) return; - - show(false); - }); - } - - @Unique - private int neoLambda$getLastColon(String input) { - if (Strings.isNullOrEmpty(input)) return -1; - - int i = -1; - Matcher matcher = COLON_PATTERN.matcher(input); - - while (matcher.find()) { - i = matcher.start(); - } - - return i; + return (CommandDispatcher) CommandManager.INSTANCE.currentDispatcher(textField.getText()); } } diff --git a/src/main/java/com/lambda/mixin/render/DarknessEffectFogMixin.java b/src/main/java/com/lambda/mixin/render/DarknessEffectFogMixin.java new file mode 100644 index 000000000..af0c941e7 --- /dev/null +++ b/src/main/java/com/lambda/mixin/render/DarknessEffectFogMixin.java @@ -0,0 +1,42 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.mixin.render; + +import com.lambda.module.modules.render.NoRender; +import net.minecraft.client.render.fog.DarknessEffectFogModifier; +import net.minecraft.block.enums.CameraSubmersionType; +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +/** + * Mixin to disable darkness fog effect when NoRender is enabled. + */ +@Mixin(DarknessEffectFogModifier.class) +public class DarknessEffectFogMixin { + + @Inject(method = "applyDarknessModifier", at = @At("HEAD"), cancellable = true) + private void injectShouldApplyDarkness(LivingEntity cameraEntity, float darkness, float tickProgress, CallbackInfoReturnable cir) { + if (NoRender.getNoDarkness() && NoRender.INSTANCE.isEnabled()) { + cir.setReturnValue(0.0f); + } + } +} diff --git a/src/main/java/com/lambda/mixin/render/DebugHudMixin.java b/src/main/java/com/lambda/mixin/render/DebugHudMixin.java index 4b29a9e66..3c0a42c54 100644 --- a/src/main/java/com/lambda/mixin/render/DebugHudMixin.java +++ b/src/main/java/com/lambda/mixin/render/DebugHudMixin.java @@ -17,19 +17,13 @@ package com.lambda.mixin.render; -import com.lambda.util.DebugInfoHud; import net.minecraft.client.gui.hud.DebugHud; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import java.util.List; @Mixin(DebugHud.class) public class DebugHudMixin { - @Inject(method = "getRightText", at = @At("TAIL")) - private void onGetRightText(CallbackInfoReturnable> cir) { - DebugInfoHud.addDebugInfo(cir.getReturnValue()); - } +// @Inject(method = "getRightText", at = @At("TAIL")) +// private void onGetRightText(CallbackInfoReturnable> cir) { +// DebugInfoHud.addDebugInfo(cir.getReturnValue()); +// } } diff --git a/src/main/java/com/lambda/mixin/render/DebugRendererMixin.java b/src/main/java/com/lambda/mixin/render/DebugRendererMixin.java index 3491d7c24..8b703e194 100644 --- a/src/main/java/com/lambda/mixin/render/DebugRendererMixin.java +++ b/src/main/java/com/lambda/mixin/render/DebugRendererMixin.java @@ -29,8 +29,8 @@ @Mixin(DebugRenderer.class) public class DebugRendererMixin { - @Inject(method = "render", at = @At("TAIL")) - private void onRender(MatrixStack matrices, Frustum frustum, VertexConsumerProvider.Immediate vertexConsumers, double cameraX, double cameraY, double cameraZ, CallbackInfo ci) { - DebugRendererModule.render(matrices, vertexConsumers, cameraX, cameraY, cameraZ); - } +// @Inject(method = "render", at = @At("TAIL")) +// private void onRender(MatrixStack matrices, Frustum frustum, VertexConsumerProvider.Immediate vertexConsumers, double cameraX, double cameraY, double cameraZ, CallbackInfo ci) { +// DebugRendererModule.render(matrices, vertexConsumers, cameraX, cameraY, cameraZ); +// } } diff --git a/src/main/java/com/lambda/mixin/render/DrawContextMixin.java b/src/main/java/com/lambda/mixin/render/DrawContextMixin.java index 825e8b9a0..c7eca6b04 100644 --- a/src/main/java/com/lambda/mixin/render/DrawContextMixin.java +++ b/src/main/java/com/lambda/mixin/render/DrawContextMixin.java @@ -18,16 +18,24 @@ package com.lambda.mixin.render; import com.lambda.module.modules.render.ContainerPreview; +import com.lambda.module.modules.render.MapPreview; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.tooltip.TooltipComponent; -import net.minecraft.client.gui.tooltip.TooltipPositioner; +import net.minecraft.client.render.MapRenderState; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.item.FilledMapItem; import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; import net.minecraft.item.tooltip.TooltipData; import net.minecraft.text.Text; import net.minecraft.util.Identifier; import org.jetbrains.annotations.Nullable; +import org.joml.Matrix3x2fStack; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -35,8 +43,48 @@ import java.util.List; import java.util.Optional; +/* +Map slot rendering code +Original source: https://github.com/Crec0/map-in-slot +Copyright (c) 2022 Crec0 +Licensed under MIT License + */ @Mixin(DrawContext.class) -public class DrawContextMixin { +public abstract class DrawContextMixin { + @Shadow + @Final + MinecraftClient client; + + @Unique + private final MapRenderState mapRenderState = new MapRenderState(); + + @Shadow + public abstract Matrix3x2fStack getMatrices(); + + @Shadow + public abstract void drawMap(MapRenderState mapRenderState); + + @Inject(method = "drawStackOverlay(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/item/ItemStack;IILjava/lang/String;)V", at = @At(value = "TAIL")) + private void injectDrawMap(TextRenderer textRenderer, ItemStack stack, int i, int j, String string, CallbackInfo ci) { + if (MapPreview.INSTANCE.isDisabled() || !MapPreview.getShowInSlot()) return; + + if (!stack.isOf(Items.FILLED_MAP)) return; + + var mapId = stack.get(DataComponentTypes.MAP_ID); + var savedData = FilledMapItem.getMapState(mapId, client.world); + + if (savedData == null) return; + + this.getMatrices().pushMatrix(); + this.getMatrices().translate(i, j); + this.getMatrices().scale(0.125F, 0.125F); + + client.getMapRenderer().update(mapId, savedData, this.mapRenderState); + this.drawMap(this.mapRenderState); + + this.getMatrices().popMatrix(); + } + @Inject(method = "drawTooltip(Lnet/minecraft/client/font/TextRenderer;Ljava/util/List;Ljava/util/Optional;IILnet/minecraft/util/Identifier;)V", at = @At("HEAD"), cancellable = true) private void onDrawTooltip(TextRenderer textRenderer, List text, Optional data, int x, int y, @Nullable Identifier texture, CallbackInfo ci) { if (!ContainerPreview.INSTANCE.isEnabled()) return; diff --git a/src/main/java/com/lambda/mixin/render/ElytraFeatureRendererMixin.java b/src/main/java/com/lambda/mixin/render/ElytraFeatureRendererMixin.java index 78a1bdf2c..72dc2c384 100644 --- a/src/main/java/com/lambda/mixin/render/ElytraFeatureRendererMixin.java +++ b/src/main/java/com/lambda/mixin/render/ElytraFeatureRendererMixin.java @@ -21,40 +21,48 @@ import com.lambda.module.modules.client.Capes; import com.lambda.module.modules.render.NoRender; import com.lambda.network.CapeManager; +import com.llamalad7.mixinextras.injector.ModifyReturnValue; import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; -import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.command.OrderedRenderCommandQueue; import net.minecraft.client.render.entity.feature.ElytraFeatureRenderer; import net.minecraft.client.render.entity.state.BipedEntityRenderState; import net.minecraft.client.render.entity.state.PlayerEntityRenderState; import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.entity.LivingEntity; import net.minecraft.util.Identifier; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; +/** + * Mixin to override elytra textures with Lambda capes and disable elytra rendering. + * + * Note: In 1.21.11, render method uses OrderedRenderCommandQueue instead of VertexConsumerProvider. + * getTexture is now a private static method. + */ @Mixin(ElytraFeatureRenderer.class) -public class ElytraFeatureRendererMixin { - @WrapOperation(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/BipedEntityRenderState;FF)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/entity/feature/ElytraFeatureRenderer;getTexture(Lnet/minecraft/client/render/entity/state/BipedEntityRenderState;)Lnet/minecraft/util/Identifier;")) - Identifier injectElytra(BipedEntityRenderState state, Operation original) { - if (!(state instanceof PlayerEntityRenderState)) - return original.call(state); +public class ElytraFeatureRendererMixin { + @ModifyReturnValue(method = "getTexture", at = @At("RETURN")) + private static Identifier injectElytra(Identifier original, BipedEntityRenderState state) { + if (!(state instanceof PlayerEntityRenderState playerState)) + return original; + + var networkHandler = Lambda.getMc().getNetworkHandler(); + if (networkHandler == null) return original; - var entry = Lambda.getMc().getNetworkHandler().getPlayerListEntry(((PlayerEntityRenderState) state).name); - if (entry == null) return original.call(state); + var entry = playerState.playerName != null ? networkHandler.getPlayerListEntry(playerState.playerName.getString()) : null; + if (entry == null) return original; var profile = entry.getProfile(); - if (!Capes.INSTANCE.isEnabled() || !CapeManager.INSTANCE.getCache().containsKey(profile.getId())) - return original.call(state); + if (!Capes.INSTANCE.isEnabled() || !CapeManager.INSTANCE.getCache().containsKey(profile.id())) + return original; - return Identifier.of("lambda", CapeManager.INSTANCE.getCache().get(profile.getId())); + return Identifier.of("lambda", CapeManager.INSTANCE.getCache().get(profile.id())); } - @WrapMethod(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/BipedEntityRenderState;FF)V") - private void injectRender(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, BipedEntityRenderState bipedEntityRenderState, float f, float g, Operation original) { + @WrapMethod(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/command/OrderedRenderCommandQueue;ILnet/minecraft/client/render/entity/state/BipedEntityRenderState;FF)V") + private void injectRender(MatrixStack matrixStack, OrderedRenderCommandQueue commandQueue, int i, BipedEntityRenderState bipedEntityRenderState, float f, float g, Operation original) { if (NoRender.INSTANCE.isDisabled() || !NoRender.getNoElytra()) - original.call(matrixStack, vertexConsumerProvider, i, bipedEntityRenderState, f, g); + original.call(matrixStack, commandQueue, i, bipedEntityRenderState, f, g); } } diff --git a/src/main/java/com/lambda/mixin/render/EntityRendererMixin.java b/src/main/java/com/lambda/mixin/render/EntityRendererMixin.java index 69af3188a..b71234dce 100644 --- a/src/main/java/com/lambda/mixin/render/EntityRendererMixin.java +++ b/src/main/java/com/lambda/mixin/render/EntityRendererMixin.java @@ -19,12 +19,12 @@ import com.lambda.module.modules.render.NoRender; import net.minecraft.client.render.Frustum; -import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.command.OrderedRenderCommandQueue; import net.minecraft.client.render.entity.EntityRenderer; import net.minecraft.client.render.entity.state.EntityRenderState; +import net.minecraft.client.render.state.CameraRenderState; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.entity.Entity; -import net.minecraft.text.Text; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -39,7 +39,7 @@ private void injectShouldRender(Entity entity, Frustum frustum, double x, double } @Inject(method = "renderLabelIfPresent", at = @At("HEAD"), cancellable = true) - private void injectRenderLabelIfPresent(EntityRenderState state, Text text, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, CallbackInfo ci) { + private void injectRenderLabelIfPresent(EntityRenderState state, MatrixStack matrices, OrderedRenderCommandQueue queue, CameraRenderState cameraRenderState, CallbackInfo ci) { if (NoRender.INSTANCE.isEnabled() && NoRender.getNoNametags()) ci.cancel(); } } diff --git a/src/main/java/com/lambda/mixin/render/FogRendererMixin.java b/src/main/java/com/lambda/mixin/render/FogRendererMixin.java new file mode 100644 index 000000000..98bdccacd --- /dev/null +++ b/src/main/java/com/lambda/mixin/render/FogRendererMixin.java @@ -0,0 +1,44 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.mixin.render; + +import com.lambda.module.modules.render.NoRender; +import com.mojang.blaze3d.buffers.GpuBuffer; +import com.mojang.blaze3d.buffers.GpuBufferSlice; +import net.minecraft.client.render.fog.FogRenderer; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import static net.minecraft.client.render.fog.FogRenderer.FOG_UBO_SIZE; + +@Mixin(FogRenderer.class) +public class FogRendererMixin { + @Final + @Shadow + private GpuBuffer emptyBuffer; + + @Inject(method = "getFogBuffer", at = @At("HEAD"), cancellable = true) + private void modify(FogRenderer.FogType fogType, CallbackInfoReturnable cir) { + if (fogType == FogRenderer.FogType.WORLD && NoRender.INSTANCE.isEnabled() && NoRender.getNoTerrainFog()) + cir.setReturnValue(emptyBuffer.slice(0L, FOG_UBO_SIZE)); + } +} diff --git a/src/main/java/com/lambda/mixin/render/GameRendererMixin.java b/src/main/java/com/lambda/mixin/render/GameRendererMixin.java index 42cb9ace5..98cf293e7 100644 --- a/src/main/java/com/lambda/mixin/render/GameRendererMixin.java +++ b/src/main/java/com/lambda/mixin/render/GameRendererMixin.java @@ -20,12 +20,14 @@ import com.lambda.event.EventFlow; import com.lambda.event.events.RenderEvent; import com.lambda.graphics.RenderMain; +import com.lambda.gui.DearImGui; import com.lambda.module.modules.render.NoRender; import com.lambda.module.modules.render.Zoom; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.injector.ModifyReturnValue; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.mojang.blaze3d.buffers.GpuBufferSlice; import net.minecraft.client.render.Camera; import net.minecraft.client.render.GameRenderer; import net.minecraft.client.render.RenderTickCounter; @@ -33,6 +35,7 @@ import net.minecraft.client.util.ObjectAllocator; import net.minecraft.item.ItemStack; import org.joml.Matrix4f; +import org.joml.Vector4f; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -47,21 +50,9 @@ private void updateTargetedEntityInvoke(float tickDelta, CallbackInfo info) { } } - /** - * Begins our 3d render after the game has rendered the world - *
{@code
-     * float m = Math.max(h, (float)(Integer)this.client.options.getFov().getValue());
-     * Matrix4f matrix4f2 = this.getBasicProjectionMatrix(m);
-     * RenderSystem.setProjectionMatrix(matrix4f, ProjectionType.PERSPECTIVE);
-     * Quaternionf quaternionf = camera.getRotation().conjugate(new Quaternionf());
-     * Matrix4f matrix4f3 = (new Matrix4f()).rotation(quaternionf);
-     * this.client.worldRenderer.setupFrustum(camera.getPos(), matrix4f3, matrix4f2);
-     * this.client.worldRenderer.render(this.pool, renderTickCounter, bl, camera, this, matrix4f3, matrix4f);
-     * }
- */ - @WrapOperation(method = "renderWorld", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/WorldRenderer;render(Lnet/minecraft/client/util/ObjectAllocator;Lnet/minecraft/client/render/RenderTickCounter;ZLnet/minecraft/client/render/Camera;Lnet/minecraft/client/render/GameRenderer;Lorg/joml/Matrix4f;Lorg/joml/Matrix4f;)V")) - void onRenderWorld(WorldRenderer instance, ObjectAllocator allocator, RenderTickCounter tickCounter, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, Matrix4f positionMatrix, Matrix4f projectionMatrix, Operation original) { - original.call(instance, allocator, tickCounter, renderBlockOutline, camera, gameRenderer, positionMatrix, projectionMatrix); + @WrapOperation(method = "renderWorld", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/WorldRenderer;render(Lnet/minecraft/client/util/ObjectAllocator;Lnet/minecraft/client/render/RenderTickCounter;ZLnet/minecraft/client/render/Camera;Lorg/joml/Matrix4f;Lorg/joml/Matrix4f;Lorg/joml/Matrix4f;Lcom/mojang/blaze3d/buffers/GpuBufferSlice;Lorg/joml/Vector4f;Z)V")) + void onRenderWorld(WorldRenderer instance, ObjectAllocator allocator, RenderTickCounter tickCounter, boolean renderBlockOutline, Camera camera, Matrix4f positionMatrix, Matrix4f basicProjectionMatrix, Matrix4f projectionMatrix, GpuBufferSlice fogBuffer, Vector4f fogColor, boolean renderSky, Operation original) { + original.call(instance, allocator, tickCounter, renderBlockOutline, camera, positionMatrix, basicProjectionMatrix, projectionMatrix, fogBuffer, fogColor, renderSky); RenderMain.render3D(positionMatrix, projectionMatrix); } @@ -80,4 +71,9 @@ private void injectShowFloatingItem(ItemStack floatingItem, CallbackInfo ci) { private float modifyGetFov(float original) { return original / Zoom.getLerpedZoom(); } + + @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/render/GuiRenderer;render(Lcom/mojang/blaze3d/buffers/GpuBufferSlice;)V", shift = At.Shift.AFTER)) + private void onGuiRenderComplete(RenderTickCounter tickCounter, boolean tick, CallbackInfo ci) { + DearImGui.INSTANCE.render(); + } } diff --git a/src/main/java/com/lambda/mixin/render/HandledScreenMixin.java b/src/main/java/com/lambda/mixin/render/HandledScreenMixin.java index 193cdc516..3c66e9c53 100644 --- a/src/main/java/com/lambda/mixin/render/HandledScreenMixin.java +++ b/src/main/java/com/lambda/mixin/render/HandledScreenMixin.java @@ -18,6 +18,7 @@ package com.lambda.mixin.render; import com.lambda.module.modules.render.ContainerPreview; +import net.minecraft.client.gui.Click; import net.minecraft.client.gui.screen.ingame.HandledScreen; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -27,18 +28,18 @@ @Mixin(HandledScreen.class) public class HandledScreenMixin { @Inject(method = "mouseClicked", at = @At("HEAD"), cancellable = true) - private void onMouseClicked(double mouseX, double mouseY, int button, CallbackInfoReturnable cir) { + private void onMouseClicked(Click click, boolean doubled, CallbackInfoReturnable cir) { if (ContainerPreview.INSTANCE.isEnabled() && ContainerPreview.isLocked()) { - if (ContainerPreview.isMouseOverLockedTooltip((int) mouseX, (int) mouseY)) { + if (ContainerPreview.isMouseOverLockedTooltip((int) click.x(), (int) click.y())) { cir.setReturnValue(true); } } } @Inject(method = "mouseReleased", at = @At("HEAD"), cancellable = true) - private void onMouseReleased(double mouseX, double mouseY, int button, CallbackInfoReturnable cir) { + private void onMouseReleased(Click click, CallbackInfoReturnable cir) { if (ContainerPreview.INSTANCE.isEnabled() && ContainerPreview.isLocked()) { - if (ContainerPreview.isMouseOverLockedTooltip((int) mouseX, (int) mouseY)) { + if (ContainerPreview.isMouseOverLockedTooltip((int) click.x(), (int) click.y())) { cir.setReturnValue(true); } } diff --git a/src/main/java/com/lambda/mixin/render/HeadFeatureRendererMixin.java b/src/main/java/com/lambda/mixin/render/HeadFeatureRendererMixin.java index 38834b1ac..bb8416614 100644 --- a/src/main/java/com/lambda/mixin/render/HeadFeatureRendererMixin.java +++ b/src/main/java/com/lambda/mixin/render/HeadFeatureRendererMixin.java @@ -19,7 +19,9 @@ import com.lambda.module.modules.render.NoRender; import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.command.OrderedRenderCommandQueue; import net.minecraft.client.render.entity.feature.HeadFeatureRenderer; +import net.minecraft.client.render.entity.state.EntityRenderState; import net.minecraft.client.render.entity.state.LivingEntityRenderState; import net.minecraft.client.util.math.MatrixStack; import org.spongepowered.asm.mixin.Mixin; @@ -29,8 +31,8 @@ @Mixin(HeadFeatureRenderer.class) public class HeadFeatureRendererMixin { - @Inject(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/LivingEntityRenderState;FF)V", at = @At("HEAD"), cancellable = true) - private void injectRender(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, LivingEntityRenderState livingEntityRenderState, float f, float g, CallbackInfo ci) { + @Inject(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/command/OrderedRenderCommandQueue;ILnet/minecraft/client/render/entity/state/EntityRenderState;FF)V", at = @At("HEAD"), cancellable = true) + private void injectRender(MatrixStack matrices, OrderedRenderCommandQueue queue, int light, EntityRenderState state, float limbAngle, float limbDistance, CallbackInfo ci) { if (NoRender.INSTANCE.isEnabled() && NoRender.getNoArmor() && NoRender.getIncludeNoOtherHeadItems()) ci.cancel(); } } diff --git a/src/main/java/com/lambda/mixin/render/HeldItemRendererMixin.java b/src/main/java/com/lambda/mixin/render/HeldItemRendererMixin.java index 48d965c1a..0be8b99b8 100644 --- a/src/main/java/com/lambda/mixin/render/HeldItemRendererMixin.java +++ b/src/main/java/com/lambda/mixin/render/HeldItemRendererMixin.java @@ -20,9 +20,10 @@ import com.google.common.base.MoreObjects; import com.lambda.Lambda; import com.lambda.module.modules.render.ViewModel; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.AbstractClientPlayerEntity; -import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.command.OrderedRenderCommandQueue; import net.minecraft.client.render.item.HeldItemRenderer; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.item.ItemStack; @@ -33,7 +34,6 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyArg; -import org.spongepowered.asm.mixin.injection.ModifyVariable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(HeldItemRenderer.class) @@ -44,18 +44,14 @@ public class HeldItemRendererMixin { @Shadow private float equipProgressMainHand; @Shadow private float equipProgressOffHand; - @Inject(method = "renderFirstPersonItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/item/HeldItemRenderer;renderArmHoldingItem(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IFFLnet/minecraft/util/Arm;)V")) - private void onRenderArmHoldingItem(AbstractClientPlayerEntity player, float tickDelta, float pitch, Hand hand, float swingProgress, ItemStack itemStack, float equipProgress, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, CallbackInfo ci) { - if (!ViewModel.INSTANCE.isEnabled()) return; - - ViewModel.INSTANCE.transform(itemStack, hand, matrices); + @Inject(method = "renderFirstPersonItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/item/HeldItemRenderer;renderItem(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/item/ItemStack;Lnet/minecraft/item/ItemDisplayContext;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/command/OrderedRenderCommandQueue;I)V")) + private void injectRenderFirstPersonItem(AbstractClientPlayerEntity player, float tickProgress, float pitch, Hand hand, float swingProgress, ItemStack item, float equipProgress, MatrixStack matrices, OrderedRenderCommandQueue orderedRenderCommandQueue, int light, CallbackInfo ci) { + if (ViewModel.INSTANCE.isEnabled()) ViewModel.INSTANCE.transform(item, hand, matrices); } - @Inject(method = "renderFirstPersonItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/item/HeldItemRenderer;renderItem(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/item/ItemStack;Lnet/minecraft/item/ItemDisplayContext;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V", ordinal = 1)) - private void onRenderFirstPersonItem(AbstractClientPlayerEntity player, float tickDelta, float pitch, Hand hand, float swingProgress, ItemStack itemStack, float equipProgress, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, CallbackInfo ci) { - if (!ViewModel.INSTANCE.isEnabled()) return; - - ViewModel.INSTANCE.transform(itemStack, hand, matrices); + @Inject(method = "renderFirstPersonItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/item/HeldItemRenderer;renderArmHoldingItem(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/command/OrderedRenderCommandQueue;IFFLnet/minecraft/util/Arm;)V")) + private void injectRenderArmHoldingItem(AbstractClientPlayerEntity player, float tickProgress, float pitch, Hand hand, float swingProgress, ItemStack item, float equipProgress, MatrixStack matrices, OrderedRenderCommandQueue orderedRenderCommandQueue, int light, CallbackInfo ci) { + if (ViewModel.INSTANCE.isEnabled()) ViewModel.INSTANCE.transform(item, hand, matrices); } @ModifyArg(method = "updateHeldItems", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/math/MathHelper;clamp(FFF)F", ordinal = 2), index = 0) @@ -68,7 +64,7 @@ private float modifyEquipProgressMainHand(float value) { mainHand = currentStack; } - float progress = config.getOldAnimations() ? 1 : (float) Math.pow(client.player.getAttackCooldownProgress(1), 3); + float progress = config.getOldAnimations() ? 1 : (float) Math.pow(client.player.getHandEquippingProgress(1), 3); return (ItemStack.areEqual(mainHand, currentStack) ? progress : 0) - equipProgressMainHand; } @@ -87,7 +83,7 @@ private float modifyEquipProgressOffHand(float value) { return (ItemStack.areEqual(offHand, currentStack) ? 1 : 0) - equipProgressOffHand; } - @ModifyVariable(method = "renderItem(FLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider$Immediate;Lnet/minecraft/client/network/ClientPlayerEntity;I)V", at = @At(value = "STORE", ordinal = 0), index = 6) + @ModifyExpressionValue(method = "renderItem(FLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/command/OrderedRenderCommandQueue;Lnet/minecraft/client/network/ClientPlayerEntity;I)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;getHandSwingProgress(F)F")) private float modifySwing(float swingProgress) { ViewModel config = ViewModel.INSTANCE; MinecraftClient mc = Lambda.getMc(); diff --git a/src/main/java/com/lambda/mixin/render/InGameHudMixin.java b/src/main/java/com/lambda/mixin/render/InGameHudMixin.java index 407ef5998..d6ca4f2ca 100644 --- a/src/main/java/com/lambda/mixin/render/InGameHudMixin.java +++ b/src/main/java/com/lambda/mixin/render/InGameHudMixin.java @@ -35,13 +35,6 @@ @Mixin(InGameHud.class) public class InGameHudMixin { - /** - * Begins our 2d render after the game has rendered all 2d elements - */ - @Inject(method = "render", at = @At("TAIL")) - private void onRender(DrawContext context, RenderTickCounter tickCounter, CallbackInfo ci) { - DearImGui.INSTANCE.render(); - } @Inject(method = "renderNauseaOverlay", at = @At("HEAD"), cancellable = true) private void injectRenderNauseaOverlay(DrawContext context, float nauseaStrength, CallbackInfo ci) { diff --git a/src/main/java/com/lambda/mixin/render/InGameOverlayRendererMixin.java b/src/main/java/com/lambda/mixin/render/InGameOverlayRendererMixin.java index 49412089b..956abd82e 100644 --- a/src/main/java/com/lambda/mixin/render/InGameOverlayRendererMixin.java +++ b/src/main/java/com/lambda/mixin/render/InGameOverlayRendererMixin.java @@ -32,9 +32,9 @@ @Mixin(InGameOverlayRenderer.class) public class InGameOverlayRendererMixin { @WrapMethod(method = "renderFireOverlay") - private static void wrapRenderFireOverlay(MatrixStack matrices, VertexConsumerProvider vertexConsumers, Operation original) { + private static void wrapRenderFireOverlay(MatrixStack matrices, VertexConsumerProvider vertexConsumers, Sprite sprite, Operation original) { if (!(NoRender.INSTANCE.isEnabled() && NoRender.getNoFireOverlay())) { - original.call(matrices, vertexConsumers); + original.call(matrices, vertexConsumers, sprite); } } diff --git a/src/main/java/com/lambda/mixin/render/LightmapTextureManagerMixin.java b/src/main/java/com/lambda/mixin/render/LightmapTextureManagerMixin.java index 849a9b297..48dec03ed 100644 --- a/src/main/java/com/lambda/mixin/render/LightmapTextureManagerMixin.java +++ b/src/main/java/com/lambda/mixin/render/LightmapTextureManagerMixin.java @@ -35,19 +35,25 @@ import java.util.OptionalInt; +/** + * Mixin to override lightmap for Fullbright/XRay and disable darkness effect. + * + * Note: In 1.21.11, the lightmap rendering was rewritten to use RenderPass with shaders. + * We override the texture after normal rendering completes. + */ @Mixin(LightmapTextureManager.class) public class LightmapTextureManagerMixin { @Shadow @Final private GpuTexture glTexture; @Inject(method = "update", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/profiler/Profiler;pop()V", shift = At.Shift.BEFORE)) - private void injectUpdate(CallbackInfo ci) { + private void injectUpdate(float tickProgress, CallbackInfo ci) { if (Fullbright.INSTANCE.isEnabled() || XRay.INSTANCE.isEnabled()) { - RenderSystem.getDevice().createCommandEncoder().createRenderPass(glTexture, OptionalInt.of(ColorHelper.getArgb(255, 255, 255, 255))).close(); + RenderSystem.getDevice().createCommandEncoder().clearColorTexture(glTexture, ColorHelper.fullAlpha(ColorHelper.getWhite(1.0f))); } } @ModifyReturnValue(method = "getDarkness", at = @At("RETURN")) - private float modifyGetDarkness(float original) { + private float modifyGetDarkness(float original, LivingEntity entity, float factor, float tickProgress) { if (NoRender.getNoDarkness() && NoRender.INSTANCE.isEnabled()) return 0.0f; return original; } diff --git a/src/main/java/com/lambda/mixin/render/ParticleManagerMixin.java b/src/main/java/com/lambda/mixin/render/ParticleManagerMixin.java deleted file mode 100644 index 654e32316..000000000 --- a/src/main/java/com/lambda/mixin/render/ParticleManagerMixin.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2025 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.mixin.render; - -import com.lambda.module.modules.render.NoRender; -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; -import net.minecraft.client.particle.Particle; -import net.minecraft.client.particle.ParticleManager; -import net.minecraft.client.particle.ParticleTextureSheet; -import net.minecraft.client.render.Camera; -import net.minecraft.client.render.VertexConsumerProvider; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.At; - -import java.util.LinkedList; -import java.util.Queue; -import java.util.stream.Collectors; - -@Mixin(ParticleManager.class) -public class ParticleManagerMixin { - // prevents the particles from being stored and potential overhead. Downside being they need to spawn back in rather than just enabling rendering again -// @Inject(method = "addParticle(Lnet/minecraft/client/particle/Particle;)V", at = @At("HEAD"), cancellable = true) -// private void injectAddParticle(Particle particle, CallbackInfo ci) { -// if (NoRender.shouldOmitParticle(particle)) ci.cancel(); -// } - - @WrapOperation(method = "renderParticles(Lnet/minecraft/client/render/Camera;FLnet/minecraft/client/render/VertexConsumerProvider$Immediate;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/ParticleManager;renderParticles(Lnet/minecraft/client/render/Camera;FLnet/minecraft/client/render/VertexConsumerProvider$Immediate;Lnet/minecraft/client/particle/ParticleTextureSheet;Ljava/util/Queue;)V")) - private void wrapRenderParticles(Camera camera, float tickProgress, VertexConsumerProvider.Immediate vertexConsumers, ParticleTextureSheet sheet, Queue particles, Operation original) { - original.call(camera, tickProgress, vertexConsumers, sheet, filterParticles(particles)); - } - - @WrapOperation(method = "renderParticles(Lnet/minecraft/client/render/Camera;FLnet/minecraft/client/render/VertexConsumerProvider$Immediate;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/ParticleManager;renderCustomParticles(Lnet/minecraft/client/render/Camera;FLnet/minecraft/client/render/VertexConsumerProvider$Immediate;Ljava/util/Queue;)V")) - private void wrapRenderParticles(Camera camera, float tickProgress, VertexConsumerProvider.Immediate vertexConsumers, Queue particles, Operation original) { - original.call(camera, tickProgress, vertexConsumers, filterParticles(particles)); - } - - @Unique - private Queue filterParticles(Queue particles) { - return particles.stream().filter(particle -> - !NoRender.shouldOmitParticle(particle)).collect(Collectors.toCollection(LinkedList::new) - ); - } -} diff --git a/src/main/java/com/lambda/mixin/render/PlayerListHudMixin.java b/src/main/java/com/lambda/mixin/render/PlayerListHudMixin.java index be408d7ef..30f55c6a8 100644 --- a/src/main/java/com/lambda/mixin/render/PlayerListHudMixin.java +++ b/src/main/java/com/lambda/mixin/render/PlayerListHudMixin.java @@ -17,15 +17,29 @@ package com.lambda.mixin.render; +import com.lambda.friend.FriendManager; import com.lambda.module.modules.render.ExtraTab; +import com.lambda.util.text.TextBuilder; +import com.lambda.util.text.TextDslKt; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import kotlin.Unit; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.hud.PlayerListHud; import net.minecraft.client.network.PlayerListEntry; +import net.minecraft.scoreboard.Team; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Nullables; +import net.minecraft.world.GameMode; +import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Constant; import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyConstant; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import java.util.Comparator; @@ -34,6 +48,12 @@ @Mixin(PlayerListHud.class) public class PlayerListHudMixin { @Shadow @Final private static Comparator ENTRY_ORDERING; + @Unique private static final Comparator FRIENDS_FIRST_ENTRY_ORDERING = Comparator + .comparingInt((PlayerListEntry entry) -> FriendManager.INSTANCE.isFriend(entry.getProfile().name()) ? 0 : 1) + .thenComparingInt(entry -> -entry.getListOrder()) + .thenComparingInt((entry) -> entry.getGameMode() == GameMode.SPECTATOR ? 1 : 0) + .thenComparing((entry) -> Nullables.mapOrElse(entry.getScoreboardTeam(), Team::getName, "")) + .thenComparing((entry) -> entry.getProfile().name(), String::compareToIgnoreCase); @Shadow @Final private MinecraftClient client; @@ -45,9 +65,35 @@ private void onCollectPlayerEntriesHead(CallbackInfoReturnable !ExtraTab.getFriendsOnly() || FriendManager.INSTANCE.isFriend(entry.getProfile())) + .sorted(ExtraTab.getSortFriendsFirst() ? FRIENDS_FIRST_ENTRY_ORDERING : ENTRY_ORDERING) .limit(ExtraTab.getTabEntries()) .toList() ); } + + @ModifyConstant(method = "render", constant = @Constant(intValue = 20)) + private int modifyRowLimit(int original) { + return ExtraTab.INSTANCE.isEnabled() ? ExtraTab.getRows() : original; + } + + @ModifyExpressionValue(method = "getPlayerName", at = @At(value = "INVOKE", target = "Lnet/minecraft/text/Text;copy()Lnet/minecraft/text/MutableText;")) + private MutableText modifyPlayerName(MutableText original) { return modifyName(original); } + + @ModifyExpressionValue(method = "getPlayerName", at = @At(value = "INVOKE", target = "Lnet/minecraft/scoreboard/Team;decorateName(Lnet/minecraft/scoreboard/AbstractTeam;Lnet/minecraft/text/Text;)Lnet/minecraft/text/MutableText;")) + private MutableText modifyDecorateName(MutableText original) { return modifyName(original); } + + @Unique + private @Nullable MutableText modifyName(Text original) { + if (ExtraTab.INSTANCE.isDisabled() || + !ExtraTab.getHighlightFriends() || + !FriendManager.INSTANCE.isFriend(original.getString())) return original.copy(); + var newText = original.copy(); + var textBuilder = new TextBuilder(); + TextDslKt.color(textBuilder, ExtraTab.getFriendColor(), builder -> { + builder.styleAndAppend(newText); + return Unit.INSTANCE; + }); + return textBuilder.getText(); + } } diff --git a/src/main/java/com/lambda/mixin/render/RenderLayersMixin.java b/src/main/java/com/lambda/mixin/render/RenderLayersMixin.java index 0b4d622dd..96e786e33 100644 --- a/src/main/java/com/lambda/mixin/render/RenderLayersMixin.java +++ b/src/main/java/com/lambda/mixin/render/RenderLayersMixin.java @@ -19,28 +19,34 @@ import com.lambda.module.modules.render.XRay; import net.minecraft.block.BlockState; -import net.minecraft.client.render.RenderLayer; -import net.minecraft.client.render.RenderLayers; +import net.minecraft.client.render.BlockRenderLayer; +import net.minecraft.client.render.BlockRenderLayers; import net.minecraft.fluid.FluidState; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -@Mixin(RenderLayers.class) +/** + * Mixin to make blocks render as translucent for XRay functionality. + * + * Note: In 1.21.11, RenderLayers was split - BlockRenderLayers now handles + * block/fluid layer determination and returns BlockRenderLayer enum instead of RenderLayer. + */ +@Mixin(BlockRenderLayers.class) public class RenderLayersMixin { @Inject(method = "getBlockLayer", at = @At("HEAD"), cancellable = true) - private static void injectGetBlockLayer(BlockState state, CallbackInfoReturnable cir) { + private static void injectGetBlockLayer(BlockState state, CallbackInfoReturnable cir) { if (XRay.INSTANCE.isDisabled()) return; final var opacity = XRay.getOpacity(); if (opacity <= 0 || opacity >= 100) return; - if (!XRay.isSelected(state)) cir.setReturnValue(RenderLayer.getTranslucent()); + if (!XRay.isSelected(state)) cir.setReturnValue(BlockRenderLayer.TRANSLUCENT); } @Inject(method = "getFluidLayer", at = @At("HEAD"), cancellable = true) - private static void injectGetFluidLayer(FluidState state, CallbackInfoReturnable cir) { + private static void injectGetFluidLayer(FluidState state, CallbackInfoReturnable cir) { if (XRay.INSTANCE.isDisabled()) return; final var opacity = XRay.getOpacity(); - if (opacity > 0 && opacity < 100) cir.setReturnValue(RenderLayer.getTranslucent()); + if (opacity > 0 && opacity < 100) cir.setReturnValue(BlockRenderLayer.TRANSLUCENT); } } diff --git a/src/main/java/com/lambda/mixin/render/ScreenMixin.java b/src/main/java/com/lambda/mixin/render/ScreenMixin.java index 9c4972653..6ed4f2c53 100644 --- a/src/main/java/com/lambda/mixin/render/ScreenMixin.java +++ b/src/main/java/com/lambda/mixin/render/ScreenMixin.java @@ -25,6 +25,7 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.input.KeyInput; import org.lwjgl.glfw.GLFW; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -35,8 +36,8 @@ @Mixin(Screen.class) public class ScreenMixin { @Inject(method = "keyPressed", at = @At("HEAD"), cancellable = true) - private void onKeyPressed(int keyCode, int scanCode, int modifiers, CallbackInfoReturnable cir) { - if (keyCode == GLFW.GLFW_KEY_ESCAPE && QuickSearch.INSTANCE.isOpen()) { + private void onKeyPressed(KeyInput input, CallbackInfoReturnable cir) { + if (input.key() == GLFW.GLFW_KEY_ESCAPE && QuickSearch.INSTANCE.isOpen()) { QuickSearch.INSTANCE.close(); cir.setReturnValue(true); } diff --git a/src/main/java/com/lambda/mixin/render/SodiumWorldRendererMixin.java b/src/main/java/com/lambda/mixin/render/SodiumWorldRendererMixin.java new file mode 100644 index 000000000..892984062 --- /dev/null +++ b/src/main/java/com/lambda/mixin/render/SodiumWorldRendererMixin.java @@ -0,0 +1,39 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.mixin.render; + +import com.lambda.module.modules.render.NoRender; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import net.caffeinemc.mods.sodium.client.render.SodiumWorldRenderer; +import net.caffeinemc.mods.sodium.client.util.FogParameters; +import org.objectweb.asm.Opcodes; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(SodiumWorldRenderer.class) +public class SodiumWorldRendererMixin { + @Unique + private final FogParameters NO_FOG = new FogParameters(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f); + + @ModifyExpressionValue(method = "drawChunkLayer(Lnet/minecraft/client/render/BlockRenderLayerGroup;Lnet/caffeinemc/mods/sodium/client/render/chunk/ChunkRenderMatrices;DDDLnet/minecraft/client/gl/GpuSampler;)V", at = @At(value = "FIELD", target = "Lnet/caffeinemc/mods/sodium/client/render/SodiumWorldRenderer;lastFogParameters:Lnet/caffeinemc/mods/sodium/client/util/FogParameters;", opcode = Opcodes.GETFIELD)) + private FogParameters modifyFogParameters(FogParameters original) { + if (NoRender.INSTANCE.isEnabled() && NoRender.getNoTerrainFog()) return NO_FOG; + return original; + } +} diff --git a/src/main/java/com/lambda/mixin/render/WorldBorderRenderingMixin.java b/src/main/java/com/lambda/mixin/render/WorldBorderRenderingMixin.java index a5e6f9269..9553a39a5 100644 --- a/src/main/java/com/lambda/mixin/render/WorldBorderRenderingMixin.java +++ b/src/main/java/com/lambda/mixin/render/WorldBorderRenderingMixin.java @@ -19,6 +19,7 @@ import com.lambda.module.modules.render.NoRender; import net.minecraft.client.render.WorldBorderRendering; +import net.minecraft.client.render.state.WorldBorderRenderState; import net.minecraft.util.math.Vec3d; import net.minecraft.world.border.WorldBorder; import org.spongepowered.asm.mixin.Mixin; @@ -29,7 +30,7 @@ @Mixin(WorldBorderRendering.class) public class WorldBorderRenderingMixin { @Inject(method = "render", at = @At("HEAD"), cancellable = true) - private void injectRender(WorldBorder border, Vec3d cameraPos, double viewDistanceBlocks, double farPlaneDistance, CallbackInfo ci) { + private void injectRender(WorldBorderRenderState state, Vec3d cameraPos, double viewDistanceBlocks, double farPlaneDistance, CallbackInfo ci) { if (NoRender.INSTANCE.isEnabled() && NoRender.getNoWorldBorder()) ci.cancel(); } } diff --git a/src/main/java/com/lambda/mixin/render/WorldRendererMixin.java b/src/main/java/com/lambda/mixin/render/WorldRendererMixin.java index d8eb33e08..313cb6ddd 100644 --- a/src/main/java/com/lambda/mixin/render/WorldRendererMixin.java +++ b/src/main/java/com/lambda/mixin/render/WorldRendererMixin.java @@ -34,23 +34,6 @@ @Mixin(WorldRenderer.class) public class WorldRendererMixin { - @ModifyExpressionValue(method = "getEntitiesToRender(Lnet/minecraft/client/render/Camera;Lnet/minecraft/client/render/Frustum;Ljava/util/List;)Z", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/Camera;isThirdPerson()Z")) - private boolean renderIsThirdPerson(boolean original) { - return Freecam.INSTANCE.isEnabled() || original; - } - - @ModifyArg(method = "render(Lnet/minecraft/client/util/ObjectAllocator;Lnet/minecraft/client/render/RenderTickCounter;ZLnet/minecraft/client/render/Camera;Lnet/minecraft/client/render/GameRenderer;Lorg/joml/Matrix4f;Lorg/joml/Matrix4f;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/WorldRenderer;setupTerrain(Lnet/minecraft/client/render/Camera;Lnet/minecraft/client/render/Frustum;ZZ)V"), index = 3) - private boolean renderSetupTerrainModifyArg(boolean hasForcedFrustum) { - return Freecam.INSTANCE.isEnabled() || CameraTweaks.INSTANCE.isEnabled() || hasForcedFrustum; - } - - @ModifyArg(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/BackgroundRenderer;applyFog(Lnet/minecraft/client/render/Camera;Lnet/minecraft/client/render/BackgroundRenderer$FogType;Lorg/joml/Vector4f;FZF)Lnet/minecraft/client/render/Fog;", ordinal = 0), index = 3) - private float modifyApplyFogRenderDistance(float viewDistance) { - return NoRender.INSTANCE.isEnabled() && NoRender.getNoTerrainFog() - ? Float.MAX_VALUE - : viewDistance; - } - @Inject(method = "hasBlindnessOrDarkness(Lnet/minecraft/client/render/Camera;)Z", at = @At(value = "HEAD"), cancellable = true) private void modifyEffectCheck(Camera camera, CallbackInfoReturnable cir) { Entity entity = camera.getFocusedEntity(); @@ -60,4 +43,14 @@ private void modifyEffectCheck(Camera camera, CallbackInfoReturnable ci cir.setReturnValue(blind || dark); } } + + @ModifyArg(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/WorldRenderer;updateCamera(Lnet/minecraft/client/render/Camera;Lnet/minecraft/client/render/Frustum;Z)V"), index = 2) + private boolean renderSetupTerrainModifyArg(boolean spectator) { + return Freecam.INSTANCE.isEnabled() || CameraTweaks.INSTANCE.isEnabled() || spectator; + } + + @ModifyExpressionValue(method = "fillEntityRenderStates", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/Camera;isThirdPerson()Z")) + private boolean modifyIsThirdPerson(boolean original) { + return Freecam.INSTANCE.isEnabled() || original; + } } diff --git a/src/main/java/com/lambda/mixin/render/AbstractSignBlockEntityRendererMixin.java b/src/main/java/com/lambda/mixin/render/blockentity/AbstractSignBlockEntityRendererMixin.java similarity index 77% rename from src/main/java/com/lambda/mixin/render/AbstractSignBlockEntityRendererMixin.java rename to src/main/java/com/lambda/mixin/render/blockentity/AbstractSignBlockEntityRendererMixin.java index 6f1d4c5c5..9d6232c03 100644 --- a/src/main/java/com/lambda/mixin/render/AbstractSignBlockEntityRendererMixin.java +++ b/src/main/java/com/lambda/mixin/render/blockentity/AbstractSignBlockEntityRendererMixin.java @@ -15,14 +15,13 @@ * along with this program. If not, see . */ -package com.lambda.mixin.render; +package com.lambda.mixin.render.blockentity; import com.lambda.module.modules.render.NoRender; -import net.minecraft.block.entity.SignText; -import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.block.entity.AbstractSignBlockEntityRenderer; +import net.minecraft.client.render.block.entity.state.SignBlockEntityRenderState; +import net.minecraft.client.render.command.OrderedRenderCommandQueue; import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.util.math.BlockPos; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -31,7 +30,7 @@ @Mixin(AbstractSignBlockEntityRenderer.class) public class AbstractSignBlockEntityRendererMixin { @Inject(method = "renderText", at = @At("HEAD"), cancellable = true) - private void injectRenderText(BlockPos pos, SignText text, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int textLineHeight, int maxTextWidth, boolean front, CallbackInfo ci) { + private void injectRenderText(SignBlockEntityRenderState renderState, MatrixStack matrices, OrderedRenderCommandQueue queue, boolean front, CallbackInfo ci) { if (NoRender.INSTANCE.isEnabled() && NoRender.getNoSignText()) ci.cancel(); } } diff --git a/src/main/java/com/lambda/mixin/render/BeaconBlockEntityRendererMixin.java b/src/main/java/com/lambda/mixin/render/blockentity/BeaconBlockEntityRendererMixin.java similarity index 61% rename from src/main/java/com/lambda/mixin/render/BeaconBlockEntityRendererMixin.java rename to src/main/java/com/lambda/mixin/render/blockentity/BeaconBlockEntityRendererMixin.java index a3d4b5e38..8ad9a6d83 100644 --- a/src/main/java/com/lambda/mixin/render/BeaconBlockEntityRendererMixin.java +++ b/src/main/java/com/lambda/mixin/render/blockentity/BeaconBlockEntityRendererMixin.java @@ -15,14 +15,14 @@ * along with this program. If not, see . */ -package com.lambda.mixin.render; +package com.lambda.mixin.render.blockentity; import com.lambda.module.modules.render.NoRender; -import net.minecraft.block.entity.BlockEntity; -import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.block.entity.BeaconBlockEntityRenderer; +import net.minecraft.client.render.block.entity.state.BeaconBlockEntityRenderState; +import net.minecraft.client.render.command.OrderedRenderCommandQueue; +import net.minecraft.client.render.state.CameraRenderState; import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.util.math.Vec3d; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -30,8 +30,8 @@ @Mixin(BeaconBlockEntityRenderer.class) public class BeaconBlockEntityRendererMixin { - @Inject(method = "render", at = @At("HEAD"), cancellable = true) - private void injectRender(BlockEntity entity, float tickProgress, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay, Vec3d cameraPos, CallbackInfo ci) { + @Inject(method = "render(Lnet/minecraft/client/render/block/entity/state/BeaconBlockEntityRenderState;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/command/OrderedRenderCommandQueue;Lnet/minecraft/client/render/state/CameraRenderState;)V", at = @At("HEAD"), cancellable = true) + private void injectRender(BeaconBlockEntityRenderState beaconBlockEntityRenderState, MatrixStack matrixStack, OrderedRenderCommandQueue orderedRenderCommandQueue, CameraRenderState cameraRenderState, CallbackInfo ci) { if (NoRender.INSTANCE.isEnabled() && NoRender.getNoBeaconBeams()) ci.cancel(); } } diff --git a/src/main/java/com/lambda/mixin/render/BlockEntityRenderDispatcherMixin.java b/src/main/java/com/lambda/mixin/render/blockentity/BlockEntityRenderDispatcherMixin.java similarity index 52% rename from src/main/java/com/lambda/mixin/render/BlockEntityRenderDispatcherMixin.java rename to src/main/java/com/lambda/mixin/render/blockentity/BlockEntityRenderDispatcherMixin.java index 268b07043..784faeee0 100644 --- a/src/main/java/com/lambda/mixin/render/BlockEntityRenderDispatcherMixin.java +++ b/src/main/java/com/lambda/mixin/render/blockentity/BlockEntityRenderDispatcherMixin.java @@ -15,24 +15,33 @@ * along with this program. If not, see . */ -package com.lambda.mixin.render; +package com.lambda.mixin.render.blockentity; import com.lambda.module.modules.render.NoRender; import net.minecraft.block.entity.BlockEntity; -import net.minecraft.client.render.VertexConsumerProvider; -import net.minecraft.client.render.block.entity.BlockEntityRenderDispatcher; -import net.minecraft.client.render.block.entity.BlockEntityRenderer; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.util.math.Vec3d; +import net.minecraft.client.render.block.entity.BlockEntityRenderManager; +import net.minecraft.client.render.block.entity.state.BlockEntityRenderState; +import net.minecraft.client.render.command.ModelCommandRenderer; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.jspecify.annotations.Nullable; -@Mixin(BlockEntityRenderDispatcher.class) +/** + * Mixin to disable block entity rendering when NoRender is enabled. + * + * Note: In 1.21.11, BlockEntityRenderDispatcher was renamed to BlockEntityRenderManager + * and uses a render state system. Returning null from getRenderState prevents rendering. + */ +@Mixin(BlockEntityRenderManager.class) public class BlockEntityRenderDispatcherMixin { - @Inject(method = "render(Lnet/minecraft/client/render/block/entity/BlockEntityRenderer;Lnet/minecraft/block/entity/BlockEntity;FLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;Lnet/minecraft/util/math/Vec3d;)V", at = @At("HEAD"), cancellable = true) - private static void injectRender(BlockEntityRenderer renderer, BlockEntity blockEntity, float tickProgress, MatrixStack matrices, VertexConsumerProvider vertexConsumers, Vec3d cameraPos, CallbackInfo ci) { - if (NoRender.shouldOmitBlockEntity(blockEntity)) ci.cancel(); + @Inject(method = "getRenderState", at = @At("HEAD"), cancellable = true) + private void injectGetRenderState( + E blockEntity, float tickProgress, ModelCommandRenderer.@Nullable CrumblingOverlayCommand crumblingOverlay, + CallbackInfoReturnable cir) { + if (NoRender.shouldOmitBlockEntity(blockEntity)) { + cir.setReturnValue(null); + } } } diff --git a/src/main/java/com/lambda/mixin/render/EnchantingTableBlockEntityRendererMixin.java b/src/main/java/com/lambda/mixin/render/blockentity/EnchantingTableBlockEntityRendererMixin.java similarity index 51% rename from src/main/java/com/lambda/mixin/render/EnchantingTableBlockEntityRendererMixin.java rename to src/main/java/com/lambda/mixin/render/blockentity/EnchantingTableBlockEntityRendererMixin.java index f80ca373d..f0e5b02dd 100644 --- a/src/main/java/com/lambda/mixin/render/EnchantingTableBlockEntityRendererMixin.java +++ b/src/main/java/com/lambda/mixin/render/blockentity/EnchantingTableBlockEntityRendererMixin.java @@ -15,21 +15,23 @@ * along with this program. If not, see . */ -package com.lambda.mixin.render; +package com.lambda.mixin.render.blockentity; import com.lambda.module.modules.render.NoRender; -import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; -import net.minecraft.client.render.VertexConsumer; import net.minecraft.client.render.block.entity.EnchantingTableBlockEntityRenderer; -import net.minecraft.client.render.entity.model.BookModel; +import net.minecraft.client.render.block.entity.state.EnchantingTableBlockEntityRenderState; +import net.minecraft.client.render.command.OrderedRenderCommandQueue; +import net.minecraft.client.render.state.CameraRenderState; import net.minecraft.client.util.math.MatrixStack; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(EnchantingTableBlockEntityRenderer.class) public class EnchantingTableBlockEntityRendererMixin { - @WrapWithCondition(method = "render(Lnet/minecraft/block/entity/EnchantingTableBlockEntity;FLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IILnet/minecraft/util/math/Vec3d;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/entity/model/BookModel;render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumer;II)V")) - private boolean wrapRender(BookModel instance, MatrixStack matrixStack, VertexConsumer vertexConsumer, int i, int j) { - return NoRender.INSTANCE.isDisabled() || !NoRender.getNoEnchantingTableBook(); + @Inject(method = "render(Lnet/minecraft/client/render/block/entity/state/EnchantingTableBlockEntityRenderState;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/command/OrderedRenderCommandQueue;Lnet/minecraft/client/render/state/CameraRenderState;)V", at = @At("HEAD"), cancellable = true) + private void injectRender(EnchantingTableBlockEntityRenderState renderState, MatrixStack matrices, OrderedRenderCommandQueue queue, CameraRenderState cameraRenderState, CallbackInfo ci) { + if (NoRender.INSTANCE.isEnabled() && NoRender.getNoEnchantingTableBook()) ci.cancel(); } } diff --git a/src/main/java/com/lambda/mixin/render/MobSpawnerBlockEntityRendererMixin.java b/src/main/java/com/lambda/mixin/render/blockentity/MobSpawnerBlockEntityRendererMixin.java similarity index 66% rename from src/main/java/com/lambda/mixin/render/MobSpawnerBlockEntityRendererMixin.java rename to src/main/java/com/lambda/mixin/render/blockentity/MobSpawnerBlockEntityRendererMixin.java index e2233bf31..f47e9d2c0 100644 --- a/src/main/java/com/lambda/mixin/render/MobSpawnerBlockEntityRendererMixin.java +++ b/src/main/java/com/lambda/mixin/render/blockentity/MobSpawnerBlockEntityRendererMixin.java @@ -15,14 +15,15 @@ * along with this program. If not, see . */ -package com.lambda.mixin.render; +package com.lambda.mixin.render.blockentity; import com.lambda.module.modules.render.NoRender; -import net.minecraft.block.entity.MobSpawnerBlockEntity; -import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.block.entity.MobSpawnerBlockEntityRenderer; +import net.minecraft.client.render.command.OrderedRenderCommandQueue; +import net.minecraft.client.render.entity.EntityRenderManager; +import net.minecraft.client.render.entity.state.EntityRenderState; +import net.minecraft.client.render.state.CameraRenderState; import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.util.math.Vec3d; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -30,8 +31,8 @@ @Mixin(MobSpawnerBlockEntityRenderer.class) public class MobSpawnerBlockEntityRendererMixin { - @Inject(method = "render(Lnet/minecraft/block/entity/MobSpawnerBlockEntity;FLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IILnet/minecraft/util/math/Vec3d;)V", at = @At("HEAD"), cancellable = true) - private void injectRender(MobSpawnerBlockEntity mobSpawnerBlockEntity, float f, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, int j, Vec3d vec3d, CallbackInfo ci) { + @Inject(method = "renderDisplayEntity", at = @At("HEAD"), cancellable = true) + private static void injectRender(MatrixStack matrices, OrderedRenderCommandQueue queue, EntityRenderState state, EntityRenderManager entityRenderDispatcher, float rotation, float scale, CameraRenderState cameraRenderState, CallbackInfo ci) { if (NoRender.INSTANCE.isEnabled() && NoRender.getNoSpawnerMob()) ci.cancel(); } } diff --git a/src/main/java/com/lambda/mixin/render/particle/BillboardParticleMixin.java b/src/main/java/com/lambda/mixin/render/particle/BillboardParticleMixin.java new file mode 100644 index 000000000..06faa1a5d --- /dev/null +++ b/src/main/java/com/lambda/mixin/render/particle/BillboardParticleMixin.java @@ -0,0 +1,36 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.mixin.render.particle; + +import com.lambda.module.modules.render.NoRender; +import net.minecraft.client.particle.BillboardParticle; +import net.minecraft.client.particle.BillboardParticleSubmittable; +import net.minecraft.client.particle.Particle; +import net.minecraft.client.render.Camera; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(BillboardParticle.class) +public class BillboardParticleMixin { + @Inject(method = "render(Lnet/minecraft/client/particle/BillboardParticleSubmittable;Lnet/minecraft/client/render/Camera;F)V", at = @At("HEAD"), cancellable = true) + private void injectRender(BillboardParticleSubmittable submittable, Camera camera, float tickDelta, CallbackInfo ci) { + if (NoRender.shouldOmitParticle((Particle) ((Object) this))) ci.cancel(); + } +} diff --git a/src/main/java/com/lambda/mixin/render/particle/ElderGuardianParticleRendererMixin.java b/src/main/java/com/lambda/mixin/render/particle/ElderGuardianParticleRendererMixin.java new file mode 100644 index 000000000..cc7b2e818 --- /dev/null +++ b/src/main/java/com/lambda/mixin/render/particle/ElderGuardianParticleRendererMixin.java @@ -0,0 +1,38 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.mixin.render.particle; + +import com.lambda.module.modules.render.NoRender; +import net.minecraft.client.particle.ElderGuardianParticle; +import net.minecraft.client.particle.ElderGuardianParticleRenderer; +import net.minecraft.client.particle.NoRenderParticleRenderer; +import net.minecraft.client.render.Camera; +import net.minecraft.client.render.Frustum; +import net.minecraft.client.render.Submittable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ElderGuardianParticleRenderer.class) +public class ElderGuardianParticleRendererMixin { + @Inject(method = "render", at = @At("HEAD"), cancellable = true) + private void injectRender(Frustum frustum, Camera camera, float tickProgress, CallbackInfoReturnable cir) { + if (NoRender.shouldOmitParticle(ElderGuardianParticle.class)) cir.setReturnValue(NoRenderParticleRenderer.EMPTY); + } +} diff --git a/src/main/java/com/lambda/mixin/render/particle/ItemPickupParticleRendererMixin.java b/src/main/java/com/lambda/mixin/render/particle/ItemPickupParticleRendererMixin.java new file mode 100644 index 000000000..509b3323c --- /dev/null +++ b/src/main/java/com/lambda/mixin/render/particle/ItemPickupParticleRendererMixin.java @@ -0,0 +1,38 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.mixin.render.particle; + +import com.lambda.module.modules.render.NoRender; +import net.minecraft.client.particle.ItemPickupParticle; +import net.minecraft.client.particle.ItemPickupParticleRenderer; +import net.minecraft.client.particle.NoRenderParticleRenderer; +import net.minecraft.client.render.Camera; +import net.minecraft.client.render.Frustum; +import net.minecraft.client.render.Submittable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ItemPickupParticleRenderer.class) +public class ItemPickupParticleRendererMixin { + @Inject(method = "render", at = @At("HEAD"), cancellable = true) + private void injectRender(Frustum frustum, Camera camera, float tickProgress, CallbackInfoReturnable cir) { + if (NoRender.shouldOmitParticle(ItemPickupParticle.class)) cir.setReturnValue(NoRenderParticleRenderer.EMPTY); + } +} diff --git a/src/main/java/com/lambda/mixin/world/ClientWorldMixin.java b/src/main/java/com/lambda/mixin/world/ClientWorldMixin.java index d98e1efb3..5b86cf797 100644 --- a/src/main/java/com/lambda/mixin/world/ClientWorldMixin.java +++ b/src/main/java/com/lambda/mixin/world/ClientWorldMixin.java @@ -46,21 +46,21 @@ private void onRemoveEntity(int entityId, Entity.RemovalReason removalReason, Ca EventFlow.post(new EntityEvent.Removal(entity, removalReason)); } - @ModifyReturnValue(method = "getCloudsColor", at = @At("RETURN")) - private int modifyGetCloudsColor(int original) { - if (WorldColors.INSTANCE.isEnabled() && WorldColors.getCustomClouds()) { - return WorldColors.getCloudColor().getRGB() & 0xFFFFFF; - } - return original; - } - - @ModifyReturnValue(method = "getSkyColor", at = @At("RETURN")) - private int modifyGetSkyColor(int original) { - if (WorldColors.INSTANCE.isEnabled() && WorldColors.getCustomSky()) { - return WorldColors.getSkyColor().getRGB() & 0xFFFFFF; - } - return original; - } +// @ModifyReturnValue(method = "getCloudsColor", at = @At("RETURN")) +// private int modifyGetCloudsColor(int original) { +// if (WorldColors.INSTANCE.isEnabled() && WorldColors.getCustomClouds()) { +// return WorldColors.getCloudColor().getRGB() & 0xFFFFFF; +// } +// return original; +// } +// +// @ModifyReturnValue(method = "getSkyColor", at = @At("RETURN")) +// private int modifyGetSkyColor(int original) { +// if (WorldColors.INSTANCE.isEnabled() && WorldColors.getCustomSky()) { +// return WorldColors.getSkyColor().getRGB() & 0xFFFFFF; +// } +// return original; +// } @Inject(method = "handleBlockUpdate", at = @At("HEAD"), cancellable = true) diff --git a/src/main/java/com/lambda/mixin/world/DirectionMixin.java b/src/main/java/com/lambda/mixin/world/DirectionMixin.java new file mode 100644 index 000000000..b3e1c770a --- /dev/null +++ b/src/main/java/com/lambda/mixin/world/DirectionMixin.java @@ -0,0 +1,40 @@ +/* + * Copyright 2026 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.mixin.world; + +import com.lambda.interaction.managers.rotating.RotationManager; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import net.minecraft.entity.Entity; +import net.minecraft.util.math.Direction; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import static com.lambda.Lambda.getMc; + +@Mixin(Direction.class) +public class DirectionMixin { + @ModifyExpressionValue(method = "getEntityFacingOrder", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;getYaw(F)F")) + private static float modifyGetYaw(float original, Entity entity) { + return entity == getMc().player ? RotationManager.getServerRotation().getYawF() : original; + } + + @ModifyExpressionValue(method = "getEntityFacingOrder", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;getPitch(F)F")) + private static float modifyGetPitch(float original, Entity entity) { + return entity == getMc().player ? RotationManager.getServerRotation().getPitchF() : original; + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/Lambda.kt b/src/main/kotlin/com/lambda/Lambda.kt index c8242977d..d90bbbaad 100644 --- a/src/main/kotlin/com/lambda/Lambda.kt +++ b/src/main/kotlin/com/lambda/Lambda.kt @@ -27,6 +27,7 @@ import com.lambda.config.serializer.ItemCodec import com.lambda.config.serializer.ItemStackCodec import com.lambda.config.serializer.KeyCodeCodec import com.lambda.config.serializer.OptionalCodec +import com.lambda.config.serializer.TextCodec import com.lambda.core.Loader import com.lambda.event.events.ClientEvent import com.lambda.event.listener.UnsafeListener.Companion.listenOnceUnsafe @@ -79,7 +80,7 @@ object Lambda : ClientModInitializer { .registerTypeAdapter(GameProfile::class.java, GameProfileCodec) .registerTypeAdapter(Optional::class.java, OptionalCodec) .registerTypeAdapter(ItemStack::class.java, ItemStackCodec) - .registerTypeAdapter(Text::class.java, Text.Serializer(DynamicRegistryManager.EMPTY)) + .registerTypeAdapter(Text::class.java, TextCodec) // ToDo: Find out if needed .registerTypeAdapter(Item::class.java, ItemCodec) .registerTypeAdapter(BlockItem::class.java, ItemCodec) .registerTypeAdapter(ArrowItem::class.java, ItemCodec) diff --git a/src/main/kotlin/com/lambda/brigadier/argument/PlayerArguments.kt b/src/main/kotlin/com/lambda/brigadier/argument/PlayerArguments.kt index 7c6ef926b..bdaae0d48 100644 --- a/src/main/kotlin/com/lambda/brigadier/argument/PlayerArguments.kt +++ b/src/main/kotlin/com/lambda/brigadier/argument/PlayerArguments.kt @@ -35,6 +35,7 @@ import net.minecraft.command.argument.EntityArgumentType import net.minecraft.command.argument.GameProfileArgumentType import net.minecraft.command.argument.TeamArgumentType import net.minecraft.scoreboard.Team +import net.minecraft.server.PlayerConfigEntry import net.minecraft.server.command.ServerCommandSource import net.minecraft.server.network.ServerPlayerEntity @@ -69,7 +70,7 @@ fun ArgumentReader< DefaultArgumentDescriptor< GameProfileArgumentType > - >.value(): Collection { + >.value(): Collection { return GameProfileArgumentType.getProfileArgument(context, name) } diff --git a/src/main/kotlin/com/lambda/command/CommandManager.kt b/src/main/kotlin/com/lambda/command/CommandManager.kt index 2344c4259..960d83a92 100644 --- a/src/main/kotlin/com/lambda/command/CommandManager.kt +++ b/src/main/kotlin/com/lambda/command/CommandManager.kt @@ -73,7 +73,7 @@ object CommandManager { canRead() && peek() == prefix } - fun currentDispatcher(message: String): CommandDispatcher { + fun currentDispatcher(message: String): CommandDispatcher { return if (message.isLambdaCommand()) { dispatcher } else { diff --git a/src/main/kotlin/com/lambda/command/commands/FriendCommand.kt b/src/main/kotlin/com/lambda/command/commands/FriendCommand.kt index 37384b666..45545877d 100644 --- a/src/main/kotlin/com/lambda/command/commands/FriendCommand.kt +++ b/src/main/kotlin/com/lambda/command/commands/FriendCommand.kt @@ -42,12 +42,12 @@ import java.awt.Color object FriendCommand : LambdaCommand( name = "friends", - usage = "friends ", + usage = "friends | add-uuid | remove >", description = "Add or remove a friend" ) { override fun CommandBuilder.create() { execute { - this@FriendCommand.info( + info( buildText { if (FriendManager.friends.isEmpty()) { literal("You have no friends yet. Go make some! :3\n") @@ -55,7 +55,13 @@ object FriendCommand : LambdaCommand( literal("Your friends (${FriendManager.friends.size}):\n") FriendManager.friends.forEachIndexed { index, gameProfile -> - literal(" ${index + 1}. ${gameProfile.name}\n") + literal(" ${index + 1}. ${gameProfile.name} ") + styled( + color = Color.RED, + clickEvent = ClickEvents.suggestCommand(";friends remove ${gameProfile.name}") + ) { + literal("x\n") + } } } @@ -84,11 +90,15 @@ object FriendCommand : LambdaCommand( } executeWithResult { - runBlocking { - val name = player().value() + val name = player().value() + + if (FriendManager.isFriend(name)) + return@executeWithResult failure("This player is already in your friend list") - if (mc.gameProfile.name == name) return@runBlocking failure("You can't befriend yourself") + if (mc.gameProfile.name == name) + return@executeWithResult failure("You can't befriend yourself") + runBlocking { val profile = mc.networkHandler ?.playerList ?.map { it.profile } @@ -96,16 +106,16 @@ object FriendCommand : LambdaCommand( ?: getProfile(name) .getOrElse { return@runBlocking failure("Could not find the player") } - return@runBlocking if (FriendManager.befriend(profile)) { - info(FriendManager.befriendedText(profile.name)) - success() - } else { - failure("This player is already in your friend list") - } + FriendManager.befriend(profile) + + info(FriendManager.befriendedText(profile.name)) + success() } } } + } + required(literal("add-uuid")) { required(uuid("player uuid")) { player -> suggests { _, builder -> mc.networkHandler @@ -118,11 +128,15 @@ object FriendCommand : LambdaCommand( } executeWithResult { - runBlocking { - val uuid = player().value() + val uuid = player().value() - if (mc.gameProfile.id == uuid) return@runBlocking failure("You can't befriend yourself") + if (FriendManager.isFriend(uuid)) + return@executeWithResult failure("This player is already in your friend list") + if (mc.gameProfile.id == uuid) + return@executeWithResult failure("You can't befriend yourself") + + runBlocking { val profile = mc.networkHandler ?.playerList ?.map { it.profile } @@ -130,12 +144,10 @@ object FriendCommand : LambdaCommand( ?: getProfile(uuid) .getOrElse { return@runBlocking failure("Could not find the player") } - return@runBlocking if (FriendManager.befriend(profile)) { - this@FriendCommand.info(FriendManager.befriendedText(profile.name)) - success() - } else { - failure("This player is already in your friend list") - } + FriendManager.befriend(profile) + + info(FriendManager.befriendedText(profile.name)) + success() } } } @@ -155,12 +167,10 @@ object FriendCommand : LambdaCommand( val profile = FriendManager.gameProfile(name) ?: return@executeWithResult failure("This player is not in your friend list") - return@executeWithResult if (FriendManager.unfriend(profile)) { - this@FriendCommand.info(FriendManager.unfriendedText(name)) - success() - } else { - failure("This player is not in your friend list") - } + FriendManager.unfriend(profile) + + info(FriendManager.unfriendedText(name)) + success() } } } diff --git a/src/main/kotlin/com/lambda/command/commands/PrefixCommand.kt b/src/main/kotlin/com/lambda/command/commands/PrefixCommand.kt new file mode 100644 index 000000000..57444d190 --- /dev/null +++ b/src/main/kotlin/com/lambda/command/commands/PrefixCommand.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.command.commands + +import com.lambda.brigadier.CommandResult.Companion.failure +import com.lambda.brigadier.CommandResult.Companion.success +import com.lambda.brigadier.argument.greedyString +import com.lambda.brigadier.argument.string +import com.lambda.brigadier.argument.value +import com.lambda.brigadier.execute +import com.lambda.brigadier.executeWithResult +import com.lambda.brigadier.required +import com.lambda.command.CommandRegistry +import com.lambda.command.LambdaCommand +import com.lambda.config.Configuration +import com.lambda.config.Setting +import com.lambda.util.Communication.info +import com.lambda.util.extension.CommandBuilder +import com.lambda.util.text.buildText +import com.lambda.util.text.literal + +object PrefixCommand : LambdaCommand( + "prefix", + usage = "prefix ", + description = "Sets the prefix for Lambda commands. If the prefix does not seem to work, try putting it in double quotes." +) { + // i have no idea why someone would want to use some of these as a prefix + // but ig the people who run 20 clients at once could benefit from this + val ptrn = Regex("^[!\"#$%&'()*+,\\-./:;<=>?@\\[\\\\\\]^_`{|}~]$") + + override fun CommandBuilder.create() { + required(greedyString("prefix")) { prefixStr -> + executeWithResult { + val prefix = prefixStr().value() + if (!ptrn.matches(prefix)) { + return@executeWithResult failure("Prefix must be a single non-alphanumeric ASCII character, excluding spaces.") + } + val prefixChar = prefix.first() + val configurable = Configuration.configurableByName("command") ?: return@executeWithResult failure("No command configurable found.") + val setting = configurable.settings.find { it.name == "prefix" } as? Setting<*, Char> + ?: return@executeWithResult failure("Prefix setting is not a Char or can not be found.") + setting.trySetValue(prefixChar) + return@executeWithResult success() + } + } + + execute { + info( + buildText { + literal("The prefix is currently: ${CommandRegistry.prefix}") + } + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt index 6172879df..3df05168a 100644 --- a/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt +++ b/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt @@ -32,9 +32,9 @@ import com.lambda.interaction.material.StackSelection.Companion.selectStack import com.lambda.interaction.material.container.ContainerManager import com.lambda.interaction.material.container.ContainerManager.findContainersWithMaterial import com.lambda.interaction.material.container.ContainerManager.findContainersWithSpace -import com.lambda.interaction.material.transfer.TransferResult -import com.lambda.task.RootTask.run -import com.lambda.threading.runSafe +import com.lambda.task.RootTask +import com.lambda.task.Task +import com.lambda.threading.runSafeAutomated import com.lambda.util.Communication.info import com.lambda.util.extension.CommandBuilder @@ -43,7 +43,7 @@ object TransferCommand : LambdaCommand( usage = "transfer ", description = "Transfer items from anywhere to anywhere", ) { - private var lastContainerTransfer: TransferResult.ContainerTransfer? = null + private var lastContainerTransfer: Task<*>? = null override fun CommandBuilder.create() { required(itemStack("stack", registry)) { stack -> @@ -54,12 +54,10 @@ object TransferCommand : LambdaCommand( val selection = selectStack(count) { isItem(stack(ctx).value().item) } - with(AutomationConfig.Companion.DEFAULT) { - runSafe { - selection.findContainersWithMaterial().forEachIndexed { i, container -> - builder.suggest("\"${i + 1}. ${container.name}\"", container.description(selection)) - } - } + AutomationConfig.Companion.DEFAULT.runSafeAutomated { + selection.findContainersWithMaterial().forEachIndexed { i, container -> + builder.suggest("\"${i + 1}. ${container.name}\"", container.description(selection)) + } } builder.buildFuture() } @@ -68,11 +66,9 @@ object TransferCommand : LambdaCommand( val selection = selectStack(amount(ctx).value()) { isItem(stack(ctx).value().item) } - with(AutomationConfig.Companion.DEFAULT) { - runSafe { - findContainersWithSpace(selection).forEachIndexed { i, container -> - builder.suggest("\"${i + 1}. ${container.name}\"", container.description(selection)) - } + AutomationConfig.Companion.DEFAULT.runSafeAutomated { + selection.findContainersWithSpace().forEachIndexed { i, container -> + builder.suggest("\"${i + 1}. ${container.name}\"", container.description(selection)) } } builder.buildFuture() @@ -81,35 +77,17 @@ object TransferCommand : LambdaCommand( val selection = selectStack(amount().value()) { isItem(stack().value().item) } - val fromContainer = ContainerManager.containers().find { - it.name == from().value().split(".").last().trim() - } ?: return@executeWithResult failure("From container not found") + AutomationConfig.Companion.DEFAULT.runSafeAutomated { + val fromContainer = ContainerManager.containers().find { + it.name == from().value().split(".").last().trim() + } ?: return@executeWithResult failure("From container not found") - val toContainer = ContainerManager.containers().find { - it.name == to().value().split(".").last().trim() - } ?: return@executeWithResult failure("To container not found") + val toContainer = ContainerManager.containers().find { + it.name == to().value().split(".").last().trim() + } ?: return@executeWithResult failure("To container not found") - with(AutomationConfig.Companion.DEFAULT) { - when (val transaction = fromContainer.transfer(selection, toContainer)) { - is TransferResult.ContainerTransfer -> { - info("${transaction.name} started.") - lastContainerTransfer = transaction - transaction.finally { - info("${transaction.name} completed.") - }.run() - return@executeWithResult success() - } - - is TransferResult.MissingItems -> { - return@executeWithResult failure("Missing items: ${transaction.missing}") - } - - is TransferResult.NoSpace -> { - return@executeWithResult failure("No space in ${toContainer.name}") - } - } + fromContainer.transferByTask(selection, toContainer).execute(RootTask) } - return@executeWithResult success() } } diff --git a/src/main/kotlin/com/lambda/config/AutomationConfig.kt b/src/main/kotlin/com/lambda/config/AutomationConfig.kt index 56e986838..c32f0677f 100644 --- a/src/main/kotlin/com/lambda/config/AutomationConfig.kt +++ b/src/main/kotlin/com/lambda/config/AutomationConfig.kt @@ -85,9 +85,9 @@ open class AutomationConfig( var drawables = listOf() init { - onStaticRender { - if (renders) - with(it) { drawables.forEach { with(it) { buildRenderer() } } } + onStaticRender { esp -> + if (renders) + drawables.forEach { it.render(esp) } } } } diff --git a/src/main/kotlin/com/lambda/config/ConfigEditor.kt b/src/main/kotlin/com/lambda/config/ConfigEditor.kt index 4a9e168b9..2e74a71b1 100644 --- a/src/main/kotlin/com/lambda/config/ConfigEditor.kt +++ b/src/main/kotlin/com/lambda/config/ConfigEditor.kt @@ -38,11 +38,11 @@ open class SettingGroupEditor(open val c: T) { throw IllegalStateException("Could not access delegate for property $name", e) } - fun KProperty0.setting() = + fun KProperty0.setting() = this.delegate as? Setting, T> ?: throw IllegalStateException("Setting delegate did not match current value's type") - fun KProperty0.settingCore() = setting().core + fun KProperty0.settingCore() = setting().core @SettingEditorDsl inline fun KProperty0.edit(edits: TypedEditBuilder.(SettingCore) -> Unit) { @@ -60,14 +60,14 @@ open class SettingGroupEditor(open val c: T) { fun edit( vararg settings: KProperty0<*>, edits: BasicEditBuilder.() -> Unit - ) = BasicEditBuilder(this, settings.map { it.setting() }).apply(edits) + ) = BasicEditBuilder(this, settings.map { (it as KProperty0).setting() }).apply(edits) @SettingEditorDsl inline fun editWith( vararg settings: KProperty0<*>, other: KProperty0, edits: BasicEditBuilder.(SettingCore) -> Unit - ) = BasicEditBuilder(this, settings.map { it.setting() }).edits(other.settingCore()) + ) = BasicEditBuilder(this, settings.map { (it as KProperty0).setting() }).edits(other.settingCore()) @SettingEditorDsl inline fun editTyped( @@ -89,7 +89,7 @@ open class SettingGroupEditor(open val c: T) { @SettingEditorDsl fun hide(vararg settings: KProperty0<*>) = - hide(settings.map { it.setting() }) + hide(settings.map { (it as KProperty0).setting() }) open class BasicEditBuilder(val c: SettingGroupEditor<*>, open val settings: Collection>) { @SettingEditorDsl @@ -123,8 +123,10 @@ class ConfigurableEditor(override val c: T) : SettingGroupEdit fun hideGroup(settingGroup: ISettingGroup) = hide(settingGroup.settings) @SettingEditorDsl - fun hideGroupExcept(settingGroup: ISettingGroup, vararg except: KProperty0<*>) = - hide(*((settingGroup.settings as List>) - except.toSet()).toTypedArray()) + fun hideGroupExcept(settingGroup: ISettingGroup, vararg except: KProperty0<*>) { + val exceptSettings = except.map { (it as KProperty0).setting() }.toSet() + hide(settingGroup.settings.filter { it !in exceptSettings }) + } @SettingEditorDsl fun hideGroups(vararg settingGroups: ISettingGroup) = diff --git a/src/main/kotlin/com/lambda/config/Configurable.kt b/src/main/kotlin/com/lambda/config/Configurable.kt index ba1cb14f7..e7e5932d2 100644 --- a/src/main/kotlin/com/lambda/config/Configurable.kt +++ b/src/main/kotlin/com/lambda/config/Configurable.kt @@ -36,12 +36,13 @@ import com.lambda.config.settings.complex.Bind import com.lambda.config.settings.complex.BlockPosSetting import com.lambda.config.settings.complex.BlockSetting import com.lambda.config.settings.complex.ColorSetting -import com.lambda.config.settings.complex.KeybindSettingCore +import com.lambda.config.settings.complex.KeybindSetting import com.lambda.config.settings.complex.Vec3dSetting import com.lambda.config.settings.numeric.DoubleSetting import com.lambda.config.settings.numeric.FloatSetting import com.lambda.config.settings.numeric.IntegerSetting import com.lambda.config.settings.numeric.LongSetting +import com.lambda.event.Muteable import com.lambda.util.Communication.logError import com.lambda.util.KeyCode import com.lambda.util.Nameable @@ -151,33 +152,23 @@ abstract class Configurable( ) = Setting(name, description, ItemCollectionSetting(immutableCollection, defaultValue.toMutableList()), this, visibility).register() @JvmName("collectionSetting3") - inline fun > setting( + inline fun setting( name: String, defaultValue: Collection, immutableList: Collection = defaultValue, description: String = "", + displayClassName: Boolean = false, + serialize: Boolean = false, noinline visibility: () -> Boolean = { true }, ) = Setting( name, description, - CollectionSetting( - defaultValue.toMutableList(), - immutableList, - TypeToken.getParameterized(Collection::class.java, T::class.java).type - ), + if (displayClassName) ClassCollectionSetting(immutableList, defaultValue.toMutableList()) + else CollectionSetting(defaultValue.toMutableList(), immutableList, TypeToken.getParameterized(Collection::class.java, T::class.java).type, serialize), this, visibility ).register() - @JvmName("collectionSetting4") - inline fun setting( - name: String, - defaultValue: Collection, - immutableList: Collection = defaultValue, - description: String = "", - noinline visibility: () -> Boolean = { true }, - ) = Setting(name, description, ClassCollectionSetting(immutableList, defaultValue.toMutableList()), this, visibility).register() - // ToDo: Actually implement maps inline fun setting( name: String, @@ -239,15 +230,19 @@ abstract class Configurable( name: String, defaultValue: Bind, description: String = "", + alwaysListening: Boolean = false, + screenCheck: Boolean = true, visibility: () -> Boolean = { true }, - ) = Setting(name, description, KeybindSettingCore(defaultValue), this, visibility).register() + ) = Setting(name, description, KeybindSetting(defaultValue, this as? Muteable, alwaysListening, screenCheck), this, visibility).register() fun setting( name: String, defaultValue: KeyCode, description: String = "", + alwaysListening: Boolean = false, + screenCheck: Boolean = true, visibility: () -> Boolean = { true }, - ) = Setting(name, description, KeybindSettingCore(defaultValue), this, visibility).register() + ) = Setting(name, description, KeybindSetting(defaultValue, this as? Muteable, alwaysListening, screenCheck), this, visibility).register() fun setting( name: String, diff --git a/src/main/kotlin/com/lambda/config/Setting.kt b/src/main/kotlin/com/lambda/config/Setting.kt index 9f16d53c4..75f9ed688 100644 --- a/src/main/kotlin/com/lambda/config/Setting.kt +++ b/src/main/kotlin/com/lambda/config/Setting.kt @@ -94,7 +94,7 @@ import kotlin.reflect.KProperty * @property type The type reflection of the setting. * @property visibility A function that determines whether the setting is visible. */ -abstract class SettingCore( +abstract class SettingCore( var defaultValue: T, val type: Type ) { @@ -150,7 +150,7 @@ abstract class SettingCore( } } -class Setting, R : Any>( +class Setting, R>( override val name: String, override val description: String, var core: T, diff --git a/src/main/kotlin/com/lambda/config/groups/ActionConfig.kt b/src/main/kotlin/com/lambda/config/groups/ActionConfig.kt index 8851ac52c..78a6c6ffc 100644 --- a/src/main/kotlin/com/lambda/config/groups/ActionConfig.kt +++ b/src/main/kotlin/com/lambda/config/groups/ActionConfig.kt @@ -22,17 +22,17 @@ import com.lambda.util.Describable import com.lambda.util.NamedEnum interface ActionConfig { - val sorter: SortMode - val tickStageMask: Collection + val sorter: SortMode + val tickStageMask: Collection - enum class SortMode( - override val displayName: String, - override val description: String - ) : NamedEnum, Describable { - Closest("Closest", "Breaks blocks closest to the player eye position"), - Farthest("Farthest", "Breaks blocks farthest from the player eye position"), - Tool("Tool", "Breaks blocks with priority given to those with tools matching the current selected"), - Rotation("Rotation", "Breaks blocks closest to the player rotation"), - Random("Random", "Breaks blocks in a random order") - } + enum class SortMode( + override val displayName: String, + override val description: String + ) : NamedEnum, Describable { + Closest("Closest", "Breaks blocks closest to the player eye position"), + Farthest("Farthest", "Breaks blocks farthest from the player eye position"), + Tool("Tool", "Breaks blocks with priority given to those with tools matching the current selected"), + Rotation("Rotation", "Breaks blocks closest to the player rotation"), + Random("Random", "Breaks blocks in a random order") + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt b/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt index f7769bd3f..f4986db31 100644 --- a/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt +++ b/src/main/kotlin/com/lambda/config/groups/BreakSettings.kt @@ -31,87 +31,87 @@ import net.minecraft.block.Block import java.awt.Color open class BreakSettings( - c: Configurable, - baseGroup: NamedEnum + c: Configurable, + baseGroup: NamedEnum ) : SettingGroup(c), BreakConfig { - private enum class Group(override val displayName: String) : NamedEnum { - General("General"), - Cosmetic("Cosmetic") - } - - // General - override val breakMode by c.setting("Break Mode", BreakMode.Packet).group(baseGroup, Group.General).index() - override val sorter by c.setting("Break Sorter", ActionConfig.SortMode.Tool, "The order in which breaks are performed").group(baseGroup, Group.General).index() - override val rebreak by c.setting("Rebreak", true, "Re-breaks blocks after they've been broken once").group(baseGroup, Group.General).index() - - // Double break - override val doubleBreak by c.setting("Double Break", true, "Allows breaking two blocks at once").group(baseGroup, Group.General).index() - override val unsafeCancels by c.setting("Unsafe Cancels", true, "Allows cancelling block breaking even if the server might continue breaking sever side, potentially causing unexpected state changes") { doubleBreak }.group(baseGroup, Group.General).index() - - // Fixes / Delays - override val breakThreshold by c.setting("Break Threshold", 0.70f, 0.1f..1.0f, 0.01f, "The break amount at which the block is considered broken").group(baseGroup, Group.General).index() - override val fudgeFactor by c.setting("Fudge Factor", 1, 0..5, 1, "The number of ticks to add to the break time, usually to account for server lag").group(baseGroup, Group.General).index() - override val serverSwapTicks by c.setting("Server Swap", 0, 0..5, 1, "The number of ticks to give the server time to recognize the player attributes on the swapped item", " tick(s)").group(baseGroup, Group.General).index() - - // override val desyncFix by c.setting("Desync Fix", false, "Predicts if the players breaking will be slowed next tick as block break packets are processed using the players next position") { vis() && page == Page.General } - override val breakDelay by c.setting("Break Delay", 0, 0..5, 1, "The delay between breaking blocks", " tick(s)").group(baseGroup, Group.General).index() - - // Timing - override val tickStageMask by c.setting("Break Stage Mask", setOf(TickEvent.Input.Post), ALL_STAGES.toSet(), description = "The sub-tick timing at which break actions can be performed").group(baseGroup, Group.General).index() - - // Swap - override val swapMode by c.setting("Break Swap Mode", BreakConfig.SwapMode.End, "Decides when to swap to the best suited tool when breaking a block").group(baseGroup, Group.General).index() - - // Swing - override val swing by c.setting("Swing Mode", SwingMode.Constant, "The times at which to swing the players hand").group(baseGroup, Group.General).index() - override val swingType by c.setting("Break Swing Type", BuildConfig.SwingType.Vanilla, "The style of swing") { swing != SwingMode.None }.group(baseGroup, Group.General).index() - - // Rotate - override val rotate by c.setting("Rotate For Break", false, "Rotate towards block while breaking").group(baseGroup, Group.General).index() - - // Pending / Post - override val breakConfirmation by c.setting("Break Confirmation", BreakConfirmationMode.BreakThenAwait, "The style of confirmation used when breaking").group(baseGroup, Group.General).index() - override val breaksPerTick by c.setting("Breaks Per Tick", 5, 1..30, 1, "Maximum instant block breaks per tick").group(baseGroup, Group.General).index() - - // Block - override val ignoredBlocks by c.setting("Ignored Blocks", emptySet(), description = "Blocks that wont be broken").group(baseGroup, Group.General).index() - override val avoidLiquids by c.setting("Avoid Liquids", true, "Avoids breaking blocks that would cause liquid to spill").group(baseGroup, Group.General).index() - override val avoidSupporting by c.setting("Avoid Supporting", true, "Avoids breaking the block supporting the player").group(baseGroup, Group.General).index() - - // Tool - override val efficientOnly by c.setting("Efficient Tools Only", true, "Only use tools suitable for the given block (will get the item drop)") { swapMode.isEnabled() }.group(baseGroup, Group.General).index() - override val suitableToolsOnly by c.setting("Suitable Tools Only", true, "Only use tools suitable for the given block (will get the item drop)") { swapMode.isEnabled() }.group(baseGroup, Group.General).index() - override val forceSilkTouch by c.setting("Force Silk Touch", false, "Force silk touch when breaking blocks") { swapMode.isEnabled() }.group(baseGroup, Group.General).index() - override val forceFortunePickaxe by c.setting("Force Fortune Pickaxe", false, "Force fortune pickaxe when breaking blocks") { swapMode.isEnabled() }.group(baseGroup, Group.General).index() - override val minFortuneLevel by c.setting("Min Fortune Level", 1, 1..3, 1, "The minimum fortune level to use") { swapMode.isEnabled() && forceFortunePickaxe }.group(baseGroup, Group.General).index() - override val useWoodenTools by c.setting("Use Wooden Tools", true, "Use wooden tools when breaking blocks") { swapMode.isEnabled() }.group(baseGroup, Group.General).index() - override val useStoneTools by c.setting("Use Stone Tools", true, "Use stone tools when breaking blocks") { swapMode.isEnabled() }.group(baseGroup, Group.General).index() - override val useIronTools by c.setting("Use Iron Tools", true, "Use iron tools when breaking blocks") { swapMode.isEnabled() }.group(baseGroup, Group.General).index() - override val useDiamondTools by c.setting("Use Diamond Tools", true, "Use diamond tools when breaking blocks") { swapMode.isEnabled() }.group(baseGroup, Group.General).index() - override val useGoldTools by c.setting("Use Gold Tools", true, "Use gold tools when breaking blocks") { swapMode.isEnabled() }.group(baseGroup, Group.General).index() - override val useNetheriteTools by c.setting("Use Netherite Tools", true, "Use netherite tools when breaking blocks") { swapMode.isEnabled() }.group(baseGroup, Group.General).index() - - // Cosmetics - override val sounds by c.setting("Break Sounds", true, "Plays the breaking sounds").group(baseGroup, Group.Cosmetic).index() - override val particles by c.setting("Particles", true, "Renders the breaking particles").group(baseGroup, Group.Cosmetic).index() - override val breakingTexture by c.setting("Breaking Overlay", true, "Overlays the breaking texture at its different stages").group(baseGroup, Group.Cosmetic).index() - - // Modes - override val renders by c.setting("Renders", true, "Enables the render settings for breaking progress").group(baseGroup, Group.Cosmetic).index() - override val animation by c.setting("Animation", AnimationMode.Out, "The style of animation used for the box") { renders }.group(baseGroup, Group.Cosmetic).index() - - // Fill - override val fill by c.setting("Fill", true, "Renders the sides of the box to display break progress") { renders }.group(baseGroup, Group.Cosmetic).index() - override val dynamicFillColor by c.setting("Dynamic Colour", true, "Enables fill color interpolation from start to finish for fill when breaking a block") { renders && fill }.group(baseGroup, Group.Cosmetic).index() - override val staticFillColor by c.setting("Fill Color", Color(255, 0, 0, 60).brighter(), "The color of the fill") { renders && !dynamicFillColor && fill }.group(baseGroup, Group.Cosmetic).index() - override val startFillColor by c.setting("Start Fill Color", Color(255, 0, 0, 60).brighter(), "The color of the fill at the start of breaking") { renders && dynamicFillColor && fill }.group(baseGroup, Group.Cosmetic).index() - override val endFillColor by c.setting("End Fill Color", Color(0, 255, 0, 60).brighter(), "The color of the fill at the end of breaking") { renders && dynamicFillColor && fill }.group(baseGroup, Group.Cosmetic).index() - - // Outline - override val outline by c.setting("Outline", true, "Renders the lines of the box to display break progress") { renders }.group(baseGroup, Group.Cosmetic).index() - override val outlineWidth by c.setting("Outline Width", 2, 0..5, 1, "The width of the outline") { renders && outline }.group(baseGroup, Group.Cosmetic).index() - override val dynamicOutlineColor by c.setting("Dynamic Outline Color", true, "Enables color interpolation from start to finish for the outline when breaking a block") { renders && outline }.group(baseGroup, Group.Cosmetic).index() - override val staticOutlineColor by c.setting("Outline Color", Color.RED.brighter(), "The Color of the outline at the start of breaking") { renders && !dynamicOutlineColor && outline }.group(baseGroup, Group.Cosmetic).index() - override val startOutlineColor by c.setting("Start Outline Color", Color.RED.brighter(), "The color of the outline at the start of breaking") { renders && dynamicOutlineColor && outline }.group(baseGroup, Group.Cosmetic).index() - override val endOutlineColor by c.setting("End Outline Color", Color.GREEN.brighter(), "The color of the outline at the end of breaking") { renders && dynamicOutlineColor && outline }.group(baseGroup, Group.Cosmetic).index() + private enum class Group(override val displayName: String) : NamedEnum { + General("General"), + Cosmetic("Cosmetic") + } + + // General + override val breakMode by c.setting("Break Mode", BreakMode.Packet).group(baseGroup, Group.General).index() + override val sorter by c.setting("Break Sorter", ActionConfig.SortMode.Tool, "The order in which breaks are performed").group(baseGroup, Group.General).index() + override val rebreak by c.setting("Rebreak", true, "Re-breaks blocks after they've been broken once").group(baseGroup, Group.General).index() + + // Double break + override val doubleBreak by c.setting("Double Break", true, "Allows breaking two blocks at once").group(baseGroup, Group.General).index() + override val unsafeCancels by c.setting("Unsafe Cancels", true, "Allows cancelling block breaking even if the server might continue breaking sever side, potentially causing unexpected state changes") { doubleBreak }.group(baseGroup, Group.General).index() + + // Fixes / Delays + override val breakThreshold by c.setting("Break Threshold", 0.70f, 0.1f..1.0f, 0.01f, "The break amount at which the block is considered broken").group(baseGroup, Group.General).index() + override val fudgeFactor by c.setting("Fudge Factor", 1, 0..5, 1, "The number of ticks to add to the break time, usually to account for server lag").group(baseGroup, Group.General).index() + override val serverSwapTicks by c.setting("Server Swap", 0, 0..5, 1, "The number of ticks to give the server time to recognize the player attributes on the swapped item", " tick(s)").group(baseGroup, Group.General).index() + + // override val desyncFix by c.setting("Desync Fix", false, "Predicts if the players breaking will be slowed next tick as block break packets are processed using the players next position") { vis() && page == Page.General } + override val breakDelay by c.setting("Break Delay", 0, 0..6, 1, "The delay between breaking blocks", " tick(s)").group(baseGroup, Group.General).index() + + // Timing + override val tickStageMask by c.setting("Break Stage Mask", setOf(TickEvent.Input.Post), ALL_STAGES.toSet(), "The sub-tick timing at which break actions can be performed", displayClassName = true).group(baseGroup, Group.General).index() + + // Swap + override val swapMode by c.setting("Break Swap Mode", BreakConfig.SwapMode.End, "Decides when to swap to the best suited tool when breaking a block").group(baseGroup, Group.General).index() + + // Swing + override val swing by c.setting("Swing Mode", SwingMode.Constant, "The times at which to swing the players hand").group(baseGroup, Group.General).index() + override val swingType by c.setting("Break Swing Type", BuildConfig.SwingType.Vanilla, "The style of swing") { swing != SwingMode.None }.group(baseGroup, Group.General).index() + + // Rotate + override val rotate by c.setting("Rotate For Break", false, "Rotate towards block while breaking").group(baseGroup, Group.General).index() + + // Pending / Post + override val breakConfirmation by c.setting("Break Confirmation", BreakConfirmationMode.BreakThenAwait, "The style of confirmation used when breaking").group(baseGroup, Group.General).index() + override val breaksPerTick by c.setting("Breaks Per Tick", 5, 1..30, 1, "Maximum instant block breaks per tick").group(baseGroup, Group.General).index() + + // Block + override val ignoredBlocks by c.setting("Ignored Blocks", emptySet(), description = "Blocks that wont be broken").group(baseGroup, Group.General).index() + override val avoidLiquids by c.setting("Avoid Liquids", true, "Avoids breaking blocks that would cause liquid to spill").group(baseGroup, Group.General).index() + override val avoidSupporting by c.setting("Avoid Supporting", true, "Avoids breaking the block supporting the player").group(baseGroup, Group.General).index() + + // Tool + override val efficientOnly by c.setting("Efficient Tools Only", true, "Only use tools suitable for the given block (will get the item drop)") { swapMode.isEnabled() }.group(baseGroup, Group.General).index() + override val suitableToolsOnly by c.setting("Suitable Tools Only", true, "Only use tools suitable for the given block (will get the item drop)") { swapMode.isEnabled() }.group(baseGroup, Group.General).index() + override val forceSilkTouch by c.setting("Force Silk Touch", false, "Force silk touch when breaking blocks") { swapMode.isEnabled() }.group(baseGroup, Group.General).index() + override val forceFortunePickaxe by c.setting("Force Fortune Pickaxe", false, "Force fortune pickaxe when breaking blocks") { swapMode.isEnabled() }.group(baseGroup, Group.General).index() + override val minFortuneLevel by c.setting("Min Fortune Level", 1, 1..3, 1, "The minimum fortune level to use") { swapMode.isEnabled() && forceFortunePickaxe }.group(baseGroup, Group.General).index() + override val useWoodenTools by c.setting("Use Wooden Tools", true, "Use wooden tools when breaking blocks") { swapMode.isEnabled() }.group(baseGroup, Group.General).index() + override val useStoneTools by c.setting("Use Stone Tools", true, "Use stone tools when breaking blocks") { swapMode.isEnabled() }.group(baseGroup, Group.General).index() + override val useIronTools by c.setting("Use Iron Tools", true, "Use iron tools when breaking blocks") { swapMode.isEnabled() }.group(baseGroup, Group.General).index() + override val useDiamondTools by c.setting("Use Diamond Tools", true, "Use diamond tools when breaking blocks") { swapMode.isEnabled() }.group(baseGroup, Group.General).index() + override val useGoldTools by c.setting("Use Gold Tools", true, "Use gold tools when breaking blocks") { swapMode.isEnabled() }.group(baseGroup, Group.General).index() + override val useNetheriteTools by c.setting("Use Netherite Tools", true, "Use netherite tools when breaking blocks") { swapMode.isEnabled() }.group(baseGroup, Group.General).index() + + // Cosmetics + override val sounds by c.setting("Break Sounds", true, "Plays the breaking sounds").group(baseGroup, Group.Cosmetic).index() + override val particles by c.setting("Particles", true, "Renders the breaking particles").group(baseGroup, Group.Cosmetic).index() + override val breakingTexture by c.setting("Breaking Overlay", true, "Overlays the breaking texture at its different stages").group(baseGroup, Group.Cosmetic).index() + + // Modes + override val renders by c.setting("Renders", true, "Enables the render settings for breaking progress").group(baseGroup, Group.Cosmetic).index() + override val animation by c.setting("Animation", AnimationMode.Out, "The style of animation used for the box") { renders }.group(baseGroup, Group.Cosmetic).index() + + // Fill + override val fill by c.setting("Fill", true, "Renders the sides of the box to display break progress") { renders }.group(baseGroup, Group.Cosmetic).index() + override val dynamicFillColor by c.setting("Dynamic Colour", true, "Enables fill color interpolation from start to finish for fill when breaking a block") { renders && fill }.group(baseGroup, Group.Cosmetic).index() + override val staticFillColor by c.setting("Fill Color", Color(255, 0, 0, 60).brighter(), "The color of the fill") { renders && !dynamicFillColor && fill }.group(baseGroup, Group.Cosmetic).index() + override val startFillColor by c.setting("Start Fill Color", Color(255, 0, 0, 60).brighter(), "The color of the fill at the start of breaking") { renders && dynamicFillColor && fill }.group(baseGroup, Group.Cosmetic).index() + override val endFillColor by c.setting("End Fill Color", Color(0, 255, 0, 60).brighter(), "The color of the fill at the end of breaking") { renders && dynamicFillColor && fill }.group(baseGroup, Group.Cosmetic).index() + + // Outline + override val outline by c.setting("Outline", true, "Renders the lines of the box to display break progress") { renders }.group(baseGroup, Group.Cosmetic).index() + override val outlineWidth by c.setting("Outline Width", 2, 0..5, 1, "The width of the outline") { renders && outline }.group(baseGroup, Group.Cosmetic).index() + override val dynamicOutlineColor by c.setting("Dynamic Outline Color", true, "Enables color interpolation from start to finish for the outline when breaking a block") { renders && outline }.group(baseGroup, Group.Cosmetic).index() + override val staticOutlineColor by c.setting("Outline Color", Color.RED.brighter(), "The Color of the outline at the start of breaking") { renders && !dynamicOutlineColor && outline }.group(baseGroup, Group.Cosmetic).index() + override val startOutlineColor by c.setting("Start Outline Color", Color.RED.brighter(), "The color of the outline at the start of breaking") { renders && dynamicOutlineColor && outline }.group(baseGroup, Group.Cosmetic).index() + override val endOutlineColor by c.setting("End Outline Color", Color.GREEN.brighter(), "The color of the outline at the end of breaking") { renders && dynamicOutlineColor && outline }.group(baseGroup, Group.Cosmetic).index() } diff --git a/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt b/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt index 323161174..9c4cad6ca 100644 --- a/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt +++ b/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt @@ -23,7 +23,9 @@ import com.lambda.util.Describable import com.lambda.util.NamedEnum interface BuildConfig : ISettingGroup { - // General + val breakBlocks: Boolean + val interactBlocks: Boolean + val pathing: Boolean val stayInRange: Boolean val collectDrops: Boolean @@ -32,8 +34,8 @@ interface BuildConfig : ISettingGroup { val actionTimeout: Int val maxBuildDependencies: Int - val entityReach: Double val blockReach: Double + val entityReach: Double val scanReach: Double val checkSideVisibility: Boolean diff --git a/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt b/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt index 1a7632bd7..e1fa36913 100644 --- a/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt +++ b/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt @@ -33,17 +33,19 @@ class BuildSettings( Scan("Scan") } - // General - override val pathing by c.setting("Pathing", true, "Path to blocks").group(baseGroup, Group.General).index() - override val stayInRange by c.setting("Stay In Range", true, "Stay in range of blocks").group(baseGroup, Group.General).index() + override val breakBlocks by c.setting("Break", true, "Break blocks").group(baseGroup, Group.General).index() + override val interactBlocks by c.setting("Place / Interact", true, "Interact blocks").group(baseGroup, Group.General).index() + + override val pathing by c.setting("Pathing", false, "Path to blocks").group(baseGroup, Group.General).index() + override val stayInRange by c.setting("Stay In Range", false, "Stay in range of blocks").group(baseGroup, Group.General).index() override val collectDrops by c.setting("Collect All Drops", false, "Collect all drops when breaking blocks").group(baseGroup, Group.General).index() override val spleefEntities by c.setting("Spleef Entities", false, "Breaks blocks beneath entities blocking placements to get them out of the way").group(baseGroup, Group.General).index() override val maxPendingActions by c.setting("Max Pending Actions", 15, 1..30, 1, "The maximum count of pending interactions to allow before pausing future interactions").group(baseGroup, Group.General).index() override val actionTimeout by c.setting("Action Timeout", 10, 1..30, 1, "Timeout for block breaks in ticks", unit = " ticks").group(baseGroup, Group.General).index() override val maxBuildDependencies by c.setting("Max Sim Dependencies", 3, 0..10, 1, "Maximum dependency build results").group(baseGroup, Group.General).index() - override var entityReach by c.setting("Attack Reach", 3.0, 1.0..7.0, 0.01, "Maximum entity interaction distance").group(baseGroup, Group.Reach).index() override var blockReach by c.setting("Interact Reach", 4.5, 1.0..7.0, 0.01, "Maximum block interaction distance").group(baseGroup, Group.Reach).index() + override var entityReach by c.setting("Attack Reach", 3.0, 1.0..7.0, 0.01, "Maximum entity interaction distance").group(baseGroup, Group.Reach).index() override val scanReach: Double get() = max(entityReach, blockReach) override val checkSideVisibility by c.setting("Visibility Check", true, "Whether to check if an AABB side is visible").group(baseGroup, Group.Scan).index() diff --git a/src/main/kotlin/com/lambda/config/groups/FormatterSettings.kt b/src/main/kotlin/com/lambda/config/groups/FormatterSettings.kt index 295c4db21..1d53a3d72 100644 --- a/src/main/kotlin/com/lambda/config/groups/FormatterSettings.kt +++ b/src/main/kotlin/com/lambda/config/groups/FormatterSettings.kt @@ -23,22 +23,22 @@ import com.lambda.util.NamedEnum class FormatterSettings( c: Configurable, - baseGroup: NamedEnum, + vararg baseGroup: NamedEnum, ) : FormatterConfig, SettingGroup(c) { - val localeEnum by c.setting("Locale", FormatterConfig.Locales.US, "The regional formatting used for numbers").group(baseGroup).index() + val localeEnum by c.setting("Locale", FormatterConfig.Locales.US, "The regional formatting used for numbers").group(*baseGroup).index() override val locale get() = localeEnum.locale - val sep by c.setting("Separator", FormatterConfig.TupleSeparator.Comma, "Separator for string serialization of tuple data structures").group(baseGroup).index() - val customSep by c.setting("Custom Separator", "") { sep == FormatterConfig.TupleSeparator.Custom }.group(baseGroup).index() + val sep by c.setting("Separator", FormatterConfig.TupleSeparator.Comma, "Separator for string serialization of tuple data structures").group(*baseGroup).index() + val customSep by c.setting("Custom Separator", "") { sep == FormatterConfig.TupleSeparator.Custom }.group(*baseGroup).index() override val separator get() = if (sep == FormatterConfig.TupleSeparator.Custom) customSep else sep.separator - val group by c.setting("Tuple Prefix", FormatterConfig.TupleGrouping.Parentheses).group(baseGroup).index() + val group by c.setting("Tuple Prefix", FormatterConfig.TupleGrouping.Parentheses).group(*baseGroup).index() override val prefix get() = group.prefix override val postfix get() = group.postfix - val floatingPrecision by c.setting("Floating Precision", 3, 0..6, 1, "Precision for floating point numbers").group(baseGroup).index() + val floatingPrecision by c.setting("Floating Precision", 3, 0..6, 1, "Precision for floating point numbers").group(*baseGroup).index() override val precision get() = floatingPrecision - val timeFormat by c.setting("Time Format", FormatterConfig.Time.IsoDateTime).group(baseGroup).index() + val timeFormat by c.setting("Time Format", FormatterConfig.Time.IsoDateTime).group(*baseGroup).index() override val format get() = timeFormat.format } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/config/groups/HotbarSettings.kt b/src/main/kotlin/com/lambda/config/groups/HotbarSettings.kt index 618e6c10e..34baab4e8 100644 --- a/src/main/kotlin/com/lambda/config/groups/HotbarSettings.kt +++ b/src/main/kotlin/com/lambda/config/groups/HotbarSettings.kt @@ -33,5 +33,5 @@ class HotbarSettings( override val swapDelay by c.setting("Swap Delay", 0, 0..3, 1, "The number of ticks delay before allowing another hotbar selection swap", " ticks").group(baseGroup).index() override val swapsPerTick by c.setting("Swaps Per Tick", 3, 1..10, 1, "The number of hotbar selection swaps that can take place each tick") { swapDelay <= 0 }.group(baseGroup).index() override val swapPause by c.setting("Swap Pause", 0, 0..20, 1, "The delay in ticks to pause actions after switching to the slot", " ticks").group(baseGroup).index() - override val tickStageMask by c.setting("Hotbar Stage Mask", setOf(TickEvent.Input.Post), ALL_STAGES.toSet(), description = "The sub-tick timing at which hotbar actions are performed").group(baseGroup).index() + override val tickStageMask by c.setting("Hotbar Stage Mask", setOf(TickEvent.Input.Post), ALL_STAGES.toSet(), "The sub-tick timing at which hotbar actions are performed", displayClassName = true).group(baseGroup).index() } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/config/groups/InteractSettings.kt b/src/main/kotlin/com/lambda/config/groups/InteractSettings.kt index 9704a6d8b..90fc0b8be 100644 --- a/src/main/kotlin/com/lambda/config/groups/InteractSettings.kt +++ b/src/main/kotlin/com/lambda/config/groups/InteractSettings.kt @@ -34,7 +34,7 @@ class InteractSettings( override val airPlace by c.setting("Air Place", AirPlaceMode.None, "Allows for placing blocks without adjacent faces").group(baseGroup).index() override val axisRotateSetting by c.setting("Axis Rotate", true, "Overrides the Rotate For Place setting and rotates the player on each axis to air place rotational blocks") { airPlace.isEnabled }.group(baseGroup).index() override val sorter by c.setting("Interaction Sorter", ActionConfig.SortMode.Tool, "The order in which placements are performed").group(baseGroup).index() - override val tickStageMask by c.setting("Interaction Stage Mask", setOf(TickEvent.Input.Post), ALL_STAGES.toSet(), description = "The sub-tick timing at which place actions are performed").group(baseGroup).index() + override val tickStageMask by c.setting("Interaction Stage Mask", setOf(TickEvent.Input.Post), ALL_STAGES.toSet(), "The sub-tick timing at which place actions are performed", displayClassName = true).group(baseGroup).index() override val interactConfirmationMode by c.setting("Interact Confirmation", InteractConfirmationMode.PlaceThenAwait, "Wait for block placement confirmation").group(baseGroup).index() override val interactDelay by c.setting("Interact Delay", 0, 0..3, 1, "Tick delay between interacting with another block").group(baseGroup).index() override val interactionsPerTick by c.setting("Interactions Per Tick", 1, 1..30, 1, "Maximum instant block places per tick").group(baseGroup).index() diff --git a/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt b/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt index 9d67ff369..2dc25e38a 100644 --- a/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt +++ b/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt @@ -35,15 +35,14 @@ class InventorySettings( } override val actionsPerSecond by c.setting("Actions Per Second", 100, 0..100, 1, "How many inventory actions can be performed per tick").group(baseGroup, Group.General).index() - override val tickStageMask by c.setting("Inventory Stage Mask", ALL_STAGES.toSet(), description = "The sub-tick timing at which inventory actions are performed").group(baseGroup, Group.General).index() + override val tickStageMask by c.setting("Inventory Stage Mask", ALL_STAGES.toSet(), description = "The sub-tick timing at which inventory actions are performed", displayClassName = true).group(baseGroup, Group.General).index() override val disposables by c.setting("Disposables", ItemUtils.defaultDisposables, description = "Items that will be ignored when checking for a free slot").group(baseGroup, Group.Container).index() override val swapWithDisposables by c.setting("Swap With Disposables", true, "Swap items with disposable ones").group(baseGroup, Group.Container).index() override val providerPriority by c.setting("Provider Priority", InventoryConfig.Priority.WithMinItems, "What container to prefer when retrieving the item from").group(baseGroup, Group.Container).index() override val storePriority by c.setting("Store Priority", InventoryConfig.Priority.WithMinItems, "What container to prefer when storing the item to").group(baseGroup, Group.Container).index() - override val immediateAccessOnly by c.setting("Immediate Access Only", false, "Only allow access to inventories that can be accessed immediately").group(baseGroup, Group.Access).index() - override val accessShulkerBoxes by c.setting("Access Shulker Boxes", true, "Allow access to the player's shulker boxes").group(baseGroup, Group.Access).index() - override val accessEnderChest by c.setting("Access Ender Chest", false, "Allow access to the player's ender chest").group(baseGroup, Group.Access).index() + override val accessShulkerBoxes by c.setting("Access Shulker Boxes", false, "Allow access to the player's shulker boxes").group(baseGroup, Group.Access).index() override val accessChests by c.setting("Access Chests", false, "Allow access to the player's normal chests").group(baseGroup, Group.Access).index() + override val accessEnderChest by c.setting("Access Ender Chest", false, "Allow access to the player's ender chest").group(baseGroup, Group.Access).index() override val accessStashes by c.setting("Access Stashes", false, "Allow access to the player's stashes").group(baseGroup, Group.Access).index() } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/config/groups/ReplaceConfig.kt b/src/main/kotlin/com/lambda/config/groups/ReplaceConfig.kt new file mode 100644 index 000000000..f67407d17 --- /dev/null +++ b/src/main/kotlin/com/lambda/config/groups/ReplaceConfig.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.config.groups + +import com.lambda.util.Describable + +interface ReplaceConfig { + val action: ActionStrategy + val replace: ReplaceStrategy + + val enabled: Boolean get() = action != ActionStrategy.None + + enum class ActionStrategy(override val description: String) : Describable { + Hide("Hides the message. Will override other strategies."), + Delete("Deletes the matching part off the message."), + Replace("Replace the matching string in the message with one of the following replace strategy."), + None("Don't do anything."), + } + + enum class ReplaceStrategy(val block: (String) -> String) { + CensorAll({ it.replaceRange(0.. if (i % 2 == 0) acc + char else "$acc*" } }), + KeepFirst({ if (it.length <= 1) it else it.replaceRange(1, it.length, "*".repeat(it.length - 1)) }), + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/config/groups/Targeting.kt b/src/main/kotlin/com/lambda/config/groups/Targeting.kt index a50753e8b..a82766c1c 100644 --- a/src/main/kotlin/com/lambda/config/groups/Targeting.kt +++ b/src/main/kotlin/com/lambda/config/groups/Targeting.kt @@ -115,8 +115,8 @@ abstract class Targeting( * @return `true` if the entity is valid for targeting, `false` otherwise. */ open fun validate(player: ClientPlayerEntity, entity: LivingEntity) = when { + !friends && entity is OtherClientPlayerEntity && entity.isFriend -> false !players && entity is OtherClientPlayerEntity -> false - players && entity is OtherClientPlayerEntity && entity.isFriend -> false !animals && entity is PassiveEntity -> false !hostiles && entity is HostileEntity -> false entity is ArmorStandEntity -> false diff --git a/src/main/kotlin/com/lambda/config/serializer/TextCodec.kt b/src/main/kotlin/com/lambda/config/serializer/TextCodec.kt new file mode 100644 index 000000000..629e3ebb6 --- /dev/null +++ b/src/main/kotlin/com/lambda/config/serializer/TextCodec.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.config.serializer + +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonElement +import com.google.gson.JsonSerializationContext +import com.lambda.config.Codec +import com.mojang.serialization.JsonOps +import net.minecraft.text.Text +import net.minecraft.text.TextCodecs +import java.lang.reflect.Type +import kotlin.jvm.optionals.getOrElse + +object TextCodec : Codec { + override fun serialize( + src: Text, + typeOfSrc: Type, + context: JsonSerializationContext, + ): JsonElement = + TextCodecs.CODEC.encodeStart(JsonOps.INSTANCE, src) + .orThrow + + override fun deserialize( + json: JsonElement?, + typeOfT: Type?, + context: JsonDeserializationContext?, + ): Text = + TextCodecs.CODEC.parse(JsonOps.INSTANCE, json) + .result() + .getOrElse { Text.empty() } +} diff --git a/src/main/kotlin/com/lambda/config/settings/collections/BlockCollectionSetting.kt b/src/main/kotlin/com/lambda/config/settings/collections/BlockCollectionSetting.kt index 223f25e2c..7d7c66f79 100644 --- a/src/main/kotlin/com/lambda/config/settings/collections/BlockCollectionSetting.kt +++ b/src/main/kotlin/com/lambda/config/settings/collections/BlockCollectionSetting.kt @@ -17,9 +17,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.Setting import com.lambda.config.serializer.BlockCodec import com.lambda.gui.dsl.ImGuiBuilder @@ -31,17 +29,9 @@ class BlockCollectionSetting( ) : CollectionSetting( defaultValue, immutableCollection, - TypeToken.getParameterized(Collection::class.java, Block::class.java).type + TypeToken.getParameterized(Collection::class.java, Block::class.java).type, + serialize = true, ) { context(setting: Setting<*, MutableCollection>) override fun ImGuiBuilder.buildLayout() = buildComboBox("block") { BlockCodec.stringify(it) } - - context(setting: Setting<*, MutableCollection>) - override fun toJson(): JsonElement = gson.toJsonTree(value, type) - - context(setting: Setting<*, MutableCollection>) - override fun loadFromJson(serialized: JsonElement) { - value = gson.fromJson>(serialized, type) - .toMutableList() - } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/config/settings/collections/ClassCollectionSetting.kt b/src/main/kotlin/com/lambda/config/settings/collections/ClassCollectionSetting.kt index 054808251..218c29673 100644 --- a/src/main/kotlin/com/lambda/config/settings/collections/ClassCollectionSetting.kt +++ b/src/main/kotlin/com/lambda/config/settings/collections/ClassCollectionSetting.kt @@ -34,7 +34,8 @@ class ClassCollectionSetting( ) : CollectionSetting( defaultValue, immutableCollection, - TypeToken.getParameterized(Collection::class.java, Any::class.java).type + TypeToken.getParameterized(Collection::class.java, Any::class.java).type, + serialize = false, ) { context(setting: Setting<*, MutableCollection>) override fun ImGuiBuilder.buildLayout() = buildComboBox("item") { it.className } diff --git a/src/main/kotlin/com/lambda/config/settings/collections/CollectionSetting.kt b/src/main/kotlin/com/lambda/config/settings/collections/CollectionSetting.kt index 812ccb90a..b092b38ee 100644 --- a/src/main/kotlin/com/lambda/config/settings/collections/CollectionSetting.kt +++ b/src/main/kotlin/com/lambda/config/settings/collections/CollectionSetting.kt @@ -46,7 +46,8 @@ import java.lang.reflect.Type open class CollectionSetting( defaultValue: MutableCollection, private var immutableCollection: Collection, - type: Type + type: Type, + private val serialize: Boolean, ) : SettingCore>( defaultValue, type @@ -106,16 +107,18 @@ open class CollectionSetting( context(setting: Setting<*, MutableCollection>) override fun toJson(): JsonElement = - gson.toJsonTree(value.map { it.toString() }) + gson.toJsonTree(value, type) context(setting: Setting<*, MutableCollection>) override fun loadFromJson(serialized: JsonElement) { - val strList = gson.fromJson>(serialized, strListType) - .mapNotNull { str -> immutableCollection.find { it.toString() == str } } - .toMutableList() + val strList = + if (serialize) gson.fromJson(serialized, type) + else gson.fromJson>(serialized, strListType) + .mapNotNull { str -> immutableCollection.find { it.toString() == str } } + .toMutableList() - value = strList - } + value = strList + } companion object { fun , R : Any> Setting>.onSelect(block: SafeContext.(R) -> Unit) = apply { diff --git a/src/main/kotlin/com/lambda/config/settings/collections/ItemCollectionSetting.kt b/src/main/kotlin/com/lambda/config/settings/collections/ItemCollectionSetting.kt index 967f4c82c..c5caab002 100644 --- a/src/main/kotlin/com/lambda/config/settings/collections/ItemCollectionSetting.kt +++ b/src/main/kotlin/com/lambda/config/settings/collections/ItemCollectionSetting.kt @@ -17,9 +17,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.Setting import com.lambda.config.serializer.ItemCodec import com.lambda.gui.dsl.ImGuiBuilder @@ -31,17 +29,9 @@ class ItemCollectionSetting( ) : CollectionSetting( defaultValue, immutableCollection, - TypeToken.getParameterized(Collection::class.java, Item::class.java).type + TypeToken.getParameterized(Collection::class.java, Item::class.java).type, + serialize = true, ) { context(setting: Setting<*, MutableCollection>) override fun ImGuiBuilder.buildLayout() = buildComboBox("item") { ItemCodec.stringify(it) } - - context(setting: Setting<*, MutableCollection>) - override fun toJson(): JsonElement = gson.toJsonTree(value, type) - - context(setting: Setting<*, MutableCollection>) - override fun loadFromJson(serialized: JsonElement) { - value = gson.fromJson>(serialized, type) - .toMutableList() - } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt b/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt index 1e8c11993..dd5e9663e 100644 --- a/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt +++ b/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt @@ -28,6 +28,10 @@ import com.lambda.brigadier.optional import com.lambda.brigadier.required import com.lambda.config.Setting import com.lambda.config.SettingCore +import com.lambda.context.SafeContext +import com.lambda.event.Muteable +import com.lambda.event.events.ButtonEvent +import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.gui.dsl.ImGuiBuilder import com.lambda.util.InputUtils import com.lambda.util.KeyCode @@ -48,14 +52,43 @@ import org.lwjgl.glfw.GLFW.GLFW_MOD_NUM_LOCK import org.lwjgl.glfw.GLFW.GLFW_MOD_SHIFT import org.lwjgl.glfw.GLFW.GLFW_MOD_SUPER -class KeybindSettingCore(defaultValue: Bind) : SettingCore( +class KeybindSetting( + defaultValue: Bind, + private val muteable: Muteable?, + private val alwaysListening: Boolean, + private val screenCheck: Boolean +) : SettingCore( defaultValue, TypeToken.get(Bind::class.java).type -) { - constructor(defaultValue: KeyCode) : this(Bind(defaultValue.code, 0, -1)) +), Muteable { + constructor(defaultValue: KeyCode, muteable: Muteable?, alwaysListen: Boolean, screenCheck: Boolean) + : this(Bind(defaultValue.code, 0, -1), muteable, alwaysListen, screenCheck) + + private val pressListeners = mutableListOf Unit>() + private val repeatListeners = mutableListOf Unit>() + private val releaseListeners = mutableListOf Unit>() private var listening = false + override val isMuted + get() = muteable?.isMuted == true && !alwaysListening + + init { + listen { event -> onButtonEvent(event) } + listen { event -> onButtonEvent(event) } + } + + private fun SafeContext.onButtonEvent(event: ButtonEvent) { + if (mc.options.commandKey.isPressed || + (screenCheck && mc.currentScreen != null) || + !event.satisfies(value)) return + + if (event.isPressed) { + if (event.isRepeated) repeatListeners.forEach { it(event) } + else pressListeners.forEach { it(event) } + } else if (event.isReleased) releaseListeners.forEach { it(event) } + } + context(setting: Setting<*, Bind>) override fun ImGuiBuilder.buildLayout() { text(setting.name) @@ -66,16 +99,18 @@ class KeybindSettingCore(defaultValue: Bind) : SettingCore( if (listening) "Press any key…" else bind.name - if (listening) { - withStyleColor(ImGuiCol.Button, 0.20f, 0.50f, 1.00f, 1.00f) { - withStyleColor(ImGuiCol.ButtonHovered, 0.25f, 0.60f, 1.00f, 1.00f) { - withStyleColor(ImGuiCol.ButtonActive, 0.20f, 0.50f, 0.95f, 1.00f) { - button(preview) + withId("##Bind-${this@KeybindSetting.hashCode()}") { + if (listening) { + withStyleColor(ImGuiCol.Button, 0.20f, 0.50f, 1.00f, 1.00f) { + withStyleColor(ImGuiCol.ButtonHovered, 0.25f, 0.60f, 1.00f, 1.00f) { + withStyleColor(ImGuiCol.ButtonActive, 0.20f, 0.50f, 0.95f, 1.00f) { + button(preview) + } } } + } else { + button(preview) { listening = true } } - } else { - button(preview) { listening = true } } lambdaTooltip { @@ -88,9 +123,11 @@ class KeybindSettingCore(defaultValue: Bind) : SettingCore( } sameLine() - smallButton("Unbind") { - value = Bind.EMPTY - listening = false + withId("##Unbind-${this@KeybindSetting.hashCode()}") { + smallButton("Unbind") { + value = Bind.EMPTY + listening = false + } } onItemHover(ImGuiHoveredFlags.Stationary) { lambdaTooltip("Clear binding") @@ -157,6 +194,20 @@ class KeybindSettingCore(defaultValue: Bind) : SettingCore( } } } + + companion object { + fun Setting.onPress(block: SafeContext.(ButtonEvent) -> Unit) = apply { + core.pressListeners.add(block) + } + + fun Setting.onRepeat(block: SafeContext.(ButtonEvent) -> Unit) = apply { + core.repeatListeners.add(block) + } + + fun Setting.onRelease(block: SafeContext.(ButtonEvent) -> Unit) = apply { + core.releaseListeners.add(block) + } + } } data class Bind( diff --git a/src/main/kotlin/com/lambda/context/Automated.kt b/src/main/kotlin/com/lambda/context/Automated.kt index 8cabc8128..a4ba9309e 100644 --- a/src/main/kotlin/com/lambda/context/Automated.kt +++ b/src/main/kotlin/com/lambda/context/Automated.kt @@ -26,11 +26,11 @@ import com.lambda.interaction.managers.inventory.InventoryConfig import com.lambda.interaction.managers.rotating.RotationConfig interface Automated { - val buildConfig: BuildConfig - val breakConfig: BreakConfig - val interactConfig: InteractConfig - val rotationConfig: RotationConfig - val inventoryConfig: InventoryConfig - val hotbarConfig: HotbarConfig - val eatConfig: EatConfig + val buildConfig: BuildConfig + val breakConfig: BreakConfig + val interactConfig: InteractConfig + val rotationConfig: RotationConfig + val inventoryConfig: InventoryConfig + val hotbarConfig: HotbarConfig + val eatConfig: EatConfig } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/context/AutomatedSafeContext.kt b/src/main/kotlin/com/lambda/context/AutomatedSafeContext.kt index f69e58460..f0bd51ed5 100644 --- a/src/main/kotlin/com/lambda/context/AutomatedSafeContext.kt +++ b/src/main/kotlin/com/lambda/context/AutomatedSafeContext.kt @@ -18,6 +18,6 @@ package com.lambda.context class AutomatedSafeContext( - safeContext: SafeContext, - automated: Automated + safeContext: SafeContext, + automated: Automated ) : IAutomatedSafeContext, SafeContext by safeContext, Automated by automated \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/context/AutomationConfig.kt b/src/main/kotlin/com/lambda/context/AutomationConfig.kt deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/main/kotlin/com/lambda/context/SafeContext.kt b/src/main/kotlin/com/lambda/context/SafeContext.kt index 379c40bb6..2cfb6d78d 100644 --- a/src/main/kotlin/com/lambda/context/SafeContext.kt +++ b/src/main/kotlin/com/lambda/context/SafeContext.kt @@ -49,25 +49,25 @@ import net.minecraft.client.world.ClientWorld * @property connection The network handler for the player. **/ interface SafeContext { - val mc: MinecraftClient - val world: ClientWorld - val player: ClientPlayerEntity - val interaction: ClientPlayerInteractionManager - val connection: ClientPlayNetworkHandler + val mc: MinecraftClient + val world: ClientWorld + val player: ClientPlayerEntity + val interaction: ClientPlayerInteractionManager + val connection: ClientPlayNetworkHandler - companion object { - fun create(): SafeContext? { - val world = mc.world ?: return null - val player = mc.player ?: return null - val interaction = mc.interactionManager ?: return null - val connection = mc.networkHandler ?: return null - return object : SafeContext { - override val mc = Lambda.mc - override val world = world - override val player = player - override val interaction = interaction - override val connection = connection - } - } - } + companion object { + fun create(): SafeContext? { + val world = mc.world ?: return null + val player = mc.player ?: return null + val interaction = mc.interactionManager ?: return null + val connection = mc.networkHandler ?: return null + return object : SafeContext { + override val mc = Lambda.mc + override val world = world + override val player = player + override val interaction = interaction + override val connection = connection + } + } + } } diff --git a/src/main/kotlin/com/lambda/event/EventFlow.kt b/src/main/kotlin/com/lambda/event/EventFlow.kt index 5fbdabbcd..ef18705cd 100644 --- a/src/main/kotlin/com/lambda/event/EventFlow.kt +++ b/src/main/kotlin/com/lambda/event/EventFlow.kt @@ -290,8 +290,8 @@ object EventFlow { * @return `true` if the listener should not be notified, `false` otherwise. */ private fun shouldNotNotify(listener: Listener, event: Event) = - listener.owner is Muteable - && (listener.owner as Muteable).isMuted - && !listener.alwaysListen - || event is ICancellable && event.isCanceled() + (listener.owner is Muteable && + (listener.owner as Muteable).isMuted && + !listener.alwaysListen) || + (event is ICancellable && event.isCanceled()) } diff --git a/src/main/kotlin/com/lambda/event/events/ButtonEvent.kt b/src/main/kotlin/com/lambda/event/events/ButtonEvent.kt new file mode 100644 index 000000000..034de460c --- /dev/null +++ b/src/main/kotlin/com/lambda/event/events/ButtonEvent.kt @@ -0,0 +1,110 @@ +/* + * Copyright 2026 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.event.events + +import com.lambda.config.settings.complex.Bind +import com.lambda.event.Event +import com.lambda.event.callback.Cancellable +import com.lambda.event.callback.ICancellable +import com.lambda.util.KeyCode +import com.lambda.util.math.Vec2d +import org.lwjgl.glfw.GLFW.GLFW_PRESS +import org.lwjgl.glfw.GLFW.GLFW_RELEASE +import org.lwjgl.glfw.GLFW.GLFW_REPEAT + +sealed class ButtonEvent : ICancellable by Cancellable() { + abstract val action: Int + abstract val modifiers: Int + + val isPressed get() = action >= GLFW_PRESS + val isReleased get() = action == GLFW_RELEASE + val isRepeated get() = action == GLFW_REPEAT + + abstract fun satisfies(bind: Bind): Boolean + + sealed class Mouse { + /** + * Represents a mouse click event + * + * @property button The button that was clicked + * @property action The action performed (e.g., press or release) + * @property modifiers An integer representing any modifiers (e.g., shift or ctrl) active during the event + */ + data class Click( + val button: Int, + override val action: Int, + override val modifiers: Int, + ) : ButtonEvent() { + override fun satisfies(bind: Bind) = bind.modifiers and modifiers == bind.modifiers && bind.mouse == button + } + + /** + * Represents a mouse scroll event + * + * @property delta The amount of scrolling in the x and y directions + */ + data class Scroll( + val delta: Vec2d, + ) : ICancellable by Cancellable() + + /** + * Represents a mouse move event. + * + * @property position The x and y position of the mouse on the screen. + */ + data class Move( + val position: Vec2d, + ) : ICancellable by Cancellable() + } + + sealed class Keyboard { + /** + * Represents a key press + * + * @property keyCode The key code of the key that was pressed + * @property scanCode The scan code of the key that was pressed + * @property action The action that was performed on the key (Pressed, Released) + * @property modifiers The modifiers that were active when the key was pressed + * + * @see About Keyboards + */ + data class Press( + val keyCode: Int, + val scanCode: Int, + override val action: Int, + override val modifiers: Int, + ) : ButtonEvent() { + val bind: Bind + get() = Bind(translated.code, modifiers, -1) + + val translated: KeyCode + get() = KeyCode.virtualMapUS(keyCode, scanCode) + + override fun satisfies(bind: Bind) = bind.key == translated.code && bind.modifiers and modifiers == bind.modifiers + } + + /** + * Represents glfwSetCharCallback events + * + * Keys and characters do not map 1:1. + * A single key press may produce several characters, and a single + * character may require several keys to produce + */ + data class Char(val char: kotlin.Char) : Event + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/event/events/ChatEvent.kt b/src/main/kotlin/com/lambda/event/events/ChatEvent.kt new file mode 100644 index 000000000..aed5d57fe --- /dev/null +++ b/src/main/kotlin/com/lambda/event/events/ChatEvent.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.event.events + +import com.lambda.event.Event +import com.lambda.event.callback.Cancellable +import net.minecraft.client.gui.hud.MessageIndicator +import net.minecraft.network.message.MessageSignatureData +import net.minecraft.text.Text + +sealed class ChatEvent { + class Send(var message: String) : Event, Cancellable() + + class Receive( + var message: Text, + var signature: MessageSignatureData?, + var indicator: MessageIndicator?, + ) : Event, Cancellable() +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/event/events/ContainerEvent.kt b/src/main/kotlin/com/lambda/event/events/ContainerEvent.kt new file mode 100644 index 000000000..2c0031cb1 --- /dev/null +++ b/src/main/kotlin/com/lambda/event/events/ContainerEvent.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2026 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.event.events + +import com.lambda.event.callback.Cancellable +import com.lambda.event.callback.ICancellable +import com.lambda.interaction.material.container.MaterialContainer +import net.minecraft.screen.slot.Slot + +sealed class ContainerEvent { + data class Transfer( + val fromSlot: Slot, + val toSlot: Slot, + val from: MaterialContainer, + val to: MaterialContainer + ) : ICancellable by Cancellable() +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/event/events/InventoryEvent.kt b/src/main/kotlin/com/lambda/event/events/InventoryEvent.kt index d8b277457..10d1e53b6 100644 --- a/src/main/kotlin/com/lambda/event/events/InventoryEvent.kt +++ b/src/main/kotlin/com/lambda/event/events/InventoryEvent.kt @@ -20,7 +20,6 @@ package com.lambda.event.events import com.lambda.event.Event import com.lambda.event.callback.Cancellable import com.lambda.event.callback.ICancellable -import com.lambda.interaction.managers.hotbar.HotbarRequest import net.minecraft.item.ItemStack import net.minecraft.screen.ScreenHandler @@ -84,22 +83,12 @@ sealed class InventoryEvent { ) : Event abstract class HotbarSlot : Event { - - data class Request(var request: HotbarRequest? = null) : HotbarSlot() - /** * Represents an event triggered when the client attempts to send slot update to the server. * * Updated slot id will come to the server if it defers from last reported slot. */ - data class Update(var slot: Int) : HotbarSlot() - - /** - * Represents an event triggered when the client sends slot update to the server. - * - * This event happens when last slot defers from the previous one - */ - data class Changed(var slot: Int) : HotbarSlot() + data class Update(val slot: Int) : HotbarSlot() /** * Represents an event triggered when the server forces the player to change active hotbar slot. diff --git a/src/main/kotlin/com/lambda/event/events/KeyboardEvent.kt b/src/main/kotlin/com/lambda/event/events/KeyboardEvent.kt deleted file mode 100644 index d496c3fc0..000000000 --- a/src/main/kotlin/com/lambda/event/events/KeyboardEvent.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2025 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.event.events - -import com.lambda.config.settings.complex.Bind -import com.lambda.event.Event -import com.lambda.util.KeyCode -import org.lwjgl.glfw.GLFW.GLFW_PRESS -import org.lwjgl.glfw.GLFW.GLFW_RELEASE -import org.lwjgl.glfw.GLFW.GLFW_REPEAT - -sealed class KeyboardEvent { - /** - * Represents a key press - * - * @property keyCode The key code of the key that was pressed - * @property scanCode The scan code of the key that was pressed - * @property action The action that was performed on the key (Pressed, Released) - * @property modifiers The modifiers that were active when the key was pressed - * - * @see About Keyboards - */ - data class Press( - val keyCode: Int, - val scanCode: Int, - val action: Int, - val modifiers: Int, - ) : Event { - val bind: Bind - get() = Bind(translated.code, modifiers, -1) - - val translated: KeyCode - get() = KeyCode.virtualMapUS(keyCode, scanCode) - - val isPressed = action >= GLFW_PRESS - val isReleased = action == GLFW_RELEASE - val isRepeated = action == GLFW_REPEAT - - fun satisfies(bind: Bind) = bind.key == translated.code && bind.modifiers and modifiers == bind.modifiers - } - - /** - * Represents glfwSetCharCallback events - * - * Keys and characters do not map 1:1. - * A single key press may produce several characters, and a single - * character may require several keys to produce - */ - data class Char(val char: kotlin.Char) : Event -} diff --git a/src/main/kotlin/com/lambda/event/events/MouseEvent.kt b/src/main/kotlin/com/lambda/event/events/MouseEvent.kt deleted file mode 100644 index 427969e39..000000000 --- a/src/main/kotlin/com/lambda/event/events/MouseEvent.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2025 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.event.events - -import com.lambda.config.settings.complex.Bind -import com.lambda.event.callback.Cancellable -import com.lambda.event.callback.ICancellable -import com.lambda.util.math.Vec2d - -sealed class MouseEvent { - /** - * Represents a mouse click event - * - * @property button The button that was clicked - * @property action The action performed (e.g., press or release) - * @property modifiers An integer representing any modifiers (e.g., shift or ctrl) active during the event - */ - data class Click( - val button: Int, - val action: Int, - val modifiers: Int, - ) : ICancellable by Cancellable() { - val isReleased = action == 0 - val isPressed = action == 1 - - fun satisfies(bind: Bind) = bind.modifiers and modifiers == bind.modifiers && bind.mouse == button - } - - /** - * Represents a mouse scroll event - * - * @property delta The amount of scrolling in the x and y directions - */ - data class Scroll( - val delta: Vec2d, - ) : ICancellable by Cancellable() - - /** - * Represents a mouse move event. - * - * @property position The x and y position of the mouse on the screen. - */ - data class Move( - val position: Vec2d, - ) : ICancellable by Cancellable() -} diff --git a/src/main/kotlin/com/lambda/event/events/PlayerPacketEvent.kt b/src/main/kotlin/com/lambda/event/events/PlayerPacketEvent.kt index fc3d5a8a2..58e767623 100644 --- a/src/main/kotlin/com/lambda/event/events/PlayerPacketEvent.kt +++ b/src/main/kotlin/com/lambda/event/events/PlayerPacketEvent.kt @@ -31,11 +31,11 @@ sealed class PlayerPacketEvent { var onGround: Boolean, var isSprinting: Boolean, var isCollidingHorizontally: Boolean, - ) : ICancellable by Cancellable() - - class Post : Event + ) : Event data class Send( val packet: PlayerMoveC2SPacket, ) : ICancellable by Cancellable() + + class Post : Event } diff --git a/src/main/kotlin/com/lambda/event/events/RenderEvent.kt b/src/main/kotlin/com/lambda/event/events/RenderEvent.kt index 812979bfa..36e2dbe37 100644 --- a/src/main/kotlin/com/lambda/event/events/RenderEvent.kt +++ b/src/main/kotlin/com/lambda/event/events/RenderEvent.kt @@ -22,13 +22,14 @@ import com.lambda.event.Event import com.lambda.event.callback.Cancellable import com.lambda.event.callback.ICancellable import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.graphics.renderer.esp.ShapeBuilder -import com.lambda.graphics.renderer.esp.Treed +import com.lambda.graphics.RenderMain +import com.lambda.graphics.mc.TransientRegionESP -fun Any.onStaticRender(block: SafeContext.(ShapeBuilder) -> Unit) = - listen { block(ShapeBuilder(Treed.Static.faceBuilder, Treed.Static.edgeBuilder)) } -fun Any.onDynamicRender(block: SafeContext.(ShapeBuilder) -> Unit) = - listen { block(ShapeBuilder(Treed.Dynamic.faceBuilder, Treed.Dynamic.edgeBuilder)) } +fun Any.onStaticRender(block: SafeContext.(TransientRegionESP) -> Unit) = + listen { block(RenderMain.StaticESP) } + +fun Any.onDynamicRender(block: SafeContext.(TransientRegionESP) -> Unit) = + listen { block(RenderMain.DynamicESP) } sealed class RenderEvent { object Upload : Event diff --git a/src/main/kotlin/com/lambda/friend/FriendManager.kt b/src/main/kotlin/com/lambda/friend/FriendManager.kt index bceafea0d..171d99527 100644 --- a/src/main/kotlin/com/lambda/friend/FriendManager.kt +++ b/src/main/kotlin/com/lambda/friend/FriendManager.kt @@ -41,9 +41,9 @@ import java.util.* // - Improve save file structure. object FriendManager : Configurable(FriendConfig), Loadable { override val name = "friends" - val friends by setting("friends", emptySet()) + val friends by setting("friends", emptySet(), serialize = true) - fun befriend(profile: GameProfile) = friends.add(profile) + fun befriend(profile: GameProfile) = if (!isFriend(profile)) friends.add(profile) else false fun unfriend(profile: GameProfile): Boolean = friends.remove(profile) fun gameProfile(name: String) = friends.firstOrNull { it.name == name } diff --git a/src/main/kotlin/com/lambda/graphics/RenderMain.kt b/src/main/kotlin/com/lambda/graphics/RenderMain.kt index 3668298f2..1353fec6e 100644 --- a/src/main/kotlin/com/lambda/graphics/RenderMain.kt +++ b/src/main/kotlin/com/lambda/graphics/RenderMain.kt @@ -22,55 +22,90 @@ import com.lambda.event.EventFlow.post import com.lambda.event.events.RenderEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.graphics.gl.GlStateUtils.setupGL import com.lambda.graphics.gl.Matrices import com.lambda.graphics.gl.Matrices.resetMatrices -import com.lambda.graphics.renderer.esp.Treed -import com.lambda.util.math.Vec2d -import com.mojang.blaze3d.opengl.GlStateManager -import com.mojang.blaze3d.systems.RenderSystem -import net.minecraft.client.gl.GlBackend -import net.minecraft.client.texture.GlTexture +import com.lambda.graphics.mc.TransientRegionESP +import net.minecraft.util.math.Vec3d import org.joml.Matrix4f -import org.lwjgl.opengl.GL30.GL_FRAMEBUFFER +import org.joml.Vector2f +import org.joml.Vector4f object RenderMain { + @JvmStatic + val StaticESP = TransientRegionESP("Static") + + @JvmStatic + val DynamicESP = TransientRegionESP("Dynamic") + val projectionMatrix = Matrix4f() - val modelViewMatrix get() = Matrices.peek() - val projModel: Matrix4f get() = Matrix4f(projectionMatrix).mul(modelViewMatrix) + val modelViewMatrix + get() = Matrices.peek() + val projModel: Matrix4f + get() = Matrix4f(projectionMatrix).mul(modelViewMatrix) + + /** + * Project a world position to screen coordinates. Returns null if the position is behind the + * camera or off-screen. + * + * @param worldPos The world position to project + * @return Screen coordinates (x, y) in pixels, or null if not visible + */ + fun worldToScreen(worldPos: Vec3d): Vector2f? { + val camera = mc.gameRenderer?.camera ?: return null + val cameraPos = camera.pos + + // Camera-relative position + val relX = (worldPos.x - cameraPos.x).toFloat() + val relY = (worldPos.y - cameraPos.y).toFloat() + val relZ = (worldPos.z - cameraPos.z).toFloat() + + // Apply projection * modelview matrix + val vec = Vector4f(relX, relY, relZ, 1f) + projModel.transform(vec) + + // Behind camera check + if (vec.w <= 0) return null - var screenSize = Vec2d.ZERO + // Perspective divide to get NDC + val ndcX = vec.x / vec.w + val ndcY = vec.y / vec.w + val ndcZ = vec.z / vec.w + + // Off-screen check (NDC is -1 to 1) + if (ndcZ < -1 || ndcZ > 1) return null + + // NDC to screen coordinates (Y is flipped in screen space) + val window = mc.window + val screenX = (ndcX + 1f) * 0.5f * window.framebufferWidth + val screenY = (1f - ndcY) * 0.5f * window.framebufferHeight + + return Vector2f(screenX, screenY) + } + + /** Check if a world position is visible on screen. */ + fun isOnScreen(worldPos: Vec3d): Boolean = worldToScreen(worldPos) != null @JvmStatic fun render3D(positionMatrix: Matrix4f, projMatrix: Matrix4f) { resetMatrices(positionMatrix) projectionMatrix.set(projMatrix) - setupGL { - val framebuffer = mc.framebuffer - val prevFramebuffer = (framebuffer.getColorAttachment() as GlTexture).getOrCreateFramebuffer( - (RenderSystem.getDevice() as GlBackend).framebufferManager, - null - ) - - GlStateManager._glBindFramebuffer(GL_FRAMEBUFFER, prevFramebuffer) + // Render transient ESPs using the new pipeline + StaticESP.render() // Uses internal depthTest flag (true) + DynamicESP.render() // Uses internal depthTest flag (false) - Treed.Static.render() - Treed.Dynamic.render() - - RenderEvent.Render.post() - } + RenderEvent.Render.post() } init { listen { - Treed.Static.clear() - Treed.Dynamic.clear() + StaticESP.clear() + DynamicESP.clear() RenderEvent.Upload.post() - Treed.Static.upload() - Treed.Dynamic.upload() + StaticESP.upload() + DynamicESP.upload() } } } diff --git a/src/main/kotlin/com/lambda/graphics/renderer/gui/font/core/LambdaFont.kt b/src/main/kotlin/com/lambda/graphics/esp/EspDsl.kt similarity index 64% rename from src/main/kotlin/com/lambda/graphics/renderer/gui/font/core/LambdaFont.kt rename to src/main/kotlin/com/lambda/graphics/esp/EspDsl.kt index dd08deb2c..0c0a19a3f 100644 --- a/src/main/kotlin/com/lambda/graphics/renderer/gui/font/core/LambdaFont.kt +++ b/src/main/kotlin/com/lambda/graphics/esp/EspDsl.kt @@ -15,16 +15,18 @@ * along with this program. If not, see . */ -package com.lambda.graphics.renderer.gui.font.core +package com.lambda.graphics.esp -import com.lambda.graphics.renderer.gui.font.core.LambdaAtlas.buildBuffer +import com.lambda.graphics.mc.ChunkedRegionESP +import com.lambda.module.Module -enum class LambdaFont(val fontName: String) { - FiraSansRegular("FiraSans-Regular"), - FiraSansBold("FiraSans-Bold"); +@DslMarker +annotation class EspDsl - fun load(): String { - entries.forEach { it.buildBuffer() } - return "Loaded ${entries.size} fonts" - } +fun Module.chunkedEsp( + name: String, + depthTest: Boolean = false, + update: ShapeScope.(net.minecraft.world.World, com.lambda.util.world.FastVector) -> Unit +): ChunkedRegionESP { + return ChunkedRegionESP(this, name, depthTest, update) } diff --git a/src/main/kotlin/com/lambda/graphics/esp/RegionESP.kt b/src/main/kotlin/com/lambda/graphics/esp/RegionESP.kt new file mode 100644 index 000000000..b32e03755 --- /dev/null +++ b/src/main/kotlin/com/lambda/graphics/esp/RegionESP.kt @@ -0,0 +1,126 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.graphics.esp + +import com.lambda.Lambda.mc +import com.lambda.graphics.mc.LambdaRenderPipelines +import com.lambda.graphics.mc.RegionRenderer +import com.lambda.graphics.mc.RenderRegion +import com.lambda.util.extension.tickDelta +import com.mojang.blaze3d.systems.RenderSystem +import java.util.concurrent.ConcurrentHashMap +import kotlin.math.floor +import org.joml.Matrix4f +import org.joml.Vector3f +import org.joml.Vector4f + +/** + * Base class for region-based ESP systems. Provides unified rendering logic and region management. + */ +abstract class RegionESP(val name: String, val depthTest: Boolean) { + protected val renderers = ConcurrentHashMap() + + /** Get or create a ShapeScope for a specific world position. */ + open fun shapes(x: Double, y: Double, z: Double, block: ShapeScope.() -> Unit) {} + + /** Upload collected geometry to GPU. Must be called on main thread. */ + open fun upload() {} + + /** Clear all geometry data. */ + abstract fun clear() + + /** Close and release all GPU resources. */ + open fun close() { + renderers.values.forEach { it.close() } + renderers.clear() + clear() + } + + /** + * Render all active regions. + * @param tickDelta Progress within current tick (used for interpolation) + */ + open fun render(tickDelta: Float = mc.tickDelta) { + val camera = mc.gameRenderer?.camera ?: return + val cameraPos = camera.pos + + val activeRenderers = renderers.values.filter { it.hasData() } + if (activeRenderers.isEmpty()) return + + val modelViewMatrix = com.lambda.graphics.RenderMain.modelViewMatrix + val transforms = activeRenderers.map { renderer -> + val offset = renderer.region.computeCameraRelativeOffset(cameraPos) + val modelView = Matrix4f(modelViewMatrix).translate(offset) + + val dynamicTransform = RenderSystem.getDynamicUniforms() + .write( + modelView, + Vector4f(1f, 1f, 1f, 1f), + Vector3f(0f, 0f, 0f), + Matrix4f() + ) + renderer to dynamicTransform + } + + // Render Faces + RegionRenderer.createRenderPass("$name Faces")?.use { pass -> + val pipeline = + if (depthTest) LambdaRenderPipelines.ESP_QUADS + else LambdaRenderPipelines.ESP_QUADS_THROUGH + pass.setPipeline(pipeline) + RenderSystem.bindDefaultUniforms(pass) + transforms.forEach { (renderer, transform) -> + pass.setUniform("DynamicTransforms", transform) + renderer.renderFaces(pass) + } + } + + // Render Edges + RegionRenderer.createRenderPass("$name Edges")?.use { pass -> + val pipeline = + if (depthTest) LambdaRenderPipelines.ESP_LINES + else LambdaRenderPipelines.ESP_LINES_THROUGH + pass.setPipeline(pipeline) + RenderSystem.bindDefaultUniforms(pass) + transforms.forEach { (renderer, transform) -> + pass.setUniform("DynamicTransforms", transform) + renderer.renderEdges(pass) + } + } + } + + /** + * Compute a unique key for a region based on its coordinates. Prevents collisions between + * regions at different Y levels. + */ + protected fun getRegionKey(x: Double, y: Double, z: Double): Long { + val rx = (RenderRegion.REGION_SIZE * floor(x / RenderRegion.REGION_SIZE)).toInt() + val ry = (RenderRegion.REGION_SIZE * floor(y / RenderRegion.REGION_SIZE)).toInt() + val rz = (RenderRegion.REGION_SIZE * floor(z / RenderRegion.REGION_SIZE)).toInt() + + return getRegionKey(rx, ry, rz) + } + + protected fun getRegionKey(rx: Int, ry: Int, rz: Int): Long { + // 20 bits for X, 20 bits for Z, 24 bits for Y (total 64) + // This supports +- 500k blocks in X/Z and full Y range + return (rx.toLong() and 0xFFFFF) or + ((rz.toLong() and 0xFFFFF) shl 20) or + ((ry.toLong() and 0xFFFFFF) shl 40) + } +} diff --git a/src/main/kotlin/com/lambda/graphics/esp/ShapeScope.kt b/src/main/kotlin/com/lambda/graphics/esp/ShapeScope.kt new file mode 100644 index 000000000..14ab277f5 --- /dev/null +++ b/src/main/kotlin/com/lambda/graphics/esp/ShapeScope.kt @@ -0,0 +1,416 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.graphics.esp + +import com.lambda.graphics.mc.RegionShapeBuilder +import com.lambda.graphics.mc.RegionVertexCollector +import com.lambda.graphics.mc.RenderRegion +import com.lambda.graphics.renderer.esp.DirectionMask +import com.lambda.graphics.renderer.esp.DynamicAABB +import net.minecraft.block.BlockState +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Box +import net.minecraft.util.math.MathHelper +import net.minecraft.util.math.Vec3d +import net.minecraft.util.shape.VoxelShape +import java.awt.Color + +@EspDsl +class ShapeScope(val region: RenderRegion, val collectShapes: Boolean = false) { + internal val builder = RegionShapeBuilder(region) + internal val shapes = if (collectShapes) mutableListOf() else null + + /** Start building a box. */ + fun box(box: Box, id: Any? = null, block: BoxScope.() -> Unit) { + val scope = BoxScope(box, this) + scope.apply(block) + if (collectShapes) { + shapes?.add( + EspShape.BoxShape( + id?.hashCode() ?: box.hashCode(), + box, + scope.filledColor, + scope.outlineColor, + scope.sides, + scope.outlineMode, + scope.thickness + ) + ) + } + } + + /** Draw a line between two points. */ + fun line(start: Vec3d, end: Vec3d, color: Color, width: Float = 1.0f, id: Any? = null) { + builder.line(start, end, color, width) + if (collectShapes) { + shapes?.add( + EspShape.LineShape( + id?.hashCode() ?: (start.hashCode() xor end.hashCode()), + start, + end, + color, + width + ) + ) + } + } + + /** Draw a tracer. */ + fun tracer(from: Vec3d, to: Vec3d, id: Any? = null, block: LineScope.() -> Unit = {}) { + val scope = LineScope(from, to, this) + scope.apply(block) + scope.draw() + if (collectShapes) { + shapes?.add( + EspShape.LineShape( + id?.hashCode() ?: (from.hashCode() xor to.hashCode()), + from, + to, + scope.lineColor, + scope.lineWidth, + scope.lineDashLength, + scope.lineGapLength + ) + ) + } + } + + /** Draw a simple filled box. */ + fun filled(box: Box, color: Color, sides: Int = DirectionMask.ALL) { + builder.filled(box, color, sides) + if (collectShapes) { + shapes?.add(EspShape.BoxShape(box.hashCode(), box, color, null, sides)) + } + } + + /** Draw a simple outlined box. */ + fun outline(box: Box, color: Color, sides: Int = DirectionMask.ALL, thickness: Float = builder.lineWidth) { + builder.outline(box, color, sides, thickness = thickness) + if (collectShapes) { + shapes?.add(EspShape.BoxShape(box.hashCode(), box, null, color, sides, thickness = thickness)) + } + } + + fun filled(box: DynamicAABB, color: Color, sides: Int = DirectionMask.ALL) { + builder.filled(box, color, sides) + if (collectShapes) { + box.pair?.second?.let { + shapes?.add(EspShape.BoxShape(it.hashCode(), it, color, null, sides)) + } + } + } + + fun outline(box: DynamicAABB, color: Color, sides: Int = DirectionMask.ALL, thickness: Float = builder.lineWidth) { + builder.outline(box, color, sides, thickness = thickness) + if (collectShapes) { + box.pair?.second?.let { + shapes?.add(EspShape.BoxShape(it.hashCode(), it, null, color, sides, thickness = thickness)) + } + } + } + + fun filled(pos: BlockPos, color: Color, sides: Int = DirectionMask.ALL) { + builder.filled(pos, color, sides) + if (collectShapes) { + shapes?.add(EspShape.BoxShape(pos.hashCode(), Box(pos), color, null, sides)) + } + } + + fun outline(pos: BlockPos, color: Color, sides: Int = DirectionMask.ALL, thickness: Float = builder.lineWidth) { + builder.outline(pos, color, sides, thickness = thickness) + if (collectShapes) { + shapes?.add(EspShape.BoxShape(pos.hashCode(), Box(pos), null, color, sides, thickness = thickness)) + } + } + + fun filled(pos: BlockPos, state: BlockState, color: Color, sides: Int = DirectionMask.ALL) { + builder.filled(pos, state, color, sides) + if (collectShapes) { + shapes?.add(EspShape.BoxShape(pos.hashCode(), Box(pos), color, null, sides)) + } + } + + fun outline(pos: BlockPos, state: BlockState, color: Color, sides: Int = DirectionMask.ALL, thickness: Float = builder.lineWidth) { + builder.outline(pos, state, color, sides, thickness = thickness) + if (collectShapes) { + shapes?.add(EspShape.BoxShape(pos.hashCode(), Box(pos), null, color, sides, thickness = thickness)) + } + } + + fun filled(shape: VoxelShape, color: Color, sides: Int = DirectionMask.ALL) { + builder.filled(shape, color, sides) + if (collectShapes) { + shape.boundingBoxes.forEach { + shapes?.add(EspShape.BoxShape(it.hashCode(), it, color, null, sides)) + } + } + } + + fun outline(shape: VoxelShape, color: Color, sides: Int = DirectionMask.ALL, thickness: Float = builder.lineWidth) { + builder.outline(shape, color, sides, thickness = thickness) + if (collectShapes) { + shape.boundingBoxes.forEach { + shapes?.add(EspShape.BoxShape(it.hashCode(), it, null, color, sides, thickness = thickness)) + } + } + } + + fun box( + pos: BlockPos, + state: BlockState, + filled: Color, + outline: Color, + sides: Int = DirectionMask.ALL, + mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And, + thickness: Float = builder.lineWidth + ) { + builder.box(pos, state, filled, outline, sides, mode, thickness = thickness) + if (collectShapes) { + shapes?.add(EspShape.BoxShape(pos.hashCode(), Box(pos), filled, outline, sides, mode, thickness = thickness)) + } + } + + fun box( + pos: BlockPos, + filled: Color, + outline: Color, + sides: Int = DirectionMask.ALL, + mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And, + thickness: Float = builder.lineWidth + ) { + builder.box(pos, filled, outline, sides, mode, thickness = thickness) + if (collectShapes) { + shapes?.add(EspShape.BoxShape(pos.hashCode(), Box(pos), filled, outline, sides, mode, thickness = thickness)) + } + } + + fun box( + box: Box, + filledColor: Color, + outlineColor: Color, + sides: Int = DirectionMask.ALL, + mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And, + thickness: Float = builder.lineWidth + ) { + builder.box(box, filledColor, outlineColor, sides, mode, thickness = thickness) + if (collectShapes) { + shapes?.add(EspShape.BoxShape(box.hashCode(), box, filledColor, outlineColor, sides, mode, thickness = thickness)) + } + } + + fun box( + box: DynamicAABB, + filledColor: Color, + outlineColor: Color, + sides: Int = DirectionMask.ALL, + mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And, + thickness: Float = builder.lineWidth + ) { + builder.box(box, filledColor, outlineColor, sides, mode, thickness = thickness) + if (collectShapes) { + box.pair?.second?.let { + shapes?.add( + EspShape.BoxShape(it.hashCode(), it, filledColor, outlineColor, sides, mode, thickness = thickness) + ) + } + } + } + + fun box( + entity: net.minecraft.block.entity.BlockEntity, + filled: Color, + outline: Color, + sides: Int = DirectionMask.ALL, + mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And, + thickness: Float = builder.lineWidth + ) { + builder.box(entity, filled, outline, sides, mode, thickness = thickness) + if (collectShapes) { + shapes?.add( + EspShape.BoxShape( + entity.pos.hashCode(), + Box(entity.pos), + filled, + outline, + sides, + mode, + thickness = thickness + ) + ) + } + } + + fun box( + entity: net.minecraft.entity.Entity, + filled: Color, + outline: Color, + sides: Int = DirectionMask.ALL, + mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And, + thickness: Float = builder.lineWidth + ) { + builder.box(entity, filled, outline, sides, mode, thickness = thickness) + if (collectShapes) { + shapes?.add( + EspShape.BoxShape( + entity.hashCode(), + entity.boundingBox, + filled, + outline, + sides, + mode, + thickness = thickness + ) + ) + } + } +} + +@EspDsl +class BoxScope(val box: Box, val parent: ShapeScope) { + internal var filledColor: Color? = null + internal var outlineColor: Color? = null + internal var sides: Int = DirectionMask.ALL + internal var outlineMode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And + internal var thickness: Float = parent.builder.lineWidth + + fun filled(color: Color, sides: Int = DirectionMask.ALL) { + this.filledColor = color + this.sides = sides + parent.builder.filled(box, color, sides) + } + + fun outline( + color: Color, + sides: Int = DirectionMask.ALL, + mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And, + thickness: Float = parent.builder.lineWidth + ) { + this.outlineColor = color + this.sides = sides + this.outlineMode = mode + this.thickness = thickness + parent.builder.outline(box, color, sides, mode, thickness = thickness) + } +} + +@EspDsl +class LineScope(val from: Vec3d, val to: Vec3d, val parent: ShapeScope) { + internal var lineColor: Color = Color.WHITE + internal var lineWidth: Float = 1.0f + internal var lineDashLength: Double? = null + internal var lineGapLength: Double? = null + + fun color(color: Color) { + this.lineColor = color + } + + fun width(width: Float) { + this.lineWidth = width + } + + fun dashed(dashLength: Double = 0.5, gapLength: Double = 0.25) { + this.lineDashLength = dashLength + this.lineGapLength = gapLength + } + + internal fun draw() { + val dLen = lineDashLength + val gLen = lineGapLength + + if (dLen != null && gLen != null) { + parent.builder.dashedLine(from, to, lineColor, dLen, gLen, lineWidth) + } else { + parent.builder.line(from, to, lineColor, lineWidth) + } + } +} + +sealed class EspShape(val id: Int) { + abstract fun renderInterpolated( + prev: EspShape, + tickDelta: Float, + collector: RegionVertexCollector, + region: RenderRegion + ) + + class BoxShape( + id: Int, + val box: Box, + val filledColor: Color?, + val outlineColor: Color?, + val sides: Int = DirectionMask.ALL, + val outlineMode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And, + val thickness: Float = 1.0f + ) : EspShape(id) { + override fun renderInterpolated( + prev: EspShape, + tickDelta: Float, + collector: RegionVertexCollector, + region: RenderRegion + ) { + val interpBox = + if (prev is BoxShape) { + Box( + MathHelper.lerp(tickDelta.toDouble(), prev.box.minX, box.minX), + MathHelper.lerp(tickDelta.toDouble(), prev.box.minY, box.minY), + MathHelper.lerp(tickDelta.toDouble(), prev.box.minZ, box.minZ), + MathHelper.lerp(tickDelta.toDouble(), prev.box.maxX, box.maxX), + MathHelper.lerp(tickDelta.toDouble(), prev.box.maxY, box.maxY), + MathHelper.lerp(tickDelta.toDouble(), prev.box.maxZ, box.maxZ) + ) + } else box + + val shapeBuilder = RegionShapeBuilder(region) + filledColor?.let { shapeBuilder.filled(interpBox, it, sides) } + outlineColor?.let { shapeBuilder.outline(interpBox, it, sides, outlineMode, thickness = thickness) } + + collector.faceVertices.addAll(shapeBuilder.collector.faceVertices) + collector.edgeVertices.addAll(shapeBuilder.collector.edgeVertices) + } + } + + class LineShape( + id: Int, + val from: Vec3d, + val to: Vec3d, + val color: Color, + val width: Float, + val dashLength: Double? = null, + val gapLength: Double? = null + ) : EspShape(id) { + override fun renderInterpolated( + prev: EspShape, + tickDelta: Float, + collector: RegionVertexCollector, + region: RenderRegion + ) { + val iFrom = if (prev is LineShape) prev.from.lerp(from, tickDelta.toDouble()) else from + val iTo = if (prev is LineShape) prev.to.lerp(to, tickDelta.toDouble()) else to + + val shapeBuilder = RegionShapeBuilder(region) + if (dashLength != null && gapLength != null) { + shapeBuilder.dashedLine(iFrom, iTo, color, dashLength, gapLength, width) + } else { + shapeBuilder.line(iFrom, iTo, color, width) + } + + collector.faceVertices.addAll(shapeBuilder.collector.faceVertices) + collector.edgeVertices.addAll(shapeBuilder.collector.edgeVertices) + } + } +} diff --git a/src/main/kotlin/com/lambda/graphics/gl/GlStateUtils.kt b/src/main/kotlin/com/lambda/graphics/gl/GlStateUtils.kt index 77b830aca..c8e57ef5f 100644 --- a/src/main/kotlin/com/lambda/graphics/gl/GlStateUtils.kt +++ b/src/main/kotlin/com/lambda/graphics/gl/GlStateUtils.kt @@ -30,6 +30,7 @@ import org.lwjgl.opengl.GL30C.glDisable import org.lwjgl.opengl.GL30C.glEnable import org.lwjgl.opengl.GL30C.glLineWidth +// ToDo: Migrate particle system so we can remove this object GlStateUtils { private var depthTestState = true private var blendState = false diff --git a/src/main/kotlin/com/lambda/graphics/mc/ChunkedRegionESP.kt b/src/main/kotlin/com/lambda/graphics/mc/ChunkedRegionESP.kt new file mode 100644 index 000000000..d367694a5 --- /dev/null +++ b/src/main/kotlin/com/lambda/graphics/mc/ChunkedRegionESP.kt @@ -0,0 +1,163 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.graphics.mc + +import com.lambda.event.events.RenderEvent +import com.lambda.event.events.TickEvent +import com.lambda.event.events.WorldEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.event.listener.SafeListener.Companion.listenConcurrently +import com.lambda.graphics.esp.RegionESP +import com.lambda.graphics.esp.ShapeScope +import com.lambda.module.Module +import com.lambda.module.modules.client.StyleEditor +import com.lambda.threading.runSafe +import com.lambda.util.world.FastVector +import com.lambda.util.world.fastVectorOf +import net.minecraft.world.World +import net.minecraft.world.chunk.WorldChunk +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentLinkedDeque + +/** + * Region-based chunked ESP system using MC 1.21.11's new render pipeline. + * + * This system: + * - Uses region-relative coordinates for precision-safe rendering + * - Maintains per-chunk geometry for efficient updates + * + * @param owner The module that owns this ESP system + * @param name The name of the ESP system + * @param depthTest Whether to use depth testing + * @param update The update function called for each block position + */ +class ChunkedRegionESP( + owner: Module, + name: String, + depthTest: Boolean = false, + private val update: ShapeScope.(World, FastVector) -> Unit +) : RegionESP(name, depthTest) { + private val chunkMap = ConcurrentHashMap() + + private val WorldChunk.regionChunk + get() = chunkMap.getOrPut(getRegionKey(pos.x shl 4, bottomY, pos.z shl 4)) { + RegionChunk(this) + } + + private val uploadQueue = ConcurrentLinkedDeque<() -> Unit>() + private val rebuildQueue = ConcurrentLinkedDeque() + + /** Mark all tracked chunks for rebuild. */ + fun rebuild() { + rebuildQueue.clear() + rebuildQueue.addAll(chunkMap.values) + } + + /** + * Load all currently loaded world chunks and mark them for rebuild. Call this when the module + * is enabled to populate initial chunks. + */ + fun rebuildAll() { + runSafe { + val chunksArray = world.chunkManager.chunks.chunks + (0 until chunksArray.length()).forEach { i -> + chunksArray.get(i)?.regionChunk?.markDirty() + } + } + } + + override fun clear() { + chunkMap.values.forEach { it.close() } + chunkMap.clear() + rebuildQueue.clear() + uploadQueue.clear() + } + + init { + owner.listen { event -> + val pos = event.pos + world.getWorldChunk(pos)?.regionChunk?.markDirty() + + val xInChunk = pos.x and 15 + val zInChunk = pos.z and 15 + + if (xInChunk == 0) world.getWorldChunk(pos.west())?.regionChunk?.markDirty() + if (xInChunk == 15) world.getWorldChunk(pos.east())?.regionChunk?.markDirty() + if (zInChunk == 0) world.getWorldChunk(pos.north())?.regionChunk?.markDirty() + if (zInChunk == 15) world.getWorldChunk(pos.south())?.regionChunk?.markDirty() + } + + owner.listen { event -> event.chunk.regionChunk.markDirty() } + + owner.listen { + val pos = getRegionKey(it.chunk.pos.x shl 4, it.chunk.bottomY, it.chunk.pos.z shl 4) + chunkMap.remove(pos)?.close() + } + + owner.listenConcurrently { + val queueSize = rebuildQueue.size + val polls = minOf(StyleEditor.rebuildsPerTick, queueSize) + repeat(polls) { rebuildQueue.poll()?.rebuild() } + } + + owner.listen { + val polls = minOf(StyleEditor.uploadsPerTick, uploadQueue.size) + repeat(polls) { uploadQueue.poll()?.invoke() } + } + + owner.listen { render() } + } + + /** Per-chunk rendering data. */ + private inner class RegionChunk(val chunk: WorldChunk) { + val region = RenderRegion.forChunk(chunk.pos.x, chunk.pos.z, chunk.bottomY) + private val key = getRegionKey(chunk.pos.x shl 4, chunk.bottomY, chunk.pos.z shl 4) + + private var isDirty = false + + fun markDirty() { + isDirty = true + if (!rebuildQueue.contains(this)) { + rebuildQueue.add(this) + } + } + + fun rebuild() { + if (!isDirty) return + val scope = ShapeScope(region) + + for (x in chunk.pos.startX..chunk.pos.endX) { + for (z in chunk.pos.startZ..chunk.pos.endZ) { + for (y in chunk.bottomY..chunk.height) { + update(scope, chunk.world, fastVectorOf(x, y, z)) + } + } + } + + uploadQueue.add { + val renderer = renderers.getOrPut(key) { RegionRenderer(region) } + renderer.upload(scope.builder.collector) + isDirty = false + } + } + + fun close() { + renderers.remove(key)?.close() + } + } +} diff --git a/src/main/kotlin/com/lambda/graphics/mc/CurveUtils.kt b/src/main/kotlin/com/lambda/graphics/mc/CurveUtils.kt new file mode 100644 index 000000000..2cf2b1ddc --- /dev/null +++ b/src/main/kotlin/com/lambda/graphics/mc/CurveUtils.kt @@ -0,0 +1,247 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.graphics.mc + +import net.minecraft.util.math.MathHelper +import net.minecraft.util.math.Vec3d + +/** + * Utility functions for curve and spline calculations. + * + * Provides Bezier curves and Catmull-Rom splines for smooth + * trajectory rendering and path visualization. + */ +object CurveUtils { + + /** + * Linear interpolation between two points. + */ + fun lerp(t: Double, p0: Vec3d, p1: Vec3d): Vec3d { + return Vec3d( + MathHelper.lerp(t, p0.x, p1.x), + MathHelper.lerp(t, p0.y, p1.y), + MathHelper.lerp(t, p0.z, p1.z) + ) + } + + /** + * Quadratic Bezier curve. + * + * B(t) = (1-t)²P0 + 2(1-t)tP1 + t²P2 + * + * @param t Parameter from 0.0 to 1.0 + * @param p0 Start point + * @param p1 Control point + * @param p2 End point + */ + fun quadraticBezier(t: Double, p0: Vec3d, p1: Vec3d, p2: Vec3d): Vec3d { + val mt = 1.0 - t + val mt2 = mt * mt + val t2 = t * t + + return Vec3d( + mt2 * p0.x + 2 * mt * t * p1.x + t2 * p2.x, + mt2 * p0.y + 2 * mt * t * p1.y + t2 * p2.y, + mt2 * p0.z + 2 * mt * t * p1.z + t2 * p2.z + ) + } + + /** + * Cubic Bezier curve. + * + * B(t) = (1-t)³P0 + 3(1-t)²tP1 + 3(1-t)t²P2 + t³P3 + * + * @param t Parameter from 0.0 to 1.0 + * @param p0 Start point + * @param p1 First control point + * @param p2 Second control point + * @param p3 End point + */ + fun cubicBezier(t: Double, p0: Vec3d, p1: Vec3d, p2: Vec3d, p3: Vec3d): Vec3d { + val mt = 1.0 - t + val mt2 = mt * mt + val mt3 = mt2 * mt + val t2 = t * t + val t3 = t2 * t + + return Vec3d( + mt3 * p0.x + 3 * mt2 * t * p1.x + 3 * mt * t2 * p2.x + t3 * p3.x, + mt3 * p0.y + 3 * mt2 * t * p1.y + 3 * mt * t2 * p2.y + t3 * p3.y, + mt3 * p0.z + 3 * mt2 * t * p1.z + 3 * mt * t2 * p2.z + t3 * p3.z + ) + } + + /** + * Catmull-Rom spline interpolation. + * + * Unlike Bezier curves, Catmull-Rom splines pass through all control points. + * Uses MC's built-in catmullRom function. + * + * @param t Parameter from 0.0 to 1.0 (interpolates between p1 and p2) + * @param p0 Point before the segment + * @param p1 Start of segment + * @param p2 End of segment + * @param p3 Point after the segment + */ + fun catmullRom(t: Float, p0: Vec3d, p1: Vec3d, p2: Vec3d, p3: Vec3d): Vec3d { + return Vec3d( + MathHelper.catmullRom(t, p0.x.toFloat(), p1.x.toFloat(), p2.x.toFloat(), p3.x.toFloat()).toDouble(), + MathHelper.catmullRom(t, p0.y.toFloat(), p1.y.toFloat(), p2.y.toFloat(), p3.y.toFloat()).toDouble(), + MathHelper.catmullRom(t, p0.z.toFloat(), p1.z.toFloat(), p2.z.toFloat(), p3.z.toFloat()).toDouble() + ) + } + + /** + * Generate points along a quadratic Bezier curve. + * + * @param p0 Start point + * @param p1 Control point + * @param p2 End point + * @param segments Number of line segments + * @return List of points along the curve + */ + fun quadraticBezierPoints(p0: Vec3d, p1: Vec3d, p2: Vec3d, segments: Int): List { + return (0..segments).map { i -> + val t = i.toDouble() / segments + quadraticBezier(t, p0, p1, p2) + } + } + + /** + * Generate points along a cubic Bezier curve. + * + * @param p0 Start point + * @param p1 First control point + * @param p2 Second control point + * @param p3 End point + * @param segments Number of line segments + * @return List of points along the curve + */ + fun cubicBezierPoints(p0: Vec3d, p1: Vec3d, p2: Vec3d, p3: Vec3d, segments: Int): List { + return (0..segments).map { i -> + val t = i.toDouble() / segments + cubicBezier(t, p0, p1, p2, p3) + } + } + + /** + * Generate points along a Catmull-Rom spline that passes through all control points. + * + * @param controlPoints List of points the spline should pass through (minimum 4) + * @param segmentsPerSection Number of segments between each pair of control points + * @return List of points along the spline + */ + fun catmullRomSplinePoints(controlPoints: List, segmentsPerSection: Int): List { + if (controlPoints.size < 4) return controlPoints + + val result = mutableListOf() + + for (i in 1 until controlPoints.size - 2) { + val p0 = controlPoints[i - 1] + val p1 = controlPoints[i] + val p2 = controlPoints[i + 1] + val p3 = controlPoints[i + 2] + + for (j in 0 until segmentsPerSection) { + val t = j.toFloat() / segmentsPerSection + result.add(catmullRom(t, p0, p1, p2, p3)) + } + } + + // Add the last point + result.add(controlPoints[controlPoints.size - 2]) + + return result + } + + /** + * Estimate the arc length of a cubic Bezier curve using subdivision. + * + * @param p0 Start point + * @param p1 First control point + * @param p2 Second control point + * @param p3 End point + * @param subdivisions Number of subdivisions for estimation + */ + fun cubicBezierLength(p0: Vec3d, p1: Vec3d, p2: Vec3d, p3: Vec3d, subdivisions: Int = 32): Double { + var length = 0.0 + var prev = p0 + + for (i in 1..subdivisions) { + val t = i.toDouble() / subdivisions + val curr = cubicBezier(t, p0, p1, p2, p3) + length += prev.distanceTo(curr) + prev = curr + } + + return length + } + + /** + * Calculate the tangent (direction) at a point on a cubic Bezier curve. + * + * B'(t) = 3(1-t)²(P1-P0) + 6(1-t)t(P2-P1) + 3t²(P3-P2) + * + * @param t Parameter from 0.0 to 1.0 + * @param p0 Start point + * @param p1 First control point + * @param p2 Second control point + * @param p3 End point + * @return Normalized tangent vector + */ + fun cubicBezierTangent(t: Double, p0: Vec3d, p1: Vec3d, p2: Vec3d, p3: Vec3d): Vec3d { + val mt = 1.0 - t + val mt2 = mt * mt + val t2 = t * t + + val d1 = p1.subtract(p0) + val d2 = p2.subtract(p1) + val d3 = p3.subtract(p2) + + return Vec3d( + 3 * mt2 * d1.x + 6 * mt * t * d2.x + 3 * t2 * d3.x, + 3 * mt2 * d1.y + 6 * mt * t * d2.y + 3 * t2 * d3.y, + 3 * mt2 * d1.z + 6 * mt * t * d2.z + 3 * t2 * d3.z + ).normalize() + } + + /** + * Create a smooth path through waypoints using Catmull-Rom splines. + * Automatically handles the first and last points by duplicating them. + * + * @param waypoints List of points to pass through (minimum 2) + * @param segmentsPerSection Smoothness (higher = smoother) + */ + fun smoothPath(waypoints: List, segmentsPerSection: Int = 16): List { + if (waypoints.size < 2) return waypoints + if (waypoints.size == 2) return listOf(waypoints[0], waypoints[1]) + + // Extend with phantom points for natural curve at endpoints + val extended = buildList { + // Mirror first point + add(waypoints[0].add(waypoints[0].subtract(waypoints[1]))) + addAll(waypoints) + // Mirror last point + val last = waypoints.last() + val secondLast = waypoints[waypoints.size - 2] + add(last.add(last.subtract(secondLast))) + } + + return catmullRomSplinePoints(extended, segmentsPerSection) + } +} diff --git a/src/main/kotlin/com/lambda/graphics/mc/ImGuiWorldText.kt b/src/main/kotlin/com/lambda/graphics/mc/ImGuiWorldText.kt new file mode 100644 index 000000000..735f17de1 --- /dev/null +++ b/src/main/kotlin/com/lambda/graphics/mc/ImGuiWorldText.kt @@ -0,0 +1,172 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.graphics.mc + +import com.lambda.graphics.RenderMain +import imgui.ImGui +import imgui.ImVec2 +import net.minecraft.util.math.Vec3d +import java.awt.Color + +/** + * ImGUI-based world text renderer. + * Projects world coordinates to screen space and draws text using ImGUI. + * + * Usage: + * ```kotlin + * // In a GuiEvent.NewFrame listener + * ImGuiWorldText.drawText(entity.pos, "Label", Color.WHITE) + * ``` + */ +object ImGuiWorldText { + + /** + * Draw text at a world position using ImGUI. + * + * @param worldPos World position for the text + * @param text The text to render + * @param color Text color + * @param centered Whether to center the text horizontally + * @param offsetY Vertical offset in screen pixels (negative = up) + */ + fun drawText( + worldPos: Vec3d, + text: String, + color: Color = Color.WHITE, + centered: Boolean = true, + offsetY: Float = 0f + ) { + val screen = RenderMain.worldToScreen(worldPos) ?: return + + val drawList = ImGui.getBackgroundDrawList() + val colorInt = colorToImGui(color) + + val x = if (centered) { + val textSize = ImVec2() + ImGui.calcTextSize(textSize, text) + screen.x - textSize.x / 2f + } else { + screen.x + } + + drawList.addText(x, screen.y + offsetY, colorInt, text) + } + + /** + * Draw text with a shadow/outline effect. + */ + fun drawTextWithShadow( + worldPos: Vec3d, + text: String, + color: Color = Color.WHITE, + shadowColor: Color = Color.BLACK, + centered: Boolean = true, + offsetY: Float = 0f + ) { + val screen = RenderMain.worldToScreen(worldPos) ?: return + + val drawList = ImGui.getBackgroundDrawList() + val textSize = ImVec2() + ImGui.calcTextSize(textSize, text) + + val x = if (centered) screen.x - textSize.x / 2f else screen.x + val y = screen.y + offsetY + + // Draw shadow (offset by 1 pixel) + val shadowInt = colorToImGui(shadowColor) + drawList.addText(x + 1f, y + 1f, shadowInt, text) + + // Draw main text + val colorInt = colorToImGui(color) + drawList.addText(x, y, colorInt, text) + } + + /** + * Draw multiple lines of text stacked vertically. + */ + fun drawMultilineText( + worldPos: Vec3d, + lines: List, + color: Color = Color.WHITE, + centered: Boolean = true, + lineSpacing: Float = 12f, + offsetY: Float = 0f + ) { + val screen = RenderMain.worldToScreen(worldPos) ?: return + + val drawList = ImGui.getBackgroundDrawList() + val colorInt = colorToImGui(color) + + lines.forEachIndexed { index, line -> + val textSize = ImVec2() + ImGui.calcTextSize(textSize, line) + + val x = if (centered) screen.x - textSize.x / 2f else screen.x + val y = screen.y + offsetY + (index * lineSpacing) + + drawList.addText(x, y, colorInt, line) + } + } + + /** + * Draw text with a background box. + */ + fun drawTextWithBackground( + worldPos: Vec3d, + text: String, + textColor: Color = Color.WHITE, + backgroundColor: Color = Color(0, 0, 0, 128), + centered: Boolean = true, + padding: Float = 4f, + offsetY: Float = 0f + ) { + val screen = RenderMain.worldToScreen(worldPos) ?: return + + val drawList = ImGui.getBackgroundDrawList() + val textSize = ImVec2() + ImGui.calcTextSize(textSize, text) + + val x = if (centered) screen.x - textSize.x / 2f else screen.x + val y = screen.y + offsetY + + // Draw background + val bgInt = colorToImGui(backgroundColor) + drawList.addRectFilled( + x - padding, + y - padding, + x + textSize.x + padding, + y + textSize.y + padding, + bgInt, + 2f // corner rounding + ) + + // Draw text + val colorInt = colorToImGui(textColor) + drawList.addText(x, y, colorInt, text) + } + + /** + * Convert java.awt.Color to ImGui color format (ABGR) + */ + private fun colorToImGui(color: Color): Int { + return (color.alpha shl 24) or + (color.blue shl 16) or + (color.green shl 8) or + color.red + } +} diff --git a/src/main/kotlin/com/lambda/graphics/mc/InterpolatedRegionESP.kt b/src/main/kotlin/com/lambda/graphics/mc/InterpolatedRegionESP.kt new file mode 100644 index 000000000..8b2b0b4b7 --- /dev/null +++ b/src/main/kotlin/com/lambda/graphics/mc/InterpolatedRegionESP.kt @@ -0,0 +1,139 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.graphics.mc + +import com.lambda.graphics.esp.RegionESP +import com.lambda.graphics.esp.ShapeScope +import java.util.concurrent.ConcurrentHashMap +import kotlin.math.floor + +/** + * Interpolated region-based ESP system for smooth entity rendering. + * + * Unlike TransientRegionESP which rebuilds every tick, this system stores both previous and current + * frame data and interpolates between them during rendering for smooth movement at any framerate. + */ +class InterpolatedRegionESP(name: String, depthTest: Boolean = false) : RegionESP(name, depthTest) { + // Current frame builders (being populated this tick) + private val currBuilders = ConcurrentHashMap() + + // Previous frame data (uploaded last tick) + private val prevBuilders = ConcurrentHashMap() + + // Interpolated collectors for rendering (computed each frame) + private val interpolatedCollectors = + ConcurrentHashMap() + + // Track if we need to re-interpolate + private var lastTickDelta = -1f + private var needsInterpolation = true + + override fun shapes(x: Double, y: Double, z: Double, block: ShapeScope.() -> Unit) { + val key = getRegionKey(x, y, z) + val scope = + currBuilders.getOrPut(key) { + val size = RenderRegion.REGION_SIZE + val rx = (size * floor(x / size)).toInt() + val ry = (size * floor(y / size)).toInt() + val rz = (size * floor(z / size)).toInt() + ShapeScope(RenderRegion(rx, ry, rz), collectShapes = true) + } + scope.apply(block) + } + + override fun clear() { + prevBuilders.clear() + currBuilders.clear() + interpolatedCollectors.clear() + } + + fun tick() { + prevBuilders.clear() + prevBuilders.putAll(currBuilders) + currBuilders.clear() + needsInterpolation = true + } + + override fun upload() { + needsInterpolation = true + } + + override fun render(tickDelta: Float) { + if (needsInterpolation || lastTickDelta != tickDelta) { + interpolate(tickDelta) + uploadInterpolated() + lastTickDelta = tickDelta + needsInterpolation = false + } + super.render(tickDelta) + } + + private fun interpolate(tickDelta: Float) { + interpolatedCollectors.clear() + (prevBuilders.keys + currBuilders.keys).toSet().forEach { key -> + val prevScope = prevBuilders[key] + val currScope = currBuilders[key] + val collector = RegionVertexCollector() + val region = currScope?.region ?: prevScope?.region ?: return@forEach + + val prevShapes = prevScope?.shapes?.associateBy { it.id } ?: emptyMap() + val currShapes = currScope?.shapes?.associateBy { it.id } ?: emptyMap() + + val allIds = (prevShapes.keys + currShapes.keys).toSet() + + for (id in allIds) { + val prev = prevShapes[id] + val curr = currShapes[id] + + when { + prev != null && curr != null -> { + curr.renderInterpolated(prev, tickDelta, collector, region) + } + curr != null -> { + // New shape - just render + curr.renderInterpolated(curr, 1.0f, collector, region) + } + prev != null -> { + // Disappeared - render at previous position + prev.renderInterpolated(prev, 1.0f, collector, region) + } + } + } + + if (collector.faceVertices.isNotEmpty() || collector.edgeVertices.isNotEmpty()) { + interpolatedCollectors[key] = collector + } + } + } + + private fun uploadInterpolated() { + val activeKeys = interpolatedCollectors.keys.toSet() + interpolatedCollectors.forEach { (key, collector) -> + val region = currBuilders[key]?.region ?: prevBuilders[key]?.region ?: return@forEach + + val renderer = renderers.getOrPut(key) { RegionRenderer(region) } + renderer.upload(collector) + } + + renderers.forEach { (key, renderer) -> + if (key !in activeKeys) { + renderer.clearData() + } + } + } +} diff --git a/src/main/kotlin/com/lambda/graphics/mc/LambdaRenderPipelines.kt b/src/main/kotlin/com/lambda/graphics/mc/LambdaRenderPipelines.kt new file mode 100644 index 000000000..350a40a77 --- /dev/null +++ b/src/main/kotlin/com/lambda/graphics/mc/LambdaRenderPipelines.kt @@ -0,0 +1,119 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.graphics.mc + +import com.lambda.core.Loadable +import com.mojang.blaze3d.pipeline.BlendFunction +import com.mojang.blaze3d.pipeline.RenderPipeline +import com.mojang.blaze3d.platform.DepthTestFunction +import com.mojang.blaze3d.vertex.VertexFormat +import net.minecraft.client.gl.RenderPipelines +import net.minecraft.client.render.VertexFormats +import net.minecraft.util.Identifier + +object LambdaRenderPipelines : Loadable { + override val priority: Int + get() = 100 // High priority to ensure pipelines are ready early + + /** + * Base snippet for Lambda ESP rendering. Includes transforms, projection, and a custom + * per-region uniform. + */ + private val LAMBDA_ESP_SNIPPET = + RenderPipeline.builder(RenderPipelines.TRANSFORMS_AND_PROJECTION_SNIPPET).buildSnippet() + + /** + * Pipeline for ESP lines/outlines. + * - Uses MC's line rendering with per-vertex line width + * - No depth write for overlapping + * - No culling + */ + val ESP_LINES: RenderPipeline = + RenderPipelines.register( + RenderPipeline.builder(LAMBDA_ESP_SNIPPET, RenderPipelines.GLOBALS_SNIPPET) + .withLocation(Identifier.of("lambda", "pipeline/esp_lines")) + .withVertexShader(Identifier.of("lambda", "core/advanced_lines")) + .withFragmentShader(Identifier.of("lambda", "core/advanced_lines")) + .withBlend(BlendFunction.TRANSLUCENT) + .withDepthWrite(false) + .withDepthTestFunction(DepthTestFunction.LEQUAL_DEPTH_TEST) + .withCull(false) + .withVertexFormat( + VertexFormats.POSITION_COLOR_NORMAL_LINE_WIDTH, + VertexFormat.DrawMode.QUADS + ) + .build() + ) + + /** Pipeline for ESP lines that render through walls. */ + val ESP_LINES_THROUGH: RenderPipeline = + RenderPipelines.register( + RenderPipeline.builder(LAMBDA_ESP_SNIPPET, RenderPipelines.GLOBALS_SNIPPET) + .withLocation(Identifier.of("lambda", "pipeline/esp_lines_through")) + .withVertexShader(Identifier.of("lambda", "core/advanced_lines")) + .withFragmentShader(Identifier.of("lambda", "core/advanced_lines")) + .withBlend(BlendFunction.TRANSLUCENT) + .withDepthWrite(false) + .withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST) + .withCull(false) + .withVertexFormat( + VertexFormats.POSITION_COLOR_NORMAL_LINE_WIDTH, + VertexFormat.DrawMode.QUADS + ) + .build() + ) + + /** + * Pipeline for quad-based ESP (compatible with existing shape building). Uses QUADS draw mode + * which MC converts to triangles internally. + */ + val ESP_QUADS: RenderPipeline = + RenderPipelines.register( + RenderPipeline.builder(LAMBDA_ESP_SNIPPET) + .withLocation(Identifier.of("lambda", "pipeline/esp_quads")) + .withVertexShader(Identifier.ofVanilla("core/position_color")) + .withFragmentShader(Identifier.ofVanilla("core/position_color")) + .withBlend(BlendFunction.TRANSLUCENT) + .withDepthWrite(false) + .withDepthTestFunction(DepthTestFunction.LEQUAL_DEPTH_TEST) + .withCull(false) + .withVertexFormat( + VertexFormats.POSITION_COLOR, + VertexFormat.DrawMode.QUADS + ) + .build() + ) + + /** Pipeline for quad-based ESP that renders through walls. */ + val ESP_QUADS_THROUGH: RenderPipeline = + RenderPipelines.register( + RenderPipeline.builder(LAMBDA_ESP_SNIPPET) + .withLocation(Identifier.of("lambda", "pipeline/esp_quads_through")) + .withVertexShader(Identifier.ofVanilla("core/position_color")) + .withFragmentShader(Identifier.ofVanilla("core/position_color")) + .withBlend(BlendFunction.TRANSLUCENT) + .withDepthWrite(false) + .withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST) + .withCull(false) + .withVertexFormat( + VertexFormats.POSITION_COLOR, + VertexFormat.DrawMode.QUADS + ) + .build() + ) +} diff --git a/src/main/kotlin/com/lambda/graphics/mc/RegionRenderer.kt b/src/main/kotlin/com/lambda/graphics/mc/RegionRenderer.kt new file mode 100644 index 000000000..8d7f36f4a --- /dev/null +++ b/src/main/kotlin/com/lambda/graphics/mc/RegionRenderer.kt @@ -0,0 +1,141 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.graphics.mc + +import com.lambda.Lambda.mc +import com.mojang.blaze3d.buffers.GpuBuffer +import com.mojang.blaze3d.systems.RenderPass +import com.mojang.blaze3d.systems.RenderSystem +import com.mojang.blaze3d.vertex.VertexFormat +import java.util.* + +/** + * Region-based renderer for ESP rendering using MC 1.21.11's new render pipeline. + * + * This renderer manages the lifecycle of dedicated GPU buffers for a specific region and provides + * methods to render them within a RenderPass. + * + * @param region The render region this renderer is associated with + */ +class RegionRenderer(val region: RenderRegion) { + + // Dedicated GPU buffers for faces and edges + private var faceVertexBuffer: GpuBuffer? = null + private var edgeVertexBuffer: GpuBuffer? = null + + // Index counts for draw calls + private var faceIndexCount = 0 + private var edgeIndexCount = 0 + + // State tracking + private var hasData = false + + /** + * Upload collected vertices from an external collector. This must be called on the main/render + * thread. + * + * @param collector The collector containing the geometry to upload + */ + fun upload(collector: RegionVertexCollector) { + val result = collector.upload() + + // Cleanup old buffers + faceVertexBuffer?.close() + edgeVertexBuffer?.close() + + // Assign new buffers and counts + faceVertexBuffer = result.faces?.buffer + faceIndexCount = result.faces?.indexCount ?: 0 + + edgeVertexBuffer = result.edges?.buffer + edgeIndexCount = result.edges?.indexCount ?: 0 + + hasData = faceVertexBuffer != null || edgeVertexBuffer != null + } + + /** + * Render faces using the given render pass. + * + * @param renderPass The active RenderPass to record commands into + */ + fun renderFaces(renderPass: RenderPass) { + val vb = faceVertexBuffer ?: return + if (faceIndexCount == 0) return + + renderPass.setVertexBuffer(0, vb) + // Use vanilla's sequential index buffer for quads + val shapeIndexBuffer = RenderSystem.getSequentialBuffer(VertexFormat.DrawMode.QUADS) + val indexBuffer = shapeIndexBuffer.getIndexBuffer(faceIndexCount) + + renderPass.setIndexBuffer(indexBuffer, shapeIndexBuffer.indexType) + renderPass.drawIndexed(0, 0, faceIndexCount, 1) + } + + /** + * Render edges using the given render pass. + * + * @param renderPass The active RenderPass to record commands into + */ + fun renderEdges(renderPass: RenderPass) { + val vb = edgeVertexBuffer ?: return + if (edgeIndexCount == 0) return + + renderPass.setVertexBuffer(0, vb) + // Use vanilla's sequential index buffer for quads + val shapeIndexBuffer = RenderSystem.getSequentialBuffer(VertexFormat.DrawMode.QUADS) + val indexBuffer = shapeIndexBuffer.getIndexBuffer(edgeIndexCount) + + renderPass.setIndexBuffer(indexBuffer, shapeIndexBuffer.indexType) + renderPass.drawIndexed(0, 0, edgeIndexCount, 1) + } + + /** Clear all geometry data and release GPU resources. */ + fun clearData() { + faceVertexBuffer?.close() + edgeVertexBuffer?.close() + faceVertexBuffer = null + edgeVertexBuffer = null + faceIndexCount = 0 + edgeIndexCount = 0 + hasData = false + } + + /** Check if this renderer has any data to render. */ + fun hasData(): Boolean = hasData + + /** Clean up all resources. */ + fun close() { + clearData() + } + + companion object { + /** Helper to create a render pass targeting the main framebuffer. */ + fun createRenderPass(label: String): RenderPass? { + val framebuffer = mc.framebuffer ?: return null + return RenderSystem.getDevice() + .createCommandEncoder() + .createRenderPass( + { label }, + framebuffer.colorAttachmentView, + OptionalInt.empty(), + framebuffer.depthAttachmentView, + OptionalDouble.empty() + ) + } + } +} diff --git a/src/main/kotlin/com/lambda/graphics/mc/RegionShapeBuilder.kt b/src/main/kotlin/com/lambda/graphics/mc/RegionShapeBuilder.kt new file mode 100644 index 000000000..8b33498be --- /dev/null +++ b/src/main/kotlin/com/lambda/graphics/mc/RegionShapeBuilder.kt @@ -0,0 +1,758 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.graphics.mc + +import com.lambda.Lambda.mc +import com.lambda.graphics.renderer.esp.DirectionMask +import com.lambda.graphics.renderer.esp.DirectionMask.hasDirection +import com.lambda.graphics.renderer.esp.DynamicAABB +import com.lambda.module.modules.client.StyleEditor +import com.lambda.threading.runSafe +import com.lambda.util.BlockUtils.blockState +import com.lambda.util.extension.partialTicks +import net.minecraft.block.BlockState +import net.minecraft.block.entity.BlockEntity +import net.minecraft.entity.Entity +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Box +import net.minecraft.util.math.MathHelper.lerp +import net.minecraft.util.math.Vec3d +import net.minecraft.util.shape.VoxelShape +import java.awt.Color +import kotlin.math.min +import kotlin.math.sqrt + +/** + * Shape builder for region-based rendering. All coordinates are automatically converted to + * region-relative positions. + * + * This class provides drawing primitives for region-based rendering and collects vertex data in thread-safe collections + * for later upload to MC's BufferBuilder. + * + * @param region The render region (provides origin for coordinate conversion) + */ +class RegionShapeBuilder(val region: RenderRegion) { + val collector = RegionVertexCollector() + + val lineWidth: Float + get() = StyleEditor.outlineWidth.toFloat() + + fun box( + entity: BlockEntity, + filled: Color, + outline: Color, + sides: Int = DirectionMask.ALL, + mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And + ) = box(entity.pos, entity.cachedState, filled, outline, sides, mode) + + fun box( + entity: Entity, + filled: Color, + outline: Color, + sides: Int = DirectionMask.ALL, + mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And + ) = box(entity.boundingBox, filled, outline, sides, mode) + + /** Convert world coordinates to region-relative. */ + private fun toRelative(x: Double, y: Double, z: Double) = + Triple( + (x - region.originX).toFloat(), + (y - region.originY).toFloat(), + (z - region.originZ).toFloat() + ) + + /** Add a colored quad face (filled rectangle). */ + fun filled( + box: Box, + bottomColor: Color, + topColor: Color = bottomColor, + sides: Int = DirectionMask.ALL + ) { + val (x1, y1, z1) = toRelative(box.minX, box.minY, box.minZ) + val (x2, y2, z2) = toRelative(box.maxX, box.maxY, box.maxZ) + + // Bottom-left-back, bottom-left-front, etc. + if (sides.hasDirection(DirectionMask.EAST)) { + // East face (+X) + faceVertex(x2, y1, z1, bottomColor) + faceVertex(x2, y2, z1, topColor) + faceVertex(x2, y2, z2, topColor) + faceVertex(x2, y1, z2, bottomColor) + } + if (sides.hasDirection(DirectionMask.WEST)) { + // West face (-X) + faceVertex(x1, y1, z1, bottomColor) + faceVertex(x1, y1, z2, bottomColor) + faceVertex(x1, y2, z2, topColor) + faceVertex(x1, y2, z1, topColor) + } + if (sides.hasDirection(DirectionMask.UP)) { + // Top face (+Y) + faceVertex(x1, y2, z1, topColor) + faceVertex(x1, y2, z2, topColor) + faceVertex(x2, y2, z2, topColor) + faceVertex(x2, y2, z1, topColor) + } + if (sides.hasDirection(DirectionMask.DOWN)) { + // Bottom face (-Y) + faceVertex(x1, y1, z1, bottomColor) + faceVertex(x2, y1, z1, bottomColor) + faceVertex(x2, y1, z2, bottomColor) + faceVertex(x1, y1, z2, bottomColor) + } + if (sides.hasDirection(DirectionMask.SOUTH)) { + // South face (+Z) + faceVertex(x1, y1, z2, bottomColor) + faceVertex(x2, y1, z2, bottomColor) + faceVertex(x2, y2, z2, topColor) + faceVertex(x1, y2, z2, topColor) + } + if (sides.hasDirection(DirectionMask.NORTH)) { + // North face (-Z) + faceVertex(x1, y1, z1, bottomColor) + faceVertex(x1, y2, z1, topColor) + faceVertex(x2, y2, z1, topColor) + faceVertex(x2, y1, z1, bottomColor) + } + } + + fun filled(box: Box, color: Color, sides: Int = DirectionMask.ALL) = + filled(box, color, color, sides) + + fun filled(box: DynamicAABB, color: Color, sides: Int = DirectionMask.ALL) { + val pair = box.pair ?: return + val prev = pair.first + val curr = pair.second + val tickDelta = mc.partialTicks + val interpolated = Box( + lerp(tickDelta, prev.minX, curr.minX), + lerp(tickDelta, prev.minY, curr.minY), + lerp(tickDelta, prev.minZ, curr.minZ), + lerp(tickDelta, prev.maxX, curr.maxX), + lerp(tickDelta, prev.maxY, curr.maxY), + lerp(tickDelta, prev.maxZ, curr.maxZ) + ) + filled(interpolated, color, sides) + } + + fun filled( + pos: BlockPos, + state: BlockState, + color: Color, + sides: Int = DirectionMask.ALL + ) = runSafe { + val shape = state.getOutlineShape(world, pos) + if (shape.isEmpty) { + filled(Box(pos), color, sides) + } else { + filled(shape.offset(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()), color, sides) + } + } + + fun filled(pos: BlockPos, color: Color, sides: Int = DirectionMask.ALL) = runSafe { + filled(pos, blockState(pos), color, sides) + } + + fun filled(pos: BlockPos, entity: BlockEntity, color: Color, sides: Int = DirectionMask.ALL) = + filled(pos, entity.cachedState, color, sides) + + fun filled(shape: VoxelShape, color: Color, sides: Int = DirectionMask.ALL) { + shape.boundingBoxes.forEach { filled(it, color, color, sides) } + } + + /** Add outline (lines) for a box. */ + fun outline( + box: Box, + bottomColor: Color, + topColor: Color = bottomColor, + sides: Int = DirectionMask.ALL, + mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And, + thickness: Float = lineWidth + ) { + val (x1, y1, z1) = toRelative(box.minX, box.minY, box.minZ) + val (x2, y2, z2) = toRelative(box.maxX, box.maxY, box.maxZ) + + val hasEast = sides.hasDirection(DirectionMask.EAST) + val hasWest = sides.hasDirection(DirectionMask.WEST) + val hasUp = sides.hasDirection(DirectionMask.UP) + val hasDown = sides.hasDirection(DirectionMask.DOWN) + val hasSouth = sides.hasDirection(DirectionMask.SOUTH) + val hasNorth = sides.hasDirection(DirectionMask.NORTH) + + // Top edges + if (mode.check(hasUp, hasNorth)) line(x1, y2, z1, x2, y2, z1, topColor, topColor, thickness) + if (mode.check(hasUp, hasSouth)) line(x1, y2, z2, x2, y2, z2, topColor, topColor, thickness) + if (mode.check(hasUp, hasWest)) line(x1, y2, z1, x1, y2, z2, topColor, topColor, thickness) + if (mode.check(hasUp, hasEast)) line(x2, y2, z2, x2, y2, z1, topColor, topColor, thickness) + + // Bottom edges + if (mode.check(hasDown, hasNorth)) line(x1, y1, z1, x2, y1, z1, bottomColor, bottomColor, thickness) + if (mode.check(hasDown, hasSouth)) line(x1, y1, z2, x2, y1, z2, bottomColor, bottomColor, thickness) + if (mode.check(hasDown, hasWest)) line(x1, y1, z1, x1, y1, z2, bottomColor, bottomColor, thickness) + if (mode.check(hasDown, hasEast)) line(x2, y1, z1, x2, y1, z2, bottomColor, bottomColor, thickness) + + // Vertical edges + if (mode.check(hasWest, hasNorth)) line(x1, y2, z1, x1, y1, z1, topColor, bottomColor, thickness) + if (mode.check(hasNorth, hasEast)) line(x2, y2, z1, x2, y1, z1, topColor, bottomColor, thickness) + if (mode.check(hasEast, hasSouth)) line(x2, y2, z2, x2, y1, z2, topColor, bottomColor, thickness) + if (mode.check(hasSouth, hasWest)) line(x1, y2, z2, x1, y1, z2, topColor, bottomColor, thickness) + } + + fun outline( + box: Box, + color: Color, + sides: Int = DirectionMask.ALL, + mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And, + thickness: Float = lineWidth + ) = outline(box, color, color, sides, mode, thickness) + + fun outline( + box: DynamicAABB, + color: Color, + sides: Int = DirectionMask.ALL, + mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And, + thickness: Float = lineWidth + ) { + val pair = box.pair ?: return + val prev = pair.first + val curr = pair.second + val tickDelta = mc.partialTicks + val interpolated = Box( + lerp(tickDelta, prev.minX, curr.minX), + lerp(tickDelta, prev.minY, curr.minY), + lerp(tickDelta, prev.minZ, curr.minZ), + lerp(tickDelta, prev.maxX, curr.maxX), + lerp(tickDelta, prev.maxY, curr.maxY), + lerp(tickDelta, prev.maxZ, curr.maxZ) + ) + outline(interpolated, color, sides, mode, thickness) + } + + fun outline( + pos: BlockPos, + state: BlockState, + color: Color, + sides: Int = DirectionMask.ALL, + mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And, + thickness: Float = lineWidth + ) = runSafe { + val shape = state.getOutlineShape(world, pos) + if (shape.isEmpty) { + outline(Box(pos), color, sides, mode, thickness) + } else { + outline(shape.offset(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()), color, sides, mode, thickness) + } + } + + fun outline( + pos: BlockPos, + color: Color, + sides: Int = DirectionMask.ALL, + mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And, + thickness: Float = lineWidth + ) = runSafe { outline(pos, blockState(pos), color, sides, mode, thickness) } + + fun outline( + pos: BlockPos, + entity: BlockEntity, + color: Color, + sides: Int = DirectionMask.ALL, + mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And, + thickness: Float = lineWidth + ) = runSafe { outline(pos, entity.cachedState, color, sides, mode, thickness) } + + fun outline( + shape: VoxelShape, + color: Color, + sides: Int = DirectionMask.ALL, + mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And, + thickness: Float = lineWidth + ) { + shape.boundingBoxes.forEach { outline(it, color, sides, mode, thickness) } + } + + /** Add both filled and outline for a box. */ + fun box( + pos: BlockPos, + state: BlockState, + filledColor: Color, + outlineColor: Color, + sides: Int = DirectionMask.ALL, + mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And, + thickness: Float = lineWidth + ) = runSafe { + filled(pos, state, filledColor, sides) + outline(pos, state, outlineColor, sides, mode, thickness) + } + + fun box( + pos: BlockPos, + filledColor: Color, + outlineColor: Color, + sides: Int = DirectionMask.ALL, + mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And, + thickness: Float = lineWidth + ) = runSafe { + filled(pos, filledColor, sides) + outline(pos, outlineColor, sides, mode, thickness) + } + + fun box( + box: Box, + filledColor: Color, + outlineColor: Color, + sides: Int = DirectionMask.ALL, + mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And, + thickness: Float = lineWidth + ) { + filled(box, filledColor, sides) + outline(box, outlineColor, sides, mode, thickness) + } + + fun box( + box: DynamicAABB, + filledColor: Color, + outlineColor: Color, + sides: Int = DirectionMask.ALL, + mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And, + thickness: Float = lineWidth + ) { + filled(box, filledColor, sides) + outline(box, outlineColor, sides, mode, thickness) + } + + fun box( + entity: BlockEntity, + filled: Color, + outlineColor: Color, + sides: Int = DirectionMask.ALL, + mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And, + thickness: Float = lineWidth + ) = runSafe { + filled(entity.pos, entity, filled, sides) + outline(entity.pos, entity, outlineColor, sides, mode, thickness) + } + + fun box( + entity: Entity, + filled: Color, + outlineColor: Color, + sides: Int = DirectionMask.ALL, + mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And, + thickness: Float = lineWidth + ) = runSafe { + filled(entity.boundingBox, filled, sides) + outline(entity.boundingBox, outlineColor, sides, mode, thickness) + } + + private fun faceVertex(x: Float, y: Float, z: Float, color: Color) { + collector.addFaceVertex(x, y, z, color) + } + + private fun line( + x1: Float, + y1: Float, + z1: Float, + x2: Float, + y2: Float, + z2: Float, + color: Color, + width: Float = lineWidth + ) { + line(x1, y1, z1, x2, y2, z2, color, color, width) + } + + private fun line( + x1: Float, + y1: Float, + z1: Float, + x2: Float, + y2: Float, + z2: Float, + color1: Color, + color2: Color, + width: Float = lineWidth + ) { + // Calculate segment vector (dx, dy, dz) + val dx = x2 - x1 + val dy = y2 - y1 + val dz = z2 - z1 + + // Quad-based lines need 4 vertices per segment + // We pass the full vector as 'Normal' so the shader knows where the other end is + collector.addEdgeVertex(x1, y1, z1, color1, dx, dy, dz, width) + collector.addEdgeVertex(x1, y1, z1, color1, dx, dy, dz, width) + collector.addEdgeVertex(x2, y2, z2, color2, dx, dy, dz, width) + collector.addEdgeVertex(x2, y2, z2, color2, dx, dy, dz, width) + } + + /** + * Draw a dashed line between two world positions. + * + * @param start Start position in world coordinates + * @param end End position in world coordinates + * @param color Line color + * @param dashLength Length of each dash in blocks + * @param gapLength Length of each gap in blocks + * @param width Line width (uses default if null) + */ + fun dashedLine( + start: Vec3d, + end: Vec3d, + color: Color, + dashLength: Double = 0.5, + gapLength: Double = 0.25, + width: Float = lineWidth + ) { + val direction = end.subtract(start) + val totalLength = direction.length() + if (totalLength < 0.001) return + + val normalizedDir = direction.normalize() + var pos = 0.0 + var isDash = true + + while (pos < totalLength) { + val segmentLength = if (isDash) dashLength else gapLength + val segmentEnd = min(pos + segmentLength, totalLength) + + if (isDash) { + val segStart = start.add(normalizedDir.multiply(pos)) + val segEnd = start.add(normalizedDir.multiply(segmentEnd)) + + val (x1, y1, z1) = toRelative(segStart.x, segStart.y, segStart.z) + val (x2, y2, z2) = toRelative(segEnd.x, segEnd.y, segEnd.z) + + lineWithWidth(x1, y1, z1, x2, y2, z2, color, width) + } + + pos = segmentEnd + isDash = !isDash + } + } + + /** Draw a dashed outline for a box. */ + fun dashedOutline( + box: Box, + color: Color, + dashLength: Double = 0.5, + gapLength: Double = 0.25, + sides: Int = DirectionMask.ALL, + mode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.And + ) { + val hasEast = sides.hasDirection(DirectionMask.EAST) + val hasWest = sides.hasDirection(DirectionMask.WEST) + val hasUp = sides.hasDirection(DirectionMask.UP) + val hasDown = sides.hasDirection(DirectionMask.DOWN) + val hasSouth = sides.hasDirection(DirectionMask.SOUTH) + val hasNorth = sides.hasDirection(DirectionMask.NORTH) + + // Top edges + if (mode.check(hasUp, hasNorth)) + dashedLine( + Vec3d(box.minX, box.maxY, box.minZ), + Vec3d(box.maxX, box.maxY, box.minZ), + color, + dashLength, + gapLength + ) + if (mode.check(hasUp, hasSouth)) + dashedLine( + Vec3d(box.minX, box.maxY, box.maxZ), + Vec3d(box.maxX, box.maxY, box.maxZ), + color, + dashLength, + gapLength + ) + if (mode.check(hasUp, hasWest)) + dashedLine( + Vec3d(box.minX, box.maxY, box.minZ), + Vec3d(box.minX, box.maxY, box.maxZ), + color, + dashLength, + gapLength + ) + if (mode.check(hasUp, hasEast)) + dashedLine( + Vec3d(box.maxX, box.maxY, box.maxZ), + Vec3d(box.maxX, box.maxY, box.minZ), + color, + dashLength, + gapLength + ) + + // Bottom edges + if (mode.check(hasDown, hasNorth)) + dashedLine( + Vec3d(box.minX, box.minY, box.minZ), + Vec3d(box.maxX, box.minY, box.minZ), + color, + dashLength, + gapLength + ) + if (mode.check(hasDown, hasSouth)) + dashedLine( + Vec3d(box.minX, box.minY, box.maxZ), + Vec3d(box.maxX, box.minY, box.maxZ), + color, + dashLength, + gapLength + ) + if (mode.check(hasDown, hasWest)) + dashedLine( + Vec3d(box.minX, box.minY, box.minZ), + Vec3d(box.minX, box.minY, box.maxZ), + color, + dashLength, + gapLength + ) + if (mode.check(hasDown, hasEast)) + dashedLine( + Vec3d(box.maxX, box.minY, box.minZ), + Vec3d(box.maxX, box.minY, box.maxZ), + color, + dashLength, + gapLength + ) + + // Vertical edges + if (mode.check(hasWest, hasNorth)) + dashedLine( + Vec3d(box.minX, box.maxY, box.minZ), + Vec3d(box.minX, box.minY, box.minZ), + color, + dashLength, + gapLength + ) + if (mode.check(hasNorth, hasEast)) + dashedLine( + Vec3d(box.maxX, box.maxY, box.minZ), + Vec3d(box.maxX, box.minY, box.minZ), + color, + dashLength, + gapLength + ) + if (mode.check(hasEast, hasSouth)) + dashedLine( + Vec3d(box.maxX, box.maxY, box.maxZ), + Vec3d(box.maxX, box.minY, box.maxZ), + color, + dashLength, + gapLength + ) + if (mode.check(hasSouth, hasWest)) + dashedLine( + Vec3d(box.minX, box.maxY, box.maxZ), + Vec3d(box.minX, box.minY, box.maxZ), + color, + dashLength, + gapLength + ) + } + + /** Draw a line between two world positions. */ + fun line(start: Vec3d, end: Vec3d, color: Color, width: Float = lineWidth) { + val (x1, y1, z1) = toRelative(start.x, start.y, start.z) + val (x2, y2, z2) = toRelative(end.x, end.y, end.z) + lineWithWidth(x1, y1, z1, x2, y2, z2, color, width) + } + + /** Draw a polyline through a list of points. */ + fun polyline(points: List, color: Color, width: Float = lineWidth) { + if (points.size < 2) return + for (i in 0 until points.size - 1) { + line(points[i], points[i + 1], color, width) + } + } + + /** Draw a dashed polyline through a list of points. */ + fun dashedPolyline( + points: List, + color: Color, + dashLength: Double = 0.5, + gapLength: Double = 0.25, + width: Float = lineWidth + ) { + if (points.size < 2) return + for (i in 0 until points.size - 1) { + dashedLine(points[i], points[i + 1], color, dashLength, gapLength, width) + } + } + + /** + * Draw a quadratic Bezier curve. + * + * @param p0 Start point + * @param p1 Control point + * @param p2 End point + * @param color Line color + * @param segments Number of line segments (higher = smoother) + */ + fun quadraticBezier( + p0: Vec3d, + p1: Vec3d, + p2: Vec3d, + color: Color, + segments: Int = 16, + width: Float = lineWidth + ) { + val points = CurveUtils.quadraticBezierPoints(p0, p1, p2, segments) + polyline(points, color, width) + } + + /** + * Draw a cubic Bezier curve. + * + * @param p0 Start point + * @param p1 First control point + * @param p2 Second control point + * @param p3 End point + * @param color Line color + * @param segments Number of line segments (higher = smoother) + */ + fun cubicBezier( + p0: Vec3d, + p1: Vec3d, + p2: Vec3d, + p3: Vec3d, + color: Color, + segments: Int = 32, + width: Float = lineWidth + ) { + val points = CurveUtils.cubicBezierPoints(p0, p1, p2, p3, segments) + polyline(points, color, width) + } + + /** + * Draw a Catmull-Rom spline that passes through all control points. + * + * @param controlPoints List of points the spline should pass through (minimum 4) + * @param color Line color + * @param segmentsPerSection Segments between each pair of control points + */ + fun catmullRomSpline( + controlPoints: List, + color: Color, + segmentsPerSection: Int = 16, + width: Float = lineWidth + ) { + val points = CurveUtils.catmullRomSplinePoints(controlPoints, segmentsPerSection) + polyline(points, color, width) + } + + /** + * Draw a smooth path through waypoints using Catmull-Rom splines. Handles endpoints + * naturally by mirroring. + * + * @param waypoints List of points to pass through (minimum 2) + * @param color Line color + * @param segmentsPerSection Smoothness (higher = smoother) + */ + fun smoothPath( + waypoints: List, + color: Color, + segmentsPerSection: Int = 16, + width: Float = lineWidth + ) { + val points = CurveUtils.smoothPath(waypoints, segmentsPerSection) + polyline(points, color, width) + } + + /** Draw a dashed Bezier curve. */ + fun dashedCubicBezier( + p0: Vec3d, + p1: Vec3d, + p2: Vec3d, + p3: Vec3d, + color: Color, + segments: Int = 32, + dashLength: Double = 0.5, + gapLength: Double = 0.25, + width: Float = lineWidth + ) { + val points = CurveUtils.cubicBezierPoints(p0, p1, p2, p3, segments) + dashedPolyline(points, color, dashLength, gapLength, width) + } + + /** Draw a dashed smooth path. */ + fun dashedSmoothPath( + waypoints: List, + color: Color, + segmentsPerSection: Int = 16, + dashLength: Double = 0.5, + gapLength: Double = 0.25, + width: Float = lineWidth + ) { + val points = CurveUtils.smoothPath(waypoints, segmentsPerSection) + dashedPolyline(points, color, dashLength, gapLength, width) + } + + /** + * Draw a circle in a plane. + * + * @param center Center of the circle + * @param radius Radius of the circle + * @param normal Normal vector of the plane (determines orientation) + * @param color Line color + * @param segments Number of segments + */ + fun circle( + center: Vec3d, + radius: Double, + normal: Vec3d = Vec3d(0.0, 1.0, 0.0), + color: Color, + segments: Int = 32, + width: Float = lineWidth + ) { + // Create basis vectors perpendicular to normal + val up = + if (kotlin.math.abs(normal.y) < 0.99) Vec3d(0.0, 1.0, 0.0) + else Vec3d(1.0, 0.0, 0.0) + val u = normal.crossProduct(up).normalize() + val v = u.crossProduct(normal).normalize() + + val points = + (0..segments).map { i -> + val angle = 2.0 * Math.PI * i / segments + val x = kotlin.math.cos(angle) * radius + val y = kotlin.math.sin(angle) * radius + center.add(u.multiply(x)).add(v.multiply(y)) + } + + polyline(points, color, width) + } + + private fun lineWithWidth( + x1: Float, + y1: Float, + z1: Float, + x2: Float, + y2: Float, + z2: Float, + color: Color, + width: Float + ) { + val dx = x2 - x1 + val dy = y2 - y1 + val dz = z2 - z1 + collector.addEdgeVertex(x1, y1, z1, color, dx, dy, dz, width) + collector.addEdgeVertex(x1, y1, z1, color, dx, dy, dz, width) + collector.addEdgeVertex(x2, y2, z2, color, dx, dy, dz, width) + collector.addEdgeVertex(x2, y2, z2, color, dx, dy, dz, width) + } +} diff --git a/src/main/kotlin/com/lambda/graphics/mc/RegionVertexCollector.kt b/src/main/kotlin/com/lambda/graphics/mc/RegionVertexCollector.kt new file mode 100644 index 000000000..c82347817 --- /dev/null +++ b/src/main/kotlin/com/lambda/graphics/mc/RegionVertexCollector.kt @@ -0,0 +1,168 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.graphics.mc + +import com.mojang.blaze3d.buffers.GpuBuffer +import com.mojang.blaze3d.systems.RenderSystem +import com.mojang.blaze3d.vertex.VertexFormat +import net.minecraft.client.render.BufferBuilder +import net.minecraft.client.render.VertexFormats +import net.minecraft.client.util.BufferAllocator +import java.awt.Color +import java.util.concurrent.ConcurrentLinkedDeque + +/** + * Thread-safe vertex collector for region-based rendering. + * + * Collects vertex data on background threads using thread-safe collections, then writes to MC's + * BufferBuilder and uploads on the main thread. + */ +class RegionVertexCollector { + val faceVertices = ConcurrentLinkedDeque() + val edgeVertices = ConcurrentLinkedDeque() + + /** Face vertex data (position + color). */ + data class FaceVertex( + val x: Float, + val y: Float, + val z: Float, + val r: Int, + val g: Int, + val b: Int, + val a: Int + ) + + /** Edge vertex data (position + color + normal + line width). */ + data class EdgeVertex( + val x: Float, + val y: Float, + val z: Float, + val r: Int, + val g: Int, + val b: Int, + val a: Int, + val nx: Float, + val ny: Float, + val nz: Float, + val lineWidth: Float + ) + + /** Add a face vertex. */ + fun addFaceVertex(x: Float, y: Float, z: Float, color: Color) { + faceVertices.add(FaceVertex(x, y, z, color.red, color.green, color.blue, color.alpha)) + } + + /** Add an edge vertex. */ + fun addEdgeVertex( + x: Float, + y: Float, + z: Float, + color: Color, + nx: Float, + ny: Float, + nz: Float, + lineWidth: Float + ) { + edgeVertices.add( + EdgeVertex(x, y, z, color.red, color.green, color.blue, color.alpha, nx, ny, nz, lineWidth) + ) + } + + /** + * Upload collected data to GPU buffers. Must be called on the main/render thread. + * + * @return Pair of (faceBuffer, edgeBuffer) and their index counts, or null if no data + */ + fun upload(): UploadResult { + val faces = uploadFaces() + val edges = uploadEdges() + return UploadResult(faces, edges) + } + + private fun uploadFaces(): BufferResult { + if (faceVertices.isEmpty()) return BufferResult(null, 0) + + val vertices = faceVertices.toList() + faceVertices.clear() + + var result: BufferResult? = null + BufferAllocator(vertices.size * 16).use { allocator -> + val builder = + BufferBuilder( + allocator, + VertexFormat.DrawMode.QUADS, + VertexFormats.POSITION_COLOR + ) + + vertices.forEach { v -> builder.vertex(v.x, v.y, v.z).color(v.r, v.g, v.b, v.a) } + + builder.endNullable()?.let { built -> + val gpuDevice = RenderSystem.getDevice() + val buffer = + gpuDevice.createBuffer( + { "Lambda ESP Face Buffer" }, + GpuBuffer.USAGE_VERTEX, + built.buffer + ) + result = BufferResult(buffer, built.drawParameters.indexCount()) + built.close() + } + } + return result ?: BufferResult(null, 0) + } + + private fun uploadEdges(): BufferResult { + if (edgeVertices.isEmpty()) return BufferResult(null, 0) + + val vertices = edgeVertices.toList() + edgeVertices.clear() + + var result: BufferResult? = null + BufferAllocator(vertices.size * 32).use { allocator -> + val builder = + BufferBuilder( + allocator, + VertexFormat.DrawMode.QUADS, + VertexFormats.POSITION_COLOR_NORMAL_LINE_WIDTH + ) + + vertices.forEach { v -> + builder.vertex(v.x, v.y, v.z) + .color(v.r, v.g, v.b, v.a) + .normal(v.nx, v.ny, v.nz) + .lineWidth(v.lineWidth) + } + + builder.endNullable()?.let { built -> + val gpuDevice = RenderSystem.getDevice() + val buffer = + gpuDevice.createBuffer( + { "Lambda ESP Edge Buffer" }, + GpuBuffer.USAGE_VERTEX, + built.buffer + ) + result = BufferResult(buffer, built.drawParameters.indexCount()) + built.close() + } + } + return result ?: BufferResult(null, 0) + } + + data class BufferResult(val buffer: GpuBuffer?, val indexCount: Int) + data class UploadResult(val faces: BufferResult?, val edges: BufferResult?) +} diff --git a/src/main/kotlin/com/lambda/graphics/mc/RenderRegion.kt b/src/main/kotlin/com/lambda/graphics/mc/RenderRegion.kt new file mode 100644 index 000000000..6687aa44e --- /dev/null +++ b/src/main/kotlin/com/lambda/graphics/mc/RenderRegion.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.graphics.mc + +import net.minecraft.util.math.Vec3d +import org.joml.Vector3f + +/** + * A render region represents a chunk-sized area in the world where vertices are stored relative to + * the region's origin. This solves floating-point precision issues at large world coordinates. + * + * @param originX The X coordinate of the region's origin (typically chunk corner) + * @param originY The Y coordinate of the region's origin + * @param originZ The Z coordinate of the region's origin + */ +class RenderRegion(val originX: Int, val originY: Int, val originZ: Int) { + /** + * Compute the camera-relative offset for this region. This is done in double precision to + * maintain accuracy at large coordinates. + * + * @param cameraPos The camera's world position (double precision) + * @return The offset from camera to region origin (small float, high precision) + */ + fun computeCameraRelativeOffset(cameraPos: Vec3d): Vector3f { + val offsetX = originX.toDouble() - cameraPos.x + val offsetY = originY.toDouble() - cameraPos.y + val offsetZ = originZ.toDouble() - cameraPos.z + return Vector3f(offsetX.toFloat(), offsetY.toFloat(), offsetZ.toFloat()) + } + + companion object { + /** Standard size of a render region (matches Minecraft chunk size). */ + const val REGION_SIZE = 16 + + /** + * Create a region for a chunk position. + * + * @param chunkX Chunk X coordinate + * @param chunkZ Chunk Z coordinate + * @param bottomY World bottom Y coordinate (typically -64) + */ + fun forChunk(chunkX: Int, chunkZ: Int, bottomY: Int) = + RenderRegion(chunkX * 16, bottomY, chunkZ * 16) + } +} diff --git a/src/main/kotlin/com/lambda/graphics/mc/TransientRegionESP.kt b/src/main/kotlin/com/lambda/graphics/mc/TransientRegionESP.kt new file mode 100644 index 000000000..bc1cd812c --- /dev/null +++ b/src/main/kotlin/com/lambda/graphics/mc/TransientRegionESP.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.graphics.mc + +import com.lambda.graphics.esp.RegionESP +import com.lambda.graphics.esp.ShapeScope +import java.util.concurrent.ConcurrentHashMap +import kotlin.math.floor + +/** + * Modern replacement for the legacy Treed system. Handles geometry that is cleared and rebuilt + * every tick. Uses region-based rendering for precision. + */ +class TransientRegionESP(name: String, depthTest: Boolean = false) : RegionESP(name, depthTest) { + private val builders = ConcurrentHashMap() + + /** Get or create a builder for a specific region. */ + override fun shapes(x: Double, y: Double, z: Double, block: ShapeScope.() -> Unit) { + val key = getRegionKey(x, y, z) + val scope = + builders.getOrPut(key) { + val size = RenderRegion.REGION_SIZE + val rx = (size * floor(x / size)).toInt() + val ry = (size * floor(y / size)).toInt() + val rz = (size * floor(z / size)).toInt() + ShapeScope(RenderRegion(rx, ry, rz)) + } + scope.apply(block) + } + + /** Clear all current builders. Call this at the end of every tick. */ + override fun clear() { + builders.clear() + } + + /** Upload collected geometry to GPU. Must be called on main thread. */ + override fun upload() { + val activeKeys = builders.keys().asSequence().toSet() + + builders.forEach { (key, scope) -> + val renderer = renderers.getOrPut(key) { RegionRenderer(scope.region) } + renderer.upload(scope.builder.collector) + } + + renderers.forEach { (key, renderer) -> + if (key !in activeKeys) { + renderer.clearData() + } + } + } +} diff --git a/src/main/kotlin/com/lambda/graphics/mc/WorldTextRenderer.kt b/src/main/kotlin/com/lambda/graphics/mc/WorldTextRenderer.kt new file mode 100644 index 000000000..9aa34034d --- /dev/null +++ b/src/main/kotlin/com/lambda/graphics/mc/WorldTextRenderer.kt @@ -0,0 +1,307 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.graphics.mc + +import com.lambda.Lambda.mc +import net.minecraft.client.font.TextRenderer +import net.minecraft.client.render.LightmapTextureManager +import net.minecraft.client.util.math.MatrixStack +import net.minecraft.text.Text +import net.minecraft.util.math.Vec3d +import java.awt.Color + +/** + * Utility for rendering text in 3D world space. + * + * Uses Minecraft's TextRenderer to draw text that faces the camera (billboard style) at any world + * position. Handles Unicode, formatting codes, and integrates with MC's rendering system. + * + * Usage: + * ```kotlin + * // In your render event + * WorldTextRenderer.drawText( + * pos = entity.pos.add(0.0, entity.height + 0.5, 0.0), + * text = entity.name, + * color = Color.WHITE, + * scale = 0.025f + * ) + * ``` + */ +object WorldTextRenderer { + + /** Default scale for world text (MC uses 0.025f for name tags) */ + const val DEFAULT_SCALE = 0.025f + + /** Maximum light level for full brightness */ + private const val FULL_BRIGHT = LightmapTextureManager.MAX_LIGHT_COORDINATE + + /** + * Draw text at a world position, facing the camera. + * + * @param pos World position for the text + * @param text The text to render + * @param color Text color (ARGB) + * @param scale Text scale (0.025f is default name tag size) + * @param shadow Whether to draw drop shadow + * @param seeThrough Whether text should be visible through blocks + * @param centered Whether to center the text horizontally + * @param backgroundColor Background color (0 for no background) + * @param light Light level (uses full bright by default) + */ + fun drawText( + pos: Vec3d, + text: Text, + color: Color = Color.WHITE, + scale: Float = DEFAULT_SCALE, + shadow: Boolean = true, + seeThrough: Boolean = false, + centered: Boolean = true, + backgroundColor: Int = 0, + light: Int = FULL_BRIGHT + ) { + val client = mc + val camera = client.gameRenderer?.camera ?: return + val textRenderer = client.textRenderer ?: return + val immediate = client.bufferBuilders?.entityVertexConsumers ?: return + + val cameraPos = camera.pos + + val matrices = MatrixStack() + matrices.push() + + // Translate to world position relative to camera + matrices.translate(pos.x - cameraPos.x, pos.y - cameraPos.y, pos.z - cameraPos.z) + + // Billboard - face camera using camera rotation directly (same as MC's LabelCommandRenderer) + matrices.multiply(camera.rotation) + + // Scale with negative Y to flip text vertically (matches MC's 0.025, -0.025, 0.025) + matrices.scale(scale, -scale, scale) + + // Calculate text position + val textWidth = textRenderer.getWidth(text) + val x = if (centered) -textWidth / 2f else 0f + + val layerType = + if (seeThrough) TextRenderer.TextLayerType.SEE_THROUGH + else TextRenderer.TextLayerType.NORMAL + + // Draw text + textRenderer.draw( + text, + x, + 0f, + color.rgb, + shadow, + matrices.peek().positionMatrix, + immediate, + layerType, + backgroundColor, + light + ) + + matrices.pop() + + // Flush immediately for world rendering + immediate.draw() + } + + /** + * Draw text at a world position with an outline effect. + * + * @param pos World position for the text + * @param text The text to render + * @param color Text color + * @param outlineColor Outline color + * @param scale Text scale + * @param centered Whether to center the text horizontally + * @param light Light level + */ + fun drawTextWithOutline( + pos: Vec3d, + text: Text, + color: Color = Color.WHITE, + outlineColor: Color = Color.BLACK, + scale: Float = DEFAULT_SCALE, + centered: Boolean = true, + light: Int = FULL_BRIGHT + ) { + val client = mc + val camera = client.gameRenderer?.camera ?: return + val textRenderer = client.textRenderer ?: return + val immediate = client.bufferBuilders?.entityVertexConsumers ?: return + + val cameraPos = camera.pos + + val matrices = MatrixStack() + matrices.push() + + matrices.translate(pos.x - cameraPos.x, pos.y - cameraPos.y, pos.z - cameraPos.z) + + // Billboard - face camera using camera rotation directly (same as MC's LabelCommandRenderer) + matrices.multiply(camera.rotation) + matrices.scale(scale, -scale, scale) + + val textWidth = textRenderer.getWidth(text) + val x = if (centered) -textWidth / 2f else 0f + + textRenderer.drawWithOutline( + text.asOrderedText(), + x, + 0f, + color.rgb, + outlineColor.rgb, + matrices.peek().positionMatrix, + immediate, + light + ) + + matrices.pop() + immediate.draw() + } + + /** Draw a simple string at a world position. */ + fun drawString( + pos: Vec3d, + text: String, + color: Color = Color.WHITE, + scale: Float = DEFAULT_SCALE, + shadow: Boolean = true, + seeThrough: Boolean = false, + centered: Boolean = true + ) { + drawText(pos, Text.literal(text), color, scale, shadow, seeThrough, centered) + } + + /** + * Draw multiple lines of text stacked vertically. + * + * @param pos World position for the top line + * @param lines List of text lines to render + * @param color Text color + * @param scale Text scale + * @param lineSpacing Spacing between lines in scaled units (default 10) + */ + fun drawMultilineText( + pos: Vec3d, + lines: List, + color: Color = Color.WHITE, + scale: Float = DEFAULT_SCALE, + lineSpacing: Float = 10f, + shadow: Boolean = true, + seeThrough: Boolean = false, + centered: Boolean = true + ) { + val client = mc + val camera = client.gameRenderer?.camera ?: return + val textRenderer = client.textRenderer ?: return + val immediate = client.bufferBuilders?.entityVertexConsumers ?: return + + val cameraPos = camera.pos + + val matrices = MatrixStack() + matrices.push() + + matrices.translate(pos.x - cameraPos.x, pos.y - cameraPos.y, pos.z - cameraPos.z) + + // Billboard - face camera using camera rotation directly (same as MC's LabelCommandRenderer) + matrices.multiply(camera.rotation) + matrices.scale(scale, -scale, scale) + + val layerType = + if (seeThrough) TextRenderer.TextLayerType.SEE_THROUGH + else TextRenderer.TextLayerType.NORMAL + + lines.forEachIndexed { index, text -> + val textWidth = textRenderer.getWidth(text) + val x = if (centered) -textWidth / 2f else 0f + val y = index * lineSpacing + + textRenderer.draw( + text, + x, + y, + color.rgb, + shadow, + matrices.peek().positionMatrix, + immediate, + layerType, + 0, + FULL_BRIGHT + ) + } + + matrices.pop() + immediate.draw() + } + + /** + * Draw text with a background box. + * + * @param pos World position + * @param text Text to render + * @param textColor Text color + * @param backgroundColor Background color (with alpha) + * @param scale Text scale + * @param padding Padding around text in pixels + */ + fun drawTextWithBackground( + pos: Vec3d, + text: Text, + textColor: Color = Color.WHITE, + backgroundColor: Color = Color(0, 0, 0, 128), + scale: Float = DEFAULT_SCALE, + padding: Int = 2, + shadow: Boolean = false, + seeThrough: Boolean = false, + centered: Boolean = true + ) { + val client = mc + client.textRenderer ?: return + + // Calculate background color as ARGB int + val bgColorInt = + (backgroundColor.alpha shl 24) or + (backgroundColor.red shl 16) or + (backgroundColor.green shl 8) or + backgroundColor.blue + + drawText( + pos = pos, + text = text, + color = textColor, + scale = scale, + shadow = shadow, + seeThrough = seeThrough, + centered = centered, + backgroundColor = bgColorInt + ) + } + + /** Calculate the width of text in world units at a given scale. */ + fun getTextWidth(text: Text, scale: Float = DEFAULT_SCALE): Float { + val textRenderer = mc.textRenderer ?: return 0f + return textRenderer.getWidth(text) * scale + } + + /** Calculate the height of text in world units at a given scale. */ + fun getTextHeight(scale: Float = DEFAULT_SCALE): Float { + val textRenderer = mc.textRenderer ?: return 0f + return textRenderer.fontHeight * scale + } +} diff --git a/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt b/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt deleted file mode 100644 index 03cf28c85..000000000 --- a/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2025 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.graphics.renderer.esp - -import com.lambda.event.events.RenderEvent -import com.lambda.event.events.TickEvent -import com.lambda.event.events.WorldEvent -import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.event.listener.SafeListener.Companion.listenConcurrently -import com.lambda.module.Module -import com.lambda.module.modules.client.StyleEditor -import com.lambda.threading.awaitMainThread -import com.lambda.util.world.FastVector -import com.lambda.util.world.fastVectorOf -import net.minecraft.world.World -import net.minecraft.world.chunk.WorldChunk -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.ConcurrentLinkedDeque - -class ChunkedESP private constructor( - owner: Module, - private val update: ShapeBuilder.(World, FastVector) -> Unit -) { - private val rendererMap = ConcurrentHashMap() - private val WorldChunk.renderer - get() = rendererMap.getOrPut(pos.toLong()) { EspChunk(this, this@ChunkedESP) } - - private val uploadQueue = ConcurrentLinkedDeque<() -> Unit>() - private val rebuildQueue = ConcurrentLinkedDeque() - - private var ticks = 0 - - fun rebuild() { - rebuildQueue.clear() - rebuildQueue.addAll(rendererMap.values) - } - - init { - listen { event -> - world.getWorldChunk(event.pos).renderer.notifyChunks() - } - - listen { event -> - event.chunk.renderer.notifyChunks() - } - - listen { - val pos = it.chunk.pos.toLong() - rendererMap.remove(pos)?.notifyChunks() - } - - owner.listenConcurrently { - val polls = minOf(StyleEditor.rebuildsPerTick, rebuildQueue.size) - repeat(polls) { rebuildQueue.poll()?.rebuild() } - } - - owner.listen { - val polls = minOf(StyleEditor.uploadsPerTick, uploadQueue.size) - repeat(polls) { uploadQueue.poll()?.invoke() } - } - - owner.listen { - rendererMap.values.forEach { it.renderer.render() } - } - } - - companion object { - fun Module.newChunkedESP( - update: ShapeBuilder.(World, FastVector) -> Unit - ) = ChunkedESP(this@newChunkedESP, update) - } - - private class EspChunk(val chunk: WorldChunk, val owner: ChunkedESP) { - var renderer = Treed(static = true) - private val builder: ShapeBuilder - get() = ShapeBuilder(renderer.faceBuilder, renderer.edgeBuilder) - - /*val neighbors = listOf(1 to 0, 0 to 1, -1 to 0, 0 to -1) - .map { ChunkPos(chunk.pos.x + it.first, chunk.pos.z + it.second) }*/ - - fun notifyChunks() { - owner.rendererMap[chunk.pos.toLong()]?.let { - if (!owner.rebuildQueue.contains(it)) - owner.rebuildQueue.add(it) - } - } - - suspend fun rebuild() { - renderer.clear() - renderer = awaitMainThread { Treed(static = true) } - - for (x in chunk.pos.startX..chunk.pos.endX) - for (z in chunk.pos.startZ..chunk.pos.endZ) - for (y in chunk.bottomY..chunk.height) - owner.update(builder, chunk.world, fastVectorOf(x, y, z)) - - owner.uploadQueue.add { renderer.upload() } - } - } -} diff --git a/src/main/kotlin/com/lambda/graphics/renderer/esp/ShapeDsl.kt b/src/main/kotlin/com/lambda/graphics/renderer/esp/ShapeDsl.kt deleted file mode 100644 index eaf223370..000000000 --- a/src/main/kotlin/com/lambda/graphics/renderer/esp/ShapeDsl.kt +++ /dev/null @@ -1,360 +0,0 @@ -/* - * Copyright 2025 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.graphics.renderer.esp - -import com.lambda.graphics.pipeline.VertexBuilder -import com.lambda.graphics.renderer.esp.DirectionMask.hasDirection -import com.lambda.threading.runSafe -import com.lambda.util.BlockUtils.blockState -import com.lambda.util.extension.max -import com.lambda.util.extension.min -import com.lambda.util.extension.outlineShape -import net.minecraft.block.BlockState -import net.minecraft.block.entity.BlockEntity -import net.minecraft.entity.Entity -import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.Box -import net.minecraft.util.shape.VoxelShape -import java.awt.Color - -@DslMarker -annotation class ShapeDsl - -class ShapeBuilder( - val faces: VertexBuilder = VertexBuilder(), - val edges: VertexBuilder = VertexBuilder(), -) { - @ShapeDsl - fun filled( - box : DynamicAABB, - color : Color, - sides : Int = DirectionMask.ALL, - ) = faces.apply { - val boxes = box.pair ?: return@apply - - val pos11 = boxes.first.min - val pos12 = boxes.first.max - val pos21 = boxes.second.min - val pos22 = boxes.second.max - - val blb by lazy { vertex { vec3(pos11.x, pos11.y, pos11.z).vec3(pos21.x, pos21.y, pos21.z).color(color) } } - val blf by lazy { vertex { vec3(pos11.x, pos11.y, pos12.z).vec3(pos21.x, pos21.y, pos22.z).color(color) } } - val brb by lazy { vertex { vec3(pos12.x, pos11.y, pos11.z).vec3(pos22.x, pos21.y, pos21.z).color(color) } } - val brf by lazy { vertex { vec3(pos12.x, pos11.y, pos12.z).vec3(pos22.x, pos21.y, pos22.z).color(color) } } - val tlb by lazy { vertex { vec3(pos11.x, pos12.y, pos11.z).vec3(pos21.x, pos22.y, pos21.z).color(color) } } - val tlf by lazy { vertex { vec3(pos11.x, pos12.y, pos12.z).vec3(pos21.x, pos22.y, pos22.z).color(color) } } - val trb by lazy { vertex { vec3(pos12.x, pos12.y, pos11.z).vec3(pos22.x, pos22.y, pos21.z).color(color) } } - val trf by lazy { vertex { vec3(pos12.x, pos12.y, pos12.z).vec3(pos22.x, pos22.y, pos22.z).color(color) } } - - if (sides.hasDirection(DirectionMask.EAST)) buildQuad(brb, trb, trf, brf) - if (sides.hasDirection(DirectionMask.WEST)) buildQuad(blb, blf, tlf, tlb) - if (sides.hasDirection(DirectionMask.UP)) buildQuad(tlb, tlf, trf, trb) - if (sides.hasDirection(DirectionMask.DOWN)) buildQuad(blb, brb, brf, blf) - if (sides.hasDirection(DirectionMask.SOUTH)) buildQuad(blf, brf, trf, tlf) - if (sides.hasDirection(DirectionMask.NORTH)) buildQuad(blb, tlb, trb, brb) - } - - @ShapeDsl - fun filled( - box : Box, - bottomColor : Color, - topColor : Color = bottomColor, - sides : Int = DirectionMask.ALL - ) = faces.apply { - val pos1 = box.min - val pos2 = box.max - - val blb by lazy { vertex { vec3(pos1.x, pos1.y, pos1.z).color(bottomColor) } } - val blf by lazy { vertex { vec3(pos1.x, pos1.y, pos2.z).color(bottomColor) } } - val brb by lazy { vertex { vec3(pos2.x, pos1.y, pos1.z).color(bottomColor) } } - val brf by lazy { vertex { vec3(pos2.x, pos1.y, pos2.z).color(bottomColor) } } - - val tlb by lazy { vertex { vec3(pos1.x, pos2.y, pos1.z).color(topColor) } } - val tlf by lazy { vertex { vec3(pos1.x, pos2.y, pos2.z).color(topColor) } } - val trb by lazy { vertex { vec3(pos2.x, pos2.y, pos1.z).color(topColor) } } - val trf by lazy { vertex { vec3(pos2.x, pos2.y, pos2.z).color(topColor) } } - - if (sides.hasDirection(DirectionMask.EAST)) buildQuad(brb, trb, trf, brf) - if (sides.hasDirection(DirectionMask.WEST)) buildQuad(blb, blf, tlf, tlb) - if (sides.hasDirection(DirectionMask.UP)) buildQuad(tlb, tlf, trf, trb) - if (sides.hasDirection(DirectionMask.DOWN)) buildQuad(blb, brb, brf, blf) - if (sides.hasDirection(DirectionMask.SOUTH)) buildQuad(blf, brf, trf, tlf) - if (sides.hasDirection(DirectionMask.NORTH)) buildQuad(blb, tlb, trb, brb) - } - - @ShapeDsl - fun filled( - pos : BlockPos, - state : BlockState, - color : Color, - sides : Int = DirectionMask.ALL, - ) = runSafe { faces.apply { - val shape = outlineShape(state, pos) - if (shape.isEmpty) { - filled(Box(pos), color, sides) - } else { - filled(shape, color, sides) - } - } } - - @ShapeDsl - fun filled( - pos : BlockPos, - color : Color, - sides : Int = DirectionMask.ALL, - ) = runSafe { faces.apply { filled(pos, blockState(pos), color, sides) } } - - @ShapeDsl - fun filled( - pos : BlockPos, - entity : BlockEntity, - color : Color, - sides : Int = DirectionMask.ALL, - ) = filled(pos, entity.cachedState, color, sides) - - @ShapeDsl - fun filled( - shape: VoxelShape, - color: Color, - sides: Int = DirectionMask.ALL, - ) { - shape.boundingBoxes - .forEach { filled(it, color, color, sides) } - } - - @ShapeDsl - fun filled( - box : Box, - color : Color, - sides : Int = DirectionMask.ALL, - ) = filled(box, color, color, sides) - - @ShapeDsl - fun outline( - box : DynamicAABB, - color : Color, - sides : Int = DirectionMask.ALL, - mode : DirectionMask.OutlineMode = DirectionMask.OutlineMode.Or, - ) = edges.apply { - val boxes = box.pair ?: return@apply - - val pos11 = boxes.first.min - val pos12 = boxes.first.max - val pos21 = boxes.second.min - val pos22 = boxes.second.max - - val blb by lazy { vertex { vec3(pos11.x, pos11.y, pos11.z).vec3(pos21.x, pos21.y, pos21.z).color(color) } } - val blf by lazy { vertex { vec3(pos11.x, pos11.y, pos12.z).vec3(pos21.x, pos21.y, pos22.z).color(color) } } - val brb by lazy { vertex { vec3(pos12.x, pos11.y, pos11.z).vec3(pos22.x, pos21.y, pos21.z).color(color) } } - val brf by lazy { vertex { vec3(pos12.x, pos11.y, pos12.z).vec3(pos22.x, pos21.y, pos22.z).color(color) } } - val tlb by lazy { vertex { vec3(pos11.x, pos12.y, pos11.z).vec3(pos21.x, pos22.y, pos21.z).color(color) } } - val tlf by lazy { vertex { vec3(pos11.x, pos12.y, pos12.z).vec3(pos21.x, pos22.y, pos22.z).color(color) } } - val trb by lazy { vertex { vec3(pos12.x, pos12.y, pos11.z).vec3(pos22.x, pos22.y, pos21.z).color(color) } } - val trf by lazy { vertex { vec3(pos12.x, pos12.y, pos12.z).vec3(pos22.x, pos22.y, pos22.z).color(color) } } - - val hasEast = sides.hasDirection(DirectionMask.EAST) - val hasWest = sides.hasDirection(DirectionMask.WEST) - val hasUp = sides.hasDirection(DirectionMask.UP) - val hasDown = sides.hasDirection(DirectionMask.DOWN) - val hasSouth = sides.hasDirection(DirectionMask.SOUTH) - val hasNorth = sides.hasDirection(DirectionMask.NORTH) - - if (mode.check(hasUp, hasNorth)) buildLine(tlb, trb) - if (mode.check(hasUp, hasSouth)) buildLine(tlf, trf) - if (mode.check(hasUp, hasWest)) buildLine(tlb, tlf) - if (mode.check(hasUp, hasEast)) buildLine(trf, trb) - - if (mode.check(hasDown, hasNorth)) buildLine(blb, brb) - if (mode.check(hasDown, hasSouth)) buildLine(blf, brf) - if (mode.check(hasDown, hasWest)) buildLine(blb, blf) - if (mode.check(hasDown, hasEast)) buildLine(brb, brf) - - if (mode.check(hasWest, hasNorth)) buildLine(tlb, blb) - if (mode.check(hasNorth, hasEast)) buildLine(trb, brb) - if (mode.check(hasEast, hasSouth)) buildLine(trf, brf) - if (mode.check(hasSouth, hasWest)) buildLine(tlf, blf) - } - - @ShapeDsl - fun outline( - box : Box, - bottomColor : Color, - topColor : Color = bottomColor, - sides : Int = DirectionMask.ALL, - mode : DirectionMask.OutlineMode = DirectionMask.OutlineMode.Or, - ) = edges.apply { - val pos1 = box.min - val pos2 = box.max - - val blb by lazy { vertex { vec3(pos1.x, pos1.y, pos1.z).color(bottomColor) } } - val blf by lazy { vertex { vec3(pos1.x, pos1.y, pos2.z).color(bottomColor) } } - val brb by lazy { vertex { vec3(pos2.x, pos1.y, pos1.z).color(bottomColor) } } - val brf by lazy { vertex { vec3(pos2.x, pos1.y, pos2.z).color(bottomColor) } } - val tlb by lazy { vertex { vec3(pos1.x, pos2.y, pos1.z).color(topColor) } } - val tlf by lazy { vertex { vec3(pos1.x, pos2.y, pos2.z).color(topColor) } } - val trb by lazy { vertex { vec3(pos2.x, pos2.y, pos1.z).color(topColor) } } - val trf by lazy { vertex { vec3(pos2.x, pos2.y, pos2.z).color(topColor) } } - - val hasEast = sides.hasDirection(DirectionMask.EAST) - val hasWest = sides.hasDirection(DirectionMask.WEST) - val hasUp = sides.hasDirection(DirectionMask.UP) - val hasDown = sides.hasDirection(DirectionMask.DOWN) - val hasSouth = sides.hasDirection(DirectionMask.SOUTH) - val hasNorth = sides.hasDirection(DirectionMask.NORTH) - - if (mode.check(hasUp, hasNorth)) buildLine(tlb, trb) - if (mode.check(hasUp, hasSouth)) buildLine(tlf, trf) - if (mode.check(hasUp, hasWest)) buildLine(tlb, tlf) - if (mode.check(hasUp, hasEast)) buildLine(trf, trb) - - if (mode.check(hasDown, hasNorth)) buildLine(blb, brb) - if (mode.check(hasDown, hasSouth)) buildLine(blf, brf) - if (mode.check(hasDown, hasWest)) buildLine(blb, blf) - if (mode.check(hasDown, hasEast)) buildLine(brb, brf) - - if (mode.check(hasWest, hasNorth)) buildLine(tlb, blb) - if (mode.check(hasNorth, hasEast)) buildLine(trb, brb) - if (mode.check(hasEast, hasSouth)) buildLine(trf, brf) - if (mode.check(hasSouth, hasWest)) buildLine(tlf, blf) - } - - @ShapeDsl - fun outline( - pos : BlockPos, - state : BlockState, - color : Color, - sides : Int = DirectionMask.ALL, - mode : DirectionMask.OutlineMode = DirectionMask.OutlineMode.Or, - ) = runSafe { - val shape = outlineShape(state, pos) - if (shape.isEmpty) { - outline(Box(pos), color, sides, mode) - } else { - outline(shape, color, sides, mode) - } - } - - @ShapeDsl - fun outline( - pos : BlockPos, - color : Color, - sides : Int = DirectionMask.ALL, - mode : DirectionMask.OutlineMode = DirectionMask.OutlineMode.Or, - ) = runSafe { outline(pos, blockState(pos), color, sides, mode) } - - @ShapeDsl - fun outline( - pos : BlockPos, - entity : BlockEntity, - color : Color, - sides : Int = DirectionMask.ALL, - mode : DirectionMask.OutlineMode = DirectionMask.OutlineMode.Or, - ) = runSafe { outline(pos, entity.cachedState, color, sides, mode) } - - @ShapeDsl - fun outline( - shape : VoxelShape, - color : Color, - sides : Int = DirectionMask.ALL, - mode : DirectionMask.OutlineMode = DirectionMask.OutlineMode.Or, - ) { - shape.boundingBoxes - .forEach { outline(it, color, sides, mode) } - } - - @ShapeDsl - fun outline( - box : Box, - color : Color, - sides : Int = DirectionMask.ALL, - mode : DirectionMask.OutlineMode = DirectionMask.OutlineMode.Or, - ) { - outline(box, color, color, sides, mode) - } - - @ShapeDsl - fun box( - pos : BlockPos, - state : BlockState, - filled : Color, - outline : Color, - sides : Int = DirectionMask.ALL, - mode : DirectionMask.OutlineMode = DirectionMask.OutlineMode.Or, - ) = runSafe { - filled(pos, state, filled, sides) - outline(pos, state, outline, sides, mode) - } - - @ShapeDsl - fun box( - pos : BlockPos, - filled : Color, - outline : Color, - sides : Int = DirectionMask.ALL, - mode : DirectionMask.OutlineMode = DirectionMask.OutlineMode.Or, - ) = runSafe { - filled(pos, filled, sides) - outline(pos, outline, sides, mode) - } - - @ShapeDsl - fun box( - box : DynamicAABB, - filled : Color, - outline : Color, - sides : Int = DirectionMask.ALL, - mode : DirectionMask.OutlineMode = DirectionMask.OutlineMode.Or, - ) { - filled(box, filled, sides) - outline(box, outline, sides, mode) - } - - @ShapeDsl - fun box( - box : Box, - filled : Color, - outline : Color, - sides : Int = DirectionMask.ALL, - mode : DirectionMask.OutlineMode = DirectionMask.OutlineMode.Or, - ) { - filled(box, filled, sides) - outline(box, outline, sides, mode) - } - - @ShapeDsl - fun box( - entity : BlockEntity, - color : Color, - sides : Int = DirectionMask.ALL, - mode : DirectionMask.OutlineMode = DirectionMask.OutlineMode.Or, - ) = runSafe { - filled(entity.pos, entity, color, sides) - outline(entity.pos, entity, color, sides, mode) - } - - @ShapeDsl - fun box( - entity : Entity, - color : Color, - sides : Int = DirectionMask.ALL, - mode : DirectionMask.OutlineMode = DirectionMask.OutlineMode.Or, - ) = runSafe { - filled(entity.boundingBox, color, sides) - outline(entity.boundingBox, color, sides, mode) - } -} diff --git a/src/main/kotlin/com/lambda/graphics/renderer/esp/Treed.kt b/src/main/kotlin/com/lambda/graphics/renderer/esp/Treed.kt deleted file mode 100644 index 42394ed93..000000000 --- a/src/main/kotlin/com/lambda/graphics/renderer/esp/Treed.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2025 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.graphics.renderer.esp - -import com.lambda.Lambda.mc -import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib -import com.lambda.graphics.buffer.vertex.attributes.VertexMode -import com.lambda.graphics.gl.GlStateUtils -import com.lambda.graphics.pipeline.VertexBuilder -import com.lambda.graphics.pipeline.VertexPipeline -import com.lambda.graphics.shader.Shader -import com.lambda.module.modules.client.StyleEditor -import com.lambda.util.extension.partialTicks - -/** - * Open class for 3d rendering. It contains two pipelines, one for edges and the other for faces. - */ -open class Treed(private val static: Boolean) { - val shader = if (static) staticMode.first else dynamicMode.first - - val faces = VertexPipeline(VertexMode.Triangles, if (static) staticMode.second else dynamicMode.second) - val edges = VertexPipeline(VertexMode.Lines, if (static) staticMode.second else dynamicMode.second) - - var faceBuilder = VertexBuilder(); private set - var edgeBuilder = VertexBuilder(); private set - - fun upload() { - faces.upload(faceBuilder) - edges.upload(edgeBuilder) - } - - fun render() { - shader.use() - - if (!static) - shader["u_TickDelta"] = mc.partialTicks - - GlStateUtils.withFaceCulling(faces::render) - GlStateUtils.withLineWidth(StyleEditor.outlineWidth, edges::render) - } - - fun clear() { - faces.clear() - edges.clear() - - faceBuilder = VertexBuilder() - edgeBuilder = VertexBuilder() - } - - /** - * Public object for static rendering. Shapes rendering by this are not interpolated. - * That means that if the shape is frequently moving, its movement will saccade. - */ - object Static : Treed(true) - - /** - * Public object for dynamic rendering. Its position will be interpolated between ticks, allowing - * for smooth movement at the cost of duplicate position and slightly higher memory consumption. - */ - object Dynamic : Treed(false) - - companion object { - private val staticMode = Shader("shaders/vertex/box_static.glsl", "shaders/fragment/pos_color.glsl") to VertexAttrib.Group.STATIC_RENDERER - private val dynamicMode = Shader("shaders/vertex/box_dynamic.glsl", "shaders/fragment/pos_color.glsl") to VertexAttrib.Group.DYNAMIC_RENDERER - } -} diff --git a/src/main/kotlin/com/lambda/graphics/renderer/gui/AbstractGUIRenderer.kt b/src/main/kotlin/com/lambda/graphics/renderer/gui/AbstractGUIRenderer.kt deleted file mode 100644 index 271614ff5..000000000 --- a/src/main/kotlin/com/lambda/graphics/renderer/gui/AbstractGUIRenderer.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2025 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.graphics.renderer.gui - -import com.lambda.event.events.TickEvent -import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.graphics.RenderMain -import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib -import com.lambda.graphics.buffer.vertex.attributes.VertexMode -import com.lambda.graphics.pipeline.VertexPipeline -import com.lambda.graphics.shader.Shader -import com.lambda.gui.components.ClickGuiLayout -import com.lambda.module.modules.client.StyleEditor -import com.lambda.util.math.MathUtils.toInt -import com.lambda.util.math.Vec2d -import org.lwjgl.glfw.GLFW - -open class AbstractGUIRenderer( - attribGroup: VertexAttrib.Group, - val shader: Shader -) { - private val pipeline = VertexPipeline(VertexMode.Triangles, attribGroup) - private var memoryMapping = true - - init { - listen(alwaysListen = true) { - memoryMapping = StyleEditor.useMemoryMapping - } - - listen(alwaysListen = true) { - if (memoryMapping) pipeline.sync() - } - } - - fun render( - shade: Boolean = false, - block: VertexPipeline.(Shader) -> Unit - ) { - shader.use() - - block(pipeline, shader) - - shader["u_Shade"] = shade.toInt().toDouble() - if (shade) { - shader["u_ShadeTime"] = GLFW.glfwGetTime() * ClickGuiLayout.colorSpeed * 5.0 - shader["u_ShadeColor1"] = ClickGuiLayout.primaryColor - shader["u_ShadeColor2"] = ClickGuiLayout.secondaryColor - - shader["u_ShadeSize"] = RenderMain.screenSize / Vec2d(ClickGuiLayout.colorWidth, ClickGuiLayout.colorHeight) - } - - pipeline.apply { - render() - if (memoryMapping) end() else clear() - } - } -} diff --git a/src/main/kotlin/com/lambda/graphics/renderer/gui/FontRenderer.kt b/src/main/kotlin/com/lambda/graphics/renderer/gui/FontRenderer.kt deleted file mode 100644 index 69dc6bf3b..000000000 --- a/src/main/kotlin/com/lambda/graphics/renderer/gui/FontRenderer.kt +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright 2025 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.graphics.renderer.gui - -import com.lambda.graphics.buffer.vertex.attributes.VertexAttrib -import com.lambda.graphics.pipeline.VertexBuilder -import com.lambda.graphics.renderer.gui.font.core.GlyphInfo -import com.lambda.graphics.renderer.gui.font.core.LambdaAtlas.get -import com.lambda.graphics.renderer.gui.font.core.LambdaAtlas.height -import com.lambda.graphics.texture.TextureOwner.bind -import com.lambda.gui.components.ClickGuiLayout -import com.lambda.module.modules.client.LambdaMoji -import com.lambda.module.modules.client.StyleEditor -import com.lambda.util.math.MathUtils.toInt -import com.lambda.util.math.Vec2d -import com.lambda.util.math.a -import com.lambda.util.math.setAlpha -import net.minecraft.client.gui.hud.ChatHud.getHeight -import sun.java2d.SunGraphicsEnvironment.getScaleFactor -import java.awt.Color - -/** - * Renders text and emoji glyphs using a shader-based font rendering system. - * This class handles text and emoji rendering, shadow effects, and text scaling. - */ -/*object FontRenderer : AbstractGUIRenderer(VertexAttrib.Group.FONT, shader("renderer/font")) { - private val chars get() = StyleEditor.textFont - private val emojis get() = StyleEditor.emojiFont - - private val shadowShift get() = StyleEditor.shadowShift * 10.0 - private val baselineOffset get() = StyleEditor.baselineOffset * 2.0f - 16f - private val gap get() = StyleEditor.gap * 0.5f - 0.8f - - /** - * Renders a text string at a specified position with configurable color, scale, shadow, and emoji parsing - * - * @param text The text to render. - * @param position The position to render the text. - * @param color The color of the text. - * @param scale The scale factor of the text. - * @param shadow Whether to render a shadow for the text. - * @param parseEmoji Whether to parse and render emojis in the text. - */ - fun drawString( - text: String, - position: Vec2d = Vec2d.ZERO, - color: Color = Color.WHITE, - scale: Double = ClickGuiLayout.fontScale, - shadow: Boolean = true, - parseEmoji: Boolean = LambdaMoji.isEnabled - ) = render { - shader["u_FontTexture"] = 0 - shader["u_EmojiTexture"] = 1 - shader["u_SDFMin"] = StyleEditor.sdfMin - shader["u_SDFMax"] = StyleEditor.sdfMax - - bind(chars, emojis) - - upload { - processText(text, color, scale, shadow, parseEmoji) { char, pos1, pos2, col, _ -> - buildGlyph(char, position, pos1, pos2, col) - } - } - } - - /** - * Renders a single glyph at the specified position with the given scale and color - * - * @param glyph The glyph information - * @param position The rendering position where the glyph will be drawn - * @param color The color of the glyph - * @param scale The scale factor of the glyph - */ - fun drawGlyph( - glyph: GlyphInfo, - position: Vec2d, - color: Color = Color.WHITE, - scale: Double = ClickGuiLayout.fontScale, - ) = render { - shader["u_FontTexture"] = 0 - shader["u_EmojiTexture"] = 1 - shader["u_SDFMin"] = StyleEditor.sdfMin - shader["u_SDFMax"] = StyleEditor.sdfMax - - bind(chars, emojis) - - val actualScale = getScaleFactor(scale) - val scaledSize = glyph.size * actualScale - - val posY = getHeight(scale) * -0.5 + baselineOffset * actualScale - val pos1 = Vec2d(0.0, posY) * actualScale - val pos2 = pos1 + scaledSize - - upload { buildGlyph(glyph, position, pos1, pos2, color) } - } - - /** - * Renders a single glyph at a given position. - * - * @param glyph The glyph information to render. - * @param origin The position to start from - * @param pos1 The starting position of the glyph. - * @param pos2 The end position of the glyph - * @param color The color of the glyph. - */ - private fun VertexBuilder.buildGlyph( - glyph: GlyphInfo, - origin: Vec2d = Vec2d.ZERO, - pos1: Vec2d, - pos2: Vec2d, - color: Color, - ) { - val x1 = pos1.x + origin.x - val y1 = pos1.y + origin.y - val x2 = pos2.x + origin.x - val y2 = pos2.y + origin.y - - val upLeft = vertex { vec3m(x1, y1).vec2(glyph.uv1.x, glyph.uv1.y).color(color) } - val downLeft = vertex { vec3m(x1, y2).vec2(glyph.uv1.x, glyph.uv2.y).color(color) } - val upRight = vertex { vec3m(x2, y2).vec2(glyph.uv2.x, glyph.uv2.y).color(color) } - val downRight = vertex { vec3m(x2, y1).vec2(glyph.uv2.x, glyph.uv1.y).color(color) } - - buildQuad(upLeft, downLeft, upRight, downRight) - } - - /** - * Calculates the width of the specified text. - * - * @param text The text to measure. - * @param scale The scale factor for the width calculation. - * @param parseEmoji Whether to include emojis in the width calculation. - * @return The width of the text at the specified scale. - */ - fun getWidth( - text: String, - scale: Double = ClickGuiLayout.fontScale, - parseEmoji: Boolean = LambdaMoji.isEnabled, - ): Double { - var width = 0.0 - var gaps = -1 - - processText(text, scale = scale, parseEmoji = parseEmoji) { char, _, _, _, isShadow -> - if (isShadow) return@processText - width += char.width; gaps++ - } - - return (width + gaps.coerceAtLeast(0) * gap) * getScaleFactor(scale) - } - - /** - * Computes the effective height of the rendered text - * - * The height is derived from the current font's base height, adjusted by a scaling factor - * that ensures consistent visual proportions - * - * @param scale The scale factor for the height calculation. - * @return The height of the text at the specified scale. - */ - fun getHeight(scale: Double = 1.0) = chars.height * getScaleFactor(scale) * 0.7 - - /** - * Processes a text string by iterating over its characters and emojis, computing rendering positions, and invoking a block for each glyph - * - * @param text The text to iterate over. - * @param color The color of the text. - * @param scale The scale of the text. - * @param shadow Whether to render a shadow. - * @param parseEmoji Whether to parse and include emojis. - * @param block The function to apply to each character or emoji glyph. - */ - private fun processText( - text: String, - color: Color = Color.WHITE, - scale: Double = 1.0, - shadow: Boolean = StyleEditor.shadow, - parseEmoji: Boolean = LambdaMoji.isEnabled, - block: (GlyphInfo, Vec2d, Vec2d, Color, Boolean) -> Unit - ) { - val actualScale = getScaleFactor(scale) - val scaledGap = gap * actualScale - - val shadowColor = getShadowColor(color) - val emojiColor = color.setAlpha(color.a) - - var posX = 0.0 - var posY = getHeight(scale) * -0.5 + baselineOffset * actualScale - - fun drawGlyph(info: GlyphInfo?, color: Color, isShadow: Boolean = false) { - if (info == null) return - - val scaledSize = info.size * actualScale - val pos1 = Vec2d(posX, posY) + shadowShift * actualScale * isShadow.toInt() - val pos2 = pos1 + scaledSize - - block(info, pos1, pos2, color, isShadow) - if (!isShadow) posX += scaledSize.x + scaledGap - } - - val parsed = if (parseEmoji) emojis.parse(text) else mutableListOf() - - fun processTextSection(section: String, hasEmojis: Boolean) { - if (section.isEmpty()) return - if (!parseEmoji || parsed.isEmpty() || !hasEmojis) { - // Draw simple characters if no emojis are present - section.forEach { char -> - // Logic for control characters - when (char) { - '\n', '\r' -> { posX = 0.0; posY += chars.height * actualScale; return@forEach } - } - - val glyph = chars[char] ?: return@forEach - - if (shadow && StyleEditor.shadow) drawGlyph(glyph, shadowColor, true) - drawGlyph(glyph, color) - } - } else { - // Only compute the first parsed emoji to avoid duplication - // This is important in order to keep the parsed ranges valid - // If you do not this, you will get out of bounds positions - // due to slicing - val emoji = parsed.removeFirstOrNull() ?: return - - // Iterate the emojis from left to right - val start = section.indexOf(emoji) - val end = start + emoji.length - - val preEmojiText = section.substring(0, start) - val postEmojiText = section.substring(end) - - // Draw the text without emoji - processTextSection(preEmojiText, hasEmojis = false) - - // Draw the emoji - drawGlyph(emojis[emoji], emojiColor) - - // Process the rest of the text after the emoji - processTextSection(postEmojiText, hasEmojis = true) - } - } - - // Start processing the full text - processTextSection(text, hasEmojis = parsed.isNotEmpty()) - } - - /** - * Calculates the scale factor for the text based on the provided scale. - * - * @param scale The base scale factor. - * @return The adjusted scale factor. - */ - fun getScaleFactor(scale: Double): Double = scale * 8.5 / chars.height - - /** - * Calculates the shadow color by adjusting the brightness of the input color. - * - * @param color The original color. - * @return The modified shadow color. - */ - fun getShadowColor(color: Color): Color = Color( - (color.red * StyleEditor.shadowBrightness).toInt(), - (color.green * StyleEditor.shadowBrightness).toInt(), - (color.blue * StyleEditor.shadowBrightness).toInt(), - color.alpha - ) -}*/ diff --git a/src/main/kotlin/com/lambda/graphics/renderer/gui/font/core/GlyphInfo.kt b/src/main/kotlin/com/lambda/graphics/renderer/gui/font/core/GlyphInfo.kt deleted file mode 100644 index 88dab1a59..000000000 --- a/src/main/kotlin/com/lambda/graphics/renderer/gui/font/core/GlyphInfo.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2025 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.graphics.renderer.gui.font.core - -import com.lambda.util.math.Vec2d - -/** - * Represents information about a character (glyph) in a font. - * - * @property size The size of the character in a 2D vector. - * @property uv1 The top-left UV coordinates of the character texture. - * @property uv2 The bottom-right UV coordinates of the character texture. - */ -data class GlyphInfo( - val size: Vec2d, - val uv1: Vec2d, - val uv2: Vec2d -) { - /** - * The width of the character. - */ - val width get() = size.x - - /** - * The height of the character. - */ - val height get() = size.y - - /** - * The U coordinate of the top-left corner of the character texture. - */ - val u1 get() = uv1.x - - /** - * The V coordinate of the top-left corner of the character texture. - */ - val v1 get() = uv1.y - - /** - * The U coordinate of the bottom-right corner of the character texture. - */ - val u2 get() = uv2.x - - /** - * The V coordinate of the bottom-right corner of the character texture. - */ - val v2 get() = uv2.y -} diff --git a/src/main/kotlin/com/lambda/graphics/renderer/gui/font/core/LambdaAtlas.kt b/src/main/kotlin/com/lambda/graphics/renderer/gui/font/core/LambdaAtlas.kt deleted file mode 100644 index bc4004394..000000000 --- a/src/main/kotlin/com/lambda/graphics/renderer/gui/font/core/LambdaAtlas.kt +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright 2025 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.graphics.renderer.gui.font.core - -import com.google.common.math.IntMath -import com.lambda.core.Loadable -import com.lambda.graphics.texture.TextureOwner.upload -import com.lambda.threading.runGameScheduled -import com.lambda.util.math.Vec2d -import com.lambda.util.stream -import it.unimi.dsi.fastutil.objects.Object2DoubleArrayMap -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap -import java.awt.Color -import java.awt.Font -import java.awt.FontMetrics -import java.awt.Graphics2D -import java.awt.RenderingHints -import java.awt.image.BufferedImage -import java.io.File -import java.util.zip.ZipFile -import javax.imageio.ImageIO -import kotlin.math.ceil -import kotlin.math.log2 -import kotlin.math.max -import kotlin.math.sqrt - -/** - * The [LambdaAtlas] manages the creation and binding of texture atlases for fonts, emojis and user defined atlases - * It stores glyph information, manages texture uploads, and provides functionality to build texture buffers for fonts and emoji sets - * - * It caches font information and emoji data for efficient rendering and includes mechanisms for uploading and binding texture atlases - * - * It's also possible to upload custom atlases and bind them with no hassle - * ```kt - * enum class ExampleFont { - * CoolFont("Cool-Font"); - * } - * - * fun loadFont(...) = BufferedImage - * - * // Function extension from [TexturePipeline] - * ExampleFont.CoolFont.upload(loadFont(...)) // The extension keeps a reference to the font owner - * - * ... - * - * onRender { - * ExampleFont.CoolFont.bind() - * } - * ``` - */ -object LambdaAtlas : Loadable { - private val fontMap = mutableMapOf>() - private val emojiMap = mutableMapOf>() - - private val bufferPool = - mutableMapOf() // This array is nuked once the data is dispatched to OpenGL - - private val fontCache = mutableMapOf() - private val metricCache = mutableMapOf() - private val heightCache = Object2DoubleArrayMap() - - operator fun LambdaFont.get(char: Char): GlyphInfo? = fontMap.getValue(this)[char] - operator fun LambdaEmoji.get(string: String): GlyphInfo? = emojiMap.getValue(this)[string.removeSurrounding(":")] - - val LambdaFont.height: Double - get() = heightCache.getDouble(fontCache[this@height]) - - val LambdaEmoji.keys - get() = emojiMap.getValue(this) - - private const val CHAR_SPACE = 8 - - /** - * Builds the buffer for an emoji set by reading a ZIP file containing emoji images. - * The images are arranged into a texture atlas, and their UV coordinates are computed for later rendering. - * - * @throws IllegalStateException If the texture size is too small to fit the emojis. - */ - fun LambdaEmoji.buildBuffer() { - var image: BufferedImage - val file = File.createTempFile("emoji", "zip") - url.stream.copyTo(file.outputStream()) - ZipFile(file).use { zip -> - val firstImage = ImageIO.read(zip.getInputStream(zip.entries().nextElement())) - val length = zip.size().toDouble() - - val textureDimensionLength: (Int) -> Int = { dimLength -> - IntMath.pow(2, ceil(log2((dimLength + CHAR_SPACE) * sqrt(length))).toInt()) - } - - val width = textureDimensionLength(firstImage.width) - val height = textureDimensionLength(firstImage.height) - val texelSize = Vec2d.ONE / Vec2d(width, height) - - image = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) - val graphics = image.graphics as Graphics2D - graphics.color = Color(0, 0, 0, 0) - - var x = CHAR_SPACE - var y = CHAR_SPACE - - val constructed = Object2ObjectOpenHashMap() - for (entry in zip.entries()) { - val name = entry.name.substringAfterLast("/").substringBeforeLast(".") - val emoji = ImageIO.read(zip.getInputStream(entry)) - - val charWidth = emoji.width + CHAR_SPACE - val charHeight = emoji.height + CHAR_SPACE - - if (x + charWidth >= image.width) { - check(y + charHeight < image.height) { "Can't load emoji glyphs. Texture size is too small" } - - y += charHeight - x = 0 - } - - graphics.drawImage(emoji, x, y, null) - - val size = Vec2d(emoji.width, emoji.height) - val uv1 = Vec2d(x, y) * texelSize - val uv2 = Vec2d(x, y).plus(size) * texelSize - - val normalized = 128.0 / size.y - constructed[name] = GlyphInfo(size * normalized, -uv1, -uv2) - - x += emoji.width + 2 - } - - emojiMap[this@buildBuffer] = constructed - } - - bufferPool[this@buildBuffer] = image - } - - fun LambdaFont.buildBuffer( - characters: Int = 2048 // How many characters from that font should be used for the generation - ) { - val font = fontCache.computeIfAbsent(this) { - Font.createFont(Font.TRUETYPE_FONT, "fonts/$fontName.ttf".stream).deriveFont(128.0f) - } - - val textureSize = characters * 2 - val oneTexelSize = 1.0 / textureSize - - val image = BufferedImage(textureSize, textureSize, BufferedImage.TYPE_INT_ARGB) - - val graphics = image.graphics as Graphics2D - graphics.background = Color(0, 0, 0, 0) - - var x = CHAR_SPACE - var y = CHAR_SPACE - var rowHeight = 0 - - val constructed = mutableMapOf() - (Char.MIN_VALUE.. - val charImage = getCharImage(font, char) ?: return@forEach - - rowHeight = max(rowHeight, charImage.height + CHAR_SPACE) - val charWidth = charImage.width + CHAR_SPACE - - if (x + charWidth >= textureSize) { - // Check if possible to step to the next row - check(y + rowHeight <= textureSize) { "Can't load font glyphs. Texture size is too small" } - - y += rowHeight - x = 0 - rowHeight = 0 - } - - graphics.drawImage(charImage, x, y, null) - - val size = Vec2d(charImage.width, charImage.height) - val uv1 = Vec2d(x, y) * oneTexelSize - val uv2 = Vec2d(x, y).plus(size) * oneTexelSize - - constructed[char] = GlyphInfo(size, uv1, uv2) - heightCache[font] = max(heightCache.getDouble(font), size.y) // No compare set unfortunately - - x += charWidth - } - - fontMap[this@buildBuffer] = constructed - bufferPool[this@buildBuffer] = image - } - - // TODO: Change this when we've refactored the loadables - override fun load(): String { - LambdaFont.entries.forEach(LambdaFont::load) - LambdaEmoji.entries.forEach(LambdaEmoji::load) - - val str = "Loaded ${bufferPool.size} fonts" // avoid race condition - - runGameScheduled { - bufferPool.forEach { (owner, image) -> owner.upload(image) } - bufferPool.clear() - } - - return str - } - - private fun getCharImage(font: Font, codePoint: Char): BufferedImage? { - if (!font.canDisplay(codePoint)) return null - - val fontMetrics = metricCache.getOrPut(font) { - val image = BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB) - val graphics2D = image.createGraphics() - - graphics2D.font = font - graphics2D.dispose() - - image.graphics.getFontMetrics(font) - } - - val charWidth = if (fontMetrics.charWidth(codePoint) > 0) fontMetrics.charWidth(codePoint) else 8 - val charHeight = if (fontMetrics.height > 0) fontMetrics.height else font.size - - val charImage = BufferedImage(charWidth, charHeight, BufferedImage.TYPE_INT_ARGB) - val graphics2D = charImage.createGraphics() - - graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) - graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_DEFAULT) - - graphics2D.font = font - graphics2D.color = Color.WHITE - graphics2D.drawString(codePoint.toString(), 0, fontMetrics.ascent) - graphics2D.dispose() - - return charImage - } -} diff --git a/src/main/kotlin/com/lambda/graphics/renderer/gui/font/core/LambdaEmoji.kt b/src/main/kotlin/com/lambda/graphics/renderer/gui/font/core/LambdaEmoji.kt deleted file mode 100644 index 78b778ed0..000000000 --- a/src/main/kotlin/com/lambda/graphics/renderer/gui/font/core/LambdaEmoji.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2025 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.graphics.renderer.gui.font.core - -import com.lambda.graphics.renderer.gui.font.core.LambdaAtlas.buildBuffer - -enum class LambdaEmoji(val url: String) { - Twemoji("fonts/emojis.zip"); - - private val emojiRegex = Regex(":[a-zA-Z0-9_]+:") - - /** - * Extracts emoji names from the provided text - * - * The function scans the input text for patterns matching emojis in the `:name:` format and - * returns a mutable list of the emoji names - * - * @param text The text to parse. - * @return A list of extract emoji names - */ - fun parse(text: String): MutableList = - emojiRegex.findAll(text).map { it.value }.toMutableList() - - fun load(): String { - entries.forEach { it.buildBuffer() } - return "Loaded ${entries.size} emoji sets" - } -} diff --git a/src/main/kotlin/com/lambda/gui/DearImGui.kt b/src/main/kotlin/com/lambda/gui/DearImGui.kt index 13b4f24fa..770468d52 100644 --- a/src/main/kotlin/com/lambda/gui/DearImGui.kt +++ b/src/main/kotlin/com/lambda/gui/DearImGui.kt @@ -36,6 +36,7 @@ import imgui.glfw.ImGuiImplGlfw import net.minecraft.client.gl.GlBackend import net.minecraft.client.texture.GlTexture import org.lwjgl.opengl.GL30.GL_FRAMEBUFFER +import org.lwjgl.opengl.GL32C import kotlin.math.abs object DearImGui : Loadable { @@ -103,7 +104,7 @@ object DearImGui : Loadable { val framebuffer = mc.framebuffer val prevFramebuffer = (framebuffer.getColorAttachment() as GlTexture).getOrCreateFramebuffer( - (RenderSystem.getDevice() as GlBackend).framebufferManager, + (RenderSystem.getDevice() as GlBackend).bufferManager, null ) @@ -120,6 +121,8 @@ object DearImGui : Loadable { GuiEvent.EndFrame.post() implGl3.renderDrawData(ImGui.getDrawData()) + + GlStateManager._glBindFramebuffer(GL_FRAMEBUFFER, 0) } fun destroy() { diff --git a/src/main/kotlin/com/lambda/gui/LambdaScreen.kt b/src/main/kotlin/com/lambda/gui/LambdaScreen.kt index cb35fc93f..bb01781ea 100644 --- a/src/main/kotlin/com/lambda/gui/LambdaScreen.kt +++ b/src/main/kotlin/com/lambda/gui/LambdaScreen.kt @@ -26,4 +26,14 @@ object LambdaScreen : Screen(Text.of("Lambda")) { override fun shouldPause() = false override fun removed() = ClickGuiLayout.close() override fun render(context: DrawContext?, mouseX: Int, mouseY: Int, deltaTicks: Float) {} + + override fun applyBlur(context: DrawContext?) { + if (!ClickGuiLayout.backgroundBlur) return + super.applyBlur(context) + } + + override fun renderDarkening(context: DrawContext?) { + if (!ClickGuiLayout.backgroundDarkening) return + super.renderDarkening(context) + } } diff --git a/src/main/kotlin/com/lambda/gui/MenuBar.kt b/src/main/kotlin/com/lambda/gui/MenuBar.kt index 3856aea6f..539f04904 100644 --- a/src/main/kotlin/com/lambda/gui/MenuBar.kt +++ b/src/main/kotlin/com/lambda/gui/MenuBar.kt @@ -52,6 +52,9 @@ import imgui.flag.ImGuiCol import imgui.flag.ImGuiStyleVar import imgui.flag.ImGuiWindowFlags import net.fabricmc.loader.api.FabricLoader +import net.minecraft.client.gui.screen.DebugOptionsScreen +import net.minecraft.network.packet.c2s.play.ChangeGameModeC2SPacket +import net.minecraft.server.command.GameModeCommand import net.minecraft.util.Util import net.minecraft.world.GameMode import java.util.* @@ -274,7 +277,7 @@ object MenuBar { .filter { it.tag == tag } .forEach { module -> menuItem(module.name, selected = module.isEnabled) { - if (module.isEnabled) module.disable() else module.enable() + module.toggle() } // Optionally, offer a "Settings..." item to focus this module’s details UI. } @@ -346,18 +349,18 @@ object MenuBar { } separator() runSafe { - menu("Gamemode", enabled = player.hasPermissionLevel(2)) { + menu("Gamemode", enabled = GameModeCommand.PERMISSION_CHECK.allows(player.permissions)) { menuItem("Survival", selected = interaction.gameMode == GameMode.SURVIVAL) { - connection.sendCommand("gamemode survival") + connection.sendPacket(ChangeGameModeC2SPacket(GameMode.SURVIVAL)) } menuItem("Creative", selected = interaction.gameMode == GameMode.CREATIVE) { - connection.sendCommand("gamemode creative") + connection.sendPacket(ChangeGameModeC2SPacket(GameMode.CREATIVE)) } menuItem("Adventure", selected = interaction.gameMode == GameMode.ADVENTURE) { - connection.sendCommand("gamemode adventure") + connection.sendPacket(ChangeGameModeC2SPacket(GameMode.ADVENTURE)) } menuItem("Spectator", selected = interaction.gameMode == GameMode.SPECTATOR) { - connection.sendCommand("gamemode spectator") + connection.sendPacket(ChangeGameModeC2SPacket(GameMode.SPECTATOR)) } } menu("Debug Menu") { @@ -365,16 +368,6 @@ object MenuBar { mc.options.advancedItemTooltips = !mc.options.advancedItemTooltips mc.options.write() } - menuItem("Show Chunk Borders", "F3+G", mc.debugRenderer.showChunkBorder) { - mc.debugRenderer.toggleShowChunkBorder() - } - menuItem("Show Octree", selected = mc.debugRenderer.showOctree) { - mc.debugRenderer.toggleShowOctree() - } - menuItem("Show Hitboxes", "F3+B", mc.entityRenderDispatcher.shouldRenderHitboxes()) { - val now = !mc.entityRenderDispatcher.shouldRenderHitboxes() - mc.entityRenderDispatcher.setRenderHitboxes(now) - } menuItem("Copy Location (as command)", "F3+C") { val cmd = String.format( Locale.ROOT, @@ -388,6 +381,9 @@ object MenuBar { menuItem("Clear Chat", "F3+D") { mc.inGameHud?.chatHud?.clear(false) } + menuItem("Open Debug Entry Menu", "(new)") { // ToDo: Put actual keybind in + mc.setScreen(DebugOptionsScreen()) + } separator() @@ -414,8 +410,8 @@ object MenuBar { separator() - menuItem("Show Debug Menu", "F3", mc.debugHud.showDebugHud) { - mc.debugHud.toggleDebugHud() + menuItem("Show Debug Menu", "F3", mc.debugHudEntryList.isF3Enabled) { + mc.debugHudEntryList.toggleF3Enabled() } menuItem("Rendering Chart", "F3+1", mc.debugHud.renderingChartVisible) { mc.debugHud.toggleRenderingChart() diff --git a/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt b/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt index b54bad9d4..2ff52d576 100644 --- a/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt +++ b/src/main/kotlin/com/lambda/gui/components/ClickGuiLayout.kt @@ -20,9 +20,9 @@ package com.lambda.gui.components import com.lambda.Lambda.mc import com.lambda.config.Configurable import com.lambda.config.configurations.GuiConfig +import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress import com.lambda.core.Loadable import com.lambda.event.events.GuiEvent -import com.lambda.event.events.KeyboardEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.gui.DearImGui import com.lambda.gui.LambdaScreen @@ -63,7 +63,12 @@ object ClickGuiLayout : Loadable, Configurable(GuiConfig) { override val name = "GUI" var open = false var developerMode = false - val keybind by setting("Keybind", KeyCode.Y) + val keybind by setting("Keybind", KeyCode.Y, screenCheck = false) + .onPress { + if (!open && mc.currentScreen != null) return@onPress + if (DearImGui.io.wantTextInput) return@onPress + toggle() + } private var initialLayoutComplete = false private var frameCount = 0 private var activeDragWindowName: String? = null @@ -115,13 +120,15 @@ object ClickGuiLayout : Loadable, Configurable(GuiConfig) { if (to) { setLambdaWindowIcon() } else { - val icon = if (SharedConstants.getGameVersion().isStable) Icons.RELEASE else Icons.SNAPSHOT + val icon = if (SharedConstants.getGameVersion().stable()) Icons.RELEASE else Icons.SNAPSHOT mc.window.setIcon(mc.defaultResourcePack, icon) } } @JvmStatic val setLambdaWindowTitle by setting("Set Lambda Window Title", true).onValueChange { _, _ -> mc.updateWindowTitle() }.group(Group.General) val lambdaTitleAppendixName by setting("Append Username", true) { setLambdaWindowTitle }.onValueChange { _, _ -> mc.updateWindowTitle() }.group(Group.General) + val backgroundBlur by setting("Background Blur", true).group(Group.General) + val backgroundDarkening by setting("Background Darkening", true).group(Group.General) // Snapping val snapEnabled by setting("Enable Snapping", true, "Master toggle for GUI/HUD snapping").group(Group.Snapping) @@ -341,16 +348,6 @@ object ClickGuiLayout : Loadable, Configurable(GuiConfig) { } } } - - listen(alwaysListen = true) { event -> - if (!event.isPressed) return@listen - if (mc.options.commandKey.isPressed) return@listen - if (!event.satisfies(keybind)) return@listen - if (!open && mc.currentScreen != null) return@listen - if (open && DearImGui.io.wantTextInput) return@listen - - toggle() - } } val Screen?.hasInput: Boolean diff --git a/src/main/kotlin/com/lambda/gui/components/QuickSearch.kt b/src/main/kotlin/com/lambda/gui/components/QuickSearch.kt index 396a9b826..a92fb7ca2 100644 --- a/src/main/kotlin/com/lambda/gui/components/QuickSearch.kt +++ b/src/main/kotlin/com/lambda/gui/components/QuickSearch.kt @@ -23,7 +23,7 @@ import com.lambda.command.LambdaCommand import com.lambda.config.Configurable import com.lambda.config.Configuration import com.lambda.config.Setting -import com.lambda.event.events.KeyboardEvent +import com.lambda.event.events.ButtonEvent import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe import com.lambda.gui.LambdaScreen import com.lambda.gui.Layout @@ -62,7 +62,7 @@ object QuickSearch { ImGuiWindowFlags.NoScrollWithMouse init { - listenUnsafe { event -> + listenUnsafe { event -> if (mc.currentScreen !is LambdaScreen) return@listenUnsafe handleKeyPress(event) } @@ -91,7 +91,7 @@ object QuickSearch { override fun ImGuiBuilder.buildLayout() { text(command.name.capitalize()) sameLine() - smallButton("Insert") { mc.setScreen(ChatScreen("${CommandRegistry.prefix}${command.name} ")) } + smallButton("Insert") { mc.setScreen(ChatScreen("${CommandRegistry.prefix}${command.name} ", true)) } if (command.description.isNotBlank()) { sameLine() textDisabled(command.description) @@ -302,7 +302,7 @@ object QuickSearch { return "$configurableName » $group" } - private fun handleKeyPress(event: KeyboardEvent.Press) { + private fun handleKeyPress(event: ButtonEvent.Keyboard.Press) { if ((!event.isPressed || event.isRepeated) || !(event.keyCode == KeyCode.LeftShift.code || event.keyCode == KeyCode.RightShift.code)) return diff --git a/src/main/kotlin/com/lambda/interaction/BaritoneManager.kt b/src/main/kotlin/com/lambda/interaction/BaritoneManager.kt index 43f36c52c..0efcda8b6 100644 --- a/src/main/kotlin/com/lambda/interaction/BaritoneManager.kt +++ b/src/main/kotlin/com/lambda/interaction/BaritoneManager.kt @@ -21,22 +21,25 @@ import baritone.api.BaritoneAPI import baritone.api.IBaritone import baritone.api.Settings import baritone.api.pathing.goals.Goal +import com.lambda.config.AutomationConfig import com.lambda.config.Configurable import com.lambda.config.configurations.LambdaConfig import com.lambda.config.groups.RotationSettings import com.lambda.context.Automated -import com.lambda.config.AutomationConfig import com.lambda.util.BlockUtils.blockPos import com.lambda.util.NamedEnum +import net.fabricmc.loader.api.FabricLoader object BaritoneManager : Configurable(LambdaConfig), Automated by AutomationConfig.Companion.DEFAULT { override val name = "baritone" - private val baritone = BaritoneAPI.getProvider() - val baritoneSettings: Settings = BaritoneAPI.getSettings() + val isBaritoneLoaded = FabricLoader.getInstance().isModLoaded("baritone") + + private val baritone = if (isBaritoneLoaded) BaritoneAPI.getProvider() else null + val baritoneSettings: Settings? = if (isBaritoneLoaded) BaritoneAPI.getSettings() else null @JvmStatic - val primary: IBaritone = baritone.primaryBaritone + val primary: IBaritone? = baritone?.primaryBaritone private enum class Group(override val displayName: String) : NamedEnum { General("General"), @@ -72,7 +75,8 @@ object BaritoneManager : Configurable(LambdaConfig), Automated by AutomationConf init { // ToDo: Dont actually save the settings as its duplicate data - with(baritoneSettings) { + if (isBaritoneLoaded) { + with(baritoneSettings!!) { // GENERAL setting("Log As Toast", logAsToast.value).group(Group.General, SubGroup.ChatAndControl).onValueChange { _, it -> logAsToast.value = it } @@ -334,6 +338,7 @@ object BaritoneManager : Configurable(LambdaConfig), Automated by AutomationConf setting("Allow Land On Nether Fortress", elytraAllowLandOnNetherFortress.value).group(Group.Elytra).onValueChange { _, it -> elytraAllowLandOnNetherFortress.value = it } setting("Terms Accepted", elytraTermsAccepted.value).group(Group.Elytra).onValueChange { _, it -> elytraTermsAccepted.value = it } setting("Chat Spam", elytraChatSpam.value).group(Group.Elytra).onValueChange { _, it -> elytraChatSpam.value = it } + } } } @@ -341,22 +346,30 @@ object BaritoneManager : Configurable(LambdaConfig), Automated by AutomationConf * Whether Baritone is currently pathing */ val isPathing: Boolean - get() = primary.pathingBehavior.isPathing + get() = isBaritoneLoaded && primary?.pathingBehavior?.isPathing == true /** * Whether Baritone is active (pathing, calculating goal, etc.) */ val isActive: Boolean - get() = primary.customGoalProcess.isActive || primary.pathingBehavior.isPathing || primary.pathingControlManager.mostRecentInControl() - .orElse(null)?.isActive == true + get() = isBaritoneLoaded && + (primary?.customGoalProcess?.isActive == true || + primary?.pathingBehavior?.isPathing == true || + primary?.pathingControlManager?.mostRecentInControl()?.orElse(null)?.isActive == true) /** * Sets the current Baritone goal and starts pathing */ - fun setGoalAndPath(goal: Goal) = primary.customGoalProcess.setGoalAndPath(goal) + fun setGoalAndPath(goal: Goal) { + if (!isBaritoneLoaded) return + primary?.customGoalProcess?.setGoalAndPath(goal) + } /** * Force cancel Baritone */ - fun cancel() = primary.pathingBehavior.cancelEverything() + fun cancel() { + if (!isBaritoneLoaded) return + primary?.pathingBehavior?.cancelEverything() + } } diff --git a/src/main/kotlin/com/lambda/interaction/PlayerPacketHandler.kt b/src/main/kotlin/com/lambda/interaction/PlayerPacketHandler.kt deleted file mode 100644 index 5cda2e509..000000000 --- a/src/main/kotlin/com/lambda/interaction/PlayerPacketHandler.kt +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2025 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.interaction - -import com.lambda.context.SafeContext -import com.lambda.event.EventFlow.post -import com.lambda.event.EventFlow.postChecked -import com.lambda.event.events.PlayerPacketEvent -import com.lambda.interaction.managers.rotating.Rotation -import com.lambda.interaction.managers.rotating.RotationManager -import com.lambda.threading.runSafe -import com.lambda.util.collections.LimitedOrderedSet -import com.lambda.util.math.approximate -import net.minecraft.network.packet.c2s.play.ClientCommandC2SPacket -import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket.Full -import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket.LookAndOnGround -import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket.OnGroundOnly -import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket.PositionAndOnGround -import net.minecraft.util.math.Vec3d - -object PlayerPacketHandler { - val configurations = LimitedOrderedSet(100) - - var lastPosition: Vec3d = Vec3d.ZERO - var lastRotation = Rotation.ZERO - var lastSprint = false - var lastSneak = false - var lastOnGround = false - var lastHorizontalCollision = false - - private var sendTicks = 0 - - @JvmStatic - fun sendPlayerPackets() { - runSafe { - PlayerPacketEvent.Pre( - player.pos, - RotationManager.activeRotation, - player.isOnGround, - player.isSprinting, - player.horizontalCollision, - ).post { - updatePlayerPackets(this) - } - } - } - - @JvmStatic - fun sendSneakPackets() { - runSafe { - val sneaking = player.isSneaking - if (sneaking == lastSneak) return@runSafe - val mode = if (sneaking) { - ClientCommandC2SPacket.Mode.PRESS_SHIFT_KEY - } else { - ClientCommandC2SPacket.Mode.RELEASE_SHIFT_KEY - } - connection.sendPacket(ClientCommandC2SPacket(player, mode)) - lastSneak = sneaking - } - } - - private fun SafeContext.updatePlayerPackets(new: PlayerPacketEvent.Pre) { - configurations.add(new) - - reportSprint(lastSprint, new.isSprinting) - - if (mc.cameraEntity != player) return - - val position = new.position - val (yaw, pitch) = new.rotation - val onGround = new.onGround - val isCollidingHorizontally = new.isCollidingHorizontally - - val updatePosition = position.approximate(lastPosition) || ++sendTicks >= 20 - val updateRotation = lastRotation.yaw != yaw || lastRotation.pitch != pitch - - when { - updatePosition && updateRotation -> Full(position, yaw.toFloat(), pitch.toFloat(), onGround, isCollidingHorizontally) - updatePosition -> PositionAndOnGround(position, onGround, isCollidingHorizontally) - updateRotation -> LookAndOnGround(yaw.toFloat(), pitch.toFloat(), onGround, isCollidingHorizontally) - lastOnGround != onGround || lastHorizontalCollision != isCollidingHorizontally -> OnGroundOnly(onGround, isCollidingHorizontally) - else -> null - }?.let { - PlayerPacketEvent.Send(it).postChecked { - connection.sendPacket(this.packet) - - if (updatePosition) { - lastPosition = position - sendTicks = 0 - } - - if (updateRotation) { - lastRotation = new.rotation - } - - lastOnGround = onGround - lastHorizontalCollision = isCollidingHorizontally - } - } - - // Update the server rotation in RotationManager - RotationManager.onRotationSend() - - PlayerPacketEvent.Post().post() - } - - fun SafeContext.reportSprint(previous: Boolean, new: Boolean) { - if (previous == new) return - - val state = if (new) { - ClientCommandC2SPacket.Mode.START_SPRINTING - } else { - ClientCommandC2SPacket.Mode.STOP_SPRINTING - } - - connection.sendPacket(ClientCommandC2SPacket(player, state)) - lastSprint = new - } -} - - diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt index e37fe716f..53c97b936 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt @@ -33,37 +33,37 @@ import kotlinx.coroutines.supervisorScope import net.minecraft.util.math.Vec3d object BuildSimulator : Sim() { - /** - * Iterates over the blueprint and performs the best suited simulation. Each simulation adds [BuildResult]s to - * the provided concurrent set. This method uses coroutines to perform the simulations in parallel. The results - * will likely not be returned in the same order they were simulated due to the parallel nature of the simulations. - * - * @see SimInfo.sim - * @see simInteraction - * @see simBreak - */ - context(automatedSafeContext: AutomatedSafeContext) - fun Structure.simulate( - pov: Vec3d = automatedSafeContext.player.eyePos - ): Set = runBlocking(Dispatchers.Default) { - supervisorScope { - val concurrentSet = ConcurrentSet() + /** + * Iterates over the blueprint and performs the best suited simulation. Each simulation adds [BuildResult]s to + * the provided concurrent set. This method uses coroutines to perform the simulations in parallel. The results + * will likely not be returned in the same order they were simulated due to the parallel nature of the simulations. + * + * @see SimInfo.sim + * @see simInteraction + * @see simBreak + */ + context(automatedSafeContext: AutomatedSafeContext) + fun Structure.simulate( + pov: Vec3d = automatedSafeContext.player.eyePos + ): Set = runBlocking(Dispatchers.Default) { + supervisorScope { + val concurrentSet = ConcurrentSet() - with(automatedSafeContext) { - forEach { (pos, targetState) -> - launch { - sim( - pos, - blockState(pos), - targetState, - pov, - concurrentSet + with(automatedSafeContext) { + forEach { (pos, targetState) -> + launch { + sim( + pos, + blockState(pos), + targetState, + pov, + concurrentSet ) - } - } - } + } + } + } - concurrentSet - } - } + concurrentSet + } + } } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/SimInfo.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/SimInfo.kt index 202799110..bb9545015 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/SimInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/SimInfo.kt @@ -19,12 +19,12 @@ package com.lambda.interaction.construction.simulation import com.lambda.context.Automated import com.lambda.context.AutomatedSafeContext -import com.lambda.interaction.construction.simulation.processing.PreProcessingData -import com.lambda.interaction.construction.simulation.processing.ProcessorRegistry.getProcessingInfo -import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.checks.BasicChecker.hasBasicRequirements import com.lambda.interaction.construction.simulation.checks.BreakSim.Companion.simBreak import com.lambda.interaction.construction.simulation.checks.InteractSim.Companion.simInteraction +import com.lambda.interaction.construction.simulation.processing.PreProcessingData +import com.lambda.interaction.construction.simulation.processing.ProcessorRegistry.getProcessingInfo +import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.verify.TargetState import com.lambda.util.BlockUtils.matches import net.minecraft.block.BlockState @@ -38,71 +38,72 @@ import java.util.* * as an extension of the [SimInfo] class to allow easy access through the DSL style sim builder. */ interface SimInfo : Automated { - val pos: BlockPos - val state: BlockState + val pos: BlockPos + val state: BlockState val targetState: TargetState - val pov: Vec3d + val pov: Vec3d val dependencyStack: Stack> - val concurrentResults: MutableSet + val concurrentResults: MutableSet @SimDsl context(_: AutomatedSafeContext, _: Sim<*>) suspend fun sim() - companion object { - /** - * Creates a [SimInfo], checks its basic requirements, and runs the [simBuilder] block. - */ - @SimDsl - context(_: BuildSimulator) - suspend fun AutomatedSafeContext.sim( - pos: BlockPos, - state: BlockState, - targetState: TargetState, - pov: Vec3d, - concurrentResults: MutableSet - ) = getTypedInfo( - pos, - state, - targetState, - pov, - Stack(), - concurrentResults - ).takeIf { it.hasBasicRequirements() }?.sim() + companion object { + /** + * Creates a [SimInfo], checks its basic requirements, and runs the [simBuilder] block. + */ + @SimDsl + context(_: BuildSimulator) + suspend fun AutomatedSafeContext.sim( + pos: BlockPos, + state: BlockState, + targetState: TargetState, + pov: Vec3d, + concurrentResults: MutableSet + ) = getTypedInfo( + pos, + state, + targetState, + pov, + Stack(), + concurrentResults + ).takeIf { it?.hasBasicRequirements() == true }?.sim() - /** - * Creates a new [SimInfo] using the current [SimInfo]'s [dependencyStack] and [concurrentResults], - * checks its basic requirements, and runs the [simBuilder] block. As simulations tend to make use of - * concurrency, a new stack is created and the dependencies from the previous stack are added. - */ - @SimDsl - context(automatedSafeContext: AutomatedSafeContext, _: Sim<*>) - suspend fun SimInfo.sim( - pos: BlockPos = this.pos, - state: BlockState = this.state, - targetState: TargetState = this.targetState, - pov: Vec3d = this.pov - ) = automatedSafeContext.getTypedInfo( - pos, - state, - targetState, - pov, - Stack>().apply { addAll(dependencyStack) }, - concurrentResults - ).takeIf { it.hasBasicRequirements() }?.sim() + /** + * Creates a new [SimInfo] using the current [SimInfo]'s [dependencyStack] and [concurrentResults], + * checks its basic requirements, and runs the [simBuilder] block. As simulations tend to make use of + * concurrency, a new stack is created and the dependencies from the previous stack are added. + */ + @SimDsl + context(automatedSafeContext: AutomatedSafeContext, _: Sim<*>) + suspend fun SimInfo.sim( + pos: BlockPos = this.pos, + state: BlockState = this.state, + targetState: TargetState = this.targetState, + pov: Vec3d = this.pov + ) = automatedSafeContext.getTypedInfo( + pos, + state, + targetState, + pov, + Stack>().apply { addAll(dependencyStack) }, + concurrentResults + ).takeIf { it?.hasBasicRequirements() == true }?.sim() - @SimDsl - private fun AutomatedSafeContext.getTypedInfo( - pos: BlockPos, - state: BlockState, - targetState: TargetState, - pov: Vec3d, - dependencyStack: Stack>, - concurrentResults: MutableSet - ): SimInfo { + @SimDsl + private fun AutomatedSafeContext.getTypedInfo( + pos: BlockPos, + state: BlockState, + targetState: TargetState, + pov: Vec3d, + dependencyStack: Stack>, + concurrentResults: MutableSet + ): SimInfo? { if (!targetState.isEmpty()) { getProcessingInfo(state, targetState, pos)?.let { preProcessing -> - return object : InteractSimInfo, Automated by this { + return if (preProcessing.info.omitInteraction) null + else object : InteractSimInfo, Automated by this { override val pos = pos override val state = state override val targetState = targetState @@ -123,19 +124,19 @@ interface SimInfo : Automated { } } - return object : BreakSimInfo, Automated by this { - override val pos = pos - override val state = state - override val targetState = targetState - override val pov = pov - override val dependencyStack = dependencyStack - override val concurrentResults = concurrentResults + return object : BreakSimInfo, Automated by this { + override val pos = pos + override val state = state + override val targetState = targetState + override val pov = pov + override val dependencyStack = dependencyStack + override val concurrentResults = concurrentResults - context(_: AutomatedSafeContext, _: Sim<*>) - override suspend fun sim() = simBreak() - } + context(_: AutomatedSafeContext, _: Sim<*>) + override suspend fun sim() = simBreak() + } } - } + } } interface InteractSimInfo : SimInfo { diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt index 98e5f6c4c..7785c578e 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt @@ -19,7 +19,8 @@ package com.lambda.interaction.construction.simulation import com.lambda.context.Automated import com.lambda.context.SafeContext -import com.lambda.graphics.renderer.esp.ShapeBuilder +import com.lambda.graphics.esp.ShapeScope +import com.lambda.graphics.mc.TransientRegionESP import com.lambda.interaction.construction.blueprint.Blueprint import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.result.Drawable @@ -41,7 +42,7 @@ data class Simulation( private val automated: Automated ) : Automated by automated { private val cache: MutableMap> = mutableMapOf() - private fun FastVector.toView(): Vec3d = toVec3d().add(0.5, ClientPlayerEntity.DEFAULT_EYE_HEIGHT.toDouble(), 0.5) + private fun FastVector.toView(): Vec3d = toVec3d().add(0.5, ClientPlayerEntity.EYE_HEIGHT.toDouble(), 0.5) fun simulate(pos: FastVector) = cache.getOrPut(pos) { @@ -63,8 +64,10 @@ data class Simulation( .map { PossiblePos(it.key.toBlockPos(), it.value.count { it.rank.ordinal < 4 }) } class PossiblePos(val pos: BlockPos, val interactions: Int) : Drawable { - override fun ShapeBuilder.buildRenderer() { - box(Vec3d.ofBottomCenter(pos).playerBox(), Color(0, 255, 0, 50), Color(0, 255, 0, 50)) + override fun render(esp: TransientRegionESP) { + esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) { + box(Vec3d.ofBottomCenter(pos).playerBox(), Color(0, 255, 0, 50), Color(0, 255, 0, 50)) + } } } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt index 3c4a88f19..05c2fe11f 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/BreakSim.kt @@ -29,8 +29,8 @@ import com.lambda.interaction.construction.simulation.result.results.BreakResult import com.lambda.interaction.construction.simulation.result.results.GenericResult import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.managers.hotbar.HotbarManager +import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest import com.lambda.interaction.managers.rotating.RotationManager -import com.lambda.interaction.managers.rotating.RotationRequest import com.lambda.interaction.managers.rotating.visibilty.lookAtBlock import com.lambda.interaction.material.ContainerSelection.Companion.selectContainer import com.lambda.interaction.material.StackSelection @@ -64,221 +64,221 @@ import net.minecraft.util.math.Direction import kotlin.jvm.optionals.getOrNull class BreakSim private constructor(simInfo: SimInfo) - : Sim(), - SimInfo by simInfo + : Sim(), + SimInfo by simInfo { - override fun dependentUpon(buildResult: BuildResult) = - BreakResult.Dependency(pos, buildResult) - - companion object { - @SimDsl - context(automatedSafeContext: AutomatedSafeContext, dependent: Sim<*>) - suspend fun BreakSimInfo.simBreak() = - BreakSim(this).run { - withDependent(dependent) { - automatedSafeContext.simBreaks() - } - } - } - - private suspend fun AutomatedSafeContext.simBreaks() { - if (breakConfig.avoidSupporting) player.supportingBlockPos.getOrNull()?.let { support -> - if (support != pos) return@let - result(BreakResult.PlayerOnTop(pos, state)) - return - } - - if (!state.fluidState.isEmpty && state.isReplaceable) { - result(BreakResult.Submerge(pos, state)) - sim(pos, state, TargetState.Solid(emptySet())) - return - } - - if (breakConfig.avoidLiquids && affectsFluids()) return - - val (swapStack, stackSelection) = getSwapStack() ?: return - val instant = instantBreakable( - state, pos, - if (breakConfig.swapMode.isEnabled()) swapStack else player.mainHandStack, - breakConfig.breakThreshold - ) - - val shape = state.getOutlineShape(world, pos) - - if (shape.boundingBoxes.map { it.offset(pos) }.any { it.contains(pov) }) { - val currentCast = RotationManager.activeRotation.rayCast(buildConfig.blockReach, pov) - currentCast?.blockResult?.let { blockHit -> - val rotationRequest = RotationRequest(lookAtBlock(pos)?.rotation ?: return, this) - val breakContext = BreakContext( - blockHit, - rotationRequest, - swapStack.inventoryIndexOrSelected, - stackSelection, - instant, - true, - state, - this - ) - result(BreakResult.Break(pos, breakContext)) - } - return - } - - val validHits = scanShape(pov, shape, pos, Direction.entries.toSet(), null) ?: return - - val bestHit = buildConfig.pointSelection.select(validHits) ?: return - val rotationRequest = RotationRequest(bestHit.rotation, this) - - val breakContext = BreakContext( - bestHit.hit.blockResult ?: return, - rotationRequest, - swapStack.inventoryIndexOrSelected, - stackSelection, - instant, - false, - state, - this - ) - - result(BreakResult.Break(pos, breakContext)) - return - } - - private fun AutomatedSafeContext.getSwapStack(): Pair? { - // Stack size 0 to account for attacking with an empty hand. Empty slots have stack size 0 - val stackSelection = selectStack( - count = 0, - sorter = compareByDescending { - it.canBreak(CachedBlockPosition(world, pos, false)) - }.thenByDescending { - state.calcItemBlockBreakingDelta(pos, it) - }.thenByDescending { - it.inventoryIndex == HotbarManager.serverSlot - } - ) { - EVERYTHING - .andIf(breakConfig.efficientOnly) { - isEfficientForBreaking(state) - }.andIf(breakConfig.suitableToolsOnly) { - isSuitableForBreaking(state) - }.andIf(breakConfig.forceSilkTouch) { - hasEnchantment(Enchantments.SILK_TOUCH) - }.andIf(breakConfig.forceFortunePickaxe) { - hasEnchantment(Enchantments.FORTUNE) - }.andIf(!breakConfig.useWoodenTools) { - hasTag(WOODEN_TOOL_MATERIALS).not() - }.andIf(!breakConfig.useStoneTools) { - hasTag(STONE_TOOL_MATERIALS).not() - }.andIf(!breakConfig.useIronTools) { - hasTag(IRON_TOOL_MATERIALS).not() - }.andIf(!breakConfig.useDiamondTools) { - hasTag(DIAMOND_TOOL_MATERIALS).not() - }.andIf(!breakConfig.useGoldTools) { - hasTag(GOLD_TOOL_MATERIALS).not() - }.andIf(!breakConfig.useNetheriteTools) { - hasTag(NETHERITE_TOOL_MATERIALS).not() - } - } - - val silentSwapSelection = selectContainer { - ofAnyType(MaterialContainer.Rank.Hotbar) - } - - val hotbarCandidates = stackSelection - .findContainersWithMaterial(silentSwapSelection) - .map { it.matchingStacks(stackSelection) } - .flatten() - if (hotbarCandidates.isEmpty()) { - result(GenericResult.WrongItemSelection(pos, stackSelection, player.mainHandStack)) - return null - } - - var bestStack = ItemStack.EMPTY - var bestBreakDelta = -1f - hotbarCandidates.forEach { stack -> - val breakDelta = state.calcItemBlockBreakingDelta(pos, stack) - if (breakDelta > bestBreakDelta || - (stack == player.mainHandStack && breakDelta >= bestBreakDelta) - ) { - bestBreakDelta = breakDelta - bestStack = stack - } - } - return if (bestBreakDelta == -1f) null - else Pair(bestStack, stackSelection) - } - - private suspend fun AutomatedSafeContext.affectsFluids(): Boolean { - val affectedBlocks = hashSetOf(pos) - val checkQueue = hashSetOf(pos) - - while (checkQueue.isNotEmpty()) { - val checkPos = checkQueue.first() - checkQueue.remove(checkPos) - for (offset in Direction.entries) { - val adjacentPos = checkPos.offset(offset) - - if (blockState(adjacentPos).block !is FallingBlock) continue - if (adjacentPos in affectedBlocks) continue - - if (offset == Direction.UP || FallingBlock.canFallThrough(blockState(adjacentPos.down()))) { - checkQueue.add(adjacentPos) - affectedBlocks.add(adjacentPos) - } - } - } - - val affectedFluids = affectedBlocks.fold(hashMapOf()) { accumulator, affectedPos -> - Direction.entries.forEach { offset -> - if (offset == Direction.DOWN) return@forEach - - val offsetPos = affectedPos.offset(offset) - val offsetState = blockState(offsetPos) - val fluidState = offsetState.fluidState - val fluid = fluidState.fluid - - if (fluidState.isEmpty || fluid !is FlowableFluid) return@forEach - - if (offset == Direction.UP) { - accumulator[offsetPos] = offsetState - return@fold accumulator - } - - if (offsetState.block is Waterloggable) { - accumulator[offsetPos] = offsetState - return@fold accumulator - } - - val levelDecreasePerBlock = - when (fluid) { - is WaterFluid -> fluid.getLevelDecreasePerBlock(world) - is LavaFluid -> fluid.getLevelDecreasePerBlock(world) - else -> 0 - } - - if (fluidState.level - levelDecreasePerBlock > 0) { - accumulator[offsetPos] = offsetState - return@fold accumulator - } - } - - return@fold accumulator - } - - if (affectedFluids.isNotEmpty()) { - val liquidOutOfBounds = affectedFluids.any { !world.worldBorder.contains(it.key) } - if (liquidOutOfBounds) { - result(GenericResult.Ignored(pos)) - return true - } - - affectedFluids.forEach { (liquidPos, liquidState) -> - result(BreakResult.Submerge(liquidPos, liquidState)) - } - result(BreakResult.BlockedByFluid(pos, state, affectedFluids.keys)) - return true - } - - return false - } + override fun dependentUpon(buildResult: BuildResult) = + BreakResult.Dependency(pos, buildResult) + + companion object { + @SimDsl + context(automatedSafeContext: AutomatedSafeContext, dependent: Sim<*>) + suspend fun BreakSimInfo.simBreak() = + BreakSim(this).run { + withDependent(dependent) { + automatedSafeContext.simBreaks() + } + } + } + + private suspend fun AutomatedSafeContext.simBreaks() { + if (breakConfig.avoidSupporting) player.supportingBlockPos.getOrNull()?.let { support -> + if (support != pos) return@let + result(BreakResult.PlayerOnTop(pos, state)) + return + } + + if (!state.fluidState.isEmpty && state.isReplaceable) { + result(BreakResult.Submerge(pos, state)) + sim(pos, state, TargetState.Solid(emptySet())) + return + } + + if (breakConfig.avoidLiquids && affectsFluids()) return + + val (swapStack, stackSelection) = getSwapStack() ?: return + val instant = instantBreakable( + state, pos, + if (breakConfig.swapMode.isEnabled()) swapStack else player.mainHandStack, + breakConfig.breakThreshold + ) + + val shape = state.getOutlineShape(world, pos) + + if (shape.boundingBoxes.map { it.offset(pos) }.any { it.contains(pov) }) { + val currentCast = RotationManager.activeRotation.rayCast(buildConfig.blockReach, pov) + currentCast?.blockResult?.let { blockHit -> + val rotationRequest = lookAtBlock(pos)?.rotation?.let { rotationRequest { rotation(it) } } ?: return + val breakContext = BreakContext( + blockHit, + rotationRequest, + swapStack.inventoryIndexOrSelected, + stackSelection, + instant, + true, + state, + this + ) + result(BreakResult.Break(pos, breakContext)) + } + return + } + + val validHits = scanShape(pov, shape, pos, Direction.entries.toSet(), null) ?: return + + val bestHit = buildConfig.pointSelection.select(validHits) ?: return + val rotationRequest = rotationRequest { rotation(bestHit.rotation) } + + val breakContext = BreakContext( + bestHit.hit.blockResult ?: return, + rotationRequest, + swapStack.inventoryIndexOrSelected, + stackSelection, + instant, + false, + state, + this + ) + + result(BreakResult.Break(pos, breakContext)) + return + } + + private fun AutomatedSafeContext.getSwapStack(): Pair? { + // Stack size 0 to account for attacking with an empty hand. Empty slots have stack size 0 + val stackSelection = selectStack( + count = 0, + sorter = compareByDescending { + it.canBreak(CachedBlockPosition(world, pos, false)) + }.thenByDescending { + state.calcItemBlockBreakingDelta(pos, it) + }.thenByDescending { + it.inventoryIndex == HotbarManager.serverSlot + } + ) { + EVERYTHING + .andIf(breakConfig.efficientOnly) { + isEfficientForBreaking(state) + }.andIf(breakConfig.suitableToolsOnly) { + isSuitableForBreaking(state) + }.andIf(breakConfig.forceSilkTouch) { + hasEnchantment(Enchantments.SILK_TOUCH) + }.andIf(breakConfig.forceFortunePickaxe) { + hasEnchantment(Enchantments.FORTUNE) + }.andIf(!breakConfig.useWoodenTools) { + hasTag(WOODEN_TOOL_MATERIALS).not() + }.andIf(!breakConfig.useStoneTools) { + hasTag(STONE_TOOL_MATERIALS).not() + }.andIf(!breakConfig.useIronTools) { + hasTag(IRON_TOOL_MATERIALS).not() + }.andIf(!breakConfig.useDiamondTools) { + hasTag(DIAMOND_TOOL_MATERIALS).not() + }.andIf(!breakConfig.useGoldTools) { + hasTag(GOLD_TOOL_MATERIALS).not() + }.andIf(!breakConfig.useNetheriteTools) { + hasTag(NETHERITE_TOOL_MATERIALS).not() + } + } + + val silentSwapSelection = selectContainer { + ofAnyType(MaterialContainer.Rank.Hotbar) + } + + val hotbarCandidates = stackSelection + .findContainersWithMaterial(silentSwapSelection) + .flatMap { it.matchingStacks(stackSelection) } + if (hotbarCandidates.isEmpty()) { + result(GenericResult.WrongItemSelection(pos, stackSelection, player.mainHandStack)) + return null + } + + var bestStack = ItemStack.EMPTY + var bestBreakDelta = -1f + hotbarCandidates.forEach { stack -> + val breakDelta = state.calcItemBlockBreakingDelta(pos, stack) + if (breakDelta > bestBreakDelta || + (stack == player.mainHandStack && breakDelta >= bestBreakDelta) + ) { + bestBreakDelta = breakDelta + bestStack = stack + } + } + return if (bestBreakDelta == -1f) null + else Pair(bestStack, stackSelection) + } + + private suspend fun AutomatedSafeContext.affectsFluids(): Boolean { + val affectedBlocks = hashSetOf(pos) + val checkQueue = hashSetOf(pos) + + while (checkQueue.isNotEmpty()) { + val checkPos = checkQueue.first() + checkQueue.remove(checkPos) + for (offset in Direction.entries) { + val adjacentPos = checkPos.offset(offset) + + if (blockState(adjacentPos).block !is FallingBlock) continue + if (adjacentPos in affectedBlocks) continue + + if (offset == Direction.UP || FallingBlock.canFallThrough(blockState(adjacentPos.down()))) { + checkQueue.add(adjacentPos) + affectedBlocks.add(adjacentPos) + } + } + } + + val affectedFluids = affectedBlocks.fold(hashMapOf()) { accumulator, affectedPos -> + Direction.entries.forEach { offset -> + if (offset == Direction.DOWN) return@forEach + + val offsetPos = affectedPos.offset(offset) + val offsetState = blockState(offsetPos) + val fluidState = offsetState.fluidState + val fluid = fluidState.fluid + + if (fluidState.isEmpty || fluid !is FlowableFluid) return@forEach + + if (offset == Direction.UP) { + accumulator[offsetPos] = offsetState + return@fold accumulator + } + + if (offsetState.block is Waterloggable) { + accumulator[offsetPos] = offsetState + return@fold accumulator + } + + val levelDecreasePerBlock = + when (fluid) { + is WaterFluid -> fluid.getLevelDecreasePerBlock(world) + is LavaFluid -> fluid.getLevelDecreasePerBlock(world) + else -> 0 + } + + if (fluidState.level - levelDecreasePerBlock > 0) { + accumulator[offsetPos] = offsetState + return@fold accumulator + } + } + + return@fold accumulator + } + + if (affectedFluids.isNotEmpty()) { + val liquidOutOfBounds = affectedFluids.any { !world.worldBorder.contains(it.key) } + if (liquidOutOfBounds) { + result(GenericResult.Ignored(pos)) + return true + } + + affectedFluids.forEach { (fluidPos, fluidState) -> + result(BreakResult.Submerge(fluidPos, fluidState)) + sim(fluidPos, fluidState, TargetState.Solid(emptySet())) + } + result(BreakResult.BlockedByFluid(pos, state, affectedFluids.keys)) + return true + } + + return false + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/InteractSim.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/InteractSim.kt index 258156fb1..64c879a91 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/InteractSim.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/checks/InteractSim.kt @@ -27,10 +27,10 @@ import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.result.results.GenericResult import com.lambda.interaction.construction.simulation.result.results.InteractResult import com.lambda.interaction.construction.verify.TargetState +import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest import com.lambda.interaction.managers.rotating.Rotation import com.lambda.interaction.managers.rotating.Rotation.Companion.rotation import com.lambda.interaction.managers.rotating.RotationManager -import com.lambda.interaction.managers.rotating.RotationRequest import com.lambda.interaction.managers.rotating.visibilty.PlaceDirection import com.lambda.interaction.managers.rotating.visibilty.VisibilityChecker.CheckedHit import com.lambda.interaction.managers.rotating.visibilty.lookInDirection @@ -47,6 +47,7 @@ import com.lambda.util.item.ItemUtils.blockItem import com.lambda.util.math.MathUtils.floorToInt import com.lambda.util.math.minus import com.lambda.util.player.MovementUtils.sneaking +import com.lambda.util.player.SlotUtils.hotbarStacks import com.lambda.util.player.copyPlayer import com.lambda.util.world.raycast.RayCastUtils.blockResult import kotlinx.coroutines.CoroutineScope @@ -70,151 +71,152 @@ import net.minecraft.util.math.RotationPropertyHelper import net.minecraft.util.shape.VoxelShapes class InteractSim private constructor(simInfo: InteractSimInfo) - : Sim(), - InteractSimInfo by simInfo + : Sim(), + InteractSimInfo by simInfo { - override fun dependentUpon(buildResult: BuildResult) = - InteractResult.Dependency(pos, buildResult) - - companion object { - context(automatedSafeContext: AutomatedSafeContext, dependent: Sim<*>) - @SimDsl - suspend fun InteractSimInfo.simInteraction() = - InteractSim(this).run { - withDependent(dependent) { - automatedSafeContext.simInteraction() - } - } - } - - private suspend fun AutomatedSafeContext.simInteraction() = - supervisorScope { - preProcessing.info.sides.forEach { side -> - val neighborPos = pos.offset(side) - val neighborSide = side.opposite - launch { testBlock(pos, side, this@supervisorScope) } - if (placing) launch { testBlock(neighborPos, neighborSide, this@supervisorScope) } - } - } - - private suspend fun AutomatedSafeContext.testBlock(pos: BlockPos, side: Direction, supervisorScope: CoroutineScope) { - if (!world.worldBorder.contains(pos)) return - - val testBlockState = blockState(pos) - val shape = testBlockState.getOutlineShape(world, pos).let { outlineShape -> - if (!outlineShape.isEmpty || !interactConfig.airPlace.isEnabled) outlineShape - else VoxelShapes.fullCube() - } - if (shape.isEmpty) return - - val fakePlayer = copyPlayer(player).apply { - val newPos = pov - (this.eyePos - this.pos) - setPos(newPos.x, newPos.y, newPos.z) - if (testBlockState.block::class in BlockUtils.interactionBlocks && preProcessing.info.placing) { - input.sneaking = true - updatePose() - } - } - val pov = fakePlayer.eyePos - - val validHits = scanShape(pov, shape, pos, setOf(side), preProcessing) ?: return - - selectHitPos(validHits, fakePlayer, supervisorScope) - } - - private suspend fun AutomatedSafeContext.selectHitPos( - validHits: Collection, - fakePlayer: ClientPlayerEntity, - supervisorScope: CoroutineScope - ) { - buildConfig.pointSelection.select(validHits)?.let { checkedHit -> - val hitResult = checkedHit.hit.blockResult ?: return - - val swapStack = getSwapStack(item) - - if (!placing) { - if (swapStack == null) return - - val interactContext = InteractContext( - hitResult, - RotationRequest(checkedHit.rotation, this@InteractSim), - swapStack.inventoryIndex, - pos, - state, - expectedState, - false, - fakePlayer.isSneaking, - true, - this@InteractSim - ) - result(InteractResult.Interact(pos, interactContext)) - return - } - - val blockItem = item as BlockItem - - val context = blockItem.getPlacementContext( - ItemPlacementContext( - world, - fakePlayer, - Hand.MAIN_HAND, - blockItem.defaultStack, - hitResult, - ) - ) ?: run { - result(InteractResult.ScaffoldExceeded(pos)) - return - } - - if (context.blockPos != pos) { - result(InteractResult.UnexpectedPosition(pos, context.blockPos)) - return - } - - val cachePos = CachedBlockPosition(context.world, context.blockPos, false) - if (!player.abilities.allowModifyWorld && !blockItem.defaultStack.canPlaceOn(cachePos)) { - result(InteractResult.IllegalUsage(pos)) - return - } - - if (!context.canPlace()) { - result(InteractResult.CantReplace(pos, context)) - return - } - - val rotatePlaceTest = simRotation(fakePlayer, checkedHit, context) ?: return - - val rotationRequest = if (interactConfig.axisRotate && !expectedState.contains(Properties.ROTATION)) - lookInDirection(PlaceDirection.fromRotation(rotatePlaceTest.rotation)) - else rotatePlaceTest.rotation - - if (swapStack == null) return - if (!swapStack.item.isEnabled(world.enabledFeatures)) { - result(InteractResult.BlockFeatureDisabled(pos, swapStack)) - supervisorScope.cancel() - return - } - - val interactContext = InteractContext( - hitResult, - RotationRequest(rotationRequest, this@InteractSim), - swapStack.inventoryIndex, - pos, - state, - rotatePlaceTest.resultState, - true, - fakePlayer.isSneaking, - rotatePlaceTest.currentDirIsValid, - this@InteractSim - ) - - result(InteractResult.Interact(pos, interactContext)) - } - - return - } - - private fun AutomatedSafeContext.getSwapStack(item: Item?): ItemStack? { + override fun dependentUpon(buildResult: BuildResult) = + InteractResult.Dependency(pos, buildResult) + + companion object { + context(automatedSafeContext: AutomatedSafeContext, dependent: Sim<*>) + @SimDsl + suspend fun InteractSimInfo.simInteraction() = + InteractSim(this).run { + withDependent(dependent) { + automatedSafeContext.simInteraction() + } + } + } + + private suspend fun AutomatedSafeContext.simInteraction() = + supervisorScope { + preProcessing.info.sides.forEach { side -> + val neighborPos = pos.offset(side) + val neighborSide = side.opposite + launch { testBlock(pos, side, this@supervisorScope) } + if (placing) launch { testBlock(neighborPos, neighborSide, this@supervisorScope) } + } + } + + private suspend fun AutomatedSafeContext.testBlock(pos: BlockPos, side: Direction, supervisorScope: CoroutineScope) { + if (!world.worldBorder.contains(pos)) return + + val testBlockState = blockState(pos) + val shape = testBlockState.getOutlineShape(world, pos).let { outlineShape -> + if (!outlineShape.isEmpty || !interactConfig.airPlace.isEnabled) outlineShape + else VoxelShapes.fullCube() + } + if (shape.isEmpty) return + + val fakePlayer = copyPlayer(player).apply { + val newPos = pov - (this.eyePos - this.pos) + setPos(newPos.x, newPos.y, newPos.z) + if (preProcessing.info.sneak == false) { + if (testBlockState.block::class in BlockUtils.interactionBlocks) return + input.sneaking = false + updatePose() + } else { + val shouldNotInteract = testBlockState.block::class in BlockUtils.interactionBlocks && preProcessing.info.placing + if (shouldNotInteract || preProcessing.info.sneak == true) { + input.sneaking = true + updatePose() + } + } + } + val pov = fakePlayer.eyePos + + val validHits = scanShape(pov, shape, pos, setOf(side), preProcessing) ?: return + + selectHitPos(validHits, fakePlayer, supervisorScope) + } + + private suspend fun AutomatedSafeContext.selectHitPos( + validHits: Collection, + fakePlayer: ClientPlayerEntity, + supervisorScope: CoroutineScope + ) { + buildConfig.pointSelection.select(validHits)?.let { checkedHit -> + val hitResult = checkedHit.hit.blockResult ?: return + + if (!placing) { + val interactContext = InteractContext( + hitResult, + rotationRequest { rotation(checkedHit.rotation) }, + getSwapStack(item, supervisorScope)?.inventoryIndex ?: return, + pos, + state, + expectedState, + preProcessing.info, + fakePlayer.isSneaking, + true, + this@InteractSim + ) + result(InteractResult.Interact(pos, interactContext)) + return + } + + val blockItem = item as BlockItem + + val context = blockItem.getPlacementContext( + ItemPlacementContext( + world, + fakePlayer, + Hand.MAIN_HAND, + blockItem.defaultStack, + hitResult, + ) + ) ?: run { + result(InteractResult.ScaffoldExceeded(pos)) + return + } + + if (context.blockPos != pos) { + result(InteractResult.UnexpectedPosition(pos, context.blockPos)) + return + } + + val cachePos = CachedBlockPosition(context.world, context.blockPos, false) + if (!player.abilities.allowModifyWorld && !blockItem.defaultStack.canPlaceOn(cachePos)) { + result(InteractResult.IllegalUsage(pos)) + return + } + + if (!context.canPlace()) { + result(InteractResult.CantReplace(pos, context)) + return + } + + val rotatePlaceTest = simRotation(fakePlayer, checkedHit, context) ?: return + + val rotationRequest = if (interactConfig.axisRotate && !expectedState.contains(Properties.ROTATION)) + lookInDirection(PlaceDirection.fromRotation(rotatePlaceTest.rotation)) + else rotatePlaceTest.rotation + + val interactContext = InteractContext( + hitResult, + rotationRequest { rotation(rotationRequest) }, + getSwapStack(item, supervisorScope)?.inventoryIndex ?: return, + pos, + state, + rotatePlaceTest.resultState, + preProcessing.info, + fakePlayer.isSneaking, + rotatePlaceTest.currentDirIsValid, + this@InteractSim + ) + + result(InteractResult.Interact(pos, interactContext)) + } + + return + } + + private fun AutomatedSafeContext.getSwapStack(item: Item?, supervisorScope: CoroutineScope): ItemStack? { + if (item?.isEnabled(world.enabledFeatures) == false) { + result(InteractResult.BlockFeatureDisabled(pos, item)) + supervisorScope.cancel() + return null + } val stackSelection = item?.select() ?: StackSelection.selectStack(0, sorter = compareByDescending { it.inventoryIndex == player.inventory.selectedSlot }) val containerSelection = selectContainer { ofAnyType(MaterialContainer.Rank.Hotbar) } @@ -222,104 +224,104 @@ class InteractSim private constructor(simInfo: InteractSimInfo) result(GenericResult.WrongItemSelection(pos, stackSelection, player.mainHandStack)) return null } + val hotbarStacks = player.hotbarStacks return stackSelection.filterStacks(container.stacks).run { - firstOrNull { it.inventoryIndex == player.inventory.selectedSlot } + firstOrNull { hotbarStacks.indexOf(it) == player.inventory.selectedSlot } ?: firstOrNull() } } - private suspend fun AutomatedSafeContext.simRotation( - fakePlayer: ClientPlayerEntity, - checkedHit: CheckedHit, - context: ItemPlacementContext - ): RotatePlaceTest? { - fakePlayer.rotation = RotationManager.serverRotation - val currentDirIsValid = testPlaceState(context) != null - - if (!interactConfig.axisRotate) { - fakePlayer.rotation = checkedHit.rotation - return testPlaceState(context)?.let { RotatePlaceTest(it, currentDirIsValid, fakePlayer.rotation) } - } - - fakePlayer.rotation = player.rotation - testPlaceState(context)?.let { playerRotTest -> - return RotatePlaceTest(playerRotTest, currentDirIsValid, fakePlayer.rotation) - } - - if (Properties.ROTATION in expectedState) { - val rotation = expectedState.get(Properties.ROTATION) - fakePlayer.yaw = RotationPropertyHelper.toDegrees(rotation) - listOf(rotation, rotation + 8).forEach { yaw -> - listOf(90f, 0f, -90f).forEach { pitch -> - fakePlayer.rotation = Rotation(RotationPropertyHelper.toDegrees(yaw), pitch) - testPlaceState(context)?.let { axisRotateTest -> - return RotatePlaceTest(axisRotateTest, currentDirIsValid, fakePlayer.rotation) - } - } - } - } - - PlaceDirection.entries.asReversed().forEach direction@{ direction -> - fakePlayer.rotation = direction.rotation - testPlaceState(context)?.let { axisRotateTest -> - return RotatePlaceTest(axisRotateTest, currentDirIsValid, fakePlayer.rotation) - } - } - - return null - } - - private suspend fun AutomatedSafeContext.testPlaceState(context: ItemPlacementContext): BlockState? { - val resultState = context.stack.blockItem.getPlacementState(context) - ?: run { - handleEntityBlockage(context) - return null - } - - return if (!matchesTarget(resultState, false)) { - result(InteractResult.NoIntegrity(pos, expectedState, context, resultState)) - null - } else resultState - } - - private suspend fun AutomatedSafeContext.handleEntityBlockage(context: ItemPlacementContext): List { - val pos = context.blockPos - val theoreticalState = context.stack.blockItem.block.getPlacementState(context) - ?: return emptyList() - - val collisionShape = theoreticalState.getCollisionShape( - world, pos, ShapeContext.ofPlacement(player) - ).offset(pos) - - val collidingEntities = collisionShape.boundingBoxes.flatMap { box -> - world.entities.filter { it.boundingBox.intersects(box) } - } - - if (collidingEntities.isNotEmpty()) { - collidingEntities - .takeIf { buildConfig.spleefEntities } - ?.run { - mapNotNull { entity -> - if (entity === player) { - result(InteractResult.BlockedBySelf(pos)) - return@mapNotNull null - } - val hitbox = entity.boundingBox - entity.getPositionsWithinHitboxXZ( - (pos.y - (hitbox.maxY - hitbox.minY)).floorToInt(), - pos.y - ) - } - .flatten() - .forEach { support -> - sim(support, blockState(support), TargetState.Empty) - } - } - result(InteractResult.BlockedByEntity(pos, collidingEntities, context.hitPos, context.side)) - } - - return collidingEntities - } - - private class RotatePlaceTest(val resultState: BlockState, val currentDirIsValid: Boolean, val rotation: Rotation) + private suspend fun AutomatedSafeContext.simRotation( + fakePlayer: ClientPlayerEntity, + checkedHit: CheckedHit, + context: ItemPlacementContext + ): RotatePlaceTest? { + fakePlayer.rotation = RotationManager.serverRotation + val currentDirIsValid = testPlaceState(context) != null + + if (!interactConfig.axisRotate) { + fakePlayer.rotation = checkedHit.rotation + return testPlaceState(context)?.let { RotatePlaceTest(it, currentDirIsValid, fakePlayer.rotation) } + } + + fakePlayer.rotation = player.rotation + testPlaceState(context)?.let { playerRotTest -> + return RotatePlaceTest(playerRotTest, currentDirIsValid, fakePlayer.rotation) + } + + if (Properties.ROTATION in expectedState) { + val rotation = expectedState.get(Properties.ROTATION) + fakePlayer.yaw = RotationPropertyHelper.toDegrees(rotation) + listOf(rotation, rotation + 8).forEach { yaw -> + listOf(90f, 0f, -90f).forEach { pitch -> + fakePlayer.rotation = Rotation(RotationPropertyHelper.toDegrees(yaw), pitch) + testPlaceState(context)?.let { axisRotateTest -> + return RotatePlaceTest(axisRotateTest, currentDirIsValid, fakePlayer.rotation) + } + } + } + } + + PlaceDirection.entries.asReversed().forEach direction@{ direction -> + fakePlayer.rotation = direction.rotation + testPlaceState(context)?.let { axisRotateTest -> + return RotatePlaceTest(axisRotateTest, currentDirIsValid, fakePlayer.rotation) + } + } + + return null + } + + private suspend fun AutomatedSafeContext.testPlaceState(context: ItemPlacementContext): BlockState? { + val resultState = (context.stack.blockItem ?: return null).getPlacementState(context) + ?: run { + handleEntityBlockage(context) + return null + } + + return if (!matchesTarget(resultState, false)) { + result(InteractResult.NoIntegrity(pos, expectedState, context, resultState)) + null + } else resultState + } + + private suspend fun AutomatedSafeContext.handleEntityBlockage(context: ItemPlacementContext): List { + val pos = context.blockPos + val theoreticalState = (context.stack.blockItem ?: return emptyList()).block.getPlacementState(context) + ?: return emptyList() + + val collisionShape = theoreticalState.getCollisionShape( + world, pos, ShapeContext.ofPlacement(player) + ).offset(pos) + + val collidingEntities = collisionShape.boundingBoxes.flatMap { box -> + world.entities.filter { it.boundingBox.intersects(box) } + } + + if (collidingEntities.isNotEmpty()) { + collidingEntities + .takeIf { buildConfig.spleefEntities } + ?.run { + mapNotNull { entity -> + if (entity === player) { + result(InteractResult.BlockedBySelf(pos)) + return@mapNotNull null + } + val hitbox = entity.boundingBox + entity.getPositionsWithinHitboxXZ( + (pos.y - (hitbox.maxY - hitbox.minY)).floorToInt(), + pos.y + ) + }.flatten() + .forEach { support -> + sim(support, blockState(support), TargetState.Empty) + } + } + result(InteractResult.BlockedByEntity(pos, collidingEntities, context.hitPos, context.side)) + } + + return collidingEntities + } + + private class RotatePlaceTest(val resultState: BlockState, val currentDirIsValid: Boolean, val rotation: Rotation) } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt index 7d2a273a0..17ecce3ef 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/context/BreakContext.kt @@ -18,19 +18,15 @@ package com.lambda.interaction.construction.simulation.context import com.lambda.context.Automated -import com.lambda.graphics.renderer.esp.ShapeBuilder -import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.managers.LogContext -import com.lambda.interaction.managers.LogContext.Companion.LogContextBuilder -import com.lambda.interaction.managers.LogContext.Companion.getLogContextBuilder +import com.lambda.graphics.mc.TransientRegionESP import com.lambda.interaction.managers.rotating.RotationRequest +import com.lambda.interaction.material.StackSelection import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.emptyState import net.minecraft.block.BlockState import net.minecraft.block.FallingBlock import net.minecraft.util.hit.BlockHitResult import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.Box import java.awt.Color import kotlin.math.sqrt @@ -43,7 +39,7 @@ data class BreakContext( val insideBlock: Boolean, override var cachedState: BlockState, private val automated: Automated -) : BuildContext(), LogContext, Automated by automated { +) : BuildContext(), Automated by automated { private val baseColor = Color(222, 0, 0, 25) private val sideColor = Color(222, 0, 0, 100) @@ -63,25 +59,9 @@ data class BreakContext( override val sorter get() = breakConfig.sorter - override fun ShapeBuilder.buildRenderer() { - val box = with(hitResult.pos) { - Box( - x - 0.05, y - 0.05, z - 0.05, - x + 0.05, y + 0.05, z + 0.05, - ).offset(hitResult.side.doubleVector.multiply(0.05)) - } - box(box, baseColor, sideColor) - } - - override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = { - group("Break Context") { - text(blockPos.getLogContextBuilder()) - text(hitResult.getLogContextBuilder()) - text(rotationRequest.getLogContextBuilder()) - value("Hotbar Index", hotbarIndex) - value("Instant Break", instantBreak) - value("Cached State", cachedState) - value("Expected State", expectedState) + override fun render(esp: TransientRegionESP) { + esp.shapes(blockPos.x.toDouble(), blockPos.y.toDouble(), blockPos.z.toDouble()) { + box(blockPos, baseColor, sideColor) } } } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/context/InteractContext.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/context/InteractContext.kt index 0ccc754e6..cd78d3f52 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/context/InteractContext.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/context/InteractContext.kt @@ -18,11 +18,8 @@ package com.lambda.interaction.construction.simulation.context import com.lambda.context.Automated -import com.lambda.graphics.renderer.esp.ShapeBuilder -import com.lambda.interaction.managers.LogContext -import com.lambda.interaction.managers.LogContext.Companion.LogContextBuilder -import com.lambda.interaction.managers.LogContext.Companion.getLogContextBuilder -import com.lambda.interaction.managers.Request.Companion.submit +import com.lambda.graphics.mc.TransientRegionESP +import com.lambda.interaction.construction.simulation.processing.PreProcessingInfo import com.lambda.interaction.managers.hotbar.HotbarRequest import com.lambda.interaction.managers.interacting.InteractRequest import com.lambda.interaction.managers.rotating.RotationRequest @@ -39,44 +36,33 @@ data class InteractContext( override val blockPos: BlockPos, override var cachedState: BlockState, override val expectedState: BlockState, - val placing: Boolean, + val preProcessingInfo: PreProcessingInfo, val sneak: Boolean, val currentDirIsValid: Boolean = false, private val automated: Automated -) : BuildContext(), LogContext, Automated by automated { +) : BuildContext(), Automated by automated { private val baseColor = Color(35, 188, 254, 50) private val sideColor = Color(35, 188, 254, 100) override val sorter get() = interactConfig.sorter - override fun ShapeBuilder.buildRenderer() { - val box = with(hitResult.pos) { - Box( - x - 0.05, y - 0.05, z - 0.05, - x + 0.05, y + 0.05, z + 0.05, - ).offset(hitResult.side.doubleVector.multiply(0.05)) + override fun render(esp: TransientRegionESP) { + esp.shapes(hitResult.pos.x, hitResult.pos.y, hitResult.pos.z) { + val box = with(hitResult.pos) { + Box( + x - 0.05, y - 0.05, z - 0.05, + x + 0.05, y + 0.05, z + 0.05, + ).offset(hitResult.side.doubleVector.multiply(0.05)) + } + box(box, baseColor, sideColor) } - box(box, baseColor, sideColor) } fun requestDependencies(request: InteractRequest): Boolean { - val hotbarRequest = submit(HotbarRequest(hotbarIndex, this), false) + val hotbarRequest = HotbarRequest(hotbarIndex, this).submit(queueIfMismatchedStage = false) val validRotation = if (request.interactConfig.rotate) { - submit(rotationRequest, false).done && currentDirIsValid + rotationRequest.submit(queueIfMismatchedStage = false).done && currentDirIsValid } else true return hotbarRequest.done && validRotation } - - override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = { - group("Place Context") { - text(blockPos.getLogContextBuilder()) - text(hitResult.getLogContextBuilder()) - text(rotationRequest.getLogContextBuilder()) - value("Hotbar Index", hotbarIndex) - value("Cached State", cachedState) - value("Expected State", expectedState) - value("Sneak", sneak) - value("Current Dir Is Valid", currentDirIsValid) - } - } } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/PlacementProcessor.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/PlacementProcessor.kt index 438208641..75aaaaa21 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/PlacementProcessor.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/PlacementProcessor.kt @@ -34,11 +34,15 @@ interface StateProcessor { } interface PropertyPreProcessor { - fun acceptsState(targetState: BlockState): Boolean - fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState) + fun acceptsState(state: BlockState, targetState: BlockState): Boolean + + context(safeContext: SafeContext) + fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState, pos: BlockPos) } interface PropertyPostProcessor { fun acceptsState(state: BlockState, targetState: BlockState): Boolean - fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState) + + context(safeContext: SafeContext) + fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState, pos: BlockPos) } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/PreProcessingInfo.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/PreProcessingInfo.kt index a3957e185..50e6efc98 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/PreProcessingInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/PreProcessingInfo.kt @@ -33,7 +33,9 @@ interface PreProcessingInfo { val item: Item? val expectedState: BlockState val placing: Boolean + val sneak: Boolean? val noCaching: Boolean + val omitInteraction: Boolean companion object { context(_: AutomatedSafeContext) @@ -44,7 +46,9 @@ interface PreProcessingInfo { override val item = targetState.getStack(pos).item override val expectedState = targetState.getState(pos) override val placing = true + override val sneak = null override val noCaching = true + override val omitInteraction = false } } } @@ -56,8 +60,9 @@ class PreProcessingInfoAccumulator( override val ignore: MutableSet> = ProcessorRegistry.postProcessedProperties.toMutableSet(), override val sides: MutableSet = Direction.entries.toMutableSet(), override var placing: Boolean = true, + override var sneak: Boolean? = null, override var noCaching: Boolean = false, - var omitPlacement: Boolean = false + override var omitInteraction: Boolean = false ) : PreProcessingInfo { @InfoAccumulator fun offerSurfaceScan(scan: SurfaceScan) { @@ -99,14 +104,20 @@ class PreProcessingInfoAccumulator( this.placing = placing } + @InfoAccumulator + @JvmName("setSneak1") + fun setSneak(sneak: Boolean) { + this.sneak = sneak + } + @InfoAccumulator fun noCaching() { noCaching = true } @InfoAccumulator - fun omitPlacement() { - omitPlacement = true + fun omitInteraction() { + omitInteraction = true } @InfoAccumulator diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/ProcessorRegistry.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/ProcessorRegistry.kt index edfe5143c..b7f83e579 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/ProcessorRegistry.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/ProcessorRegistry.kt @@ -22,6 +22,7 @@ import com.lambda.context.SafeContext import com.lambda.core.Loadable import com.lambda.interaction.construction.simulation.SimDsl import com.lambda.interaction.construction.verify.TargetState +import com.lambda.util.BlockUtils.matches import com.lambda.util.reflections.getInstances import net.minecraft.block.BlockState import net.minecraft.item.ItemStack @@ -118,9 +119,8 @@ object ProcessorRegistry : Loadable { Properties.DELAY, Properties.COMPARATOR_MODE, Properties.OPEN, - Properties.NOTE, - - ) + Properties.NOTE + ) override fun load() = "Loaded ${propertyPreProcessors.size} pre processors" @@ -139,29 +139,34 @@ object ProcessorRegistry : Loadable { preProcess(pos, state, targetBlockState, targetState.getStack(pos)).also { info -> if (info?.noCaching != true) processorCache[processorCacheKey] = info } - } + } ?: return null - return PreProcessingData(preProcessingInfo ?: return null, pos) + return PreProcessingData(preProcessingInfo, pos) } context(safeContext: SafeContext) private fun preProcess(pos: BlockPos, state: BlockState, targetState: BlockState, itemStack: ItemStack) = PreProcessingInfoAccumulator(targetState, itemStack.item).run { - val stateProcessing = stateProcessors.any { processor -> - processor.acceptsState(state, targetState).also { accepted -> - if (accepted) - with(processor) { preProcess(state, targetState, pos) } + stateProcessors.forEach { processor -> + if (processor.acceptsState(state, targetState)) { + with(processor) { preProcess(state, targetState, pos) } } } - if (omitPlacement) return@run complete() - if (!stateProcessing) { - if (!state.isReplaceable && state.block != expectedState.block) return@run null - if (state.block != expectedState.block) propertyPreProcessors.forEach { processor -> - if (processor.acceptsState(targetState)) - with(processor) { preProcess(state, expectedState) } - } else propertyPostProcessors.forEach { processor -> - if (processor.acceptsState(state, expectedState)) - with(processor) { preProcess(state, expectedState) } + if (!omitInteraction) { + if (state.block != expectedState.block) { + if (!state.isReplaceable) return@run null + propertyPreProcessors.forEach { processor -> + if (processor.acceptsState(state, expectedState)) { + with(processor) { preProcess(state, expectedState, pos) } + } + } + } else { + propertyPostProcessors.forEach { processor -> + if (processor.acceptsState(state, expectedState)) { + with(processor) { preProcess(state, expectedState, pos) } + } + } + if (!state.matches(expectedState, ignore)) return@run null } } complete() diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/post/ChestPostProcessor.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/post/ChestPostProcessor.kt new file mode 100644 index 000000000..8b5df6b32 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/post/ChestPostProcessor.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2026 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.interaction.construction.simulation.processing.preprocessors.property.placement.post + +import com.lambda.context.SafeContext +import com.lambda.interaction.construction.simulation.processing.PreProcessingInfoAccumulator +import com.lambda.interaction.construction.simulation.processing.PropertyPostProcessor +import com.lambda.util.BlockUtils.blockState +import net.minecraft.block.BlockState +import net.minecraft.block.ChestBlock +import net.minecraft.block.enums.ChestType +import net.minecraft.state.property.Properties +import net.minecraft.util.math.BlockPos + +// Collected using reflections and then accessed from a collection in ProcessorRegistry +@Suppress("unused") +object ChestPostProcessor : PropertyPostProcessor { + override fun acceptsState(state: BlockState, targetState: BlockState) = + state.block is ChestBlock && state.block === targetState.block && + Properties.CHEST_TYPE in state && Properties.HORIZONTAL_FACING in state && + state.get(Properties.HORIZONTAL_FACING) == targetState.get(Properties.HORIZONTAL_FACING) && + state.get(Properties.CHEST_TYPE) != targetState.get(Properties.CHEST_TYPE) + + context(safeContext: SafeContext) + override fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState, pos: BlockPos) { + noCaching() + val targetType = targetState.get(Properties.CHEST_TYPE) + val currentType = state.get(Properties.CHEST_TYPE) + if (currentType != ChestType.SINGLE) return + val currentFacing = state.get(Properties.HORIZONTAL_FACING) + val otherChestDirection = when(targetType) { + ChestType.LEFT -> currentFacing.rotateYClockwise() + else -> currentFacing.rotateYCounterclockwise() + } + val otherChest = safeContext.blockState(pos.offset(otherChestDirection)) + if (otherChest.block !is ChestBlock || otherChest.get(Properties.CHEST_TYPE) != ChestType.SINGLE) + addIgnores(Properties.CHEST_TYPE) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/post/StandardInteractPreProcessor.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/post/StandardInteractPostProcessor.kt similarity index 80% rename from src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/post/StandardInteractPreProcessor.kt rename to src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/post/StandardInteractPostProcessor.kt index 9c134f9a3..8a5db3e5c 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/post/StandardInteractPreProcessor.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/post/StandardInteractPostProcessor.kt @@ -17,18 +17,23 @@ package com.lambda.interaction.construction.simulation.processing.preprocessors.property.placement.post +import com.lambda.context.SafeContext import com.lambda.interaction.construction.simulation.processing.PreProcessingInfoAccumulator import com.lambda.interaction.construction.simulation.processing.ProcessorRegistry.standardInteractProperties import com.lambda.interaction.construction.simulation.processing.PropertyPostProcessor import net.minecraft.block.BlockState +import net.minecraft.util.math.BlockPos -object StandardInteractPreProcessor : PropertyPostProcessor { +// Collected using reflections and then accessed from a collection in ProcessorRegistry +@Suppress("unused") +object StandardInteractPostProcessor : PropertyPostProcessor { override fun acceptsState(state: BlockState, targetState: BlockState) = standardInteractProperties.any { it in targetState && state.get(it) != targetState.get(it) } - override fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState) { + context(safeContext: SafeContext) + override fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState, pos: BlockPos) { setItem(null) setPlacing(false) } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/AttachmentPreProcessor.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/AttachmentPreProcessor.kt index 6a399c562..756fc0d07 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/AttachmentPreProcessor.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/AttachmentPreProcessor.kt @@ -17,20 +17,23 @@ package com.lambda.interaction.construction.simulation.processing.preprocessors.property.placement.pre +import com.lambda.context.SafeContext import com.lambda.interaction.construction.simulation.processing.PreProcessingInfoAccumulator import com.lambda.interaction.construction.simulation.processing.PropertyPreProcessor import net.minecraft.block.BlockState import net.minecraft.block.enums.Attachment import net.minecraft.state.property.Properties +import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction // Collected using reflections and then accessed from a collection in ProcessorRegistry @Suppress("unused") object AttachmentPreProcessor : PropertyPreProcessor { - override fun acceptsState(targetState: BlockState) = + override fun acceptsState(state: BlockState, targetState: BlockState) = Properties.ATTACHMENT in targetState - override fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState) { + context(safeContext: SafeContext) + override fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState, pos: BlockPos) { val attachment = targetState.get(Properties.ATTACHMENT) ?: return when (attachment) { Attachment.FLOOR -> retainSides(Direction.DOWN) diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/AxisPreProcessor.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/AxisPreProcessor.kt index 11aff0c2a..9c2322bfa 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/AxisPreProcessor.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/AxisPreProcessor.kt @@ -17,18 +17,21 @@ package com.lambda.interaction.construction.simulation.processing.preprocessors.property.placement.pre +import com.lambda.context.SafeContext import com.lambda.interaction.construction.simulation.processing.PreProcessingInfoAccumulator import com.lambda.interaction.construction.simulation.processing.PropertyPreProcessor import net.minecraft.block.BlockState import net.minecraft.state.property.Properties +import net.minecraft.util.math.BlockPos // Collected using reflections and then accessed from a collection in ProcessorRegistry @Suppress("unused") object AxisPreProcessor : PropertyPreProcessor { - override fun acceptsState(targetState: BlockState) = + override fun acceptsState(state: BlockState, targetState: BlockState) = Properties.AXIS in targetState - override fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState) { + context(safeContext: SafeContext) + override fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState, pos: BlockPos) { val axis = targetState.get(Properties.AXIS) retainSides { side -> side.axis == axis } } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/BlockFacePreProcessor.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/BlockFacePreProcessor.kt index aecbd5820..1d43ea1db 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/BlockFacePreProcessor.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/BlockFacePreProcessor.kt @@ -17,20 +17,23 @@ package com.lambda.interaction.construction.simulation.processing.preprocessors.property.placement.pre +import com.lambda.context.SafeContext import com.lambda.interaction.construction.simulation.processing.PreProcessingInfoAccumulator import com.lambda.interaction.construction.simulation.processing.PropertyPreProcessor import net.minecraft.block.BlockState import net.minecraft.block.enums.BlockFace import net.minecraft.state.property.Properties +import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction // Collected using reflections and then accessed from a collection in ProcessorRegistry @Suppress("unused") object BlockFacePreProcessor : PropertyPreProcessor { - override fun acceptsState(targetState: BlockState) = + override fun acceptsState(state: BlockState, targetState: BlockState) = Properties.BLOCK_FACE in targetState - override fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState) { + context(safeContext: SafeContext) + override fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState, pos: BlockPos) { val property = targetState.get(Properties.BLOCK_FACE) ?: return when (property) { BlockFace.FLOOR -> retainSides(Direction.DOWN) diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/BlockHalfPreProcessor.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/BlockHalfPreProcessor.kt index 41153b10d..61c318c99 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/BlockHalfPreProcessor.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/BlockHalfPreProcessor.kt @@ -17,6 +17,7 @@ package com.lambda.interaction.construction.simulation.processing.preprocessors.property.placement.pre +import com.lambda.context.SafeContext import com.lambda.interaction.construction.simulation.processing.PreProcessingInfoAccumulator import com.lambda.interaction.construction.simulation.processing.PropertyPreProcessor import com.lambda.interaction.construction.verify.ScanMode @@ -24,15 +25,17 @@ import com.lambda.interaction.construction.verify.SurfaceScan import net.minecraft.block.BlockState import net.minecraft.block.enums.BlockHalf import net.minecraft.state.property.Properties +import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction // Collected using reflections and then accessed from a collection in ProcessorRegistry @Suppress("unused") object BlockHalfPreProcessor : PropertyPreProcessor { - override fun acceptsState(targetState: BlockState) = + override fun acceptsState(state: BlockState, targetState: BlockState) = Properties.BLOCK_HALF in targetState - override fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState) { + context(safeContext: SafeContext) + override fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState, pos: BlockPos) { val slab = targetState.get(Properties.BLOCK_HALF) ?: return val surfaceScan = when (slab) { diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/ChestPreProcessor.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/ChestPreProcessor.kt new file mode 100644 index 000000000..e82691361 --- /dev/null +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/ChestPreProcessor.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2026 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.interaction.construction.simulation.processing.preprocessors.property.placement.pre + +import com.lambda.context.SafeContext +import com.lambda.interaction.construction.simulation.processing.PreProcessingInfoAccumulator +import com.lambda.interaction.construction.simulation.processing.PropertyPreProcessor +import net.minecraft.block.BlockState +import net.minecraft.block.ChestBlock +import net.minecraft.block.enums.ChestType +import net.minecraft.state.property.Properties +import net.minecraft.util.math.BlockPos + +// Collected using reflections and then accessed from a collection in ProcessorRegistry +@Suppress("unused") +object ChestPreProcessor : PropertyPreProcessor { + override fun acceptsState(state: BlockState, targetState: BlockState) = + targetState.block is ChestBlock && Properties.CHEST_TYPE in targetState && Properties.HORIZONTAL_FACING in targetState + + context(safeContext: SafeContext) + override fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState, pos: BlockPos) { + noCaching() + val chestBlock = targetState.block as? ChestBlock ?: return + val targetType = targetState.get(Properties.CHEST_TYPE) + val targetFacing = targetState.get(Properties.HORIZONTAL_FACING) + val placeType = chestBlock.getChestType(safeContext.world, pos, targetFacing) + if (placeType == targetType) return + if (targetType != ChestType.SINGLE) { + if (placeType == ChestType.SINGLE) addIgnores(Properties.CHEST_TYPE) + else { + val canPlaceWithTypeRight = targetFacing == chestBlock.getNeighborChestDirection(safeContext.world, pos, targetFacing.rotateYCounterclockwise()) + if (targetType != ChestType.RIGHT || !canPlaceWithTypeRight) { + setExpectedState(targetState.with(Properties.CHEST_TYPE, ChestType.SINGLE)) + setSneak(true) + } + } + } else setSneak(true) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/DoorHingePreProcessor.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/DoorHingePreProcessor.kt index eaa037bb8..88ff92b7e 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/DoorHingePreProcessor.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/DoorHingePreProcessor.kt @@ -17,6 +17,7 @@ package com.lambda.interaction.construction.simulation.processing.preprocessors.property.placement.pre +import com.lambda.context.SafeContext import com.lambda.interaction.construction.simulation.processing.PreProcessingInfoAccumulator import com.lambda.interaction.construction.simulation.processing.PropertyPreProcessor import com.lambda.interaction.construction.verify.ScanMode @@ -24,15 +25,17 @@ import com.lambda.interaction.construction.verify.SurfaceScan import net.minecraft.block.BlockState import net.minecraft.block.enums.DoorHinge import net.minecraft.state.property.Properties +import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction // Collected using reflections and then accessed from a collection in ProcessorRegistry @Suppress("unused") object DoorHingePreProcessor : PropertyPreProcessor { - override fun acceptsState(targetState: BlockState) = + override fun acceptsState(state: BlockState, targetState: BlockState) = Properties.DOOR_HINGE in targetState - override fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState) { + context(safeContext: SafeContext) + override fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState, pos: BlockPos) { val side = targetState.get(Properties.DOOR_HINGE) ?: return val scanner = when (targetState.get(Properties.HORIZONTAL_FACING) ?: return) { Direction.NORTH -> diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/HopperFacingPreProcessor.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/HopperFacingPreProcessor.kt index 87b43486f..e26a0bccc 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/HopperFacingPreProcessor.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/HopperFacingPreProcessor.kt @@ -17,23 +17,26 @@ package com.lambda.interaction.construction.simulation.processing.preprocessors.property.placement.pre +import com.lambda.context.SafeContext import com.lambda.interaction.construction.simulation.processing.PreProcessingInfoAccumulator import com.lambda.interaction.construction.simulation.processing.PropertyPreProcessor import net.minecraft.block.BlockState import net.minecraft.state.property.Properties +import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction // Collected using reflections and then accessed from a collection in ProcessorRegistry @Suppress("unused") object HopperFacingPreProcessor : PropertyPreProcessor { - override fun acceptsState(targetState: BlockState) = + override fun acceptsState(state: BlockState, targetState: BlockState) = Properties.HOPPER_FACING in targetState - override fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState) { + context(safeContext: SafeContext) + override fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState, pos: BlockPos) { val facing = targetState.get(Properties.HOPPER_FACING) ?: return when { facing.axis == Direction.Axis.Y -> retainSides { it.axis == Direction.Axis.Y } - else -> retainSides(facing) + else -> retainSides(facing, facing.opposite) } } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/SlabPreProcessor.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/SlabPreProcessor.kt index 08300790d..4ef2df262 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/SlabPreProcessor.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/property/placement/pre/SlabPreProcessor.kt @@ -17,6 +17,7 @@ package com.lambda.interaction.construction.simulation.processing.preprocessors.property.placement.pre +import com.lambda.context.SafeContext import com.lambda.interaction.construction.simulation.processing.PreProcessingInfoAccumulator import com.lambda.interaction.construction.simulation.processing.PropertyPreProcessor import com.lambda.interaction.construction.verify.ScanMode @@ -25,14 +26,16 @@ import net.minecraft.block.BlockState import net.minecraft.block.SlabBlock import net.minecraft.block.enums.SlabType import net.minecraft.state.property.Properties +import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction // Collected using reflections and then accessed from a collection in ProcessorRegistry @Suppress("unused") object SlabPreProcessor : PropertyPreProcessor { - override fun acceptsState(targetState: BlockState) = targetState.block is SlabBlock + override fun acceptsState(state: BlockState, targetState: BlockState) = targetState.block is SlabBlock - override fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState) { + context(safeContext: SafeContext) + override fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState, pos: BlockPos) { val slab = targetState.get(Properties.SLAB_TYPE) ?: return val surfaceScan = when (slab) { diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/state/BambooPreProcessor.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/state/BambooPreProcessor.kt index a15db85e3..85d21073f 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/state/BambooPreProcessor.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/processing/preprocessors/state/BambooPreProcessor.kt @@ -36,7 +36,7 @@ object BambooPreProcessor : StateProcessor { context(safeContext: SafeContext) override fun PreProcessingInfoAccumulator.preProcess(state: BlockState, targetState: BlockState, pos: BlockPos) { if (state.block == Blocks.BAMBOO_SAPLING) { - omitPlacement() + omitInteraction() return } noCaching() diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Contextual.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Contextual.kt index 2fbae62ae..73accade9 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Contextual.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Contextual.kt @@ -22,7 +22,6 @@ import com.lambda.interaction.construction.simulation.context.BreakContext import com.lambda.interaction.construction.simulation.context.BuildContext import com.lambda.interaction.construction.simulation.context.InteractContext import com.lambda.interaction.managers.hotbar.HotbarManager -import com.lambda.interaction.managers.rotating.Rotation.Companion.dist import com.lambda.interaction.managers.rotating.RotationManager import com.lambda.threading.runSafe import com.lambda.util.BlockUtils @@ -51,7 +50,7 @@ interface Contextual : ComparableResult { ActionConfig.SortMode.Tool, ActionConfig.SortMode.Closest -> it.sortDistance ActionConfig.SortMode.Farthest -> -it.sortDistance - ActionConfig.SortMode.Rotation -> it.rotationRequest.rotation.value?.dist(RotationManager.activeRotation) + ActionConfig.SortMode.Rotation -> it.rotationRequest dist RotationManager.activeRotation ActionConfig.SortMode.Random -> it.random } }.thenByDescending { diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Drawable.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Drawable.kt index cd51bd728..ac339712a 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Drawable.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Drawable.kt @@ -17,11 +17,11 @@ package com.lambda.interaction.construction.simulation.result -import com.lambda.graphics.renderer.esp.ShapeBuilder +import com.lambda.graphics.mc.TransientRegionESP /** * Represents a [BuildResult] that can be rendered in-game. */ interface Drawable { - fun ShapeBuilder.buildRenderer() + fun render(esp: TransientRegionESP) } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Resolvable.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Resolvable.kt index ba8f70371..afbff5de1 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Resolvable.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/Resolvable.kt @@ -17,14 +17,13 @@ package com.lambda.interaction.construction.simulation.result -import com.lambda.context.Automated -import com.lambda.context.SafeContext +import com.lambda.context.AutomatedSafeContext import com.lambda.task.Task /** * Represents a [BuildResult] with a resolvable [Task] */ interface Resolvable { - context(automated: Automated, safeContext: SafeContext) - fun resolve(): Task<*>? + context(task: Task<*>, _: AutomatedSafeContext) + fun resolve() } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt index 100724f93..ed85fd9e8 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/BreakResult.kt @@ -19,10 +19,9 @@ package com.lambda.interaction.construction.simulation.result.results import baritone.api.pathing.goals.GoalBlock import baritone.api.pathing.goals.GoalInverted -import com.lambda.context.Automated -import com.lambda.context.SafeContext +import com.lambda.context.AutomatedSafeContext +import com.lambda.graphics.mc.TransientRegionESP import com.lambda.graphics.renderer.esp.DirectionMask.mask -import com.lambda.graphics.renderer.esp.ShapeBuilder import com.lambda.interaction.construction.simulation.context.BreakContext import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.result.ComparableResult @@ -33,8 +32,9 @@ import com.lambda.interaction.construction.simulation.result.Navigable import com.lambda.interaction.construction.simulation.result.Rank import com.lambda.interaction.construction.simulation.result.Resolvable import com.lambda.interaction.material.StackSelection.Companion.selectStack -import com.lambda.interaction.material.container.ContainerManager.transfer -import com.lambda.interaction.material.container.containers.MainHandContainer +import com.lambda.interaction.material.container.ContainerManager.transferByTask +import com.lambda.interaction.material.container.containers.HotbarContainer +import com.lambda.task.Task import net.minecraft.block.BlockState import net.minecraft.item.Item import net.minecraft.util.math.BlockPos @@ -55,8 +55,8 @@ sealed class BreakResult : BuildResult() { ) : Contextual, Drawable, BreakResult() { override val rank = Rank.BreakSuccess - override fun ShapeBuilder.buildRenderer() { - with(context) { buildRenderer() } + override fun render(esp: TransientRegionESP) { + context.render(esp) } } @@ -72,8 +72,10 @@ sealed class BreakResult : BuildResult() { override val rank = Rank.BreakNotExposed private val color = Color(46, 0, 0, 30) - override fun ShapeBuilder.buildRenderer() { - box(pos, color, color, side.mask) + override fun render(esp: TransientRegionESP) { + esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) { + box(pos, color, color, side.mask) + } } override fun compareResult(other: ComparableResult) = @@ -95,11 +97,12 @@ sealed class BreakResult : BuildResult() { ) : Resolvable, BreakResult() { override val rank = Rank.BreakItemCantMine - context(automated: Automated, safeContext: SafeContext) - override fun resolve() = + context(task: Task<*>, _: AutomatedSafeContext) + override fun resolve() { selectStack { isItem(badItem).not() - }.transfer(MainHandContainer) + }.transferByTask(HotbarContainer)?.execute(task) + } override fun compareResult(other: ComparableResult) = when (other) { @@ -119,8 +122,10 @@ sealed class BreakResult : BuildResult() { override val rank = Rank.BreakSubmerge private val color = Color(114, 27, 255, 100) - override fun ShapeBuilder.buildRenderer() { - box(pos, color, color) + override fun render(esp: TransientRegionESP) { + esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) { + box(pos, color, color) + } } } @@ -135,13 +140,15 @@ sealed class BreakResult : BuildResult() { override val rank = Rank.BreakIsBlockedByFluid private val color = Color(50, 12, 112, 100) - override fun ShapeBuilder.buildRenderer() { - val center = pos.toCenterPos() - val box = Box( - center.x - 0.1, center.y - 0.1, center.z - 0.1, - center.x + 0.1, center.y + 0.1, center.z + 0.1 - ) - box(box, color, color) + override fun render(esp: TransientRegionESP) { + esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) { + val center = pos.toCenterPos() + val box = Box( + center.x - 0.1, center.y - 0.1, center.z - 0.1, + center.x + 0.1, center.y + 0.1, center.z + 0.1 + ) + box(box, color, color) + } } } @@ -157,8 +164,10 @@ sealed class BreakResult : BuildResult() { override val goal = GoalInverted(GoalBlock(pos)) - override fun ShapeBuilder.buildRenderer() { - box(pos, color, color) + override fun render(esp: TransientRegionESP) { + esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) { + box(pos, color, color) + } } } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt index 6009ebaf4..0694c3c6d 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/GenericResult.kt @@ -18,9 +18,8 @@ package com.lambda.interaction.construction.simulation.result.results import baritone.api.pathing.goals.GoalNear -import com.lambda.context.Automated -import com.lambda.context.SafeContext -import com.lambda.graphics.renderer.esp.ShapeBuilder +import com.lambda.context.AutomatedSafeContext +import com.lambda.graphics.mc.TransientRegionESP import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.result.ComparableResult import com.lambda.interaction.construction.simulation.result.Drawable @@ -28,8 +27,9 @@ import com.lambda.interaction.construction.simulation.result.Navigable import com.lambda.interaction.construction.simulation.result.Rank import com.lambda.interaction.construction.simulation.result.Resolvable import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.container.ContainerManager.transfer -import com.lambda.interaction.material.container.containers.MainHandContainer +import com.lambda.interaction.material.container.ContainerManager.transferByTask +import com.lambda.interaction.material.container.containers.HotbarContainer +import com.lambda.task.Task import net.minecraft.client.data.TextureMap.side import net.minecraft.item.ItemStack import net.minecraft.util.math.BlockPos @@ -53,14 +53,16 @@ sealed class GenericResult : BuildResult() { override val rank = Rank.NotVisible private val color = Color(46, 0, 0, 80) - override fun ShapeBuilder.buildRenderer() { - val box = with(pos) { - Box( - x - 0.05, y - 0.05, z - 0.05, - x + 0.05, y + 0.05, z + 0.05, - ).offset(pos) + override fun render(esp: TransientRegionESP) { + esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) { + val box = with(pos) { + Box( + x - 0.05, y - 0.05, z - 0.05, + x + 0.05, y + 0.05, z + 0.05, + ).offset(pos) + } + box(box, color, color) } - box(box, color, color) } override fun compareResult(other: ComparableResult): Int { @@ -95,16 +97,20 @@ sealed class GenericResult : BuildResult() { override val rank = Rank.WrongItem private val color = Color(3, 252, 169, 25) - context(automated: Automated, safeContext: SafeContext) - override fun resolve() = neededSelection.transfer(MainHandContainer) + context(task: Task<*>, _: AutomatedSafeContext) + override fun resolve() { + neededSelection.transferByTask(HotbarContainer)?.execute(task) + } - override fun ShapeBuilder.buildRenderer() { - val center = pos.toCenterPos() - val box = Box( - center.x - 0.1, center.y - 0.1, center.z - 0.1, - center.x + 0.1, center.y + 0.1, center.z + 0.1 - ) - box(box, color, color) + override fun render(esp: TransientRegionESP) { + esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) { + val center = pos.toCenterPos() + val box = Box( + center.x - 0.1, center.y - 0.1, center.z - 0.1, + center.x + 0.1, center.y + 0.1, center.z + 0.1 + ) + box(box, color, color) + } } } @@ -129,13 +135,15 @@ sealed class GenericResult : BuildResult() { override val goal = GoalNear(pos, 3) - override fun ShapeBuilder.buildRenderer() { - val center = pos.toCenterPos() - val box = Box( - center.x - 0.1, center.y - 0.1, center.z - 0.1, - center.x + 0.1, center.y + 0.1, center.z + 0.1 - ) - box(box, color, color) + override fun render(esp: TransientRegionESP) { + esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) { + val center = pos.toCenterPos() + val box = Box( + center.x - 0.1, center.y - 0.1, center.z - 0.1, + center.x + 0.1, center.y + 0.1, center.z + 0.1 + ) + box(box, color, color) + } } override fun compareResult(other: ComparableResult): Int { diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/InteractResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/InteractResult.kt index 653fa75ff..b4537bcec 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/InteractResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/InteractResult.kt @@ -19,7 +19,7 @@ package com.lambda.interaction.construction.simulation.result.results import baritone.api.pathing.goals.GoalBlock import baritone.api.pathing.goals.GoalInverted -import com.lambda.graphics.renderer.esp.ShapeBuilder +import com.lambda.graphics.mc.TransientRegionESP import com.lambda.interaction.construction.simulation.context.InteractContext import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.result.Contextual @@ -29,8 +29,8 @@ import com.lambda.interaction.construction.simulation.result.Navigable import com.lambda.interaction.construction.simulation.result.Rank import net.minecraft.block.BlockState import net.minecraft.entity.Entity +import net.minecraft.item.Item import net.minecraft.item.ItemPlacementContext -import net.minecraft.item.ItemStack import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Box import net.minecraft.util.math.Direction @@ -57,8 +57,8 @@ sealed class InteractResult : BuildResult() { ) : Contextual, Drawable, InteractResult() { override val rank = Rank.PlaceSuccess - override fun ShapeBuilder.buildRenderer() { - with(context) { buildRenderer() } + override fun render(esp: TransientRegionESP) { + context.render(esp) } } @@ -82,14 +82,16 @@ sealed class InteractResult : BuildResult() { override val rank = Rank.PlaceNoIntegrity private val color = Color(252, 3, 3, 100) - override fun ShapeBuilder.buildRenderer() { - val box = with(simulated.hitPos) { - Box( - x - 0.05, y - 0.05, z - 0.05, - x + 0.05, y + 0.05, z + 0.05, - ).offset(simulated.side.doubleVector.multiply(0.05)) + override fun render(esp: TransientRegionESP) { + esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) { + val box = with(simulated.hitPos) { + Box( + x - 0.05, y - 0.05, z - 0.05, + x + 0.05, y + 0.05, z + 0.05, + ).offset(simulated.side.doubleVector.multiply(0.05)) + } + box(box, color, color) } - box(box, color, color) } } @@ -119,14 +121,16 @@ sealed class InteractResult : BuildResult() { override val rank = Rank.PlaceBlockedByEntity private val color = Color(252, 3, 3, 100) - override fun ShapeBuilder.buildRenderer() { - val box = with(hitPos) { - Box( - x - 0.05, y - 0.05, z - 0.05, - x + 0.05, y + 0.05, z + 0.05, - ).offset(side.doubleVector.multiply(0.05)) + override fun render(esp: TransientRegionESP) { + esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) { + val box = with(hitPos) { + Box( + x - 0.05, y - 0.05, z - 0.05, + x + 0.05, y + 0.05, z + 0.05, + ).offset(side.doubleVector.multiply(0.05)) + } + box(box, color, color) } - box(box, color, color) } } @@ -163,7 +167,7 @@ sealed class InteractResult : BuildResult() { */ data class BlockFeatureDisabled( override val pos: BlockPos, - val itemStack: ItemStack, + val item: Item, ) : InteractResult() { override val rank = Rank.PlaceBlockFeatureDisabled } diff --git a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/PreSimResult.kt b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/PreSimResult.kt index c92bda7b1..baa2a2513 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/PreSimResult.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/simulation/result/results/PreSimResult.kt @@ -18,7 +18,8 @@ package com.lambda.interaction.construction.simulation.result.results import baritone.api.pathing.goals.GoalBlock -import com.lambda.graphics.renderer.esp.ShapeBuilder +import com.lambda.graphics.esp.ShapeScope +import com.lambda.graphics.mc.TransientRegionESP import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.result.ComparableResult import com.lambda.interaction.construction.simulation.result.Drawable @@ -55,8 +56,10 @@ sealed class PreSimResult : BuildResult() { override val goal = GoalBlock(pos) - override fun ShapeBuilder.buildRenderer() { - box(pos, color, color) + override fun render(esp: TransientRegionESP) { + esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) { + box(pos, color, color) + } } override fun compareResult(other: ComparableResult) = @@ -77,8 +80,10 @@ sealed class PreSimResult : BuildResult() { override val rank = Rank.BreakRestricted private val color = Color(255, 0, 0, 100) - override fun ShapeBuilder.buildRenderer() { - box(pos, color, color) + override fun render(esp: TransientRegionESP) { + esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) { + box(pos, color, color) + } } } @@ -95,8 +100,10 @@ sealed class PreSimResult : BuildResult() { override val rank get() = Rank.BreakNoPermission private val color = Color(255, 0, 0, 100) - override fun ShapeBuilder.buildRenderer() { - box(pos, color, color) + override fun render(esp: TransientRegionESP) { + esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) { + box(pos, color, color) + } } } @@ -111,8 +118,10 @@ sealed class PreSimResult : BuildResult() { override val rank = Rank.OutOfWorld private val color = Color(3, 148, 252, 100) - override fun ShapeBuilder.buildRenderer() { - box(pos, color, color) + override fun render(esp: TransientRegionESP) { + esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) { + box(pos, color, color) + } } } @@ -129,8 +138,10 @@ sealed class PreSimResult : BuildResult() { override val rank = Rank.Unbreakable private val color = Color(11, 11, 11, 100) - override fun ShapeBuilder.buildRenderer() { - box(pos, color, color) + override fun render(esp: TransientRegionESP) { + esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) { + box(pos, color, color) + } } } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt index aa7eaacaf..3bbfcee78 100644 --- a/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt +++ b/src/main/kotlin/com/lambda/interaction/construction/verify/TargetState.kt @@ -34,7 +34,7 @@ import net.minecraft.state.property.Property import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction -sealed class TargetState() : StateMatcher { +sealed class TargetState : StateMatcher { data object Empty : TargetState() { override fun toString() = "Empty" @@ -87,7 +87,7 @@ sealed class TargetState() : StateMatcher { override fun getStack(pos: BlockPos) = with(automatedSafeContext) { findDisposable()?.stacks?.firstOrNull { - it.item.block in inventoryConfig.disposables && it.item.block !in replace + it.item in inventoryConfig.disposables && it.item.block !in replace } ?: ItemStack(Items.NETHERRACK) } @@ -114,7 +114,7 @@ sealed class TargetState() : StateMatcher { override fun getStack(pos: BlockPos) = with(automatedSafeContext) { findDisposable()?.stacks?.firstOrNull { - it.item.block in inventoryConfig.disposables + it.item in inventoryConfig.disposables } ?: ItemStack(Items.NETHERRACK) } @@ -165,8 +165,7 @@ sealed class TargetState() : StateMatcher { } data class Stack(val itemStack: ItemStack) : TargetState() { - private val startStack: ItemStack = itemStack.copy() - override fun toString() = "Stack of ${startStack.item.name.string.capitalize()}" + override fun toString() = "Stack of ${itemStack.item.name.string.capitalize()}" private val block = itemStack.item.block diff --git a/src/main/kotlin/com/lambda/interaction/managers/ActionInfo.kt b/src/main/kotlin/com/lambda/interaction/managers/ActionInfo.kt index 2d54656ad..0090b29d4 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/ActionInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/ActionInfo.kt @@ -24,6 +24,6 @@ import com.lambda.interaction.construction.simulation.context.BuildContext * must persist longer than the request. */ interface ActionInfo { - val context: BuildContext - val pendingInteractionsList: MutableCollection + val context: BuildContext + val pendingInteractionsList: MutableCollection } diff --git a/src/main/kotlin/com/lambda/interaction/managers/DebugLogger.kt b/src/main/kotlin/com/lambda/interaction/managers/DebugLogger.kt deleted file mode 100644 index 0e6f85b67..000000000 --- a/src/main/kotlin/com/lambda/interaction/managers/DebugLogger.kt +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright 2025 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.interaction.managers - -import com.lambda.gui.components.ClickGuiLayout -import com.lambda.gui.dsl.ImGuiBuilder -import com.lambda.interaction.managers.LogContext.Companion.buildLogContext -import com.lambda.module.hud.ManagerDebugLoggers.autoScroll -import com.lambda.module.hud.ManagerDebugLoggers.maxLogEntries -import com.lambda.module.hud.ManagerDebugLoggers.showDebug -import com.lambda.module.hud.ManagerDebugLoggers.showError -import com.lambda.module.hud.ManagerDebugLoggers.showSuccess -import com.lambda.module.hud.ManagerDebugLoggers.showSystem -import com.lambda.module.hud.ManagerDebugLoggers.showWarning -import com.lambda.module.hud.ManagerDebugLoggers.wrapText -import com.lambda.util.math.a -import imgui.ImGui -import imgui.flag.ImGuiCol -import imgui.flag.ImGuiWindowFlags -import java.awt.Color -import java.util.* - -/** - * A simple logger that can be used to display information about what is happening within the managers. - */ -class DebugLogger( - val name: String -) { - val logs = LinkedList() - - private fun log(message: String, logColor: LogType, extraContext: List) { - if (logs.size + 1 > maxLogEntries) { - logs.removeFirst() - } - logs.add(LogEntry(message, logColor, extraContext.filterNotNull())) - } - - fun debug(message: String) = log(message, LogType.Debug, emptyList()) - fun debug(message: String, vararg extraContext: String?) = log(message, LogType.Debug, extraContext.toList()) - fun debug(message: String, vararg extraContext: LogContext?) = - log(message, LogType.Debug, extraContext.filterNotNull().map { buildLogContext(builder = it.getLogContextBuilder()) }) - fun success(message: String) = log(message, LogType.Success, emptyList()) - fun success(message: String, vararg extraContext: String?) = log(message, LogType.Success, extraContext.toList()) - fun success(message: String, vararg extraContext: LogContext?) = - log(message, LogType.Success, extraContext.filterNotNull().map { buildLogContext(builder = it.getLogContextBuilder()) }) - fun warning(message: String) = log(message, LogType.Warning, emptyList()) - fun warning(message: String, vararg extraContext: String?) = log(message, LogType.Warning, extraContext.toList()) - fun warning(message: String, vararg extraContext: LogContext?) = - log(message, LogType.Warning, extraContext.filterNotNull().map { buildLogContext(builder = it.getLogContextBuilder()) }) - fun error(message: String) = log(message, LogType.Error, emptyList()) - fun error(message: String, vararg extraContext: String?) = log(message, LogType.Error, extraContext.toList()) - fun error(message: String, vararg extraContext: LogContext?) = - log(message, LogType.Error, extraContext.filterNotNull().map { buildLogContext(builder = it.getLogContextBuilder()) }) - fun system(message: String) = log(message, LogType.System, emptyList()) - fun system(message: String, vararg extraContext: String?) = log(message, LogType.System, extraContext.toList()) - fun system(message: String, vararg extraContext: LogContext?) = - log(message, LogType.System, extraContext.filterNotNull().map { buildLogContext(builder = it.getLogContextBuilder()) }) - - fun ImGuiBuilder.buildLayout() { - ImGui.setNextWindowSizeConstraints(300f, 400f, windowViewport.workSizeX, windowViewport.workSizeY) - var flags = if (autoScroll) ImGuiWindowFlags.NoScrollbar or ImGuiWindowFlags.NoScrollWithMouse else 0 - flags = flags or ImGuiWindowFlags.NoBackground - if (!ClickGuiLayout.open) flags = flags or ImGuiWindowFlags.NoInputs - child("Log Content", border = false, windowFlags = flags) { - if (wrapText) ImGui.pushTextWrapPos() - - logs.forEach { logEntry -> - if (shouldDisplay(logEntry)) { - val type = logEntry.type - val (logTypeStr, color) = when (type) { - LogType.Debug -> Pair("[DEBUG]", type.color) - LogType.Success -> Pair("[SUCCESS]", type.color) - LogType.Warning -> Pair("[WARNING]", type.color) - LogType.Error -> Pair("[ERROR]", type.color) - LogType.System -> Pair("[SYSTEM]", type.color) - } - - val floats = floatArrayOf(0f, 0f, 0f) - val (r, g, b) = color.getColorComponents(floats) - ImGui.pushStyleColor(ImGuiCol.Text, r, g, b, color.a.toFloat()) - if (logEntry.type == LogType.System) { - text("$logTypeStr ${logEntry.message}") - } else { - treeNode("$logTypeStr ${logEntry.message}", logEntry.uuid) { - logEntry.extraContext - .filterNotNull() - .forEach { - text(it) - } - } - } - ImGui.popStyleColor() - } - } - - if (wrapText) ImGui.popTextWrapPos() - - if (autoScroll) { - ImGui.setScrollHereY(1f) - } - } - } - - fun shouldDisplay(logEntry: LogEntry) = - when (logEntry.type) { - LogType.Debug -> showDebug - LogType.Success -> showSuccess - LogType.Warning -> showWarning - LogType.Error -> showError - LogType.System -> showSystem - } - - fun clear() = logs.clear() - - class LogEntry( - val message: String, - val type: LogType, - val extraContext: Collection - ) { - val uuid = UUID.randomUUID().toString() - } - - enum class LogType(val color: Color) { - Debug(Color(1.0f, 1.0f, 1.0f, 1f)), - Success(Color(0.28f, 1.0f, 0.28f, 1f)), - Warning(Color(1.0f, 1.0f, 0.28f, 1f)), - Error(Color(1.0f, 0.28f, 0.28f, 1f)), - System(Color(0.28f, 0.28f, 1.0f, 1f)) - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/managers/LogContext.kt b/src/main/kotlin/com/lambda/interaction/managers/LogContext.kt deleted file mode 100644 index a2df2d272..000000000 --- a/src/main/kotlin/com/lambda/interaction/managers/LogContext.kt +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2025 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.interaction.managers - -import net.minecraft.util.hit.BlockHitResult -import net.minecraft.util.math.BlockPos - -interface LogContext { - fun getLogContextBuilder(): LogContextBuilder.() -> Unit - - companion object { - @DslMarker - private annotation class LogContextDsl - - fun BlockPos.getLogContextBuilder(): LogContextBuilder.() -> Unit { - val pos = if (this is BlockPos.Mutable) toImmutable() else this - return { value("Block Pos", pos.toShortString()) } - } - - fun BlockHitResult.getLogContextBuilder(): LogContextBuilder.() -> Unit = { - group("Block Hit Result") { - value("Side", side) - text(blockPos.getLogContextBuilder()) - value("Pos", pos) - } - } - - @LogContextDsl - fun buildLogContext(tabMin: Int = 0, builder: LogContextBuilder.() -> Unit): String = - LogContextBuilder(tabMin).apply(builder).build() - - @LogContextDsl - private fun LogContextBuilder.build() = logContext - - @LogContextDsl - class LogContextBuilder(val tabMin: Int = 0) { - var logContext = "" - - private var tabs = tabMin - - @LogContextDsl - fun sameLine() { - val length = logContext.length - val first = logContext[length - 2] - val second = logContext[length - 1] - if (first != '\\' || second != 'n') throw IllegalStateException("String does not end in a new line") - logContext = logContext.removeRange(length - 2, length - 1) - } - - @LogContextDsl - fun text(text: String) { - repeat(tabs) { - logContext += "----" - } - logContext += "$text\n" - } - - @LogContextDsl - fun text(builder: LogContextBuilder.() -> Unit) { - logContext += LogContextBuilder(tabs).apply(builder).build() - } - - @LogContextDsl - fun value(name: String, value: Any) { - text("$name: $value") - } - - @LogContextDsl - fun value(name: String, value: String) { - text("$name: $value") - } - - @LogContextDsl - fun group(name: String, builder: LogContextBuilder.() -> Unit) { - text("$name {") - logContext += LogContextBuilder(tabs + 1).apply(builder).build() - text("}") - } - - @LogContextDsl - fun pushTab() { - tabs++ - } - - @LogContextDsl - fun popTab() { - tabs-- - if (tabs < tabMin) throw IllegalStateException("Cannot reduce tabs beneath the minimum tab count") - } - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/managers/Manager.kt b/src/main/kotlin/com/lambda/interaction/managers/Manager.kt index 972572fda..2649ee769 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/Manager.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/Manager.kt @@ -34,95 +34,95 @@ import kotlin.reflect.KClass * next opening if closed */ abstract class Manager( - val stagePriority: Int, - vararg val blacklistedStages: TickEvent, - private val onOpen: (SafeContext.() -> Unit)? = null, - private val onClose: (SafeContext.() -> Unit)? = null + val stagePriority: Int, + vararg val blacklistedStages: TickEvent, + private val onOpen: (SafeContext.() -> Unit)? = null, + private val onClose: (SafeContext.() -> Unit)? = null ) : Loadable { val openStages: List = ALL_STAGES.filter { it !in blacklistedStages } - /** - * Represents if the handler is accepting requests at any given time - */ - private var acceptingRequests = false + /** + * Represents if the handler is accepting requests at any given time + */ + private var acceptingRequests = false - /** - * Represents the sequence stage the current tick is at - */ - var tickStage: Event? = null; private set + /** + * Represents the sequence stage the current tick is at + */ + var tickStage: Event? = null; private set - /** - * If a request is made while the handler isn't accepting requests, it is placed into [queuedRequest] and run - * at the start of the next open request timeframe - */ - var queuedRequest: R? = null; protected set + /** + * If a request is made while the handler isn't accepting requests, it is placed into [queuedRequest] and run + * at the start of the next open request timeframe + */ + var queuedRequest: R? = null; protected set - /** - * Represents if the handler performed any actions within this tick - */ - var activeThisTick = false; protected set + /** + * Represents if the handler performed any actions within this tick + */ + var activeThisTick = false; protected set - override fun load(): String { - openStages.forEach { openRequestsFor(it::class, it) } + override fun load(): String { + openStages.forEach { openRequestsFor(it::class, it) } - listen(Int.MIN_VALUE) { - activeThisTick = false - queuedRequest = null - } + listen(Int.MIN_VALUE) { + activeThisTick = false + queuedRequest = null + } - return super.load() - } + return super.load() + } - /** - * opens the handler for requests for the duration of the given event - */ - private inline fun openRequestsFor(instance: KClass, stage: T) { - listen(instance, priority = (Int.MAX_VALUE - 1) - (accumulatedManagerPriority - stagePriority)) { - tickStage = stage - queuedRequest?.let { request -> - if (tickStage !in request.tickStageMask) return@let - request.runSafeAutomated { handleRequest(request) } - request.fresh = false - queuedRequest = null - } - acceptingRequests = true - onOpen?.invoke(this) - } + /** + * opens the handler for requests for the duration of the given event + */ + private inline fun openRequestsFor(instance: KClass, stage: T) { + listen(instance, priority = (Int.MAX_VALUE - 1) - (accumulatedManagerPriority - stagePriority)) { + tickStage = stage + queuedRequest?.let { request -> + if (tickStage !in request.tickStageMask) return@let + request.runSafeAutomated { handleRequest(request) } + request.fresh = false + queuedRequest = null + } + acceptingRequests = true + onOpen?.invoke(this) + } - listen(instance, priority = (Int.MIN_VALUE + 1) + stagePriority) { - onClose?.invoke(this) - acceptingRequests = false - } - } + listen(instance, priority = (Int.MIN_VALUE + 1) + stagePriority) { + onClose?.invoke(this) + acceptingRequests = false + } + } - /** - * Registers a new request - * - * @param request The request to register. - * @param queueIfMismatchedStage queues the request for the next time the handlers accepting requests - * @return The registered request. - */ - fun request(request: R, queueIfMismatchedStage: Boolean = true): R { - val canOverrideQueued = queuedRequest?.let { it as Automated === request as Automated } != false - if (!canOverrideQueued) return request - if ((!acceptingRequests || tickStage !in request.tickStageMask)) { - if (!queueIfMismatchedStage || request.nowOrNothing) return request - val currentStageIndex = ALL_STAGES.indexOf(tickStage) - if (openStages.none { ALL_STAGES.indexOf(it) > currentStageIndex && it in request.tickStageMask }) - return request - queuedRequest = request - return request - } + /** + * Registers a new request + * + * @param request The request to register. + * @param queueIfMismatchedStage queues the request for the next time the handlers accepting requests + * @return The registered request. + */ + fun request(request: R, queueIfMismatchedStage: Boolean = true): R { + val canOverrideQueued = queuedRequest?.let { it as Automated === request as Automated } != false + if (!canOverrideQueued) return request + if ((!acceptingRequests || tickStage !in request.tickStageMask)) { + if (!queueIfMismatchedStage || request.nowOrNothing) return request + val currentStageIndex = ALL_STAGES.indexOf(tickStage) + if (openStages.none { ALL_STAGES.indexOf(it) > currentStageIndex && it in request.tickStageMask }) + return request + queuedRequest = request + return request + } - request.runSafeAutomated { - handleRequest(request) - request.fresh = false - } - return request - } + request.runSafeAutomated { + handleRequest(request) + request.fresh = false + } + return request + } - /** - * Handles a request - */ - abstract fun AutomatedSafeContext.handleRequest(request: R) + /** + * Handles a request + */ + abstract fun AutomatedSafeContext.handleRequest(request: R) } diff --git a/src/main/kotlin/com/lambda/interaction/managers/ManagerUtils.kt b/src/main/kotlin/com/lambda/interaction/managers/ManagerUtils.kt index ef21c2a4b..8d59b7819 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/ManagerUtils.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/ManagerUtils.kt @@ -17,23 +17,14 @@ package com.lambda.interaction.managers -import com.lambda.event.Event import com.lambda.util.reflections.getInstances import net.minecraft.util.math.BlockPos object ManagerUtils { - val managers = getInstances>() - val accumulatedManagerPriority = managers.map { it.stagePriority }.reduce { acc, priority -> acc + priority } - val positionBlockingManagers = getInstances() + val managers = getInstances>() + val accumulatedManagerPriority = managers.map { it.stagePriority }.reduce { acc, priority -> acc + priority } + val positionBlockingManagers = getInstances() - fun DebugLogger.newTick() = - system("------------- New Tick -------------") - - fun DebugLogger.newStage(tickStage: Event?) = - system("Tick stage ${tickStage?.run { this.toLogContext() }}") - - fun Event.toLogContext() = this::class.qualifiedName?.substringAfter("com.lambda.event.events.") - - fun isPosBlocked(pos: BlockPos) = - positionBlockingManagers.any { pos in it.blockedPositions } + fun isPosBlocked(pos: BlockPos) = + positionBlockingManagers.any { pos in it.blockedPositions } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/managers/PositionBlocking.kt b/src/main/kotlin/com/lambda/interaction/managers/PositionBlocking.kt index 3f56d978e..2d776c347 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/PositionBlocking.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/PositionBlocking.kt @@ -25,5 +25,5 @@ import net.minecraft.util.math.BlockPos * Reasons a position could be blocked include pending interactions and or active interactions. */ interface PositionBlocking { - val blockedPositions: List + val blockedPositions: List } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/managers/PostActionHandler.kt b/src/main/kotlin/com/lambda/interaction/managers/PostActionHandler.kt index 2d1a73858..a20f4d1c3 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/PostActionHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/PostActionHandler.kt @@ -29,30 +29,30 @@ import com.lambda.util.collections.LimitedDecayQueue * A simple interface for handlers of actions that need some sort of server response after being executed. */ abstract class PostActionHandler { - abstract val pendingActions: LimitedDecayQueue - - init { - listen(priority = Int.MAX_VALUE) { - pendingActions.cleanUp() - } - - listenUnsafe(priority = Int.MIN_VALUE) { - pendingActions.clear() - } - } - - fun T.startPending() { - pendingActions.add(this) - pendingInteractionsList.add(context) - } - - fun T.stopPending() { - pendingActions.remove(this) - pendingInteractionsList.remove(context) - } - - fun Automated.setPendingConfigs() { - BrokenBlockHandler.pendingActions.setSizeLimit(buildConfig.maxPendingActions) - BrokenBlockHandler.pendingActions.setDecayTime(buildConfig.actionTimeout * 50L) - } + abstract val pendingActions: LimitedDecayQueue + + init { + listen(priority = Int.MAX_VALUE) { + pendingActions.cleanUp() + } + + listenUnsafe(priority = Int.MIN_VALUE) { + pendingActions.clear() + } + } + + fun T.startPending() { + pendingActions.add(this) + pendingInteractionsList.add(context) + } + + fun T.stopPending() { + pendingActions.remove(this) + pendingInteractionsList.remove(context) + } + + fun Automated.setPendingConfigs() { + BrokenBlockHandler.pendingActions.setSizeLimit(buildConfig.maxPendingActions) + BrokenBlockHandler.pendingActions.setDecayTime(buildConfig.actionTimeout * 50L) + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/managers/Request.kt b/src/main/kotlin/com/lambda/interaction/managers/Request.kt index 731ba6ac0..f1d378723 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/Request.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/Request.kt @@ -28,18 +28,13 @@ import com.lambda.event.events.TickEvent * @property done If this request has been completed. */ abstract class Request : Automated { - abstract val requestId: Int - var fresh = true + abstract val requestId: Int + var fresh = true - abstract val tickStageMask: Collection - abstract val nowOrNothing: Boolean + abstract val tickStageMask: Collection + abstract val nowOrNothing: Boolean - abstract val done: Boolean + abstract val done: Boolean - abstract fun submit(queueIfMismatchedStage: Boolean = true): Request - - companion object { - fun submit(request: Request, queueIfClosed: Boolean = true) = - request.submit(queueIfClosed) - } + abstract fun submit(queueIfMismatchedStage: Boolean = true): Request } diff --git a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakConfig.kt b/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakConfig.kt index f6d7cc492..2cba3128b 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakConfig.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakConfig.kt @@ -26,117 +26,117 @@ import net.minecraft.block.Block import java.awt.Color interface BreakConfig : ActionConfig, ISettingGroup { - val breakMode: BreakMode - val rebreak: Boolean + val breakMode: BreakMode + val rebreak: Boolean - val doubleBreak: Boolean - val unsafeCancels: Boolean + val doubleBreak: Boolean + val unsafeCancels: Boolean - val breakThreshold: Float - val fudgeFactor: Int - val serverSwapTicks: Int - //ToDo: Needs a more advanced player simulation implementation to predict the next ticks onGround / submerged status + val breakThreshold: Float + val fudgeFactor: Int + val serverSwapTicks: Int + //ToDo: Needs a more advanced player simulation implementation to predict the next ticks onGround / submerged status // abstract val desyncFix: Boolean - val breakDelay: Int - - val swapMode: SwapMode - - val swing: SwingMode - val swingType: BuildConfig.SwingType - - val rotate: Boolean - - val breakConfirmation: BreakConfirmationMode - val breaksPerTick: Int - - val avoidLiquids: Boolean - val avoidSupporting: Boolean - val ignoredBlocks: Collection - - val efficientOnly: Boolean - val suitableToolsOnly: Boolean - val forceSilkTouch: Boolean - val forceFortunePickaxe: Boolean - val minFortuneLevel: Int - - val useWoodenTools: Boolean - val useStoneTools: Boolean - val useIronTools: Boolean - val useDiamondTools: Boolean - val useGoldTools: Boolean - val useNetheriteTools: Boolean - - val sounds: Boolean - val particles: Boolean - val breakingTexture: Boolean - - val renders: Boolean - val fill: Boolean - val outline: Boolean - val outlineWidth: Int - val animation: AnimationMode - - val dynamicFillColor: Boolean - val staticFillColor: Color - val startFillColor: Color - val endFillColor: Color - - val dynamicOutlineColor: Boolean - val staticOutlineColor: Color - val startOutlineColor: Color - val endOutlineColor: Color - - enum class BreakMode( - override val displayName: String, - override val description: String - ) : NamedEnum, Describable { - Vanilla("Vanilla", "Uses vanilla breaking"), - Packet("Packet", "Breaks blocks using only using packets") - } - - enum class SwapMode( - override val displayName: String, - override val description: String - ) : NamedEnum, Describable { - None("None", "Never auto-swap tools. Keeps whatever you’re holding"), - Start("Start", "Auto-swap to the best tool right when the break starts. No further swaps during the same break"), - End("End", "Stay on your current tool at first, then auto-swap to the best tool right before the block finishes breaking to speed up the final stretch"), - StartAndEnd("Start and End", "Auto-swap to the best tool at the start, and again right before the block finishes breaking if it would be faster"), - Constant("Constant", "Always keep the best tool selected for the entire break. Swaps as needed to maintain optimal speed"); - - fun isEnabled() = this != None - } - - enum class SwingMode( - override val displayName: String, - override val description: String - ) : NamedEnum, Describable { - Constant("Constant", "Swings the hand every tick"), - StartAndEnd("Start and End", "Swings the hand at the start and end of breaking"), - Start("Start", "Swings the hand at the start of breaking"), - End("End", "Swings the hand at the end of breaking"), - None("None", "Does not swing the hand at all"); - - fun isEnabled() = this != None - } - - enum class BreakConfirmationMode( - override val displayName: String, - override val description: String - ) : NamedEnum, Describable { - None("No confirmation", "Breaks immediately without waiting for the server. Lowest latency, but can briefly show break effects even if the server later disagrees."), - BreakThenAwait("Break now, confirm later", "Shows the break effects right away (particles/sounds) and then waits for the server to confirm. Feels instant while keeping results consistent."), - AwaitThenBreak("Confirm first, then break", "Waits for the server response before showing break effects. Most accurate and safest, but adds a short delay."); - } - - enum class AnimationMode( - override val displayName: String, - override val description: String - ) : NamedEnum, Describable { - None("None", "Does not render any breaking animation"), - Out("Out", "Renders a growing animation"), - In("In", "Renders a shrinking animation"), - OutIn("Out In", "Renders a growing and shrinking animation"), - InOut("In Out", "Renders a shrinking and growing animation") - } + val breakDelay: Int + + val swapMode: SwapMode + + val swing: SwingMode + val swingType: BuildConfig.SwingType + + val rotate: Boolean + + val breakConfirmation: BreakConfirmationMode + val breaksPerTick: Int + + val avoidLiquids: Boolean + val avoidSupporting: Boolean + val ignoredBlocks: Collection + + val efficientOnly: Boolean + val suitableToolsOnly: Boolean + val forceSilkTouch: Boolean + val forceFortunePickaxe: Boolean + val minFortuneLevel: Int + + val useWoodenTools: Boolean + val useStoneTools: Boolean + val useIronTools: Boolean + val useDiamondTools: Boolean + val useGoldTools: Boolean + val useNetheriteTools: Boolean + + val sounds: Boolean + val particles: Boolean + val breakingTexture: Boolean + + val renders: Boolean + val fill: Boolean + val outline: Boolean + val outlineWidth: Int + val animation: AnimationMode + + val dynamicFillColor: Boolean + val staticFillColor: Color + val startFillColor: Color + val endFillColor: Color + + val dynamicOutlineColor: Boolean + val staticOutlineColor: Color + val startOutlineColor: Color + val endOutlineColor: Color + + enum class BreakMode( + override val displayName: String, + override val description: String + ) : NamedEnum, Describable { + Vanilla("Vanilla", "Uses vanilla breaking"), + Packet("Packet", "Breaks blocks using only using packets") + } + + enum class SwapMode( + override val displayName: String, + override val description: String + ) : NamedEnum, Describable { + None("None", "Never auto-swap tools. Keeps whatever you’re holding"), + Start("Start", "Auto-swap to the best tool right when the break starts. No further swaps during the same break"), + End("End", "Stay on your current tool at first, then auto-swap to the best tool right before the block finishes breaking to speed up the final stretch"), + StartAndEnd("Start and End", "Auto-swap to the best tool at the start, and again right before the block finishes breaking if it would be faster"), + Constant("Constant", "Always keep the best tool selected for the entire break. Swaps as needed to maintain optimal speed"); + + fun isEnabled() = this != None + } + + enum class SwingMode( + override val displayName: String, + override val description: String + ) : NamedEnum, Describable { + Constant("Constant", "Swings the hand every tick"), + StartAndEnd("Start and End", "Swings the hand at the start and end of breaking"), + Start("Start", "Swings the hand at the start of breaking"), + End("End", "Swings the hand at the end of breaking"), + None("None", "Does not swing the hand at all"); + + fun isEnabled() = this != None + } + + enum class BreakConfirmationMode( + override val displayName: String, + override val description: String + ) : NamedEnum, Describable { + None("No confirmation", "Breaks immediately without waiting for the server. Lowest latency, but can briefly show break effects even if the server later disagrees."), + BreakThenAwait("Break now, confirm later", "Shows the break effects right away (particles/sounds) and then waits for the server to confirm. Feels instant while keeping results consistent."), + AwaitThenBreak("Confirm first, then break", "Waits for the server response before showing break effects. Most accurate and safest, but adds a short delay."); + } + + enum class AnimationMode( + override val displayName: String, + override val description: String + ) : NamedEnum, Describable { + None("None", "Does not render any breaking animation"), + Out("Out", "Renders a growing animation"), + In("In", "Renders a shrinking animation"), + OutIn("Out In", "Renders a growing and shrinking animation"), + InOut("In Out", "Renders a shrinking and growing animation") + } } diff --git a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakInfo.kt b/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakInfo.kt index 0c216040b..e7598d7af 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakInfo.kt @@ -20,8 +20,6 @@ package com.lambda.interaction.managers.breaking import com.lambda.context.SafeContext import com.lambda.interaction.construction.simulation.context.BreakContext import com.lambda.interaction.managers.ActionInfo -import com.lambda.interaction.managers.LogContext -import com.lambda.interaction.managers.LogContext.Companion.LogContextBuilder import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.Primary import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.Rebreak import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.RedundantSecondary @@ -41,150 +39,128 @@ import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket.Action * A data class that holds all the information required to process and continue a break. */ data class BreakInfo( - override var context: BreakContext, - var type: BreakType, - var request: BreakRequest -) : ActionInfo, LogContext { - // Delegates - val breakConfig get() = request.breakConfig - override val pendingInteractionsList get() = request.pendingInteractions - - // Pre Processing - var shouldProgress = false - var rebreakPotential = RebreakHandler.RebreakPotential.None - var swapInfo = SwapInfo.EMPTY - var swapStack: ItemStack = ItemStack.EMPTY - - // BreakInfo Specific - var updatedThisTick = true - var updatedPreProcessingThisTick = false - var progressedThisTick = false - - // Processing - var breaking = false - var abandoned = false - var breakingTicks = 0 - var soundsCooldown = 0f - var vanillaInstantBreakable = false - val rebreakable get() = !vanillaInstantBreakable && type == Primary - - enum class BreakType( - override val displayName: String, - override val description: String - ) : NamedEnum, Describable { - Primary("Primary", "The main block you’re breaking right now."), - Secondary("Secondary", "A second block broken at the same time (when double‑break is enabled)."), - RedundantSecondary("Redundant Secondary", "A previously started secondary break that’s now ignored/monitored only (no new actions)."), - Rebreak("Rebreak", "A previously broken block which new breaks in the same position can compound progression on. Often rebreaking instantly."); - } - - // Post Processing - var broken = false; private set - private var item: ItemEntity? = null - val callbacksCompleted - get() = broken && (request.onItemDrop == null || item != null) - - context(safeContext: SafeContext) - fun internalOnBreak() { - if (type != Rebreak) broken = true - item?.let { item -> - request.onItemDrop?.invoke(safeContext, item) - } - } - - context(safeContext: SafeContext) - fun internalOnItemDrop(item: ItemEntity) { - if (type != Rebreak) this.item = item - if (broken || type == Rebreak) { - request.onItemDrop?.invoke(safeContext, item) - } - } - - fun updateInfo(context: BreakContext, request: BreakRequest? = null) { - updatedThisTick = true - this.context = context - request?.let { this.request = it } - if (type == RedundantSecondary) type = Secondary - } - - fun resetCallbacks() { - broken = false - item = null - } - - fun tickChecks() { - updatedThisTick = false - updatedPreProcessingThisTick = false - progressedThisTick = false - } - - context(safeContext: SafeContext) - fun setBreakingTextureStage( - player: ClientPlayerEntity, - world: ClientWorld, - stage: Int = getBreakTextureProgress() - ) = world.setBlockBreakingInfo(player.id, context.blockPos, stage) - - context(safeContext: SafeContext) - private fun getBreakTextureProgress(): Int = with(safeContext) { - val item = - if (breakConfig.swapMode.isEnabled() && breakConfig.swapMode != BreakConfig.SwapMode.Start) swapStack - else player.mainHandStack - val breakDelta = request.runSafeAutomated { context.cachedState.calcBreakDelta(context.blockPos, item) } - val progress = (breakDelta * breakingTicks) / (getBreakThreshold() + (breakDelta * breakConfig.fudgeFactor)) - return if (progress > 0.0f) (progress * 10.0f).toInt().coerceAtMost(9) else -1 - } - - fun getBreakThreshold() = - when (type) { - Primary, - Rebreak-> breakConfig.breakThreshold - else -> 1.0f - } - - context(_: SafeContext) - fun startBreakPacket() = breakPacket(Action.START_DESTROY_BLOCK) - - context(_: SafeContext) - fun stopBreakPacket() = breakPacket(Action.STOP_DESTROY_BLOCK) - - context(_: SafeContext) - fun abortBreakPacket() = breakPacket(Action.ABORT_DESTROY_BLOCK) - - context(safeContext: SafeContext) - private fun breakPacket(action: Action) = - with(safeContext) { - interaction.sendSequencedPacket(world) { sequence: Int -> - PlayerActionC2SPacket( - action, - context.blockPos, - context.hitResult.side, - sequence - ) - } - } - - override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = { - group("Break Info") { - value("Type", type) - text(context.getLogContextBuilder()) - group("Details") { - value("Should Progress", shouldProgress) - value("Rebreak Potential", rebreakPotential) - text(swapInfo.getLogContextBuilder()) - value("Swap Stack", swapStack) - value("Updated This Tick", updatedThisTick) - value("Updated Pre-Processing This Tick", updatedPreProcessingThisTick) - value("Progressed This Tick", progressedThisTick) - value("Breaking", breaking) - value("Abandoned", abandoned) - value("Breaking Ticks", breakingTicks) - value("Sounds Cooldown", soundsCooldown) - value("Vanilla Instant Breakable", vanillaInstantBreakable) - value("Rebreakable", rebreakable) - } - } - } - - override fun toString() = "$type, ${context.cachedState}, ${context.blockPos}" + override var context: BreakContext, + var type: BreakType, + var request: BreakRequest +) : ActionInfo { + // Delegates + val breakConfig get() = request.breakConfig + override val pendingInteractionsList get() = request.pendingInteractions + + // Pre Processing + var shouldProgress = false + var rebreakPotential = RebreakHandler.RebreakPotential.None + var swapInfo = SwapInfo.EMPTY + var swapStack: ItemStack = ItemStack.EMPTY + + // BreakInfo Specific + var updatedThisTick = true + var updatedPreProcessingThisTick = false + var progressedThisTick = false + + // Processing + var breaking = false + var abandoned = false + var breakingTicks = 0 + var soundsCooldown = 0f + var vanillaInstantBreakable = false + val rebreakable get() = !vanillaInstantBreakable && type == Primary + + enum class BreakType( + override val displayName: String, + override val description: String + ) : NamedEnum, Describable { + Primary("Primary", "The main block you’re breaking right now."), + Secondary("Secondary", "A second block broken at the same time (when double‑break is enabled)."), + RedundantSecondary("Redundant Secondary", "A previously started secondary break that’s now ignored/monitored only (no new actions)."), + Rebreak("Rebreak", "A previously broken block which new breaks in the same position can compound progression on. Often rebreaking instantly."); + } + + // Post Processing + var broken = false; private set + private var item: ItemEntity? = null + val callbacksCompleted + get() = broken && (request.onItemDrop == null || item != null) + + context(safeContext: SafeContext) + fun internalOnBreak() { + if (type != Rebreak) broken = true + item?.let { item -> + request.onItemDrop?.invoke(safeContext, item) + } + } + + context(safeContext: SafeContext) + fun internalOnItemDrop(item: ItemEntity) { + if (type != Rebreak) this.item = item + if (broken || type == Rebreak) { + request.onItemDrop?.invoke(safeContext, item) + } + } + + fun updateInfo(context: BreakContext, request: BreakRequest? = null) { + updatedThisTick = true + this.context = context + request?.let { this.request = it } + if (type == RedundantSecondary) type = Secondary + } + + fun resetCallbacks() { + broken = false + item = null + } + + fun tickChecks() { + updatedThisTick = false + updatedPreProcessingThisTick = false + progressedThisTick = false + } + + context(safeContext: SafeContext) + fun setBreakingTextureStage( + player: ClientPlayerEntity, + world: ClientWorld, + stage: Int = getBreakTextureProgress() + ) = world.setBlockBreakingInfo(player.id, context.blockPos, stage) + + context(safeContext: SafeContext) + private fun getBreakTextureProgress(): Int = with(safeContext) { + val item = + if (breakConfig.swapMode.isEnabled() && breakConfig.swapMode != BreakConfig.SwapMode.Start) swapStack + else player.mainHandStack + val breakDelta = request.runSafeAutomated { context.cachedState.calcBreakDelta(context.blockPos, item) } + val progress = (breakDelta * breakingTicks) / (getBreakThreshold() + (breakDelta * breakConfig.fudgeFactor)) + return if (progress > 0.0f) (progress * 10.0f).toInt().coerceAtMost(9) else -1 + } + + fun getBreakThreshold() = + when (type) { + Primary, + Rebreak-> breakConfig.breakThreshold + else -> 1.0f + } + + context(_: SafeContext) + fun startBreakPacket() = breakPacket(Action.START_DESTROY_BLOCK) + + context(_: SafeContext) + fun stopBreakPacket() = breakPacket(Action.STOP_DESTROY_BLOCK) + + context(_: SafeContext) + fun abortBreakPacket() = breakPacket(Action.ABORT_DESTROY_BLOCK) + + context(safeContext: SafeContext) + private fun breakPacket(action: Action) = + with(safeContext) { + interaction.sendSequencedPacket(world) { sequence: Int -> + PlayerActionC2SPacket( + action, + context.blockPos, + context.hitResult.side, + sequence + ) + } + } + + override fun toString() = "$type, ${context.cachedState}, ${context.blockPos}" } diff --git a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakManager.kt b/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakManager.kt index e66f188c3..b985c82e9 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakManager.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakManager.kt @@ -19,8 +19,6 @@ package com.lambda.interaction.managers.breaking import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext -import com.lambda.event.Event -import com.lambda.event.EventFlow.post import com.lambda.event.events.ConnectionEvent import com.lambda.event.events.EntityEvent import com.lambda.event.events.TickEvent @@ -34,11 +32,8 @@ import com.lambda.interaction.construction.simulation.BuildSimulator.simulate import com.lambda.interaction.construction.simulation.context.BreakContext import com.lambda.interaction.construction.simulation.result.results.BreakResult import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.managers.Logger import com.lambda.interaction.managers.Manager import com.lambda.interaction.managers.ManagerUtils.isPosBlocked -import com.lambda.interaction.managers.ManagerUtils.newStage -import com.lambda.interaction.managers.ManagerUtils.newTick import com.lambda.interaction.managers.PositionBlocking import com.lambda.interaction.managers.breaking.BreakConfig.BreakConfirmationMode import com.lambda.interaction.managers.breaking.BreakConfig.BreakMode @@ -76,7 +71,6 @@ import com.lambda.interaction.managers.interacting.InteractManager import com.lambda.interaction.managers.rotating.RotationRequest import com.lambda.interaction.material.StackSelection import com.lambda.interaction.material.StackSelection.Companion.select -import com.lambda.module.hud.ManagerDebugLoggers.breakManagerLogger import com.lambda.threading.runSafeAutomated import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.calcItemBlockBreakingDelta @@ -108,773 +102,743 @@ import kotlin.math.max * style system. */ object BreakManager : Manager( - 0, - onOpen = { - if (activeInfos.isNotEmpty() || breaks.isNotEmpty()) - BreakManager.logger.newStage(BreakManager.tickStage) - processRequest(activeRequest) - simulateAbandoned() - }, - onClose = { checkForCancels() } -), PositionBlocking, Logger { - private val breakInfos = arrayOfNulls(2) - - private val activeInfos - get() = breakInfos - .filterNotNull() - .filter { it.type != RedundantSecondary } - - private var primaryBreak: BreakInfo? - get() = breakInfos[0] - set(value) { breakInfos[0] = value } - - private var secondaryBreak: BreakInfo? - get() = breakInfos[1] - set(value) { breakInfos[1] = value } - - private val abandonedBreak - get() = breakInfos[1].let { secondary -> - if (secondary?.abandoned == true && secondary.type != RedundantSecondary) secondary - else null - } - - val currentStackSelection - get() = activeInfos - .lastOrNull { - it.breakConfig.doubleBreak || it.type == Secondary - }?.context?.itemSelection - ?: StackSelection.EVERYTHING.select() - - private val pendingBreakCount get() = activeInfos.count() + pendingActions.size - override val blockedPositions - get() = activeInfos.map { it.context.blockPos } + pendingActions.map { it.context.blockPos } - - private var activeRequest: BreakRequest? = null - - private var hotbarRequest: HotbarRequest? = null - private val swapped get() = hotbarRequest?.done != false - - private var rotationRequest: RotationRequest? = null - private val rotated get() = rotationRequest?.done != false - - private var breakCooldown = 0 - var breaksThisTick = 0 - private var maxBreaksThisTick = 0 - - private var breaks = mutableListOf() - - var lastPosStarted: BlockPos? = null - set(value) { - if (value != field) RebreakHandler.clearRebreak() - field = value - } - - override val logger = breakManagerLogger - - override fun load(): String { - super.load() - - listen(priority = Int.MAX_VALUE) { - if (activeInfos.isEmpty() && breaks.isEmpty()) return@listen - logger.newTick() - } - - listen(priority = Int.MIN_VALUE) { - breakInfos.forEach { it?.tickChecks() } - if (breakCooldown > 0) { - breakCooldown-- - } - activeRequest = null - breaks = mutableListOf() - breaksThisTick = 0 - } - - listen(priority = Int.MIN_VALUE) { event -> - if (event.pos == RebreakHandler.rebreak?.context?.blockPos) return@listen - - breakInfos - .firstOrNull { it?.context?.blockPos == event.pos } - ?.let { info -> - // if not broken - if (isNotBroken(info.context.cachedState, event.newState)) { - // update the cached state - info.context.cachedState = event.newState - return@listen - } - destroyBlock(info) - if (info.type == RedundantSecondary) { - info.nullify() - return@listen - } - info.request.onStop?.invoke(this@listen, info.context.blockPos) - info.internalOnBreak() - if (info.callbacksCompleted) - RebreakHandler.offerRebreak(info) - else info.startPending() - info.nullify() - } - } - - // ToDo: Dependent on the tracked data order. When set stack is called after position it wont work - listen(priority = Int.MIN_VALUE) { - if (it.entity !is ItemEntity) return@listen - - // ToDo: Proper item drop prediction system - RebreakHandler.rebreak?.let { reBreak -> - if (matchesBlockItem(reBreak, it.entity)) return@listen - } - - breakInfos - .filterNotNull() - .firstOrNull { info -> matchesBlockItem(info, it.entity) } - ?.internalOnItemDrop(it.entity) - } - - onDynamicRender { render -> - val activeStack = breakInfos - .filterNotNull() - .firstOrNull()?.swapStack ?: return@onDynamicRender - - breakInfos - .filterNotNull() - .forEach { info -> - if (!info.breaking) return@forEach - - val config = info.breakConfig - if (!config.renders) return@onDynamicRender - val swapMode = info.breakConfig.swapMode - val breakDelta = info.request.runSafeAutomated { - info.context.cachedState.calcBreakDelta( - info.context.blockPos, - if (info.type != RedundantSecondary && - swapMode.isEnabled() && - swapMode != BreakConfig.SwapMode.Start - ) activeStack - else null - ).toDouble() - } - val currentDelta = info.breakingTicks * breakDelta - - val threshold = if (info.type == Primary) info.breakConfig.breakThreshold else 1f - val adjustedThreshold = threshold + (breakDelta * config.fudgeFactor) - - val currentProgress = currentDelta / adjustedThreshold - val nextTicksProgress = (currentDelta + breakDelta) / adjustedThreshold - val interpolatedProgress = lerp(mc.partialTicks, currentProgress, nextTicksProgress) - - val fillColor = if (config.dynamicFillColor) lerp( - interpolatedProgress, - config.startFillColor, - config.endFillColor - ) - else config.staticFillColor - val outlineColor = if (config.dynamicOutlineColor) lerp( - interpolatedProgress, - config.startOutlineColor, - config.endOutlineColor - ) - else config.staticOutlineColor - - info.context.cachedState.getOutlineShape(world, info.context.blockPos).boundingBoxes.map { - it.offset(info.context.blockPos) - }.forEach boxes@ { box -> - val animationMode = info.breakConfig.animation - val currentProgress = interpolateBox(box, currentProgress, animationMode) - val nextProgress = interpolateBox(box, nextTicksProgress, animationMode) - val dynamicAABB = DynamicAABB().update(currentProgress).update(nextProgress) - if (config.fill) render.filled(dynamicAABB, fillColor) - if (config.outline) render.outline(dynamicAABB, outlineColor) - } - } - } - - listenUnsafe(priority = Int.MIN_VALUE) { - primaryBreak = null - secondaryBreak = null - breakCooldown = 0 - } - - return "Loaded Break Manager" - } - - /** - * Attempts to accept and process the request, if there is not already an [activeRequest] and the - * [BreakRequest.contexts] collection is not empty. If nowOrNothing is true, the request is cleared - * after the first process. - * - * @see processRequest - */ - override fun AutomatedSafeContext.handleRequest(request: BreakRequest) { - if (activeRequest != null || request.contexts.isEmpty()) return - if (InteractManager.activeThisTick) return - - activeRequest = request - processRequest(request) - if (request.nowOrNothing) { - activeRequest = null - breaks = mutableListOf() - } - } - - /** - * Handles populating the manager, updating break progresses, and clearing the active request - * when all breaks are complete. - * - * @see populateFrom - * @see processNewBreak - * @see handlePreProcessing - * @see updateBreakProgress - */ - private fun SafeContext.processRequest(request: BreakRequest?) { - request?.let { request -> - logger.debug("Processing request", request) - if (request.fresh) populateFrom(request) - } - - var noNew: Boolean - var noProgression: Boolean - - while (true) { - noNew = request?.let { !processNewBreak(request) } != false - - // Reversed so that the breaking order feels natural to the user as the primary break is always the - // last break to be started - handlePreProcessing() - noProgression = - activeInfos - .filter { it.updatedThisTick && it.shouldProgress } - .asReversed() - .run { - if (isEmpty()) true - else { - forEach { breakInfo -> - updateBreakProgress(breakInfo) - } - false - } - } - - if (noNew && noProgression) break - } - - if (breaks.isEmpty()) { - if (activeRequest != null) logger.debug("Clearing active request", activeRequest) - activeRequest = null - } - if (breaksThisTick > 0 || activeInfos.isNotEmpty()) { - activeThisTick = true - } - } - - /** - * Filters the requests [BreakContext]s, and iterates over the [breakInfos] collection looking for matches - * in positions. If a match is found, the [BreakInfo] is updated with the new context. - * The [breaks] collection is then populated with the new appropriate contexts, and the [maxBreaksThisTick] - * value is set. - * - * @see canAccept - * @see BreakInfo.updateInfo - */ - private fun SafeContext.populateFrom(request: BreakRequest) = request.runSafeAutomated { - logger.debug("Populating from request", request) - - // Sanitize the new breaks - val newBreaks = request.contexts - .distinctBy { it.blockPos } - .filter { canAccept(it) && (!request.nowOrNothing || it.instantBreak) } - .toMutableList() - - // Update the current break infos - breakInfos - .filterNotNull() - .forEach { info -> - val ctx = newBreaks.find { ctx -> - ctx.blockPos == info.context.blockPos - } ?: return@forEach - - newBreaks.remove(ctx) - - if (info.updatedThisTick && info.type != RedundantSecondary && !info.abandoned) return@forEach - - logger.debug("Updating info", info, ctx) - when { - info.type == RedundantSecondary -> info.request.onStart?.invoke(this, info.context.blockPos) - info.abandoned -> { - info.abandoned = false - info.request.onStart?.invoke(this, info.context.blockPos) - } - else -> info.request.onUpdate?.invoke(this, info.context.blockPos) - } - - info.updateInfo(ctx, request) - } - - breaks = newBreaks - .take(buildConfig.maxPendingActions - request.pendingInteractions.size.coerceAtLeast(0)) - .toMutableList() - - logger.debug("${breaks.size} unprocessed breaks") - - maxBreaksThisTick = breakConfig.breaksPerTick - } - - /** - * @return if the break context can be accepted. - */ - private fun SafeContext.canAccept(newCtx: BreakContext): Boolean { - if (activeInfos.none { it.context.blockPos == newCtx.blockPos } && isPosBlocked(newCtx.blockPos)) return false - - val hardness = newCtx.cachedState.getHardness(world, newCtx.blockPos) - - return newCtx.cachedState.isNotEmpty && (hardness != -1f || player.isCreative) - } - - /** - * Updates the pre-processing for [BreakInfo] elements within [activeInfos] as long as they've been updated this tick. - * This method also populates [rotationRequest] and [hotbarRequest]. - * - * @see updatePreProcessing - */ - private fun SafeContext.handlePreProcessing() { - if (activeInfos.isEmpty()) return - - activeInfos - .filter { it.updatedThisTick } - .let { infos -> - rotationRequest = infos.lastOrNull { info -> - info.breakConfig.rotate - }?.let { info -> - val rotation = info.context.rotationRequest - logger.debug("Requesting rotation", rotation) - rotation.submit(false) - } - - infos.forEach { it.updatePreProcessing() } - - val first = infos.firstOrNull() ?: return@let - val last = infos.lastOrNull { it.swapInfo.swap && it.shouldProgress } ?: return@let - - val minKeepTicks = if (first.swapInfo.longSwap || last.swapInfo.longSwap) 1 else 0 - val serverSwapTicks = max(first.breakConfig.serverSwapTicks, last.breakConfig.serverSwapTicks) - - hotbarRequest = with(last) { - HotbarRequest( - context.hotbarIndex, - request, - request.hotbarConfig.keepTicks.coerceAtLeast(minKeepTicks), - request.hotbarConfig.swapPause.coerceAtLeast(serverSwapTicks - 1) - ).submit(false) - } - - logger.debug("Submitted hotbar request", hotbarRequest) - return - } - - hotbarRequest = null - } - - /** - * Attempts to start breaking as many [BreakContext]'s from the [breaks] collection as possible. - * - * @return false if a context cannot be started or the maximum active breaks have been reached. - * - * @see initNewBreak - */ - private fun SafeContext.processNewBreak(request: BreakRequest): Boolean = request.runSafeAutomated { - breaks.forEach { ctx -> - if (breaksThisTick >= maxBreaksThisTick) return false - if (!currentStackSelection.filterStack(player.inventory.getStack(ctx.hotbarIndex))) return@forEach - - initNewBreak(ctx, request) ?: return false - breaks.remove(ctx) - return true - } - return false - } - - /** - * Attempts to accept the [requestCtx] into the [breakInfos]. - * - * If a primary [BreakInfo] is active, as long as the tick stage is valid, it is transformed - * into a secondary break, so a new primary can be initialized. This means sending a - * PlayerActionC2SPacket with action: STOP_DESTROY_BLOCK - * packet to the server to start the automated breaking server side. - * - * If there is no way to keep both breaks, and the primary break hasn't been updated yet, - * the primary break is canceled. Otherwise, the break cannot be started. - * - * @return the [BreakInfo], or null, if the break context wasn't accepted. - * - * @see net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket - */ - private fun AutomatedSafeContext.initNewBreak( - requestCtx: BreakContext, - request: BreakRequest - ): BreakInfo? { - if (breakCooldown > 0) return null - - val breakInfo = BreakInfo(requestCtx, Primary, request) - primaryBreak?.let { primaryInfo -> - if (tickStage !in primaryInfo.breakConfig.tickStageMask) return null - - if (!primaryInfo.breakConfig.doubleBreak || secondaryBreak != null) { - if (!primaryInfo.updatedThisTick) { - primaryInfo.cancelBreak() - return@let - } else return null - } - - if (!primaryInfo.breaking) return null - - logger.debug("Transforming primary to secondary", primaryInfo) - secondaryBreak = primaryInfo.apply { type = Secondary } - secondaryBreak?.stopBreakPacket() - return@let - } - - primaryBreak = breakInfo - setPendingConfigs() - logger.success("Initialized break info", breakInfo) - return primaryBreak - } - - /** - * Simulates and updates the [abandonedBreak]. - */ - private fun SafeContext.simulateAbandoned() { - // Canceled but double breaking so requires break manager to continue the simulation - val abandonedInfo = abandonedBreak ?: return - - abandonedInfo.request.runSafeAutomated { - abandonedInfo.context.blockPos - .toStructure(TargetState.Empty) - .simulate() - .filterIsInstance() - .filter { canAccept(it.context) } - .sorted() - .let { sim -> - abandonedInfo.updateInfo(sim.firstOrNull()?.context ?: return) - } - } - } - - /** - * Checks if any active [BreakInfo]s are not updated this tick, and are within the timeframe of a valid tick stage. - * If so, the [BreakInfo] is either canceled, or progressed if the break is redundant. - */ - private fun SafeContext.checkForCancels() { - breakInfos - .filterNotNull() - .filter { !it.updatedThisTick && tickStage in it.breakConfig.tickStageMask } - .forEach { info -> - if (info.type == RedundantSecondary && !info.progressedThisTick) { - val cachedState = info.context.cachedState - if (cachedState.isEmpty) { - info.nullify() - return@forEach - } - info.progressedThisTick = true - info.breakingTicks++ - } else info.cancelBreak() - } - } - - /** - * Begins the post-break logic sequence for the given [info]. - * - * [BreakConfirmationMode.None] Will assume the block has been broken server side, and will only persist - * the [info] if the requester has any untriggered callbacks. E.g., if the block has broken, but the item hasn't dropped - * and the requester has specified an itemDrop callback. - * - * [BreakConfirmationMode.BreakThenAwait] Will perform all post-block break actions, such as spawning break particles, - * playing sounds, etc. However, it will store the [info] in the pending interaction collections before triggering the - * [BreakInfo.internalOnBreak] callback, in case the server rejects the break. - * - * [BreakConfirmationMode.AwaitThenBreak] Will immediately place the [info] into the pending interaction collections. - * Once the server responds, confirming the break, the post-break actions will take place, and the [BreakInfo.internalOnBreak] - * callback will be triggered. - * - * @see destroyBlock - * @see startPending - * @see nullify - */ - private fun AutomatedSafeContext.onBlockBreak(info: BreakInfo) { - info.request.onStop?.invoke(this, info.context.blockPos) - when (breakConfig.breakConfirmation) { - BreakConfirmationMode.None -> { - destroyBlock(info) - info.internalOnBreak() - if (!info.callbacksCompleted) { - info.startPending() - } else { - RebreakHandler.offerRebreak(info) - } - } - BreakConfirmationMode.BreakThenAwait -> { - destroyBlock(info) - info.startPending() - } - BreakConfirmationMode.AwaitThenBreak -> { - info.startPending() - } - } - breaksThisTick++ - info.nullify() - } - - context(_: SafeContext) - private fun BreakInfo.updatePreProcessing() = request.runSafeAutomated { - logger.debug("Updating pre-processing", this@updatePreProcessing) - - shouldProgress = !progressedThisTick - && tickStage in breakConfig.tickStageMask - && (rotated || type != Primary) - - if (updatedPreProcessingThisTick) return - updatedPreProcessingThisTick = true - - swapStack = player.inventory.getStack(context.hotbarIndex) - rebreakPotential = getRebreakPotential() - swapInfo = getSwapInfo() - } - - /** - * Attempts to cancel the break. - * - * Secondary blocks are monitored by the server and keep breaking regardless of the clients' actions. - * This means that the break cannot be completely stopped. Instead, it must be monitored as we can't start - * another secondary [BreakInfo] until the previous has broken or its state has become empty. - * - * If the user has [BreakConfig.unsafeCancels] enabled, the info is made redundant, and mostly ignored. - * If not, the break continues. - */ - context(safeContext: SafeContext) - private fun BreakInfo.cancelBreak() = with(safeContext) { - if (type == RedundantSecondary || abandoned) return@with - when (type) { - Primary -> { - logger.warning("Cancelling break", this@cancelBreak) - nullify() - setBreakingTextureStage(player, world, -1) - abortBreakPacket() - request.onCancel?.invoke(this, context.blockPos) - } - Secondary -> { - if (breakConfig.unsafeCancels) { - logger.warning("Making break redundant", this@cancelBreak) - type = RedundantSecondary - setBreakingTextureStage(player, world, -1) - request.onCancel?.invoke(this, context.blockPos) - } else { - logger.warning("Abandoning break", this@cancelBreak) - abandoned = true - } - } - else -> {} - } - } - - private fun BreakInfo.nullify() = - when (type) { - Primary, Rebreak -> primaryBreak = null - else -> secondaryBreak = null - } - - /** - * A modified version of the vanilla updateBlockBreakingProgress method. - * - * @return if the update was successful. - * - * @see net.minecraft.client.network.ClientPlayerInteractionManager.updateBlockBreakingProgress - */ - private fun SafeContext.updateBreakProgress(info: BreakInfo): Unit = info.request.runSafeAutomated { - val ctx = info.context - - info.progressedThisTick = true - - if (!info.breaking) { - if (info.swapInfo.swap && !swapped) return - if (!startBreaking(info)) { - info.nullify() - info.request.onCancel?.invoke(this, ctx.blockPos) - } - return - } - - val hitResult = ctx.hitResult - - if (gamemode.isCreative && world.worldBorder.contains(ctx.blockPos)) { - breakCooldown = breakConfig.breakDelay - lastPosStarted = ctx.blockPos - onBlockBreak(info) - info.startBreakPacket() - if (breakConfig.swing.isEnabled()) swingHand(breakConfig.swingType, Hand.MAIN_HAND) - return - } - - val blockState = blockState(ctx.blockPos) - if (blockState.isEmpty) { - info.nullify() - info.request.onCancel?.invoke(this, ctx.blockPos) - logger.warning("Block state was unexpectedly empty", info) - return - } - - if (breakConfig.swapMode == BreakConfig.SwapMode.Constant && !swapped) return - - info.breakingTicks++ - val breakDelta = blockState.calcBreakDelta(ctx.blockPos) - val progress = breakDelta * (info.breakingTicks - breakConfig.fudgeFactor) - logger.debug("${info.type} progress: $progress, breaking ticks: ${info.breakingTicks}", info) - - if (breakConfig.sounds) { - if (info.soundsCooldown % 4.0f == 0.0f) { - val blockSoundGroup = blockState.soundGroup - mc.soundManager.play( - PositionedSoundInstance( - blockSoundGroup.hitSound, - SoundCategory.BLOCKS, - (blockSoundGroup.getVolume() + 1.0f) / 8.0f, - blockSoundGroup.getPitch() * 0.5f, - SoundInstance.createRandom(), - ctx.blockPos - ) - ) - } - info.soundsCooldown++ - } - - if (breakConfig.particles) { - mc.particleManager.addBlockBreakingParticles(ctx.blockPos, hitResult.side) - } - - if (breakConfig.breakingTexture) { - info.setBreakingTextureStage(player, world) - } - - val swing = breakConfig.swing - if (progress >= info.getBreakThreshold()) { - if (info.swapInfo.swap && !swapped) return - - logger.success("Breaking", info) - onBlockBreak(info) - if (info.type == Primary) info.stopBreakPacket() - if (swing.isEnabled() && swing != BreakConfig.SwingMode.Start) - swingHand(breakConfig.swingType, Hand.MAIN_HAND) - breakCooldown = breakConfig.breakDelay - } else { - if (swing == BreakConfig.SwingMode.Constant) - swingHand(breakConfig.swingType, Hand.MAIN_HAND) - } - } - - /** - * A modified version of the minecraft attackBlock method. - * - * @return if the block started breaking successfully. - * - * @see net.minecraft.client.network.ClientPlayerInteractionManager.attackBlock - */ - private fun AutomatedSafeContext.startBreaking(info: BreakInfo): Boolean { - val ctx = info.context - - if (info.rebreakPotential.isPossible()) { - logger.debug("Handling potential rebreak") - when (val rebreakResult = RebreakHandler.handleUpdate(info.context, info.request)) { - is RebreakResult.StillBreaking -> { - logger.debug("Rebreak not complete", info) - primaryBreak = rebreakResult.breakInfo.apply { - type = Primary - RebreakHandler.clearRebreak() - request.onStart?.invoke(this@startBreaking, ctx.blockPos) - } - - primaryBreak?.let { primary -> - handlePreProcessing() - updateBreakProgress(primary) - } - return true - } - is RebreakResult.Rebroke -> { - logger.debug("Rebroke", info) - info.type = Rebreak - info.nullify() - info.request.onReBreak?.invoke(this, ctx.blockPos) - return true - } - else -> {} - } - } - - if (player.isBlockBreakingRestricted(world, ctx.blockPos, gamemode)) return false - if (!world.worldBorder.contains(ctx.blockPos)) return false - - if (gamemode.isCreative) { - lastPosStarted = ctx.blockPos - info.request.onStart?.invoke(this, ctx.blockPos) - onBlockBreak(info) - info.startBreakPacket() - breakCooldown = breakConfig.breakDelay - if (breakConfig.swing.isEnabled()) swingHand(breakConfig.swingType, Hand.MAIN_HAND) - return true - } - if (info.breaking) return false - info.request.onStart?.invoke(this, ctx.blockPos) - - lastPosStarted = ctx.blockPos - - val blockState = blockState(ctx.blockPos) - if (info.breakingTicks == 0) { - blockState.onBlockBreakStart(world, ctx.blockPos, player) - } - - val progress = blockState.calcBreakDelta(ctx.blockPos) - - val instantBreakable = progress >= info.getBreakThreshold() - if (instantBreakable) { - logger.success("Instant breaking", info) - info.vanillaInstantBreakable = progress >= 1 - onBlockBreak(info) - if (!info.vanillaInstantBreakable) breakCooldown = breakConfig.breakDelay + 1 - } else { - logger.debug("Starting break", info) - info.apply { - breaking = true - breakingTicks = 1 - soundsCooldown = 0.0f - if (breakConfig.breakingTexture) { - setBreakingTextureStage(player, world) - } - } - } - - if (breakConfig.breakMode == BreakMode.Packet) { - info.stopBreakPacket() - } - - info.startBreakPacket() - - if (info.type == Secondary || (instantBreakable && !info.vanillaInstantBreakable)) { - info.stopBreakPacket() - } - - if (breakConfig.swing.isEnabled() && (breakConfig.swing != BreakConfig.SwingMode.End || instantBreakable)) { - swingHand(breakConfig.swingType, Hand.MAIN_HAND) - } - - return true - } - - /** - * Wrapper method for calculating block-breaking delta. - */ - context(automatedSafeContext: AutomatedSafeContext) - fun BlockState.calcBreakDelta( - pos: BlockPos, - item: ItemStack? = null - ) = with(automatedSafeContext) { - val delta = calcItemBlockBreakingDelta( pos, item ?: player.mainHandStack) - //ToDo: This setting requires some fixes / improvements in the player movement prediction to work properly. Currently, it's broken + 0, + onOpen = { + processRequest(activeRequest) + simulateAbandoned() + }, + onClose = { checkForCancels() } +), PositionBlocking { + private val breakInfos = arrayOfNulls(2) + + private val activeInfos + get() = breakInfos + .filterNotNull() + .filter { it.type != RedundantSecondary } + + private var primaryBreak: BreakInfo? + get() = breakInfos[0] + set(value) { breakInfos[0] = value } + + private var secondaryBreak: BreakInfo? + get() = breakInfos[1] + set(value) { breakInfos[1] = value } + + private val abandonedBreak + get() = breakInfos[1].let { secondary -> + if (secondary?.abandoned == true && secondary.type != RedundantSecondary) secondary + else null + } + + val currentStackSelection + get() = activeInfos + .lastOrNull { + it.breakConfig.doubleBreak || it.type == Secondary + }?.context?.itemSelection + ?: StackSelection.EVERYTHING.select() + + override val blockedPositions + get() = activeInfos.map { it.context.blockPos } + pendingActions.map { it.context.blockPos } + + private var activeRequest: BreakRequest? = null + + private var hotbarRequest: HotbarRequest? = null + private val swapped get() = hotbarRequest?.done != false + + private var rotationRequest: RotationRequest? = null + private val rotated get() = rotationRequest?.done != false + + private var breakCooldown = 0 + var breaksThisTick = 0 + private var maxBreaksThisTick = 0 + + private var breaks = mutableListOf() + + var lastPosStarted: BlockPos? = null + set(value) { + if (value != field) RebreakHandler.clearRebreak() + field = value + } + + override fun load(): String { + super.load() + + listen(priority = Int.MAX_VALUE) { + if (activeInfos.isEmpty() && breaks.isEmpty()) return@listen + } + + listen(priority = Int.MIN_VALUE) { + breakInfos.forEach { it?.tickChecks() } + if (breakCooldown > 0) { + breakCooldown-- + } + activeRequest = null + breaks = mutableListOf() + breaksThisTick = 0 + } + + listen(priority = Int.MIN_VALUE) { event -> + if (event.pos == RebreakHandler.rebreak?.context?.blockPos) return@listen + + breakInfos + .firstOrNull { it?.context?.blockPos == event.pos } + ?.let { info -> + // if not broken + if (isNotBroken(info.context.cachedState, event.newState)) { + // update the cached state + info.context.cachedState = event.newState + return@listen + } + destroyBlock(info) + if (info.type == RedundantSecondary) { + info.nullify() + return@listen + } + info.request.onStop?.invoke(this@listen, info.context.blockPos) + info.internalOnBreak() + if (info.callbacksCompleted) + RebreakHandler.offerRebreak(info) + else info.startPending() + info.nullify() + } + } + + // ToDo: Dependent on the tracked data order. When set stack is called after position it wont work + listen(priority = Int.MIN_VALUE) { + if (it.entity !is ItemEntity) return@listen + + // ToDo: Proper item drop prediction system + RebreakHandler.rebreak?.let { reBreak -> + if (matchesBlockItem(reBreak, it.entity)) return@listen + } + + breakInfos + .filterNotNull() + .firstOrNull { info -> matchesBlockItem(info, it.entity) } + ?.internalOnItemDrop(it.entity) + } + + onDynamicRender { esp -> + val activeStack = breakInfos + .filterNotNull() + .firstOrNull()?.swapStack ?: return@onDynamicRender + + breakInfos + .filterNotNull() + .forEach { info -> + if (!info.breaking) return@forEach + + val config = info.breakConfig + if (!config.renders) return@onDynamicRender + val swapMode = info.breakConfig.swapMode + val breakDelta = info.request.runSafeAutomated { + info.context.cachedState.calcBreakDelta( + info.context.blockPos, + if (info.type != RedundantSecondary && + swapMode.isEnabled() && + swapMode != BreakConfig.SwapMode.Start + ) activeStack + else null + ).toDouble() + } + val currentDelta = info.breakingTicks * breakDelta + + val threshold = if (info.type == Primary) info.breakConfig.breakThreshold else 1f + val adjustedThreshold = threshold + (breakDelta * config.fudgeFactor) + + val currentProgress = currentDelta / adjustedThreshold + val nextTicksProgress = (currentDelta + breakDelta) / adjustedThreshold + val interpolatedProgress = lerp(mc.partialTicks, currentProgress, nextTicksProgress) + + val fillColor = if (config.dynamicFillColor) lerp( + interpolatedProgress, + config.startFillColor, + config.endFillColor + ) + else config.staticFillColor + val outlineColor = if (config.dynamicOutlineColor) lerp( + interpolatedProgress, + config.startOutlineColor, + config.endOutlineColor + ) + else config.staticOutlineColor + + val pos = info.context.blockPos + esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) { + info.context.cachedState.getOutlineShape(world, pos).boundingBoxes.map { + it.offset(pos) + }.forEach boxes@{ box -> + val animationMode = info.breakConfig.animation + val currentProgressBox = interpolateBox(box, currentProgress, animationMode) + val nextProgressBox = interpolateBox(box, nextTicksProgress, animationMode) + val dynamicAABB = DynamicAABB().update(currentProgressBox).update(nextProgressBox) + if (config.fill) filled(dynamicAABB, fillColor) + if (config.outline) outline(dynamicAABB, outlineColor) + } + } + } + } + + listenUnsafe(priority = Int.MIN_VALUE) { + primaryBreak = null + secondaryBreak = null + breakCooldown = 0 + } + + return "Loaded Break Manager" + } + + /** + * Attempts to accept and process the request, if there is not already an [activeRequest] and the + * [BreakRequest.contexts] collection is not empty. If nowOrNothing is true, the request is cleared + * after the first process. + * + * @see processRequest + */ + override fun AutomatedSafeContext.handleRequest(request: BreakRequest) { + if (!request.buildConfig.breakBlocks || activeRequest != null || request.contexts.isEmpty()) return + if (InteractManager.activeThisTick) return + + activeRequest = request + processRequest(request) + if (request.nowOrNothing) { + activeRequest = null + breaks = mutableListOf() + } + } + + /** + * Handles populating the manager, updating break progresses, and clearing the active request + * when all breaks are complete. + * + * @see populateFrom + * @see processNewBreak + * @see handlePreProcessing + * @see updateBreakProgress + */ + private fun SafeContext.processRequest(request: BreakRequest?) { + request?.let { request -> + if (request.fresh) populateFrom(request) + } + + var noNew: Boolean + var noProgression: Boolean + + while (true) { + noNew = request?.let { !processNewBreak(request) } != false + + // Reversed so that the breaking order feels natural to the user as the primary break is always the + // last break to be started + handlePreProcessing() + noProgression = + activeInfos + .filter { it.updatedThisTick && it.shouldProgress } + .asReversed() + .run { + if (isEmpty()) true + else { + forEach { breakInfo -> + updateBreakProgress(breakInfo) + } + false + } + } + + if (noNew && noProgression) break + } + + if (breaks.isEmpty()) activeRequest = null + if (breaksThisTick > 0 || activeInfos.isNotEmpty()) { + activeThisTick = true + } + } + + /** + * Filters the requests [BreakContext]s, and iterates over the [breakInfos] collection looking for matches + * in positions. If a match is found, the [BreakInfo] is updated with the new context. + * The [breaks] collection is then populated with the new appropriate contexts, and the [maxBreaksThisTick] + * value is set. + * + * @see canAccept + * @see BreakInfo.updateInfo + */ + private fun SafeContext.populateFrom(request: BreakRequest) = request.runSafeAutomated { + // Sanitize the new breaks + val newBreaks = request.contexts + .distinctBy { it.blockPos } + .filter { canAccept(it) && (!request.nowOrNothing || it.instantBreak) } + .toMutableList() + + // Update the current break infos + breakInfos + .filterNotNull() + .forEach { info -> + val ctx = newBreaks.find { ctx -> + ctx.blockPos == info.context.blockPos + } ?: return@forEach + + newBreaks.remove(ctx) + + if (info.updatedThisTick && info.type != RedundantSecondary && !info.abandoned) return@forEach + + when { + info.type == RedundantSecondary -> info.request.onStart?.invoke(this, info.context.blockPos) + info.abandoned -> { + info.abandoned = false + info.request.onStart?.invoke(this, info.context.blockPos) + } + else -> info.request.onUpdate?.invoke(this, info.context.blockPos) + } + + info.updateInfo(ctx, request) + } + + breaks = newBreaks + .take(buildConfig.maxPendingActions - request.pendingInteractions.size.coerceAtLeast(0)) + .toMutableList() + + maxBreaksThisTick = breakConfig.breaksPerTick + } + + /** + * @return if the break context can be accepted. + */ + private fun SafeContext.canAccept(newCtx: BreakContext): Boolean { + if (activeInfos.none { it.context.blockPos == newCtx.blockPos } && isPosBlocked(newCtx.blockPos)) return false + + val hardness = newCtx.cachedState.getHardness(world, newCtx.blockPos) + + return newCtx.cachedState.isNotEmpty && (hardness != -1f || player.isCreative) + } + + /** + * Updates the pre-processing for [BreakInfo] elements within [activeInfos] as long as they've been updated this tick. + * This method also populates [rotationRequest] and [hotbarRequest]. + * + * @see updatePreProcessing + */ + private fun SafeContext.handlePreProcessing() { + if (activeInfos.isEmpty()) return + + activeInfos + .filter { it.updatedThisTick } + .let { infos -> + rotationRequest = infos.lastOrNull { info -> + info.breakConfig.rotate + }?.let { info -> + val rotation = info.context.rotationRequest + rotation.submit(false) + } + + infos.forEach { it.updatePreProcessing() } + + val first = infos.firstOrNull() ?: return@let + val last = infos.lastOrNull { it.swapInfo.swap && it.shouldProgress } ?: return@let + + val minKeepTicks = if (first.swapInfo.longSwap || last.swapInfo.longSwap) 1 else 0 + val serverSwapTicks = max(first.breakConfig.serverSwapTicks, last.breakConfig.serverSwapTicks) + + hotbarRequest = with(last) { + HotbarRequest( + context.hotbarIndex, + request, + request.hotbarConfig.keepTicks.coerceAtLeast(minKeepTicks), + request.hotbarConfig.swapPause.coerceAtLeast(serverSwapTicks - 1) + ).submit(false) + } + + return + } + + hotbarRequest = null + } + + /** + * Attempts to start breaking as many [BreakContext]'s from the [breaks] collection as possible. + * + * @return false if a context cannot be started or the maximum active breaks have been reached. + * + * @see initNewBreak + */ + private fun SafeContext.processNewBreak(request: BreakRequest): Boolean = request.runSafeAutomated { + breaks.forEach { ctx -> + if (breaksThisTick >= maxBreaksThisTick) return false + if (!currentStackSelection.filterStack(player.inventory.getStack(ctx.hotbarIndex))) return@forEach + + initNewBreak(ctx, request) ?: return false + breaks.remove(ctx) + return true + } + return false + } + + /** + * Attempts to accept the [requestCtx] into the [breakInfos]. + * + * If a primary [BreakInfo] is active, as long as the tick stage is valid, it is transformed + * into a secondary break, so a new primary can be initialized. This means sending a + * PlayerActionC2SPacket with action: STOP_DESTROY_BLOCK + * packet to the server to start the automated breaking server side. + * + * If there is no way to keep both breaks, and the primary break hasn't been updated yet, + * the primary break is canceled. Otherwise, the break cannot be started. + * + * @return the [BreakInfo], or null, if the break context wasn't accepted. + * + * @see net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket + */ + private fun AutomatedSafeContext.initNewBreak( + requestCtx: BreakContext, + request: BreakRequest + ): BreakInfo? { + if (breakCooldown > 0) return null + + val breakInfo = BreakInfo(requestCtx, Primary, request) + primaryBreak?.let { primaryInfo -> + if (tickStage !in primaryInfo.breakConfig.tickStageMask) return null + + if (!primaryInfo.breakConfig.doubleBreak || secondaryBreak != null) { + if (!primaryInfo.updatedThisTick) { + primaryInfo.cancelBreak() + return@let + } else return null + } + + if (!primaryInfo.breaking) return null + + secondaryBreak = primaryInfo.apply { type = Secondary } + secondaryBreak?.stopBreakPacket() + return@let + } + + primaryBreak = breakInfo + setPendingConfigs() + return primaryBreak + } + + /** + * Simulates and updates the [abandonedBreak]. + */ + private fun SafeContext.simulateAbandoned() { + // Canceled but double breaking so requires break manager to continue the simulation + val abandonedInfo = abandonedBreak ?: return + + abandonedInfo.request.runSafeAutomated { + abandonedInfo.context.blockPos + .toStructure(TargetState.Empty) + .simulate() + .filterIsInstance() + .filter { canAccept(it.context) } + .sorted() + .let { sim -> + abandonedInfo.updateInfo(sim.firstOrNull()?.context ?: return) + } + } + } + + /** + * Checks if any active [BreakInfo]s are not updated this tick, and are within the timeframe of a valid tick stage. + * If so, the [BreakInfo] is either canceled, or progressed if the break is redundant. + */ + private fun SafeContext.checkForCancels() { + breakInfos + .filterNotNull() + .filter { !it.updatedThisTick && tickStage in it.breakConfig.tickStageMask } + .forEach { info -> + if (info.type == RedundantSecondary && !info.progressedThisTick) { + val cachedState = info.context.cachedState + if (cachedState.isEmpty) { + info.nullify() + return@forEach + } + info.progressedThisTick = true + info.breakingTicks++ + } else info.cancelBreak() + } + } + + /** + * Begins the post-break logic sequence for the given [info]. + * + * [BreakConfirmationMode.None] Will assume the block has been broken server side, and will only persist + * the [info] if the requester has any untriggered callbacks. E.g., if the block has broken, but the item hasn't dropped + * and the requester has specified an itemDrop callback. + * + * [BreakConfirmationMode.BreakThenAwait] Will perform all post-block break actions, such as spawning break particles, + * playing sounds, etc. However, it will store the [info] in the pending interaction collections before triggering the + * [BreakInfo.internalOnBreak] callback, in case the server rejects the break. + * + * [BreakConfirmationMode.AwaitThenBreak] Will immediately place the [info] into the pending interaction collections. + * Once the server responds, confirming the break, the post-break actions will take place, and the [BreakInfo.internalOnBreak] + * callback will be triggered. + * + * @see destroyBlock + * @see startPending + * @see nullify + */ + private fun AutomatedSafeContext.onBlockBreak(info: BreakInfo) { + info.request.onStop?.invoke(this, info.context.blockPos) + when (breakConfig.breakConfirmation) { + BreakConfirmationMode.None -> { + destroyBlock(info) + info.internalOnBreak() + if (!info.callbacksCompleted) { + info.startPending() + } else { + RebreakHandler.offerRebreak(info) + } + } + BreakConfirmationMode.BreakThenAwait -> { + destroyBlock(info) + info.startPending() + } + BreakConfirmationMode.AwaitThenBreak -> { + info.startPending() + } + } + breaksThisTick++ + info.nullify() + } + + context(_: SafeContext) + private fun BreakInfo.updatePreProcessing() = request.runSafeAutomated { + shouldProgress = !progressedThisTick + && tickStage in breakConfig.tickStageMask + && (rotated || type != Primary) + + if (updatedPreProcessingThisTick) return@runSafeAutomated + updatedPreProcessingThisTick = true + + swapStack = player.inventory.getStack(context.hotbarIndex) + rebreakPotential = getRebreakPotential() + swapInfo = getSwapInfo() + } + + /** + * Attempts to cancel the break. + * + * Secondary blocks are monitored by the server and keep breaking regardless of the clients' actions. + * This means that the break cannot be completely stopped. Instead, it must be monitored as we can't start + * another secondary [BreakInfo] until the previous has broken or its state has become empty. + * + * If the user has [BreakConfig.unsafeCancels] enabled, the info is made redundant, and mostly ignored. + * If not, the break continues. + */ + context(safeContext: SafeContext) + private fun BreakInfo.cancelBreak() = with(safeContext) { + if (type == RedundantSecondary || abandoned) return@with + when (type) { + Primary -> { + nullify() + setBreakingTextureStage(player, world, -1) + abortBreakPacket() + request.onCancel?.invoke(this, context.blockPos) + } + Secondary -> { + if (breakConfig.unsafeCancels) { + type = RedundantSecondary + setBreakingTextureStage(player, world, -1) + request.onCancel?.invoke(this, context.blockPos) + } else abandoned = true + } + else -> {} + } + } + + private fun BreakInfo.nullify() = + when (type) { + Primary, Rebreak -> primaryBreak = null + else -> secondaryBreak = null + } + + /** + * A modified version of the vanilla updateBlockBreakingProgress method. + * + * @return if the update was successful. + * + * @see net.minecraft.client.network.ClientPlayerInteractionManager.updateBlockBreakingProgress + */ + private fun SafeContext.updateBreakProgress(info: BreakInfo): Unit = info.request.runSafeAutomated { + val ctx = info.context + + info.progressedThisTick = true + + if (!info.breaking) { + if (info.swapInfo.swap && !swapped) return + if (!startBreaking(info)) { + info.nullify() + info.request.onCancel?.invoke(this, ctx.blockPos) + } + return + } + + val hitResult = ctx.hitResult + + if (gamemode.isCreative && world.worldBorder.contains(ctx.blockPos)) { + breakCooldown = breakConfig.breakDelay + lastPosStarted = ctx.blockPos + onBlockBreak(info) + info.startBreakPacket() + if (breakConfig.swing.isEnabled()) swingHand(breakConfig.swingType, Hand.MAIN_HAND) + return + } + + val blockState = blockState(ctx.blockPos) + if (blockState.isEmpty) { + info.nullify() + info.request.onCancel?.invoke(this, ctx.blockPos) + return + } + + if (breakConfig.swapMode == BreakConfig.SwapMode.Constant && !swapped) return + + info.breakingTicks++ + val breakDelta = blockState.calcBreakDelta(ctx.blockPos) + val progress = breakDelta * (info.breakingTicks - breakConfig.fudgeFactor) + + if (breakConfig.sounds) { + if (info.soundsCooldown % 4.0f == 0.0f) { + val blockSoundGroup = blockState.soundGroup + mc.soundManager.play( + PositionedSoundInstance( + blockSoundGroup.hitSound, + SoundCategory.BLOCKS, + (blockSoundGroup.getVolume() + 1.0f) / 8.0f, + blockSoundGroup.getPitch() * 0.5f, + SoundInstance.createRandom(), + ctx.blockPos + ) + ) + } + info.soundsCooldown++ + } + + if (breakConfig.particles) { + world.spawnBlockBreakingParticle(ctx.blockPos, hitResult.side) + } + + if (breakConfig.breakingTexture) { + info.setBreakingTextureStage(player, world) + } + + val swing = breakConfig.swing + if (progress >= info.getBreakThreshold()) { + if (info.swapInfo.swap && !swapped) return + + onBlockBreak(info) + if (info.type == Primary) info.stopBreakPacket() + if (swing.isEnabled() && swing != BreakConfig.SwingMode.Start) + swingHand(breakConfig.swingType, Hand.MAIN_HAND) + breakCooldown = breakConfig.breakDelay + } else { + if (swing == BreakConfig.SwingMode.Constant) + swingHand(breakConfig.swingType, Hand.MAIN_HAND) + } + } + + /** + * A modified version of the minecraft attackBlock method. + * + * @return if the block started breaking successfully. + * + * @see net.minecraft.client.network.ClientPlayerInteractionManager.attackBlock + */ + private fun AutomatedSafeContext.startBreaking(info: BreakInfo): Boolean { + val ctx = info.context + + if (info.rebreakPotential.isPossible()) { + when (val rebreakResult = RebreakHandler.handleUpdate(info.context, info.request)) { + is RebreakResult.StillBreaking -> { + primaryBreak = rebreakResult.breakInfo.apply { + type = Primary + RebreakHandler.clearRebreak() + request.onStart?.invoke(this@startBreaking, ctx.blockPos) + } + + primaryBreak?.let { primary -> + handlePreProcessing() + updateBreakProgress(primary) + } + return true + } + is RebreakResult.Rebroke -> { + info.type = Rebreak + info.nullify() + info.request.onReBreak?.invoke(this, ctx.blockPos) + return true + } + else -> {} + } + } + + if (player.isBlockBreakingRestricted(world, ctx.blockPos, gamemode)) return false + if (!world.worldBorder.contains(ctx.blockPos)) return false + + if (gamemode.isCreative) { + lastPosStarted = ctx.blockPos + info.request.onStart?.invoke(this, ctx.blockPos) + onBlockBreak(info) + info.startBreakPacket() + breakCooldown = breakConfig.breakDelay + if (breakConfig.swing.isEnabled()) swingHand(breakConfig.swingType, Hand.MAIN_HAND) + return true + } + if (info.breaking) return false + info.request.onStart?.invoke(this, ctx.blockPos) + + lastPosStarted = ctx.blockPos + + val blockState = blockState(ctx.blockPos) + if (info.breakingTicks == 0) { + blockState.onBlockBreakStart(world, ctx.blockPos, player) + } + + val progress = blockState.calcBreakDelta(ctx.blockPos) + + val instantBreakable = progress >= info.getBreakThreshold() + if (instantBreakable) { + info.vanillaInstantBreakable = progress >= 1 + onBlockBreak(info) + if (!info.vanillaInstantBreakable) + breakCooldown = if (breakConfig.breakDelay == 0) 0 else breakConfig.breakDelay + 1 + } else { + info.apply { + breaking = true + breakingTicks = 1 + soundsCooldown = 0.0f + if (breakConfig.breakingTexture) { + setBreakingTextureStage(player, world) + } + } + } + + if (breakConfig.breakMode == BreakMode.Packet) { + info.stopBreakPacket() + } + + info.startBreakPacket() + + if (info.type == Secondary || (instantBreakable && !info.vanillaInstantBreakable)) { + info.stopBreakPacket() + } + + if (breakConfig.swing.isEnabled() && (breakConfig.swing != BreakConfig.SwingMode.End || instantBreakable)) { + swingHand(breakConfig.swingType, Hand.MAIN_HAND) + } + + return true + } + + /** + * Wrapper method for calculating block-breaking delta. + */ + context(automatedSafeContext: AutomatedSafeContext) + fun BlockState.calcBreakDelta( + pos: BlockPos, + item: ItemStack? = null + ) = with(automatedSafeContext) { + val delta = calcItemBlockBreakingDelta( pos, item ?: player.mainHandStack) + //ToDo: This setting requires some fixes / improvements in the player movement prediction to work properly. Currently, it's broken // if (config.desyncFix) { // val nextTickPrediction = buildPlayerPrediction().next() // if (player.isOnGround && !nextTickPrediction.onGround) { @@ -888,33 +852,33 @@ object BreakManager : Manager( // delta /= 5.0f // } // } - delta - } - - /** - * @return if the [ItemEntity] matches the [BreakInfo]'s expected item drop. - */ - fun matchesBlockItem(info: BreakInfo, entity: ItemEntity): Boolean { - val inRange = info.context.blockPos.toCenterPos().isInRange(entity.pos, 0.5) - val correctMaterial = info.context.cachedState.block == entity.stack.item.block - return inRange && correctMaterial - } - - /** - * Interpolates the give [box] using the [BreakConfig]'s animation mode. - */ - private fun interpolateBox(box: Box, progress: Double, animationMode: BreakConfig.AnimationMode): Box { - val boxCenter = Box(box.center, box.center) - return when (animationMode) { - BreakConfig.AnimationMode.Out -> lerp(progress, boxCenter, box) - BreakConfig.AnimationMode.In -> lerp(progress, box, boxCenter) - BreakConfig.AnimationMode.InOut -> - if (progress >= 0.5f) lerp((progress - 0.5) * 2, boxCenter, box) - else lerp(progress * 2, box, boxCenter) - BreakConfig.AnimationMode.OutIn -> - if (progress >= 0.5f) lerp((progress - 0.5) * 2, box, boxCenter) - else lerp(progress * 2, boxCenter, box) - else -> box - } - } + delta + } + + /** + * @return if the [ItemEntity] matches the [BreakInfo]'s expected item drop. + */ + fun matchesBlockItem(info: BreakInfo, entity: ItemEntity): Boolean { + val inRange = info.context.blockPos.toCenterPos().isInRange(entity.pos, 0.5) + val correctMaterial = info.context.cachedState.block == entity.stack.item.block + return inRange && correctMaterial + } + + /** + * Interpolates the give [box] using the [BreakConfig]'s animation mode. + */ + private fun interpolateBox(box: Box, progress: Double, animationMode: BreakConfig.AnimationMode): Box { + val boxCenter = Box(box.center, box.center) + return when (animationMode) { + BreakConfig.AnimationMode.Out -> lerp(progress, boxCenter, box) + BreakConfig.AnimationMode.In -> lerp(progress, box, boxCenter) + BreakConfig.AnimationMode.InOut -> + if (progress >= 0.5f) lerp((progress - 0.5) * 2, boxCenter, box) + else lerp(progress * 2, box, boxCenter) + BreakConfig.AnimationMode.OutIn -> + if (progress >= 0.5f) lerp((progress - 0.5) * 2, box, boxCenter) + else lerp(progress * 2, boxCenter, box) + else -> box + } + } } diff --git a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakRequest.kt b/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakRequest.kt index 32a50031d..5e304134e 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/breaking/BreakRequest.kt @@ -27,8 +27,6 @@ import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.result.Dependent import com.lambda.interaction.construction.simulation.result.results.BreakResult import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.managers.LogContext -import com.lambda.interaction.managers.LogContext.Companion.LogContextBuilder import com.lambda.interaction.managers.Request import com.lambda.interaction.managers.breaking.BreakRequest.Companion.breakRequest import com.lambda.threading.runSafe @@ -51,136 +49,120 @@ import net.minecraft.util.math.BlockPos * @see com.lambda.interaction.construction.simulation.BuildSimulator */ data class BreakRequest private constructor( - val contexts: Collection, - val pendingInteractions: MutableCollection, - private val automated: Automated, - override val nowOrNothing: Boolean = false -) : Request(), LogContext, Automated by automated { - override val requestId = ++requestCount - override val tickStageMask get() = breakConfig.tickStageMask - - var onStart: (SafeContext.(BlockPos) -> Unit)? = null - var onUpdate: (SafeContext.(BlockPos) -> Unit)? = null - var onStop: (SafeContext.(BlockPos) -> Unit)? = null - var onCancel: (SafeContext.(BlockPos) -> Unit)? = null - var onItemDrop: (SafeContext.(ItemEntity) -> Unit)? = null - var onReBreakStart: (SafeContext.(BlockPos) -> Unit)? = null - var onReBreak: (SafeContext.(BlockPos) -> Unit)? = null - - override val done: Boolean - get() = runSafe { contexts.all { blockState(it.blockPos).isEmpty } } == true - - override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = { - group("Break Request") { - value("Request ID", requestId) - value("Contexts", contexts.size) - group("Callbacks") { - value("onStart", onStart != null) - value("onUpdate", onUpdate != null) - value("onStop", onStop != null) - value("onCancel", onCancel != null) - value("onItemDrop", onItemDrop != null) - value("onReBreakStart", onReBreakStart != null) - value("onReBreak", onReBreak != null) - } - } - } - - @DslMarker - annotation class BreakRequestDsl + val contexts: Collection, + val pendingInteractions: MutableCollection, + private val automated: Automated, + override val nowOrNothing: Boolean = false +) : Request(), Automated by automated { + override val requestId = ++requestCount + override val tickStageMask get() = breakConfig.tickStageMask + + var onStart: (SafeContext.(BlockPos) -> Unit)? = null + var onUpdate: (SafeContext.(BlockPos) -> Unit)? = null + var onStop: (SafeContext.(BlockPos) -> Unit)? = null + var onCancel: (SafeContext.(BlockPos) -> Unit)? = null + var onItemDrop: (SafeContext.(ItemEntity) -> Unit)? = null + var onReBreakStart: (SafeContext.(BlockPos) -> Unit)? = null + var onReBreak: (SafeContext.(BlockPos) -> Unit)? = null + + override val done: Boolean + get() = runSafe { contexts.all { blockState(it.blockPos).isEmpty } } == true + + @DslMarker + annotation class BreakRequestDsl @BreakRequestDsl override fun submit(queueIfMismatchedStage: Boolean) = BreakManager.request(this, queueIfMismatchedStage) - @BreakRequestDsl - class BreakRequestBuilder( - contexts: Collection, - pendingInteractions: MutableCollection, - nowOrNothing: Boolean, - automated: Automated - ) { - val request = BreakRequest(contexts, pendingInteractions, automated, nowOrNothing) - - @BreakRequestDsl - fun onStart(callback: SafeContext.(BlockPos) -> Unit) { - request.onStart = callback - } - - @BreakRequestDsl - fun onUpdate(callback: SafeContext.(BlockPos) -> Unit) { - request.onUpdate = callback - } - - @BreakRequestDsl - fun onStop(callback: SafeContext.(BlockPos) -> Unit) { - request.onStop = callback - } - - @BreakRequestDsl - fun onCancel(callback: SafeContext.(BlockPos) -> Unit) { - request.onCancel = callback - } - - @BreakRequestDsl - fun onItemDrop(callback: SafeContext.(ItemEntity) -> Unit) { - request.onItemDrop = callback - } - - @BreakRequestDsl - fun onReBreakStart(callback: SafeContext.(BlockPos) -> Unit) { - request.onReBreakStart = callback - } - - @BreakRequestDsl - fun onReBreak(callback: SafeContext.(BlockPos) -> Unit) { - request.onReBreak = callback - } - } - - companion object { - var requestCount = 0 - - @BreakRequestDsl - @JvmName("breakRequest1") - fun AutomatedSafeContext.breakRequest( + @BreakRequestDsl + class BreakRequestBuilder( + contexts: Collection, + pendingInteractions: MutableCollection, + nowOrNothing: Boolean, + automated: Automated + ) { + val request = BreakRequest(contexts, pendingInteractions, automated, nowOrNothing) + + @BreakRequestDsl + fun onStart(callback: SafeContext.(BlockPos) -> Unit) { + request.onStart = callback + } + + @BreakRequestDsl + fun onUpdate(callback: SafeContext.(BlockPos) -> Unit) { + request.onUpdate = callback + } + + @BreakRequestDsl + fun onStop(callback: SafeContext.(BlockPos) -> Unit) { + request.onStop = callback + } + + @BreakRequestDsl + fun onCancel(callback: SafeContext.(BlockPos) -> Unit) { + request.onCancel = callback + } + + @BreakRequestDsl + fun onItemDrop(callback: SafeContext.(ItemEntity) -> Unit) { + request.onItemDrop = callback + } + + @BreakRequestDsl + fun onReBreakStart(callback: SafeContext.(BlockPos) -> Unit) { + request.onReBreakStart = callback + } + + @BreakRequestDsl + fun onReBreak(callback: SafeContext.(BlockPos) -> Unit) { + request.onReBreak = callback + } + } + + companion object { + var requestCount = 0 + + @BreakRequestDsl + @JvmName("breakRequest1") + fun AutomatedSafeContext.breakRequest( positions: Collection, pendingInteractions: MutableCollection, nowOrNothing: Boolean = false, builder: (BreakRequestBuilder.() -> Unit)? = null ) = positions .associateWith { TargetState.Empty } - .simulate() - .breakRequest(pendingInteractions, nowOrNothing, builder) + .simulate() + .breakRequest(pendingInteractions, nowOrNothing, builder) - @BreakRequestDsl - @JvmName("breakRequest2") - context(automated: Automated) - fun Collection.breakRequest( + @BreakRequestDsl + @JvmName("breakRequest2") + context(automated: Automated) + fun Collection.breakRequest( pendingInteractions: MutableCollection, nowOrNothing: Boolean = false, builder: (BreakRequestBuilder.() -> Unit)? = null ) = asSequence() - .map { if (it is Dependent) it.lastDependency else it } - .filterIsInstance() - .sorted() + .map { if (it is Dependent) it.lastDependency else it } + .filterIsInstance() + .sorted() .map { it.context } - .toSet() - .takeIf { it.isNotEmpty() } - ?.let { automated.breakRequest(it, pendingInteractions, nowOrNothing, builder) } - - @BreakRequestDsl - @JvmName("breakRequest3") - fun Automated.breakRequest( - contexts: Collection, - pendingInteractions: MutableCollection, - nowOrNothing: Boolean = false, - builder: (BreakRequestBuilder.() -> Unit)? = null - ) = BreakRequestBuilder( - contexts, pendingInteractions, nowOrNothing, this + .toSet() + .takeIf { it.isNotEmpty() } + ?.let { automated.breakRequest(it, pendingInteractions, nowOrNothing, builder) } + + @BreakRequestDsl + @JvmName("breakRequest3") + fun Automated.breakRequest( + contexts: Collection, + pendingInteractions: MutableCollection, + nowOrNothing: Boolean = false, + builder: (BreakRequestBuilder.() -> Unit)? = null + ) = BreakRequestBuilder( + contexts, pendingInteractions, nowOrNothing, this ).apply { builder?.invoke(this) }.build() - @BreakRequestDsl - private fun BreakRequestBuilder.build(): BreakRequest = request - } + @BreakRequestDsl + private fun BreakRequestBuilder.build(): BreakRequest = request + } } diff --git a/src/main/kotlin/com/lambda/interaction/managers/breaking/BrokenBlockHandler.kt b/src/main/kotlin/com/lambda/interaction/managers/breaking/BrokenBlockHandler.kt index 5188dcfee..cd88756cc 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/breaking/BrokenBlockHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/breaking/BrokenBlockHandler.kt @@ -49,119 +49,116 @@ import net.minecraft.util.math.ChunkSectionPos * @see BreakManager */ object BrokenBlockHandler : PostActionHandler() { - override val pendingActions = LimitedDecayQueue( - DEFAULT.buildConfig.maxPendingActions, DEFAULT.buildConfig.actionTimeout * 50L - ) { info -> - runSafe { - val pos = info.context.blockPos - val loaded = - world.isChunkLoaded(ChunkSectionPos.getSectionCoord(pos.x), ChunkSectionPos.getSectionCoord(pos.z)) - if (!loaded) return@runSafe - - if (!info.broken) { - val message = "${info.type} ${info::class.simpleName} at ${info.context.blockPos.toShortString()} timed out with cached state ${info.context.cachedState}" - BreakManager.logger.error(message) - if (managerDebugLogs) this@BrokenBlockHandler.warn(message) - } else if (!DEFAULT.ignoreItemDropWarnings) { - val message = "${info.type} ${info::class.simpleName}'s item drop at ${info.context.blockPos.toShortString()} timed out" - BreakManager.logger.warning(message) - if (managerDebugLogs) this@BrokenBlockHandler.warn(message) - } - - if (!info.broken && info.breakConfig.breakConfirmation != BreakConfirmationMode.AwaitThenBreak) { - world.setBlockState(info.context.blockPos, info.context.cachedState) - } - - info.request.onCancel?.invoke(this, info.context.blockPos) - } - info.pendingInteractionsList.remove(info.context) - } - - init { - listen(priority = Int.MIN_VALUE) { event -> - run { - pendingActions.firstOrNull { it.context.blockPos == event.pos } - ?: if (rebreak?.context?.blockPos == event.pos) rebreak - else null - }?.let { pending -> - val currentState = pending.context.cachedState - // return if the block's not broken - if (isNotBroken(currentState, event.newState)) { - // return if the state hasn't changed - if (event.newState.matches(currentState, ProcessorRegistry.postProcessedProperties)) { - pending.context.cachedState = event.newState - return@listen - } - - if (pending.type == BreakInfo.BreakType.Rebreak) { - pending.context.cachedState = event.newState - } else { - val message = "Broken block at ${event.pos.toShortString()} was rejected with ${event.newState} instead of ${pending.context.cachedState.emptyState}" - BreakManager.logger.error(message) - if (managerDebugLogs) this@BrokenBlockHandler.warn(message) - pending.stopPending() - } - return@listen - } - - if (pending.breakConfig.breakConfirmation == BreakConfirmationMode.AwaitThenBreak - || (pending.type == BreakInfo.BreakType.Rebreak && !pending.breakConfig.rebreak) - ) { - destroyBlock(pending) - } - pending.internalOnBreak() - if (pending.callbacksCompleted) { - pending.stopPending() - if (lastPosStarted == pending.context.blockPos) { - RebreakHandler.offerRebreak(pending) - } - } - return@listen - } - } - - listen(priority = Int.MIN_VALUE) { - if (it.entity !is ItemEntity) return@listen - val pending = - pendingActions.firstOrNull { info -> matchesBlockItem(info, it.entity) } - ?: rebreak?.let { info -> - if (matchesBlockItem(info, it.entity)) info - else return@listen - } ?: return@listen - - pending.internalOnItemDrop(it.entity) - if (pending.callbacksCompleted) { - pending.stopPending() - if (lastPosStarted == pending.context.blockPos) { - RebreakHandler.offerRebreak(pending) - } - } - } - } - - /** - * A modified version of the minecraft breakBlock method. - * - * Performs the actions required to display breaking particles, sounds, texture overlay, etc. - * based on the user's settings. - * - * @see net.minecraft.client.world.ClientWorld.breakBlock - */ - fun SafeContext.destroyBlock(info: BreakInfo) { - val ctx = info.context - - if (player.isBlockBreakingRestricted(world, ctx.blockPos, gamemode)) return - if (!player.mainHandStack.canMine(ctx.cachedState, world, ctx.blockPos, player)) return - - val block = ctx.cachedState.block - if (block is OperatorBlock && !player.isCreativeLevelTwoOp) return - if (ctx.cachedState.isEmpty) return - - block.onBreak(world, ctx.blockPos, ctx.cachedState, player) - val fluidState = fluidState(ctx.blockPos) - val setState = world.setBlockState(ctx.blockPos, fluidState.blockState, 11) - if (setState) block.onBroken(world, ctx.blockPos, ctx.cachedState) - - if (info.breakConfig.breakingTexture) info.setBreakingTextureStage(player, world, -1) - } + override val pendingActions = LimitedDecayQueue( + DEFAULT.buildConfig.maxPendingActions, DEFAULT.buildConfig.actionTimeout * 50L + ) { info -> + runSafe { + val pos = info.context.blockPos + val loaded = + world.isChunkLoaded(ChunkSectionPos.getSectionCoord(pos.x), ChunkSectionPos.getSectionCoord(pos.z)) + if (!loaded) return@runSafe + + if (!info.broken) { + val message = "${info.type} ${info::class.simpleName} at ${info.context.blockPos.toShortString()} timed out with cached state ${info.context.cachedState}" + if (managerDebugLogs) this@BrokenBlockHandler.warn(message) + } else if (!DEFAULT.ignoreItemDropWarnings) { + val message = "${info.type} ${info::class.simpleName}'s item drop at ${info.context.blockPos.toShortString()} timed out" + if (managerDebugLogs) this@BrokenBlockHandler.warn(message) + } + + if (!info.broken && info.breakConfig.breakConfirmation != BreakConfirmationMode.AwaitThenBreak) { + world.setBlockState(info.context.blockPos, info.context.cachedState) + } + + info.request.onCancel?.invoke(this, info.context.blockPos) + } + info.pendingInteractionsList.remove(info.context) + } + + init { + listen(priority = Int.MIN_VALUE) { event -> + run { + pendingActions.firstOrNull { it.context.blockPos == event.pos } + ?: if (rebreak?.context?.blockPos == event.pos) rebreak + else null + }?.let { pending -> + val currentState = pending.context.cachedState + // return if the block's not broken + if (isNotBroken(currentState, event.newState)) { + // return if the state hasn't changed + if (event.newState.matches(currentState, ProcessorRegistry.postProcessedProperties)) { + pending.context.cachedState = event.newState + return@listen + } + + if (pending.type == BreakInfo.BreakType.Rebreak) { + pending.context.cachedState = event.newState + } else { + val message = "Broken block at ${event.pos.toShortString()} was rejected with ${event.newState} instead of ${pending.context.cachedState.emptyState}" + if (managerDebugLogs) this@BrokenBlockHandler.warn(message) + pending.stopPending() + } + return@listen + } + + if (pending.breakConfig.breakConfirmation == BreakConfirmationMode.AwaitThenBreak + || (pending.type == BreakInfo.BreakType.Rebreak && !pending.breakConfig.rebreak) + ) { + destroyBlock(pending) + } + pending.internalOnBreak() + if (pending.callbacksCompleted) { + pending.stopPending() + if (lastPosStarted == pending.context.blockPos) { + RebreakHandler.offerRebreak(pending) + } + } + return@listen + } + } + + listen(priority = Int.MIN_VALUE) { + if (it.entity !is ItemEntity) return@listen + val pending = + pendingActions.firstOrNull { info -> matchesBlockItem(info, it.entity) } + ?: rebreak?.let { info -> + if (matchesBlockItem(info, it.entity)) info + else return@listen + } ?: return@listen + + pending.internalOnItemDrop(it.entity) + if (pending.callbacksCompleted) { + pending.stopPending() + if (lastPosStarted == pending.context.blockPos) { + RebreakHandler.offerRebreak(pending) + } + } + } + } + + /** + * A modified version of the minecraft breakBlock method. + * + * Performs the actions required to display breaking particles, sounds, texture overlay, etc. + * based on the user's settings. + * + * @see net.minecraft.client.world.ClientWorld.breakBlock + */ + fun SafeContext.destroyBlock(info: BreakInfo) { + val ctx = info.context + + if (player.isBlockBreakingRestricted(world, ctx.blockPos, gamemode)) return + if (!player.mainHandStack.canMine(ctx.cachedState, world, ctx.blockPos, player)) return + + val block = ctx.cachedState.block + if (block is OperatorBlock && !player.isCreativeLevelTwoOp) return + if (ctx.cachedState.isEmpty) return + + block.onBreak(world, ctx.blockPos, ctx.cachedState, player) + val fluidState = fluidState(ctx.blockPos) + val setState = world.setBlockState(ctx.blockPos, fluidState.blockState, 11) + if (setState) block.onBroken(world, ctx.blockPos, ctx.cachedState) + + if (info.breakConfig.breakingTexture) info.setBreakingTextureStage(player, world, -1) + } } diff --git a/src/main/kotlin/com/lambda/interaction/managers/breaking/RebreakHandler.kt b/src/main/kotlin/com/lambda/interaction/managers/breaking/RebreakHandler.kt index 17bd3366d..9e41c666f 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/breaking/RebreakHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/breaking/RebreakHandler.kt @@ -35,104 +35,104 @@ import net.minecraft.util.Hand * the user to break any block placed in said position using the progress from the previously broken block. */ object RebreakHandler { - var rebreak: BreakInfo? = null + var rebreak: BreakInfo? = null - init { - listen(priority = Int.MIN_VALUE + 1) { - rebreak?.run { - if (!progressedThisTick) { - breakingTicks++ - progressedThisTick = true - } - } - } + init { + listen(priority = Int.MIN_VALUE + 1) { + rebreak?.run { + if (!progressedThisTick) { + breakingTicks++ + progressedThisTick = true + } + } + } - listenUnsafe(priority = Int.MIN_VALUE) { - rebreak = null - } - } + listenUnsafe(priority = Int.MIN_VALUE) { + rebreak = null + } + } - /** - * Tests to see if the [BreakInfo] can be accepted. If not, nothing happens. Otherwise, - * the [rebreak] is set, and the [BreakRequest.onReBreakStart] callback is invoked. - */ - context(safeContext: SafeContext) - fun offerRebreak(info: BreakInfo) { - if (!info.rebreakable) return + /** + * Tests to see if the [BreakInfo] can be accepted. If not, nothing happens. Otherwise, + * the [rebreak] is set, and the [BreakRequest.onReBreakStart] callback is invoked. + */ + context(safeContext: SafeContext) + fun offerRebreak(info: BreakInfo) { + if (!info.rebreakable) return - rebreak = info.apply { - type = BreakInfo.BreakType.Rebreak - breaking = true - resetCallbacks() - } - info.request.onReBreakStart?.invoke(safeContext, info.context.blockPos) - } + rebreak = info.apply { + type = BreakInfo.BreakType.Rebreak + breaking = true + resetCallbacks() + } + info.request.onReBreakStart?.invoke(safeContext, info.context.blockPos) + } - fun clearRebreak() { - rebreak = null - } + fun clearRebreak() { + rebreak = null + } - /** - * [RebreakPotential.None] if it cannot be rebroken at all. - * - * [RebreakPotential.PartialProgress] if some progress would be added to the break. - * - * [RebreakPotential.Instant] if the block can be instantly rebroken. - * - * @return In what way this block can be rebroken. - */ - context(_: SafeContext) - fun BreakInfo.getRebreakPotential() = request.runSafeAutomated { - rebreak?.let { reBreak -> - val stack = if (breakConfig.swapMode.isEnabled()) - swapStack - else player.mainHandStack - val breakDelta = context.cachedState.calcBreakDelta(context.blockPos, stack) - val possible = reBreak.breakConfig.rebreak && - context.blockPos == reBreak.context.blockPos - val instant = (reBreak.breakingTicks - breakConfig.fudgeFactor) * breakDelta >= breakConfig.breakThreshold - when { - possible && instant -> RebreakPotential.Instant - possible -> RebreakPotential.PartialProgress - else -> RebreakPotential.None - } - } ?: RebreakPotential.None - } + /** + * [RebreakPotential.None] if it cannot be rebroken at all. + * + * [RebreakPotential.PartialProgress] if some progress would be added to the break. + * + * [RebreakPotential.Instant] if the block can be instantly rebroken. + * + * @return In what way this block can be rebroken. + */ + context(_: SafeContext) + fun BreakInfo.getRebreakPotential() = request.runSafeAutomated { + rebreak?.let { reBreak -> + val stack = if (breakConfig.swapMode.isEnabled()) + swapStack + else player.mainHandStack + val breakDelta = context.cachedState.calcBreakDelta(context.blockPos, stack) + val possible = reBreak.breakConfig.rebreak && + context.blockPos == reBreak.context.blockPos + val instant = (reBreak.breakingTicks - breakConfig.fudgeFactor) * breakDelta >= breakConfig.breakThreshold + when { + possible && instant -> RebreakPotential.Instant + possible -> RebreakPotential.PartialProgress + else -> RebreakPotential.None + } + } ?: RebreakPotential.None + } - /** - * Updates the current [rebreak] with a fresh [BreakContext], and attempts to rebreak the block if possible. - * - * @return A [RebreakResult] to indicate how the update has been processed. - */ - context(_: SafeContext) - fun handleUpdate(ctx: BreakContext, breakRequest: BreakRequest) = breakRequest.runSafeAutomated { - val reBreak = this@RebreakHandler.rebreak ?: return@runSafeAutomated RebreakResult.Ignored + /** + * Updates the current [rebreak] with a fresh [BreakContext], and attempts to rebreak the block if possible. + * + * @return A [RebreakResult] to indicate how the update has been processed. + */ + context(_: SafeContext) + fun handleUpdate(ctx: BreakContext, breakRequest: BreakRequest) = breakRequest.runSafeAutomated { + val reBreak = this@RebreakHandler.rebreak ?: return@runSafeAutomated RebreakResult.Ignored - reBreak.updateInfo(ctx, breakRequest) + reBreak.updateInfo(ctx, breakRequest) - val context = reBreak.context - val breakDelta = context.cachedState.calcBreakDelta(context.blockPos) - val breakTicks = reBreak.breakingTicks - breakConfig.fudgeFactor - return@runSafeAutomated if (breakTicks * breakDelta >= reBreak.getBreakThreshold()) { - if (breakConfig.breakConfirmation != BreakConfig.BreakConfirmationMode.AwaitThenBreak) { - destroyBlock(reBreak) - } - reBreak.stopBreakPacket() - if (breakConfig.swing.isEnabled()) { - swingHand(breakConfig.swingType, Hand.MAIN_HAND) - } - BreakManager.breaksThisTick++ - RebreakResult.Rebroke - } else { - RebreakResult.StillBreaking(reBreak) - } - } + val context = reBreak.context + val breakDelta = context.cachedState.calcBreakDelta(context.blockPos) + val breakTicks = reBreak.breakingTicks - breakConfig.fudgeFactor + return@runSafeAutomated if (breakTicks * breakDelta >= reBreak.getBreakThreshold()) { + if (breakConfig.breakConfirmation != BreakConfig.BreakConfirmationMode.AwaitThenBreak) { + destroyBlock(reBreak) + } + reBreak.stopBreakPacket() + if (breakConfig.swing.isEnabled()) { + swingHand(breakConfig.swingType, Hand.MAIN_HAND) + } + BreakManager.breaksThisTick++ + RebreakResult.Rebroke + } else { + RebreakResult.StillBreaking(reBreak) + } + } - enum class RebreakPotential { - Instant, - PartialProgress, - None; + enum class RebreakPotential { + Instant, + PartialProgress, + None; - fun isPossible() = this != None - } + fun isPossible() = this != None + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/managers/breaking/RebreakResult.kt b/src/main/kotlin/com/lambda/interaction/managers/breaking/RebreakResult.kt index 2b4c1c05b..5726fe81c 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/breaking/RebreakResult.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/breaking/RebreakResult.kt @@ -18,11 +18,11 @@ package com.lambda.interaction.managers.breaking sealed class RebreakResult { - data object Ignored : RebreakResult() + data object Ignored : RebreakResult() - data object Rebroke : RebreakResult() + data object Rebroke : RebreakResult() - class StillBreaking( - val breakInfo: BreakInfo - ) : RebreakResult() + class StillBreaking( + val breakInfo: BreakInfo + ) : RebreakResult() } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/managers/breaking/SwapInfo.kt b/src/main/kotlin/com/lambda/interaction/managers/breaking/SwapInfo.kt index 89bb61e16..851e4f817 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/breaking/SwapInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/breaking/SwapInfo.kt @@ -17,11 +17,9 @@ package com.lambda.interaction.managers.breaking -import com.lambda.context.Automated import com.lambda.config.AutomationConfig.Companion.DEFAULT +import com.lambda.context.Automated import com.lambda.context.SafeContext -import com.lambda.interaction.managers.LogContext -import com.lambda.interaction.managers.LogContext.Companion.LogContextBuilder import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.Primary import com.lambda.interaction.managers.breaking.BreakInfo.BreakType.Secondary import com.lambda.interaction.managers.breaking.BreakManager.calcBreakDelta @@ -31,57 +29,50 @@ import com.lambda.threading.runSafeAutomated * A simple data class to store info about when the [BreakManager] should swap tool. */ data class SwapInfo( - private val type: BreakInfo.BreakType, - private val automated: Automated = DEFAULT, - val swap: Boolean = false, - val longSwap: Boolean = false -) : LogContext, Automated by automated { - override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = { - group("Swap Info") { - value("Type", type) - value("Swap", swap) - } - } - - companion object { - val EMPTY = SwapInfo(Primary) + private val type: BreakInfo.BreakType, + private val automated: Automated = DEFAULT, + val swap: Boolean = false, + val longSwap: Boolean = false +) : Automated by automated { + companion object { + val EMPTY = SwapInfo(Primary) - /** - * Calculates the contents and returns a [SwapInfo]. - */ - context(_: SafeContext) - fun BreakInfo.getSwapInfo() = request.runSafeAutomated { - val breakDelta = context.cachedState.calcBreakDelta(context.blockPos, swapStack) + /** + * Calculates the contents and returns a [SwapInfo]. + */ + context(_: SafeContext) + fun BreakInfo.getSwapInfo() = request.runSafeAutomated { + val breakDelta = context.cachedState.calcBreakDelta(context.blockPos, swapStack) - val threshold = getBreakThreshold() + val threshold = getBreakThreshold() - // Plus one as this is calculated before this ticks' progress is calculated and the breakingTicks are incremented - val breakTicks = (if (rebreakPotential.isPossible()) RebreakHandler.rebreak?.breakingTicks - ?: throw IllegalStateException("Rebreak BreakInfo was null when rebreak was considered possible") - else breakingTicks) + 1 - breakConfig.fudgeFactor + // Plus one as this is calculated before this ticks' progress is calculated and the breakingTicks are incremented + val breakTicks = (if (rebreakPotential.isPossible()) RebreakHandler.rebreak?.breakingTicks + ?: throw IllegalStateException("Rebreak BreakInfo was null when rebreak was considered possible") + else breakingTicks) + 1 - breakConfig.fudgeFactor - val swapAtEnd = run { - val swapTickProgress = if (type == Primary) - breakDelta * (breakTicks + breakConfig.serverSwapTicks - 1).coerceAtLeast(1) - else { - val serverSwapTicks = hotbarConfig.swapPause.coerceAtLeast(3) - breakDelta * (breakTicks + serverSwapTicks - 1).coerceAtLeast(1) - } - swapTickProgress >= threshold - } + val swapAtEnd = run { + val swapTickProgress = if (type == Primary) + breakDelta * (breakTicks + breakConfig.serverSwapTicks - 1).coerceAtLeast(1) + else { + val serverSwapTicks = hotbarConfig.swapPause.coerceAtLeast(3) + breakDelta * (breakTicks + serverSwapTicks - 1).coerceAtLeast(1) + } + swapTickProgress >= threshold + } - val swap = when (breakConfig.swapMode) { - BreakConfig.SwapMode.None -> false - BreakConfig.SwapMode.Start -> !breaking - BreakConfig.SwapMode.End -> swapAtEnd - BreakConfig.SwapMode.StartAndEnd -> !breaking || swapAtEnd - BreakConfig.SwapMode.Constant -> true - } + val swap = when (breakConfig.swapMode) { + BreakConfig.SwapMode.None -> false + BreakConfig.SwapMode.Start -> !breaking + BreakConfig.SwapMode.End -> swapAtEnd + BreakConfig.SwapMode.StartAndEnd -> !breaking || swapAtEnd + BreakConfig.SwapMode.Constant -> true + } - SwapInfo( - type, this, swap, - breakConfig.serverSwapTicks > 0 || (type == Secondary && swapAtEnd) - ) - } - } + SwapInfo( + type, this, swap, + breakConfig.serverSwapTicks > 0 || (type == Secondary && swapAtEnd) + ) + } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarConfig.kt b/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarConfig.kt index b8f173ac4..9dc64be9b 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarConfig.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarConfig.kt @@ -26,38 +26,38 @@ import com.lambda.event.events.TickEvent * @param priority The priority of this configuration. */ interface HotbarConfig : ISettingGroup { - val swapMode: SwapMode - /** - * The number of ticks to keep the current hotbar selection active. - */ - val keepTicks: Int - - /** - * The delay, in ticks, between swapping hotbar selections - */ - val swapDelay: Int - - /** - * The amount of hotbar selection swaps that can happen per tick - * - * Only makes a difference if swapDelay is set to 0 - */ - val swapsPerTick: Int - - /** - * The delay in ticks to pause actions after switching to the slot. - * - * Affects the validity state of the request - */ - val swapPause: Int - - /** - * The sub-tick timings at which hotbar actions can be performed - */ - val tickStageMask: Collection - - enum class SwapMode { - Temporary, - Permanent - } + val swapMode: SwapMode + /** + * The number of ticks to keep the current hotbar selection active. + */ + val keepTicks: Int + + /** + * The delay, in ticks, between swapping hotbar selections + */ + val swapDelay: Int + + /** + * The amount of hotbar selection swaps that can happen per tick + * + * Only makes a difference if swapDelay is set to 0 + */ + val swapsPerTick: Int + + /** + * The delay in ticks to pause actions after switching to the slot. + * + * Affects the validity state of the request + */ + val swapPause: Int + + /** + * The sub-tick timings at which hotbar actions can be performed + */ + val tickStageMask: Collection + + enum class SwapMode { + Temporary, + Permanent + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarManager.kt b/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarManager.kt index d8bfe17d0..330636480 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarManager.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarManager.kt @@ -19,21 +19,15 @@ package com.lambda.interaction.managers.hotbar import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext -import com.lambda.event.Event -import com.lambda.event.EventFlow.post import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.Logger import com.lambda.interaction.managers.Manager -import com.lambda.interaction.managers.ManagerUtils.newStage -import com.lambda.interaction.managers.ManagerUtils.newTick import com.lambda.interaction.managers.hotbar.HotbarConfig.SwapMode import com.lambda.interaction.managers.hotbar.HotbarManager.activeRequest import com.lambda.interaction.managers.hotbar.HotbarManager.activeSlot import com.lambda.interaction.managers.hotbar.HotbarManager.checkResetSwap import com.lambda.interaction.managers.hotbar.HotbarManager.setActiveRequest import com.lambda.interaction.managers.hotbar.HotbarManager.setActiveSlot -import com.lambda.module.hud.ManagerDebugLoggers.hotbarManagerLogger import com.lambda.threading.runSafe import net.minecraft.item.ItemStack @@ -46,140 +40,129 @@ import net.minecraft.item.ItemStack * After which, the manager will end the request and swap back to the player's selected slot. */ object HotbarManager : Manager( - 1, - onOpen = { - if (activeRequest != null) { - setActiveSlot() - HotbarManager.logger.newStage(HotbarManager.tickStage) - } - }, - onClose = { checkResetSwap() } -), Logger { - var activeRequest: HotbarRequest? = null - @JvmStatic var activeSlot: Int = -1 - - val serverSlot get() = runSafe { - interaction.lastSelectedSlot - } ?: -1 - //ToDo: something to manage stacks so the hotbar manager is strictly index based - private var previousStack: ItemStack? = null - private var swappedTicks = 0 - - private var swapsThisTick = 0 - private var maxSwapsThisTick = 0 - private var swapDelay = 0 - - override val logger = hotbarManagerLogger - - override fun load(): String { - super.load() - - listen(priority = Int.MAX_VALUE) { - if (activeRequest != null) - logger.newTick() - } - - listen(priority = Int.MIN_VALUE) { - swapsThisTick = 0 - if (swapDelay > 0) swapDelay-- - - val currentStack = player.mainHandStack - if (previousStack != currentStack) swappedTicks = 1 - else swappedTicks++ - previousStack = currentStack - - val activeInfo = activeRequest ?: return@listen - if (activeInfo.slot != activeSlot) return@listen - activeInfo.swapPauseAge = swappedTicks - activeInfo.activeRequestAge++ - activeInfo.keepTicks-- - } - - return "Loaded Hotbar Manager" - } - - /** - * If the [activeRequest] is not null, being kept, and the [request]'s slot matches the [activeSlot], the - * [request]'s swapPauseAge is set to the swapped ticks and the request is denied. Otherwise, the active - * request is set and it attempts to set the active slot. - * - * @see setActiveRequest - * @see setActiveSlot - */ - override fun AutomatedSafeContext.handleRequest(request: HotbarRequest) { - logger.debug("Handling request:", request) - - if (request.nowOrNothing && tickStage !in hotbarConfig.tickStageMask) return - - activeRequest?.let { active -> - if (active.activeRequestAge <= 0 && active.keepTicks > 0) { - if (activeSlot == request.slot) request.swapPauseAge = swappedTicks - return - } - } - - setActiveRequest(request) - if (!setActiveSlot() && request.nowOrNothing) { - activeRequest = null - activeSlot = -1 - } - } - - private fun AutomatedSafeContext.setActiveRequest(request: HotbarRequest) { - maxSwapsThisTick = hotbarConfig.swapsPerTick - activeRequest = request - logger.success("Set active request", request) - } - - /** - * Sets the [activeSlot]. This also calls syncSelectedSlot to - * update the server to keep predictability. - * - * @see net.minecraft.client.network.ClientPlayerInteractionManager.syncSelectedSlot - */ - private fun SafeContext.setActiveSlot(): Boolean { - activeRequest?.let { activeRequest -> - if (serverSlot != activeRequest.slot) { - if (tickStage !in activeRequest.hotbarConfig.tickStageMask) return false - if (swapsThisTick + 1 > maxSwapsThisTick || swapDelay > 0) return false - swapsThisTick++ - swappedTicks = 0 - swapDelay = activeRequest.hotbarConfig.swapDelay - } else activeRequest.swapPauseAge = swappedTicks - if (activeRequest.slot == activeSlot) return true - activeSlot = activeRequest.slot - if (activeRequest.hotbarConfig.swapMode == SwapMode.Permanent) - player.inventory.selectedSlot = activeRequest.slot - interaction.syncSelectedSlot() - } - return true - } - - /** - * Called after every [tickStage] closes. This method checks if the current [activeRequest] should be stopped. - * This action is counted as another swap, so the conditions for a regular swap must be met. If the requests - * [HotbarConfig.tickStageMask] does not contain the current tick stage, no actions can be performed. - * - * @see net.minecraft.client.network.ClientPlayerInteractionManager.syncSelectedSlot - */ - private fun SafeContext.checkResetSwap() { - activeRequest?.let { active -> - if (active.keepTicks <= 0) { - if (active.hotbarConfig.swapMode == SwapMode.Permanent) { - activeRequest = null - activeSlot = -1 - return - } - val canStopSwap = swapsThisTick < maxSwapsThisTick - if (tickStage in active.hotbarConfig.tickStageMask && canStopSwap) { - logger.debug("Clearing request and syncing slot", activeRequest) - val prevSlot = activeSlot - activeRequest = null - activeSlot = -1 - interaction.syncSelectedSlot() - if (serverSlot != prevSlot) swapsThisTick++ - } - } - } - } + 1, + onOpen = { + if (activeRequest != null) { + setActiveSlot() + } + }, + onClose = { checkResetSwap() } +) { + private var activeRequest: HotbarRequest? = null + @JvmStatic var activeSlot: Int = -1 + + val serverSlot get() = runSafe { + interaction.lastSelectedSlot + } ?: -1 + //ToDo: something to manage stacks so the hotbar manager is strictly index based + private var previousStack: ItemStack? = null + private var swappedTicks = 0 + + private var swapsThisTick = 0 + private var maxSwapsThisTick = 0 + private var swapDelay = 0 + + override fun load(): String { + super.load() + + listen(priority = Int.MIN_VALUE) { + swapsThisTick = 0 + if (swapDelay > 0) swapDelay-- + + val currentStack = player.mainHandStack + if (previousStack != currentStack) swappedTicks = 1 + else swappedTicks++ + previousStack = currentStack + + val activeInfo = activeRequest ?: return@listen + if (activeInfo.slot != activeSlot) return@listen + activeInfo.swapPauseAge = swappedTicks + activeInfo.activeRequestAge++ + activeInfo.keepTicks-- + } + + return "Loaded Hotbar Manager" + } + + /** + * If the [activeRequest] is not null, being kept, and the [request]'s slot matches the [activeSlot], the + * [request]'s swapPauseAge is set to the swapped ticks and the request is denied. Otherwise, the active + * request is set and it attempts to set the active slot. + * + * @see setActiveRequest + * @see setActiveSlot + */ + override fun AutomatedSafeContext.handleRequest(request: HotbarRequest) { + if (request.slot !in 0..8) return + if (request.nowOrNothing && tickStage !in hotbarConfig.tickStageMask) return + + activeRequest?.let { active -> + if (active.activeRequestAge <= 0 && active.keepTicks > 0) { + if (activeSlot == request.slot) request.swapPauseAge = swappedTicks + return + } + } + + setActiveRequest(request) + if (!setActiveSlot() && request.nowOrNothing) { + activeRequest = null + activeSlot = -1 + } + } + + private fun AutomatedSafeContext.setActiveRequest(request: HotbarRequest) { + maxSwapsThisTick = hotbarConfig.swapsPerTick + activeRequest = request + } + + /** + * Sets the [activeSlot]. This also calls syncSelectedSlot to + * update the server to keep predictability. + * + * @see net.minecraft.client.network.ClientPlayerInteractionManager.syncSelectedSlot + */ + private fun SafeContext.setActiveSlot(): Boolean { + activeRequest?.let { activeRequest -> + if (serverSlot != activeRequest.slot) { + if (tickStage !in activeRequest.hotbarConfig.tickStageMask) return false + if (swapsThisTick + 1 > maxSwapsThisTick || swapDelay > 0) return false + swapsThisTick++ + swappedTicks = 0 + swapDelay = activeRequest.hotbarConfig.swapDelay + } else activeRequest.swapPauseAge = swappedTicks + if (activeRequest.slot == activeSlot) return true + activeSlot = activeRequest.slot + if (activeRequest.hotbarConfig.swapMode == SwapMode.Permanent) + player.inventory.selectedSlot = activeRequest.slot + interaction.syncSelectedSlot() + } + return true + } + + /** + * Called after every [tickStage] closes. This method checks if the current [activeRequest] should be stopped. + * This action is counted as another swap, so the conditions for a regular swap must be met. If the requests + * [HotbarConfig.tickStageMask] does not contain the current tick stage, no actions can be performed. + * + * @see net.minecraft.client.network.ClientPlayerInteractionManager.syncSelectedSlot + */ + private fun SafeContext.checkResetSwap() { + activeRequest?.let { active -> + if (active.keepTicks <= 0) { + if (active.hotbarConfig.swapMode == SwapMode.Permanent) { + activeRequest = null + activeSlot = -1 + return + } + val canStopSwap = swapsThisTick < maxSwapsThisTick + if (tickStage in active.hotbarConfig.tickStageMask && canStopSwap) { + val prevSlot = activeSlot + activeRequest = null + activeSlot = -1 + interaction.syncSelectedSlot() + if (serverSlot != prevSlot) swapsThisTick++ + } + } + } + } } diff --git a/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarRequest.kt b/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarRequest.kt index 2c23715bb..bfd6548d0 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarRequest.kt @@ -18,41 +18,28 @@ package com.lambda.interaction.managers.hotbar import com.lambda.context.Automated -import com.lambda.interaction.managers.LogContext -import com.lambda.interaction.managers.LogContext.Companion.LogContextBuilder import com.lambda.interaction.managers.Request class HotbarRequest( - val slot: Int, - automated: Automated, - var keepTicks: Int = automated.hotbarConfig.keepTicks, - var swapPause: Int = automated.hotbarConfig.swapPause, - override val nowOrNothing: Boolean = true -) : Request(), LogContext, Automated by automated { - override val requestId = ++requestCount - override val tickStageMask get() = hotbarConfig.tickStageMask - - var activeRequestAge = 0 - var swapPauseAge = 0 - - override val done: Boolean - get() = slot == HotbarManager.activeSlot && swapPauseAge >= swapPause - - override fun submit(queueIfMismatchedStage: Boolean) = - HotbarManager.request(this, queueIfMismatchedStage) - - override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = { - group("Hotbar Request") { - value("Request ID", requestId) - value("Slot", slot) - value("Keep Ticks", keepTicks) - value("Swap Pause", swapPause) - value("Swap Pause Age", swapPauseAge) - value("Active Request Age", activeRequestAge) - } - } - - companion object { - var requestCount = 0 - } + val slot: Int, + automated: Automated, + var keepTicks: Int = automated.hotbarConfig.keepTicks, + val swapPause: Int = automated.hotbarConfig.swapPause, + override val nowOrNothing: Boolean = true +) : Request(), Automated by automated { + override val requestId = ++requestCount + override val tickStageMask get() = hotbarConfig.tickStageMask + + var activeRequestAge = 0 + var swapPauseAge = 0 + + override val done: Boolean + get() = slot == HotbarManager.activeSlot && swapPauseAge >= swapPause + + override fun submit(queueIfMismatchedStage: Boolean) = + HotbarManager.request(this, queueIfMismatchedStage) + + companion object { + var requestCount = 0 + } } diff --git a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractConfig.kt b/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractConfig.kt index dbbf34f12..757aa53c7 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractConfig.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractConfig.kt @@ -24,35 +24,35 @@ import com.lambda.util.Describable import com.lambda.util.NamedEnum interface InteractConfig : ActionConfig, ISettingGroup { - val rotate: Boolean - val airPlace: AirPlaceMode - val axisRotateSetting: Boolean - val axisRotate get() = rotate && airPlace.isEnabled && axisRotateSetting - val interactConfirmationMode: InteractConfirmationMode - val interactDelay: Int - val interactionsPerTick: Int - val swing: Boolean - val swingType: BuildConfig.SwingType - val sounds: Boolean + val rotate: Boolean + val airPlace: AirPlaceMode + val axisRotateSetting: Boolean + val axisRotate get() = rotate && airPlace.isEnabled && axisRotateSetting + val interactConfirmationMode: InteractConfirmationMode + val interactDelay: Int + val interactionsPerTick: Int + val swing: Boolean + val swingType: BuildConfig.SwingType + val sounds: Boolean - enum class AirPlaceMode( - override val displayName: String, - override val description: String - ) : NamedEnum, Describable { - None("None", "Do not attempt air placements; only place against valid supports."), - Standard("Standard", "Try common air-place techniques for convenience; moderate compatibility."), - Grim("Grim", "Use grim specific air placing.") - ; + enum class AirPlaceMode( + override val displayName: String, + override val description: String + ) : NamedEnum, Describable { + None("None", "Do not attempt air placements; only place against valid supports."), + Standard("Standard", "Try common air-place techniques for convenience; moderate compatibility."), + Grim("Grim", "Use grim specific air placing.") + ; - val isEnabled get() = this != None - } + val isEnabled get() = this != None + } - enum class InteractConfirmationMode( - override val displayName: String, - override val description: String - ) : NamedEnum, Describable { - None("No confirmation", "Interact immediately without waiting for the server; possible desync."), - PlaceThenAwait("Interact now, confirm later", "Interact immediately, then wait for server confirmation to verify."), - AwaitThenPlace("Confirm first, then Interact", "Wait for server response before interacting; safest, adds a short delay.") - } + enum class InteractConfirmationMode( + override val displayName: String, + override val description: String + ) : NamedEnum, Describable { + None("No confirmation", "Interact immediately without waiting for the server; possible desync."), + PlaceThenAwait("Interact now, confirm later", "Interact immediately, then wait for server confirmation to verify."), + AwaitThenPlace("Confirm first, then Interact", "Wait for server response before interacting; safest, adds a short delay.") + } } diff --git a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractInfo.kt b/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractInfo.kt index e9826d895..782a71e7a 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractInfo.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractInfo.kt @@ -21,8 +21,6 @@ import com.lambda.context.SafeContext import com.lambda.interaction.construction.simulation.context.BuildContext import com.lambda.interaction.construction.simulation.context.InteractContext import com.lambda.interaction.managers.ActionInfo -import com.lambda.interaction.managers.LogContext -import com.lambda.interaction.managers.LogContext.Companion.LogContextBuilder import net.minecraft.util.math.BlockPos data class InteractInfo( @@ -30,13 +28,4 @@ data class InteractInfo( override val pendingInteractionsList: MutableCollection, val onPlace: (SafeContext.(BlockPos) -> Unit)?, val interactConfig: InteractConfig -) : ActionInfo, LogContext { - override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = { - group("Place Info") { - text(context.getLogContextBuilder()) - group("Callbacks") { - value("onPlace", onPlace != null) - } - } - } -} \ No newline at end of file +) : ActionInfo \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractManager.kt b/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractManager.kt index d1940fe39..5559ac00b 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractManager.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractManager.kt @@ -26,11 +26,8 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe import com.lambda.interaction.construction.simulation.context.InteractContext -import com.lambda.interaction.managers.Logger import com.lambda.interaction.managers.Manager import com.lambda.interaction.managers.ManagerUtils.isPosBlocked -import com.lambda.interaction.managers.ManagerUtils.newStage -import com.lambda.interaction.managers.ManagerUtils.newTick import com.lambda.interaction.managers.PositionBlocking import com.lambda.interaction.managers.breaking.BreakManager import com.lambda.interaction.managers.interacting.InteractManager.activeRequest @@ -42,7 +39,6 @@ import com.lambda.interaction.managers.interacting.InteractedBlockHandler.pendin import com.lambda.interaction.managers.interacting.InteractedBlockHandler.setPendingConfigs import com.lambda.interaction.managers.interacting.InteractedBlockHandler.startPending import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest -import com.lambda.module.hud.ManagerDebugLoggers.placeManagerLogger import com.lambda.threading.runSafeAutomated import com.lambda.util.BlockUtils.blockState import com.lambda.util.item.ItemUtils.blockItem @@ -67,337 +63,287 @@ import net.minecraft.util.math.BlockPos import net.minecraft.world.GameMode object InteractManager : Manager( - 0, - onOpen = { - if (potentialPlacements.isNotEmpty()) - InteractManager.logger.newStage(InteractManager.tickStage) - activeRequest?.let { it.runSafeAutomated { processRequest(it) } } - } -), PositionBlocking, Logger { - private var activeRequest: InteractRequest? = null - private var potentialPlacements = mutableListOf() + 0, + onOpen = { activeRequest?.let { it.runSafeAutomated { processRequest(it) } } } +), PositionBlocking { + private var activeRequest: InteractRequest? = null + private var potentialPlacements = mutableListOf() private var interactCooldown = 0 - private var placementsThisTick = 0 - private var maxPlacementsThisTick = 0 + private var placementsThisTick = 0 + private var maxPlacementsThisTick = 0 - private var shouldSneak = false - private val validSneak: (player: ClientPlayerEntity) -> Boolean = - { player -> !shouldSneak || player.isSneaking } + private var shouldSneak = false + private val ClientPlayerEntity.validSneak get() = isSneaking == shouldSneak - override val blockedPositions - get() = pendingActions.map { it.context.blockPos } + override val blockedPositions + get() = pendingActions.map { it.context.blockPos } - override val logger = placeManagerLogger + override fun load(): String { + super.load() - override fun load(): String { - super.load() - - listen(priority = Int.MAX_VALUE) { - if (potentialPlacements.isNotEmpty()) - logger.newTick() - } - - listen(priority = Int.MIN_VALUE) { - activeRequest = null - placementsThisTick = 0 - potentialPlacements.clear() - if (interactCooldown > 0) { + listen(priority = Int.MIN_VALUE) { + activeRequest = null + placementsThisTick = 0 + potentialPlacements.clear() + if (interactCooldown > 0) { interactCooldown-- } - } - - listen(priority = Int.MIN_VALUE) { - if (shouldSneak) { - shouldSneak = false - it.input.sneaking = true - } - } - - listenUnsafe(priority = Int.MIN_VALUE) { - interactCooldown = 0 - } - - return "Loaded Place Manager" - } - - /** - * Accepts, and processes the request, as long as the current [activeRequest] is null, and the [BreakManager] has not - * been active this tick. If nowOrNothing is true, the request is cleared after the first process. - * - * @see processRequest - */ - override fun AutomatedSafeContext.handleRequest(request: InteractRequest) { - if (activeRequest != null || request.contexts.isEmpty()) return - if (BreakManager.activeThisTick) return - - activeRequest = request - processRequest(request) - if (request.nowOrNothing) { - activeRequest = null - potentialPlacements = mutableListOf() - } - if (placementsThisTick > 0) activeThisTick = true - } - - /** - * Returns immediately if [BreakManager] or [InteractManager] have been active this tick. - * Otherwise, for fresh requests, [populateFrom] is called to fill the [potentialPlacements] collection. - * It then attempts to perform as many placements as possible from the [potentialPlacements] collection within - * the [maxPlacementsThisTick] limit. - * - * @see populateFrom - * @see interactBlock - */ - fun AutomatedSafeContext.processRequest(request: InteractRequest) { - logger.debug("Processing request", request) - - if (request.fresh) populateFrom(request) - - val iterator = potentialPlacements.iterator() - while (iterator.hasNext()) { + } + + listen(priority = Int.MIN_VALUE) { + if (shouldSneak) { + shouldSneak = false + it.input.sneaking = true + } + } + + listenUnsafe(priority = Int.MIN_VALUE) { + interactCooldown = 0 + } + + return "Loaded Place Manager" + } + + /** + * Accepts, and processes the request, as long as the current [activeRequest] is null, and the [BreakManager] has not + * been active this tick. If nowOrNothing is true, the request is cleared after the first process. + * + * @see processRequest + */ + override fun AutomatedSafeContext.handleRequest(request: InteractRequest) { + if (!request.buildConfig.interactBlocks || activeRequest != null || request.contexts.isEmpty()) return + if (BreakManager.activeThisTick) return + + activeRequest = request + processRequest(request) + if (request.nowOrNothing) { + activeRequest = null + potentialPlacements = mutableListOf() + } + if (placementsThisTick > 0) activeThisTick = true + } + + /** + * Returns immediately if [BreakManager] or [InteractManager] have been active this tick. + * Otherwise, for fresh requests, [populateFrom] is called to fill the [potentialPlacements] collection. + * It then attempts to perform as many placements as possible from the [potentialPlacements] collection within + * the [maxPlacementsThisTick] limit. + * + * @see populateFrom + * @see interactBlock + */ + fun AutomatedSafeContext.processRequest(request: InteractRequest) { + if (request.fresh) populateFrom(request) + + val iterator = potentialPlacements.iterator() + while (iterator.hasNext()) { if (interactCooldown > 0) break - if (placementsThisTick + 1 > maxPlacementsThisTick) break - val ctx = iterator.next() - - if (ctx.sneak) shouldSneak = true - if (!ctx.requestDependencies(request)) { - logger.warning("Dependencies failed for context", ctx, request) - return - } - if (!validSneak(player)) return - if (tickStage !in interactConfig.tickStageMask) return - - val actionResult = if (ctx.placing) placeBlock(ctx, request, Hand.MAIN_HAND) - else interaction.interactBlock(player, Hand.MAIN_HAND, ctx.hitResult) - if (!actionResult.isAccepted) { - logger.warning("Placement interaction failed with $actionResult", ctx, request) - } else if (interactConfig.swing) { - swingHand(interactConfig.swingType, Hand.MAIN_HAND) - - val stackInHand = player.getStackInHand(Hand.MAIN_HAND) - val stackCountPre = stackInHand.count - if (!stackInHand.isEmpty && (stackInHand.count != stackCountPre || player.isInCreativeMode)) { - mc.gameRenderer.firstPersonRenderer.resetEquipProgress(Hand.MAIN_HAND) - } - } - interactCooldown = ctx.interactConfig.interactDelay + 1 - placementsThisTick++ - iterator.remove() - } - if (potentialPlacements.isEmpty()) { - if (activeRequest != null) { - logger.debug("Clearing active request", activeRequest) - activeRequest = null - } - } - } - - /** - * Filters the [request]'s [InteractContext]s, placing them into the [potentialPlacements] collection, and - * setting other configurations. - * - * @see isPosBlocked - */ - private fun Automated.populateFrom(request: InteractRequest) { - logger.debug("Populating from request", request) - setPendingConfigs() - potentialPlacements = request.contexts - .distinctBy { it.blockPos } - .filter { !isPosBlocked(it.blockPos) } - .take(buildConfig.maxPendingActions - request.pendingInteractions.size.coerceAtLeast(0)) - .toMutableList() - logger.debug("${potentialPlacements.size} potential placements") - - maxPlacementsThisTick = interactConfig.interactionsPerTick - } - - /** - * A modified version of the minecraft interactBlock method, - * renamed to better suit its usage. - * - * @see net.minecraft.client.network.ClientPlayerInteractionManager.interactBlock - */ - private fun AutomatedSafeContext.placeBlock(interactContext: InteractContext, request: InteractRequest, hand: Hand): ActionResult { - interaction.syncSelectedSlot() - val hitResult = interactContext.hitResult - if (!world.worldBorder.contains(hitResult.blockPos)) { - logger.error("Placement position outside the world border", interactContext, request) - return ActionResult.FAIL - } - if (gamemode == GameMode.SPECTATOR) { - logger.error("Player is in spectator mode", interactContext, request) - return ActionResult.PASS - } - return interactBlockInternal(interactContext, request, hand, hitResult) - } - - /** - * A modified version of the minecraft interactBlockInternal method. - * - * @see net.minecraft.client.network.ClientPlayerInteractionManager.interactBlockInternal - */ - private fun AutomatedSafeContext.interactBlockInternal( - interactContext: InteractContext, - request: InteractRequest, - hand: Hand, - hitResult: BlockHitResult - ): ActionResult { - val handNotEmpty = player.getStackInHand(hand).isEmpty.not() - val cantInteract = player.shouldCancelInteraction() && handNotEmpty - if (!cantInteract) { - val blockState = blockState(hitResult.blockPos) - if (!connection.hasFeature(blockState.block.requiredFeatures)) { - logger.error("Required features not met for $blockState", interactContext, request) - return ActionResult.FAIL - } - - val actionResult = blockState.onUseWithItem(player.getStackInHand(hand), world, player, hand, hitResult) - if (actionResult.isAccepted) return actionResult - - if (actionResult is PassToDefaultBlockAction && hand == Hand.MAIN_HAND) { - val actionResult2 = blockState.onUse(world, player, hitResult) - if (actionResult2.isAccepted) return actionResult2 - } - } - - val stack = player.mainHandStack - - if (!stack.isEmpty && !isItemOnCooldown(stack)) { - val itemUsageContext = ItemUsageContext(player, hand, hitResult) - return if (gamemode.isCreative) { - val i = stack.count - useOnBlock(interactContext, request, hand, hitResult, stack, itemUsageContext) - .also { stack.count = i } - } else useOnBlock(interactContext, request, hand, hitResult, stack, itemUsageContext) - } - return ActionResult.PASS - } - - /** - * A modified version of the minecraft useOnBlock method. - * - * @see net.minecraft.item.Item.useOnBlock - */ - private fun AutomatedSafeContext.useOnBlock( - interactContext: InteractContext, - request: InteractRequest, - hand: Hand, - hitResult: BlockHitResult, - itemStack: ItemStack, - context: ItemUsageContext - ): ActionResult { - val blockPos = context.blockPos - return if (!player.abilities.allowModifyWorld && - !itemStack.canPlaceOn(CachedBlockPosition(world, blockPos, false)) + if (placementsThisTick + 1 > maxPlacementsThisTick) break + val ctx = iterator.next() + + shouldSneak = ctx.sneak + if (!ctx.requestDependencies(request)) return + if (!player.validSneak) return + if (tickStage !in interactConfig.tickStageMask) return + + val actionResult = if (ctx.preProcessingInfo.placing) placeBlock(ctx, request, Hand.MAIN_HAND) + else interaction.interactBlock(player, Hand.MAIN_HAND, ctx.hitResult) + if (actionResult.isAccepted && interactConfig.swing) { + swingHand(interactConfig.swingType, Hand.MAIN_HAND) + + val stackInHand = player.getStackInHand(Hand.MAIN_HAND) + val stackCountPre = stackInHand.count + if (!stackInHand.isEmpty && (stackInHand.count != stackCountPre || player.isInCreativeMode)) { + mc.gameRenderer.firstPersonRenderer.resetEquipProgress(Hand.MAIN_HAND) + } + } + interactCooldown = ctx.interactConfig.interactDelay + 1 + placementsThisTick++ + iterator.remove() + } + if (potentialPlacements.isEmpty()) { + if (activeRequest != null) activeRequest = null + } + } + + /** + * Filters the [request]'s [InteractContext]s, placing them into the [potentialPlacements] collection, and + * setting other configurations. + * + * @see isPosBlocked + */ + private fun Automated.populateFrom(request: InteractRequest) { + setPendingConfigs() + potentialPlacements = request.contexts + .distinctBy { it.blockPos } + .filter { !isPosBlocked(it.blockPos) } + .take(buildConfig.maxPendingActions - request.pendingInteractions.size.coerceAtLeast(0)) + .toMutableList() + + maxPlacementsThisTick = interactConfig.interactionsPerTick + } + + /** + * A modified version of the minecraft interactBlock method, + * renamed to better suit its usage. + * + * @see net.minecraft.client.network.ClientPlayerInteractionManager.interactBlock + */ + private fun AutomatedSafeContext.placeBlock(interactContext: InteractContext, request: InteractRequest, hand: Hand): ActionResult { + interaction.syncSelectedSlot() + val hitResult = interactContext.hitResult + if (!world.worldBorder.contains(hitResult.blockPos)) return ActionResult.FAIL + if (gamemode == GameMode.SPECTATOR) return ActionResult.PASS + return interactBlockInternal(interactContext, request, hand, hitResult) + } + + /** + * A modified version of the minecraft interactBlockInternal method. + * + * @see net.minecraft.client.network.ClientPlayerInteractionManager.interactBlockInternal + */ + private fun AutomatedSafeContext.interactBlockInternal( + interactContext: InteractContext, + request: InteractRequest, + hand: Hand, + hitResult: BlockHitResult + ): ActionResult { + val handNotEmpty = player.getStackInHand(hand).isEmpty.not() + val cantInteract = player.shouldCancelInteraction() && handNotEmpty + if (!cantInteract) { + val blockState = blockState(hitResult.blockPos) + if (!connection.hasFeature(blockState.block.requiredFeatures)) return ActionResult.FAIL + + val actionResult = blockState.onUseWithItem(player.getStackInHand(hand), world, player, hand, hitResult) + if (actionResult.isAccepted) return actionResult + + if (actionResult is PassToDefaultBlockAction && hand == Hand.MAIN_HAND) { + val actionResult2 = blockState.onUse(world, player, hitResult) + if (actionResult2.isAccepted) return actionResult2 + } + } + + val stack = player.mainHandStack + + if (!stack.isEmpty && !isItemOnCooldown(stack)) { + val itemUsageContext = ItemUsageContext(player, hand, hitResult) + return if (gamemode.isCreative) { + val i = stack.count + useOnBlock(interactContext, request, hand, hitResult, stack, itemUsageContext) + .also { stack.count = i } + } else useOnBlock(interactContext, request, hand, hitResult, stack, itemUsageContext) + } + return ActionResult.PASS + } + + /** + * A modified version of the minecraft useOnBlock method. + * + * @see net.minecraft.item.Item.useOnBlock + */ + private fun AutomatedSafeContext.useOnBlock( + interactContext: InteractContext, + request: InteractRequest, + hand: Hand, + hitResult: BlockHitResult, + itemStack: ItemStack, + context: ItemUsageContext + ): ActionResult { + val blockPos = context.blockPos + return if (!player.abilities.allowModifyWorld && + !itemStack.canPlaceOn(CachedBlockPosition(world, blockPos, false)) ) { ActionResult.PASS - } else { - val item = itemStack.blockItem - place(interactContext, request, hand, hitResult, item, ItemPlacementContext(context)) - } - } - - /** - * A modified version of the minecraft place method. - * - * @see net.minecraft.item.BlockItem.place - */ - private fun AutomatedSafeContext.place( - interactContext: InteractContext, - request: InteractRequest, - hand: Hand, - hitResult: BlockHitResult, - item: BlockItem, - context: ItemPlacementContext - ): ActionResult { - if (!item.block.isEnabled(world.enabledFeatures)) { - logger.error("Block ${item.block.name} is not enabled", interactContext, request) - return ActionResult.FAIL - } - if (!context.canPlace()) { - logger.error("Cannot place at ${interactContext.blockPos} with current state ${interactContext.cachedState}", interactContext, request) - return ActionResult.FAIL - } - - val itemPlacementContext = item.getPlacementContext(context) ?: run { - logger.error("Could not retrieve item placement context", interactContext, request) - return ActionResult.FAIL - } - val blockState = item.getPlacementState(itemPlacementContext) ?: run { - logger.error("Could not retrieve placement state", interactContext, request) - return ActionResult.FAIL - } - - if (interactConfig.airPlace == InteractConfig.AirPlaceMode.Grim) { - val placeHand = if (hand == Hand.MAIN_HAND) Hand.OFF_HAND else Hand.MAIN_HAND - val inventoryRequest = inventoryRequest { - swapHands() - action { sendInteractPacket(placeHand, hitResult) } - swapHands() - }.submit(queueIfMismatchedStage = false) - if (!inventoryRequest.done) return ActionResult.FAIL - } else { - sendInteractPacket(hand, hitResult) - } - - if (interactConfig.interactConfirmationMode != InteractConfig.InteractConfirmationMode.None) { - InteractInfo(interactContext, request.pendingInteractions, request.onPlace, interactConfig).startPending() - } - - val itemStack = itemPlacementContext.stack - itemStack.decrementUnlessCreative(1, player) - - if (interactConfig.interactConfirmationMode == InteractConfig.InteractConfirmationMode.AwaitThenPlace) - return ActionResult.SUCCESS - - // TODO: Implement restriction checks (e.g., world height) to prevent unnecessary server requests when the - // "AwaitThenPlace" confirmation setting is enabled, as the block state setting methods that validate these - // rules are not called. - if (!item.place(itemPlacementContext, blockState)) { - logger.error("Could not place block client side at ${interactContext.blockPos} with placement state ${interactContext.expectedState}", interactContext, request) - return ActionResult.FAIL - } - - val blockPos = itemPlacementContext.blockPos - var state = world.getBlockState(blockPos) - if (state.isOf(blockState.block)) { - state = item.placeFromNbt(blockPos, world, itemStack, state) - item.postPlacement(blockPos, world, player, itemStack, state) - state.block.onPlaced(world, blockPos, state, player, itemStack) - } - - if (interactConfig.sounds) placeSound(state, blockPos) - - if (interactConfig.interactConfirmationMode == InteractConfig.InteractConfirmationMode.None) { - request.onPlace?.invoke(this, interactContext.blockPos) - } - - logger.success("Placed ${interactContext.expectedState} at ${interactContext.blockPos}", interactContext, request) - - return ActionResult.SUCCESS - } - - /** - * sends the block placement packet using the given [hand] and [hitResult]. - */ - private fun SafeContext.sendInteractPacket(hand: Hand, hitResult: BlockHitResult) = - interaction.sendSequencedPacket(world) { sequence: Int -> - PlayerInteractBlockC2SPacket(hand, hitResult, sequence) - } - - /** - * Plays the block placement sound at a given [pos]. - */ - fun SafeContext.placeSound(state: BlockState, pos: BlockPos) { - val blockSoundGroup = state.soundGroup - world.playSound( - player, - pos, - state.soundGroup.placeSound, - SoundCategory.BLOCKS, - (blockSoundGroup.getVolume() + 1.0f) / 2.0f, - blockSoundGroup.getPitch() * 0.8f - ) - } + } else { + val item = itemStack.blockItem ?: return ActionResult.FAIL + place(interactContext, request, hand, hitResult, item, ItemPlacementContext(context)) + } + } + + /** + * A modified version of the minecraft place method. + * + * @see net.minecraft.item.BlockItem.place + */ + private fun AutomatedSafeContext.place( + interactContext: InteractContext, + request: InteractRequest, + hand: Hand, + hitResult: BlockHitResult, + item: BlockItem, + context: ItemPlacementContext + ): ActionResult { + if (!item.block.isEnabled(world.enabledFeatures)) return ActionResult.FAIL + if (!context.canPlace()) return ActionResult.FAIL + + val itemPlacementContext = item.getPlacementContext(context) ?: return ActionResult.FAIL + val blockState = item.getPlacementState(itemPlacementContext) ?: return ActionResult.FAIL + + if (interactConfig.airPlace == InteractConfig.AirPlaceMode.Grim) { + val placeHand = if (hand == Hand.MAIN_HAND) Hand.OFF_HAND else Hand.MAIN_HAND + val inventoryRequest = inventoryRequest { + swapHands() + action { sendInteractPacket(placeHand, hitResult) } + swapHands() + }.submit(queueIfMismatchedStage = false) + if (!inventoryRequest.done) return ActionResult.FAIL + } else { + sendInteractPacket(hand, hitResult) + } + + if (interactConfig.interactConfirmationMode != InteractConfig.InteractConfirmationMode.None) { + InteractInfo(interactContext, request.pendingInteractions, request.onPlace, interactConfig).startPending() + } + + val itemStack = itemPlacementContext.stack + itemStack.decrementUnlessCreative(1, player) + + if (interactConfig.interactConfirmationMode == InteractConfig.InteractConfirmationMode.AwaitThenPlace) + return ActionResult.SUCCESS + + // TODO: Implement restriction checks (e.g., world height) to prevent unnecessary server requests when the + // "AwaitThenPlace" confirmation setting is enabled, as the block state setting methods that validate these + // rules are not called. + if (!item.place(itemPlacementContext, blockState)) return ActionResult.FAIL + + val blockPos = itemPlacementContext.blockPos + var state = world.getBlockState(blockPos) + if (state.isOf(blockState.block)) { + state = item.placeFromNbt(blockPos, world, itemStack, state) + item.postPlacement(blockPos, world, player, itemStack, state) + state.block.onPlaced(world, blockPos, state, player, itemStack) + } + + if (interactConfig.sounds) placeSound(state, blockPos) + + if (interactConfig.interactConfirmationMode == InteractConfig.InteractConfirmationMode.None) { + request.onPlace?.invoke(this, interactContext.blockPos) + } + + return ActionResult.SUCCESS + } + + /** + * sends the block placement packet using the given [hand] and [hitResult]. + */ + private fun SafeContext.sendInteractPacket(hand: Hand, hitResult: BlockHitResult) = + interaction.sendSequencedPacket(world) { sequence: Int -> + PlayerInteractBlockC2SPacket(hand, hitResult, sequence) + } + + /** + * Plays the block placement sound at a given [pos]. + */ + fun SafeContext.placeSound(state: BlockState, pos: BlockPos) { + val blockSoundGroup = state.soundGroup + world.playSound( + player, + pos, + state.soundGroup.placeSound, + SoundCategory.BLOCKS, + (blockSoundGroup.getVolume() + 1.0f) / 2.0f, + blockSoundGroup.getPitch() * 0.8f + ) + } } diff --git a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractRequest.kt b/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractRequest.kt index 5e5289c99..ac6fad630 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractRequest.kt @@ -24,8 +24,6 @@ import com.lambda.interaction.construction.simulation.context.InteractContext import com.lambda.interaction.construction.simulation.result.BuildResult import com.lambda.interaction.construction.simulation.result.Dependent import com.lambda.interaction.construction.simulation.result.results.InteractResult -import com.lambda.interaction.managers.LogContext -import com.lambda.interaction.managers.LogContext.Companion.LogContextBuilder import com.lambda.interaction.managers.Request import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState @@ -37,23 +35,16 @@ data class InteractRequest private constructor( val pendingInteractions: MutableCollection, private val automated: Automated, override val nowOrNothing: Boolean = false, -) : Request(), LogContext, Automated by automated { - override val requestId = ++requestCount - override val tickStageMask get() = interactConfig.tickStageMask +) : Request(), Automated by automated { + override val requestId = ++requestCount + override val tickStageMask get() = interactConfig.tickStageMask var onPlace: (SafeContext.(BlockPos) -> Unit)? = null - override val done: Boolean - get() = runSafe { - contexts.all { it.expectedState.matches(blockState(it.blockPos)) } - } == true - - override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = { - group("PlaceRequest") { - value("Request ID", requestId) - value("Contexts", contexts.size) - } - } + override val done: Boolean + get() = runSafe { + contexts.all { it.expectedState.matches(blockState(it.blockPos)) } + } == true @DslMarker annotation class PlaceRequestDsl @@ -77,35 +68,35 @@ data class InteractRequest private constructor( } } - companion object { - var requestCount = 0 + companion object { + var requestCount = 0 - @PlaceRequestDsl - @JvmName("interactRequest1") - context(automated: Automated) - fun Collection.interactRequest( - pendingInteractions: MutableCollection, - nowOrNothing: Boolean = false, - builder: (PlaceRequestBuilder.() -> Unit)? = null - ) = asSequence() - .map { if (it is Dependent) it.lastDependency else it } - .filterIsInstance() - .sorted() + @PlaceRequestDsl + @JvmName("interactRequest1") + context(automated: Automated) + fun Collection.interactRequest( + pendingInteractions: MutableCollection, + nowOrNothing: Boolean = false, + builder: (PlaceRequestBuilder.() -> Unit)? = null + ) = asSequence() + .map { if (it is Dependent) it.lastDependency else it } + .filterIsInstance() + .sorted() .map { it.context } - .toSet() - .takeIf { it.isNotEmpty() } - ?.let { automated.interactRequest(it, pendingInteractions, nowOrNothing, builder) } + .toSet() + .takeIf { it.isNotEmpty() } + ?.let { automated.interactRequest(it, pendingInteractions, nowOrNothing, builder) } - @PlaceRequestDsl - @JvmName("interactRequest2") - fun Automated.interactRequest( - contexts: Collection, - pendingInteractions: MutableCollection, - nowOrNothing: Boolean = false, - builder: (PlaceRequestBuilder.() -> Unit)? = null + @PlaceRequestDsl + @JvmName("interactRequest2") + fun Automated.interactRequest( + contexts: Collection, + pendingInteractions: MutableCollection, + nowOrNothing: Boolean = false, + builder: (PlaceRequestBuilder.() -> Unit)? = null ) = PlaceRequestBuilder(contexts, pendingInteractions, nowOrNothing, this).apply { builder?.invoke(this) }.build() - @PlaceRequestDsl - fun PlaceRequestBuilder.build() = request - } + @PlaceRequestDsl + fun PlaceRequestBuilder.build() = request + } } diff --git a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractedBlockHandler.kt b/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractedBlockHandler.kt index b6f90c09d..2c931006d 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractedBlockHandler.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/interacting/InteractedBlockHandler.kt @@ -21,7 +21,6 @@ import com.lambda.config.AutomationConfig.Companion.DEFAULT import com.lambda.config.AutomationConfig.Companion.DEFAULT.managerDebugLogs import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.construction.simulation.processing.ProcessorRegistry import com.lambda.interaction.managers.PostActionHandler import com.lambda.interaction.managers.interacting.InteractManager.placeSound import com.lambda.threading.runSafe @@ -30,46 +29,42 @@ import com.lambda.util.Communication.warn import com.lambda.util.collections.LimitedDecayQueue object InteractedBlockHandler : PostActionHandler() { - override val pendingActions = LimitedDecayQueue( - DEFAULT.buildConfig.maxPendingActions, - DEFAULT.buildConfig.actionTimeout * 50L - ) { - if (managerDebugLogs) warn("${it::class.simpleName} at ${it.context.blockPos.toShortString()} timed out") - if (it.interactConfig.interactConfirmationMode != InteractConfig.InteractConfirmationMode.AwaitThenPlace) { - runSafe { - world.setBlockState(it.context.blockPos, it.context.cachedState) - } - } - it.pendingInteractionsList.remove(it.context) - } + override val pendingActions = LimitedDecayQueue( + DEFAULT.buildConfig.maxPendingActions, + DEFAULT.buildConfig.actionTimeout * 50L + ) { + if (managerDebugLogs) warn("${it::class.simpleName} at ${it.context.blockPos.toShortString()} timed out") + if (it.interactConfig.interactConfirmationMode != InteractConfig.InteractConfirmationMode.AwaitThenPlace) { + runSafe { + world.setBlockState(it.context.blockPos, it.context.cachedState) + } + } + it.pendingInteractionsList.remove(it.context) + } - init { - listen(priority = Int.MIN_VALUE) { event -> - pendingActions - .firstOrNull { it.context.blockPos == event.pos } - ?.let { pending -> - if (!pending.context.expectedState.matches(event.newState)) { - if (pending.context.cachedState.matches( - event.newState, - ProcessorRegistry.postProcessedProperties - ) - ) { - pending.context.cachedState = event.newState - return@listen - } + init { + listen(priority = Int.MIN_VALUE) { event -> + pendingActions + .firstOrNull { it.context.blockPos == event.pos } + ?.let { pending -> + if (!pending.context.expectedState.matches(event.newState)) { + if (pending.context.cachedState.matches(event.newState, pending.context.preProcessingInfo.ignore)) { + pending.context.cachedState = event.newState + return@listen + } - pending.stopPending() + pending.stopPending() - if (managerDebugLogs) this@InteractedBlockHandler.warn("Placed block at ${event.pos.toShortString()} was rejected with ${event.newState} instead of ${pending.context.expectedState}") - return@listen - } + if (managerDebugLogs) this@InteractedBlockHandler.warn("Placed block at ${event.pos.toShortString()} was rejected with ${event.newState} instead of ${pending.context.expectedState}") + return@listen + } - pending.stopPending() + pending.stopPending() - if (pending.interactConfig.interactConfirmationMode == InteractConfig.InteractConfirmationMode.AwaitThenPlace) - with(pending.context) { placeSound(expectedState, blockPos) } - pending.onPlace?.invoke(this, pending.context.blockPos) - } - } - } + if (pending.interactConfig.interactConfirmationMode == InteractConfig.InteractConfirmationMode.AwaitThenPlace) + with(pending.context) { placeSound(expectedState, blockPos) } + pending.onPlace?.invoke(this, pending.context.blockPos) + } + } + } } diff --git a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryAction.kt b/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryAction.kt index 302f5cb83..b1cc09480 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryAction.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryAction.kt @@ -25,8 +25,8 @@ import com.lambda.context.SafeContext * to avoid having to use multiple requests. */ sealed interface InventoryAction { - val action: SafeContext.() -> Unit + val action: SafeContext.() -> Unit - class Inventory(override val action: SafeContext.() -> Unit) : InventoryAction - class Other(override val action: SafeContext.() -> Unit) : InventoryAction + class Inventory(override val action: SafeContext.() -> Unit) : InventoryAction + class Other(override val action: SafeContext.() -> Unit) : InventoryAction } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryConfig.kt b/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryConfig.kt index 254ec6e51..803eb26ae 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryConfig.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryConfig.kt @@ -18,67 +18,69 @@ package com.lambda.interaction.managers.inventory import com.lambda.config.ISettingGroup +import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.interaction.material.ContainerSelection import com.lambda.interaction.material.StackSelection import com.lambda.interaction.material.container.MaterialContainer import com.lambda.util.Describable import com.lambda.util.NamedEnum -import net.minecraft.block.Block +import net.minecraft.item.Item interface InventoryConfig : ISettingGroup { - val actionsPerSecond: Int - val tickStageMask: Collection - val disposables: Collection - val swapWithDisposables: Boolean - val providerPriority: Priority - val storePriority: Priority + val actionsPerSecond: Int + val tickStageMask: Collection + val disposables: Collection + val swapWithDisposables: Boolean + val providerPriority: Priority + val storePriority: Priority - val immediateAccessOnly: Boolean - val accessShulkerBoxes: Boolean - val accessEnderChest: Boolean - val accessChests: Boolean - val accessStashes: Boolean + val accessShulkerBoxes: Boolean + val accessChests: Boolean + val accessEnderChest: Boolean + val accessStashes: Boolean - val containerSelection: ContainerSelection - get() = ContainerSelection.selectContainer { - val allowedContainers = mutableSetOf().apply { - addAll(MaterialContainer.Rank.entries) - if (!accessShulkerBoxes) remove(MaterialContainer.Rank.ShulkerBox) - if (!accessEnderChest) remove(MaterialContainer.Rank.EnderChest) - if (!accessChests) remove(MaterialContainer.Rank.Chest) - if (!accessStashes) remove(MaterialContainer.Rank.Stash) - } - ofAnyType(*allowedContainers.toTypedArray()) - } + val containerSelection: ContainerSelection + get() = ContainerSelection.selectContainer { + val allowedContainers = buildSet { + addAll(MaterialContainer.Rank.entries) + if (!accessShulkerBoxes) remove(MaterialContainer.Rank.ShulkerBox) + if (!accessEnderChest) remove(MaterialContainer.Rank.EnderChest) + if (!accessChests) remove(MaterialContainer.Rank.Chest) + if (!accessStashes) remove(MaterialContainer.Rank.Stash) + } + ofAnyType(*allowedContainers.toTypedArray()) + } - enum class Priority( - override val displayName: String, - override val description: String - ) : NamedEnum, Describable { - WithMinItems("With Min Items", "Pick containers with the fewest matching items (or least space) first; useful for topping off or clearing leftovers."), - WithMaxItems("With Max Items", "Pick containers with the most matching items (or most space) first; ideal for bulk moves with fewer transfers."); + enum class Priority( + override val displayName: String, + override val description: String + ) : NamedEnum, Describable { + WithMinItems("With Min Items", "Pick containers with the fewest matching items (or least space) first; useful for topping off or clearing leftovers."), + WithMaxItems("With Max Items", "Pick containers with the most matching items (or most space) first; ideal for bulk moves with fewer transfers."); - fun materialComparator(selection: StackSelection) = - when (this) { - WithMaxItems -> compareBy { it.rank } - .thenByDescending { it.materialAvailable(selection) } - .thenBy { it.name } + context(_: SafeContext) + fun materialComparator(selection: StackSelection) = + when (this) { + WithMaxItems -> compareBy { it.rank } + .thenByDescending { it.materialAvailable(selection) } + .thenBy { it.name } - WithMinItems -> compareBy { it.rank } - .thenBy { it.materialAvailable(selection) } - .thenBy { it.name } - } + WithMinItems -> compareBy { it.rank } + .thenBy { it.materialAvailable(selection) } + .thenBy { it.name } + } - fun spaceComparator(selection: StackSelection) = - when (this) { - WithMaxItems -> compareBy { it.rank } - .thenByDescending { it.spaceAvailable(selection) } - .thenBy { it.name } + context(_: SafeContext) + fun spaceComparator(selection: StackSelection) = + when (this) { + WithMaxItems -> compareBy { it.rank } + .thenByDescending { it.spaceAvailable(selection) } + .thenBy { it.name } - WithMinItems -> compareBy { it.rank } - .thenBy { it.spaceAvailable(selection) } - .thenBy { it.name } - } - } + WithMinItems -> compareBy { it.rank } + .thenBy { it.spaceAvailable(selection) } + .thenBy { it.name } + } + } } diff --git a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryManager.kt b/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryManager.kt index 79c4a024d..7d7159adf 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryManager.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryManager.kt @@ -23,14 +23,11 @@ import com.lambda.context.SafeContext import com.lambda.event.events.PacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.Logger import com.lambda.interaction.managers.Manager -import com.lambda.interaction.managers.interacting.InteractManager import com.lambda.interaction.managers.inventory.InventoryManager.actions import com.lambda.interaction.managers.inventory.InventoryManager.activeRequest import com.lambda.interaction.managers.inventory.InventoryManager.alteredSlots import com.lambda.interaction.managers.inventory.InventoryManager.processActiveRequest -import com.lambda.module.hud.ManagerDebugLoggers.inventoryManagerLogger import com.lambda.threading.runSafe import com.lambda.util.collections.LimitedDecayQueue import com.lambda.util.item.ItemStackUtils.equal @@ -49,227 +46,223 @@ import net.minecraft.screen.slot.Slot * avoid accepting old information from the server in cases where ping is high. This helps to prevent desync. */ object InventoryManager : Manager( - 1, - onOpen = { processActiveRequest() } -), Logger { - private var activeRequest: InventoryRequest? = null - private var actions = mutableListOf() + 1, + onOpen = { processActiveRequest() } +) { + private var activeRequest: InventoryRequest? = null + private var actions = mutableListOf() - private var slots = listOf() - private var alteredSlots = LimitedDecayQueue(Int.MAX_VALUE, DEFAULT.desyncTimeout * 50L) + private var slots = listOf() + private var alteredSlots = LimitedDecayQueue(Int.MAX_VALUE, DEFAULT.desyncTimeout * 50L) private var alteredPlayerSlots = LimitedDecayQueue(Int.MAX_VALUE, DEFAULT.desyncTimeout * 50L) - var screenHandler: ScreenHandler? = null - set(value) { - if (value != null) { - alteredSlots.clear() - slots = getStacks(value.slots) - } - field = value - } + var screenHandler: ScreenHandler? = null + set(value) { + if (value != null) { + alteredSlots.clear() + slots = getStacks(value.slots) + } + field = value + } - private var maxActionsThisSecond = 0 - private var actionsThisSecond = 0 - private var secondCounter = 0 - private var actionsThisTick = 0 + private var maxActionsThisSecond = 0 + private var actionsThisSecond = 0 + private var secondCounter = 0 + private var actionsThisTick = 0 - override val logger = inventoryManagerLogger + override fun load(): String { + super.load() - override fun load(): String { - super.load() + listen(priority = Int.MIN_VALUE) { + if (DEFAULT.avoidDesync) indexInventoryChanges() + if (++secondCounter >= 20) { + secondCounter = 0 + actionsThisSecond = 0 + } + actionsThisTick = 0 + activeRequest = null + actions = mutableListOf() + } - listen(priority = Int.MIN_VALUE) { - if (DEFAULT.avoidDesync) indexInventoryChanges() - if (++secondCounter >= 20) { - secondCounter = 0 - actionsThisSecond = 0 - } - actionsThisTick = 0 - activeRequest = null - actions = mutableListOf() - } + listen { event -> + if (event.packet is CloseHandledScreenC2SPacket) { + screenHandler = player.playerScreenHandler + } + } - listen { event -> - if (event.packet is CloseHandledScreenC2SPacket) { - screenHandler = player.playerScreenHandler - } - } + return "Loaded Inventory Manager" + } - return "Loaded Inventory Manager" - } + /** + * Attempts to accept the request and perform the actions. If, for example, the tick stage isn't valid, or + * not all the actions can be performed and [InventoryRequest.settleForLess] is set to false, the request is rejected. + * All checks aside from tick stage are ignored if the request has [InventoryRequest.mustPerform] set to true. + * This is typically used in dangerous situations where typical rules are worth breaking. For example, if the player + * needs to equip a totem of undying. + */ + override fun AutomatedSafeContext.handleRequest(request: InventoryRequest) { + if (activeRequest != null) return - /** - * Attempts to accept the request and perform the actions. If, for example, the tick stage isn't valid, or - * not all the actions can be performed and [InventoryRequest.settleForLess] is set to false, the request is rejected. - * All checks aside from tick stage are ignored if the request has [InventoryRequest.mustPerform] set to true. - * This is typically used in dangerous situations where typical rules are worth breaking. For example, if the player - * needs to equip a totem of undying. - */ - override fun AutomatedSafeContext.handleRequest(request: InventoryRequest) { - if (activeRequest != null) return + val inventoryActionCount = request.actions.count { it is InventoryAction.Inventory } + if (inventoryActionCount > request.inventoryConfig.actionsPerSecond - actionsThisSecond && + !request.settleForLess && + !request.mustPerform) return - val inventoryActionCount = request.actions.count { it is InventoryAction.Inventory } - if (inventoryActionCount > request.inventoryConfig.actionsPerSecond - actionsThisSecond && - !request.settleForLess && - !request.mustPerform) return + if (request.fresh) populateFrom(request) - if (request.fresh) populateFrom(request) + processActiveRequest() + if (request.nowOrNothing) { + activeRequest = null + actions = mutableListOf() + } + } - processActiveRequest() - if (request.nowOrNothing) { - activeRequest = null - actions = mutableListOf() - } - } + private fun populateFrom(request: InventoryRequest) { + activeRequest = request + actions = request.actions.toMutableList() + maxActionsThisSecond = request.inventoryConfig.actionsPerSecond + alteredSlots.setDecayTime(DEFAULT.desyncTimeout * 50L) + alteredPlayerSlots.setDecayTime(DEFAULT.desyncTimeout * 50L) + } - private fun populateFrom(request: InventoryRequest) { - InteractManager.logger.debug("Populating from request", request) - activeRequest = request - actions = request.actions.toMutableList() - maxActionsThisSecond = request.inventoryConfig.actionsPerSecond - alteredSlots.setDecayTime(DEFAULT.desyncTimeout * 50L) - alteredPlayerSlots.setDecayTime(DEFAULT.desyncTimeout * 50L) - } + /** + * Attempts to perform as many actions as possible from the [actions] collection. If + * [actions] is empty, the request is set to done, and the onComplete callback is invoked. + * The [activeRequest] is then set to null. + */ + private fun SafeContext.processActiveRequest() { + activeRequest?.let { active -> + if (tickStage !in active.inventoryConfig.tickStageMask && active.nowOrNothing) return + val iterator = actions.iterator() + while (iterator.hasNext()) { + val action = iterator.next() + if (action is InventoryAction.Inventory && actionsThisSecond + 1 > maxActionsThisSecond && !active.mustPerform) + break + action.action(this) + if (DEFAULT.avoidDesync) indexInventoryChanges() + actionsThisTick++ + actionsThisSecond++ + iterator.remove() + } - /** - * Attempts to perform as many actions as possible from the [actions] collection. If - * [actions] is empty, the request is set to done, and the onComplete callback is invoked. - * The [activeRequest] is then set to null. - */ - private fun SafeContext.processActiveRequest() { - activeRequest?.let { active -> - InteractManager.logger.debug("Processing request", active) - if (tickStage !in active.inventoryConfig.tickStageMask && active.nowOrNothing) return - val iterator = actions.iterator() - while (iterator.hasNext()) { - val action = iterator.next() - if (action is InventoryAction.Inventory && actionsThisSecond + 1 > maxActionsThisSecond && !active.mustPerform) - break - action.action(this) - if (DEFAULT.avoidDesync) indexInventoryChanges() - actionsThisTick++ - actionsThisSecond++ - iterator.remove() - } + if (actions.isEmpty()) { + active.done = true + active.onComplete?.invoke(this) + activeRequest = null + } - if (actions.isEmpty()) { - active.done = true - active.onComplete?.invoke(this) - activeRequest = null - } + if (actionsThisTick > 0) activeThisTick = true + } + } - if (actionsThisTick > 0) activeThisTick = true - } - } + /** + * Detects changes in item stacks between now and the last time slots were cached and + * adds them all to the [alteredSlots] collection where they can be compared to + * incoming [InventoryS2CPacket] and [ScreenHandlerSlotUpdateS2CPacket] packets to decide whether to + * block certain updates. + */ + private fun SafeContext.indexInventoryChanges() { + if (player.currentScreenHandler.syncId != screenHandler?.syncId) return + val changes = screenHandler?.slots + ?.filter { !it.stack.equal(slots[it.id]) } + ?.map { InventoryChange(it.id, slots[it.id], it.stack.copy()) } + ?: emptyList() + if (player.currentScreenHandler.syncId == 0) alteredPlayerSlots.addAll(changes) + else alteredSlots.addAll(changes) + slots = getStacks(player.currentScreenHandler.slots) + } - /** - * Detects changes in item stacks between now and the last time slots were cached and - * adds them all to the [alteredSlots] collection where they can be compared to - * incoming [InventoryS2CPacket] and [ScreenHandlerSlotUpdateS2CPacket] packets to decide whether to - * block certain updates. - */ - private fun SafeContext.indexInventoryChanges() { - if (player.currentScreenHandler.syncId != screenHandler?.syncId) return - val changes = screenHandler?.slots - ?.filter { !it.stack.equal(slots[it.id]) } - ?.map { InventoryChange(it.id, slots[it.id], it.stack.copy()) } - ?: emptyList() - if (player.currentScreenHandler.syncId == 0) alteredPlayerSlots.addAll(changes) - else alteredSlots.addAll(changes) - slots = getStacks(player.currentScreenHandler.slots) - } + private fun getStacks(slots: Collection) = slots.map { it.stack.copy() } - private fun getStacks(slots: Collection) = slots.map { it.stack.copy() } + /** + * A modified version of the minecraft onInventory method + * + * @see net.minecraft.client.network.ClientPlayNetworkHandler.onInventory + */ + @JvmStatic + fun onInventoryUpdate(packet: InventoryS2CPacket, original: Operation){ + runSafe { + if (!mc.isOnThread || !DEFAULT.avoidDesync) { + original.call(packet) + return + } + val packetScreenHandler = + when (packet.syncId) { + 0 -> player.playerScreenHandler + player.currentScreenHandler.syncId -> player.currentScreenHandler + else -> return@runSafe + } + val alteredContents = mutableListOf() + val alteredSlots = if (packet.syncId == 0) alteredPlayerSlots else alteredSlots + packet.contents.forEachIndexed { index, incomingStack -> + val matches = alteredSlots.removeIf { cached -> + incomingStack.equal(cached.after) + } + if (matches) alteredContents.add(packetScreenHandler.slots[index].stack) + else alteredContents.add(incomingStack) + } + packetScreenHandler.updateSlotStacks(packet.revision(), alteredContents, packet.cursorStack()) + return + } + original.call(packet) + } - /** - * A modified version of the minecraft onInventory method - * - * @see net.minecraft.client.network.ClientPlayNetworkHandler.onInventory - */ - @JvmStatic - fun onInventoryUpdate(packet: InventoryS2CPacket, original: Operation){ - runSafe { - if (!mc.isOnThread || !DEFAULT.avoidDesync) { - original.call(packet) - return - } - val packetScreenHandler = - when (packet.syncId) { - 0 -> player.playerScreenHandler - player.currentScreenHandler.syncId -> player.currentScreenHandler - else -> return@runSafe - } - val alteredContents = mutableListOf() - val alteredSlots = if (packet.syncId == 0) alteredPlayerSlots else alteredSlots - packet.contents.forEachIndexed { index, incomingStack -> - val matches = alteredSlots.removeIf { cached -> - incomingStack.equal(cached.after) - } - if (matches) alteredContents.add(packetScreenHandler.slots[index].stack) - else alteredContents.add(incomingStack) - } - packetScreenHandler.updateSlotStacks(packet.revision(), alteredContents, packet.cursorStack()) - return - } - original.call(packet) - } + /** + * A modified version of the minecraft onScreenHandlerSlotUpdate method + * + * @see net.minecraft.client.network.ClientPlayNetworkHandler.onScreenHandlerSlotUpdate + */ + @JvmStatic + fun onSlotUpdate(packet: ScreenHandlerSlotUpdateS2CPacket, original: Operation) { + runSafe { + if (!mc.isOnThread || !DEFAULT.avoidDesync) { + original.call(packet) + return + } + val itemStack = packet.stack + mc.tutorialManager.onSlotUpdate(itemStack) - /** - * A modified version of the minecraft onScreenHandlerSlotUpdate method - * - * @see net.minecraft.client.network.ClientPlayNetworkHandler.onScreenHandlerSlotUpdate - */ - @JvmStatic - fun onSlotUpdate(packet: ScreenHandlerSlotUpdateS2CPacket, original: Operation) { - runSafe { - if (!mc.isOnThread || !DEFAULT.avoidDesync) { - original.call(packet) - return - } - val itemStack = packet.stack - mc.tutorialManager.onSlotUpdate(itemStack) + val bl = (mc.currentScreen as? CreativeInventoryScreen)?.let { + !it.isInventoryTabSelected + } ?: false - val bl = (mc.currentScreen as? CreativeInventoryScreen)?.let { - !it.isInventoryTabSelected - } ?: false + val alteredSlots = if (packet.syncId == 0) alteredPlayerSlots else alteredSlots + val matches = alteredSlots.removeIf { + it.syncId == packet.slot && it.after.equal(itemStack) + } - val alteredSlots = if (packet.syncId == 0) alteredPlayerSlots else alteredSlots - val matches = alteredSlots.removeIf { - it.syncId == packet.slot && it.after.equal(itemStack) - } + if (packet.syncId == 0) { + if (PlayerScreenHandler.isInHotbar(packet.slot) && !itemStack.isEmpty) { + val itemStack2 = player.playerScreenHandler.getSlot(packet.slot).stack + if (itemStack2.isEmpty || itemStack2.count < itemStack.count) { + itemStack.bobbingAnimationTime = 5 + } + } - if (packet.syncId == 0) { - if (PlayerScreenHandler.isInHotbar(packet.slot) && !itemStack.isEmpty) { - val itemStack2 = player.playerScreenHandler.getSlot(packet.slot).stack - if (itemStack2.isEmpty || itemStack2.count < itemStack.count) { - itemStack.bobbingAnimationTime = 5 - } - } + if (matches) player.playerScreenHandler.revision = packet.revision + else player.playerScreenHandler.setStackInSlot(packet.slot, packet.revision, itemStack) + } else if (packet.syncId == player.currentScreenHandler.syncId && (packet.syncId != 0 || !bl)) { + if (matches) player.currentScreenHandler.revision = packet.revision + else player.currentScreenHandler.setStackInSlot(packet.slot, packet.revision, itemStack) + } - if (matches) player.playerScreenHandler.revision = packet.revision - else player.playerScreenHandler.setStackInSlot(packet.slot, packet.revision, itemStack) - } else if (packet.syncId == player.currentScreenHandler.syncId && (packet.syncId != 0 || !bl)) { - if (matches) player.currentScreenHandler.revision = packet.revision - else player.currentScreenHandler.setStackInSlot(packet.slot, packet.revision, itemStack) - } + if (mc.currentScreen is CreativeInventoryScreen) { + player.playerScreenHandler.setReceivedStack(packet.slot, itemStack) + player.playerScreenHandler.sendContentUpdates() + } + return + } + original.call(packet) + } - if (mc.currentScreen is CreativeInventoryScreen) { - player.playerScreenHandler.setReceivedStack(packet.slot, itemStack) - player.playerScreenHandler.sendContentUpdates() - } - return - } - original.call(packet) - } + @JvmStatic + fun onSetScreenHandler(screenHandler: ScreenHandler) { + this.screenHandler = screenHandler + } - @JvmStatic - fun onSetScreenHandler(screenHandler: ScreenHandler) { - this.screenHandler = screenHandler - } - - private data class InventoryChange( - val syncId: Int, - val before: ItemStack, - val after: ItemStack - ) + private data class InventoryChange( + val syncId: Int, + val before: ItemStack, + val after: ItemStack + ) } diff --git a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryRequest.kt b/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryRequest.kt index 74862cecb..af095632f 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/inventory/InventoryRequest.kt @@ -19,8 +19,6 @@ package com.lambda.interaction.managers.inventory import com.lambda.context.Automated import com.lambda.context.SafeContext -import com.lambda.interaction.managers.LogContext -import com.lambda.interaction.managers.LogContext.Companion.LogContextBuilder import com.lambda.interaction.managers.Request import com.lambda.util.player.SlotUtils.clickSlot import net.minecraft.item.ItemStack @@ -38,154 +36,147 @@ import net.minecraft.util.math.Direction * @property mustPerform A flag indicating whether the request must be performed regardless of conditions as long as the tick stage is valid. */ class InventoryRequest private constructor( - val actions: List, - val settleForLess: Boolean, - val mustPerform: Boolean, - automated: Automated, - override val nowOrNothing: Boolean = false, - val onComplete: (SafeContext.() -> Unit)? -) : Request(), LogContext, Automated by automated { - override val requestId = ++requestCount - override val tickStageMask get() = inventoryConfig.tickStageMask - override var done = false - - override fun submit(queueIfMismatchedStage: Boolean) = - InventoryManager.request(this, queueIfMismatchedStage) - - override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = { - group("Inventory Request") { - value("Request ID", requestId) - value("Action Count", actions.size) - } - } - - @DslMarker - private annotation class InvRequestDsl - - @InvRequestDsl - class InvRequestBuilder(val settleForLess: Boolean, val mustPerform: Boolean) { - val actions = mutableListOf() - var onComplete: (SafeContext.() -> Unit)? = null - - @InvRequestDsl - fun click(slotId: Int, button: Int, actionType: SlotActionType) { - InventoryAction.Inventory { clickSlot(slotId, button, actionType) }.addToActions() - } - - @InvRequestDsl - fun pickFromInventory(slotId: Int) { - InventoryAction.Inventory { - clickSlot(slotId, player.inventory.selectedSlot, SlotActionType.SWAP) - }.addToActions() - } - - @InvRequestDsl - fun dropItemInHand(entireStack: Boolean = true) { - InventoryAction.Inventory { player.dropSelectedItem(entireStack) }.addToActions() - } - - @InvRequestDsl - fun swapHands() { - InventoryAction.Inventory { - val offhandStack = player.getStackInHand(Hand.OFF_HAND) - player.setStackInHand(Hand.OFF_HAND, player.getStackInHand(Hand.MAIN_HAND)) - player.setStackInHand(Hand.MAIN_HAND, offhandStack) - connection.sendPacket( - PlayerActionC2SPacket( - PlayerActionC2SPacket.Action.SWAP_ITEM_WITH_OFFHAND, - BlockPos.ORIGIN, - Direction.DOWN - ) - ) - }.addToActions() - } - - @InvRequestDsl - fun clickCreativeStack(stack: ItemStack, slotId: Int) { - InventoryAction.Inventory { interaction.clickCreativeStack(stack, slotId) }.addToActions() - } - - @InvRequestDsl - fun pickup(slotId: Int, button: Int = 0) = click(slotId, button, SlotActionType.PICKUP) - - // Quick move action (Shift-click) - @InvRequestDsl - fun quickMove(slotId: Int) = click(slotId, 0, SlotActionType.QUICK_MOVE) - - @InvRequestDsl - fun swap(slotId: Int, hotbarSlot: Int) = click(slotId, hotbarSlot, SlotActionType.SWAP) - - // Clone action (Creative mode) - @InvRequestDsl - fun clone(slotId: Int) = click(slotId, 2, SlotActionType.CLONE) - - // Throw stack or single item - @InvRequestDsl - fun throwStack(slotId: Int) = click(slotId, 1, SlotActionType.THROW) - - @InvRequestDsl - fun throwSingle(slotId: Int) = click(slotId, 0, SlotActionType.THROW) - - // Quick craft action - @InvRequestDsl - fun quickCraftStart(slotId: Int) = click(slotId, 0, SlotActionType.QUICK_CRAFT) - - @InvRequestDsl - fun quickCraftDrag(slotId: Int) = click(slotId, 1, SlotActionType.QUICK_CRAFT) - - @InvRequestDsl - fun quickCraftEnd(slotId: Int) = click(slotId, 2, SlotActionType.QUICK_CRAFT) - - // Pickup all items (double-click) - @InvRequestDsl - fun pickupAll(slotId: Int) = click(slotId, 0, SlotActionType.PICKUP_ALL) - - // Helper function: Move items from one slot to another - @InvRequestDsl - fun moveSlot(fromSlotId: Int, toSlotId: Int, button: Int = 0) { - pickup(fromSlotId, button) - pickup(toSlotId, button) - } - - // Helper function: Split a stack into two - @InvRequestDsl - fun splitStack(slotId: Int, targetSlotId: Int) { - pickup(slotId, 1) // Pickup half the stack - pickup(targetSlotId, 0) // Place it in the target slot - } - - // Helper function: Merge stacks - @InvRequestDsl - fun mergeStacks(sourceSlotId: Int, targetSlotId: Int) { - pickup(sourceSlotId, 0) - pickup(targetSlotId, 0) - } - - @InvRequestDsl - fun action(action: SafeContext.() -> Unit) { - InventoryAction.Other(action).addToActions() - } - - @InvRequestDsl - fun onComplete(callback: SafeContext.() -> Unit) { - onComplete = callback - } - - @InvRequestDsl - private fun InventoryAction.addToActions() { - actions.add(this) - } - } - - companion object { - var requestCount = 0 - - @InvRequestDsl - fun Automated.inventoryRequest(settleForLess: Boolean = false, mustPerform: Boolean = false, builder: InvRequestBuilder.() -> Unit) = - InvRequestBuilder(settleForLess, mustPerform).apply(builder).build() - - @InvRequestDsl - context(automated: Automated) - private fun InvRequestBuilder.build() = InventoryRequest(actions, settleForLess, mustPerform, automated, onComplete = onComplete) - } + val actions: List, + val settleForLess: Boolean, + val mustPerform: Boolean, + automated: Automated, + override val nowOrNothing: Boolean = false, + val onComplete: (SafeContext.() -> Unit)? +) : Request(), Automated by automated { + override val requestId = ++requestCount + override val tickStageMask get() = inventoryConfig.tickStageMask + override var done = false + + override fun submit(queueIfMismatchedStage: Boolean) = + InventoryManager.request(this, queueIfMismatchedStage) + + @DslMarker + private annotation class InvRequestDsl + + @InvRequestDsl + class InvRequestBuilder(val settleForLess: Boolean, val mustPerform: Boolean) { + val actions = mutableListOf() + var onComplete: (SafeContext.() -> Unit)? = null + + @InvRequestDsl + fun click(slotId: Int, button: Int, actionType: SlotActionType) { + InventoryAction.Inventory { clickSlot(slotId, button, actionType) }.addToActions() + } + + @InvRequestDsl + fun pickFromInventory(slotId: Int) { + InventoryAction.Inventory { + clickSlot(slotId, player.inventory.selectedSlot, SlotActionType.SWAP) + }.addToActions() + } + + @InvRequestDsl + fun dropItemInHand(entireStack: Boolean = true) { + InventoryAction.Inventory { player.dropSelectedItem(entireStack) }.addToActions() + } + + @InvRequestDsl + fun swapHands() { + InventoryAction.Inventory { + val offhandStack = player.getStackInHand(Hand.OFF_HAND) + player.setStackInHand(Hand.OFF_HAND, player.getStackInHand(Hand.MAIN_HAND)) + player.setStackInHand(Hand.MAIN_HAND, offhandStack) + connection.sendPacket( + PlayerActionC2SPacket( + PlayerActionC2SPacket.Action.SWAP_ITEM_WITH_OFFHAND, + BlockPos.ORIGIN, + Direction.DOWN + ) + ) + }.addToActions() + } + + @InvRequestDsl + fun clickCreativeStack(stack: ItemStack, slotId: Int) { + InventoryAction.Inventory { interaction.clickCreativeStack(stack, slotId) }.addToActions() + } + + @InvRequestDsl + fun pickup(slotId: Int, button: Int = 0) = click(slotId, button, SlotActionType.PICKUP) + + // Quick move action (Shift-click) + @InvRequestDsl + fun quickMove(slotId: Int) = click(slotId, 0, SlotActionType.QUICK_MOVE) + + @InvRequestDsl + fun swap(slotId: Int, hotbarSlot: Int) = click(slotId, hotbarSlot, SlotActionType.SWAP) + + // Clone action (Creative mode) + @InvRequestDsl + fun clone(slotId: Int) = click(slotId, 2, SlotActionType.CLONE) + + // Throw stack or single item + @InvRequestDsl + fun throwStack(slotId: Int) = click(slotId, 1, SlotActionType.THROW) + + @InvRequestDsl + fun throwSingle(slotId: Int) = click(slotId, 0, SlotActionType.THROW) + + // Quick craft action + @InvRequestDsl + fun quickCraftStart(slotId: Int) = click(slotId, 0, SlotActionType.QUICK_CRAFT) + + @InvRequestDsl + fun quickCraftDrag(slotId: Int) = click(slotId, 1, SlotActionType.QUICK_CRAFT) + + @InvRequestDsl + fun quickCraftEnd(slotId: Int) = click(slotId, 2, SlotActionType.QUICK_CRAFT) + + // Pickup all items (double-click) + @InvRequestDsl + fun pickupAll(slotId: Int) = click(slotId, 0, SlotActionType.PICKUP_ALL) + + // Helper function: Move items from one slot to another + @InvRequestDsl + fun moveSlot(fromSlotId: Int, toSlotId: Int, button: Int = 0) { + pickup(fromSlotId, button) + pickup(toSlotId, button) + } + + // Helper function: Split a stack into two + @InvRequestDsl + fun splitStack(slotId: Int, targetSlotId: Int) { + pickup(slotId, 1) // Pickup half the stack + pickup(targetSlotId, 0) // Place it in the target slot + } + + // Helper function: Merge stacks + @InvRequestDsl + fun mergeStacks(sourceSlotId: Int, targetSlotId: Int) { + pickup(sourceSlotId, 0) + pickup(targetSlotId, 0) + } + + @InvRequestDsl + fun action(action: SafeContext.() -> Unit) { + InventoryAction.Other(action).addToActions() + } + + @InvRequestDsl + fun onComplete(callback: SafeContext.() -> Unit) { + onComplete = callback + } + + @InvRequestDsl + private fun InventoryAction.addToActions() { + actions.add(this) + } + } + + companion object { + var requestCount = 0 + + @InvRequestDsl + fun Automated.inventoryRequest(settleForLess: Boolean = false, mustPerform: Boolean = false, builder: InvRequestBuilder.() -> Unit) = + InvRequestBuilder(settleForLess, mustPerform).apply(builder).build() + + @InvRequestDsl + context(automated: Automated) + private fun InvRequestBuilder.build() = InventoryRequest(actions, settleForLess, mustPerform, automated, onComplete = onComplete) + } } diff --git a/src/main/kotlin/com/lambda/interaction/managers/rotating/Rotation.kt b/src/main/kotlin/com/lambda/interaction/managers/rotating/Rotation.kt index 96c9ff7ca..e8e98a705 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/rotating/Rotation.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/rotating/Rotation.kt @@ -115,6 +115,16 @@ data class Rotation(val yaw: Double, val pitch: Double) { return Rotation(yaw, pitch) } + fun Rotation.slerpYaw(targetYaw: Double, speed: Double): Double { + val yawDiff = wrap(targetYaw - yaw) + return yaw + yawDiff.coerceIn(-speed, speed) + } + + fun Rotation.slerpPitch(targetPitch: Double, speed: Double): Double { + val pitchDiff = targetPitch - pitch + return (pitch + pitchDiff.coerceIn(-speed, speed)).coerceIn(-90.0, 90.0) + } + fun Rotation.slerp(other: Rotation, speed: Double): Rotation { val yawDiff = wrap(other.yaw - yaw) val pitchDiff = other.pitch - pitch diff --git a/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationManager.kt b/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationManager.kt index 290090c80..e71ed314a 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationManager.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationManager.kt @@ -29,14 +29,12 @@ import com.lambda.event.events.TickEvent.Companion.ALL_STAGES import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe import com.lambda.interaction.BaritoneManager -import com.lambda.interaction.managers.Logger import com.lambda.interaction.managers.Manager -import com.lambda.interaction.managers.rotating.Rotation.Companion.slerp -import com.lambda.interaction.managers.rotating.RotationManager.activeRequest +import com.lambda.interaction.managers.rotating.Rotation.Companion.slerpPitch +import com.lambda.interaction.managers.rotating.Rotation.Companion.slerpYaw import com.lambda.interaction.managers.rotating.RotationManager.activeRotation import com.lambda.interaction.managers.rotating.RotationManager.serverRotation import com.lambda.interaction.managers.rotating.RotationManager.updateActiveRotation -import com.lambda.module.hud.ManagerDebugLoggers.rotationManagerLogger import com.lambda.threading.runGameScheduled import com.lambda.threading.runSafe import com.lambda.util.extension.rotation @@ -58,295 +56,344 @@ import kotlin.math.sin * Manager designed to rotate the player and adjust movement input to match the camera's direction. */ object RotationManager : Manager( - 1, + 1, *(ALL_STAGES.subList(ALL_STAGES.indexOf(TickEvent.Player.Post), ALL_STAGES.size - 1).toTypedArray()), -), Logger { - var activeRotation = Rotation.ZERO - var serverRotation = Rotation.ZERO - @JvmStatic var prevServerRotation = Rotation.ZERO - private var usingBaritoneRotation = false - - var activeRequest: RotationRequest? = null - private var changedThisTick = false - - override val logger = rotationManagerLogger - - override fun load(): String { - super.load() - - listen(priority = Int.MAX_VALUE) { - activeRequest?.let { - if (it.keepTicks <= 0 && it.decayTicks <= 0) { - logger.debug("Clearing active request", it) - activeRequest = null - } - } - } - - listen(priority = Int.MIN_VALUE) { - usingBaritoneRotation = false - activeRequest?.let { request -> - request.age++ - } - changedThisTick = false - } - - listen { - if (activeRequest == null) return@listen - it.cancel() - - val eye = player.eyePos - val entityHit = player.rotation.rayCast(player.entityInteractionRange, eye).orMiss - mc.targetedEntity = (entityHit as? EntityHitResult)?.entity - val blockHit = player.rotation.rayCast(player.blockInteractionRange, eye).orMiss - mc.crosshairTarget = blockHit - } - - listen(priority = Int.MIN_VALUE) { event -> - val packet = event.packet - if (packet !is PlayerPositionLookS2CPacket) return@listen - - runGameScheduled { - reset(Rotation(packet.change.yaw, packet.change.pitch)) - } - } - - listenUnsafe(priority = Int.MIN_VALUE) { - reset(Rotation.ZERO) - } - - // Override user interactions with max priority - listen(priority = Int.MAX_VALUE) { - activeRotation = player.rotation - } - - listen(priority = Int.MAX_VALUE) { - activeRotation = player.rotation - } - - listen(priority = Int.MAX_VALUE) { - activeRotation = player.rotation - } - - listen(priority = Int.MAX_VALUE) { - activeRotation = player.rotation - } - - listen(priority = Int.MAX_VALUE) { - activeRotation = player.rotation - } - - return "Loaded Rotation Manager" - } - - /** - * If the [activeRequest] is from an older tick or null, and the [request]'s target rotation is not null, - * the request is accepted and set as the [activeRequest]. The [activeRotation] is then updated. - * - * @see updateActiveRotation - */ - override fun AutomatedSafeContext.handleRequest(request: RotationRequest) { - activeRequest?.let { if (it.age <= 0) return } - if (request.rotation.value != null) { - logger.debug("Accepting request", request) - activeRequest = request - updateActiveRotation() - changedThisTick = true - } - } - - /** - * If the rotation has not been changed this tick, the [activeRequest]'s target rotation is updated, and - * likewise the [activeRotation]. The [activeRequest] is then updated, ticking the [RotationRequest.keepTicks] - * and [RotationRequest.decayTicks]. - */ - @JvmStatic - fun processRotations() = runSafe { - if (activeRequest != null) activeThisTick = true - - if (!changedThisTick) { // rebuild the rotation if the same context gets used again - activeRequest?.rotation?.update() - updateActiveRotation() - } - } - - @JvmStatic - fun handleBaritoneRotation(yaw: Double, pitch: Double) { - runSafe { - usingBaritoneRotation = true - activeRequest = RotationRequest(Rotation(yaw, pitch), BaritoneManager) - updateActiveRotation() - changedThisTick = true - } - } - - /** - * Calculates and sets the optimal movement input for moving in the direction the player is facing. This - * is not the direction the rotation manager is rotated towards, but the underlying player rotation, typically - * also the camera's rotation. - */ - @JvmStatic - fun redirectStrafeInputs(input: Input) = runSafe { - if (usingBaritoneRotation) return@runSafe - - val movementYaw = movementYaw ?: return@runSafe - val playerYaw = player.yaw - - if (movementYaw.minus(playerYaw).rem(360f).let { it * it } < 0.001f) return@runSafe - - val originalStrafe = input.movementVector.x - val originalForward = input.movementVector.y - - if (originalStrafe == 0.0f && originalForward == 0.0f) return@runSafe - - val deltaYawRad = (playerYaw - movementYaw).toRadian() - - val cos = cos(deltaYawRad) - val sin = sin(deltaYawRad) - val newStrafe = originalStrafe * cos - originalForward * sin - val newForward = originalStrafe * sin + originalForward * cos - - val angle = atan2(newStrafe.toDouble(), newForward.toDouble()) - - // Define the boundaries for our 8 sectors (in radians). Each sector is 45 degrees (PI/4). - val sector = (PI / 4.0).toFloat() - val boundary = (PI / 8.0).toFloat() // The halfway point between sectors (22.5 degrees) - - var pressForward = false - var pressBackward = false - var pressLeft = false - var pressRight = false - - // Determine which 45-degree sector the angle falls into and set the corresponding keys. - when { - angle > -boundary && angle <= boundary -> { - pressForward = true - } - angle > boundary && angle <= boundary + sector -> { - pressForward = true - pressLeft = true - } - angle > boundary + sector && angle <= boundary + 2 * sector -> { - pressLeft = true - } - angle > boundary + 2 * sector && angle <= boundary + 3 * sector -> { - pressBackward = true - pressLeft = true - } - angle > boundary + 3 * sector || angle <= -(boundary + 3 * sector) -> { - pressBackward = true - } - angle > -(boundary + 3 * sector) && angle <= -(boundary + 2 * sector) -> { - pressBackward = true - pressRight = true - } - angle > -(boundary + 2 * sector) && angle <= -(boundary + sector) -> { - pressRight = true - } - angle > -(boundary + sector) && angle <= -boundary -> { - pressForward = true - pressRight = true - } - } - - input.playerInput = PlayerInput( - pressForward, - pressBackward, - pressLeft, - pressRight, - input.playerInput.jump(), - input.playerInput.sneak(), - input.playerInput.sprint() - ) - - val x = multiplier(input.playerInput.left(), input.playerInput.right()) - val y = multiplier(input.playerInput.forward(), input.playerInput.backward()) - input.movementVector = Vec2f(x, y).normalize() - } - - private fun multiplier(positive: Boolean, negative: Boolean) = - ((if (positive) 1 else 0) - (if (negative) 1 else 0)).toFloat() - - fun onRotationSend() { - prevServerRotation = serverRotation - serverRotation = activeRotation - - if (activeRequest?.rotationConfig?.rotationMode == RotationMode.Lock) { - mc.player?.yaw = serverRotation.yawF - mc.player?.pitch = serverRotation.pitchF - } - } - - /** - * Updates the [activeRotation]. If [activeRequest] is null, the player's rotation is used. - * Otherwise, the [serverRotation] is interpolated towards the [RotationRequest.target] rotation. - */ - private fun SafeContext.updateActiveRotation() { - activeRotation = activeRequest?.let { active -> - val rotationTo = if (active.keepTicks >= 0) - active.rotation.value ?: activeRotation // the same context gets used again && the rotation is null this tick - else player.rotation - - if (active.keepTicks-- <= 0) { - active.decayTicks-- - } - - // Important: do NOT wrap the result yaw; keep it continuous to match vanilla packets - serverRotation.slerp(rotationTo, active.rotationConfig.turnSpeed) - } ?: player.rotation - - logger.debug("Active rotation set to $activeRotation", activeRequest) - } - - private fun reset(rotation: Rotation) { - logger.debug("Resetting values with rotation $rotation") - prevServerRotation = rotation - serverRotation = rotation - activeRotation = rotation - activeRequest = null - } - - @JvmStatic - val lockRotation - get() = activeRequest?.let { active -> - activeRotation.takeIf { active.rotationConfig.rotationMode == RotationMode.Lock && active.keepTicks > 0 && active.decayTicks > 0 } - } - - @JvmStatic - val headYaw - get() = activeRotation.yawF.takeIf { activeRequest != null } - - @JvmStatic - val headPitch - get() = activeRotation.pitchF.takeIf { activeRequest != null } - - @JvmStatic - val handYaw - get() = activeRotation.yawF.takeIf { activeRequest?.rotationConfig?.rotationMode == RotationMode.Lock } - - @JvmStatic - val handPitch - get() = activeRotation.pitchF.takeIf { activeRequest?.rotationConfig?.rotationMode == RotationMode.Lock } - - @JvmStatic - val movementYaw: Float? - get() { - return if (activeRequest == null || activeRequest?.rotationConfig?.rotationMode == RotationMode.Silent) null - else activeRotation.yaw.toFloat() - } - - @JvmStatic - val movementPitch: Float? - get() { - return if (activeRequest == null || activeRequest?.rotationConfig?.rotationMode == RotationMode.Silent) null - else activeRotation.pitch.toFloat() - } - - @JvmStatic - fun getRotationForVector(deltaTime: Double): Vec2d? { - if (activeRequest == null || activeRequest?.rotationConfig?.rotationMode == RotationMode.Silent) return null - - val rot = lerp(deltaTime, serverRotation, activeRotation) - return Vec2d(rot.yaw, rot.pitch) - } +) { + var pitchRequest + get() = requests[0] as? IRotationRequest.PitchRot + set(value) { requests[0] = value } + var yawRequest + get() = requests[1] as? IRotationRequest.YawRot + set(value) { requests[1] = value } + @JvmStatic val requests = mutableListOf(null, null) + + private var usingBaritoneRotation = false + @JvmStatic var activeRotation = Rotation.ZERO + @JvmStatic var serverRotation = Rotation.ZERO + @JvmStatic var prevServerRotation = Rotation.ZERO + + private var changedThisTick = false + + private val IRotationRequest.overridable get() = age >= 1 + + override fun load(): String { + super.load() + + listen(priority = Int.MAX_VALUE) { + requests.forEachIndexed { index, request -> + if (request == null) return@forEachIndexed + if (request.keepTicks <= 0 && request.decayTicks <= 0) { + requests[index] = null + } + } + } + + listen(priority = Int.MIN_VALUE) { + usingBaritoneRotation = false + requests.forEach { request -> + request?.age++ + } + changedThisTick = false + } + + listen { + if (requests.any { request -> request == null }) return@listen + it.cancel() + + val eye = player.eyePos + val entityHit = player.rotation.rayCast(player.entityInteractionRange, eye).orMiss + mc.targetedEntity = (entityHit as? EntityHitResult)?.entity + val blockHit = player.rotation.rayCast(player.blockInteractionRange, eye).orMiss + mc.crosshairTarget = blockHit + } + + listen(priority = Int.MIN_VALUE) { event -> + val packet = event.packet + if (packet !is PlayerPositionLookS2CPacket) return@listen + + runGameScheduled { + reset(Rotation(packet.change.yaw, packet.change.pitch)) + } + } + + listenUnsafe(priority = Int.MIN_VALUE) { + reset(Rotation.ZERO) + } + + // Override user interactions with max priority + listen(priority = Int.MAX_VALUE) { activeRotation = player.rotation } + listen(priority = Int.MAX_VALUE) { activeRotation = player.rotation } + listen(priority = Int.MAX_VALUE) { activeRotation = player.rotation } + listen(priority = Int.MAX_VALUE) { activeRotation = player.rotation } + listen(priority = Int.MAX_VALUE) { activeRotation = player.rotation } + + return "Loaded Rotation Manager" + } + + /** + * If the [activeRequest] is from an older tick or null, and the [request]'s target rotation is not null, + * the request is accepted and set as the [activeRequest]. The [activeRotation] is then updated. + * + * @see updateActiveRotation + */ + override fun AutomatedSafeContext.handleRequest(request: RotationRequest) { + if (acceptAndSetRequests(request)) { + updateActiveRotation() + changedThisTick = true + } + } + + private fun acceptAndSetRequests(request: RotationRequest) = + when (request) { + is IRotationRequest.Full -> + (request.rotation.value != null && requests.all { it?.overridable != false }).also { accepted -> + if (accepted) { + pitchRequest = request + yawRequest = request + } + } + is IRotationRequest.Yaw -> + (request.yaw.value != null && yawRequest?.overridable != false).also { accepted -> + if (accepted) yawRequest = request + } + is IRotationRequest.Pitch -> + (request.pitch.value != null && pitchRequest?.overridable != false).also { accepted -> + if (accepted) pitchRequest = request + } + else -> false + } + + context(safeContext: SafeContext) + fun setPlayerYaw(yaw: Double) { + if (lockYaw == null) safeContext.player.yaw = yaw.toFloat() + } + + context(safeContext: SafeContext) + fun setPlayerPitch(pitch: Double) { + if (lockPitch == null) safeContext.player.pitch = pitch.toFloat() + } + + context(safeContext: SafeContext) + fun setPlayerRotation(rotation: Rotation) { + setPlayerYaw(rotation.yaw) + setPlayerPitch(rotation.pitch) + } + + /** + * If the rotation has not been changed this tick, the [activeRequest]'s target rotation is updated, and + * likewise the [activeRotation]. The [activeRequest] is then updated, ticking the [RotationRequest.keepTicks] + * and [RotationRequest.decayTicks]. + */ + @JvmStatic + fun processRotations() = runSafe { + if (requests.any { it != null }) activeThisTick = true + + if (!changedThisTick) { // rebuild the rotation if the same context gets used again + requests.forEach { request -> request?.updateRotation() } + updateActiveRotation() + } + } + + @JvmStatic + fun handleBaritoneRotation(yaw: Double, pitch: Double) { + runSafe { + usingBaritoneRotation = true + val request = IRotationRequest.Full(BaritoneManager) { Rotation(yaw, pitch) } + yawRequest = request + pitchRequest = request + updateActiveRotation() + changedThisTick = true + } + } + + /** + * Calculates and sets the optimal movement input for moving in the direction the player is facing. This + * is not the direction the rotation manager is rotated towards, but the underlying player rotation, typically + * also the camera's rotation. + */ + @JvmStatic + fun redirectStrafeInputs(input: Input) = runSafe { + if (usingBaritoneRotation) return@runSafe + + val movementYaw = movementYaw ?: return@runSafe + val playerYaw = player.yaw + + if (movementYaw.minus(playerYaw).rem(360f).let { it * it } < 0.001f) return@runSafe + + val originalStrafe = input.movementVector.x + val originalForward = input.movementVector.y + + if (originalStrafe == 0.0f && originalForward == 0.0f) return@runSafe + + val deltaYawRad = (playerYaw - movementYaw).toRadian() + + val cos = cos(deltaYawRad) + val sin = sin(deltaYawRad) + val newStrafe = originalStrafe * cos - originalForward * sin + val newForward = originalStrafe * sin + originalForward * cos + + val angle = atan2(newStrafe.toDouble(), newForward.toDouble()) + + // Define the boundaries for our 8 sectors (in radians). Each sector is 45 degrees (PI/4). + val sector = (PI / 4.0).toFloat() + val boundary = (PI / 8.0).toFloat() // The halfway point between sectors (22.5 degrees) + + var pressForward = false + var pressBackward = false + var pressLeft = false + var pressRight = false + + // Determine which 45-degree sector the angle falls into and set the corresponding keys. + when { + angle > -boundary && angle <= boundary -> { + pressForward = true + } + angle > boundary && angle <= boundary + sector -> { + pressForward = true + pressLeft = true + } + angle > boundary + sector && angle <= boundary + 2 * sector -> { + pressLeft = true + } + angle > boundary + 2 * sector && angle <= boundary + 3 * sector -> { + pressBackward = true + pressLeft = true + } + angle > boundary + 3 * sector || angle <= -(boundary + 3 * sector) -> { + pressBackward = true + } + angle > -(boundary + 3 * sector) && angle <= -(boundary + 2 * sector) -> { + pressBackward = true + pressRight = true + } + angle > -(boundary + 2 * sector) && angle <= -(boundary + sector) -> { + pressRight = true + } + angle > -(boundary + sector) && angle <= -boundary -> { + pressForward = true + pressRight = true + } + } + + input.playerInput = PlayerInput( + pressForward, + pressBackward, + pressLeft, + pressRight, + input.playerInput.jump(), + input.playerInput.sneak(), + input.playerInput.sprint() + ) + + val x = multiplier(input.playerInput.left(), input.playerInput.right()) + val y = multiplier(input.playerInput.forward(), input.playerInput.backward()) + input.movementVector = Vec2f(x, y).normalize() + } + + private fun multiplier(positive: Boolean, negative: Boolean) = + ((if (positive) 1 else 0) - (if (negative) 1 else 0)).toFloat() + + @JvmStatic fun onRotationSend() { + prevServerRotation = serverRotation + serverRotation = activeRotation + + if (yawRequest?.rotationConfig?.rotationMode == RotationMode.Lock) + mc.player?.yaw = serverRotation.yawF + if (pitchRequest?.rotationConfig?.rotationMode == RotationMode.Lock) + mc.player?.pitch = serverRotation.pitchF + } + + /** + * Updates the [activeRotation]. If [activeRequest] is null, the player's rotation is used. + * Otherwise, the [serverRotation] is interpolated towards the [RotationRequest.target] rotation. + */ + private fun SafeContext.updateActiveRotation() { + val newYaw = yawRequest?.let { yawRequest -> + val toYaw = if (yawRequest.keepTicks >= 0) + yawRequest.yaw.value ?: activeRotation.yaw + else player.rotation.yaw + serverRotation.slerpYaw(toYaw, yawRequest.rotationConfig.turnSpeed) + } ?: player.rotation.yaw + + val newPitch = pitchRequest?.let { pitchRequest -> + val toPitch = if (pitchRequest.keepTicks >= 0) + pitchRequest.pitch.value ?: activeRotation.pitch + else player.rotation.pitch + serverRotation.slerpPitch(toPitch, pitchRequest.rotationConfig.turnSpeed) + } ?: player.rotation.pitch + + requests.forEach { request -> + if (request == null) return@forEach + if (request.keepTicks-- <= 0) { + request.decayTicks-- + } + } + + activeRotation = Rotation(newYaw, newPitch) + } + + private fun reset(rotation: Rotation) { + prevServerRotation = rotation + serverRotation = rotation + activeRotation = rotation + pitchRequest = null + yawRequest = null + } + + @JvmStatic + val lockRotation: Rotation? + get() = runSafe { + val pitch = activeRotation.pitchF.takeIf { pitchRequest?.rotationConfig?.rotationMode == RotationMode.Lock } + val yaw = activeRotation.yawF.takeIf { yawRequest?.rotationConfig?.rotationMode == RotationMode.Lock } + if (pitch == null && yaw == null) return@runSafe null + Rotation(yaw ?: player.yaw, pitch ?: player.pitch) + } + + @JvmStatic + val lockYaw: Double? + get() = runSafe { activeRotation.yaw.takeIf { yawRequest?.rotationConfig?.rotationMode == RotationMode.Lock } } + + @JvmStatic + val lockPitch: Double? + get() = runSafe { activeRotation.pitch.takeIf { pitchRequest?.rotationConfig?.rotationMode == RotationMode.Lock } } + + @JvmStatic + val headYaw + get() = activeRotation.yawF.takeIf { yawRequest != null } + + @JvmStatic + val headPitch + get() = activeRotation.pitchF.takeIf { pitchRequest != null } + + @JvmStatic + val handYaw + get() = activeRotation.yawF.takeIf { yawRequest?.rotationConfig?.rotationMode == RotationMode.Lock } + + @JvmStatic + val handPitch + get() = activeRotation.pitchF.takeIf { pitchRequest?.rotationConfig?.rotationMode == RotationMode.Lock } + + @JvmStatic + val movementYaw: Float? + get() { + return if (yawRequest == null || yawRequest?.rotationConfig?.rotationMode == RotationMode.Silent) null + else activeRotation.yaw.toFloat() + } + + @JvmStatic + val movementPitch: Float? + get() { + return if (pitchRequest == null || pitchRequest?.rotationConfig?.rotationMode == RotationMode.Silent) null + else activeRotation.pitch.toFloat() + } + + @JvmStatic + fun getRotationForVector(deltaTime: Double): Vec2d? = runSafe { + val yaw = activeRotation.yawF.takeIf { yawRequest != null && yawRequest?.rotationConfig?.rotationMode != RotationMode.Silent } + val pitch = activeRotation.pitchF.takeIf { pitchRequest != null && pitchRequest?.rotationConfig?.rotationMode != RotationMode.Silent } + if (yaw == null && pitch == null) return@runSafe null + + val rot = lerp(deltaTime, serverRotation, Rotation(yaw ?: player.yaw, pitch ?: player.pitch)) + return Vec2d(rot.yaw, rot.pitch) + } } diff --git a/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationRequest.kt b/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationRequest.kt index febb15543..64cf50573 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/rotating/RotationRequest.kt @@ -19,53 +19,179 @@ package com.lambda.interaction.managers.rotating import com.lambda.context.Automated import com.lambda.context.SafeContext -import com.lambda.interaction.managers.LogContext -import com.lambda.interaction.managers.LogContext.Companion.LogContextBuilder import com.lambda.interaction.managers.Request +import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.requestCount import com.lambda.interaction.managers.rotating.Rotation.Companion.dist +import com.lambda.interaction.managers.rotating.Rotation.Companion.wrap import com.lambda.threading.runSafe +import com.lambda.util.collections.UpdatableLazy import com.lambda.util.collections.updatableLazy +import kotlin.math.abs +import kotlin.math.hypot -data class RotationRequest( - val buildRotation: SafeContext.() -> Rotation?, - private val automated: Automated, - var keepTicks: Int = automated.rotationConfig.keepTicks, - var decayTicks: Int = automated.rotationConfig.decayTicks -) : Request(), LogContext, Automated by automated { - constructor( - rotation: Rotation, - automated: Automated, - keepTicks: Int = automated.rotationConfig.keepTicks, - decayTicks: Int = automated.rotationConfig.decayTicks - ) : this({ rotation }, automated, keepTicks, decayTicks) - - override val requestId = ++requestCount - override val tickStageMask get() = rotationConfig.tickStageMask - - val rotation = updatableLazy { - runSafe { buildRotation() } - } - - var age = 0 - override val nowOrNothing = false - - override val done: Boolean get() { - return RotationManager.activeRotation.dist(rotation.value ?: return false) <= 0.001 - } - - override fun submit(queueIfMismatchedStage: Boolean): RotationRequest = - RotationManager.request(this, queueIfMismatchedStage) - - override fun getLogContextBuilder(): LogContextBuilder.() -> Unit = { - group("Rotation Request") { - value("Request ID", requestId) - value("Age", age) - value("Keep Ticks", keepTicks) - value("Decay Ticks", decayTicks) - } - } - - companion object { - var requestCount = 0 - } -} \ No newline at end of file +@DslMarker +annotation class RotationRequestDsl + +abstract class RotationRequest(automated: Automated) : Request(), Automated by automated { + override val requestId = requestCount++ + override val tickStageMask = automated.rotationConfig.tickStageMask + override val nowOrNothing = true + + abstract infix fun dist(rotation: Rotation): Double + + @RotationRequestDsl + override fun submit(queueIfMismatchedStage: Boolean) = + RotationManager.request(this, queueIfMismatchedStage) +} + +interface IRotationRequest : Automated { + var keepTicks: Int + var decayTicks: Int + var age: Int + + val done: Boolean + + fun updateRotation() + + class Yaw( + automated: Automated, + val buildYaw: SafeContext.() -> Double + ) : RotationRequest(automated), YawRot { + override val yaw = updatableLazy { runSafe { buildYaw() } } + override var keepTicks = rotationConfig.keepTicks + override var decayTicks = rotationConfig.decayTicks + override var age = 0 + + override val done: Boolean + get() { + val delta = RotationManager.activeRotation.yaw - (yaw.value ?: return false) + val wrappedDelta = ((delta + 180) % 360 + 360) % 360 - 180 + return abs(wrappedDelta) <= 0.001 + } + + override fun dist(rotation: Rotation): Double { + return wrap((yaw.value ?: return Double.MAX_VALUE) - rotation.yaw) + } + + override fun updateRotation() = yaw.update() + } + + class Pitch( + automated: Automated, + val buildPitch: SafeContext.() -> Double + ) : RotationRequest(automated), PitchRot { + override val pitch = updatableLazy { runSafe { buildPitch() } } + override var keepTicks = rotationConfig.keepTicks + override var decayTicks = rotationConfig.decayTicks + override var age = 0 + + override val done get(): Boolean { + return abs(RotationManager.activeRotation.pitch - (pitch.value ?: return false)) <= 0.001 + } + + override fun dist(rotation: Rotation): Double { + return wrap((pitch.value ?: return Double.MAX_VALUE) - rotation.pitch) + } + + override fun updateRotation() = pitch.update() + } + + class Full( + automated: Automated, + val buildRotation: SafeContext.() -> Rotation + ) : RotationRequest(automated), FullRot { + override val rotation = updatableLazy { runSafe { buildRotation() } } + override val yaw get() = updatableLazy { rotation.value?.yaw } + override val pitch get() = updatableLazy { rotation.value?.pitch } + override var keepTicks = rotationConfig.keepTicks + override var decayTicks = rotationConfig.decayTicks + override var age = 0 + + override val done get(): Boolean { + return RotationManager.activeRotation.dist(rotation.value ?: return false) <= 0.001 + } + + override fun dist(rotation: Rotation) = + this.rotation.value?.let { + hypot( + wrap(it.yaw - rotation.yaw), + wrap(it.pitch - rotation.pitch) + ) + } ?: Double.MAX_VALUE + + override fun updateRotation() = rotation.update() + } + + interface YawRot : IRotationRequest { val yaw: UpdatableLazy } + interface PitchRot : IRotationRequest { val pitch: UpdatableLazy } + interface FullRot : YawRot, PitchRot { val rotation: UpdatableLazy } + + class RotationRequestBuilder { + var pitchBuilder: (SafeContext.() -> Double)? = null + var yawBuilder: (SafeContext.() -> Double)? = null + var rotationBuilder: (SafeContext.() -> Rotation)? = null + + @JvmName("yawBuilder1") + @RotationRequestDsl + fun yaw(builder: SafeContext.() -> Double) { yawBuilder = builder } + + @JvmName("yawBuilder2") + @RotationRequestDsl + fun yaw(builder: SafeContext.() -> Float) { yawBuilder = { builder().toDouble() } } + + @RotationRequestDsl + fun yaw(yaw: Double) { yawBuilder = { yaw } } + + @RotationRequestDsl + fun yaw(yaw: Float) { yawBuilder = { yaw.toDouble() } } + + @JvmName("pitchBuilder1") + @RotationRequestDsl + fun pitch(builder: SafeContext.() -> Double) { pitchBuilder = builder } + + @JvmName("pitchBuilder2") + @RotationRequestDsl + fun pitch(builder: SafeContext.() -> Float) { pitchBuilder = { builder().toDouble() } } + + @RotationRequestDsl + fun pitch(pitch: Double) { pitchBuilder = { pitch } } + + @RotationRequestDsl + fun pitch(pitch: Float) { pitchBuilder = { pitch.toDouble() } } + + @RotationRequestDsl + fun rotation(builder: SafeContext.() -> Rotation) { rotationBuilder = builder } + + @RotationRequestDsl + fun rotation(pitch: Double, yaw: Double) { rotationBuilder = { Rotation(pitch, yaw) } } + + @RotationRequestDsl + fun rotation(pitch: Float, yaw: Float) { rotationBuilder = { Rotation(pitch, yaw) } } + + @RotationRequestDsl + fun rotation(rotation: Rotation) { rotationBuilder = { rotation } } + } + + companion object { + var requestCount = 0 + + @RotationRequestDsl + fun Automated.rotationRequest(builder: RotationRequestBuilder.() -> Unit) = + RotationRequestBuilder().apply(builder).build() + + @RotationRequestDsl + context(automated: Automated) + private fun RotationRequestBuilder.build(): RotationRequest { + val yawBuilder = yawBuilder + val pitchBuilder = pitchBuilder + val rotationBuilder = rotationBuilder + return when { + rotationBuilder != null -> Full(automated, rotationBuilder) + yawBuilder != null && pitchBuilder != null -> Full(automated) { Rotation(yawBuilder(), pitchBuilder()) } + yawBuilder != null -> Yaw(automated, yawBuilder) + pitchBuilder != null -> Pitch(automated, pitchBuilder) + else -> throw IllegalArgumentException("Must specify at least one rotation value to build a rotation request") + } + } + } +} diff --git a/src/main/kotlin/com/lambda/interaction/managers/rotating/visibilty/PlaceDirection.kt b/src/main/kotlin/com/lambda/interaction/managers/rotating/visibilty/PlaceDirection.kt index eb858d4d0..63ecfbceb 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/rotating/visibilty/PlaceDirection.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/rotating/visibilty/PlaceDirection.kt @@ -22,79 +22,80 @@ import net.minecraft.entity.Entity import net.minecraft.util.math.Direction import net.minecraft.util.math.MathHelper import net.minecraft.util.math.Vec3i +import kotlin.math.sin enum class PlaceDirection( - val rotation: Rotation, - val vector: Vec3i, + val rotation: Rotation, + val vector: Vec3i, ) { - UpNorth(-180.0, -90.0, 0, 1, -1), - UpSouth(0.0, -90.0, 0, 1, 1), - UpWest(90.0, -90.0, 1, 1, 0), - UpEast(-90.0, -90.0, -1, 1, 0), + UpNorth(-180.0, -90.0, 0, 1, -1), + UpSouth(0.0, -90.0, 0, 1, 1), + UpWest(90.0, -90.0, 1, 1, 0), + UpEast(-90.0, -90.0, -1, 1, 0), - DownNorth(-180.0, 90.0, 0, -1, -1), - DownSouth(0.0, 90.0, 0, -1, 1), - DownWest(90.0, 90.0, 1, -1, 0), - DownEast(-90.0, 90.0, -1, -1, 0), + DownNorth(-180.0, 90.0, 0, -1, -1), + DownSouth(0.0, 90.0, 0, -1, 1), + DownWest(90.0, 90.0, 1, -1, 0), + DownEast(-90.0, 90.0, -1, -1, 0), - North(-180.0, 0.0, 0, 0, -1), - South(0.0, 0.0, 0, 0, 1), - West(90.0, 0.0, 1, 0, 0), - East(-90.0, 0.0, -1, 0, 0); + North(-180.0, 0.0, 0, 0, -1), + South(0.0, 0.0, 0, 0, 1), + West(90.0, 0.0, 1, 0, 0), + East(-90.0, 0.0, -1, 0, 0); - constructor(yaw: Double, pitch: Double, x: Int, y: Int, z: Int) - : this(Rotation(yaw, pitch), Vec3i(x, y, z)) + constructor(yaw: Double, pitch: Double, x: Int, y: Int, z: Int) + : this(Rotation(yaw, pitch), Vec3i(x, y, z)) - //ToDo: snap to the area not the cardinal to avoid excess rotation distance - fun snapToArea(rot: Rotation): Rotation = rotation + //ToDo: snap to the area not the cardinal to avoid excess rotation distance + fun snapToArea(rot: Rotation): Rotation = rotation - fun isInArea(rot: Rotation) = fromRotation(rot) == this + fun isInArea(rot: Rotation) = fromRotation(rot) == this - companion object { - /** - * A modified version of the minecraft getEntityFacingOrder method. This version takes a - * [Rotation] instead of an [Entity] - * - * @see Direction.getEntityFacingOrder - */ - fun fromRotation(rotation: Rotation): PlaceDirection { - val pitchRad = rotation.pitchF * (Math.PI.toFloat() / 180f) - val yawRad = -rotation.yawF * (Math.PI.toFloat() / 180f) + companion object { + /** + * A modified version of the minecraft getEntityFacingOrder method. This version takes a + * [Rotation] instead of an [Entity] + * + * @see Direction.getEntityFacingOrder + */ + fun fromRotation(rotation: Rotation): PlaceDirection { + val pitchRad = rotation.pitchF * (Math.PI.toFloat() / 180f).toDouble() + val yawRad = -rotation.yawF * (Math.PI.toFloat() / 180f).toDouble() - val sinPitch = MathHelper.sin(pitchRad) - val cosPitch = MathHelper.cos(pitchRad) - val sinYaw = MathHelper.sin(yawRad) - val cosYaw = MathHelper.cos(yawRad) + val sinPitch = MathHelper.sin(pitchRad) + val cosPitch = MathHelper.cos(pitchRad) + val sinYaw = MathHelper.sin(yawRad) + val cosYaw = MathHelper.cos(yawRad) - val isFacingEast = sinYaw > 0.0f - val isFacingUp = sinPitch < 0.0f - val isFacingSouth = cosYaw > 0.0f + val isFacingEast = sinYaw > 0.0f + val isFacingUp = sinPitch < 0.0f + val isFacingSouth = cosYaw > 0.0f - val eastWestStrength = if (isFacingEast) sinYaw else -sinYaw - val upDownStrength = if (isFacingUp) -sinPitch else sinPitch - val northSouthStrength = if (isFacingSouth) cosYaw else -cosYaw + val eastWestStrength = if (isFacingEast) sinYaw else -sinYaw + val upDownStrength = if (isFacingUp) -sinPitch else sinPitch + val northSouthStrength = if (isFacingSouth) cosYaw else -cosYaw - val adjustedEastWestStrength = eastWestStrength * cosPitch - val adjustedNorthSouthStrength = northSouthStrength * cosPitch + val adjustedEastWestStrength = eastWestStrength * cosPitch + val adjustedNorthSouthStrength = northSouthStrength * cosPitch - return when { - eastWestStrength > northSouthStrength -> when { - upDownStrength > adjustedEastWestStrength -> when { - isFacingUp && isFacingEast -> UpEast - isFacingUp -> UpWest - isFacingEast -> DownEast - else -> DownWest - } - else -> if (isFacingEast) East else West - } - upDownStrength > adjustedNorthSouthStrength -> when { - isFacingUp && isFacingSouth -> UpSouth - isFacingUp -> UpNorth - isFacingSouth -> DownSouth - else -> DownNorth - } - else -> if (isFacingSouth) South else North - } - } - } + return when { + eastWestStrength > northSouthStrength -> when { + upDownStrength > adjustedEastWestStrength -> when { + isFacingUp && isFacingEast -> UpEast + isFacingUp -> UpWest + isFacingEast -> DownEast + else -> DownWest + } + else -> if (isFacingEast) East else West + } + upDownStrength > adjustedNorthSouthStrength -> when { + isFacingUp && isFacingSouth -> UpSouth + isFacingUp -> UpNorth + isFacingSouth -> DownSouth + else -> DownNorth + } + else -> if (isFacingSouth) South else North + } + } + } } diff --git a/src/main/kotlin/com/lambda/interaction/managers/rotating/visibilty/RotationTargets.kt b/src/main/kotlin/com/lambda/interaction/managers/rotating/visibilty/RotationTargets.kt index 35bcfcb97..483b34396 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/rotating/visibilty/RotationTargets.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/rotating/visibilty/RotationTargets.kt @@ -39,29 +39,29 @@ annotation class RotationDsl @RotationDsl fun SafeContext.lookAt(pos: Vec3d): Rotation { - val direction = pos.subtract(player.eyePos).normalize() - val yaw = Math.toDegrees(atan2(direction.z, direction.x)) - 90.0 - val pitch = -Math.toDegrees(atan2(direction.y, hypot(direction.x, direction.z))) - return Rotation(yaw, pitch) + val direction = pos.subtract(player.eyePos).normalize() + val yaw = Math.toDegrees(atan2(direction.z, direction.x)) - 90.0 + val pitch = -Math.toDegrees(atan2(direction.y, hypot(direction.x, direction.z))) + return Rotation(yaw, pitch) } @RotationDsl fun SafeContext.lookInDirection(direction: PlaceDirection) = - if (!direction.isInArea(player.rotation)) direction.snapToArea(RotationManager.activeRotation) - else player.rotation + if (!direction.isInArea(player.rotation)) direction.snapToArea(RotationManager.activeRotation) + else player.rotation @RotationDsl fun AutomatedSafeContext.lookAtHit(hit: HitResult) = - when (hit) { - is BlockHitResult -> lookAtBlock(hit.blockPos, setOf(hit.side)) - is EntityHitResult -> lookAtEntity(hit.entity) - else -> null - } + when (hit) { + is BlockHitResult -> lookAtBlock(hit.blockPos, setOf(hit.side)) + is EntityHitResult -> lookAtEntity(hit.entity) + else -> null + } @RotationDsl fun AutomatedSafeContext.lookAtEntity(entity: Entity, sides: Set = ALL_SIDES) = - entity.findRotation(buildConfig.entityReach, player.eyePos, sides) + entity.findRotation(buildConfig.entityReach, player.eyePos, sides) @RotationDsl fun AutomatedSafeContext.lookAtBlock(pos: BlockPos, sides: Set = ALL_SIDES) = - pos.findRotation(buildConfig.blockReach, player.eyePos, sides) \ No newline at end of file + pos.findRotation(buildConfig.blockReach, player.eyePos, sides) \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/managers/rotating/visibilty/VisibilityChecker.kt b/src/main/kotlin/com/lambda/interaction/managers/rotating/visibilty/VisibilityChecker.kt index 62346494b..7450e3cee 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/rotating/visibilty/VisibilityChecker.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/rotating/visibilty/VisibilityChecker.kt @@ -48,291 +48,291 @@ import kotlin.math.pow * Object for handling visibility checks, rotation calculations, and hit detection. */ object VisibilityChecker { - val ALL_SIDES = Direction.entries.toSet() + val ALL_SIDES = Direction.entries.toSet() - /** - * Finds a rotation that intersects with one of the specified bounding boxes, allowing the player to look at entities or blocks. - * To increase the stability, it will pause the rotation if eye position is within any of the bounding boxes - * - * @param reach The maximum reach distance for the interaction. - * @param pov The player's eye position. - * @param sides Set of block sides to consider for targeting. - * @param verify A lambda to verify if a [CheckedHit] meets the desired criteria. - * - * @return A [CheckedHit] if a valid rotation was found; otherwise, null. - */ - context(automatedSafeContext: AutomatedSafeContext) - fun Entity.findRotation( - reach: Double, - pov: Vec3d, - sides: Set = ALL_SIDES, - preProcessing: PreProcessingData? = null, - allowInsideBox: Boolean = false, - verify: (CheckedHit.() -> Boolean)? = null - ): CheckedHit? = with (automatedSafeContext) { - if (boundingBox.contains(pov)) { - val currentRotation = RotationManager.activeRotation - currentRotation.rayCast(reach, pov)?.let { hit -> - return CheckedHit(hit, currentRotation) - } - } + /** + * Finds a rotation that intersects with one of the specified bounding boxes, allowing the player to look at entities or blocks. + * To increase the stability, it will pause the rotation if eye position is within any of the bounding boxes + * + * @param reach The maximum reach distance for the interaction. + * @param pov The player's eye position. + * @param sides Set of block sides to consider for targeting. + * @param verify A lambda to verify if a [CheckedHit] meets the desired criteria. + * + * @return A [CheckedHit] if a valid rotation was found; otherwise, null. + */ + context(automatedSafeContext: AutomatedSafeContext) + fun Entity.findRotation( + reach: Double, + pov: Vec3d, + sides: Set = ALL_SIDES, + preProcessing: PreProcessingData? = null, + allowInsideBox: Boolean = false, + verify: (CheckedHit.() -> Boolean)? = null + ): CheckedHit? = with (automatedSafeContext) { + if (boundingBox.contains(pov)) { + val currentRotation = RotationManager.activeRotation + currentRotation.rayCast(reach, pov)?.let { hit -> + return CheckedHit(hit, currentRotation) + } + } - val reachSq = reach.pow(2) + val reachSq = reach.pow(2) - val validHits = mutableSetOf() - boundingBox.scanClosestPoints(pov, sides, preProcessing, allowInsideBox) { pos, _ -> - if (pov distSq pos > reachSq) return@scanClosestPoints + val validHits = mutableSetOf() + boundingBox.scanClosestPoints(pov, sides, preProcessing, allowInsideBox) { pos, _ -> + if (pov distSq pos > reachSq) return@scanClosestPoints - val newRotation = pov.rotationTo(pos) - val hit = if (buildConfig.strictRayCast) newRotation.rayCast(reach, pov) ?: return@scanClosestPoints - else EntityHitResult(this@findRotation, pos) + val newRotation = pov.rotationTo(pos) + val hit = if (buildConfig.strictRayCast) newRotation.rayCast(reach, pov) ?: return@scanClosestPoints + else EntityHitResult(this@findRotation, pos) - if (hit.entityResult?.entity != this@findRotation) return@scanClosestPoints + if (hit.entityResult?.entity != this@findRotation) return@scanClosestPoints - val checkedHit = CheckedHit(hit, newRotation) - if (verify?.invoke(checkedHit) != false) validHits.add(checkedHit) - } - return buildConfig.pointSelection.select(validHits) - } + val checkedHit = CheckedHit(hit, newRotation) + if (verify?.invoke(checkedHit) != false) validHits.add(checkedHit) + } + return buildConfig.pointSelection.select(validHits) + } - context(automatedSafeContext: AutomatedSafeContext) - fun BlockPos.findRotation( - reach: Double, - pov: Vec3d, - sides: Set = ALL_SIDES, - preProcessing: PreProcessingData? = null, - allowInsideBox: Boolean = false, - verify: (CheckedHit.() -> Boolean)? = null - ): CheckedHit? = with (automatedSafeContext) { - val shape = blockState(this@findRotation) - .getOutlineShape(world, this@findRotation) - .offset(this@findRotation) + context(automatedSafeContext: AutomatedSafeContext) + fun BlockPos.findRotation( + reach: Double, + pov: Vec3d, + sides: Set = ALL_SIDES, + preProcessing: PreProcessingData? = null, + allowInsideBox: Boolean = false, + verify: (CheckedHit.() -> Boolean)? = null + ): CheckedHit? = with (automatedSafeContext) { + val shape = blockState(this@findRotation) + .getOutlineShape(world, this@findRotation) + .offset(this@findRotation) - if (shape.boundingBoxes.any { it.contains(pov) }) { - val currentRotation = RotationManager.activeRotation - currentRotation.rayCast(reach, pov)?.let { hit -> - return CheckedHit(hit, currentRotation) - } - } + if (shape.boundingBoxes.any { it.contains(pov) }) { + val currentRotation = RotationManager.activeRotation + currentRotation.rayCast(reach, pov)?.let { hit -> + return CheckedHit(hit, currentRotation) + } + } - val reachSq = reach.pow(2) + val reachSq = reach.pow(2) - val validHits = mutableSetOf() - shape.boundingBoxes.forEach { box -> - box.scanClosestPoints(pov, sides, preProcessing, allowInsideBox) { pos, side -> - if (pov distSq pos > reachSq) return@scanClosestPoints + val validHits = mutableSetOf() + shape.boundingBoxes.forEach { box -> + box.scanClosestPoints(pov, sides, preProcessing, allowInsideBox) { pos, side -> + if (pov distSq pos > reachSq) return@scanClosestPoints - val newRotation = pov.rotationTo(pos) - val hit = if (buildConfig.strictRayCast) newRotation.rayCast(reach, pov) ?: return@scanClosestPoints - else BlockHitResult(pos, side, this@findRotation, interactConfig.airPlace.isEnabled) + val newRotation = pov.rotationTo(pos) + val hit = if (buildConfig.strictRayCast) newRotation.rayCast(reach, pov) ?: return@scanClosestPoints + else BlockHitResult(pos, side, this@findRotation, interactConfig.airPlace.isEnabled) - if (hit.blockResult?.blockPos != this@findRotation) return@scanClosestPoints + if (hit.blockResult?.blockPos != this@findRotation) return@scanClosestPoints - val checkedHit = CheckedHit(hit, newRotation) - if (verify?.invoke(checkedHit) != false) validHits.add(checkedHit) - } - } - return buildConfig.pointSelection.select(validHits) - } + val checkedHit = CheckedHit(hit, newRotation) + if (verify?.invoke(checkedHit) != false) validHits.add(checkedHit) + } + } + return buildConfig.pointSelection.select(validHits) + } - /** - * Scans the surfaces of a given box on the [sides] specified - * and executes a callback for each point calculated based on the scanning parameters. - * - * @param sides A set of sides to scan - * @param resolution The number of intervals into which each dimension is divided for scanning (default is 5). - * @param preProcessing Configuration specifying the axis and mode of the scan. - * @param check A callback function that performs an action for each surface point, receiving the direction of the surface and the current 3D vector. - */ - context(_: Automated) - fun Box.scanSurfaces( - pov: Vec3d, - sides: Collection, - resolution: Int = 5, - preProcessing: PreProcessingData? = null, - allowInsideBox: Boolean = false, - check: (Vec3d, Direction) -> Unit - ) { - val visibleSides = sides.visibleSides(this, pov) - val (scanBox, invalidSides) = getScanBox(preProcessing, allowInsideBox) ?: return - (visibleSides - invalidSides).forEach { side -> - val (minX, minY, minZ, maxX, maxY, maxZ) = scanBox - .offset(side.doubleVector.multiply(DEFAULT.shrinkFactor)) - .bounds(side) + /** + * Scans the surfaces of a given box on the [sides] specified + * and executes a callback for each point calculated based on the scanning parameters. + * + * @param sides A set of sides to scan + * @param resolution The number of intervals into which each dimension is divided for scanning (default is 5). + * @param preProcessing Configuration specifying the axis and mode of the scan. + * @param check A callback function that performs an action for each surface point, receiving the direction of the surface and the current 3D vector. + */ + context(_: Automated) + fun Box.scanSurfaces( + pov: Vec3d, + sides: Collection, + resolution: Int = 5, + preProcessing: PreProcessingData? = null, + allowInsideBox: Boolean = false, + check: (Vec3d, Direction) -> Unit + ) { + val visibleSides = sides.visibleSides(this, pov) + val (scanBox, invalidSides) = getScanBox(preProcessing, allowInsideBox) ?: return + (visibleSides - invalidSides).forEach { side -> + val (minX, minY, minZ, maxX, maxY, maxZ) = scanBox + .offset(side.doubleVector.multiply(DEFAULT.shrinkFactor)) + .bounds(side) - val stepX = (maxX - minX) / resolution - val stepY = (maxY - minY) / resolution - val stepZ = (maxZ - minZ) / resolution + val stepX = (maxX - minX) / resolution + val stepY = (maxY - minY) / resolution + val stepZ = (maxZ - minZ) / resolution - (0..resolution).forEach outer@{ i -> - val x = if (stepX != 0.0) minX + (stepX * i) else minX - (0..resolution).forEach inner@{ j -> - val y = if (stepY != 0.0) minY + (stepY * j) else minY - val z = if (stepZ != 0.0) minZ + stepZ * ((if (stepX != 0.0) j else i)) else minZ - check(Vec3d(x, y, z), side) - } - } - } - } + (0..resolution).forEach outer@{ i -> + val x = if (stepX != 0.0) minX + (stepX * i) else minX + (0..resolution).forEach inner@{ j -> + val y = if (stepY != 0.0) minY + (stepY * j) else minY + val z = if (stepZ != 0.0) minZ + stepZ * ((if (stepX != 0.0) j else i)) else minZ + check(Vec3d(x, y, z), side) + } + } + } + } - context(_: Automated) - fun Box.scanClosestPoints( - pov: Vec3d, - sides: Set, - preProcessing: PreProcessingData? = null, - allowInsideBox: Boolean = false, - check: (Vec3d, Direction) -> Unit - ) { - val visibleSides = sides.visibleSides(this, pov) - val (scanBox, invalidSides) = getScanBox(preProcessing, allowInsideBox) ?: return - with(scanBox) { - (visibleSides - invalidSides).forEach { side -> - val pos = when (side) { - Direction.DOWN -> Vec3d( - pov.x.coerceIn(minX, maxX), - minY + DEFAULT.shrinkFactor, - pov.z.coerceIn(minZ, maxZ) - ) - Direction.UP -> Vec3d( - pov.x.coerceIn(minX, maxX), - maxY + DEFAULT.shrinkFactor, - pov.z.coerceIn(minZ, maxZ) - ) - Direction.NORTH -> Vec3d( - pov.x.coerceIn(minX, maxX), - pov.y.coerceIn(minY, maxY), - minZ + DEFAULT.shrinkFactor - ) - Direction.SOUTH -> Vec3d( - pov.x.coerceIn(minX, maxX), - pov.y.coerceIn(minY, maxY), - maxZ + DEFAULT.shrinkFactor - ) - Direction.WEST -> Vec3d( - minX + DEFAULT.shrinkFactor, - pov.y.coerceIn(minY, maxY), - pov.z.coerceIn(minZ, maxZ) - ) - Direction.EAST -> Vec3d( - maxX + DEFAULT.shrinkFactor, - pov.y.coerceIn(minY, maxY), - pov.z.coerceIn(minZ, maxZ) - ) - } - check(pos, side) - } - } - } + context(_: Automated) + fun Box.scanClosestPoints( + pov: Vec3d, + sides: Set, + preProcessing: PreProcessingData? = null, + allowInsideBox: Boolean = false, + check: (Vec3d, Direction) -> Unit + ) { + val visibleSides = sides.visibleSides(this, pov) + val (scanBox, invalidSides) = getScanBox(preProcessing, allowInsideBox) ?: return + with(scanBox) { + (visibleSides - invalidSides).forEach { side -> + val pos = when (side) { + Direction.DOWN -> Vec3d( + pov.x.coerceIn(minX, maxX), + minY + DEFAULT.shrinkFactor, + pov.z.coerceIn(minZ, maxZ) + ) + Direction.UP -> Vec3d( + pov.x.coerceIn(minX, maxX), + maxY + DEFAULT.shrinkFactor, + pov.z.coerceIn(minZ, maxZ) + ) + Direction.NORTH -> Vec3d( + pov.x.coerceIn(minX, maxX), + pov.y.coerceIn(minY, maxY), + minZ + DEFAULT.shrinkFactor + ) + Direction.SOUTH -> Vec3d( + pov.x.coerceIn(minX, maxX), + pov.y.coerceIn(minY, maxY), + maxZ + DEFAULT.shrinkFactor + ) + Direction.WEST -> Vec3d( + minX + DEFAULT.shrinkFactor, + pov.y.coerceIn(minY, maxY), + pov.z.coerceIn(minZ, maxZ) + ) + Direction.EAST -> Vec3d( + maxX + DEFAULT.shrinkFactor, + pov.y.coerceIn(minY, maxY), + pov.z.coerceIn(minZ, maxZ) + ) + } + check(pos, side) + } + } + } - /** - * Determines which surfaces of the box are visible from a specific position, typically the player's eyes. - * - * @param eyes The position to determine visibility from. - * @return A set of directions corresponding to visible sides. - */ - fun Box.getVisibleSurfaces(eyes: Vec3d) = - EnumSet.noneOf(Direction::class.java) - .checkAxis(eyes.x - center.x, lengthX / 2, Direction.WEST, Direction.EAST) - .checkAxis(eyes.y - center.y, lengthY / 2, Direction.DOWN, Direction.UP) - .checkAxis(eyes.z - center.z, lengthZ / 2, Direction.NORTH, Direction.SOUTH) + /** + * Determines which surfaces of the box are visible from a specific position, typically the player's eyes. + * + * @param eyes The position to determine visibility from. + * @return A set of directions corresponding to visible sides. + */ + fun Box.getVisibleSurfaces(eyes: Vec3d) = + EnumSet.noneOf(Direction::class.java) + .checkAxis(eyes.x - center.x, lengthX / 2, Direction.WEST, Direction.EAST) + .checkAxis(eyes.y - center.y, lengthY / 2, Direction.DOWN, Direction.UP) + .checkAxis(eyes.z - center.z, lengthZ / 2, Direction.NORTH, Direction.SOUTH) - private fun Box.getScanBox( - preProcessing: PreProcessingData?, - allowInsideBox: Boolean - ): Pair>? = - with(contract(DEFAULT.shrinkFactor)) { - if (preProcessing == null || preProcessing.info.surfaceScan.mode == ScanMode.Full) return Pair(this, emptySet()) + private fun Box.getScanBox( + preProcessing: PreProcessingData?, + allowInsideBox: Boolean + ): Pair>? = + with(contract(DEFAULT.shrinkFactor)) { + if (preProcessing == null || preProcessing.info.surfaceScan.mode == ScanMode.Full) return Pair(this, emptySet()) - val (newXBounds, shrunkXSide) = toScanRange(minX, maxX, preProcessing.pos.x, Direction.Axis.X, preProcessing.info.surfaceScan) - val (newYBounds, shrunkYSide) = toScanRange(minY, maxY, preProcessing.pos.y, Direction.Axis.Y, preProcessing.info.surfaceScan) - val (newZBounds, shrunkZSide) = toScanRange(minZ, maxZ, preProcessing.pos.z, Direction.Axis.Z, preProcessing.info.surfaceScan) + val (newXBounds, shrunkXSide) = toScanRange(minX, maxX, preProcessing.pos.x, Direction.Axis.X, preProcessing.info.surfaceScan) + val (newYBounds, shrunkYSide) = toScanRange(minY, maxY, preProcessing.pos.y, Direction.Axis.Y, preProcessing.info.surfaceScan) + val (newZBounds, shrunkZSide) = toScanRange(minZ, maxZ, preProcessing.pos.z, Direction.Axis.Z, preProcessing.info.surfaceScan) - if (newXBounds.isEmpty() || newYBounds.isEmpty() || newZBounds.isEmpty()) return null + if (newXBounds.isEmpty() || newYBounds.isEmpty() || newZBounds.isEmpty()) return null - val invalidSides = buildSet { - if (!allowInsideBox) { - addAll(listOfNotNull(shrunkXSide, shrunkYSide, shrunkZSide)) - } - } + val invalidSides = buildSet { + if (!allowInsideBox) { + addAll(listOfNotNull(shrunkXSide, shrunkYSide, shrunkZSide)) + } + } - val box = Box( - newXBounds.start, newYBounds.start, newZBounds.start, - newXBounds.endInclusive, newYBounds.endInclusive, newZBounds.endInclusive - ) - return Pair(box, invalidSides) - } + val box = Box( + newXBounds.start, newYBounds.start, newZBounds.start, + newXBounds.endInclusive, newYBounds.endInclusive, newZBounds.endInclusive + ) + return Pair(box, invalidSides) + } - private fun toScanRange( - min: Double, - max: Double, - origin: Int, - axis: Direction.Axis, - scan: SurfaceScan - ): Pair, Direction?> { - val range = if (scan.axis == axis) { - when (scan.mode) { - ScanMode.GreaterBlockHalf -> max(origin + 0.501, min)..max - else -> min..min(origin + 0.499, max) - } - } else min..max - return Pair( - range, - when (scan.mode) { - ScanMode.GreaterBlockHalf if range.start > min -> axis.negativeDirection - ScanMode.LesserBlockHalf if range.endInclusive < max -> axis.positiveDirection - else -> null - } - ) - } + private fun toScanRange( + min: Double, + max: Double, + origin: Int, + axis: Direction.Axis, + scan: SurfaceScan + ): Pair, Direction?> { + val range = if (scan.axis == axis) { + when (scan.mode) { + ScanMode.GreaterBlockHalf -> max(origin + 0.501, min)..max + else -> min..min(origin + 0.499, max) + } + } else min..max + return Pair( + range, + when (scan.mode) { + ScanMode.GreaterBlockHalf if range.start > min -> axis.negativeDirection + ScanMode.LesserBlockHalf if range.endInclusive < max -> axis.positiveDirection + else -> null + } + ) + } - /** - * Helper function to add visible sides to an EnumSet based on positional differences. - */ - private fun EnumSet.checkAxis( - diff: Double, - limit: Double, - negativeSide: Direction, - positiveSide: Direction, - ) = apply { - when { - diff < -limit -> add(negativeSide) - diff > limit -> add(positiveSide) - } - } + /** + * Helper function to add visible sides to an EnumSet based on positional differences. + */ + private fun EnumSet.checkAxis( + diff: Double, + limit: Double, + negativeSide: Direction, + positiveSide: Direction, + ) = apply { + when { + diff < -limit -> add(negativeSide) + diff > limit -> add(positiveSide) + } + } - /** - * Gets the bounding coordinates of a box's side, specifying min and max values for each axis. - * - * @param side The side of the box to calculate bounds for. - * @return An array of doubles representing the side's bounds. - */ - private fun Box.bounds(side: Direction) = - when (side) { - Direction.DOWN -> doubleArrayOf(minX, minY, minZ, maxX, minY, maxZ) - Direction.UP -> doubleArrayOf(minX, maxY, minZ, maxX, maxY, maxZ) - Direction.NORTH -> doubleArrayOf(minX, minY, minZ, maxX, maxY, minZ) - Direction.SOUTH -> doubleArrayOf(minX, minY, maxZ, maxX, maxY, maxZ) - Direction.WEST -> doubleArrayOf(minX, minY, minZ, minX, maxY, maxZ) - Direction.EAST -> doubleArrayOf(maxX, minY, minZ, maxX, maxY, maxZ) - } + /** + * Gets the bounding coordinates of a box's side, specifying min and max values for each axis. + * + * @param side The side of the box to calculate bounds for. + * @return An array of doubles representing the side's bounds. + */ + private fun Box.bounds(side: Direction) = + when (side) { + Direction.DOWN -> doubleArrayOf(minX, minY, minZ, maxX, minY, maxZ) + Direction.UP -> doubleArrayOf(minX, maxY, minZ, maxX, maxY, maxZ) + Direction.NORTH -> doubleArrayOf(minX, minY, minZ, maxX, maxY, minZ) + Direction.SOUTH -> doubleArrayOf(minX, minY, maxZ, maxX, maxY, maxZ) + Direction.WEST -> doubleArrayOf(minX, minY, minZ, minX, maxY, maxZ) + Direction.EAST -> doubleArrayOf(maxX, minY, minZ, maxX, maxY, maxZ) + } - /** - * Determines the sides of a box that are visible from a given position, based on interaction settings. - * - * @param box The box whose visible sides are to be determined. - * @param eye The position (e.g., the player's eyes) to determine visibility from. - * @return A set of directions corresponding to the visible sides of the box. - */ - context(automated: Automated) - private fun Collection.visibleSides( - box: Box, - eye: Vec3d - ) = if (automated.buildConfig.checkSideVisibility || automated.buildConfig.strictRayCast) { - intersect(box.getVisibleSurfaces(eye)) - } else this + /** + * Determines the sides of a box that are visible from a given position, based on interaction settings. + * + * @param box The box whose visible sides are to be determined. + * @param eye The position (e.g., the player's eyes) to determine visibility from. + * @return A set of directions corresponding to the visible sides of the box. + */ + context(automated: Automated) + private fun Collection.visibleSides( + box: Box, + eye: Vec3d + ) = if (automated.buildConfig.checkSideVisibility || automated.buildConfig.strictRayCast) { + intersect(box.getVisibleSurfaces(eye)) + } else this - class CheckedHit( - val hit: HitResult, - val rotation: Rotation - ) + class CheckedHit( + val hit: HitResult, + val rotation: Rotation + ) } diff --git a/src/main/kotlin/com/lambda/interaction/material/ContainerSelection.kt b/src/main/kotlin/com/lambda/interaction/material/ContainerSelection.kt index c4381adc6..88f8a2483 100644 --- a/src/main/kotlin/com/lambda/interaction/material/ContainerSelection.kt +++ b/src/main/kotlin/com/lambda/interaction/material/ContainerSelection.kt @@ -17,6 +17,7 @@ package com.lambda.interaction.material +import com.lambda.context.SafeContext import com.lambda.interaction.material.container.MaterialContainer /** @@ -37,8 +38,9 @@ class ContainerSelection { * which matches the given StackSelection. */ @ContainerSelectionDsl + context(_: SafeContext) fun matches(stackSelection: StackSelection): (MaterialContainer) -> Boolean = - { container -> container.matchingStacks(stackSelection).isNotEmpty() } + { container -> container.matchingSlots(stackSelection).isNotEmpty() } /** * Returns a function that checks whether a given MaterialContainer matches the criteria diff --git a/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt b/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt index 754988e64..594ac5efe 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt +++ b/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt @@ -17,7 +17,7 @@ package com.lambda.interaction.material.container -import com.lambda.context.Automated +import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext import com.lambda.core.Loadable import com.lambda.event.events.InventoryEvent @@ -36,6 +36,7 @@ import net.minecraft.block.entity.ChestBlockEntity import net.minecraft.block.entity.EnderChestBlockEntity import net.minecraft.screen.GenericContainerScreenHandler import net.minecraft.screen.ScreenHandlerType +import net.minecraft.screen.slot.Slot // ToDo: Make this a Configurable to save container caches. Should use a cached region based storage system. object ContainerManager : Loadable { @@ -45,7 +46,7 @@ object ContainerManager : Loadable { private val compileContainers = getInstances() private val runtimeContainers = mutableSetOf() - private var lastInteractedBlockEntity: BlockEntity? = null + var lastInteractedBlockEntity: BlockEntity? = null override fun load() = "Loaded ${compileContainers.size} containers" @@ -82,47 +83,73 @@ object ContainerManager : Loadable { } } + context(_: SafeContext) fun containers() = containers.flatMap { setOf(it) + it.shulkerContainer }.sorted() - context(automated: Automated, _: SafeContext) + context(automatedSafeContext: AutomatedSafeContext) fun StackSelection.transfer(destination: MaterialContainer) = - findContainerWithMaterial()?.transfer(this, destination) + with(automatedSafeContext) { + findContainerWithMaterial( + inventoryConfig.containerSelection + )?.transfer(this@transfer, destination) + ?: false + } + + context(automatedSafeContext: AutomatedSafeContext) + fun StackSelection.transferByTask(destination: MaterialContainer) = + with(automatedSafeContext) { + findContainerWithMaterial()?.transferByTask(this@transferByTask, destination) + } + context(_: SafeContext) fun findContainer( block: (MaterialContainer) -> Boolean, ): MaterialContainer? = containers().find(block) - context(_: Automated, _: SafeContext) - fun StackSelection.findContainerWithMaterial(): MaterialContainer? = - findContainersWithMaterial().firstOrNull() + context(automatedSafeContext: AutomatedSafeContext) + fun StackSelection.findContainerWithMaterial( + containerSelection: ContainerSelection = automatedSafeContext.inventoryConfig.containerSelection + ): MaterialContainer? = findContainersWithMaterial(containerSelection).firstOrNull() - context(_: Automated, _: SafeContext) - fun findContainerWithSpace(selection: StackSelection): MaterialContainer? = - findContainersWithSpace(selection).firstOrNull() - - context(automated: Automated, safeContext: SafeContext) + context(automatedSafeContext: AutomatedSafeContext) fun StackSelection.findContainersWithMaterial( - containerSelection: ContainerSelection = automated.inventoryConfig.containerSelection, + containerSelection: ContainerSelection = automatedSafeContext.inventoryConfig.containerSelection, ): List = containers() - .filter { !automated.inventoryConfig.immediateAccessOnly || it.isImmediatelyAccessible() } - .filter { it.materialAvailable(this) >= count } .filter { containerSelection.matches(it) } - .sortedWith(automated.inventoryConfig.providerPriority.materialComparator(this)) + .filter { it.materialAvailable(this) >= count } + .sortedWith(automatedSafeContext.inventoryConfig.providerPriority.materialComparator(this)) - context(automated: Automated, safeContext: SafeContext) - fun findContainersWithSpace( - selection: StackSelection, + context(automatedSafeContext: AutomatedSafeContext) + fun StackSelection.findContainerWithSpace( + containerSelection: ContainerSelection = automatedSafeContext.inventoryConfig.containerSelection + ): MaterialContainer? = findContainersWithSpace(containerSelection).firstOrNull() + + context(automatedSafeContext: AutomatedSafeContext) + fun StackSelection.findContainersWithSpace( + containerSelection: ContainerSelection = automatedSafeContext.inventoryConfig.containerSelection ): List = containers() - .filter { !automated.inventoryConfig.immediateAccessOnly || it.isImmediatelyAccessible() } - .filter { it.spaceAvailable(selection) >= selection.count } - .filter { automated.inventoryConfig.containerSelection.matches(it) } - .sortedWith(automated.inventoryConfig.providerPriority.spaceComparator(selection)) + .filter { containerSelection.matches(it) } + .filter { it.spaceAvailable(this) >= count } + .sortedWith(automatedSafeContext.inventoryConfig.providerPriority.spaceComparator(this)) + + context(automatedSafeContext: AutomatedSafeContext) + fun StackSelection.findSlotWithMaterial( + containerSelection: ContainerSelection = automatedSafeContext.inventoryConfig.containerSelection + ) = findSlotsWithMaterial(containerSelection).firstOrNull() + + context(automatedSafeContext: AutomatedSafeContext) + fun StackSelection.findSlotsWithMaterial( + containerSelection: ContainerSelection = automatedSafeContext.inventoryConfig.containerSelection + ): List = + findContainersWithMaterial(containerSelection) + .flatMap { filterSlots(it.slots) } + - context(automated: Automated) + context(automatedSafeContext: AutomatedSafeContext) fun findDisposable() = containers().find { container -> - automated.inventoryConfig.disposables.any { container.materialAvailable(it.asItem().select()) > 0 } + automatedSafeContext.inventoryConfig.disposables.any { container.materialAvailable(it.asItem().select()) > 0 } } class NoContainerFound(selection: StackSelection) : Exception("No container found matching $selection") diff --git a/src/main/kotlin/com/lambda/interaction/material/ContainerTask.kt b/src/main/kotlin/com/lambda/interaction/material/container/ExternalContainer.kt similarity index 57% rename from src/main/kotlin/com/lambda/interaction/material/ContainerTask.kt rename to src/main/kotlin/com/lambda/interaction/material/container/ExternalContainer.kt index 630c8d664..291cbbaa2 100644 --- a/src/main/kotlin/com/lambda/interaction/material/ContainerTask.kt +++ b/src/main/kotlin/com/lambda/interaction/material/container/ExternalContainer.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 Lambda + * Copyright 2026 Lambda * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,26 +15,13 @@ * along with this program. If not, see . */ -package com.lambda.interaction.material +package com.lambda.interaction.material.container -import com.lambda.event.events.TickEvent -import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.context.AutomatedSafeContext import com.lambda.task.Task +import com.lambda.task.TaskGenerator -abstract class ContainerTask : Task() { - private var finish = false - private val delay = 5 - private var currentDelay = 0 - - fun delayedFinish() { - finish = true - } - - init { - listen { - if (finish) { - if (currentDelay++ > delay) success() - } - } - } -} +interface ExternalContainer { + context(_: AutomatedSafeContext) + fun accessThen(exitAfter: Boolean = true, taskGenerator: TaskGenerator): Task<*>? +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/material/container/MaterialContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/MaterialContainer.kt index 7279c308d..4fb36b765 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/MaterialContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/material/container/MaterialContainer.kt @@ -18,15 +18,16 @@ package com.lambda.interaction.material.container import com.lambda.context.Automated +import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext -import com.lambda.event.events.TickEvent -import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.event.EventFlow.post +import com.lambda.event.events.ContainerEvent +import com.lambda.interaction.managers.inventory.InventoryRequest +import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.container.ContainerManager.findContainerWithMaterial import com.lambda.interaction.material.container.containers.ShulkerBoxContainer -import com.lambda.interaction.material.transfer.TransferResult import com.lambda.task.Task -import com.lambda.util.Communication.logError +import com.lambda.task.tasks.ContainerTransferTask import com.lambda.util.Nameable import com.lambda.util.item.ItemStackUtils.count import com.lambda.util.item.ItemStackUtils.empty @@ -40,17 +41,38 @@ import com.lambda.util.text.buildText import com.lambda.util.text.highlighted import com.lambda.util.text.literal import com.lambda.util.text.text +import net.minecraft.component.DataComponentTypes import net.minecraft.item.ItemStack +import net.minecraft.screen.slot.Slot import net.minecraft.text.Text // ToDo: Make jsonable to persistently store them abstract class MaterialContainer( val rank: Rank ) : Nameable, Comparable { + context(_: SafeContext) + abstract val slots: List abstract var stacks: List + open val swapMethodPriority = 0 abstract val description: Text + context(automated: Automated) + open val replaceSorter get() = compareByDescending { + it.stack.isEmpty + }.thenByDescending { + it.stack.item in automated.inventoryConfig.disposables + }.thenByDescending { + !it.stack.item.components.contains(DataComponentTypes.TOOL) + }.thenByDescending { + !it.stack.item.components.contains(DataComponentTypes.FOOD) + }.thenByDescending { + !it.stack.item.components.contains(DataComponentTypes.CONSUMABLE) + }.thenByDescending { + it.stack.isStackable + } + @TextDsl + context(_: SafeContext) fun TextBuilder.stock(selection: StackSelection) { literal("\n") literal("Contains ") @@ -66,6 +88,7 @@ abstract class MaterialContainer( highlighted("${selection.optimalStack?.name?.string}") } + context(_: SafeContext) fun description(selection: StackSelection) = buildText { text(description) @@ -75,20 +98,21 @@ abstract class MaterialContainer( override val name: String get() = buildText { text(description) }.string + context(_: SafeContext) val shulkerContainer get() = - stacks.filter { - it.item in ItemUtils.shulkerBoxes - }.map { stack -> + slots.filter { + it.stack.item in ItemUtils.shulkerBoxes + }.map { slot -> ShulkerBoxContainer( - stack.shulkerBoxContents, + slot.stack.shulkerBoxContents, containedIn = this@MaterialContainer, - shulkerStack = stack + shulkerSlot = slot ) }.toSet() - fun update(stacks: List) { - this.stacks = stacks + fun update(slots: List) { + this.stacks = slots } class FailureTask(override val name: String) : Task() { @@ -97,68 +121,59 @@ abstract class MaterialContainer( } } - class AwaitItemTask( - override val name: String, - val selection: StackSelection, - automated: Automated - ) : Task(), Automated by automated { - init { - listen { - if (selection.findContainerWithMaterial() != null) { - success() - } - } + context(_: SafeContext) + open fun InventoryRequest.InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { + if (fromHere.stack.isEmpty) pickupAndPlace(toSlot.id, fromHere.id) + else { + pickupAndPlace(fromHere.id, toSlot.id) + if (!toSlot.stack.isEmpty) pickup(fromHere.id) } + } - override fun SafeContext.onStart() { - logError(name) + context(automatedSafeContext: AutomatedSafeContext) + fun transfer(stackSelection: StackSelection, destination: MaterialContainer): Boolean = + with(automatedSafeContext) { + val fromSlot = getSlot(stackSelection) ?: return false + val toSlot = destination.getReplaceableSlot() ?: return false + val transferEvent = ContainerEvent.Transfer(fromSlot, toSlot, this@MaterialContainer, destination) + if (transferEvent.post().isCanceled()) return false + return inventoryRequest { + if (swapMethodPriority > destination.swapMethodPriority) transfer(fromSlot, toSlot) + else with(destination) { transfer(toSlot, fromSlot) } + }.submit().done } - } - /** - * Withdraws items from the container to the player's inventory. - */ - @Task.Ta5kBuilder - context(automated: Automated) - open fun withdraw(selection: StackSelection): Task<*>? = null + context(automatedSafeContext: AutomatedSafeContext) + fun transferByTask(stackSelection: StackSelection, destination: MaterialContainer, failIfNoMaterial: Boolean = false) = + ContainerTransferTask(this, destination, stackSelection, automatedSafeContext, failIfNoMaterial) - /** - * Deposits items from the player's inventory into the container. - */ - @Task.Ta5kBuilder - context(automated: Automated) - open fun deposit(selection: StackSelection): Task<*>? = null + protected fun InventoryRequest.InvRequestBuilder.pickupAndPlace(fromId: Int, toId: Int) { + pickup(fromId) + pickup(toId) + } + context(_: SafeContext) open fun matchingStacks(selection: StackSelection) = selection.filterStacks(stacks) + context(_: SafeContext) + open fun matchingSlots(selection: StackSelection) = + selection.filterSlots(slots) + + context(_: SafeContext) open fun materialAvailable(selection: StackSelection) = matchingStacks(selection).count + context(_: SafeContext) open fun spaceAvailable(selection: StackSelection) = matchingStacks(selection).spaceLeft + stacks.empty * selection.stackSize - context(safeContext: SafeContext) - abstract fun isImmediatelyAccessible(): Boolean - - context(automated: Automated) - fun transfer(selection: StackSelection, destination: MaterialContainer): TransferResult { - val amount = materialAvailable(selection) - if (amount < selection.count) { - return TransferResult.MissingItems(selection.count - amount) - } - -// val space = destination.spaceAvailable(selection) -// if (space == 0) { -// return TransferResult.NoSpace -// } + context(_: AutomatedSafeContext) + open fun getReplaceableSlot() = slots.sortedWith(replaceSorter).firstOrNull() -// val transferAmount = minOf(amount, space) -// selection.selector = { true } -// selection.count = transferAmount - - return TransferResult.ContainerTransfer(selection, from = this, to = destination, automated) - } + context(_: SafeContext) + open fun getSlot(stackSelection: StackSelection): Slot? = + stackSelection.filterSlots(slots).firstOrNull() enum class Rank { MainHand, diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/ChestContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/ChestContainer.kt index 1eadb0b53..2bc6f264d 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/ChestContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/material/container/containers/ChestContainer.kt @@ -17,25 +17,35 @@ package com.lambda.interaction.material.container.containers -import com.lambda.context.Automated +import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext -import com.lambda.interaction.material.StackSelection +import com.lambda.interaction.material.container.ContainerManager +import com.lambda.interaction.material.container.ExternalContainer import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.interaction.material.transfer.SlotTransfer.Companion.deposit -import com.lambda.interaction.material.transfer.SlotTransfer.Companion.withdraw -import com.lambda.task.tasks.OpenContainer -import com.lambda.util.Communication.info +import com.lambda.task.Task +import com.lambda.task.TaskGenerator +import com.lambda.task.tasks.OpenContainerTask +import com.lambda.util.extension.containerSlots import com.lambda.util.text.buildText import com.lambda.util.text.highlighted import com.lambda.util.text.literal +import net.minecraft.block.entity.ChestBlockEntity import net.minecraft.item.ItemStack +import net.minecraft.screen.slot.Slot import net.minecraft.util.math.BlockPos data class ChestContainer( override var stacks: List, val blockPos: BlockPos, val containedInStash: StashContainer? = null -) : MaterialContainer(Rank.Chest) { +) : MaterialContainer(Rank.Chest), ExternalContainer { + context(safeContext: SafeContext) + override val slots + get(): List = + if (ContainerManager.lastInteractedBlockEntity is ChestBlockEntity) + safeContext.player.currentScreenHandler.containerSlots + else emptyList() + override val description = buildText { literal("Chest at ") @@ -47,33 +57,11 @@ data class ChestContainer( } } -// override fun prepare() = -// moveIntoEntityRange(blockPos).onSuccess { _, _ -> -//// when { -//// ChestBlock.hasBlockOnTop(world, blockPos) -> breakBlock(blockPos.up()) -//// ChestBlock.hasCatOnTop(world, blockPos) -> kill(cat) -//// } -// if (ChestBlock.isChestBlocked(world, blockPos)) { -// throw ChestBlockedException() -// } -// } - - context(automated: Automated) - override fun withdraw(selection: StackSelection) = - OpenContainer(blockPos, automated) - .then { - info("Withdrawing $selection from ${it.type}") - withdraw(it, selection) - } - - context(automated: Automated) - override fun deposit(selection: StackSelection) = - OpenContainer(blockPos, automated) - .then { - info("Depositing $selection to ${it.type}") - deposit(it, selection) + context(automatedSafeContext: AutomatedSafeContext) + override fun accessThen(exitAfter: Boolean, taskGenerator: TaskGenerator): Task<*> = + OpenContainerTask(blockPos, automatedSafeContext).then { + taskGenerator.invoke(automatedSafeContext, Unit).finally { + if (exitAfter) automatedSafeContext.player.closeScreen() } - - context(safeContext: SafeContext) - override fun isImmediatelyAccessible() = false + } } diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt index 398e6f014..cce06e423 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/material/container/containers/CreativeContainer.kt @@ -17,92 +17,58 @@ package com.lambda.interaction.material.container.containers -import com.lambda.Lambda.mc -import com.lambda.context.Automated import com.lambda.context.SafeContext -import com.lambda.event.events.TickEvent -import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest +import com.lambda.interaction.managers.inventory.InventoryRequest import com.lambda.interaction.material.StackSelection import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.task.Task -import com.lambda.util.item.ItemStackUtils.equal -import com.lambda.util.player.gamemode import com.lambda.util.text.buildText import com.lambda.util.text.literal +import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.inventory.SingleStackInventory import net.minecraft.item.ItemStack +import net.minecraft.screen.PlayerScreenHandler +import net.minecraft.screen.slot.Slot data object CreativeContainer : MaterialContainer(Rank.Creative) { - override var stacks = emptyList() - - override val description = buildText { literal("Creative") } - - override fun materialAvailable(selection: StackSelection): Int = - if (mc.player?.isCreative == true && selection.optimalStack != null) Int.MAX_VALUE else 0 - - override fun spaceAvailable(selection: StackSelection): Int = - if (mc.player?.isCreative == true && selection.optimalStack != null) Int.MAX_VALUE else 0 - - class CreativeDeposit @Ta5kBuilder constructor(val selection: StackSelection, automated: Automated) : Task(), Automated by automated { - override val name: String get() = "Removing $selection from creative inventory" - - init { - listen { - if (!gamemode.isCreative) { - // ToDo: Maybe switch gamemode? - failure(NotInCreativeModeException()) - } - - inventoryRequest { - player.currentScreenHandler?.slots?.let { slots -> - selection.filterSlots(slots).forEach { - clickCreativeStack(ItemStack.EMPTY, it.id) - } - } - onComplete { success() } - }.submit(queueIfMismatchedStage = false) - } - } - } - - context(automated: Automated) - override fun deposit(selection: StackSelection) = CreativeDeposit(selection, automated) - - class CreativeWithdrawal @Ta5kBuilder constructor(val selection: StackSelection, automated: Automated) : Task(), Automated by automated { - override val name: String get() = "Withdrawing $selection from creative inventory" - - init { - listen { - selection.optimalStack?.let { optimalStack -> - if (player.mainHandStack.equal(optimalStack)) { - success() - return@listen - } - - if (!gamemode.isCreative) { - // ToDo: Maybe switch gamemode? - failure(NotInCreativeModeException()) - } - - inventoryRequest { - clickCreativeStack(optimalStack, 36 + player.inventory.selectedSlot) - action { player.inventory.selectedStack = optimalStack } - onComplete { success() } - }.submit(queueIfMismatchedStage = false) - return@listen - } - - failure(IllegalStateException("Cannot move item: no optimal stack")) - } - } - } - - // Withdraws items from the creative menu to the player's main hand - context(automated: Automated) - override fun withdraw(selection: StackSelection) = CreativeWithdrawal(selection, automated) - - context(safeContext: SafeContext) - override fun isImmediatelyAccessible() = safeContext.gamemode.isCreative - - class NotInCreativeModeException : IllegalStateException("Insufficient permission: not in creative mode") + context(_: SafeContext) + override val slots: List + get() = emptyList() + override var stacks = emptyList() + + override val swapMethodPriority = 11 + + override val description = buildText { literal("Creative") } + + context(safeContext : SafeContext) + override fun InventoryRequest.InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { + clickCreativeStack(fromHere.stack, toSlot.id) + safeContext.player.currentScreenHandler.slots[toSlot.id].stack = fromHere.stack + } + + context(safeContext: SafeContext) + override fun getSlot(stackSelection: StackSelection) = + stackSelection.optimalStack?.let { stack -> + Slot( + object : SingleStackInventory { + override fun getStack() = stack + override fun setStack(stack: ItemStack?) {} + override fun markDirty() {} + override fun canPlayerUse(player: PlayerEntity?) = false + }, + 0, 0, 0 + ) + } + + context(safeContext: SafeContext) + override fun materialAvailable(selection: StackSelection): Int = + if (safeContext.player.isCreative && correctScreenHandler && selection.optimalStack != null) Int.MAX_VALUE else -1 + + context(safeContext: SafeContext) + override fun spaceAvailable(selection: StackSelection): Int = + if (safeContext.player.isCreative && correctScreenHandler && selection.optimalStack != null) Int.MAX_VALUE else -1 + + context(safeContext: SafeContext) + private val correctScreenHandler + get() = safeContext.player.currentScreenHandler is PlayerScreenHandler || safeContext.player.currentScreenHandler is CreativeInventoryScreen.CreativeScreenHandler } diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/EnderChestContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/EnderChestContainer.kt index a290a050a..27a45f79a 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/EnderChestContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/material/container/containers/EnderChestContainer.kt @@ -17,61 +17,52 @@ package com.lambda.interaction.material.container.containers -import com.lambda.context.Automated +import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext -import com.lambda.interaction.material.StackSelection +import com.lambda.interaction.material.StackSelection.Companion.select +import com.lambda.interaction.material.container.ContainerManager +import com.lambda.interaction.material.container.ContainerManager.findSlotsWithMaterial +import com.lambda.interaction.material.container.ExternalContainer import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.task.Task -import com.lambda.util.Communication.info +import com.lambda.task.TaskGenerator +import com.lambda.task.tasks.BuildTask.Companion.breakAndCollectBlock +import com.lambda.task.tasks.OpenContainerTask +import com.lambda.task.tasks.PlaceContainerTask +import com.lambda.util.extension.containerSlots import com.lambda.util.text.buildText import com.lambda.util.text.literal +import net.minecraft.block.entity.EnderChestBlockEntity import net.minecraft.item.ItemStack +import net.minecraft.item.Items import net.minecraft.util.math.BlockPos -object EnderChestContainer : MaterialContainer(Rank.EnderChest) { - override var stacks = emptyList() - private var placePos: BlockPos? = null +object EnderChestContainer : MaterialContainer(Rank.EnderChest), ExternalContainer { + context(safeContext: SafeContext) + override val slots + get() = + if (ContainerManager.lastInteractedBlockEntity is EnderChestBlockEntity) + safeContext.player.currentScreenHandler.containerSlots + else emptyList() + override var stacks = emptyList() - override val description = buildText { literal("Ender Chest") } + override val description = buildText { literal("Ender Chest") } -// override fun prepare(): Task<*> { -// TODO("Not yet implemented") -// } -// findBlock(Blocks.ENDER_CHEST).onSuccess { pos -> -// moveIntoEntityRange(pos) -// placePos = pos -// }.onFailure { -// acquireStack(Items.ENDER_CHEST.select()).onSuccess { _, stack -> -// placeContainer(stack).onSuccess { _, pos -> -// placePos = pos -// } -// } -// } + private var placePos = BlockPos.ORIGIN - class EnderchestWithdrawal @Ta5kBuilder constructor(selection: StackSelection) : Task() { - override val name = "Withdrawing $selection from ender chest" - - override fun SafeContext.onStart() { - info("Not yet implemented") - success() - } - } - - context(automated: Automated) - override fun withdraw(selection: StackSelection) = EnderchestWithdrawal(selection) - - class EnderchestDeposit @Ta5kBuilder constructor(selection: StackSelection) : Task() { - override val name = "Depositing $selection into ender chest" - - override fun SafeContext.onStart() { - info("Not yet implemented") - success() - } - } - - context(automated: Automated) - override fun deposit(selection: StackSelection) = EnderchestDeposit(selection) - - context(safeContext: SafeContext) - override fun isImmediatelyAccessible() = false + context(automatedSafeContext: AutomatedSafeContext) + override fun accessThen(exitAfter: Boolean, taskGenerator: TaskGenerator) = + Items.ENDER_CHEST + .select() + .findSlotsWithMaterial() + .firstOrNull()?.let { slot -> + PlaceContainerTask(slot, automatedSafeContext).then { pos -> + placePos = pos + OpenContainerTask(pos, automatedSafeContext).then { + taskGenerator.invoke(automatedSafeContext, Unit).thenOrNull { + if (exitAfter) automatedSafeContext.breakAndCollectBlock(placePos, lifeMaintenance = false) + else null + } + } + } + } } diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/HotbarContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/HotbarContainer.kt index b822c098c..12b57dfa9 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/HotbarContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/material/container/containers/HotbarContainer.kt @@ -18,41 +18,29 @@ package com.lambda.interaction.material.container.containers import com.lambda.Lambda.mc -import com.lambda.context.Automated import com.lambda.context.SafeContext -import com.lambda.interaction.material.ContainerTask -import com.lambda.interaction.material.StackSelection +import com.lambda.interaction.managers.inventory.InventoryRequest import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.interaction.material.transfer.SlotTransfer.Companion.deposit -import com.lambda.util.player.SlotUtils.hotbar +import com.lambda.util.player.SlotUtils.hotbarSlots +import com.lambda.util.player.SlotUtils.hotbarStacks import com.lambda.util.text.buildText import com.lambda.util.text.literal -import net.minecraft.item.ItemStack +import net.minecraft.screen.slot.Slot object HotbarContainer : MaterialContainer(Rank.Hotbar) { - override var stacks: List - get() = mc.player?.hotbar ?: emptyList() + context(safeContext: SafeContext) + override val slots: List + get() = safeContext.player.hotbarSlots + override var stacks + get() = mc.player?.hotbarStacks ?: emptyList() set(_) {} - override val description = buildText { literal("Hotbar") } - - class HotbarDeposit @Ta5kBuilder constructor( - val selection: StackSelection, - automated: Automated - ) : ContainerTask(), Automated by automated { - override val name: String get() = "Depositing $selection into hotbar" + override val swapMethodPriority = 9 - override fun SafeContext.onStart() { - val handler = player.currentScreenHandler - deposit(handler, selection).finally { - delayedFinish() - }.execute(this@HotbarDeposit) - } - } - - context(automated: Automated) - override fun deposit(selection: StackSelection) = HotbarDeposit(selection, automated) + override val description = buildText { literal("Hotbar") } context(safeContext: SafeContext) - override fun isImmediatelyAccessible() = true + override fun InventoryRequest.InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { + swap(toSlot.id, safeContext.player.hotbarSlots.indexOf(fromHere)) + } } diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/InventoryContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/InventoryContainer.kt index b0ecc2cde..af1b0646c 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/InventoryContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/material/container/containers/InventoryContainer.kt @@ -20,18 +20,20 @@ package com.lambda.interaction.material.container.containers import com.lambda.Lambda.mc import com.lambda.context.SafeContext import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.util.player.SlotUtils.combined +import com.lambda.util.player.SlotUtils.inventorySlots +import com.lambda.util.player.SlotUtils.inventoryStacks import com.lambda.util.text.buildText import com.lambda.util.text.literal import net.minecraft.item.ItemStack +import net.minecraft.screen.slot.Slot object InventoryContainer : MaterialContainer(Rank.Inventory) { + context(safeContext: SafeContext) + override val slots: List + get() = safeContext.player.inventorySlots override var stacks: List - get() = mc.player?.combined ?: emptyList() + get() = mc.player?.inventoryStacks ?: emptyList() set(_) {} override val description = buildText { literal("Inventory") } - - context(safeContext: SafeContext) - override fun isImmediatelyAccessible() = true } diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt index 64206d15e..2b12e47de 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/material/container/containers/MainHandContainer.kt @@ -18,73 +18,29 @@ package com.lambda.interaction.material.container.containers import com.lambda.Lambda.mc -import com.lambda.context.Automated import com.lambda.context.SafeContext -import com.lambda.event.events.TickEvent -import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest -import com.lambda.interaction.material.ContainerTask -import com.lambda.interaction.material.StackSelection +import com.lambda.interaction.managers.inventory.InventoryRequest import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.util.item.ItemStackUtils.equal +import com.lambda.util.player.SlotUtils.mainHandSlots import com.lambda.util.text.buildText import com.lambda.util.text.literal import net.minecraft.item.ItemStack -import net.minecraft.util.Hand +import net.minecraft.screen.slot.Slot object MainHandContainer : MaterialContainer(Rank.MainHand) { + context(safeContext: SafeContext) + override val slots: List + get() = safeContext.player.mainHandSlots override var stacks: List get() = mc.player?.mainHandStack?.let { listOf(it) } ?: emptyList() set(_) {} - override val description = buildText { literal("MainHand") } - - class HandDeposit @Ta5kBuilder constructor( - val selection: StackSelection, - val hand: Hand, - automated: Automated - ) : ContainerTask(), Automated by automated { - override val name: String get() = "Depositing [$selection] to ${hand.name.lowercase().replace("_", " ")}" - - init { - listen { - val moveStack = InventoryContainer.matchingStacks(selection).firstOrNull() ?: run { - failure("No matching stacks found in inventory") - return@listen - } - - val handStack = player.getStackInHand(hand) - if (moveStack.equal(handStack)) { - success() - return@listen - } - - inventoryRequest { - val stackInOffHand = moveStack.equal(player.offHandStack) - val stackInMainHand = moveStack.equal(player.mainHandStack) - if ((hand == Hand.MAIN_HAND && stackInOffHand) || (hand == Hand.OFF_HAND && stackInMainHand)) { - swapHands() - return@inventoryRequest - } + override val swapMethodPriority = 10 - val slot = player.currentScreenHandler.slots.firstOrNull { it.stack == moveStack } - ?: run { - failure(IllegalStateException("Cannot find stack in inventory")) - return@inventoryRequest - } - swap(slot.id, player.inventory.selectedSlot) - - if (hand == Hand.OFF_HAND) swapHands() - - onComplete { success() } - }.submit(queueIfMismatchedStage = false) - } - } - } - - context(automated: Automated) - override fun deposit(selection: StackSelection) = HandDeposit(selection, Hand.MAIN_HAND, automated) + override val description = buildText { literal("MainHand") } context(safeContext: SafeContext) - override fun isImmediatelyAccessible() = true + override fun InventoryRequest.InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { + swap(toSlot.id, safeContext.player.inventory.selectedSlot) + } } diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/OffHandContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/OffHandContainer.kt index 3820f499b..235350717 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/OffHandContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/material/container/containers/OffHandContainer.kt @@ -18,25 +18,29 @@ package com.lambda.interaction.material.container.containers import com.lambda.Lambda.mc -import com.lambda.context.Automated import com.lambda.context.SafeContext -import com.lambda.interaction.material.StackSelection +import com.lambda.interaction.managers.inventory.InventoryRequest import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.util.player.SlotUtils.offHandSlots import com.lambda.util.text.buildText import com.lambda.util.text.literal import net.minecraft.item.ItemStack -import net.minecraft.util.Hand +import net.minecraft.screen.slot.Slot object OffHandContainer : MaterialContainer(Rank.OffHand) { + context(safeContext: SafeContext) + override val slots: List + get() = safeContext.player.offHandSlots override var stacks: List get() = mc.player?.offHandStack?.let { listOf(it) } ?: emptyList() set(_) {} - override val description = buildText { literal("OffHand") } + override val swapMethodPriority = 10 - context(automated: Automated) - override fun deposit(selection: StackSelection) = MainHandContainer.HandDeposit(selection, Hand.OFF_HAND, automated) + override val description = buildText { literal("OffHand") } - context(safeContext: SafeContext) - override fun isImmediatelyAccessible() = true + context(_: SafeContext) + override fun InventoryRequest.InvRequestBuilder.transfer(fromHere: Slot, toSlot: Slot) { + swap(toSlot.id, 40) + } } diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/ShulkerBoxContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/ShulkerBoxContainer.kt index 7307bb73d..bacee449b 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/ShulkerBoxContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/material/container/containers/ShulkerBoxContainer.kt @@ -17,83 +17,60 @@ package com.lambda.interaction.material.container.containers -import com.lambda.context.Automated +import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext -import com.lambda.interaction.material.StackSelection +import com.lambda.interaction.material.container.ContainerManager +import com.lambda.interaction.material.container.ExternalContainer import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.interaction.material.transfer.SlotTransfer.Companion.deposit -import com.lambda.interaction.material.transfer.SlotTransfer.Companion.withdraw -import com.lambda.task.Task +import com.lambda.task.TaskGenerator import com.lambda.task.tasks.BuildTask.Companion.breakAndCollectBlock -import com.lambda.task.tasks.OpenContainer -import com.lambda.task.tasks.PlaceContainer +import com.lambda.task.tasks.OpenContainerTask +import com.lambda.task.tasks.PlaceContainerTask +import com.lambda.threading.runSafe +import com.lambda.util.extension.containerSlots import com.lambda.util.text.buildText import com.lambda.util.text.highlighted import com.lambda.util.text.literal +import net.minecraft.block.entity.ShulkerBoxBlockEntity import net.minecraft.item.ItemStack +import net.minecraft.screen.slot.Slot +import net.minecraft.util.math.BlockPos data class ShulkerBoxContainer( override var stacks: List, val containedIn: MaterialContainer, - val shulkerStack: ItemStack, -) : MaterialContainer(Rank.ShulkerBox) { + val shulkerSlot: Slot, +) : MaterialContainer(Rank.ShulkerBox), ExternalContainer { + context(safeContext: SafeContext) + override val slots + get(): List = + if (ContainerManager.lastInteractedBlockEntity is ShulkerBoxBlockEntity) + safeContext.player.currentScreenHandler.containerSlots + else emptyList() + override val description = buildText { - highlighted(shulkerStack.name.string) + highlighted(shulkerSlot.stack.name.string) literal(" in ") highlighted(containedIn.name) literal(" in slot ") - highlighted("$slotInContainer") - } - - private val slotInContainer: Int get() = containedIn.stacks.indexOf(shulkerStack) - - class ShulkerWithdraw( - private val selection: StackSelection, - private val shulkerStack: ItemStack, - automated: Automated - ) : Task(), Automated by automated { - override val name = "Withdraw $selection from ${shulkerStack.name.string}" - - override fun SafeContext.onStart() { - PlaceContainer(shulkerStack, this@ShulkerWithdraw).then { placePos -> - OpenContainer(placePos, this@ShulkerWithdraw).then { screen -> - withdraw(screen, selection).then { - breakAndCollectBlock(placePos).finally { - success() - } - } - } - }.execute(this@ShulkerWithdraw) + highlighted("${runSafe { slotInContainer }}") } - } - context(automated: Automated) - override fun withdraw(selection: StackSelection) = ShulkerWithdraw(selection, shulkerStack, automated) + context(_: SafeContext) + private val slotInContainer: Int get() = containedIn.slots.indexOf(shulkerSlot) - class ShulkerDeposit( - private val selection: StackSelection, - private val shulkerStack: ItemStack, - automated: Automated - ) : Task(), Automated by automated { - override val name = "Deposit $selection into ${shulkerStack.name.string}" + private var placePos = BlockPos.ORIGIN - override fun SafeContext.onStart() { - PlaceContainer(shulkerStack, this@ShulkerDeposit).then { placePos -> - OpenContainer(placePos, this@ShulkerDeposit).then { screen -> - deposit(screen, selection).then { - breakAndCollectBlock(placePos).finally { - success() - } - } + context(automatedSafeContext: AutomatedSafeContext) + override fun accessThen(exitAfter: Boolean, taskGenerator: TaskGenerator) = + PlaceContainerTask(shulkerSlot, automatedSafeContext).then { pos -> + placePos = pos + OpenContainerTask(pos, automatedSafeContext).then { + taskGenerator.invoke(automatedSafeContext, Unit).thenOrNull { + if (exitAfter) automatedSafeContext.breakAndCollectBlock(placePos) + else null } - }.execute(this@ShulkerDeposit) + } } - } - - context(automated: Automated) - override fun deposit(selection: StackSelection) = ShulkerDeposit(selection, shulkerStack, automated) - - context(safeContext: SafeContext) - override fun isImmediatelyAccessible() = false -} +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/interaction/material/container/containers/StashContainer.kt b/src/main/kotlin/com/lambda/interaction/material/container/containers/StashContainer.kt index bd317df73..282fcd7da 100644 --- a/src/main/kotlin/com/lambda/interaction/material/container/containers/StashContainer.kt +++ b/src/main/kotlin/com/lambda/interaction/material/container/containers/StashContainer.kt @@ -25,12 +25,16 @@ import com.lambda.util.text.buildText import com.lambda.util.text.highlighted import com.lambda.util.text.literal import net.minecraft.item.ItemStack +import net.minecraft.screen.slot.Slot import net.minecraft.util.math.Box data class StashContainer( val chests: Set, val pos: Box, ) : MaterialContainer(Rank.Stash) { + context(_: SafeContext) + override val slots: List + get() = chests.flatMap { it.slots } override var stacks: List get() = chests.flatMap { it.stacks } set(_) {} @@ -40,11 +44,9 @@ data class StashContainer( highlighted(pos.center.roundedBlockPos.toShortString()) } + context(_: SafeContext) override fun materialAvailable(selection: StackSelection): Int = chests.sumOf { it.materialAvailable(selection) } - - context(safeContext: SafeContext) - override fun isImmediatelyAccessible() = false } diff --git a/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryChanges.kt b/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryChanges.kt deleted file mode 100644 index 9845dc3a7..000000000 --- a/src/main/kotlin/com/lambda/interaction/material/transfer/InventoryChanges.kt +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2025 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.interaction.material.transfer - -import com.lambda.interaction.material.StackSelection -import com.lambda.util.item.ItemStackUtils.equal -import net.minecraft.item.ItemStack -import net.minecraft.screen.slot.Slot - -/** - * A class representing changes in an inventory's state over time. It acts as a tracker for - * detecting, storing, and merging differences between the original and updated states of - * inventory slots. This class extends a map-like structure, where the key is the slot index - * and the value is a list of pairs representing the original and updated states of an inventory slot. - * - * Example: - * ``` - * #0: 64 obsidian -> 0 air, 0 air -> 64 obsidian - * #36: 12 observer -> 11 observer - * ``` - * - Where `#0` is the slot id, first it was emptied and then got `64 obsidian` again - * - Where `#36` is the slot id, that was reduced by `1 observer` - * - * @property slots A list of `Slot` objects representing the current inventory slots being tracked. - * Defaults to an empty list. - */ -class InventoryChanges( - private var slots: List, -) : MutableMap>> by HashMap() { - private val originalStacks = slots.map { it.stack.copy() } - - /** - * Detect and store changes directly in the map. - */ - fun detectChanges() { - require(slots.isNotEmpty()) { "Cannot detect changes on an empty slots list" } - slots.forEachIndexed { index, slot -> - val originalStack = originalStacks[index] - val updatedStack = slot.stack - if (!originalStack.equal(updatedStack)) { - getOrPut(index) { mutableListOf() }.add(originalStack to updatedStack.copy()) - } - } - } - - /** - * Create a new `InventoryChanges` object that merges this changes object with another one. - * - * @param other Another `InventoryChanges` instance to merge with. - * @return A new `InventoryChanges` instance containing merged entries. - */ - infix fun merge(other: InventoryChanges) { - require(slots.isNotEmpty()) { "Cannot merge changes to an empty slots list" } - other.forEach { (key, value) -> - getOrPut(key) { mutableListOf() }.addAll(value) - } - } - - /** - * Evaluates if the current inventory changes fulfill the given selection requirement. - * - * @param to A list of `Slot` objects to evaluate for the selection criteria. - * @param selection A `StackSelection` object that specifies the selection criteria, including the - * target items and their required count. - * @return `true` if the total count of matching items across the filtered slots meets or exceeds - * the required count specified in the `StackSelection`; `false` otherwise. - */ - fun fulfillsSelection(to: List, selection: StackSelection): Boolean { - require(slots.isNotEmpty()) { "Cannot evaluate selection on an empty slots list" } - val targetSlots = selection.filterSlots(to).map { it.id } - return filter { it.key in targetSlots }.entries.sumOf { (_, changes) -> - changes.lastOrNull()?.second?.count ?: 0 - } >= selection.count - } - - override fun toString() = - if (entries.isEmpty()) { - "No changes detected" - } else { - entries.joinToString("\n") { key -> - "#${key.key}: ${key.value.joinToString { "${it.first} -> ${it.second}" }}" - } - } -} diff --git a/src/main/kotlin/com/lambda/interaction/material/transfer/SlotTransfer.kt b/src/main/kotlin/com/lambda/interaction/material/transfer/SlotTransfer.kt deleted file mode 100644 index 70788f938..000000000 --- a/src/main/kotlin/com/lambda/interaction/material/transfer/SlotTransfer.kt +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2025 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.interaction.material.transfer - -import com.lambda.context.Automated -import com.lambda.context.SafeContext -import com.lambda.event.events.TickEvent -import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest -import com.lambda.task.Task -import com.lambda.util.extension.containerSlots -import com.lambda.util.extension.inventorySlots -import com.lambda.util.item.ItemUtils.block -import net.minecraft.screen.ScreenHandler -import net.minecraft.screen.slot.Slot - -class SlotTransfer @Ta5kBuilder constructor( - val screen: ScreenHandler, - private val selection: StackSelection, - val from: List, - val to: List, - private val closeScreen: Boolean = true, - automated: Automated -) : Task(), Automated by automated { - private var selectedFrom = listOf() - private var selectedTo = listOf() - override val name: String - get() = "Moving $selection from slots [${selectedFrom.joinToString { "${it.id}" }}] to slots [${selectedTo.joinToString { "${it.id}" }}] in ${screen::class.simpleName}" - - private var delay = 0 - private lateinit var changes: InventoryChanges - - override fun SafeContext.onStart() { - changes = InventoryChanges(player.currentScreenHandler.slots) - } - - init { - listen { - if (changes.fulfillsSelection(to, selection)) { - if (closeScreen) { player.closeHandledScreen() } - success() - return@listen - } - - val current = player.currentScreenHandler - if (current != screen) { - failure("Screen has changed. Expected ${screen::class.simpleName} (revision ${screen.revision}) but got ${current::class.simpleName} (revision ${current.revision})") - return@listen - } - - selectedFrom = selection.filterSlots(from) - selectedTo = to.filter { it.stack.isEmpty } + to.filter { it.stack.item.block in inventoryConfig.disposables } - - val nextFrom = selectedFrom.firstOrNull() ?: return@listen - val nextTo = selectedTo.firstOrNull() ?: return@listen - - inventoryRequest { - swap(nextTo.id, 1) - swap(nextFrom.id, 1) - onComplete { success() } - }.submit(queueIfMismatchedStage = false) - } - } - - companion object { - @Ta5kBuilder - fun Automated.moveItems( - screen: ScreenHandler, - selection: StackSelection, - from: List, - to: List, - closeScreen: Boolean = true, - ) = SlotTransfer(screen, selection, from, to, closeScreen, this) - - @Ta5kBuilder - context(automated: Automated) - fun withdraw(screen: ScreenHandler, selection: StackSelection, closeScreen: Boolean = true) = - automated.moveItems(screen, selection, screen.containerSlots, screen.inventorySlots, closeScreen) - - @Ta5kBuilder - context(automated: Automated) - fun deposit(screen: ScreenHandler, selection: StackSelection, closeScreen: Boolean = true) = - automated.moveItems(screen, selection, screen.inventorySlots, screen.containerSlots, closeScreen) - } -} diff --git a/src/main/kotlin/com/lambda/interaction/material/transfer/TransferResult.kt b/src/main/kotlin/com/lambda/interaction/material/transfer/TransferResult.kt deleted file mode 100644 index 0bc68f3fc..000000000 --- a/src/main/kotlin/com/lambda/interaction/material/transfer/TransferResult.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2025 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.interaction.material.transfer - -import com.lambda.context.Automated -import com.lambda.context.SafeContext -import com.lambda.interaction.material.StackSelection -import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.task.Task - -abstract class TransferResult : Task() { - data class ContainerTransfer( - val selection: StackSelection, - val from: MaterialContainer, - val to: MaterialContainer, - val automated: Automated - ) : TransferResult(), Automated by automated { - override val name = "Container Transfer of [$selection] from [${from.name}] to [${to.name}]" - - override fun SafeContext.onStart() { - val withdrawal = from.withdraw(selection) - val deposit = to.deposit(selection) - - val task = when { - withdrawal != null && deposit != null -> { - withdrawal.then { - deposit.finally { success() } - } - } - withdrawal != null -> { - withdrawal.finally { success() } - } - deposit != null -> { - deposit.finally { success() } - } - else -> null - } - - task?.execute(this@ContainerTransfer) - } - } - - data object NoSpace : TransferResult() { - override val name = "No space left in the target container" - - // ToDo: Needs inventory space resolver. compressing or disposing - override fun SafeContext.onStart() { - failure("No space left in the target container") - } - } - - data class MissingItems(val missing: Int) : TransferResult() { - override val name = "Missing $missing items" - - // ToDo: Find other satisfying permutations - override fun SafeContext.onStart() { - failure("Missing $missing items") - } - } -} diff --git a/src/main/kotlin/com/lambda/module/HudModule.kt b/src/main/kotlin/com/lambda/module/HudModule.kt index 5c26f37cd..3762a1f0b 100644 --- a/src/main/kotlin/com/lambda/module/HudModule.kt +++ b/src/main/kotlin/com/lambda/module/HudModule.kt @@ -26,7 +26,6 @@ abstract class HudModule( name: String, description: String = "", tag: ModuleTag, - val customWindow: Boolean = false, alwaysListening: Boolean = false, enabledByDefault: Boolean = false, defaultKeybind: Bind = Bind.EMPTY, diff --git a/src/main/kotlin/com/lambda/module/Module.kt b/src/main/kotlin/com/lambda/module/Module.kt index 2ba08c91b..0066648b3 100644 --- a/src/main/kotlin/com/lambda/module/Module.kt +++ b/src/main/kotlin/com/lambda/module/Module.kt @@ -17,7 +17,6 @@ package com.lambda.module -import com.lambda.Lambda import com.lambda.command.LambdaCommand import com.lambda.config.Configurable import com.lambda.config.Configuration @@ -26,12 +25,12 @@ import com.lambda.config.MutableAutomationConfigImpl import com.lambda.config.SettingCore import com.lambda.config.configurations.ModuleConfigs import com.lambda.config.settings.complex.Bind +import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress +import com.lambda.config.settings.complex.KeybindSetting.Companion.onRelease import com.lambda.context.SafeContext import com.lambda.event.Muteable import com.lambda.event.events.ClientEvent import com.lambda.event.events.ConnectionEvent -import com.lambda.event.events.KeyboardEvent -import com.lambda.event.events.MouseEvent import com.lambda.event.listener.Listener import com.lambda.event.listener.SafeListener import com.lambda.event.listener.SafeListener.Companion.listen @@ -122,7 +121,9 @@ abstract class Module( autoDisable: Boolean = false ) : Nameable, Muteable, Configurable(ModuleConfigs), MutableAutomationConfig by MutableAutomationConfigImpl() { private val isEnabledSetting = setting("Enabled", enabledByDefault) { false } - val keybindSetting = setting("Keybind", defaultKeybind) { false } + val keybindSetting = setting("Keybind", defaultKeybind, alwaysListening = true) { false } + .onPress { toggle() } + .onRelease { if (disableOnRelease) disable() } val disableOnReleaseSetting = setting("Disable On Release", false) { false } val drawSetting = setting("Draw", true, "Draws the module in the module list hud element") @@ -137,24 +138,6 @@ abstract class Module( get() = !isEnabled && !alwaysListening init { - listen(alwaysListen = true) { event -> - if (mc.options.commandKey.isPressed - || Lambda.mc.currentScreen != null - || !event.satisfies(keybind)) return@listen - - if (event.isPressed && !event.isRepeated) toggle() - else if (event.isReleased && disableOnRelease) disable() - } - - listen(alwaysListen = true) { event -> - if (mc.options.commandKey.isPressed - || mc.currentScreen != null - || !event.satisfies(keybind)) return@listen - - if (event.isPressed) toggle() - else if (event.isReleased && disableOnRelease) disable() - } - onEnable { LambdaSound.ModuleOn.play() } onDisable { LambdaSound.ModuleOff.play() } diff --git a/src/main/kotlin/com/lambda/module/hud/Baritone.kt b/src/main/kotlin/com/lambda/module/hud/Baritone.kt index 8d20b3f62..86bae308f 100644 --- a/src/main/kotlin/com/lambda/module/hud/Baritone.kt +++ b/src/main/kotlin/com/lambda/module/hud/Baritone.kt @@ -18,7 +18,7 @@ package com.lambda.module.hud import com.lambda.gui.dsl.ImGuiBuilder -import com.lambda.interaction.BaritoneManager.primary +import com.lambda.interaction.BaritoneManager import com.lambda.interaction.construction.simulation.BuildGoal import com.lambda.module.HudModule import com.lambda.module.tag.ModuleTag @@ -29,7 +29,12 @@ object Baritone : HudModule( tag = ModuleTag.HUD, ) { override fun ImGuiBuilder.buildLayout() { - primary.customGoalProcess.goal?.let { + if (!BaritoneManager.isBaritoneLoaded) { + text("Baritone is not loaded") + return + } + + BaritoneManager.primary?.customGoalProcess?.goal?.let { when(it) { is BuildGoal -> text("Lambda Simulation: ${it.sim}") else -> text("Baritone: $it") diff --git a/src/main/kotlin/com/lambda/module/hud/ManagerDebugLoggers.kt b/src/main/kotlin/com/lambda/module/hud/ManagerDebugLoggers.kt deleted file mode 100644 index b0385a1f7..000000000 --- a/src/main/kotlin/com/lambda/module/hud/ManagerDebugLoggers.kt +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2025 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.module.hud - -import com.lambda.gui.dsl.ImGuiBuilder -import com.lambda.interaction.managers.DebugLogger -import com.lambda.module.HudModule -import com.lambda.module.tag.ModuleTag -import com.lambda.util.NamedEnum - -@Suppress("Unused") -object ManagerDebugLoggers : HudModule( - "ManagerDebugLoggers", - "debug loggers for all action managers in lambda", - ModuleTag.HUD, - customWindow = true -) { - enum class Group(override val displayName: String) : NamedEnum { - General("General"), - Break("Break"), - Place("Place"), - Interact("Interact"), - Rotation("Rotation"), - Hotbar("Hotbar"), - Inventory("Inventory") - } - - private val loggers = mutableMapOf<() -> Boolean, DebugLogger>() - - val autoScroll by setting("Auto-Scroll", true, "Locks the page at the bottom of the logs").group(Group.General) - val wrapText by setting("Wrap Text", true, "Wraps the logs to new lines if they go off the panel").group(Group.General) - val showDebug by setting("Show Debug", true, "Shows debug logs").group(Group.General) - val showSuccess by setting("Show Success", true, "Shows success logs").group(Group.General) - val showWarning by setting("Show Warning", true, "Shows warning logs").group(Group.General) - val showError by setting("Show Error", true, "Shows error logs").group(Group.General) - val showSystem by setting("Show System", true, "Shows system logs").group(Group.General) - val maxLogEntries by setting("Max Log Entries", 100, 1..1000, 1, "Maximum amount of entries in the log").group(Group.General) - .onValueChange { from, to -> - if (to < from) { - loggers.values.forEach { logger -> - while(logger.logs.size > to) { - logger.logs.removeFirst() - } - } - } - } - - private val showBreakManager by setting("Show Break Manager Logger", false).group(Group.Break) - val breakManagerLogger = DebugLogger("Break Manager Logger").store { showBreakManager } - - private val showPlaceManager by setting("Show Place Manager Logger", false).group(Group.Place) - val placeManagerLogger = DebugLogger("Place Manager Logger").store { showPlaceManager } - - private val showInteractionManager by setting("Show Interaction Manager Logger", false).group(Group.Interact) - val interactionManagerLogger = DebugLogger("Interaction Manager Logger").store { showInteractionManager } - - private val showRotationManager by setting("Show Rotation Manager Logger", false).group(Group.Rotation) - val rotationManagerLogger = DebugLogger("Rotation Manager Logger").store { showRotationManager } - - private val showHotbarManager by setting("Show Hotbar Manager Logger", false).group(Group.Hotbar) - val hotbarManagerLogger = DebugLogger("Hotbar Manager Logger").store { showHotbarManager } - - private val showInventoryManager by setting("Show Inventory Manager Logger", false).group(Group.Inventory) - val inventoryManagerLogger = DebugLogger("Inventory Manager Logger").store { showInventoryManager } - - private fun DebugLogger.store(show: () -> Boolean) = - also { loggers.put(show, this) } - - override fun ImGuiBuilder.buildLayout() { - loggers.entries.forEach { entry -> - if (entry.key()) with(entry.value) { - buildLayout() - } - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/module/hud/Speed.kt b/src/main/kotlin/com/lambda/module/hud/Speedometer.kt similarity index 97% rename from src/main/kotlin/com/lambda/module/hud/Speed.kt rename to src/main/kotlin/com/lambda/module/hud/Speedometer.kt index e67594f51..d1f01138f 100644 --- a/src/main/kotlin/com/lambda/module/hud/Speed.kt +++ b/src/main/kotlin/com/lambda/module/hud/Speedometer.kt @@ -26,8 +26,8 @@ import com.lambda.threading.runSafe import com.lambda.util.SpeedUnit import net.minecraft.util.math.Vec3d -object Speed : HudModule( - name = "Speed", +object Speedometer : HudModule( + name = "Speedometer", description = "Displays player speed", tag = ModuleTag.HUD ) { diff --git a/src/main/kotlin/com/lambda/module/modules/chat/AntiSpam.kt b/src/main/kotlin/com/lambda/module/modules/chat/AntiSpam.kt new file mode 100644 index 000000000..d01d101eb --- /dev/null +++ b/src/main/kotlin/com/lambda/module/modules/chat/AntiSpam.kt @@ -0,0 +1,144 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.module.modules.chat + +import com.lambda.config.Configurable +import com.lambda.config.SettingGroup +import com.lambda.config.applyEdits +import com.lambda.config.groups.ReplaceConfig +import com.lambda.event.events.ChatEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.friend.FriendManager +import com.lambda.module.Module +import com.lambda.module.tag.ModuleTag +import com.lambda.util.ChatUtils.addresses +import com.lambda.util.ChatUtils.colors +import com.lambda.util.ChatUtils.discord +import com.lambda.util.ChatUtils.hex +import com.lambda.util.ChatUtils.sexual +import com.lambda.util.ChatUtils.slurs +import com.lambda.util.ChatUtils.swears +import com.lambda.util.ChatUtils.toAscii +import com.lambda.util.NamedEnum +import com.lambda.util.text.DirectMessage +import com.lambda.util.text.MessageParser +import com.lambda.util.text.MessageType +import net.minecraft.text.Text + +object AntiSpam : Module( + name = "AntiSpam", + description = "Keeps your chat clean", + tag = ModuleTag.CHAT, +) { + private val fancyChats by setting("Replace Fancy Chat", false) + + private val filterSelf by setting("Ignore Self", true) + private val filterFriends by setting("Ignore Friends", true) + private val filterSystem by setting("Filter System Messages", false) + private val filterDms by setting("Filter DMs", true) + + private val ignoreSystem by setting("Ignore System", false) + private val ignoreDms by setting("Ignore DMs", false) + + private val detectSlurs = ReplaceSettings("Slurs", this, Group.Slurs) + private val detectSwears = ReplaceSettings("Swears", this, Group.Swears) + private val detectSexual = ReplaceSettings("Sexual", this, Group.Sexual) + private val detectDiscord = ReplaceSettings("Discord", this, Group.Discord) + .apply { applyEdits { editTyped(::action) { defaultValue(ReplaceConfig.ActionStrategy.Hide) } } } + private val detectAddresses = ReplaceSettings("Addresses", this, Group.Addresses) + .apply { applyEdits { editTyped(::action) { defaultValue(ReplaceConfig.ActionStrategy.Hide) } } } + private val detectHexBypass = ReplaceSettings("Hex", this, Group.Hex) + .apply { applyEdits { editTyped(::action) { defaultValue(ReplaceConfig.ActionStrategy.Hide) } } } + private val detectColors = ReplaceSettings("Colors", this, Group.Colors) + .apply { applyEdits { editTyped(::action) { defaultValue(ReplaceConfig.ActionStrategy.None) } } } + + enum class Group(override val displayName: String) : NamedEnum { + General("General"), + Slurs("Slurs"), + Swears("Swears"), + Sexual("Sexual"), + Discord("Discord Invites"), + Addresses("IPs and Addresses"), + Hex("Hex Bypass"), + Colors("Color Prefixes") + } + + init { + listen { event -> + var raw = event.message.string + val author = MessageParser.playerName(raw) + + if ( + ignoreSystem && !MessageType.Both.matches(raw) && !DirectMessage.Both.matches(raw) || + ignoreDms && DirectMessage.Receive.matches(raw) + ) return@listen + + val slurMatches = slurs.takeIf { detectSlurs.enabled }.orEmpty().flatMap { it.findAll(raw).toList().reversed() } + val swearMatches = swears.takeIf { detectSwears.enabled }.orEmpty().flatMap { it.findAll(raw).toList().reversed() } + val sexualMatches = sexual.takeIf { detectSexual.enabled }.orEmpty().flatMap { it.findAll(raw).toList().reversed() } + val discordMatches = discord.takeIf { detectDiscord.enabled }.orEmpty().flatMap { it.findAll(raw).toList().reversed() } + val addressMatches = addresses.takeIf { detectAddresses.enabled }.orEmpty().flatMap { it.findAll(raw).toList().reversed() } + val hexMatches = hex.takeIf { detectHexBypass.enabled }.orEmpty().flatMap { it.findAll(raw).toList().reversed() } + val colorMatches = colors.takeIf { detectColors.enabled }.orEmpty().flatMap { it.findAll(raw).toList().reversed() } + + var cancelled = false + var hasMatches = false + + fun doMatch(replace: ReplaceConfig, matches: Sequence) { + if ( + cancelled || + filterSystem && !MessageType.Both.matches(raw) && !DirectMessage.Both.matches(raw) || + filterDms && DirectMessage.Receive.matches(raw) || + filterFriends && author?.let { FriendManager.isFriend(it) } == true || + filterSelf && MessageType.Self.matches(raw) + ) return + + when (replace.action) { + ReplaceConfig.ActionStrategy.Hide -> matches.firstOrNull()?.let { event.cancel(); cancelled = true } // If there's one detection, nuke the whole damn thang + ReplaceConfig.ActionStrategy.Delete -> matches + .forEach { raw = raw.replaceRange(it.range, ""); hasMatches = true } + ReplaceConfig.ActionStrategy.Replace -> matches + .forEach { raw = raw.replaceRange(it.range, replace.replace.block(it.value)); hasMatches = true } + ReplaceConfig.ActionStrategy.None -> {} + } + } + + doMatch(detectSlurs, slurMatches) + doMatch(detectSwears, swearMatches) + doMatch(detectSexual, sexualMatches) + doMatch(detectDiscord, discordMatches) + doMatch(detectAddresses, addressMatches) + doMatch(detectHexBypass, hexMatches) + doMatch(detectColors, colorMatches) + + if (cancelled) return@listen event.cancel() + + if (hasMatches) + event.message = Text.of(if (fancyChats) raw.toAscii else raw) + } + } + + class ReplaceSettings( + name: String, + c: Configurable, + baseGroup: NamedEnum, + ) : ReplaceConfig, SettingGroup(c) { + override val action by setting("$name Action Strategy", ReplaceConfig.ActionStrategy.Replace).group(baseGroup) + override val replace by setting("$name Replace Strategy", ReplaceConfig.ReplaceStrategy.CensorAll) { action == ReplaceConfig.ActionStrategy.Replace }.group(baseGroup) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/module/modules/chat/ChatTimestamp.kt b/src/main/kotlin/com/lambda/module/modules/chat/ChatTimestamp.kt new file mode 100644 index 000000000..d28caeaa0 --- /dev/null +++ b/src/main/kotlin/com/lambda/module/modules/chat/ChatTimestamp.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.module.modules.chat + +import com.lambda.config.applyEdits +import com.lambda.config.groups.FormatterConfig +import com.lambda.config.groups.FormatterSettings +import com.lambda.event.events.ChatEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.module.Module +import com.lambda.module.tag.ModuleTag +import com.lambda.util.Formatting.format +import com.lambda.util.text.buildText +import com.lambda.util.text.literal +import com.lambda.util.text.styled +import com.lambda.util.text.text +import net.minecraft.util.Formatting +import java.awt.Color +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.ZonedDateTime +import java.time.temporal.ChronoUnit + +object ChatTimestamp : Module( + name = "ChatTimestamp", + description = "Displays the time a message was sent next to it", + tag = ModuleTag.CHAT, +) { + var color: Formatting by setting("Color", Formatting.GRAY) + .onValueChange { from, to -> if (to.colorIndex !in 0..15) color = from } + + val javaColor: Color get() = Color(color.colorValue!! and 16777215) + + val formatter = FormatterSettings(this).apply { applyEdits { hide(::localeEnum, ::sep, ::customSep, ::group, ::floatingPrecision); editTyped(::timeFormat) { defaultValue(FormatterConfig.Time.IsoLocalTime) } } } + + private val currentTime get() = + ZonedDateTime.of(LocalDateTime.now(), ZoneId.systemDefault()) + .truncatedTo(ChronoUnit.SECONDS) + + init { + listen { + it.message = buildText { + text(it.message) + literal(" ") + styled(javaColor, italic = true) { literal(currentTime.format(formatter)) } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/module/modules/chat/CustomChat.kt b/src/main/kotlin/com/lambda/module/modules/chat/CustomChat.kt new file mode 100644 index 000000000..af1b9edf0 --- /dev/null +++ b/src/main/kotlin/com/lambda/module/modules/chat/CustomChat.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.module.modules.chat + +import com.google.common.collect.Comparators.min +import com.lambda.event.events.ChatEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.module.Module +import com.lambda.module.tag.ModuleTag +import com.lambda.util.NamedEnum + +object CustomChat : Module( + name = "CustomChat", + description = "Adds a custom ending to your message", + tag = ModuleTag.CHAT, +) { + private val decoration by setting("Decoration", Decoration.Separator) + private val text by setting("Text", Text.Lambda) + + private val customText by setting("Custom Text", "") { text == Text.Custom } + + init { + listen { + val message = "${it.message} ${decoration.block(text.block())}" + it.message = message.take(min(256, message.length)) + } + } + + enum class Decoration(val block: (String) -> String) { + Separator({ "| $it" }), + Classic({ "\u00ab $it \u00bb" }), + None({ it }) + } + + private enum class Text(override val displayName: String, val block: () -> String) : NamedEnum { + Lambda("Lambda", { "ʟᴀᴍʙᴅᴀ" }), + LambdaOnTop("Lambda On Top", { "ʟᴀᴍʙᴅᴀ ᴏɴ ᴛᴏᴘ" }), + KamiBlue("Kami Blue", { "ᴋᴀᴍɪ ʙʟᴜᴇ" }), + LambdaWebsite("Lambda Website", { "lambda-client.org" }), + Custom("Custom", { customText }) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/module/modules/chat/FancyChat.kt b/src/main/kotlin/com/lambda/module/modules/chat/FancyChat.kt new file mode 100644 index 000000000..f748993f2 --- /dev/null +++ b/src/main/kotlin/com/lambda/module/modules/chat/FancyChat.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.module.modules.chat + +import com.lambda.event.events.ChatEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.module.Module +import com.lambda.module.tag.ModuleTag +import com.lambda.util.ChatUtils.toBlue +import com.lambda.util.ChatUtils.toGreen +import com.lambda.util.ChatUtils.toLeet +import com.lambda.util.ChatUtils.toUwu + +object FancyChat : Module( + name = "FancyChat", + description = "Makes messages you send - fancy", + tag = ModuleTag.CHAT, +) { + private val uwu by setting("uwu", true) + private val leet by setting("1337", false) + private val green by setting(">", false) + private val blue by setting("`", false) + + init { + listen { + if (uwu) it.message = it.message.toUwu + if (leet) it.message = it.message.toLeet + if (green) it.message = it.message.toGreen + if (blue) it.message = it.message.toBlue + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/module/modules/chat/FriendHighlight.kt b/src/main/kotlin/com/lambda/module/modules/chat/FriendHighlight.kt new file mode 100644 index 000000000..0f257abf8 --- /dev/null +++ b/src/main/kotlin/com/lambda/module/modules/chat/FriendHighlight.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.module.modules.chat + +import com.lambda.event.events.ChatEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.friend.FriendManager +import com.lambda.module.Module +import com.lambda.module.tag.ModuleTag +import com.lambda.sound.SoundManager.playSound +import com.lambda.util.Communication.logError +import com.lambda.util.text.MessageType +import com.lambda.util.text.buildText +import com.lambda.util.text.literal +import com.lambda.util.text.styled +import net.minecraft.sound.SoundEvents +import net.minecraft.util.Formatting +import java.awt.Color + +object FriendHighlight : Module( + name = "FriendHighlight", + description = "Highlights your friends names in chat", + tag = ModuleTag.CHAT, +) { + var color: Formatting by setting("Color", Formatting.GREEN) + .onValueChange { from, to -> if (to.colorIndex !in 0..15) color = from } + + val javaColor: Color get() = Color(color.colorValue!! and 16777215) + + val bold by setting("Bold", true) + val italic by setting("Italic", false) + val underlined by setting("Underlined", false) + val strikethrough by setting("Strikethrough", false) + + val ping by setting("Ping On Message", true) + + init { + onEnable { + if (FriendManager.friends.isEmpty()) + logError("You don't have any friends added, silly! Go add some friends before using the module") + } + + listen { + val raw = it.message.string + val author = MessageType.Others.playerName(raw) ?: return@listen + val content = MessageType.Others.removedOrNull(raw) ?: return@listen + + if (!FriendManager.isFriend(author)) return@listen + + if (ping) playSound(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP) + + it.message = buildText { + literal("<") + styled(javaColor, bold, italic, underlined, strikethrough) { literal(author) } + literal(">") + + literal(content.toString()) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/module/modules/client/LambdaMoji.kt b/src/main/kotlin/com/lambda/module/modules/client/LambdaMoji.kt deleted file mode 100644 index fa1a86dc2..000000000 --- a/src/main/kotlin/com/lambda/module/modules/client/LambdaMoji.kt +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2025 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.module.modules.client - -import com.lambda.Lambda.mc -import com.lambda.graphics.renderer.gui.font.core.GlyphInfo -import com.lambda.graphics.renderer.gui.font.core.LambdaAtlas.get -import com.lambda.module.Module -import com.lambda.module.tag.ModuleTag -import com.lambda.util.math.Vec2d -import net.minecraft.text.OrderedText -import net.minecraft.text.Style -import java.awt.Color - -// This is the worst code I have ever wrote in my life -object LambdaMoji : Module( - name = "LambdaMoji", - description = "", - tag = ModuleTag.CLIENT, - enabledByDefault = false, -) { - val suggestions by setting("Chat Suggestions", true) - - private val renderQueue = mutableListOf>() - - init { - /*listen { - renderQueue.forEach { (glyph, position, color) -> - drawGlyph(glyph, position, color) - } - - renderQueue.clear() - }*/ - } - - // FixMe: Doesn't render properly when the chat scale is modified - fun parse(text: OrderedText, x: Float, y: Float, color: Int): OrderedText { - val saved = mutableMapOf() - val builder = StringBuilder() - - var absoluteIndex = 0 - text.accept { _, style, codePoint -> - saved[absoluteIndex++] = style - builder.appendCodePoint(codePoint) - true - } - - var raw = builder.toString() - StyleEditor.emojiFont.parse(raw) - .forEach { emoji -> - val index = raw.indexOf(emoji) - if (index == -1) return@forEach - - val width = mc.textRenderer.getWidth(raw.substring(0, index)) - - // Dude I'm sick of working with the shitcode that is minecraft's codebase :sob: - val trueColor = when (color) { - 0x00E0E0E0, 0 -> Color(255, 255, 255, 255) - else -> Color(255, 255, 255, (color shr 24 and 0xFF)) - } - - val glyph = StyleEditor.emojiFont[emoji] ?: return@forEach - renderQueue.add(Triple(glyph, Vec2d(x + width, y), trueColor)) - - // Replace the emoji with whitespaces depending on the player's settings - raw = raw.replaceFirst(emoji, " ") - } - - val constructed = mutableListOf() - - // Will not work properly if the emoji is part of the style - saved.forEach { (index, style) -> - if (index >= raw.length) return@forEach - constructed.add(OrderedText.styledForwardsVisitedString(raw.substring(index, index + 1), style)) - } - - return OrderedText.concat(constructed) - } -} diff --git a/src/main/kotlin/com/lambda/module/modules/client/StyleEditor.kt b/src/main/kotlin/com/lambda/module/modules/client/StyleEditor.kt index 3552a75db..caf8ff73e 100644 --- a/src/main/kotlin/com/lambda/module/modules/client/StyleEditor.kt +++ b/src/main/kotlin/com/lambda/module/modules/client/StyleEditor.kt @@ -17,8 +17,6 @@ package com.lambda.module.modules.client -import com.lambda.graphics.renderer.gui.font.core.LambdaEmoji -import com.lambda.graphics.renderer.gui.font.core.LambdaFont import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.NamedEnum @@ -39,8 +37,6 @@ object StyleEditor : Module( val useMemoryMapping by setting("Use Memory Mapping", true).group(Group.General) // Font - val textFont by setting("Text Font", LambdaFont.FiraSansRegular).group(Group.Font) - val emojiFont by setting("Emoji Font", LambdaEmoji.Twemoji).group(Group.Font) val shadow by setting("Shadow", true).group(Group.Font) val shadowBrightness by setting("Shadow Brightness", 0.35, 0.0..0.5, 0.01) { shadow }.group(Group.Font) val shadowShift by setting("Shadow Shift", 1.0, 0.0..2.0, 0.05) { shadow }.group(Group.Font) diff --git a/src/main/kotlin/com/lambda/module/modules/combat/AutoDisconnect.kt b/src/main/kotlin/com/lambda/module/modules/combat/AutoDisconnect.kt index 7f174465d..3ada7d918 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/AutoDisconnect.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/AutoDisconnect.kt @@ -32,7 +32,7 @@ import com.lambda.util.combat.CombatUtils.hasDeadlyCrystal import com.lambda.util.combat.DamageUtils.isFallDeadly import com.lambda.util.extension.fullHealth import com.lambda.util.extension.tickDelta -import com.lambda.util.player.SlotUtils.combined +import com.lambda.util.player.SlotUtils.allStacks import com.lambda.util.text.buildText import com.lambda.util.text.color import com.lambda.util.text.highlighted @@ -41,12 +41,10 @@ import com.lambda.util.text.text import com.lambda.util.world.fastEntitySearch import net.minecraft.entity.damage.DamageSource import net.minecraft.entity.damage.DamageTypes -import net.minecraft.entity.effect.StatusEffect import net.minecraft.entity.effect.StatusEffects import net.minecraft.entity.mob.CreeperEntity import net.minecraft.entity.player.PlayerEntity import net.minecraft.item.Items -import net.minecraft.registry.Registries import net.minecraft.sound.SoundEvents import net.minecraft.text.Text import net.minecraft.world.GameMode @@ -59,6 +57,8 @@ object AutoDisconnect : Module( ) { private val health by setting("Health", true, "Disconnect from the server when health is below the set limit.") private val minimumHealth by setting("Min Health", 10, 1..36, 1, "Set the minimum health threshold for disconnection.", unit = " half-hearts") { health } + private val yLevel by setting("Y Level", false, "Disconnect from the server when the player is below a certain y level") + private val minimumYLevel by setting("Minimum Y Level", 50, 0..319, 1, "The minimum y level the player can be at before disconnecting") { yLevel } private val falls by setting("Falls", false, "Disconnect if the player will die of fall damage") private val fallDistance by setting("Falls Time", 10, 0..30, 1, "Number of blocks fallen before disconnecting for fall damage.", unit = " blocks") { falls } private val crystals by setting("Crystals", false, "Disconnect if an End Crystal explosion would be lethal.") @@ -152,7 +152,7 @@ object AutoDisconnect : Module( } private fun SafeContext.disconnect(reasonText: Text, reason: Reason? = null) { - if (connection.brand == "2b2t (Velocity)" && player.gameMode == GameMode.SPECTATOR) return + if (player.gameMode != GameMode.SURVIVAL && player.gameMode != GameMode.ADVENTURE) return if (reason == Reason.Health || reason == Reason.Totem) disable() connection.connection.disconnect(generateInfo(reasonText)) playSound(SoundEvents.BLOCK_ANVIL_LAND) @@ -207,8 +207,17 @@ object AutoDisconnect : Module( } } else null }), + YLevel({ yLevel }, { + if (player.pos.y < minimumYLevel) { + buildText { + literal("Player went below y level ") + highlighted("$minimumYLevel") + literal("!") + } + } else null + }), Totem({ totem }, { - val totemCount = player.combined.count { it.item == Items.TOTEM_OF_UNDYING } + val totemCount = player.allStacks.count { it.item == Items.TOTEM_OF_UNDYING } if (totemCount < minTotems) { buildText { literal("Only ") diff --git a/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt b/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt index 422c91c9f..322486cd7 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/AutoTotem.kt @@ -17,7 +17,8 @@ package com.lambda.module.modules.combat -import com.lambda.config.groups.InventorySettings +import com.lambda.config.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.applyEdits import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen @@ -53,9 +54,13 @@ object AutoTotem : Module( private val minPlayerDistance by setting("Player Distance", 64, 32..128, 4, "Set the distance to detect players to swap") { !always && players }.group(Group.General) private val friends by setting("Friends", false, "Exclude friends from triggering player-based swaps") { !always && players }.group(Group.General) - override val inventoryConfig = InventorySettings(this, Group.Inventory) - init { + setDefaultAutomationConfig { + applyEdits { + hideAllGroupsExcept(inventoryConfig) + } + } + listen { if (!always && Reason.entries.none { it.check(this) }) return@listen diff --git a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt index d7513cc7e..6f85dce46 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt @@ -17,24 +17,26 @@ package com.lambda.module.modules.combat -import com.lambda.config.groups.RotationSettings +import com.lambda.config.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.applyEdits import com.lambda.config.groups.Targeting import com.lambda.context.SafeContext import com.lambda.event.events.EntityEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.managers.hotbar.HotbarRequest +import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest import com.lambda.interaction.managers.rotating.Rotation.Companion.rotationTo import com.lambda.interaction.managers.rotating.RotationManager -import com.lambda.interaction.managers.rotating.RotationRequest import com.lambda.interaction.managers.rotating.visibilty.VisibilityChecker.getVisibleSurfaces import com.lambda.interaction.material.StackSelection.Companion.selectStack import com.lambda.interaction.material.container.ContainerManager.transfer -import com.lambda.interaction.material.container.containers.MainHandContainer +import com.lambda.interaction.material.container.containers.HotbarContainer import com.lambda.interaction.material.container.containers.OffHandContainer import com.lambda.module.Module import com.lambda.module.tag.ModuleTag -import com.lambda.task.RootTask.run import com.lambda.threading.runSafe +import com.lambda.threading.runSafeAutomated import com.lambda.threading.runSafeGameScheduled import com.lambda.util.BlockUtils.blockState import com.lambda.util.Communication.info @@ -51,6 +53,7 @@ import com.lambda.util.math.flooredBlockPos import com.lambda.util.math.getHitVec import com.lambda.util.math.minus import com.lambda.util.math.plus +import com.lambda.util.player.SlotUtils.hotbarStacks import com.lambda.util.world.fastEntitySearch import net.minecraft.block.Blocks import net.minecraft.entity.Entity @@ -109,9 +112,6 @@ object CrystalAura : Module( /* Targeting */ private val targeting = Targeting.Combat(this, Group.Targeting, 10.0) - /* Rotation */ - override val rotationConfig = RotationSettings(this, Group.Rotation) - private val blueprint = mutableMapOf() private var activeOpportunity: Opportunity? = null private var currentTarget: LivingEntity? = null @@ -141,7 +141,19 @@ object CrystalAura : Module( } } - init { + init { + setDefaultAutomationConfig { + applyEdits { + hideAllGroupsExcept(buildConfig, rotationConfig, hotbarConfig, inventoryConfig) + buildConfig.apply { + hide( + ::pathing, ::stayInRange, ::collectDrops, ::spleefEntities, + ::maxPendingActions, ::actionTimeout, ::maxBuildDependencies, ::breakBlocks, ::interactBlocks + ) + } + } + } + // Async ticking fixedRateTimer( name = "Crystal Aura Thread", @@ -266,15 +278,16 @@ object CrystalAura : Module( blueprint[mutableBlockPos] }.filter { it.hasCrystal }.maxByOrNull { it.priority }?.explode() - best.place() - } + best.place() + } - private fun SafeContext.placeInternal(opportunity: Opportunity, hand: Hand) { - connection.sendPacket { - PlayerInteractBlockC2SPacket( - hand, BlockHitResult(opportunity.crystalPosition, opportunity.side, opportunity.blockPos, false), 0 - ) - } + private fun SafeContext.placeInternal(opportunity: Opportunity, hand: Hand) { + interaction.syncSelectedSlot() + connection.sendPacket { + PlayerInteractBlockC2SPacket( + hand, BlockHitResult(opportunity.crystalPosition, opportunity.side, opportunity.blockPos, false), 0 + ) + } player.swingHand(hand) } @@ -463,15 +476,25 @@ object CrystalAura : Module( * Places the crystal on [blockPos] */ fun place() = runSafe { - if (rotate && !RotationRequest(placeRotation, this@CrystalAura).submit().done) + if (rotate && !rotationRequest { rotation(placeRotation) }.submit().done) return@runSafe - val selection = selectStack { isItem(Items.END_CRYSTAL) } - if (swap && - (swapHand == Hand.MAIN_HAND && player.mainHandStack.item != selection.item) || - (swapHand == Hand.OFF_HAND && player.offHandStack.item != selection.item) - ) selection.transfer(when (swapHand) { Hand.MAIN_HAND -> MainHandContainer; Hand.OFF_HAND -> OffHandContainer }) - ?.run() + val selection = selectStack { isItem(Items.END_CRYSTAL) } + if ((swapHand == Hand.MAIN_HAND && player.mainHandStack.item != selection.item) || + (swapHand == Hand.OFF_HAND && player.offHandStack.item != selection.item) + ) runSafeAutomated { + if (!swap) return@runSafe + var crystalSlot = player.hotbarStacks.indexOfFirst { selection.filterStack(it) } + if (crystalSlot < 0) { + val swapTo = when (swapHand) { + Hand.MAIN_HAND -> HotbarContainer + Hand.OFF_HAND -> OffHandContainer + } + if (!selection.transfer(swapTo)) return@runSafe + crystalSlot = player.hotbarStacks.indexOfFirst { selection.filterStack(it) } + } + if (!HotbarRequest(crystalSlot, this).submit().done) return@runSafe + } placeTimer.runSafeIfPassed(placeDelay.milliseconds) { placeInternal(this@Opportunity, swapHand) @@ -495,7 +518,7 @@ object CrystalAura : Module( * @return Whether the delay passed, null if the interaction failed or no crystal found */ fun explode() { - if (rotate && !RotationRequest(placeRotation, this@CrystalAura).submit().done) return + if (rotate && !rotationRequest { rotation(placeRotation) }.submit().done) return explodeTimer.runSafeIfPassed(explodeDelay.milliseconds) { crystal?.let { crystal -> diff --git a/src/main/kotlin/com/lambda/module/modules/combat/FakePlayer.kt b/src/main/kotlin/com/lambda/module/modules/combat/FakePlayer.kt index 7afdb5a64..6a9634899 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/FakePlayer.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/FakePlayer.kt @@ -19,6 +19,7 @@ package com.lambda.module.modules.combat import com.lambda.context.SafeContext import com.lambda.event.events.ConnectionEvent +import com.lambda.event.events.PlayerEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.event.listener.SafeListener.Companion.listenConcurrently @@ -27,11 +28,15 @@ import com.lambda.module.tag.ModuleTag import com.lambda.network.mojang.getProfile import com.lambda.threading.onShutdown import com.lambda.util.Timer +import com.lambda.util.player.FakePlayerId import com.lambda.util.player.spawnFakePlayer import com.mojang.authlib.GameProfile +import com.mojang.datafixers.util.Either import net.minecraft.client.network.OtherClientPlayerEntity import net.minecraft.client.network.PlayerListEntry import java.util.* +import kotlin.jvm.optionals.getOrElse +import kotlin.jvm.optionals.getOrNull import kotlin.time.Duration.Companion.seconds object FakePlayer : Module( @@ -67,6 +72,10 @@ object FakePlayer : Module( cachedProfiles.getOrPut(playerName) { fetchProfile(playerName) } } + listen { + if (it.entity.id == FakePlayerId) it.cancel() + } + listen { disable() } onShutdown { disable() } onDisable { fakePlayer?.discard(); fakePlayer = null } @@ -76,7 +85,8 @@ object FakePlayer : Module( val requestedProfile = getProfile(user).getOrElse { return nilProfile } // Fetch the skin properties from mojang - val properties = mc.sessionService.fetchProfile(requestedProfile.id, true)?.profile?.properties + val properties = mc.apiServices.profileResolver + .getProfile(Either.right(requestedProfile.id)).getOrNull()?.properties // We use the nil profile to avoid the nil username if something wrong happens // Check the GameProfile deserializer you'll understand diff --git a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt index a09350bfb..1cdf18cd0 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt @@ -20,25 +20,27 @@ package com.lambda.module.modules.combat import com.lambda.config.AutomationConfig.Companion.setDefaultAutomationConfig import com.lambda.config.applyEdits import com.lambda.config.groups.Targeting -import com.lambda.event.events.PlayerPacketEvent +import com.lambda.context.SafeContext +import com.lambda.event.events.InventoryEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.RotationRequest +import com.lambda.interaction.managers.hotbar.HotbarRequest +import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest import com.lambda.interaction.managers.rotating.visibilty.lookAtEntity import com.lambda.interaction.material.StackSelection.Companion.selectStack -import com.lambda.interaction.material.container.ContainerManager.transfer -import com.lambda.interaction.material.container.containers.MainHandContainer import com.lambda.module.Module import com.lambda.module.tag.ModuleTag -import com.lambda.task.RootTask.run import com.lambda.threading.runSafeAutomated import com.lambda.util.NamedEnum import com.lambda.util.item.ItemStackUtils.attackDamage -import com.lambda.util.item.ItemStackUtils.equal +import com.lambda.util.item.ItemStackUtils.attackSpeed import com.lambda.util.math.random -import com.lambda.util.player.SlotUtils.hotbarAndStorage +import com.lambda.util.player.SlotUtils.hotbarStacks import net.minecraft.entity.LivingEntity +import net.minecraft.item.ItemStack +import net.minecraft.network.packet.c2s.play.PlayerInteractEntityC2SPacket import net.minecraft.util.Hand +import net.minecraft.world.GameMode object KillAura : Module( name = "KillAura", @@ -48,6 +50,7 @@ object KillAura : Module( // Interact private val rotate by setting("Rotate", true).group(Group.General) private val swap by setting("Swap", true, "Swap to the item with the highest damage").group(Group.General) + private val damageMode by setting("Damage Mode", DamageMode.DPS).group(Group.General) private val attackMode by setting("Attack Mode", AttackMode.Cooldown).group(Group.General) private val cooldownShrink by setting("Cooldown Offset", 0, 0..5, 1) { attackMode == AttackMode.Cooldown }.group(Group.General) private val hitDelay1 by setting("Hit Delay 1", 2.0, 0.0..20.0, 1.0) { attackMode == AttackMode.Delay }.group(Group.General) @@ -59,12 +62,12 @@ object KillAura : Module( val target: LivingEntity? get() = targeting.target() + private var prevEntity = target + private var validServerRot = false + private var lastAttackTime = 0L private var hitDelay = 100.0 - - private var prevY = 0.0 - private var lastY = 0.0 - private var lastOnGround = true + private var cooldownFromSwap = false enum class Group(override val displayName: String) : NamedEnum { General("General"), @@ -76,62 +79,67 @@ object KillAura : Module( Delay } + @Suppress("unused") + enum class DamageMode(override val displayName: String, val block: SafeContext.(ItemStack) -> Double) : NamedEnum { + DPS("Damage Per Second", { player.attackDamage(stack = it) * player.attackSpeed(stack = it) }), + Total("Hit Damage", { player.attackDamage(stack = it) }) + } + init { setDefaultAutomationConfig { applyEdits { - hideAllGroupsExcept(buildConfig) + hideAllGroupsExcept(buildConfig, hotbarConfig, rotationConfig) buildConfig.apply { hide(::pathing, ::stayInRange, ::collectDrops, ::spleefEntities, ::maxPendingActions, ::actionTimeout, ::maxBuildDependencies, ::blockReach) } } } - listen(Int.MIN_VALUE) { event -> - prevY = lastY - lastY = event.position.y - lastOnGround = event.onGround - } + listen { cooldownFromSwap = true } - listen { + listen { target?.let { entity -> - if (swap) { - val selection = selectStack().sortByDescending { player.attackDamage(stack = it) } - - if (!selection.bestItemMatch(player.hotbarAndStorage).equal(player.mainHandStack)) - selection.transfer(MainHandContainer)?.run() - } - // Wait until the rotation has a hit result on the entity + var rotated = true if (rotate) runSafeAutomated { - val rotationRequest = RotationRequest(lookAtEntity(entity)?.rotation ?: return@listen, this@KillAura).submit() - if (!rotationRequest.done) return@listen + val rotationRequest = lookAtEntity(entity)?.rotation?.let { rotationRequest { rotation(it) } }?.submit() ?: return@listen + rotated = rotationRequest.done && entity === prevEntity && validServerRot + prevEntity = entity + validServerRot = rotationRequest.done } + if (swap) { + val selection = selectStack().sortByDescending { + damageMode.block(this, it) + } + + selection.bestItemMatch(player.hotbarStacks)?.let { bestStack -> + val slotId = player.hotbarStacks.indexOf(bestStack) + if (!HotbarRequest(slotId, this@KillAura, nowOrNothing = false).submit().done) return@listen + } + } + + if (!rotated) return@listen + // Cooldown check when (attackMode) { - AttackMode.Cooldown -> if (player.getAttackCooldownProgress(0.5f) + (cooldownShrink / 20f) < 1.0f) return@listen + AttackMode.Cooldown -> if (player.getAttackCooldownProgress(0.5f) + (cooldownShrink / 20f) < 1.0f && !cooldownFromSwap) return@listen AttackMode.Delay -> if (System.currentTimeMillis() - lastAttackTime < hitDelay) return@listen } + cooldownFromSwap = false + // Attack - interaction.attackEntity(player, target) + connection.sendPacket(PlayerInteractEntityC2SPacket.attack(target, player.isSneaking)) + if (interaction.gameMode != GameMode.SPECTATOR) { + player.attack(target) + player.resetTicksSince() + } if (interactConfig.swing) player.swingHand(Hand.MAIN_HAND) lastAttackTime = System.currentTimeMillis() hitDelay = (hitDelay1..hitDelay2).random() * 50 } } - - onEnable { reset() } - onDisable { reset() } - } - - private fun reset() { - lastY = 0.0 - prevY = 0.0 - lastOnGround = true - - lastAttackTime = 0L - hitDelay = 100.0 } } diff --git a/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt b/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt index e84318e5d..842df2a1a 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/PlayerTrap.kt @@ -33,7 +33,7 @@ import com.lambda.util.BlockUtils.blockState import com.lambda.util.extension.shrinkByEpsilon import com.lambda.util.item.ItemUtils.block import com.lambda.util.math.flooredBlockPos -import com.lambda.util.player.SlotUtils.hotbarAndStorage +import com.lambda.util.player.SlotUtils.hotbarAndInventoryStacks import com.lambda.util.world.entitySearch import net.minecraft.block.Blocks import net.minecraft.client.network.OtherClientPlayerEntity @@ -74,7 +74,7 @@ object PlayerTrap : Module( onEnable { task = tickingBlueprint { - val block = player.hotbarAndStorage.firstOrNull { + val block = player.hotbarAndInventoryStacks.firstOrNull { it.item is BlockItem && blocks.contains(it.item.block) }?.item?.block ?: return@tickingBlueprint emptyMap() val targetPlayer = if (self) player diff --git a/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt b/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt index 3973bca7b..e889b0bc0 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/Surround.kt @@ -29,7 +29,7 @@ import com.lambda.task.RootTask.run import com.lambda.task.Task import com.lambda.task.tasks.BuildTask.Companion.build import com.lambda.util.item.ItemUtils.block -import com.lambda.util.player.SlotUtils.hotbarAndStorage +import com.lambda.util.player.SlotUtils.hotbarAndInventoryStacks import net.minecraft.block.Blocks import net.minecraft.item.BlockItem @@ -63,7 +63,7 @@ object Surround : Module( onEnable { task = tickingBlueprint { - val block = player.hotbarAndStorage.firstOrNull { + val block = player.hotbarAndInventoryStacks.firstOrNull { it.item is BlockItem && blocks.contains(it.item.block) }?.item?.block ?: return@tickingBlueprint emptyMap() getTrapPositions(player) diff --git a/src/main/kotlin/com/lambda/module/modules/debug/BlockTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/BlockTest.kt index 1a649301c..499db8337 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/BlockTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/BlockTest.kt @@ -47,12 +47,14 @@ object BlockTest : Module( private val outlineColor = Color(100, 150, 255, 51) init { - onStaticRender { + onStaticRender { esp -> blockSearch(range, step = step) { _, state -> state.isOf(Blocks.DIAMOND_BLOCK) }.forEach { (pos, state) -> - state.getOutlineShape(world, pos).boundingBoxes.forEach { box -> - it.box(box.offset(pos), filledColor, outlineColor) + esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) { + state.getOutlineShape(world, pos).boundingBoxes.forEach { box -> + box(box.offset(pos), filledColor, outlineColor) + } } } } diff --git a/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt index b17420d68..e3ecd9642 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt @@ -23,7 +23,7 @@ import com.lambda.interaction.material.StackSelection.Companion.select import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.task.RootTask.run -import com.lambda.task.tasks.AcquireMaterial.Companion.acquire +import com.lambda.task.tasks.AcquireMaterialTask.Companion.acquire import net.minecraft.item.Items object ContainerTest : Module( diff --git a/src/main/kotlin/com/lambda/module/modules/debug/DebugRendererModule.kt b/src/main/kotlin/com/lambda/module/modules/debug/DebugRendererModule.kt index 9e8c6bb73..152abaec5 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/DebugRendererModule.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/DebugRendererModule.kt @@ -53,35 +53,36 @@ object DebugRendererModule: Module( // private val chunkDebugRenderer by setting("Chunk Debug Renderer", false) // private val octreeDebugRenderer by setting("Octree Debug Renderer", false) - @JvmStatic - fun render( - matrices: MatrixStack, - vertexConsumers: VertexConsumerProvider.Immediate, - cameraX: Double, - cameraY: Double, cameraZ: Double - ) { - val renderers = mc.debugRenderer - mutableListOf().apply { - if (waterDebugRenderer) add(renderers.waterDebugRenderer) - if (chunkBorderDebugRenderer) add(renderers.chunkBorderDebugRenderer) - if (heightmapDebugRenderer) add(renderers.heightmapDebugRenderer) - if (collisionDebugRenderer) add(renderers.collisionDebugRenderer) - if (supportingBlockDebugRenderer) add(renderers.supportingBlockDebugRenderer) - if (neighborUpdateDebugRenderer) add(renderers.neighborUpdateDebugRenderer) - if (redstoneUpdateOrderDebugRenderer) add(renderers.redstoneUpdateOrderDebugRenderer) - if (structureDebugRenderer) add(renderers.structureDebugRenderer) - if (skyLightDebugRenderer) add(renderers.skyLightDebugRenderer) - if (worldGenAttemptDebugRenderer) add(renderers.worldGenAttemptDebugRenderer) - if (blockOutlineDebugRenderer) add(renderers.blockOutlineDebugRenderer) - if (chunkLoadingDebugRenderer) add(renderers.chunkLoadingDebugRenderer) - if (villageDebugRenderer) add(renderers.villageDebugRenderer) - if (villageSectionsDebugRenderer) add(renderers.villageSectionsDebugRenderer) - if (beeDebugRenderer) add(renderers.beeDebugRenderer) - if (raidCenterDebugRenderer) add(renderers.raidCenterDebugRenderer) - if (goalSelectorDebugRenderer) add(renderers.goalSelectorDebugRenderer) - if (gameTestDebugRenderer) add(renderers.gameTestDebugRenderer) - if (gameEventDebugRenderer) add(renderers.gameEventDebugRenderer) - if (lightDebugRenderer) add(renderers.lightDebugRenderer) - }.forEach { it.render(matrices, vertexConsumers, cameraX, cameraY, cameraZ) } - } + // ToDo: Was changed in 1.21.11 -> now we have editable HUD but we may want to add all the hidden options here eg pathfinder +// @JvmStatic +// fun render( +// matrices: MatrixStack, +// vertexConsumers: VertexConsumerProvider.Immediate, +// cameraX: Double, +// cameraY: Double, cameraZ: Double +// ) { +// val renderers = mc.worldRenderer.debugRenderer +// mutableListOf().apply { +// if (waterDebugRenderer) add(renderers.waterDebugRenderer) +// if (chunkBorderDebugRenderer) add(renderers.chunkBorderDebugRenderer) +// if (heightmapDebugRenderer) add(renderers.heightmapDebugRenderer) +// if (collisionDebugRenderer) add(renderers.collisionDebugRenderer) +// if (supportingBlockDebugRenderer) add(renderers.supportingBlockDebugRenderer) +// if (neighborUpdateDebugRenderer) add(renderers.neighborUpdateDebugRenderer) +// if (redstoneUpdateOrderDebugRenderer) add(renderers.redstoneUpdateOrderDebugRenderer) +// if (structureDebugRenderer) add(renderers.structureDebugRenderer) +// if (skyLightDebugRenderer) add(renderers.skyLightDebugRenderer) +// if (worldGenAttemptDebugRenderer) add(renderers.worldGenAttemptDebugRenderer) +// if (blockOutlineDebugRenderer) add(renderers.blockOutlineDebugRenderer) +// if (chunkLoadingDebugRenderer) add(renderers.chunkLoadingDebugRenderer) +// if (villageDebugRenderer) add(renderers.villageDebugRenderer) +// if (villageSectionsDebugRenderer) add(renderers.villageSectionsDebugRenderer) +// if (beeDebugRenderer) add(renderers.beeDebugRenderer) +// if (raidCenterDebugRenderer) add(renderers.raidCenterDebugRenderer) +// if (goalSelectorDebugRenderer) add(renderers.goalSelectorDebugRenderer) +// if (gameTestDebugRenderer) add(renderers.gameTestDebugRenderer) +// if (gameEventDebugRenderer) add(renderers.gameEventDebugRenderer) +// if (lightDebugRenderer) add(renderers.lightDebugRenderer) +// }.forEach { it.render(matrices, vertexConsumers, cameraX, cameraY, cameraZ) } +// } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt index c6a68efef..7520f308a 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt @@ -45,15 +45,19 @@ object RenderTest : Module( private val filledColor = outlineColor.setAlpha(0.2) init { - onDynamicRender { + onDynamicRender { esp -> entitySearch(8.0) .forEach { entity -> - it.box(entity.dynamicBox, filledColor, outlineColor, DirectionMask.ALL, DirectionMask.OutlineMode.And) + esp.shapes(entity.x, entity.y, entity.z) { + box(entity.dynamicBox, filledColor, outlineColor, DirectionMask.ALL, DirectionMask.OutlineMode.And) + } } } - onStaticRender { - it.box(Box.of(player.pos, 0.3, 0.3, 0.3), filledColor, outlineColor) + onStaticRender { esp -> + esp.shapes(player.x, player.y, player.z) { + box(Box.of(player.pos, 0.3, 0.3, 0.3), filledColor, outlineColor) + } } } } diff --git a/src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt index 27f94191f..acbe687f1 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt @@ -21,7 +21,7 @@ import com.lambda.config.AutomationConfig import com.lambda.config.groups.RotationSettings import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.RotationRequest +import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest import com.lambda.interaction.managers.rotating.visibilty.lookAt import com.lambda.module.Module import com.lambda.module.tag.ModuleTag @@ -40,7 +40,7 @@ object RotationTest : Module( } listen { - hitPos?.let { RotationRequest(lookAt(it.pos), this@RotationTest).submit() } + hitPos?.let { rotationRequest { rotation(lookAt(it.pos)) }.submit() } } } } diff --git a/src/main/kotlin/com/lambda/module/modules/debug/SilentSwap.kt b/src/main/kotlin/com/lambda/module/modules/debug/SilentSwap.kt index cd53754e4..5d9f56e3d 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/SilentSwap.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/SilentSwap.kt @@ -20,7 +20,6 @@ package com.lambda.module.modules.debug import com.lambda.config.groups.HotbarSettings import com.lambda.event.events.PlayerEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.Request.Companion.submit import com.lambda.interaction.managers.hotbar.HotbarRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag @@ -40,7 +39,7 @@ object SilentSwap : Module( init { listen { - if (!submit(HotbarRequest(0, this@SilentSwap)).done) { + if (!HotbarRequest(0, this@SilentSwap).submit().done) { it.cancel() return@listen } diff --git a/src/main/kotlin/com/lambda/module/modules/debug/StateInfo.kt b/src/main/kotlin/com/lambda/module/modules/debug/StateInfo.kt index d835e623b..2ddc49190 100644 --- a/src/main/kotlin/com/lambda/module/modules/debug/StateInfo.kt +++ b/src/main/kotlin/com/lambda/module/modules/debug/StateInfo.kt @@ -17,8 +17,7 @@ package com.lambda.module.modules.debug -import com.lambda.event.events.KeyboardEvent -import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.BlockUtils.blockState @@ -35,6 +34,11 @@ object StateInfo : Module( tag = ModuleTag.DEBUG, ) { private val printBind by setting("Print", KeyCode.Unbound, "The bind used to print the info to chat") + .onPress { + val crosshair = mc.crosshairTarget ?: return@onPress + if (crosshair !is BlockHitResult) return@onPress + info(blockState(crosshair.blockPos).betterToString()) + } val propertyFields = Properties::class.java.declaredFields .filter { Property::class.java.isAssignableFrom(it.type) } @@ -46,15 +50,6 @@ object StateInfo : Module( if (crosshair !is BlockHitResult) return@onEnable info(blockState(crosshair.blockPos).betterToString()) } - - listen { event -> - if (!event.isPressed || - !event.satisfies(printBind)) return@listen - - val crosshair = mc.crosshairTarget ?: return@listen - if (crosshair !is BlockHitResult) return@listen - info(blockState(crosshair.blockPos).betterToString()) - } } private fun BlockState.betterToString(): String { diff --git a/src/main/kotlin/com/lambda/module/modules/movement/BackTrack.kt b/src/main/kotlin/com/lambda/module/modules/movement/BackTrack.kt index cae9fd743..a6b255d66 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/BackTrack.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/BackTrack.kt @@ -23,6 +23,7 @@ import com.lambda.event.events.PacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.events.onDynamicRender import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.graphics.esp.ShapeScope import com.lambda.graphics.renderer.esp.DynamicAABB import com.lambda.gui.components.ClickGuiLayout import com.lambda.module.Module @@ -110,7 +111,7 @@ object BackTrack : Module( poolPackets() } - onDynamicRender { + onDynamicRender { esp -> val target = target ?: return@onDynamicRender val c1 = ClickGuiLayout.primaryColor @@ -118,7 +119,9 @@ object BackTrack : Module( val p = target.hurtTime / 10.0 val c = lerp(p, c1, c2) - it.box(box, c.multAlpha(0.3), c.multAlpha(0.8)) + esp.shapes(target.pos.x, target.pos.y, target.pos.z) { + box(box, c.multAlpha(0.3), c.multAlpha(0.8)) + } } listen { event -> diff --git a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt index 9983cab53..cdfb26208 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/BetterFirework.kt @@ -20,9 +20,8 @@ package com.lambda.module.modules.movement import com.lambda.config.AutomationConfig.Companion.setDefaultAutomationConfig import com.lambda.config.applyEdits import com.lambda.config.settings.complex.Bind +import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress import com.lambda.context.SafeContext -import com.lambda.event.events.KeyboardEvent -import com.lambda.event.events.MouseEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.managers.hotbar.HotbarRequest @@ -33,8 +32,8 @@ import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.KeyCode import com.lambda.util.Mouse -import com.lambda.util.player.SlotUtils.hotbar -import com.lambda.util.player.SlotUtils.hotbarAndStorage +import com.lambda.util.player.SlotUtils.hotbarAndInventoryStacks +import com.lambda.util.player.SlotUtils.hotbarStacks import net.minecraft.client.network.ClientPlayerEntity import net.minecraft.entity.effect.StatusEffects import net.minecraft.item.Items @@ -49,7 +48,14 @@ object BetterFirework : Module( tag = ModuleTag.MOVEMENT, ) { private var activateButton by setting("Activate Key", Bind(0, 0, Mouse.Middle.ordinal), "Button to activate Firework") - private var midFlightActivationKey by setting("Mid-Flight Activation Key", Bind(0, 0), "Firework use key for mid flight activation") + .onPress { + if (takeoffState != TakeoffState.None) return@onPress // Prevent using multiple times + // If already gliding use another firework + if (player.canOpenElytra || player.isGliding) takeoffState = TakeoffState.StartFlying + else if (player.canTakeoff) takeoffState = TakeoffState.Jumping + } + private var midFlightActivationKey by setting("Mid-Flight Activation Key", Bind.EMPTY, "Firework use key for mid flight activation") + .onPress { if (player.isGliding) takeoffState = TakeoffState.StartFlying } private var middleClickCancel by setting("Middle Click Cancel", false, description = "Cancel pick block action on middle mouse click") { activateButton.key != KeyCode.Unbound.code } private var fireworkInteract by setting("Right Click Fly", true, "Automatically start flying when right clicking fireworks") private var fireworkInteractCancel by setting("Right Click Cancel", false, "Cancel block interactions while holding fireworks") { fireworkInteract } @@ -93,58 +99,6 @@ object BetterFirework : Module( } } } - listen { - if (!it.isPressed) { - return@listen - } - if (it.satisfies(activateButton)) { - if (activateButton.mouse == mc.options.pickItemKey.boundKey.code) { - return@listen - } - runSafe { - if (takeoffState != TakeoffState.None) { - return@listen // Prevent using multiple times - } - if (player.canOpenElytra || player.isGliding) { - // If already gliding use another firework - takeoffState = TakeoffState.StartFlying - } else if (player.canTakeoff) { - takeoffState = TakeoffState.Jumping - } - } - } - if (it.satisfies(midFlightActivationKey)) { - runSafe { - if (player.isGliding) - takeoffState = TakeoffState.StartFlying - } - } - } - listen { - if (!it.isPressed) { - return@listen - } - if (it.satisfies(activateButton)) { - if (activateButton.key != mc.options.pickItemKey.boundKey.code) { - runSafe { - if (takeoffState == TakeoffState.None) { - if (player.canOpenElytra || player.isGliding) { - // If already gliding use another firework - takeoffState = TakeoffState.StartFlying - } else if (player.canTakeoff) { - takeoffState = TakeoffState.Jumping - } - } - } - } - } - if (it.satisfies(midFlightActivationKey)) { - runSafe { - if (player.isGliding) - takeoffState = TakeoffState.StartFlying - } - } - } } /** @@ -179,17 +133,9 @@ object BetterFirework : Module( runSafe { when { (mc.crosshairTarget?.type == HitResult.Type.BLOCK && !middleClickCancel) || - (!activateButton.isMouseBind || activateButton.mouse != mc.options.pickItemKey.boundKey.code) || + activateButton.mouse != mc.options.pickItemKey.boundKey.code || takeoffState != TakeoffState.None -> false // Prevent using multiple times - else -> { - if (player.canOpenElytra || player.isGliding) { - // If already gliding use another firework - takeoffState = TakeoffState.StartFlying - } else if (player.canTakeoff) { - takeoffState = TakeoffState.Jumping - } - middleClickCancel - } + else -> middleClickCancel } } ?: false @@ -209,9 +155,9 @@ object BetterFirework : Module( fun SafeContext.startFirework(silent: Boolean) { val stack = selectStack(count = 1) { isItem(Items.FIREWORK_ROCKET) } - stack.bestItemMatch(player.hotbar) + stack.bestItemMatch(player.hotbarStacks) ?.let { - val request = HotbarRequest(player.hotbar.indexOf(it), this@BetterFirework, keepTicks = 0) + val request = HotbarRequest(player.hotbarStacks.indexOf(it), this@BetterFirework, keepTicks = 0) .submit(queueIfMismatchedStage = false) if (request.done) { interaction.interactItem(player, Hand.MAIN_HAND) @@ -222,10 +168,10 @@ object BetterFirework : Module( if (!silent) return - stack.bestItemMatch(player.hotbarAndStorage) + stack.bestItemMatch(player.hotbarAndInventoryStacks) ?.let { - val swapSlotId = player.hotbarAndStorage.indexOf(it) - val hotbarSlotToSwapWith = player.hotbar.find { slot -> slot.isEmpty }?.let { slot -> player.hotbar.indexOf(slot) } ?: 8 + val swapSlotId = player.hotbarAndInventoryStacks.indexOf(it) + val hotbarSlotToSwapWith = player.hotbarStacks.find { slot -> slot.isEmpty }?.let { slot -> player.hotbarStacks.indexOf(slot) } ?: 8 inventoryRequest { swap(swapSlotId, hotbarSlotToSwapWith) diff --git a/src/main/kotlin/com/lambda/module/modules/movement/Blink.kt b/src/main/kotlin/com/lambda/module/modules/movement/Blink.kt index 5c4f4848b..f94ddb8fd 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/Blink.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/Blink.kt @@ -22,6 +22,7 @@ import com.lambda.event.events.PacketEvent import com.lambda.event.events.RenderEvent import com.lambda.event.events.onDynamicRender import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.graphics.esp.ShapeScope import com.lambda.graphics.renderer.esp.DynamicAABB import com.lambda.gui.components.ClickGuiLayout import com.lambda.module.Module @@ -67,9 +68,12 @@ object Blink : Module( poolPackets() } - onDynamicRender { + onDynamicRender { esp -> val color = ClickGuiLayout.primaryColor - it.box(box.update(lastBox), color.setAlpha(0.3), color) + val pos = player.pos + esp.shapes(pos.x, pos.y, pos.z) { + box(box.update(lastBox), color.setAlpha(0.3), color) + } } listen { event -> diff --git a/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt b/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt index da17e36f4..9e8ca0753 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt @@ -17,11 +17,11 @@ package com.lambda.module.modules.movement -import com.lambda.config.groups.RotationSettings +import com.lambda.config.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.applyEdits import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.Rotation -import com.lambda.interaction.managers.rotating.RotationRequest +import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest import com.lambda.module.Module import com.lambda.module.modules.movement.BetterFirework.startFirework import com.lambda.module.tag.ModuleTag @@ -80,8 +80,6 @@ object ElytraAltitudeControl : Module( val pitch40SpeedThreshold by setting("Speed Threshold", 41f, 10f..100f, .5f, description = "Speed at which to start pitching up") { usePitch40OnHeight }.group(Group.Pitch40Control) val pitch40UseFireworkOnUpTrajectory by setting("Use Firework On Up Trajectory", false, "Use fireworks when converting speed to altitude in the Pitch 40 maneuver") { usePitch40OnHeight }.group(Group.Pitch40Control) - override val rotationConfig = RotationSettings(this, Group.Rotation) - var controlState = ControlState.AttitudeControl var state = Pitch40State.GainSpeed var lastAngle = pitch40UpStartAngle @@ -91,12 +89,18 @@ object ElytraAltitudeControl : Module( val usageDelay = Timer() init { + setDefaultAutomationConfig { + applyEdits { + hideAllGroupsExcept(rotationConfig) + } + } + listen { if (!player.isGliding) return@listen run { when (controlState) { ControlState.AttitudeControl -> { - if (disableOnFirework && player.hasFirework) { + if (disableOnFirework && hasFirework) { return@run } if (usePitch40OnHeight) { @@ -114,9 +118,9 @@ object ElytraAltitudeControl : Module( -1 * altitudeController.getOutput(targetAltitude.toDouble(), player.y) // Negative because in minecraft pitch > 0 is looking down not up } }.coerceIn(-maxPitchAngle, maxPitchAngle) - RotationRequest(Rotation(player.yaw, outputPitch.toFloat()), this@ElytraAltitudeControl).submit() + rotationRequest { pitch(outputPitch) }.submit() - if (usageDelay.timePassed(2.seconds) && !player.hasFirework) { + if (usageDelay.timePassed(2.seconds) && !hasFirework) { if (useFireworkOnHeight && minHeight > player.y) { usageDelay.reset() runSafe { @@ -133,14 +137,14 @@ object ElytraAltitudeControl : Module( } ControlState.Pitch40Fly -> when (state) { Pitch40State.GainSpeed -> { - RotationRequest(Rotation(player.yaw, pitch40DownAngle), this@ElytraAltitudeControl).submit() + rotationRequest { pitch(pitch40DownAngle) }.submit() if (player.flySpeed() > pitch40SpeedThreshold) { state = Pitch40State.PitchUp } } Pitch40State.PitchUp -> { lastAngle -= 5f - RotationRequest(Rotation(player.yaw, lastAngle), this@ElytraAltitudeControl).submit() + rotationRequest { pitch(lastAngle) }.submit() if (lastAngle <= pitch40UpStartAngle) { state = Pitch40State.FlyUp if (pitch40UseFireworkOnUpTrajectory) { @@ -152,7 +156,7 @@ object ElytraAltitudeControl : Module( } Pitch40State.FlyUp -> { lastAngle += pitch40AngleChangeRate - RotationRequest(Rotation(player.yaw, lastAngle), this@ElytraAltitudeControl).submit() + rotationRequest { pitch(lastAngle) }.submit() if (lastAngle >= 0f) { state = Pitch40State.GainSpeed if (logHeightGain) { @@ -187,8 +191,8 @@ object ElytraAltitudeControl : Module( } } - val ClientPlayerEntity.hasFirework: Boolean - get() = runSafe { return fastEntitySearch(4.0) { it.shooter == this.player }.any() } ?: false + val hasFirework: Boolean + get() = runSafe { return fastEntitySearch(4.0) { it.shooter == player }.any() } ?: false class PIController(val valueP: () -> Double, val valueD: () -> Double, val valueI: () -> Double, val constant: () -> Double) { var accumulator = 0.0 // Integral term accumulator diff --git a/src/main/kotlin/com/lambda/module/modules/movement/ElytraFly.kt b/src/main/kotlin/com/lambda/module/modules/movement/ElytraFly.kt index 7ba58563e..2a3182b04 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/ElytraFly.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/ElytraFly.kt @@ -17,14 +17,25 @@ package com.lambda.module.modules.movement +import com.lambda.config.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.applyEdits +import com.lambda.context.SafeContext import com.lambda.event.events.ClientEvent import com.lambda.event.events.MovementEvent +import com.lambda.event.events.PacketEvent +import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest import com.lambda.module.Module +import com.lambda.module.modules.movement.BetterFirework.canOpenElytra +import com.lambda.module.modules.movement.BetterFirework.canTakeoff import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.extension.isElytraFlying import com.lambda.util.player.MovementUtils.addSpeed +import net.minecraft.entity.Entity +import net.minecraft.network.packet.c2s.play.ClientCommandC2SPacket +import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket import net.minecraft.sound.SoundEvents object ElytraFly : Module( @@ -32,20 +43,70 @@ object ElytraFly : Module( description = "Allows you to fly with an elytra", tag = ModuleTag.MOVEMENT, ) { - private val playerBoost by setting("Player Boost", true, description = "Boosts the player when flying") - private val playerSpeed by setting("Player Speed", 0.02, 0.0..0.5, 0.005, description = "Speed to add when flying") { playerBoost } - private val rocketBoost by setting("Rocket Boost", false, description = "Boosts the player when using a firework") - private val rocketSpeed by setting("Rocket Speed", 2.0, 0.0 ..2.0, description = "Speed multiplier that the rocket gives you") { rocketBoost } + @JvmStatic val mode by setting("Mode", FlyMode.Bounce) + + //ToDo: Implement these commented out settings + private val takeoff by setting("Takeoff", true, "Automatically jumps and initiates gliding") { mode == FlyMode.Bounce } + private val autoPitch by setting("Auto Pitch", true, "Automatically pitches the players rotation down to bounce at faster speeds") { mode == FlyMode.Bounce } + private val pitch by setting("Pitch", 80, 0..90, 1) { autoPitch && mode == FlyMode.Bounce } + private val jump by setting("Jump", true, "Automatically jumps") { mode == FlyMode.Bounce } + private val flagPause by setting("Flag Pause", 20, 0..100, 1, "How long to pause if the server flags you for a movement check") { mode == FlyMode.Bounce } +// private val passObstacles by setting("Pass Obstacles", true, "Automatically paths around obstacles using baritone") { mode == FlyMode.Bounce } + + private val boostSpeed by setting("Boost", 0.00, 0.0..0.5, 0.005, description = "Speed to add when flying") + private val rocketSpeed by setting("Rocket Speed", 0.0, 0.0 ..2.0, description = "Speed multiplier that the rocket gives you") { mode == FlyMode.Enhanced } private val mute by setting("Mute Elytra", false, "Mutes the elytra sound when gliding") - @JvmStatic - val doBoost: Boolean get() = isEnabled && rocketBoost + var jumpThisTick = false + var previouslyFlying: Boolean? = null + var glidePause = 0 init { + setDefaultAutomationConfig { + applyEdits { + hideAllGroupsExcept(inventoryConfig) + } + } + + listen { + if (mode != FlyMode.Bounce) return@listen + if (autoPitch) rotationRequest { pitch(pitch.toFloat()) }.submit() + + if (!player.isGliding) { + if (takeoff && player.canTakeoff) { + if (player.canOpenElytra) { + player.startGliding() + startFlyPacket() + } else jumpThisTick = true + } + return@listen + } + + startFlyPacket() + } + + listen { + if (glidePause > 0) glidePause-- + } + + listen { event -> + if (event.packet !is PlayerPositionLookS2CPacket) return@listen + if (mode == FlyMode.Bounce && player.isGliding) { + glidePause = flagPause + } + } + + listen { event -> + if (mode == FlyMode.Bounce && ((player.isGliding && jump) || jumpThisTick)) { + event.input.jump() + jumpThisTick = false + } + } + listen { - if (playerBoost && player.isElytraFlying && !player.isUsingItem) { - addSpeed(playerSpeed) + if (player.isElytraFlying && !player.isUsingItem) { + addSpeed(boostSpeed) } } @@ -56,8 +117,26 @@ object ElytraFly : Module( } } + private fun SafeContext.startFlyPacket() = + connection.sendPacket(ClientCommandC2SPacket(player, ClientCommandC2SPacket.Mode.START_FALL_FLYING)) + + @JvmStatic + fun isGliding(): Boolean? = runSafe { + val original: Boolean = player.getFlag(Entity.GLIDING_FLAG_INDEX) + if (previouslyFlying == null) { + previouslyFlying = original + return@runSafe original + } + return if (isEnabled && mode == FlyMode.Bounce && previouslyFlying == true && glidePause <= 0) true + else { + previouslyFlying = original + original + } + } + @JvmStatic fun boostRocket() = runSafe { + if (mode == FlyMode.Bounce) return@runSafe val vec = player.rotationVector val velocity = player.velocity @@ -70,4 +149,9 @@ object ElytraFly : Module( vec.z * e + (vec.z * d - velocity.z) * 0.5 ) } + + enum class FlyMode { + Bounce, + Enhanced + } } diff --git a/src/main/kotlin/com/lambda/interaction/managers/Logger.kt b/src/main/kotlin/com/lambda/module/modules/movement/NoJumpCooldown.kt similarity index 73% rename from src/main/kotlin/com/lambda/interaction/managers/Logger.kt rename to src/main/kotlin/com/lambda/module/modules/movement/NoJumpCooldown.kt index 4f46c29f2..b9dc8e2ba 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/Logger.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/NoJumpCooldown.kt @@ -15,8 +15,13 @@ * along with this program. If not, see . */ -package com.lambda.interaction.managers +package com.lambda.module.modules.movement -interface Logger { - val logger: DebugLogger -} \ No newline at end of file +import com.lambda.module.Module +import com.lambda.module.tag.ModuleTag + +object NoJumpCooldown : Module( + name = "NoJumpCooldown", + description = "Removes delay between jumps", + tag = ModuleTag.MOVEMENT +) \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt b/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt index c30d1d5a3..355af4df2 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt @@ -22,7 +22,7 @@ import com.lambda.event.events.ClientEvent import com.lambda.event.events.MovementEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.Request.Companion.submit +import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest import com.lambda.interaction.managers.rotating.Rotation import com.lambda.interaction.managers.rotating.RotationConfig import com.lambda.interaction.managers.rotating.RotationMode @@ -121,7 +121,7 @@ object Speed : Module( intendedMoveYaw - 45.0f } else intendedMoveYaw - submit(RotationRequest(Rotation(targetYaw, player.pitch.toDouble()), this@Speed)) + rotationRequest { rotation(targetYaw, player.pitch.toDouble()) }.submit() } onEnable { diff --git a/src/main/kotlin/com/lambda/module/modules/network/Rubberband.kt b/src/main/kotlin/com/lambda/module/modules/network/Rubberband.kt index b988e2e18..dd6f8040e 100644 --- a/src/main/kotlin/com/lambda/module/modules/network/Rubberband.kt +++ b/src/main/kotlin/com/lambda/module/modules/network/Rubberband.kt @@ -18,11 +18,12 @@ package com.lambda.module.modules.network import com.lambda.event.events.PacketEvent +import com.lambda.event.events.PlayerPacketEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.PlayerPacketHandler import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.Communication.warn +import com.lambda.util.collections.LimitedOrderedSet import com.lambda.util.math.dist import com.lambda.util.math.distSq import com.lambda.util.text.buildText @@ -43,25 +44,27 @@ object Rubberband : Module( private val showConnectionState by setting("Show Connection State", true) private val showRubberbandInfo by setting("Show Rubberband Info", true) + val configurations = LimitedOrderedSet(100) + init { listen { event -> if (!showRubberbandInfo) return@listen if (event.packet !is PlayerPositionLookS2CPacket) return@listen - if (PlayerPacketHandler.configurations.isEmpty()) { + if (configurations.isEmpty()) { this@Rubberband.warn("Position was reverted") return@listen } val newPos = event.packet.change.position - val last = PlayerPacketHandler.configurations.minBy { + val last = configurations.minBy { it.position distSq newPos } this@Rubberband.warn(buildText { literal("Reverted position by ") color(Color.YELLOW) { - literal("${PlayerPacketHandler.configurations.toList().asReversed().indexOf(last) + 1}") + literal("${configurations.toList().asReversed().indexOf(last) + 1}") } literal(" ticks (deviation: ") color(Color.YELLOW) { @@ -70,5 +73,7 @@ object Rubberband : Module( literal(")") }) } + + listen { configurations.add(it) } } } diff --git a/src/main/kotlin/com/lambda/module/modules/player/AirPlace.kt b/src/main/kotlin/com/lambda/module/modules/player/AirPlace.kt index 3aef26543..c5b3b0b1b 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/AirPlace.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/AirPlace.kt @@ -21,7 +21,7 @@ import com.lambda.config.AutomationConfig.Companion.setDefaultAutomationConfig import com.lambda.config.applyEdits import com.lambda.config.settings.complex.Bind import com.lambda.context.SafeContext -import com.lambda.event.events.MouseEvent +import com.lambda.event.events.ButtonEvent import com.lambda.event.events.PlayerEvent import com.lambda.event.events.TickEvent import com.lambda.event.events.onStaticRender @@ -106,17 +106,19 @@ object AirPlace : Module( listen { if (airPlace()) it.cancel() } listen { if (airPlace()) it.cancel() } - onStaticRender { event -> + onStaticRender { esp -> placementPos?.let { pos -> val boxes = placementState?.getOutlineShape(world, pos)?.boundingBoxes ?: listOf(Box(0.0, 0.0, 0.0, 1.0, 1.0, 1.0)) - boxes.forEach { box -> - event.outline(box.offset(pos), outlineColor) + esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) { + boxes.forEach { box -> + outline(box.offset(pos), outlineColor) + } } } } - listen { event -> + listen { event -> if (!scrollBind.isSatisfied()) return@listen event.cancel() distance += event.delta.y diff --git a/src/main/kotlin/com/lambda/module/modules/player/AntiAim.kt b/src/main/kotlin/com/lambda/module/modules/player/AntiAim.kt index 17b144120..5039ebcf5 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/AntiAim.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/AntiAim.kt @@ -21,11 +21,8 @@ import com.lambda.config.groups.RotationSettings import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.Request.Companion.submit -import com.lambda.interaction.managers.rotating.Rotation +import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest import com.lambda.interaction.managers.rotating.Rotation.Companion.rotationTo -import com.lambda.interaction.managers.rotating.Rotation.Companion.wrap -import com.lambda.interaction.managers.rotating.RotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.NamedEnum @@ -86,7 +83,7 @@ object AntiAim : Module( currentPitch = player.pitch } - listen(priority = Int.MAX_VALUE) { + listen { currentYaw = wrapDegrees(when (yaw) { YawMode.Spin -> when (spinMode) { LeftRight.Left -> currentYaw - yawSpeed @@ -158,11 +155,11 @@ object AntiAim : Module( } PitchMode.None -> player.pitch }.coerceIn(-90f..90f) - } - listen(priority = Int.MIN_VALUE) { - if (currentYaw == wrap(player.yaw) && currentPitch == player.pitch) return@listen - submit(RotationRequest(Rotation(currentYaw, currentPitch), this@AntiAim), false) + rotationRequest { + if (yaw != YawMode.None) yaw(currentYaw) + if (pitch != PitchMode.None) pitch(currentPitch) + }.submit() } } diff --git a/src/main/kotlin/com/lambda/module/modules/player/AutoArmor.kt b/src/main/kotlin/com/lambda/module/modules/player/AutoArmor.kt new file mode 100644 index 000000000..89e7259f9 --- /dev/null +++ b/src/main/kotlin/com/lambda/module/modules/player/AutoArmor.kt @@ -0,0 +1,146 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.module.modules.player + +import com.lambda.config.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.applyEdits +import com.lambda.config.settings.complex.Bind +import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress +import com.lambda.context.SafeContext +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest +import com.lambda.module.Module +import com.lambda.module.tag.ModuleTag +import com.lambda.util.EnchantmentUtils.getEnchantment +import com.lambda.util.player.SlotUtils.armorSlots +import com.lambda.util.player.SlotUtils.hotbarAndInventorySlots +import net.minecraft.component.DataComponentTypes +import net.minecraft.enchantment.Enchantment +import net.minecraft.enchantment.Enchantments +import net.minecraft.entity.attribute.EntityAttributes +import net.minecraft.item.ItemStack +import net.minecraft.item.Items +import net.minecraft.registry.RegistryKey +import net.minecraft.registry.tag.ItemTags +import net.minecraft.screen.slot.Slot + +object AutoArmor : Module( + name = "AutoArmor", + description = "Automatically equips armor", + tag = ModuleTag.COMBAT +) { + private var elytraPriority by setting("Elytra Priority", true, "Prioritizes elytra's over other armor pieces in the chest slot") + private val toggleElytraPriority by setting("Toggle Elytra Priority", Bind.EMPTY) + .onPress { elytraPriority = !elytraPriority } + private val minDurabilityPercentage by setting("Min Durability", 5, 0..100, 1, "Minimum durability percentage before being swapped for a new piece", "%") + private val headProtection by setting("Preferred Head Protection", Protection.Protection) + private val chestProtection by setting("Preferred Chest Protection", Protection.Protection) + private val legProtection by setting("Preferred Leg Protection", Protection.BlastProtection) + private val feetProtection by setting("Preferred Feet Protection", Protection.Protection) + private val ignoreBinding by setting("Ignore Binding", true, "Ignores curse of binding armor pieces") + + val sorter = compareByDescending { + if (it.stack.isDamageable && 1 - (it.stack.damage.toFloat() / it.stack.maxDamage) < minDurabilityPercentage.toFloat() / 100) + -Double.MAX_VALUE + else 0.0 + }.thenByDescending { + if (elytraPriority) { + if (it.stack.item == Items.ELYTRA) 1.0 + else 0.0 + } else 0.0 + }.thenByDescending { + it.stack.getOrDefault(DataComponentTypes.ATTRIBUTE_MODIFIERS, null) + ?.modifiers + ?.find { modifier -> modifier.attribute == EntityAttributes.ARMOR } + ?.modifier?.value + ?: 0.0 + }.thenByDescending { + it.stack.getOrDefault(DataComponentTypes.ATTRIBUTE_MODIFIERS, null) + ?.modifiers + ?.find { modifier -> modifier.attribute == EntityAttributes.ARMOR_TOUGHNESS } + ?.modifier?.value + ?: 0.0 + }.thenByDescending { + val stack = it.stack + when { + stack.isIn(ItemTags.FOOT_ARMOR) -> stack.getEnchantment(feetProtection.enchant) + stack.isIn(ItemTags.LEG_ARMOR) -> stack.getEnchantment(legProtection.enchant) + stack.isIn(ItemTags.CHEST_ARMOR) -> stack.getEnchantment(chestProtection.enchant) + else -> stack.getEnchantment(headProtection.enchant) + } + }.thenByDescending { slot -> + Protection.entries.fold(0) { acc, protection -> + acc + slot.stack.getEnchantment(protection.enchant) + } + }.thenByDescending { slot -> + slot.stack.getEnchantment(Enchantments.UNBREAKING) + + slot.stack.getEnchantment(Enchantments.MENDING) + } + + init { + setDefaultAutomationConfig { + applyEdits { + hideAllGroupsExcept(inventoryConfig) + } + } + + listen { + val armorSlots = player.armorSlots + + val swappable = player.hotbarAndInventorySlots + .filter { it.stack.isEquipable && (!ignoreBinding || it.stack.getEnchantment(Enchantments.BINDING_CURSE) <= 0) } + .sortedWith(sorter) + .distinctBy { it.stack.armorSlot } + + val swaps = mutableListOf>() + armorSlots.forEach { equipped -> + val new = swappable.find { new -> + equipped.canInsert(new.stack) && sorter.compare(equipped, new) > 0 + } ?: return@forEach + + swaps.add(Pair(new, equipped)) + } + + if (swaps.isEmpty()) return@listen + inventoryRequest { + swaps.forEach { + pickup(it.first.id) + pickup(it.second.id) + if (!it.second.stack.isEmpty) + pickup(it.first.id) + } + }.submit() + } + } + + context(safeContext: SafeContext) + private val ItemStack.isEquipable get() = + safeContext.player.armorSlots.any { it.canInsert(this) } + + context(safeContext: SafeContext) + private val ItemStack.armorSlot get() = + safeContext.player.armorSlots.firstOrNull { it.canInsert(this) } + + private enum class Protection(val enchant: RegistryKey) { + Protection(Enchantments.PROTECTION), + BlastProtection(Enchantments.BLAST_PROTECTION), + ProjectileProtection(Enchantments.PROJECTILE_PROTECTION), + FireProtection(Enchantments.FIRE_PROTECTION); + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/module/modules/player/ClickFriend.kt b/src/main/kotlin/com/lambda/module/modules/player/ClickFriend.kt index a23e6e779..d1b357033 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/ClickFriend.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/ClickFriend.kt @@ -18,8 +18,8 @@ package com.lambda.module.modules.player import com.lambda.config.settings.complex.Bind -import com.lambda.event.events.MouseEvent -import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress +import com.lambda.context.SafeContext import com.lambda.friend.FriendManager import com.lambda.friend.FriendManager.befriend import com.lambda.friend.FriendManager.isFriend @@ -27,6 +27,7 @@ import com.lambda.friend.FriendManager.unfriend import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.Communication.info +import com.lambda.util.InputUtils.isSatisfied import com.lambda.util.world.raycast.RayCastUtils.entityResult import net.minecraft.client.network.OtherClientPlayerEntity import org.lwjgl.glfw.GLFW @@ -37,23 +38,18 @@ object ClickFriend : Module( description = "Add or remove friends with a single click", tag = ModuleTag.PLAYER, ) { - private val friendBind by setting("Friend Bind", Bind(0, 0, GLFW.GLFW_MOUSE_BUTTON_MIDDLE), "Bind to press to befriend a player") - private val unfriendBind by setting("Unfriend Bind", Bind(0, GLFW_MOD_SHIFT, GLFW.GLFW_MOUSE_BUTTON_MIDDLE), "Bind to press to unfriend a player") + private val friendBind: Bind by setting("Friend Bind", Bind(0, 0, GLFW.GLFW_MOUSE_BUTTON_MIDDLE), "Bind to press to befriend a player") + .onPress { if (!unfriendBind.isSatisfied()) if (checkSetFriend(true)) it.cancel() } - init { - listen { - if (mc.currentScreen != null) return@listen + private val unfriendBind: Bind by setting("Unfriend Bind", Bind(0, GLFW_MOD_SHIFT, GLFW.GLFW_MOUSE_BUTTON_MIDDLE), "Bind to press to unfriend a player") + .onPress { if (!friendBind.isSatisfied()) if (checkSetFriend(false)) it.cancel() } - val target = mc.crosshairTarget?.entityResult?.entity as? OtherClientPlayerEntity - ?: return@listen + private fun SafeContext.checkSetFriend(friend: Boolean): Boolean { + val target = mc.crosshairTarget?.entityResult?.entity as? OtherClientPlayerEntity + ?: return false - when { - it.satisfies(friendBind) && !target.isFriend && target.befriend() -> - info(FriendManager.befriendedText(target.name)) - - it.satisfies(unfriendBind) && target.isFriend && target.unfriend() -> - info(FriendManager.unfriendedText(target.name)) - } - } + if (friend && !target.isFriend && target.befriend()) info(FriendManager.befriendedText(target.name)) + else if (!friend && target.isFriend && target.unfriend()) info(FriendManager.unfriendedText(target.name)) + return true } } diff --git a/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt b/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt index 95d393b1f..018c2624e 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/FastBreak.kt @@ -30,13 +30,13 @@ import com.lambda.threading.runSafeAutomated import java.util.concurrent.ConcurrentLinkedQueue object FastBreak : Module( - name = "FastBreak", - description = "Break blocks faster.", - tag = ModuleTag.PLAYER, + name = "FastBreak", + description = "Break blocks faster.", + tag = ModuleTag.PLAYER, ) { - private val pendingActions = ConcurrentLinkedQueue() + private val pendingActions = ConcurrentLinkedQueue() - init { + init { setDefaultAutomationConfig { applyEdits { hideAllGroupsExcept(buildConfig, breakConfig, rotationConfig, hotbarConfig) @@ -48,7 +48,9 @@ object FastBreak : Module( ::maxBuildDependencies, ::collectDrops, ::blockReach, - ::entityReach + ::entityReach, + ::breakBlocks, + ::interactBlocks ) ::maxBuildDependencies.edit { defaultValue(0) } editTyped( @@ -76,12 +78,12 @@ object FastBreak : Module( } } - listen { it.cancel() } - listen { event -> - event.cancel() - runSafeAutomated { + listen { it.cancel() } + listen { event -> + event.cancel() + runSafeAutomated { breakRequest(listOf(event.pos), pendingActions)?.submit() } - } - } + } + } } diff --git a/src/main/kotlin/com/lambda/module/modules/player/Freecam.kt b/src/main/kotlin/com/lambda/module/modules/player/Freecam.kt index 5f3c03772..4c0558313 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Freecam.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Freecam.kt @@ -23,11 +23,10 @@ import com.lambda.event.events.PlayerEvent import com.lambda.event.events.RenderEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.Request.Companion.submit +import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest import com.lambda.interaction.managers.rotating.Rotation import com.lambda.interaction.managers.rotating.RotationConfig import com.lambda.interaction.managers.rotating.RotationMode -import com.lambda.interaction.managers.rotating.RotationRequest import com.lambda.interaction.managers.rotating.visibilty.lookAt import com.lambda.module.Module import com.lambda.module.tag.ModuleTag @@ -58,7 +57,7 @@ import net.minecraft.util.math.Vec3d object Freecam : Module( name = "Freecam", description = "Move your camera freely", - tag = ModuleTag.PLAYER, + tag = ModuleTag.RENDER, autoDisable = true, ) { private val speed by setting("Speed", 0.5, 0.1..1.0, 0.1, "Freecam movement speed", unit = "m/s") @@ -114,11 +113,11 @@ object Freecam : Module( listen { when (rotateMode) { FreecamRotationMode.None -> return@listen - FreecamRotationMode.KeepRotation -> submit(RotationRequest(rotation, this@Freecam)) + FreecamRotationMode.KeepRotation -> rotationRequest { rotation(rotation) }.submit() FreecamRotationMode.LookAtTarget -> mc.crosshairTarget?.let { runSafeAutomated { - submit(RotationRequest(lookAt(it.pos), this@Freecam)) + rotationRequest { rotation(lookAt(it.pos)) }.submit() } } } diff --git a/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt b/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt index c98611b69..26e56ee43 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt @@ -18,6 +18,7 @@ package com.lambda.module.modules.player import com.lambda.config.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.applyEdits import com.lambda.interaction.BaritoneManager import com.lambda.interaction.construction.blueprint.Blueprint.Companion.emptyStructure import com.lambda.interaction.construction.blueprint.PropagatingBlueprint.Companion.propagatingBlueprint @@ -88,7 +89,13 @@ object HighwayTools : Module( } init { - setDefaultAutomationConfig() + setDefaultAutomationConfig { + applyEdits { + buildConfig.apply { + editTyped(::pathing, ::stayInRange) { defaultValue(true) } + } + } + } onEnable { octant = player.octant diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt index 822f1ca9c..378d289fc 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt @@ -21,11 +21,9 @@ import com.lambda.Lambda.mc import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.gui.LambdaScreen -import com.lambda.interaction.managers.Request.Companion.submit -import com.lambda.interaction.managers.rotating.Rotation +import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest import com.lambda.interaction.managers.rotating.RotationConfig import com.lambda.interaction.managers.rotating.RotationMode -import com.lambda.interaction.managers.rotating.RotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.InputUtils.isKeyPressed @@ -46,57 +44,57 @@ import org.lwjgl.glfw.GLFW.GLFW_KEY_RIGHT import org.lwjgl.glfw.GLFW.GLFW_KEY_UP object InventoryMove : Module( - name = "InventoryMove", - description = "Allows you to move with GUIs opened", - tag = ModuleTag.PLAYER, + name = "InventoryMove", + description = "Allows you to move with GUIs opened", + tag = ModuleTag.PLAYER, ) { - private val clickGui by setting("ClickGui", false) - private val disableSneak by setting("Disable Crouch", false) - private val arrowKeys by setting("Arrow Keys", false, "Allows rotating the players camera using the arrow keys") - private val speed by setting("Rotation Speed", 5, 1..20, 1, unit = "°/tick") { arrowKeys } - override val rotationConfig = RotationConfig.Instant(RotationMode.Lock) + private val clickGui by setting("ClickGui", false) + private val disableSneak by setting("Disable Crouch", false) + private val arrowKeys by setting("Arrow Keys", false, "Allows rotating the players camera using the arrow keys") + private val speed by setting("Rotation Speed", 5, 1..20, 1, unit = "°/tick") { arrowKeys } + override val rotationConfig = RotationConfig.Instant(RotationMode.Lock) - @JvmStatic - val shouldMove get() = isEnabled && !mc.currentScreen.hasInputOrNull + @JvmStatic + val shouldMove get() = isEnabled && !mc.currentScreen.hasInputOrNull - /** - * Whether the current screen has text inputs or is null - */ - @JvmStatic - val Screen?.hasInputOrNull: Boolean - get() = this is ChatScreen || - this is AbstractSignEditScreen || - this is AnvilScreen || - this is AbstractCommandBlockScreen || - (this is LambdaScreen && !clickGui) || - this is BookEditScreen || - this == null + /** + * Whether the current screen has text inputs or is null + */ + @JvmStatic + val Screen?.hasInputOrNull: Boolean + get() = this is ChatScreen || + this is AbstractSignEditScreen || + this is AnvilScreen || + this is AbstractCommandBlockScreen || + (this is LambdaScreen && !clickGui) || + this is BookEditScreen || + this == null - init { - listen { - if (!arrowKeys || mc.currentScreen.hasInputOrNull) return@listen + init { + listen { + if (!arrowKeys || mc.currentScreen.hasInputOrNull) return@listen - val pitch = (isKeyPressed(GLFW_KEY_DOWN, GLFW_KEY_KP_2).toFloatSign() - - isKeyPressed(GLFW_KEY_UP, GLFW_KEY_KP_8).toFloatSign()) * speed - val yaw = (isKeyPressed(GLFW_KEY_RIGHT, GLFW_KEY_KP_6).toFloatSign() - - isKeyPressed(GLFW_KEY_LEFT, GLFW_KEY_KP_4).toFloatSign()) * speed + val pitch = (isKeyPressed(GLFW_KEY_DOWN, GLFW_KEY_KP_2).toFloatSign() - + isKeyPressed(GLFW_KEY_UP, GLFW_KEY_KP_8).toFloatSign()) * speed + val yaw = (isKeyPressed(GLFW_KEY_RIGHT, GLFW_KEY_KP_6).toFloatSign() - + isKeyPressed(GLFW_KEY_LEFT, GLFW_KEY_KP_4).toFloatSign()) * speed - submit(RotationRequest(Rotation(player.yaw + yaw, (player.pitch + pitch).coerceIn(-90f, 90f)), this@InventoryMove)) - } - } + rotationRequest { rotation(player.yaw + yaw, (player.pitch + pitch).coerceIn(-90f, 90f)) }.submit() + } + } - @JvmStatic - fun isKeyMovementRelated(key: Int): Boolean { - val options = mc.options - return when (key) { - options.forwardKey.boundKey.code, - options.backKey.boundKey.code, - options.leftKey.boundKey.code, - options.rightKey.boundKey.code, - options.jumpKey.boundKey.code, - options.sprintKey.boundKey.code -> true - options.sneakKey.boundKey.code if (!disableSneak) -> true - else -> false - } - } + @JvmStatic + fun isKeyMovementRelated(key: Int): Boolean { + val options = mc.options + return when (key) { + options.forwardKey.boundKey.code, + options.backKey.boundKey.code, + options.leftKey.boundKey.code, + options.rightKey.boundKey.code, + options.jumpKey.boundKey.code, + options.sprintKey.boundKey.code -> true + options.sneakKey.boundKey.code if (!disableSneak) -> true + else -> false + } + } } diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt index 107a925ad..f0c3868da 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt @@ -27,8 +27,8 @@ import com.lambda.module.tag.ModuleTag import com.lambda.task.RootTask.run import com.lambda.task.Task import com.lambda.task.tasks.BuildTask.Companion.breakAndCollectBlock -import com.lambda.task.tasks.OpenContainer -import com.lambda.task.tasks.PlaceContainer +import com.lambda.task.tasks.OpenContainerTask +import com.lambda.task.tasks.PlaceContainerTask import com.lambda.util.item.ItemUtils.shulkerBoxes import net.minecraft.item.Items import net.minecraft.screen.ScreenHandler @@ -55,13 +55,13 @@ object InventoryTweaks : Module( listen { if (it.action != SlotActionType.PICKUP || it.button != 1) return@listen - val stack = it.screenHandler.getSlot(it.slot).stack - if (!(instantShulker && stack.item in shulkerBoxes) && !(instantEChest && stack.item == Items.ENDER_CHEST)) return@listen + val slot = it.screenHandler.getSlot(it.slot) + if (!(instantShulker && slot.stack.item in shulkerBoxes) && !(instantEChest && slot.stack.item == Items.ENDER_CHEST)) return@listen it.cancel() lastOpenScreen = null - placeAndOpen = PlaceContainer(stack, this@InventoryTweaks).then { placePos -> + placeAndOpen = PlaceContainerTask(slot, this@InventoryTweaks).then { placePos -> placedPos = placePos - OpenContainer(placePos, this@InventoryTweaks).finally { screenHandler -> + OpenContainerTask(placePos, this@InventoryTweaks).finally { screenHandler -> lastOpenScreen = screenHandler } }.run() diff --git a/src/main/kotlin/com/lambda/module/modules/player/MapDownloader.kt b/src/main/kotlin/com/lambda/module/modules/player/MapDownloader.kt index b9ec11e5c..96005d38a 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/MapDownloader.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/MapDownloader.kt @@ -24,7 +24,7 @@ import com.lambda.module.tag.ModuleTag import com.lambda.util.FileUtils.locationBoundDirectory import com.lambda.util.FolderRegister import com.lambda.util.StringUtils.hashString -import com.lambda.util.player.SlotUtils.combined +import com.lambda.util.player.SlotUtils.allStacks import com.lambda.util.world.entitySearch import net.minecraft.block.MapColor import net.minecraft.entity.decoration.ItemFrameEntity @@ -42,7 +42,7 @@ object MapDownloader : Module( listen { val mapStates = entitySearch(128.0) .mapNotNull { FilledMapItem.getMapState(it.heldItemStack, world) } + - player.combined.mapNotNull { FilledMapItem.getMapState(it, world) } + player.allStacks.mapNotNull { FilledMapItem.getMapState(it, world) } mapStates.forEach { map -> val name = map.hash diff --git a/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt b/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt index 4591c084a..c0c041a53 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt @@ -32,62 +32,58 @@ import net.minecraft.block.Blocks import net.minecraft.util.math.BlockPos object Nuker : Module( - name = "Nuker", - description = "Breaks blocks around you", - tag = ModuleTag.PLAYER, + name = "Nuker", + description = "Breaks blocks around you", + tag = ModuleTag.PLAYER, ) { - private val height by setting("Height", 6, 1..8, 1) - private val width by setting("Width", 6, 1..8, 1) - private val flatten by setting("Flatten", true) - private val onGround by setting("On Ground", false, "Only break blocks when the player is standing on ground") - private val fillFluids by setting("Fill Fluids", false, "Removes liquids by filling them in before breaking") - private val fillFloor by setting("Fill Floor", false) - private val baritoneSelection by setting("Baritone Selection", false, "Restricts nuker to your baritone selection") + private val height by setting("Height", 6, 1..8, 1) + private val width by setting("Width", 6, 1..8, 1) + private val flatten by setting("Flatten", true) + private val onGround by setting("On Ground", false, "Only break blocks when the player is standing on ground") + private val fillFluids by setting("Fill Fluids", false, "Removes liquids by filling them in before breaking") + private val fillFloor by setting("Fill Floor", false) + private val baritoneSelection by setting("Baritone Selection", false, "Restricts nuker to your baritone selection") - private var task: Task<*>? = null + private var task: Task<*>? = null - init { - setDefaultAutomationConfig { - applyEdits { - inventoryConfig::immediateAccessOnly.edit { defaultValue(true) } - } - } + init { + setDefaultAutomationConfig() - onEnable { - task = tickingBlueprint { - if (onGround && !player.isOnGround) return@tickingBlueprint emptyMap() + onEnable { + task = tickingBlueprint { + if (onGround && !player.isOnGround) return@tickingBlueprint emptyMap() - val selection = BlockPos.iterateOutwards(player.blockPos, width, height, width) - .asSequence() - .map { it.blockPos } - .filter { !world.isAir(it) } - .filter { !flatten || it.y >= player.blockPos.y } - .filter { pos -> - if (!baritoneSelection) true - else BaritoneManager.primary.selectionManager.selections.any { - val min = it.min() - val max = it.max() - pos.x >= min.x && pos.x <= max.x - && pos.y >= min.y && pos.y <= max.y - && pos.z >= min.z && pos.z <= max.z - } - } - .associateWith { if (fillFluids) TargetState.Air else TargetState.Empty } + val selection = BlockPos.iterateOutwards(player.blockPos, width, height, width) + .asSequence() + .map { it.blockPos } + .filter { !world.isAir(it) } + .filter { !flatten || it.y >= player.blockPos.y } + .filter { pos -> + if (!baritoneSelection) true + else BaritoneManager.primary?.selectionManager?.selections?.any { + val min = it.min() + val max = it.max() + pos.x >= min.x && pos.x <= max.x + && pos.y >= min.y && pos.y <= max.y + && pos.z >= min.z && pos.z <= max.z + } ?: false + } + .associateWith { if (fillFluids) TargetState.Air else TargetState.Empty } - if (fillFloor) { - val floor = BlockPos.iterateOutwards(player.blockPos.down(), width, 0, width) - .map { it.blockPos } - .associateWith { TargetState.Solid(setOf(Blocks.MAGMA_BLOCK)) } - return@tickingBlueprint selection + floor - } + if (fillFloor) { + val floor = BlockPos.iterateOutwards(player.blockPos.down(), width, 0, width) + .map { it.blockPos } + .associateWith { TargetState.Solid(setOf(Blocks.MAGMA_BLOCK)) } + return@tickingBlueprint selection + floor + } - selection - }.build(finishOnDone = false) + selection + }.build(finishOnDone = false) .run() - } + } - onDisable { - task?.cancel() - } - } + onDisable { + task?.cancel() + } + } } diff --git a/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt b/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt index 0cece4a75..bc16a1d11 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt @@ -46,71 +46,73 @@ import java.awt.Color import java.util.concurrent.ConcurrentLinkedQueue object PacketMine : Module( - name = "PacketMine", - description = "automatically breaks blocks, and does it faster", - tag = ModuleTag.PLAYER + name = "PacketMine", + description = "automatically breaks blocks, and does it faster", + tag = ModuleTag.PLAYER ) { - private enum class Group(override val displayName: String) : NamedEnum { - General("General"), - Renders("Renders") - } + private enum class Group(override val displayName: String) : NamedEnum { + General("General"), + Renders("Renders") + } - private val rebreakMode by setting("Rebreak Mode", RebreakMode.Manual, "The method used to re-break blocks after they've been broken once").disabled { !breakConfig.rebreak }.group(Group.General) - private val breakRadius by setting("Break Radius", 0, 0..5, 1, "Selects and breaks all blocks within the break radius of the selected block").group(Group.General) - private val flatten by setting("Flatten", true, "Wont allow breaking extra blocks under your players position") { breakRadius > 0 }.group(Group.General) - private val queue by setting("Queue", false, "Queues blocks to break so you can select multiple at once").group(Group.General) - .onValueChange { _, to -> if (!to) queuePositions.clear() } - private val queueOrder by setting("Queue Order", QueueOrder.Standard, "Which end of the queue to break blocks from") { queue }.group(Group.General) + private val rebreakMode by setting("Rebreak Mode", RebreakMode.Manual, "The method used to re-break blocks after they've been broken once").disabled { !breakConfig.rebreak }.group(Group.General) + private val breakRadius by setting("Break Radius", 0, 0..5, 1, "Selects and breaks all blocks within the break radius of the selected block").group(Group.General) + private val flatten by setting("Flatten", true, "Wont allow breaking extra blocks under your players position") { breakRadius > 0 }.group(Group.General) + private val queue by setting("Queue", false, "Queues blocks to break so you can select multiple at once").group(Group.General) + .onValueChange { _, to -> if (!to) queuePositions.clear() } + private val queueOrder by setting("Queue Order", QueueOrder.Standard, "Which end of the queue to break blocks from") { queue }.group(Group.General) - private val renderRebreak by setting("Render Rebreak", true, "Displays what block is being checked for rebreak").group(Group.Renders) - private val rebreakColor by setting("Rebreak Color", Color.RED) { renderRebreak }.group(Group.Renders) - private val renderQueue by setting("Render Queue", true, "Adds renders to signify what block positions are queued").group(Group.Renders) - private val renderSize by setting("Render Size", 0.3f, 0.01f..1f, 0.01f, "The scale of the queue renders") { renderQueue }.group(Group.Renders) - private val renderMode by setting("Render Mode", RenderMode.State, "The style of the queue renders") { renderQueue }.group(Group.Renders) - private val dynamicColor by setting("Dynamic Color", true, "Interpolates the color between start and end") { renderQueue }.group(Group.Renders) - private val staticColor by setting("Color", Color(255, 0, 0, 60)) { renderQueue && !dynamicColor }.group(Group.Renders) - private val startColor by setting("Start Color", Color(255, 255, 0, 60), "The color of the start (closest to breaking) of the queue") { renderQueue && dynamicColor }.group(Group.Renders) - private val endColor by setting("End Color", Color(255, 0, 0, 60), "The color of the end (farthest from breaking) of the queue") { renderQueue && dynamicColor }.group(Group.Renders) + private val renderRebreak by setting("Render Rebreak", true, "Displays what block is being checked for rebreak").group(Group.Renders) + private val rebreakColor by setting("Rebreak Color", Color.RED) { renderRebreak }.group(Group.Renders) + private val renderQueue by setting("Render Queue", true, "Adds renders to signify what block positions are queued").group(Group.Renders) + private val renderSize by setting("Render Size", 0.3f, 0.01f..1f, 0.01f, "The scale of the queue renders") { renderQueue }.group(Group.Renders) + private val renderMode by setting("Render Mode", RenderMode.State, "The style of the queue renders") { renderQueue }.group(Group.Renders) + private val dynamicColor by setting("Dynamic Color", true, "Interpolates the color between start and end") { renderQueue }.group(Group.Renders) + private val staticColor by setting("Color", Color(255, 0, 0, 60)) { renderQueue && !dynamicColor }.group(Group.Renders) + private val startColor by setting("Start Color", Color(255, 255, 0, 60), "The color of the start (closest to breaking) of the queue") { renderQueue && dynamicColor }.group(Group.Renders) + private val endColor by setting("End Color", Color(255, 0, 0, 60), "The color of the end (farthest from breaking) of the queue") { renderQueue && dynamicColor }.group(Group.Renders) - private val pendingActions = ConcurrentLinkedQueue() + private val pendingActions = ConcurrentLinkedQueue() - private var breaks = 0 - private var itemDrops = 0 + private var breaks = 0 + private var itemDrops = 0 - private val breakPositions = arrayOfNulls(2) - private val queuePositions = ArrayList>() - private val SafeContext.queueSorted - get() = when (queueOrder) { - QueueOrder.Standard, - QueueOrder.Efficient -> queuePositions - QueueOrder.Reversed -> queuePositions.asReversed() - QueueOrder.Closest -> queuePositions.sortedBy { - it.firstOrNull() - ?.toCenterPos() - ?.let { center -> - center distSq player.pos - } ?: Double.MAX_VALUE - } - } + private val breakPositions = arrayOfNulls(2) + private val queuePositions = ArrayList>() + private val SafeContext.queueSorted + get() = when (queueOrder) { + QueueOrder.Standard, + QueueOrder.Efficient -> queuePositions + QueueOrder.Reversed -> queuePositions.asReversed() + QueueOrder.Closest -> queuePositions.sortedBy { + it.firstOrNull() + ?.toCenterPos() + ?.let { center -> + center distSq player.pos + } ?: Double.MAX_VALUE + } + } - private var rebreakPos: BlockPos? = null - private var attackedThisTick = false + private var rebreakPos: BlockPos? = null + private var attackedThisTick = false - init { + init { setDefaultAutomationConfig { applyEdits { hideAllGroupsExcept(buildConfig, breakConfig, rotationConfig, hotbarConfig) - buildConfig.apply { - hide( - ::pathing, - ::stayInRange, - ::spleefEntities, - ::maxBuildDependencies, - ::collectDrops, - ::entityReach - ) - ::maxBuildDependencies.edit { defaultValue(0) } - } + buildConfig.apply { + hide( + ::pathing, + ::stayInRange, + ::spleefEntities, + ::maxBuildDependencies, + ::collectDrops, + ::entityReach, + ::breakBlocks, + ::interactBlocks + ) + ::maxBuildDependencies.edit { defaultValue(0) } + } breakConfig.apply { editTyped( ::avoidLiquids, @@ -120,193 +122,199 @@ object PacketMine : Module( ) { defaultValue(false) } ::swing.edit { defaultValue(BreakConfig.SwingMode.Start) } } - hotbarConfig::keepTicks.edit { defaultValue(0) } + hotbarConfig::keepTicks.edit { defaultValue(0) } } } - listen { - attackedThisTick = false - } + listen { + attackedThisTick = false + } - listen { it.cancel() } - listen { event -> - event.cancel() - val pos = event.pos - val positions = mutableListOf().apply { - if (breakRadius <= 0) { - add(pos) - return@apply - } - BlockPos.iterateOutwards(pos, breakRadius, breakRadius, breakRadius).forEach { blockPos -> - if (blockPos distSq pos <= (breakRadius * breakRadius) && (!flatten || (blockPos.y >= player.blockPos.y || blockPos == pos))) { - add(blockPos.toImmutable()) - } - } - } - positions.removeIf { breakPos -> - (queue && queuePositions.any { it == breakPos }) || breakPos == breakPositions[1] - } - if (positions.isEmpty()) return@listen - val activeBreaking = if (queue) { - queuePositions.add(positions) - breakPositions.toList() + queueSorted.flatten() - } else { - queuePositions.clear() - queuePositions.add(positions) - queuePositions.flatten() + if (breakConfig.doubleBreak) { - breakPositions[1] ?: breakPositions[0] - } else null - } - requestBreakManager(activeBreaking) - attackedThisTick = true - queuePositions.trimToSize() - } + listen { it.cancel() } + listen { event -> + event.cancel() + val pos = event.pos + val positions = mutableListOf().apply { + if (breakRadius <= 0) { + add(pos) + return@apply + } + BlockPos.iterateOutwards(pos, breakRadius, breakRadius, breakRadius).forEach { blockPos -> + if (blockPos distSq pos <= (breakRadius * breakRadius) && (!flatten || (blockPos.y >= player.blockPos.y || blockPos == pos))) { + add(blockPos.toImmutable()) + } + } + } + positions.removeIf { breakPos -> + (queue && queuePositions.any { it == breakPos }) || breakPos == breakPositions[1] + } + if (positions.isEmpty()) return@listen + val activeBreaking = if (queue) { + queuePositions.add(positions) + breakPositions.toList() + queueSorted.flatten() + } else { + queuePositions.clear() + queuePositions.add(positions) + queuePositions.flatten() + if (breakConfig.doubleBreak) { + breakPositions[1] ?: breakPositions[0] + } else null + } + requestBreakManager(activeBreaking) + attackedThisTick = true + queuePositions.trimToSize() + } - listen { - if (!attackedThisTick) { - requestBreakManager((breakPositions + queueSorted.flatten()).toList()) - if (!breakConfig.rebreak || (rebreakMode != RebreakMode.Auto /*&& rebreakMode != RebreakMode.AutoConstant*/)) return@listen - val reBreak = rebreakPos ?: return@listen - requestBreakManager(listOf(reBreak), true) - } - } + listen { + if (!attackedThisTick) { + requestBreakManager((breakPositions + queueSorted.flatten()).toList()) + if (!breakConfig.rebreak || (rebreakMode != RebreakMode.Auto /*&& rebreakMode != RebreakMode.AutoConstant*/)) return@listen + val reBreak = rebreakPos ?: return@listen + requestBreakManager(listOf(reBreak), true) + } + } - onStaticRender { event -> - if (renderRebreak) { - rebreakPos?.let { event.outline(it, rebreakColor) } - } - if (!renderQueue) return@onStaticRender - queueSorted.forEachIndexed { index, positions -> - positions.forEach { pos -> - val color = if (dynamicColor) lerp(index / queuePositions.size.toDouble(), startColor, endColor) - else staticColor - val boxes = when (renderMode) { - RenderMode.State -> blockState(pos).getOutlineShape(world, pos).boundingBoxes - RenderMode.Box -> listOf(Box(0.0, 0.0, 0.0, 1.0, 1.0, 1.0)) - }.map { lerp(renderSize.toDouble(), Box(it.center, it.center), it).offset(pos) } + onStaticRender { esp -> + if (renderRebreak) { + rebreakPos?.let { pos -> + esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) { + outline(pos, rebreakColor) + } + } + } + if (!renderQueue) return@onStaticRender + queueSorted.forEachIndexed { index, positions -> + positions.forEach { pos -> + val color = if (dynamicColor) lerp(index / queuePositions.size.toDouble(), startColor, endColor) + else staticColor + val boxes = when (renderMode) { + RenderMode.State -> blockState(pos).getOutlineShape(world, pos).boundingBoxes + RenderMode.Box -> listOf(Box(0.0, 0.0, 0.0, 1.0, 1.0, 1.0)) + }.map { lerp(renderSize.toDouble(), Box(it.center, it.center), it).offset(pos) } - boxes.forEach { box -> - event.box(box, color, color.setAlpha(1.0)) - } - } - } - } + esp.shapes(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) { + boxes.forEach { box -> + box(box, color, color.setAlpha(1.0)) + } + } + } + } + } - onDisable { - breakPositions[0] = null - breakPositions[1] = null - queuePositions.clear() - rebreakPos = null - attackedThisTick = false - } - } + onDisable { + breakPositions[0] = null + breakPositions[1] = null + queuePositions.clear() + rebreakPos = null + attackedThisTick = false + } + } - private fun SafeContext.requestBreakManager(requestPositions: Collection, reBreaking: Boolean = false) { - if (requestPositions.count { it != null } <= 0) return - val breakContexts = runSafeAutomated { - requestPositions - .filterNotNull() - .associateWith { TargetState.Empty } - .simulate() - .filterIsInstance() - .let { - if (queueOrder == QueueOrder.Efficient) it.sorted() - else it.sortedBy { ctx -> requestPositions.indexOf(ctx.pos) } - } - .map { it.context } + private fun SafeContext.requestBreakManager(requestPositions: Collection, reBreaking: Boolean = false) { + if (requestPositions.count { it != null } <= 0) return + val breakContexts = runSafeAutomated { + requestPositions + .filterNotNull() + .associateWith { TargetState.Empty } + .simulate() + .filterIsInstance() + .let { + if (queueOrder == QueueOrder.Efficient) it.sorted() + else it.sortedBy { ctx -> requestPositions.indexOf(ctx.pos) } + } + .map { it.context } - } - if (!reBreaking) { - queuePositions.retainAllPositions(breakContexts) - } - breakRequest(breakContexts, pendingActions) { - onStart { onProgress(it) } - onUpdate { onProgress(it) } - onStop { removeBreak(it); breaks++ } - onCancel { removeBreak(it, true) } - onReBreakStart { rebreakPos = it } - onReBreak { removeBreak(it); rebreakPos = it } - }.submit() - } + } + if (!reBreaking) { + queuePositions.retainAllPositions(breakContexts) + } + breakRequest(breakContexts, pendingActions) { + onStart { onProgress(it) } + onUpdate { onProgress(it) } + onStop { removeBreak(it); breaks++ } + onCancel { removeBreak(it, true) } + onReBreakStart { rebreakPos = it } + onReBreak { removeBreak(it); rebreakPos = it } + }.submit() + } - private fun onProgress(blockPos: BlockPos) { - queuePositions.removePos(blockPos) - if (breakPositions.none { pos -> pos == blockPos }) { - addBreak(blockPos) - } - } + private fun onProgress(blockPos: BlockPos) { + queuePositions.removePos(blockPos) + if (breakPositions.none { pos -> pos == blockPos }) { + addBreak(blockPos) + } + } - private fun addBreak(pos: BlockPos) { - if (breakConfig.doubleBreak && breakPositions[0] != null) { - breakPositions[1] = breakPositions[0] - } - breakPositions[0] = pos - rebreakPos = null - } + private fun addBreak(pos: BlockPos) { + if (breakConfig.doubleBreak && breakPositions[0] != null) { + breakPositions[1] = breakPositions[0] + } + breakPositions[0] = pos + rebreakPos = null + } - private fun removeBreak(pos: BlockPos, includeReBreak: Boolean = false) { - breakPositions.forEachIndexed { index, breakPos -> - if (breakPos == pos) { - breakPositions[index] = null - } - } - if (includeReBreak && pos == rebreakPos) { - rebreakPos = null - } - } + private fun removeBreak(pos: BlockPos, includeReBreak: Boolean = false) { + breakPositions.forEachIndexed { index, breakPos -> + if (breakPos == pos) { + breakPositions[index] = null + } + } + if (includeReBreak && pos == rebreakPos) { + rebreakPos = null + } + } - private fun ArrayList>.removePos(element: BlockPos): Boolean { - var anyRemoved = false - removeIf { - anyRemoved = anyRemoved or it.remove(element) - return@removeIf it.isEmpty() - } - return anyRemoved - } + private fun ArrayList>.removePos(element: BlockPos): Boolean { + var anyRemoved = false + removeIf { + anyRemoved = anyRemoved or it.remove(element) + return@removeIf it.isEmpty() + } + return anyRemoved + } - private fun ArrayList>.retainAllPositions(positions: Collection): Boolean { - var modified = false - forEach { - modified = modified or it.retainAll { pos -> - positions.any { retain -> - retain.blockPos == pos - } - } - } - return modified - } + private fun ArrayList>.retainAllPositions(positions: Collection): Boolean { + var modified = false + forEach { + modified = modified or it.retainAll { pos -> + positions.any { retain -> + retain.blockPos == pos + } + } + } + return modified + } - private fun ArrayList>.any(predicate: (BlockPos) -> Boolean): Boolean { - if (isEmpty()) return false - forEach { if (it.any(predicate)) return true } - return false - } + private fun ArrayList>.any(predicate: (BlockPos) -> Boolean): Boolean { + if (isEmpty()) return false + forEach { if (it.any(predicate)) return true } + return false + } - enum class RebreakMode( - override val displayName: String, - override val description: String - ) : NamedEnum, Describable { - Manual("Manual", "Re-break only when you trigger it explicitly."), - Auto("Auto", "Automatically re-break when it’s beneficial or required."), - //ToDo: Implement auto constant rebreak + enum class RebreakMode( + override val displayName: String, + override val description: String + ) : NamedEnum, Describable { + Manual("Manual", "Re-break only when you trigger it explicitly."), + Auto("Auto", "Automatically re-break when it’s beneficial or required."), + //ToDo: Implement auto constant rebreak // AutoConstant("Auto (Constant)", "Continuously re-break as soon as conditions allow; most aggressive.") - } + } - enum class QueueOrder( - override val displayName: String, - override val description: String - ) : NamedEnum, Describable { - Standard("Standard", "Process in planned order (first in, first out)."), - Reversed("Reversed", "Process in reverse planned order (last in, first out)."), - Closest("Closest", "Process the closest targets first."), - Efficient("Efficient", "Process the most efficient targets first.") - } + enum class QueueOrder( + override val displayName: String, + override val description: String + ) : NamedEnum, Describable { + Standard("Standard", "Process in planned order (first in, first out)."), + Reversed("Reversed", "Process in reverse planned order (last in, first out)."), + Closest("Closest", "Process the closest targets first."), + Efficient("Efficient", "Process the most efficient targets first.") + } - private enum class RenderMode( - override val displayName: String, - override val description: String - ) : NamedEnum, Describable { - State("State", "Render the actual block state for preview."), - Box("Box", "Render a simple box to show position and size.") - } + private enum class RenderMode( + override val displayName: String, + override val description: String + ) : NamedEnum, Describable { + State("State", "Render the actual block state for preview."), + Box("Box", "Render a simple box to show position and size.") + } } diff --git a/src/main/kotlin/com/lambda/module/modules/player/Printer.kt b/src/main/kotlin/com/lambda/module/modules/player/Printer.kt index fe3e57480..e27744933 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Printer.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Printer.kt @@ -34,47 +34,46 @@ import fi.dy.masa.litematica.world.SchematicWorldHandler import net.minecraft.util.math.BlockPos object Printer : Module( - name = "Printer", - description = "Automatically prints schematics", - tag = ModuleTag.PLAYER + name = "Printer", + description = "Automatically prints schematics", + tag = ModuleTag.PLAYER ) { - private fun isLitematicaAvailable(): Boolean = runCatching { - Class.forName("fi.dy.masa.litematica.Litematica") - true - }.getOrDefault(false) + private fun isLitematicaAvailable(): Boolean = runCatching { + Class.forName("fi.dy.masa.litematica.Litematica") + true + }.getOrDefault(false) - private val range by setting("Range", 5, 1..7, 1) - private val air by setting("Air", false) + private val range by setting("Range", 5, 1..7, 1) + private val air by setting("Air", false) - private var buildTask: Task<*>? = null + private var buildTask: Task<*>? = null - init { + init { setDefaultAutomationConfig { applyEdits { editTyped(buildConfig::pathing, buildConfig::stayInRange) { defaultValue(false) } editTyped(breakConfig::efficientOnly, breakConfig::suitableToolsOnly) { defaultValue(false) } interactConfig::airPlace.edit { defaultValue(InteractConfig.AirPlaceMode.Grim) } - inventoryConfig::immediateAccessOnly.edit { defaultValue(true) } } } - onEnable { - if (!isLitematicaAvailable()) { - logError("Litematica is not installed!") - disable() - return@onEnable - } - buildTask = TickingBlueprint { - val schematicWorld = SchematicWorldHandler.getSchematicWorld() ?: return@TickingBlueprint emptyMap() - BlockPos.iterateOutwards(player.blockPos, range, range, range) - .map { it.blockPos } - .asSequence() - .filter { DataManager.getRenderLayerRange().isPositionWithinRange(it) } - .associateWith { TargetState.State(schematicWorld.getBlockState(it)) } - .filter { air || !it.value.blockState.isAir } - }.build(finishOnDone = false).run() - } + onEnable { + if (!isLitematicaAvailable()) { + logError("Litematica is not installed!") + disable() + return@onEnable + } + buildTask = TickingBlueprint { + val schematicWorld = SchematicWorldHandler.getSchematicWorld() ?: return@TickingBlueprint emptyMap() + BlockPos.iterateOutwards(player.blockPos, range, range, range) + .map { it.blockPos } + .asSequence() + .filter { DataManager.getRenderLayerRange().isPositionWithinRange(it) } + .associateWith { TargetState.State(schematicWorld.getBlockState(it)) } + .filter { air || !it.value.blockState.isAir } + }.build(finishOnDone = false).run() + } - onDisable { buildTask?.cancel(); buildTask = null } - } + onDisable { buildTask?.cancel(); buildTask = null } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/module/modules/player/Replay.kt b/src/main/kotlin/com/lambda/module/modules/player/Replay.kt index 803780a27..e6345aa1b 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Replay.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Replay.kt @@ -26,19 +26,18 @@ import com.google.gson.JsonNull import com.google.gson.JsonSerializationContext import com.google.gson.JsonSerializer import com.lambda.brigadier.CommandResult +import com.lambda.config.settings.complex.KeybindSetting.Companion.onPress import com.lambda.context.SafeContext import com.lambda.core.TimerManager import com.lambda.event.EventFlow.lambdaScope -import com.lambda.event.events.KeyboardEvent import com.lambda.event.events.MovementEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.gui.components.ClickGuiLayout -import com.lambda.interaction.managers.Request.Companion.submit +import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest import com.lambda.interaction.managers.rotating.Rotation import com.lambda.interaction.managers.rotating.RotationConfig import com.lambda.interaction.managers.rotating.RotationMode -import com.lambda.interaction.managers.rotating.RotationRequest import com.lambda.module.Module import com.lambda.module.modules.player.Replay.InputAction.Companion.toAction import com.lambda.module.tag.ModuleTag @@ -89,9 +88,13 @@ object Replay : Module( autoDisable = true ) { private val record by setting("Record", KeyCode.R) + .onPress { handleRecord() } private val play by setting("Play / Stop", KeyCode.C) + .onPress { handlePlay() } private val cycle by setting("Cycle Play Mode", KeyCode.B, description = "REPLAY: Replay the recording once. CONTINUE: Replay the recording and continue recording. LOOP: Loop the recording.") + .onPress { handlePlayModeCycle() } private val check by setting("Set Checkpoint", KeyCode.V, description = "Create a checkpoint while recording.") + .onPress { handleCheckpoint() } private val loops by setting("Loops", -1, -1..10, 1, description = "Number of times to loop the replay. -1 for infinite.", unit = " repeats") private val velocityCheck by setting("Velocity check", true, description = "Check if the player is moving before starting a recording.") @@ -131,19 +134,6 @@ object Replay : Module( .create() init { - listen { - if (!it.isPressed) return@listen - if (mc.currentScreen != null && !mc.options.commandKey.isPressed) return@listen - - when (it.bind) { - record -> handleRecord() - play -> handlePlay() - cycle -> handlePlayModeCycle() - check -> handleCheckpoint() - else -> {} - } - } - listen { event -> when (state) { State.Recording -> { @@ -186,7 +176,7 @@ object Replay : Module( State.Playing -> { buffer?.rotation?.removeFirstOrNull()?.let { rot -> - submit(RotationRequest(rot, this@Replay)) + rotationRequest { rotation(rot) }.submit() } } diff --git a/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt b/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt index ef697e089..bc6967292 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/RotationLock.kt @@ -21,63 +21,67 @@ import com.lambda.config.applyEdits import com.lambda.config.groups.RotationSettings import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.Rotation +import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest import com.lambda.interaction.managers.rotating.RotationMode -import com.lambda.interaction.managers.rotating.RotationRequest import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.NamedEnum import kotlin.math.roundToInt object RotationLock : Module( - name = "RotationLock", - description = "Locks the player rotation to the given configuration", - tag = ModuleTag.PLAYER, + name = "RotationLock", + description = "Locks the player rotation to the given configuration", + tag = ModuleTag.PLAYER, ) { - private enum class Group(override val displayName: String) : NamedEnum { - General("General"), - Rotation("Rotation") - } + private enum class Group(override val displayName: String) : NamedEnum { + General("General"), + Rotation("Rotation") + } - @JvmStatic val yawMode by setting("Yaw Mode", Mode.Snap).group(Group.General) - private val yawStep by setting("Yaw Step", 45.0, 1.0..180.0, 1.0) { yawMode == Mode.Snap }.group(Group.General) - private val customYaw by setting("Custom Yaw", 0.0, -179.0..180.0, 1.0) { yawMode == Mode.Custom }.group(Group.General) - @JvmStatic val pitchMode by setting("Pitch Mode", Mode.None).group(Group.General) - private val pitchStep by setting("Pitch Step", 45.0, 1.0..90.0, 1.0) { pitchMode == Mode.Snap }.group(Group.General) - private val customPitch by setting("Custom Pitch", 0.0, -90.0..90.0, 1.0) { pitchMode == Mode.Custom }.group(Group.General) + @JvmStatic val yawMode by setting("Yaw Mode", Mode.Snap).group(Group.General) + private val yawStep by setting("Yaw Step", 45.0, 1.0..180.0, 1.0) { yawMode == Mode.Snap }.group(Group.General) + private val customYaw by setting("Custom Yaw", 0.0, -179.0..180.0, 1.0) { yawMode == Mode.Custom }.group(Group.General) + @JvmStatic val pitchMode by setting("Pitch Mode", Mode.None).group(Group.General) + private val pitchStep by setting("Pitch Step", 45.0, 1.0..90.0, 1.0) { pitchMode == Mode.Snap }.group(Group.General) + private val customPitch by setting("Custom Pitch", 0.0, -90.0..90.0, 1.0) { pitchMode == Mode.Custom }.group(Group.General) - override val rotationConfig = RotationSettings(this, Group.Rotation).apply { - applyEdits { - ::rotationMode.edit { defaultValue(RotationMode.Lock) } - } - } + override val rotationConfig = RotationSettings(this, Group.Rotation).apply { + applyEdits { + ::rotationMode.edit { defaultValue(RotationMode.Lock) } + } + } - init { - listen { - val yaw = when (yawMode) { - Mode.Custom -> customYaw - Mode.Snap -> { - val normalizedYaw = (player.yaw % 360.0 + 360.0) % 360.0 - (normalizedYaw / yawStep).roundToInt() * yawStep - } - Mode.None -> player.yaw.toDouble() - } - val pitch = when (pitchMode) { - Mode.Custom -> customPitch - Mode.Snap -> { - val clampedPitch = player.pitch.coerceIn(-90f, 90f) - (clampedPitch / pitchStep).roundToInt() * pitchStep - } - Mode.None -> player.pitch.toDouble() - } + init { + listen { + val yaw = when (yawMode) { + Mode.Custom -> customYaw + Mode.Snap -> { + val normalizedYaw = (player.yaw % 360.0 + 360.0) % 360.0 + (normalizedYaw / yawStep).roundToInt() * yawStep + } + Mode.None -> null + } + val pitch = when (pitchMode) { + Mode.Custom -> customPitch + Mode.Snap -> { + val clampedPitch = player.pitch.coerceIn(-90f, 90f) + (clampedPitch / pitchStep).roundToInt() * pitchStep + } + Mode.None -> null + } - RotationRequest(Rotation(yaw, pitch), this@RotationLock).submit() - } - } + if (yaw == null && pitch == null) return@listen - enum class Mode { - Snap, - Custom, - None - } + rotationRequest { + yaw?.let { yaw(it) } + pitch?.let { pitch(it) } + }.submit() + } + } + + enum class Mode { + Snap, + Custom, + None + } } diff --git a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt index c5c1c4ea7..67e6951eb 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt @@ -32,24 +32,24 @@ import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafeAutomated import com.lambda.util.BlockUtils.blockPos import com.lambda.util.BlockUtils.blockState -import com.lambda.util.InputUtils.isKeyPressed +import com.lambda.util.InputUtils.isSatisfied import com.lambda.util.KeyCode import net.minecraft.util.math.BlockPos import java.util.concurrent.ConcurrentLinkedQueue object Scaffold : Module( - name = "Scaffold", - description = "Places blocks under the player", - tag = ModuleTag.PLAYER, + name = "Scaffold", + description = "Places blocks under the player", + tag = ModuleTag.PLAYER, ) { - private val bridgeRange by setting("Bridge Range", 5, 0..5, 1, "The range at which blocks can be placed to help build support for the player", unit = " blocks") - private val onlyBelow by setting("Only Below", true, "Restricts bridging to only below the player to avoid place spam if it's impossible to reach the supporting position") { bridgeRange > 0 } - private val descend by setting("Descend", KeyCode.Unbound, "Lower the place position by one to allow the player to lower y level") - private val descendAmount by setting("Descend Amount", 1, 1..5, 1, "The amount to lower the place position by when descending", unit = " blocks") { descend != Bind.EMPTY } + private val bridgeRange by setting("Bridge Range", 5, 0..5, 1, "The range at which blocks can be placed to help build support for the player", unit = " blocks") + private val onlyBelow by setting("Only Below", true, "Restricts bridging to only below the player to avoid place spam if it's impossible to reach the supporting position") { bridgeRange > 0 } + private val descend by setting("Descend", KeyCode.Unbound, "Lower the place position by one to allow the player to lower y level") + private val descendAmount by setting("Descend Amount", 1, 1..5, 1, "The amount to lower the place position by when descending", unit = " blocks") { descend != Bind.EMPTY } - private val pendingActions = ConcurrentLinkedQueue() + private val pendingActions = ConcurrentLinkedQueue() - init { + init { setDefaultAutomationConfig { applyEdits { buildConfig.apply { @@ -70,7 +70,6 @@ object Scaffold : Module( ::swapWithDisposables, ::providerPriority, ::storePriority, - ::immediateAccessOnly, ::accessShulkerBoxes, ::accessEnderChest, ::accessChests, @@ -81,31 +80,31 @@ object Scaffold : Module( } } - listen { - val playerSupport = player.blockPos.down() - val alreadySupported = blockState(playerSupport).hasSolidTopSurface(world, playerSupport, player) - if (alreadySupported) return@listen - val offset = if (isKeyPressed(descend.key)) descendAmount else 0 - val beneath = playerSupport.down(offset) - runSafeAutomated { - scaffoldPositions(beneath) - .associateWith { TargetState.Solid(emptySet()) } - .simulate() - .interactRequest(pendingActions) - ?.submit() - } - } - } + listen { + val playerSupport = player.blockPos.down() + val alreadySupported = blockState(playerSupport).hasSolidTopSurface(world, playerSupport, player) + if (alreadySupported) return@listen + val offset = if (descend.isSatisfied()) descendAmount else 0 + val beneath = playerSupport.down(offset) + runSafeAutomated { + scaffoldPositions(beneath) + .associateWith { TargetState.Solid(emptySet()) } + .simulate() + .interactRequest(pendingActions) + ?.submit() + } + } + } - private fun SafeContext.scaffoldPositions(beneath: BlockPos): List { - if (!blockState(beneath).isReplaceable) return emptyList() - if (interactConfig.airPlace.isEnabled) return listOf(beneath) + private fun SafeContext.scaffoldPositions(beneath: BlockPos): List { + if (!blockState(beneath).isReplaceable) return emptyList() + if (interactConfig.airPlace.isEnabled) return listOf(beneath) - return BlockPos.iterateOutwards(beneath, bridgeRange, bridgeRange, bridgeRange) - .asSequence() - .filter { !onlyBelow || it.y <= beneath.y } - .filter { blockState(it).isReplaceable } - .map { it.blockPos } - .toList() - } + return BlockPos.iterateOutwards(beneath, bridgeRange, bridgeRange, bridgeRange) + .asSequence() + .filter { !onlyBelow || it.y <= beneath.y } + .filter { blockState(it).isReplaceable } + .map { it.blockPos } + .toList() + } } diff --git a/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt b/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt new file mode 100644 index 000000000..393ee2558 --- /dev/null +++ b/src/main/kotlin/com/lambda/module/modules/player/StackReplenish.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.module.modules.player + +import com.lambda.config.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.applyEdits +import com.lambda.context.SafeContext +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest +import com.lambda.module.Module +import com.lambda.module.tag.ModuleTag +import com.lambda.util.item.ItemStackUtils.slotId +import com.lambda.util.player.SlotUtils.hotbarStacks +import com.lambda.util.player.SlotUtils.inventoryStacks +import net.minecraft.item.ItemStack +import net.minecraft.item.Items + +object StackReplenish : Module( + name = "StackReplenish", + description = "Automatically refills stacks from your inventory", + tag = ModuleTag.PLAYER +) { + private val minStackPercent by setting("Min Stack Percentage", 30, 0..100, 1, "Minimum percentage of a complete stack before refilling", "%") + private val offhand by setting("Offhand", false, "Replenishes the players offhand stack") + + init { + setDefaultAutomationConfig { + applyEdits { + hideAllGroupsExcept(inventoryConfig) + inventoryConfig.apply { + hide(::disposables, ::swapWithDisposables, ::providerPriority, ::storePriority) + } + } + } + + listen { + if (player.currentScreenHandler.cursorStack.item !== Items.AIR) return@listen + player.hotbarStacks.forEach { stack -> checkReplenish(stack) } + if (offhand) checkReplenish(player.offHandStack) + } + } + + private fun SafeContext.checkReplenish(stack: ItemStack) { + if (stack.count.toFloat() / stack.maxCount >= (minStackPercent.toFloat() / 100)) return + if (!stack.isStackable) return + + player.inventoryStacks.forEach { invStack -> + if (!ItemStack.areItemsAndComponentsEqual(invStack, stack)) return@forEach + val invId = invStack.slotId + val completing = stack.count + invStack.count >= stack.maxCount + val tooMany = invStack.count + stack.count > stack.maxCount + inventoryRequest { + moveSlot(invId, stack.slotId) + if (tooMany) pickup(invId) + }.submit() + if (completing) return + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt b/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt new file mode 100644 index 000000000..54eae94ea --- /dev/null +++ b/src/main/kotlin/com/lambda/module/modules/player/ToolSaver.kt @@ -0,0 +1,103 @@ +/* + * Copyright 2026 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.module.modules.player + +import com.lambda.config.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.applyEdits +import com.lambda.event.events.ContainerEvent +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest +import com.lambda.interaction.material.container.containers.HotbarContainer +import com.lambda.module.Module +import com.lambda.module.tag.ModuleTag +import com.lambda.util.EnchantmentUtils.forEachEnchantment +import com.lambda.util.EnchantmentUtils.getEnchantment +import com.lambda.util.player.SlotUtils.hotbarSlots +import com.lambda.util.player.SlotUtils.inventorySlots +import net.minecraft.item.ItemStack +import net.minecraft.screen.slot.Slot + +object ToolSaver : Module( + name = "ToolSaver", + description = "Moves tools from your hotbar into your inventory when they get too damaged", + ModuleTag.PLAYER +) { + private val minDurabilityPercentage by setting("Min Durability", 5, 0..100, 1, "Minimum durability percentage before being swapped for a new piece", "%") + private val replace by setting("Replace", true, "Replaces the tool with the one of the same kind") + + init { + setDefaultAutomationConfig { + applyEdits { + hideAllGroupsExcept(inventoryConfig) + } + } + + listen { + val endangeredStacks = player.hotbarSlots.filter { it.stack.isEndangered } + + val inventorySlots = player.inventorySlots + val swaps = endangeredStacks + .mapNotNull { endangered -> + val sorter = compareByDescending { swapSlot -> + if (!replace) 0 + else swapSlot.stack.item == endangered.stack.item + }.thenByDescending { swapSlot -> + if (!replace) return@thenByDescending 0 + var matchingEnchantments = 0 + endangered.stack.forEachEnchantment { enchantment, level -> + val swapEnchantmentLevel = swapSlot.stack.getEnchantment(enchantment) + if (swapEnchantmentLevel != 0 && swapEnchantmentLevel == level) matchingEnchantments++ + } + matchingEnchantments + }.thenByDescending { + it.stack.isEmpty + }.thenByDescending { + it.stack.item in inventoryConfig.disposables + }.thenByDescending { + it.stack.isStackable + } + val swapWith = inventorySlots + .filter { !it.stack.isEndangered } + .sortedWith(sorter) + .firstOrNull() + ?: return@mapNotNull null + endangered to swapWith + } + + if (swaps.isEmpty()) return@listen + + inventoryRequest { + swaps.forEach { + pickup(it.first.id) + pickup(it.second.id) + if (!it.second.stack.isEmpty) + pickup(it.first.id) + } + }.submit() + } + + listen { event -> + if (event.to is HotbarContainer && event.fromSlot.stack.isEndangered) event.cancel() + else if (event.from is HotbarContainer && event.toSlot.stack.isEndangered) event.cancel() + } + } + + private val ItemStack.isEndangered get() = + isDamageable && 1 - (damage.toFloat() / maxDamage) < minDurabilityPercentage.toFloat() / 100 +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/module/modules/player/WorldEater.kt b/src/main/kotlin/com/lambda/module/modules/player/WorldEater.kt index 3d45ed445..c97f8242a 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/WorldEater.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/WorldEater.kt @@ -65,7 +65,11 @@ object WorldEater : Module( BaritoneManager.cancel() } - onStaticRender { it.outline(Box.enclosing(pos1, pos2), Color.BLUE) } + onStaticRender { esp -> + esp.shapes(pos1.x.toDouble(), pos1.y.toDouble(), pos1.z.toDouble()) { + outline(Box.enclosing(pos1, pos2), Color.BLUE) + } + } } private fun buildLayer() { diff --git a/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt b/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt index 282c6fcd1..84a031bdc 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt @@ -21,20 +21,18 @@ import com.lambda.Lambda.mc import com.lambda.config.settings.collections.CollectionSetting.Companion.onDeselect import com.lambda.config.settings.collections.CollectionSetting.Companion.onSelect import com.lambda.context.SafeContext -import com.lambda.graphics.renderer.esp.ChunkedESP.Companion.newChunkedESP +import com.lambda.graphics.esp.chunkedEsp import com.lambda.graphics.renderer.esp.DirectionMask import com.lambda.graphics.renderer.esp.DirectionMask.buildSideMesh -import com.lambda.graphics.renderer.esp.ShapeBuilder import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafe import com.lambda.util.extension.blockColor import com.lambda.util.extension.getBlockState import com.lambda.util.world.toBlockPos -import net.minecraft.block.BlockState import net.minecraft.block.Blocks import net.minecraft.client.render.model.BlockStateModel -import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Box import java.awt.Color object BlockESP : Module( @@ -52,8 +50,11 @@ object BlockESP : Module( private val mesh by setting("Mesh", true, "Connect similar adjacent blocks") { searchBlocks }.onValueChange(::rebuildMesh) private val useBlockColor by setting("Use Block Color", false, "Use the color of the block instead") { searchBlocks }.onValueChange(::rebuildMesh) + private val blockColorAlpha by setting("Block Color Alpha", 0.3, 0.1..1.0, 0.05) { searchBlocks && useBlockColor }.onValueChange { _, _ -> ::rebuildMesh } + private val faceColor by setting("Face Color", Color(100, 150, 255, 51), "Color of the surfaces") { searchBlocks && drawFaces && !useBlockColor }.onValueChange(::rebuildMesh) private val outlineColor by setting("Outline Color", Color(100, 150, 255, 128), "Color of the outlines") { searchBlocks && drawOutlines && !useBlockColor }.onValueChange(::rebuildMesh) + private val outlineWidth by setting("Outline Width", 1.0f, 0.5f..5.0f, 0.5f) { searchBlocks && drawOutlines }.onValueChange(::rebuildMesh) private val outlineMode by setting("Outline Mode", DirectionMask.OutlineMode.And, "Outline mode") { searchBlocks }.onValueChange(::rebuildMesh) @@ -67,9 +68,9 @@ object BlockESP : Module( @JvmStatic val model: BlockStateModel get() = mc.bakedModelManager.missingModel - private val esp = newChunkedESP { world, position -> + private val esp = chunkedEsp("BlockESP") { world, position -> val state = world.getBlockState(position) - if (state.block !in blocks) return@newChunkedESP + if (state.block !in blocks) return@chunkedEsp val sides = if (mesh) { buildSideMesh(position) { @@ -77,18 +78,25 @@ object BlockESP : Module( } } else DirectionMask.ALL - build(state, position.toBlockPos(), sides) + runSafe { + // TODO: Add custom color option when map options are implemented + val extractedColor = blockColor(state, position.toBlockPos()) + val finalColor = Color(extractedColor.red, extractedColor.green, extractedColor.blue, (blockColorAlpha * 255).toInt()) + val pos = position.toBlockPos() + val shape = state.getOutlineShape(world, pos) + val worldBox = if (shape.isEmpty) Box(pos) else shape.boundingBox.offset(pos) + box(worldBox) { + if (drawFaces) + filled(if (useBlockColor) finalColor else faceColor, sides) + if (drawOutlines) + outline(if (useBlockColor) extractedColor else BlockESP.outlineColor, sides, BlockESP.outlineMode, thickness = outlineWidth) + } + } } - private fun ShapeBuilder.build( - state: BlockState, - pos: BlockPos, - sides: Int, - ) = runSafe { - val blockColor = blockColor(state, pos) - - if (drawFaces) filled(pos, state, if (useBlockColor) blockColor else faceColor, sides) - if (drawOutlines) outline(pos, state, if (useBlockColor) blockColor else outlineColor, sides, outlineMode) + init { + onEnable { esp.rebuildAll() } + onDisable { esp.close() } } private fun rebuildMesh(ctx: SafeContext, from: Any?, to: Any?): Unit = esp.rebuild() diff --git a/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt b/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt index 22c8ee793..03a30fd93 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/ContainerPreview.kt @@ -29,7 +29,7 @@ import net.minecraft.block.ShulkerBoxBlock import net.minecraft.client.font.TextRenderer import net.minecraft.client.gui.DrawContext import net.minecraft.client.gui.tooltip.TooltipComponent -import net.minecraft.client.render.RenderLayer +import net.minecraft.client.gl.RenderPipelines import net.minecraft.item.BlockItem import net.minecraft.item.ItemStack import net.minecraft.item.Items @@ -152,8 +152,7 @@ object ContainerPreview : Module( val height = getTooltipHeight() val matrices = context.matrices - matrices.push() - matrices.translate(0f, 0f, 400f) + matrices.pushMatrix() val tintColor = getContainerTintColor(stack) @@ -201,11 +200,10 @@ object ContainerPreview : Module( } } - matrices.pop() + matrices.popMatrix() hoveredStack?.let { stack -> - matrices.push() - matrices.translate(0f, 0f, 500f) + matrices.pushMatrix() if (isPreviewableContainer(stack)) { val nestedWidth = getTooltipWidth() @@ -221,7 +219,7 @@ object ContainerPreview : Module( isRenderingSubTooltip = false } } - matrices.pop() + matrices.popMatrix() } } @@ -274,11 +272,12 @@ object ContainerPreview : Module( // Bottom: y=160 onwards context.drawTexture( - RenderLayer::getGuiTextured, + RenderPipelines.GUI_TEXTURED, background, x, y, 0f, 0f, width, TITLE_HEIGHT, + width, TITLE_HEIGHT, 256, 256, tintColor ) @@ -286,11 +285,12 @@ object ContainerPreview : Module( // Middle rows (0 until ROWS).forEach { row -> context.drawTexture( - RenderLayer::getGuiTextured, + RenderPipelines.GUI_TEXTURED, background, x, y + TITLE_HEIGHT + row * SLOT_SIZE, 0f, 17f, width, SLOT_SIZE, + width, SLOT_SIZE, 256, 256, tintColor ) @@ -298,11 +298,12 @@ object ContainerPreview : Module( // Bottom context.drawTexture( - RenderLayer::getGuiTextured, + RenderPipelines.GUI_TEXTURED, background, x, y + TITLE_HEIGHT + ROWS * SLOT_SIZE, 0f, 160f, width, PADDING, + width, PADDING, 256, 256, tintColor ) diff --git a/src/main/kotlin/com/lambda/module/modules/render/EntityESP.kt b/src/main/kotlin/com/lambda/module/modules/render/EntityESP.kt new file mode 100644 index 000000000..506a98a2f --- /dev/null +++ b/src/main/kotlin/com/lambda/module/modules/render/EntityESP.kt @@ -0,0 +1,347 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.module.modules.render + +import com.lambda.Lambda.mc +import com.lambda.context.SafeContext +import com.lambda.event.events.GuiEvent +import com.lambda.event.events.RenderEvent +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.graphics.RenderMain +import com.lambda.graphics.mc.InterpolatedRegionESP +import com.lambda.graphics.mc.TransientRegionESP +import com.lambda.module.Module +import com.lambda.module.tag.ModuleTag +import com.lambda.util.NamedEnum +import com.lambda.util.extension.tickDelta +import com.lambda.util.math.setAlpha +import com.lambda.util.world.entitySearch +import imgui.ImGui +import net.minecraft.entity.Entity +import net.minecraft.entity.ItemEntity +import net.minecraft.entity.LivingEntity +import net.minecraft.entity.decoration.ArmorStandEntity +import net.minecraft.entity.decoration.EndCrystalEntity +import net.minecraft.entity.mob.HostileEntity +import net.minecraft.entity.mob.MobEntity +import net.minecraft.entity.passive.AnimalEntity +import net.minecraft.entity.passive.PassiveEntity +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.entity.projectile.ProjectileEntity +import net.minecraft.entity.vehicle.AbstractMinecartEntity +import net.minecraft.entity.vehicle.BoatEntity +import net.minecraft.util.math.Vec3d +import java.awt.Color + +object EntityESP : Module( + name = "EntityESP", + description = "Highlight entities with smooth interpolated rendering", + tag = ModuleTag.RENDER +) { + private val esp = InterpolatedRegionESP("EntityESP") + + private data class LabelData( + val screenX: Float, + val screenY: Float, + val text: String, + val color: Color, + val scale: Float + ) + + private val pendingLabels = mutableListOf() + + private val range by setting("Range", 64.0, 8.0..256.0, 1.0, "Maximum render distance").group(Group.General) + private val throughWalls by setting("Through Walls", true, "Render through blocks").group(Group.General) + private val self by setting("Self", false, "Render own player in third person").group(Group.General) + + private val players by setting("Players", true, "Highlight players").group(Group.Entities) + private val hostiles by setting("Hostiles", true, "Highlight hostile mobs").group(Group.Entities) + private val passives by setting("Passives", false, "Highlight passive mobs (animals)").group(Group.Entities) + private val neutrals by setting("Neutrals", false, "Highlight neutral mobs").group(Group.Entities) + private val items by setting("Items", false, "Highlight dropped items").group(Group.Entities) + private val projectiles by setting("Projectiles", false, "Highlight projectiles").group(Group.Entities) + private val vehicles by setting("Vehicles", false, "Highlight boats and minecarts").group(Group.Entities) + private val crystals by setting("Crystals", true, "Highlight end crystals").group(Group.Entities) + private val armorStands by setting("Armor Stands", false, "Highlight armor stands").group(Group.Entities) + + private val drawBoxes by setting("Boxes", true, "Draw entity boxes").group(Group.Render) + private val drawFilled by setting("Filled", true, "Fill entity boxes") { drawBoxes }.group(Group.Render) + private val drawOutline by setting("Outline", true, "Draw box outlines") { drawBoxes }.group(Group.Render) + private val filledAlpha by setting("Filled Alpha", 0.2, 0.0..1.0, 0.05) { drawBoxes && drawFilled }.group(Group.Render) + private val outlineAlpha by setting("Outline Alpha", 0.8, 0.0..1.0, 0.05) { drawBoxes && drawOutline }.group(Group.Render) + private val outlineWidth by setting("Outline Width", 1.0f, 0.5f..5.0f, 0.5f) { drawBoxes && drawOutline }.group(Group.Render) + + private val tracers by setting("Tracers", true, "Draw lines to entities").group(Group.Tracers) + private val tracerOrigin by setting("Tracer Origin", TracerOrigin.Eyes, "Where tracers start from") { tracers }.group(Group.Tracers) + private val tracerWidth by setting("Tracer Width", 1.5f, 0.5f..5f, 0.5f) { tracers }.group(Group.Tracers) + private val dashedTracers by setting("Dashed Tracers", false, "Use dashed lines for tracers") { tracers }.group(Group.Tracers) + private val dashLength by setting("Dash Length", 1.0, 0.25..2.0, 0.25) { tracers && dashedTracers }.group(Group.Tracers) + private val gapLength by setting("Gap Length", 0.5, 0.1..1.0, 0.1) { tracers && dashedTracers }.group(Group.Tracers) + + private val nameTags by setting("Name Tags", false, "Show entity name tags").group(Group.NameTags) + private val nameTagDistance by setting("Show Distance", true, "Show distance in name tags") { nameTags }.group(Group.NameTags) + private val nameTagHealth by setting("Show Health", true, "Show health in name tags") { nameTags }.group(Group.NameTags) + private val nameTagBackground by setting("Name Background", true, "Draw background behind name tags") { nameTags }.group(Group.NameTags) + + private val playerColor by setting("Player Color", Color(255, 50, 50), "Color for players").group(Group.Colors) + private val hostileColor by setting("Hostile Color", Color(255, 100, 0), "Color for hostile mobs").group(Group.Colors) + private val passiveColor by setting("Passive Color", Color(50, 255, 50), "Color for passive mobs").group(Group.Colors) + private val neutralColor by setting("Neutral Color", Color(255, 255, 50), "Color for neutral mobs").group(Group.Colors) + private val itemColor by setting("Item Color", Color(100, 100, 255), "Color for items").group(Group.Colors) + private val projectileColor by setting("Projectile Color", Color(200, 200, 200), "Color for projectiles").group(Group.Colors) + private val vehicleColor by setting("Vehicle Color", Color(150, 100, 50), "Color for vehicles").group(Group.Colors) + private val crystalColor by setting("Crystal Color", Color(255, 0, 255), "Color for end crystals").group(Group.Colors) + private val otherColor by setting("Other Color", Color(200, 200, 200), "Color for other entities").group(Group.Colors) + + init { + listen { + esp.tick() + + entitySearch(range) { shouldRender(it) }.forEach { entity -> + val color = getEntityColor(entity) + val box = entity.boundingBox + + esp.shapes(entity.x, entity.y, entity.z) { + if (drawBoxes) { + box(box, entity.id) { + if (drawFilled) + filled(color.setAlpha(filledAlpha)) + if (drawOutline) + outline( + color.setAlpha(outlineAlpha), + thickness = outlineWidth + ) + } + } + } + } + + esp.upload() + } + + listen { + val tickDelta = mc.tickDelta + esp.render(tickDelta) + + // Clear pending labels from previous frame + pendingLabels.clear() + + if (tracers || nameTags) { + val tracerEsp = TransientRegionESP( + "EntityESP-Tracers", + depthTest = !throughWalls + ) + entitySearch(range) { shouldRender(it) }.forEach { entity -> + val color = getEntityColor(entity) + val entityPos = getInterpolatedPos(entity, tickDelta) + + if (tracers) { + val startPos = getTracerStartPos(tickDelta) + val endPos = entityPos.add(0.0, entity.height / 2.0, 0.0) + + tracerEsp.shapes(entity.x, entity.y, entity.z) { + tracer(startPos, endPos, entity.id) { + color(color.setAlpha(outlineAlpha)) + width(tracerWidth) + if (dashedTracers) dashed(dashLength, gapLength) + } + } + } + + if (nameTags) { + val namePos = entityPos.add(0.0, entity.height + 0.3, 0.0) + // Project to screen coords NOW while matrices are + // valid + val screen = RenderMain.worldToScreen(namePos) + if (screen != null) { + val nameText = buildNameTag(entity) + // Calculate distance-based scale (closer = + // larger) + val distance = player.pos.distanceTo(namePos).toFloat() + val scale = (1.0f / (distance * 0.1f + 1f)).coerceIn(0.5f, 2.0f) + pendingLabels.add( + LabelData( + screen.x, + screen.y, + nameText, + color, + scale + ) + ) + } + } + } + + tracerEsp.upload() + tracerEsp.render() + tracerEsp.close() + } + } + + // Draw ImGUI labels using pre-computed screen coordinates + listen { + val drawList = ImGui.getBackgroundDrawList() + val font = ImGui.getFont() + + pendingLabels.forEach { label -> + val fontSize = (font.fontSize * label.scale).toInt() + val textSize = imgui.ImVec2() + ImGui.calcTextSize(textSize, label.text) + + // Scale text size based on our custom scale + val tw = textSize.x * label.scale + val th = textSize.y * label.scale + + // Center text horizontally + val x = label.screenX - tw / 2f + val y = label.screenY + + // Color conversion (ABGR for ImGui) + val textColor = + (label.color.alpha shl 24) or + (label.color.blue shl 16) or + (label.color.green shl 8) or + label.color.red + val shadowColor = (200 shl 24) or 0 // Black with alpha + + if (nameTagBackground) { + val bgColor = (160 shl 24) or 0 // Black with 160 alpha + val padX = 4f * label.scale + val padY = 2f * label.scale + drawList.addRectFilled( + x - padX, + y - padY, + x + tw + padX, + y + th + padY, + bgColor, + 3f * label.scale + ) + } else { + // Shadow + drawList.addText( + font, + fontSize, + imgui.ImVec2( + x + 1f * label.scale, + y + 1f * label.scale + ), + shadowColor, + label.text + ) + } + drawList.addText( + font, + fontSize, + imgui.ImVec2(x, y), + textColor, + label.text + ) + } + } + + onDisable { esp.close() } + } + + private fun SafeContext.shouldRender(entity: Entity): Boolean { + if (entity == player && !self) return false + if (entity is LivingEntity && !entity.isAlive) return false + return when (entity) { + is PlayerEntity -> players + is HostileEntity -> hostiles + is AnimalEntity -> passives + is PassiveEntity -> passives + is MobEntity -> neutrals + is ItemEntity -> items + is ProjectileEntity -> projectiles + is BoatEntity -> vehicles + is AbstractMinecartEntity -> vehicles + is EndCrystalEntity -> crystals + is ArmorStandEntity -> armorStands + else -> false + } + } + + private fun getEntityColor(entity: Entity): Color { + return when (entity) { + is PlayerEntity -> playerColor + is HostileEntity -> hostileColor + is AnimalEntity -> passiveColor + is PassiveEntity -> passiveColor + is MobEntity -> neutralColor + is ItemEntity -> itemColor + is ProjectileEntity -> projectileColor + is BoatEntity -> vehicleColor + is AbstractMinecartEntity -> vehicleColor + is EndCrystalEntity -> crystalColor + else -> otherColor + } + } + + private fun getInterpolatedPos(entity: Entity, tickDelta: Float): Vec3d { + val x = entity.lastRenderX + (entity.x - entity.lastRenderX) * tickDelta + val y = entity.lastRenderY + (entity.y - entity.lastRenderY) * tickDelta + val z = entity.lastRenderZ + (entity.z - entity.lastRenderZ) * tickDelta + return Vec3d(x, y, z) + } + + private fun SafeContext.getTracerStartPos(tickDelta: Float): Vec3d { + val playerPos = getInterpolatedPos(player, tickDelta) + + return when (tracerOrigin) { + TracerOrigin.Feet -> playerPos + TracerOrigin.Center -> playerPos.add(0.0, player.height / 2.0, 0.0) + TracerOrigin.Eyes -> + playerPos.add(0.0, player.standingEyeHeight.toDouble(), 0.0) + TracerOrigin.Crosshair -> { + val camera = mc.gameRenderer?.camera ?: return playerPos + camera.pos.add(Vec3d(camera.horizontalPlane).multiply(0.1)) + } + } + } + + private fun SafeContext.buildNameTag(entity: Entity): String { + val builder = StringBuilder() + val name = entity.displayName?.string ?: entity.type.name.string + builder.append(name) + if (nameTagHealth && entity is LivingEntity) { + builder.append(" [${entity.health.toInt()}/${entity.maxHealth.toInt()}]") + } + if (nameTagDistance) { + val dist = player.distanceTo(entity).toInt() + builder.append(" ${dist}m") + } + return builder.toString() + } + + enum class TracerOrigin(override val displayName: String) : NamedEnum { + Feet("Feet"), + Center("Center"), + Eyes("Eyes"), + Crosshair("Crosshair") + } + + private enum class Group(override val displayName: String) : NamedEnum { + General("General"), + Entities("Entities"), + Render("Render"), + Tracers("Tracers"), + NameTags("Name Tags"), + Colors("Colors") + } +} diff --git a/src/main/kotlin/com/lambda/module/modules/render/ExtraTab.kt b/src/main/kotlin/com/lambda/module/modules/render/ExtraTab.kt index 5b555df83..fbd5b960f 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/ExtraTab.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/ExtraTab.kt @@ -19,15 +19,17 @@ package com.lambda.module.modules.render import com.lambda.module.Module import com.lambda.module.tag.ModuleTag +import java.awt.Color object ExtraTab : Module( - name = "ExtraTab", - description = "Adds more tabs to the main menu", - tag = ModuleTag.RENDER, + name = "ExtraTab", + description = "Adds more tabs to the main menu", + tag = ModuleTag.RENDER, ) { - @JvmStatic - val tabEntries by setting("Tab Entries", 80L, 1L..500L, 1L) - - @JvmStatic - val rows by setting("Rows", 20, 1..100, 1) + @JvmStatic val tabEntries by setting("Tab Entries", 80L, 1L..500L, 1L) + @JvmStatic val rows by setting("Rows", 20, 1..100, 1) + @JvmStatic val friendsOnly by setting("Friends Only", false) + @JvmStatic val sortFriendsFirst by setting("Sort Friends First", true) + @JvmStatic val highlightFriends by setting("Highlight Friends", true) + @JvmStatic val friendColor by setting("Friend Color", Color(120, 120, 255, 255), "The color friends will be highlighted") { highlightFriends } } diff --git a/src/main/kotlin/com/lambda/module/modules/render/FreeLook.kt b/src/main/kotlin/com/lambda/module/modules/render/FreeLook.kt index 36cd3d07c..6570a7e0c 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/FreeLook.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/FreeLook.kt @@ -21,6 +21,7 @@ import com.lambda.Lambda.mc import com.lambda.event.events.PlayerEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.managers.rotating.Rotation +import com.lambda.interaction.managers.rotating.RotationManager import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.extension.rotation @@ -32,8 +33,8 @@ object FreeLook : Module( description = "Allows you to look around freely while moving", tag = ModuleTag.PLAYER, ) { - val enableYaw by setting("Enable Yaw", false, "Don't effect pitch if enabled") - val enablePitch by setting("Enable Pitch", false, "Don't effect yaw if enabled") + @JvmStatic val enableYaw by setting("Enable Yaw", false, "Don't effect pitch if enabled") + @JvmStatic val enablePitch by setting("Enable Pitch", false, "Don't effect yaw if enabled") val togglePerspective by setting("Toggle Perspective", true, "Toggle perspective when enabling FreeLook") var camera: Rotation = Rotation.ZERO @@ -73,8 +74,8 @@ object FreeLook : Module( it.deltaPitch * SENSITIVITY_FACTOR ) - if (enablePitch) player.pitch = camera.pitchF - if (enableYaw) player.yaw = camera.yawF + if (enableYaw) RotationManager.setPlayerYaw(camera.yaw) + if (enablePitch) RotationManager.setPlayerPitch(camera.pitch) it.cancel() } diff --git a/src/main/kotlin/com/lambda/module/modules/render/MapPreview.kt b/src/main/kotlin/com/lambda/module/modules/render/MapPreview.kt index cb17e9d65..118fc2258 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/MapPreview.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/MapPreview.kt @@ -21,10 +21,10 @@ import com.lambda.Lambda.mc import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import net.minecraft.client.font.TextRenderer +import net.minecraft.client.gl.RenderPipelines import net.minecraft.client.gui.DrawContext import net.minecraft.client.gui.tooltip.TooltipComponent import net.minecraft.client.render.MapRenderState -import net.minecraft.client.render.RenderLayer import net.minecraft.component.DataComponentTypes import net.minecraft.component.type.MapIdComponent import net.minecraft.item.FilledMapItem @@ -38,6 +38,8 @@ object MapPreview : Module( description = "Preview maps in your inventory", tag = ModuleTag.RENDER, ) { + @JvmStatic val showInSlot by setting("Show In Slot", true, "Shows the map in the slot rather than the basic map icon") + private val background = Identifier.ofVanilla("textures/map/map_background.png") class MapComponent(val stack: ItemStack) : TooltipData, TooltipComponent { @@ -56,19 +58,19 @@ object MapPreview : Module( val matrices = context.matrices // Render the map background - matrices.push() - context.drawTexture(RenderLayer::getGuiTextured, background, x, y, 0f, 0f, 64, 64, 64, 64) - matrices.pop() + matrices.pushMatrix() + context.drawTexture(RenderPipelines.GUI_TEXTURED, background, x, y, 0f, 0f, 64, 64, 64, 64, 64, 64) + matrices.popMatrix() // Render the map texture - matrices.push() - matrices.translate(x + 3.2, y + 3.2, 401.0) - matrices.scale(0.45f, 0.45f, 1f) + matrices.pushMatrix() + matrices.translate(x + 3.2f, y + 3.2f) + matrices.scale(0.45f, 0.45f) val renderState = MapRenderState() mc.mapRenderer.update(id, state, renderState) - context.draw { mc.mapRenderer.draw(renderState, matrices, it, true, 0xF000F0) } - matrices.pop() + context.drawMap(renderState) + matrices.popMatrix() } override fun getHeight(textRenderer: TextRenderer) = diff --git a/src/main/kotlin/com/lambda/module/modules/render/NoRender.kt b/src/main/kotlin/com/lambda/module/modules/render/NoRender.kt index e496a768b..09b287385 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/NoRender.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/NoRender.kt @@ -25,168 +25,162 @@ import com.lambda.util.reflections.scanResult import io.github.classgraph.ClassInfo import net.minecraft.block.entity.BlockEntity import net.minecraft.client.particle.Particle -import net.minecraft.client.render.BackgroundRenderer.StatusEffectFogModifier import net.minecraft.entity.Entity import net.minecraft.entity.SpawnGroup -import net.minecraft.entity.effect.StatusEffects //ToDo: Implement unimplemented settings. (Keep in mind compatibility with other mods like sodium) object NoRender : Module( - name = "NoRender", - description = "Disables rendering of certain things", - tag = ModuleTag.RENDER, + name = "NoRender", + description = "Disables rendering of certain things", + tag = ModuleTag.RENDER, ) { - private val entities = scanResult - .getSubclasses(Entity::class.java) - .filter { !it.isAbstract && it.name.startsWith("net.minecraft") } - - private val particleMap = createParticleNameMap() - private val blockEntityMap = createBlockEntityNameMap() - private val playerEntityMap = createEntityNameMap("net.minecraft.client.network.") - private val bossEntityMap = createEntityNameMap("net.minecraft.entity.boss.") - private val decorationEntityMap = createEntityNameMap("net.minecraft.entity.decoration.") - private val mobEntityMap = createEntityNameMap("net.minecraft.entity.mob.") - private val passiveEntityMap = createEntityNameMap("net.minecraft.entity.passive.") - private val projectileEntityMap = createEntityNameMap("net.minecraft.entity.projectile.") - private val vehicleEntityMap = createEntityNameMap("net.minecraft.entity.vehicle.") - private val miscEntityMap = createEntityNameMap("net.minecraft.entity.", strictDir = true) - - private enum class Group(override val displayName: String) : NamedEnum { - Hud("Hud"), - Entity("Entity"), - World("World"), - Effect("Effect") - } - - @JvmStatic val noBlindness by setting("No Blindness", true).group(Group.Effect) - @JvmStatic val noDarkness by setting("No Darkness", true).group(Group.Effect) - @JvmStatic val noNausea by setting("No Nausea", true).group(Group.Effect) - - @JvmStatic val noFireOverlay by setting("No Fire Overlay", false).group(Group.Hud) - @JvmStatic val fireOverlayYOffset by setting("Fire Overlay Y Offset", 0.0, -0.4..0.4, 0.02) { !noFireOverlay }.group(Group.Hud) - @JvmStatic val noPortalOverlay by setting("No Portal Overlay", true).group(Group.Hud) - @JvmStatic val noFluidOverlay by setting("No Fluid Overlay", true).group(Group.Hud) - @JvmStatic val noPowderedSnowOverlay by setting("No Powdered Snow Overlay", true).group(Group.Hud) - @JvmStatic val noInWall by setting("No In Wall Overlay", true).group(Group.Hud) - @JvmStatic val noPumpkinOverlay by setting("No Pumpkin Overlay", true).group(Group.Hud) - @JvmStatic val noVignette by setting("No Vignette", true).group(Group.Hud) - @JvmStatic val noChatVerificationToast by setting("No Chat Verification Toast", true).group(Group.Hud) - @JvmStatic val noSpyglassOverlay by setting("No Spyglass Overlay", false).group(Group.Hud) - @JvmStatic val noGuiShadow by setting("No Gui Shadow", false).group(Group.Hud) - @JvmStatic val noFloatingItemAnimation by setting("No Floating Item Animation", false, "Disables floating item animations, typically used when a totem pops").group(Group.Hud) - @JvmStatic val noCrosshair by setting("No Crosshair", false).group(Group.Hud) - @JvmStatic val noBossBar by setting("No Boss Bar", false).group(Group.Hud) - @JvmStatic val noScoreBoard by setting("No Score Board", false).group(Group.Hud) - @JvmStatic val noStatusEffects by setting("No Status Effects", false).group(Group.Hud) - - @JvmStatic val noArmor by setting("No Armor", false).group(Group.Entity) - @JvmStatic val includeNoOtherHeadItems by setting("Include No Other Head Items", false) { noArmor }.group(Group.Entity) - @JvmStatic val noElytra by setting("No Elytra", false).group(Group.Entity) - @JvmStatic val noInvisibility by setting("No Invisibility", true).group(Group.Entity) - @JvmStatic val noGlow by setting("No Glow", false).group(Group.Entity) - @JvmStatic val noNametags by setting("No Nametags", false).group(Group.Entity) + private val entities = scanResult + .getSubclasses(Entity::class.java) + .filter { !it.isAbstract && it.name.startsWith("net.minecraft") } + + private val particleMap = createParticleNameMap() + private val blockEntityMap = createBlockEntityNameMap() + private val playerEntityMap = createEntityNameMap("net.minecraft.client.network.") + private val bossEntityMap = createEntityNameMap("net.minecraft.entity.boss.") + private val decorationEntityMap = createEntityNameMap("net.minecraft.entity.decoration.") + private val mobEntityMap = createEntityNameMap("net.minecraft.entity.mob.") + private val passiveEntityMap = createEntityNameMap("net.minecraft.entity.passive.") + private val projectileEntityMap = createEntityNameMap("net.minecraft.entity.projectile.") + private val vehicleEntityMap = createEntityNameMap("net.minecraft.entity.vehicle.") + private val miscEntityMap = createEntityNameMap("net.minecraft.entity.", strictDir = true) + + private enum class Group(override val displayName: String) : NamedEnum { + Hud("Hud"), + Entity("Entity"), + World("World"), + Effect("Effect") + } + + @JvmStatic val noBlindness by setting("No Blindness", true).group(Group.Effect) + @JvmStatic val noDarkness by setting("No Darkness", true).group(Group.Effect) + @JvmStatic val noNausea by setting("No Nausea", true).group(Group.Effect) + + @JvmStatic val noFireOverlay by setting("No Fire Overlay", false).group(Group.Hud) + @JvmStatic val fireOverlayYOffset by setting("Fire Overlay Y Offset", 0.0, -0.4..0.4, 0.02) { !noFireOverlay }.group(Group.Hud) + @JvmStatic val noPortalOverlay by setting("No Portal Overlay", true).group(Group.Hud) + @JvmStatic val noFluidOverlay by setting("No Fluid Overlay", true).group(Group.Hud) + @JvmStatic val noPowderedSnowOverlay by setting("No Powdered Snow Overlay", true).group(Group.Hud) + @JvmStatic val noInWall by setting("No In Wall Overlay", true).group(Group.Hud) + @JvmStatic val noPumpkinOverlay by setting("No Pumpkin Overlay", true).group(Group.Hud) + @JvmStatic val noVignette by setting("No Vignette", true).group(Group.Hud) + @JvmStatic val noChatVerificationToast by setting("No Chat Verification Toast", true).group(Group.Hud) + @JvmStatic val noSpyglassOverlay by setting("No Spyglass Overlay", false).group(Group.Hud) + @JvmStatic val noGuiShadow by setting("No Gui Shadow", false).group(Group.Hud) + @JvmStatic val noFloatingItemAnimation by setting("No Floating Item Animation", false, "Disables floating item animations, typically used when a totem pops").group(Group.Hud) + @JvmStatic val noCrosshair by setting("No Crosshair", false).group(Group.Hud) + @JvmStatic val noBossBar by setting("No Boss Bar", false).group(Group.Hud) + @JvmStatic val noScoreBoard by setting("No Score Board", false).group(Group.Hud) + @JvmStatic val noStatusEffects by setting("No Status Effects", false).group(Group.Hud) + + @JvmStatic val noArmor by setting("No Armor", false).group(Group.Entity) + @JvmStatic val includeNoOtherHeadItems by setting("Include No Other Head Items", false) { noArmor }.group(Group.Entity) + @JvmStatic val noElytra by setting("No Elytra", false).group(Group.Entity) + @JvmStatic val noInvisibility by setting("No Invisibility", true).group(Group.Entity) + @JvmStatic val noGlow by setting("No Glow", false).group(Group.Entity) + @JvmStatic val noNametags by setting("No Nametags", false).group(Group.Entity) // RenderLayer.getArmorEntityGlint(), RenderLayer.getGlint(), RenderLayer.getGlintTranslucent(), RenderLayer.getEntityGlint() // @JvmStatic val noEnchantmentGlint by setting("No Enchantment Glint", false).group(Group.Entity) // @JvmStatic val noDeadEntities by setting("No Dead Entities", false).group(Group.Entity) - private val playerEntities by setting("Player Entities", emptySet(), playerEntityMap.values.toSet(), "Player entities to omit from rendering").group(Group.Entity) - private val bossEntities by setting("Boss Entities", emptySet(), bossEntityMap.values.toSet(), "Boss entities to omit from rendering").group(Group.Entity) - private val decorationEntities by setting("Decoration Entities", emptySet(), decorationEntityMap.values.toSet(), "Decoration entities to omit from rendering").group(Group.Entity) - private val mobEntities by setting("Mob Entities", emptySet(), mobEntityMap.values.toSet(), "Mob entities to omit from rendering").group(Group.Entity) - private val passiveEntities by setting("Passive Entities", emptySet(), passiveEntityMap.values.toSet(), "Passive entities to omit from rendering").group(Group.Entity) - private val projectileEntities by setting("Projectile Entities", emptySet(), projectileEntityMap.values.toSet(), "Projectile entities to omit from rendering").group(Group.Entity) - private val vehicleEntities by setting("Vehicle Entities", emptySet(), vehicleEntityMap.values.toSet(), "Vehicle entities to omit from rendering").group(Group.Entity) - private val miscEntities by setting("Misc Entities", emptySet(), miscEntityMap.values.toSet(), "Miscellaneous entities to omit from rendering").group(Group.Entity) - private val blockEntities by setting("Block Entities", emptySet(), blockEntityMap.values.toSet(), "Block entities to omit from rendering").group(Group.Entity) - - @JvmStatic val noTerrainFog by setting("No Terrain Fog", false).group(Group.World) - @JvmStatic val noSignText by setting("No Sign Text", false).group(Group.World) - @JvmStatic val noWorldBorder by setting("No World Border", false).group(Group.World) - @JvmStatic val noEnchantingTableBook by setting("No Enchanting Table Book", false).group(Group.World) - // Couldn't get to work with block entities without crashing with sodium on boot + private val playerEntities by setting("Player Entities", emptySet(), playerEntityMap.values.toSet(), "Player entities to omit from rendering").group(Group.Entity) + private val bossEntities by setting("Boss Entities", emptySet(), bossEntityMap.values.toSet(), "Boss entities to omit from rendering").group(Group.Entity) + private val decorationEntities by setting("Decoration Entities", emptySet(), decorationEntityMap.values.toSet(), "Decoration entities to omit from rendering").group(Group.Entity) + private val mobEntities by setting("Mob Entities", emptySet(), mobEntityMap.values.toSet(), "Mob entities to omit from rendering").group(Group.Entity) + private val passiveEntities by setting("Passive Entities", emptySet(), passiveEntityMap.values.toSet(), "Passive entities to omit from rendering").group(Group.Entity) + private val projectileEntities by setting("Projectile Entities", emptySet(), projectileEntityMap.values.toSet(), "Projectile entities to omit from rendering").group(Group.Entity) + private val vehicleEntities by setting("Vehicle Entities", emptySet(), vehicleEntityMap.values.toSet(), "Vehicle entities to omit from rendering").group(Group.Entity) + private val miscEntities by setting("Misc Entities", emptySet(), miscEntityMap.values.toSet(), "Miscellaneous entities to omit from rendering").group(Group.Entity) + private val blockEntities by setting("Block Entities", emptySet(), blockEntityMap.values.toSet(), "Block entities to omit from rendering").group(Group.Entity) + + @JvmStatic val noTerrainFog by setting("No Terrain Fog", false).group(Group.World) + @JvmStatic val noSignText by setting("No Sign Text", false).group(Group.World) + @JvmStatic val noWorldBorder by setting("No World Border", false).group(Group.World) + @JvmStatic val noEnchantingTableBook by setting("No Enchanting Table Book", false).group(Group.World) + // Couldn't get to work with block entities without crashing with sodium on boot // @JvmStatic val noBlockBreakingOverlay by setting("No Block Breaking Overlay", false).group(Group.World) - @JvmStatic val noBeaconBeams by setting("No Beacon Beams", false).group(Group.World) - @JvmStatic val noSpawnerMob by setting("No Spawner Mob", false).group(Group.World) - private val particles by setting("Particles", emptySet(), particleMap.values.toSet(), "Particles to omit from rendering").group(Group.World) - - private fun createParticleNameMap() = - scanResult - .getSubclasses(Particle::class.java) - .filter { !it.isAbstract } - .createNameMap("net.minecraft.client.particle.", "Particle") - - private fun createEntityNameMap(directory: String, strictDir: Boolean = false) = - entities.createNameMap(directory, "Entity", strictDir) - - private fun createBlockEntityNameMap() = - scanResult - .getSubclasses(BlockEntity::class.java) - .filter { !it.isAbstract }.createNameMap("net.minecraft.block.entity", "BlockEntity") - - private fun Collection.createNameMap( - directory: String, - removePattern: String = "", - strictDirectory: Boolean = false - ) = map { - val remappedName = it.name.remappedName - val displayName = remappedName - .substring(remappedName.indexOfLast { it == '.' } + 1) - .replace(removePattern, "") - .fancyFormat() - MappingInfo(it.simpleName, remappedName, displayName) - } - .sortedBy { it.displayName.lowercase() } - .filter { info -> - if (strictDirectory) - info.remapped.startsWith(directory) && !info.remapped.substring(directory.length).contains(".") - else info.remapped.startsWith(directory) - } - .associate { it.raw to it.displayName } - - private fun String.fancyFormat() = - replace("$", " - ").replace("(? - miscEntityMap[simpleName] in miscEntities || - playerEntityMap[simpleName] in playerEntities || - projectileEntityMap[simpleName] in projectileEntities || - vehicleEntityMap[simpleName] in vehicleEntities || - decorationEntityMap[simpleName] in decorationEntities || - passiveEntityMap[simpleName] in passiveEntities || - mobEntityMap[simpleName] in mobEntities || - bossEntityMap[simpleName] in bossEntities - SpawnGroup.WATER_AMBIENT, - SpawnGroup.WATER_CREATURE, - SpawnGroup.AMBIENT, - SpawnGroup.AXOLOTLS, - SpawnGroup.CREATURE, - SpawnGroup.UNDERGROUND_WATER_CREATURE -> passiveEntityMap[simpleName] in passiveEntities - SpawnGroup.MONSTER -> mobEntityMap[simpleName] in mobEntities - } - } - - @JvmStatic - fun shouldOmitBlockEntity(blockEntity: BlockEntity) = - isEnabled && blockEntityMap[blockEntity.javaClass.simpleName] in blockEntities - - @JvmStatic - fun shouldAcceptFog(modifier: StatusEffectFogModifier) = - when (modifier.statusEffect) { - StatusEffects.BLINDNESS if (noBlindness && isEnabled) -> false - StatusEffects.DARKNESS if (noDarkness && isEnabled) -> false - else -> true - } - - private data class MappingInfo( - val raw: String, - val remapped: String, - val displayName: String - ) + @JvmStatic val noBeaconBeams by setting("No Beacon Beams", false).group(Group.World) + @JvmStatic val noSpawnerMob by setting("No Spawner Mob", false).group(Group.World) + private val particles by setting("Particles", emptySet(), particleMap.values.toSet(), "Particles to omit from rendering").group(Group.World) + + private fun createParticleNameMap() = + scanResult + .getSubclasses(Particle::class.java) + .filter { !it.isAbstract } + .createNameMap("net.minecraft.client.particle.", "Particle") + + private fun createEntityNameMap(directory: String, strictDir: Boolean = false) = + entities.createNameMap(directory, "Entity", strictDir) + + private fun createBlockEntityNameMap() = + scanResult + .getSubclasses(BlockEntity::class.java) + .filter { !it.isAbstract }.createNameMap("net.minecraft.block.entity", "BlockEntity") + + private fun Collection.createNameMap( + directory: String, + removePattern: String = "", + strictDirectory: Boolean = false + ) = map { + val remappedName = it.name.remappedName + val displayName = remappedName + .substring(remappedName.indexOfLast { it == '.' } + 1) + .replace(removePattern, "") + .fancyFormat() + MappingInfo(it.simpleName, remappedName, displayName) + } + .sortedBy { it.displayName.lowercase() } + .filter { info -> + if (strictDirectory) + info.remapped.startsWith(directory) && !info.remapped.substring(directory.length).contains(".") + else info.remapped.startsWith(directory) + } + .associate { it.raw to it.displayName } + + private fun String.fancyFormat() = + replace("$", " - ").replace("(?) = + isEnabled && particleMap[particle.simpleName] in particles + + @JvmStatic + fun shouldOmitEntity(entity: Entity): Boolean { + val simpleName = entity.javaClass.simpleName + return isEnabled && when (entity.type.spawnGroup) { + SpawnGroup.MISC -> + miscEntityMap[simpleName] in miscEntities || + playerEntityMap[simpleName] in playerEntities || + projectileEntityMap[simpleName] in projectileEntities || + vehicleEntityMap[simpleName] in vehicleEntities || + decorationEntityMap[simpleName] in decorationEntities || + passiveEntityMap[simpleName] in passiveEntities || + mobEntityMap[simpleName] in mobEntities || + bossEntityMap[simpleName] in bossEntities + SpawnGroup.WATER_AMBIENT, + SpawnGroup.WATER_CREATURE, + SpawnGroup.AMBIENT, + SpawnGroup.AXOLOTLS, + SpawnGroup.CREATURE, + SpawnGroup.UNDERGROUND_WATER_CREATURE -> passiveEntityMap[simpleName] in passiveEntities + SpawnGroup.MONSTER -> mobEntityMap[simpleName] in mobEntities + } + } + + @JvmStatic + fun shouldOmitBlockEntity(blockEntity: BlockEntity) = + isEnabled && blockEntityMap[blockEntity.javaClass.simpleName] in blockEntities + + private data class MappingInfo( + val raw: String, + val remapped: String, + val displayName: String + ) } diff --git a/src/main/kotlin/com/lambda/module/modules/render/StorageESP.kt b/src/main/kotlin/com/lambda/module/modules/render/StorageESP.kt index 910684cc9..e20dbe40d 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/StorageESP.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/StorageESP.kt @@ -18,19 +18,18 @@ package com.lambda.module.modules.render import com.lambda.context.SafeContext -import com.lambda.event.events.onStaticRender +import com.lambda.graphics.esp.ShapeScope import com.lambda.graphics.renderer.esp.DirectionMask import com.lambda.graphics.renderer.esp.DirectionMask.buildSideMesh -import com.lambda.graphics.renderer.esp.ShapeBuilder +import com.lambda.event.events.onStaticRender import com.lambda.module.Module import com.lambda.module.tag.ModuleTag +import com.lambda.util.world.blockEntitySearch +import com.lambda.util.world.entitySearch import com.lambda.threading.runSafe import com.lambda.util.NamedEnum import com.lambda.util.extension.blockColor -import com.lambda.util.extension.outlineShape import com.lambda.util.math.setAlpha -import com.lambda.util.world.blockEntitySearch -import com.lambda.util.world.entitySearch import net.minecraft.block.entity.BarrelBlockEntity import net.minecraft.block.entity.BlastFurnaceBlockEntity import net.minecraft.block.entity.BlockEntity @@ -50,145 +49,149 @@ import net.minecraft.entity.vehicle.MinecartEntity import java.awt.Color object StorageESP : Module( - name = "StorageESP", - description = "Render storage blocks/entities", - tag = ModuleTag.RENDER, + name = "StorageESP", + description = "Render storage blocks/entities", + tag = ModuleTag.RENDER, ) { - /* General settings */ - private val distance by setting("Distance", 64.0, 10.0..256.0, 1.0, "Maximum distance for rendering").group(Group.General) - - /* Render settings */ - private var drawFaces: Boolean by setting("Draw Faces", true, "Draw faces of blocks").onValueChange { _, to -> drawEdges = !to && !drawFaces }.group(Group.Render) - private var drawEdges: Boolean by setting("Draw Edges", true, "Draw edges of blocks").onValueChange { _, to -> drawFaces = !to && !drawEdges }.group(Group.Render) - private val mode by setting("Outline Mode", DirectionMask.OutlineMode.And, "Outline mode").group(Group.Render) - private val mesh by setting("Mesh", true, "Connect similar adjacent blocks").group(Group.Render) - - /* Color settings */ - private val useBlockColor by setting("Use Block Color", true, "Use the color of the block instead").group(Group.Color) - private val facesAlpha by setting("Faces Alpha", 0.3, 0.1..1.0, 0.05).group(Group.Color) - private val edgesAlpha by setting("Edges Alpha", 0.3, 0.1..1.0, 0.05).group(Group.Color) - - // TODO: - // val blockColors by setting("Block Colors", mapOf()) { page == Page.Color && !useBlockColor } - // val renders by setting("Render Blocks", mapOf()) { page == Page.General } - // - // TODO: Create enum of MapColors - - // I used this to extract the colors as rgb format - //> function extract(color) { - // ... console.log((color >> 16) & 0xFF) - // ... console.log((color >> 8) & 0xFF) - // ... console.log(color & 0xFF) - // ... } - - private val barrelColor by setting("Barrel Color", Color(143, 119, 72)) { !useBlockColor }.group(Group.Color) - private val blastFurnaceColor by setting("Blast Furnace Color", Color(153, 153, 153)) { !useBlockColor }.group(Group.Color) - private val brewingStandColor by setting("Brewing Stand Color", Color(167, 167, 167)) { !useBlockColor }.group(Group.Color) - private val trappedChestColor by setting("Trapped Chest Color", Color(216, 127, 51)) { !useBlockColor }.group(Group.Color) - private val chestColor by setting("Chest Color", Color(216, 127, 51)) { !useBlockColor }.group(Group.Color) - private val dispenserColor by setting("Dispenser Color", Color(153, 153, 153)) { !useBlockColor }.group(Group.Color) - private val enderChestColor by setting("Ender Chest Color", Color(127, 63, 178)) { !useBlockColor }.group(Group.Color) - private val furnaceColor by setting("Furnace Color", Color(153, 153, 153)) { !useBlockColor }.group(Group.Color) - private val hopperColor by setting("Hopper Color", Color(76, 76, 76)) { !useBlockColor }.group(Group.Color) - private val smokerColor by setting("Smoker Color", Color(112, 112, 112)) { !useBlockColor }.group(Group.Color) - private val shulkerColor by setting("Shulker Color", Color(178, 76, 216)) { !useBlockColor }.group(Group.Color) - private val itemFrameColor by setting("Item Frame Color", Color(216, 127, 51)) { !useBlockColor }.group(Group.Color) - private val cartColor by setting("Minecart Color", Color(102, 127, 51)) { !useBlockColor }.group(Group.Color) - - private val entities = setOf( - BarrelBlockEntity::class, - BlastFurnaceBlockEntity::class, - BrewingStandBlockEntity::class, - TrappedChestBlockEntity::class, - ChestBlockEntity::class, - DispenserBlockEntity::class, - EnderChestBlockEntity::class, - FurnaceBlockEntity::class, - HopperBlockEntity::class, - SmokerBlockEntity::class, - ShulkerBoxBlockEntity::class, - AbstractMinecartEntity::class, - ItemFrameEntity::class, - MinecartEntity::class, - ) - - init { - onStaticRender { builder -> - blockEntitySearch(distance) - .filter { it::class in entities } - .forEach { with(builder) { build(it, excludedSides(it)) } } - - val mineCarts = entitySearch(distance) - val itemFrames = entitySearch(distance) - (mineCarts + itemFrames) - .forEach { with(builder) { build(it, DirectionMask.ALL) } } // FixMe: Exclude entity shape sides - } - } - - private fun SafeContext.excludedSides(blockEntity: BlockEntity): Int { - val isFullCube = blockEntity.cachedState.isFullCube(world, blockEntity.pos) - return if (mesh && isFullCube) { - buildSideMesh(blockEntity.pos) { neighbor -> - val other = world.getBlockEntity(neighbor) ?: return@buildSideMesh false - val otherFullCube = other.cachedState.isFullCube(world, other.pos) - val sameType = blockEntity.cachedState.block == other.cachedState.block - val searchedFor = other::class in entities - - searchedFor && otherFullCube && sameType - } - } else DirectionMask.ALL - } - - private fun ShapeBuilder.build( - block: BlockEntity, - sides: Int, - ) = runSafe { - val color = - if (useBlockColor) blockColor(block.cachedState, block.pos) - else block.color ?: return@runSafe - - val shape = outlineShape(block.cachedState, block.pos) - - if (drawFaces) filled(shape, color.setAlpha(facesAlpha), sides) - if (drawEdges) outline(shape, color.setAlpha(edgesAlpha), sides, mode) - } - - private fun ShapeBuilder.build( - entity: Entity, - sides: Int, - ) = runSafe { - val color = entity.color ?: return@runSafe - - if (drawFaces) filled(entity.boundingBox, color.setAlpha(facesAlpha), sides) - if (drawEdges) outline(entity.boundingBox, color.setAlpha(edgesAlpha), sides, mode) - } - - private val BlockEntity?.color get() = - when (this) { - is BarrelBlockEntity -> barrelColor - is BlastFurnaceBlockEntity -> blastFurnaceColor - is BrewingStandBlockEntity -> brewingStandColor - is TrappedChestBlockEntity -> trappedChestColor - is ChestBlockEntity -> chestColor - is DispenserBlockEntity -> dispenserColor - is EnderChestBlockEntity -> enderChestColor - is FurnaceBlockEntity -> furnaceColor - is HopperBlockEntity -> hopperColor - is SmokerBlockEntity -> smokerColor - is ShulkerBoxBlockEntity -> shulkerColor - else -> null - } - - private val Entity?.color get() = - when (this) { - is AbstractMinecartEntity -> cartColor - is ItemFrameEntity -> itemFrameColor - else -> null - } - - private enum class Group(override val displayName: String) : NamedEnum { - General("General"), - Render("Render"), - Color("Color") - } + private val distance by setting("Distance", 64.0, 10.0..256.0, 1.0, "Maximum distance for rendering").group(Group.General) + private var drawFaces: Boolean by setting("Draw Faces", true, "Draw faces of blocks").group(Group.Render) + private var drawEdges: Boolean by setting("Draw Edges", true, "Draw edges of blocks").group(Group.Render) + private val mode by setting("Outline Mode", DirectionMask.OutlineMode.And, "Outline mode").group(Group.Render) + private val mesh by setting("Mesh", true, "Connect similar adjacent blocks").group(Group.Render) + private val useBlockColor by setting("Use Block Color", true, "Use the color of the block instead").group(Group.Color) + private val facesAlpha by setting("Faces Alpha", 0.3, 0.1..1.0, 0.05).group(Group.Color) + private val edgesAlpha by setting("Edges Alpha", 0.3, 0.1..1.0, 0.05).group(Group.Color) + private val outlineWidth by setting("Outline Width", 1.0f, 0.5f..5.0f, 0.5f) { drawEdges }.group(Group.Render) + + // TODO: + // val blockColors by setting("Block Colors", mapOf()) { page == Page.Color + // && + // !useBlockColor } + // val renders by setting("Render Blocks", mapOf()) { page == Page.General + // } + // + // TODO: Create enum of MapColors + + // I used this to extract the colors as rgb format + // > function extract(color) { + // ... console.log((color >> 16) & 0xFF) + // ... console.log((color >> 8) & 0xFF) + // ... console.log(color & 0xFF) + // ... } + + private val barrelColor by setting("Barrel Color", Color(143, 119, 72)) { !useBlockColor }.group(Group.Color) + private val blastFurnaceColor by setting("Blast Furnace Color", Color(153, 153, 153)) { !useBlockColor }.group(Group.Color) + private val brewingStandColor by setting("Brewing Stand Color", Color(167, 167, 167)) { !useBlockColor }.group(Group.Color) + private val trappedChestColor by setting("Trapped Chest Color", Color(216, 127, 51)) { !useBlockColor }.group(Group.Color) + private val chestColor by setting("Chest Color", Color(216, 127, 51)) { !useBlockColor }.group(Group.Color) + private val dispenserColor by setting("Dispenser Color", Color(153, 153, 153)) { !useBlockColor }.group(Group.Color) + private val enderChestColor by setting("Ender Chest Color", Color(127, 63, 178)) { !useBlockColor }.group(Group.Color) + private val furnaceColor by setting("Furnace Color", Color(153, 153, 153)) { !useBlockColor }.group(Group.Color) + private val hopperColor by setting("Hopper Color", Color(76, 76, 76)) { !useBlockColor }.group(Group.Color) + private val smokerColor by setting("Smoker Color", Color(112, 112, 112)) { !useBlockColor }.group(Group.Color) + private val shulkerColor by setting("Shulker Color", Color(178, 76, 216)) { !useBlockColor }.group(Group.Color) + private val itemFrameColor by setting("Item Frame Color", Color(216, 127, 51)) { !useBlockColor }.group(Group.Color) + private val cartColor by setting("Minecart Color", Color(102, 127, 51)) { !useBlockColor }.group(Group.Color) + + private val entities = setOf( + BarrelBlockEntity::class, + BlastFurnaceBlockEntity::class, + BrewingStandBlockEntity::class, + TrappedChestBlockEntity::class, + ChestBlockEntity::class, + DispenserBlockEntity::class, + EnderChestBlockEntity::class, + FurnaceBlockEntity::class, + HopperBlockEntity::class, + SmokerBlockEntity::class, + ShulkerBoxBlockEntity::class, + AbstractMinecartEntity::class, + ItemFrameEntity::class, + MinecartEntity::class, + ) + + init { + onStaticRender { esp -> + blockEntitySearch(distance) + .filter { it::class in entities } + .forEach { be -> + esp.shapes(be.pos.x.toDouble(), be.pos.y.toDouble(), be.pos.z.toDouble()) { + build(be, excludedSides(be)) + } + } + + val mineCarts = + entitySearch(distance).filter { + it::class in entities + } + val itemFrames = + entitySearch(distance).filter { + it::class in entities + } + (mineCarts + itemFrames).forEach { entity -> + esp.shapes(entity.getX(), entity.getY(), entity.getZ()) { + build(entity, DirectionMask.ALL) + } + } + } + } + + private fun SafeContext.excludedSides(blockEntity: BlockEntity): Int { + val isFullCube = blockEntity.cachedState.isFullCube(world, blockEntity.pos) + return if (mesh && isFullCube) { + buildSideMesh(blockEntity.pos) { neighbor -> + val other = + world.getBlockEntity(neighbor) ?: return@buildSideMesh false + val otherFullCube = other.cachedState.isFullCube(world, other.pos) + val sameType = + blockEntity.cachedState.block == other.cachedState.block + val searchedFor = other::class in entities + + searchedFor && otherFullCube && sameType + } + } else DirectionMask.ALL + } + + private fun ShapeScope.build(block: BlockEntity, sides: Int) = runSafe { + val color = + if (useBlockColor) blockColor(block.cachedState, block.pos) + else block.color ?: return@runSafe + box(block, color.setAlpha(facesAlpha), color.setAlpha(edgesAlpha), sides, mode, thickness = outlineWidth) + } + + private fun ShapeScope.build(entity: Entity, sides: Int) = runSafe { + val color = entity.color ?: return@runSafe + box(entity, color.setAlpha(facesAlpha), color.setAlpha(edgesAlpha), sides, mode, thickness = outlineWidth) + } + + private val BlockEntity?.color + get() = + when (this) { + is BarrelBlockEntity -> barrelColor + is BlastFurnaceBlockEntity -> blastFurnaceColor + is BrewingStandBlockEntity -> brewingStandColor + is TrappedChestBlockEntity -> trappedChestColor + is ChestBlockEntity -> chestColor + is DispenserBlockEntity -> dispenserColor + is EnderChestBlockEntity -> enderChestColor + is FurnaceBlockEntity -> furnaceColor + is HopperBlockEntity -> hopperColor + is SmokerBlockEntity -> smokerColor + is ShulkerBoxBlockEntity -> shulkerColor + else -> null + } + + private val Entity?.color + get() = + when (this) { + is AbstractMinecartEntity -> cartColor + is ItemFrameEntity -> itemFrameColor + else -> null + } + + private enum class Group(override val displayName: String) : NamedEnum { + General("General"), + Render("Render"), + Color("Color") + } } diff --git a/src/main/kotlin/com/lambda/module/modules/render/ViewModel.kt b/src/main/kotlin/com/lambda/module/modules/render/ViewModel.kt index e51c6f497..4fca01ba9 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/ViewModel.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/ViewModel.kt @@ -18,8 +18,7 @@ package com.lambda.module.modules.render import com.lambda.Lambda.mc -import com.lambda.event.events.KeyboardEvent -import com.lambda.event.events.MouseEvent +import com.lambda.event.events.ButtonEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module @@ -36,230 +35,221 @@ import org.joml.Vector3f import org.joml.Vector3i import kotlin.math.tan +@Suppress("unused") object ViewModel : Module( - name = "ViewModel", - description = "Adjusts hand and held item rendering", - tag = ModuleTag.RENDER, + name = "ViewModel", + description = "Adjusts hand and held item rendering", + tag = ModuleTag.RENDER, ) { - private val swingMode by setting("Swing Mode", SwingMode.Standard, "Changes which hands swing").group(Group.General) - val swingDuration by setting("Swing Duration", 6, 0..20, 1, "Adjusts how fast the player swings", "ticks").group(Group.General) - private val noSwingDelay by setting("No Swing Delay", false, "Removes the delay between swings").group(Group.General) - val mainSwingProgress by setting("Main Swing Progress", 0.0f, 0.0f..1.0f, 0.025f, "Renders as if the players main hand was this progress through the swing animation").group(Group.General) - val offhandSwingProgress by setting("Offhand Swing Progress", 0.0f, 0.0f..1.0f, 0.025f, "Renders as if the players offhand was this progress through the swing animation").group(Group.General) - val oldAnimations by setting("Old Animations", false, "Adjusts the animations to look like they did in 1.8").group(Group.General) - val swapAnimation by setting("Swap Animation", true, "If disabled, removes the drop down animation when swapping item") { oldAnimations }.group(Group.General) - //ToDo: Implement + private val swingMode by setting("Swing Mode", SwingMode.Standard, "Changes which hands swing").group(Group.General) + val swingDuration by setting("Swing Duration", 6, 0..20, 1, "Adjusts how fast the player swings", "ticks").group(Group.General) + private val noSwingDelay by setting("No Swing Delay", false, "Removes the delay between swings").group(Group.General) + val mainSwingProgress by setting("Main Swing Progress", 0.0f, 0.0f..1.0f, 0.025f, "Renders as if the players main hand was this progress through the swing animation").group(Group.General) + val offhandSwingProgress by setting("Offhand Swing Progress", 0.0f, 0.0f..1.0f, 0.025f, "Renders as if the players offhand was this progress through the swing animation").group(Group.General) + val oldAnimations by setting("Old Animations", false, "Adjusts the animations to look like they did in 1.8").group(Group.General) + val swapAnimation by setting("Swap Animation", true, "If disabled, removes the drop down animation when swapping item") { oldAnimations }.group(Group.General) + //ToDo: Implement // val shadow by setting("Shadows", true, "If disabled, removes shadows on the model") { page == Page.General } - private val splitScale by setting("Split Scale", false, "Splits left and right hand scale settings").group(Group.Scale) - private val xScale by setting("X Scale", 1.0f, -1.0f..1.0f, 0.025f) { !splitScale }.onValueChange { _, to -> leftXScale = to; rightXScale = to }.group(Group.Scale) - private val yScale by setting("Y Scale", 1.0f, -1.0f..1.0f, 0.025f) { !splitScale }.onValueChange { _, to -> leftYScale = to; rightYScale = to }.group(Group.Scale) - private val zScale by setting("Z Scale", 1.0f, -1.0f..1.0f, 0.025f) { !splitScale }.onValueChange { _, to -> leftZScale = to; rightZScale = to }.group(Group.Scale) - private var leftXScale by setting("Left X Scale", 1.0f, -1.0f..1.0f, 0.025f) { splitScale }.group(Group.Scale) - private var leftYScale by setting("Left Y Scale", 1.0f, -1.0f..1.0f, 0.025f) { splitScale }.group(Group.Scale) - private var leftZScale by setting("Left Z Scale", 1.0f, -1.0f..1.0f, 0.025f) { splitScale }.group(Group.Scale) - private var rightXScale by setting("Right X Scale", 1.0f, -1.0f..1.0f, 0.025f) { splitScale }.group(Group.Scale) - private var rightYScale by setting("Right Y Scale", 1.0f, -1.0f..1.0f, 0.025f) { splitScale }.group(Group.Scale) - private var rightZScale by setting("Right Z Scale", 1.0f, -1.0f..1.0f, 0.025f) { splitScale }.group(Group.Scale) - - private val splitPosition by setting("Split Position", false, "Splits left and right position settings").group(Group.Position) - private val xPosition by setting("X Position", 1.0f, -1.0f..1.0f, 0.025f) { !splitPosition }.onValueChange { _, to -> leftXPosition = to; rightXPosition = to }.group(Group.Position) - private val yPosition by setting("Y Position", 1.0f, -1.0f..1.0f, 0.025f) { !splitPosition }.onValueChange { _, to -> leftYPosition = to; rightYPosition = to }.group(Group.Position) - private val zPosition by setting("Z Position", 1.0f, -1.0f..1.0f, 0.025f) { !splitPosition }.onValueChange { _, to -> leftZPosition = to; rightZPosition = to }.group(Group.Position) - private var leftXPosition by setting("Left X Position", 0.0f, -1.0f..1.0f, 0.025f) { splitPosition }.group(Group.Position) - private var leftYPosition by setting("Left Y Position", 0.0f, -1.0f..1.0f, 0.025f) { splitPosition }.group(Group.Position) - private var leftZPosition by setting("Left Z Position", 0.0f, -1.0f..1.0f, 0.025f) { splitPosition }.group(Group.Position) - private var rightXPosition by setting("Right X Position", 0.0f, -1.0f..1.0f, 0.025f) { splitPosition }.group(Group.Position) - private var rightYPosition by setting("Right Y Position", 0.0f, -1.0f..1.0f, 0.025f) { splitPosition }.group(Group.Position) - private var rightZPosition by setting("Right Z Position", 0.0f, -1.0f..1.0f, 0.025f) { splitPosition }.group(Group.Position) - - private val splitRotation by setting("Split Rotation", false, "Splits left and right rotation settings").group(Group.Rotation) - private val xRotation by setting("X Rotation", 0, -180..180, 1) { !splitRotation }.onValueChange { _, to -> leftXRotation = to; rightXRotation = to }.group(Group.Rotation) - private val yRotation by setting("Y Rotation", 0, -180..180, 1) { !splitRotation }.onValueChange { _, to -> leftYRotation = to; rightYRotation = to }.group(Group.Rotation) - private val zRotation by setting("Z Rotation", 0, -180..180, 1) { !splitRotation }.onValueChange { _, to -> leftZRotation = to; rightZRotation = to }.group(Group.Rotation) - private var leftXRotation by setting("Left X Rotation", 0, -180..180, 1) { splitRotation }.group(Group.Rotation) - private var leftYRotation by setting("Left Y Rotation", 0, -180..180, 1) { splitRotation }.group(Group.Rotation) - private var leftZRotation by setting("Left Z Rotation", 0, -180..180, 1) { splitRotation }.group(Group.Rotation) - private var rightXRotation by setting("Right X Rotation", 0, -180..180, 1) { splitRotation }.group(Group.Rotation) - private var rightYRotation by setting("Right Y Rotation", 0, -180..180, 1) { splitRotation }.group(Group.Rotation) - private var rightZRotation by setting("Right Z Rotation", 0, -180..180, 1) { splitRotation }.group(Group.Rotation) - - private val splitFov by setting("Split FOV", false, "Splits left and right Fov settings").group(Group.Fov) - private val fov by setting("FOV", 70, 10..180, 1) { !splitFov }.onValueChange { _, to -> leftFov = to; rightFov = to }.group(Group.Fov) - private val fovAnchorDistance by setting("Anchor Distance", 0.5f, 0.0f..1.0f, 0.01f, "The distance to anchor the FOV transformation from") { !splitFov }.onValueChange { _, to -> leftFovAnchorDistance = to; rightFovAnchorDistance = to }.group(Group.Fov) - private var leftFov by setting("Left FOV", 70, 10..180, 1) { splitFov }.group(Group.Fov) - private var leftFovAnchorDistance by setting("Left Anchor Distance", 0.5f, 0.0f..1.0f, 0.01f, "The distance to anchor the left FOV transformation from") { splitFov }.group(Group.Fov) - private var rightFov by setting("Right FOV", 70, 10..180, 1) { splitFov }.group(Group.Fov) - private var rightFovAnchorDistance by setting("Right Anchor Distance", 0.5f, 0.0f..1.0f, 0.01f, "The distance to anchor the right FOV transformation from") { splitFov }.group(Group.Fov) - - private val enableHand by setting("Hand", false, "Enables settings for the players hand").group(Group.Hand) - private val handXScale by setting("Hand X Scale", 1.0f, -1.0f..1.0f, 0.025f) { enableHand }.group(Group.Hand) - private val handYScale by setting("Hand Y Scale", 1.0f, -1.0f..1.0f, 0.025f) { enableHand }.group(Group.Hand) - private val handZScale by setting("Hand Z Scale", 1.0f, -1.0f..1.0f, 0.025f) { enableHand }.group(Group.Hand) - private val handXPosition by setting("Hand X Position", 0.0f, -1.0f..1.0f, 0.025f) { enableHand }.group(Group.Hand) - private val handYPosition by setting("Hand Y Position", 0.0f, -1.0f..1.0f, 0.025f) { enableHand }.group(Group.Hand) - private val handZPosition by setting("Hand Z Position", 0.0f, -1.0f..1.0f, 0.025f) { enableHand }.group(Group.Hand) - private val handXRotation by setting("Hand X Rotation", 0, -180..180, 1) { enableHand }.group(Group.Hand) - private val handYRotation by setting("Hand Y Rotation", 0, -180..180, 1) { enableHand }.group(Group.Hand) - private val handZRotation by setting("Hand Z Rotation", 0, -180..180, 1) { enableHand }.group(Group.Hand) - private val handFov by setting("Hand FOV", 70, 10..180, 1) { enableHand }.group(Group.Hand) - private val handFovAnchorDistance by setting("Hand FOV Anchor Distance", 0.5f, 0.0f..1.0f, 0.01f, "The distance to anchor the hands FOV transformation from") { enableHand }.group(Group.Hand) - - private var attackKeyTicksPressed = -1 - - init { - listen { event -> - if (event.button == mc.options.attackKey.boundKey.code) - attackKeyTicksPressed = if (event.action == 0) -1 else 0 - } - - listen { event -> - if (event.keyCode == mc.options.attackKey.boundKey.code) { - if (event.isPressed) { - attackKeyTicksPressed = 0 - } else if (event.isReleased) { - attackKeyTicksPressed = -1 - } - } - } - - listen { - if (attackKeyTicksPressed != -1) - attackKeyTicksPressed++ - } - } - - fun transform(itemStack: ItemStack, hand: Hand, matrices: MatrixStack) { - val side = if (mc.options.mainArm.value == Arm.LEFT) { - if (hand == Hand.MAIN_HAND) Side.Left else Side.Right - } else { - if (hand == Hand.MAIN_HAND) Side.Right else Side.Left - } - - val emptyHand = itemStack.isEmpty - if (!enableHand && emptyHand) return - - applyItemFov(matrices, side, emptyHand) - scale(side, matrices, emptyHand) - position(side, matrices, emptyHand) - rotate(side, matrices, emptyHand) - } - - private fun applyItemFov(matrices: MatrixStack, side: Side, emptyHand: Boolean) { - val fov = when { - side == Side.Left -> leftFov - emptyHand -> handFov - else -> rightFov - }.toFloat() - - if (fov == 70f) return - - val fovRatio = tan(Math.toRadians(fov.toDouble() / 2)).toFloat() / tan(Math.toRadians(70.0 / 2)).toFloat() - - val matrix = matrices.peek().positionMatrix - - val distance = if (emptyHand) { - handFovAnchorDistance - } else { - when (side) { - Side.Left -> leftFovAnchorDistance - Side.Right -> rightFovAnchorDistance - } - } - - val warpMatrix = Matrix4f().apply { - translate(0f, 0f, -distance) - scale(1f, 1f, fovRatio) - translate(0f, 0f, distance) - } - - matrix.mul(warpMatrix) - } - - private fun scale(side: Side, matrices: MatrixStack, emptyHand: Boolean) { - val scaleVec = getScaleVec(side, emptyHand) - matrices.scale(scaleVec.x, scaleVec.y, scaleVec.z) - } - - private fun getScaleVec(side: Side, emptyHand: Boolean): Vector3f = - if (emptyHand) Vector3f(handXScale, handYScale, handZScale) - else when (side) { - Side.Left -> Vector3f(leftXScale, leftYScale, leftZScale) - Side.Right -> Vector3f(rightXScale, rightYScale, rightZScale) - } - - private fun position(side: Side, matrices: MatrixStack, emptyHand: Boolean) { - val positionVec = getPositionVec(side, emptyHand) - matrices.translate(positionVec.x, positionVec.y, positionVec.z) - } - - private fun getPositionVec(side: Side, emptyHand: Boolean) = - when (side) { - Side.Left -> - if (emptyHand) Vector3f(-handXPosition, handYPosition, handZPosition) - else Vector3f(-leftXPosition, leftYPosition, leftZPosition) - Side.Right -> - if (emptyHand) Vector3f(handXPosition, handYPosition, handZPosition) - else Vector3f(rightXPosition, rightYPosition, rightZPosition) - } - - private fun rotate(side: Side, matrices: MatrixStack, emptyHand: Boolean) { - val rotationVec = getRotationVec(side, emptyHand) - matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(rotationVec.x.toFloat())) - matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(rotationVec.y.toFloat())) - matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(rotationVec.z.toFloat())) - } - - private fun getRotationVec(side: Side, emptyHand: Boolean) = - when (side) { - Side.Left -> { - if (emptyHand) Vector3i(handXRotation, -handYRotation, -handZRotation) - else Vector3i(leftXRotation, -leftYRotation, -leftZRotation) - } - Side.Right -> { - if (emptyHand) Vector3i(handXRotation, handYRotation, handZRotation) - else Vector3i(rightXRotation, rightYRotation, rightZRotation) - } - } - - fun adjustSwing(hand: Hand, player: AbstractClientPlayerEntity) = - when (swingMode) { - SwingMode.Standard -> swingHand(hand, player) - SwingMode.Opposites -> - if (hand == Hand.MAIN_HAND) swingHand(Hand.OFF_HAND, player) - else swingHand(Hand.MAIN_HAND, player) - SwingMode.MainHand -> swingHand(Hand.MAIN_HAND, player) - SwingMode.OffHand -> swingHand(Hand.OFF_HAND, player) - SwingMode.None -> {} - } - - private fun swingHand(hand: Hand, player: AbstractClientPlayerEntity) = - with(player) { - if ( - (!handSwinging || handSwingTicks >= handSwingDuration / 2) || - handSwingTicks < 0 || - (noSwingDelay && attackKeyTicksPressed <= 1)) - { - handSwingTicks = -1 - handSwinging = true - preferredHand = hand - } - } - - private enum class Group(override val displayName: String): NamedEnum { - General("General"), - Scale("Scale"), - Position("Position"), - Rotation("Rotation"), - Fov("FOV"), - Hand("Hand") - } - - private enum class Side { - Left, Right - } - - private enum class SwingMode { - Standard, Opposites, MainHand, OffHand, None - } + private val splitScale by setting("Split Scale", false, "Splits left and right hand scale settings").group(Group.Scale) + private val xScale by setting("X Scale", 1.0f, 0.0f..2.0f, 0.025f) { !splitScale }.onValueChange { _, to -> leftXScale = to; rightXScale = to }.group(Group.Scale) + private val yScale by setting("Y Scale", 1.0f, 0.0f..2.0f, 0.025f) { !splitScale }.onValueChange { _, to -> leftYScale = to; rightYScale = to }.group(Group.Scale) + private val zScale by setting("Z Scale", 1.0f, 0.0f..2.0f, 0.025f) { !splitScale }.onValueChange { _, to -> leftZScale = to; rightZScale = to }.group(Group.Scale) + private var leftXScale by setting("Left X Scale", 1.0f, 0.0f..2.0f, 0.025f) { splitScale }.group(Group.Scale) + private var leftYScale by setting("Left Y Scale", 1.0f, 0.0f..2.0f, 0.025f) { splitScale }.group(Group.Scale) + private var leftZScale by setting("Left Z Scale", 1.0f, 0.0f..2.0f, 0.025f) { splitScale }.group(Group.Scale) + private var rightXScale by setting("Right X Scale", 1.0f, 0.0f..2.0f, 0.025f) { splitScale }.group(Group.Scale) + private var rightYScale by setting("Right Y Scale", 1.0f, 0.0f..2.0f, 0.025f) { splitScale }.group(Group.Scale) + private var rightZScale by setting("Right Z Scale", 1.0f, 0.0f..2.0f, 0.025f) { splitScale }.group(Group.Scale) + + private val splitPosition by setting("Split Position", false, "Splits left and right position settings").group(Group.Position) + private val xPosition by setting("X Position", 0.0f, -1.0f..1.0f, 0.025f) { !splitPosition }.onValueChange { _, to -> leftXPosition = to; rightXPosition = to }.group(Group.Position) + private val yPosition by setting("Y Position", 0.0f, -1.0f..1.0f, 0.025f) { !splitPosition }.onValueChange { _, to -> leftYPosition = to; rightYPosition = to }.group(Group.Position) + private val zPosition by setting("Z Position", 0.0f, -1.0f..1.0f, 0.025f) { !splitPosition }.onValueChange { _, to -> leftZPosition = to; rightZPosition = to }.group(Group.Position) + private var leftXPosition by setting("Left X Position", 0.0f, -1.0f..1.0f, 0.025f) { splitPosition }.group(Group.Position) + private var leftYPosition by setting("Left Y Position", 0.0f, -1.0f..1.0f, 0.025f) { splitPosition }.group(Group.Position) + private var leftZPosition by setting("Left Z Position", 0.0f, -1.0f..1.0f, 0.025f) { splitPosition }.group(Group.Position) + private var rightXPosition by setting("Right X Position", 0.0f, -1.0f..1.0f, 0.025f) { splitPosition }.group(Group.Position) + private var rightYPosition by setting("Right Y Position", 0.0f, -1.0f..1.0f, 0.025f) { splitPosition }.group(Group.Position) + private var rightZPosition by setting("Right Z Position", 0.0f, -1.0f..1.0f, 0.025f) { splitPosition }.group(Group.Position) + + private val splitRotation by setting("Split Rotation", false, "Splits left and right rotation settings").group(Group.Rotation) + private val xRotation by setting("X Rotation", 0, -180..180, 1) { !splitRotation }.onValueChange { _, to -> leftXRotation = to; rightXRotation = to }.group(Group.Rotation) + private val yRotation by setting("Y Rotation", 0, -180..180, 1) { !splitRotation }.onValueChange { _, to -> leftYRotation = to; rightYRotation = to }.group(Group.Rotation) + private val zRotation by setting("Z Rotation", 0, -180..180, 1) { !splitRotation }.onValueChange { _, to -> leftZRotation = to; rightZRotation = to }.group(Group.Rotation) + private var leftXRotation by setting("Left X Rotation", 0, -180..180, 1) { splitRotation }.group(Group.Rotation) + private var leftYRotation by setting("Left Y Rotation", 0, -180..180, 1) { splitRotation }.group(Group.Rotation) + private var leftZRotation by setting("Left Z Rotation", 0, -180..180, 1) { splitRotation }.group(Group.Rotation) + private var rightXRotation by setting("Right X Rotation", 0, -180..180, 1) { splitRotation }.group(Group.Rotation) + private var rightYRotation by setting("Right Y Rotation", 0, -180..180, 1) { splitRotation }.group(Group.Rotation) + private var rightZRotation by setting("Right Z Rotation", 0, -180..180, 1) { splitRotation }.group(Group.Rotation) + + private val splitFov by setting("Split FOV", false, "Splits left and right Fov settings").group(Group.Fov) + private val fov by setting("FOV", 70, 10..180, 1) { !splitFov }.onValueChange { _, to -> leftFov = to; rightFov = to }.group(Group.Fov) + private val fovAnchorDistance by setting("Anchor Distance", 0.5f, 0.0f..1.0f, 0.01f, "The distance to anchor the FOV transformation from") { !splitFov }.onValueChange { _, to -> leftFovAnchorDistance = to; rightFovAnchorDistance = to }.group(Group.Fov) + private var leftFov by setting("Left FOV", 70, 10..180, 1) { splitFov }.group(Group.Fov) + private var leftFovAnchorDistance by setting("Left Anchor Distance", 0.5f, 0.0f..1.0f, 0.01f, "The distance to anchor the left FOV transformation from") { splitFov }.group(Group.Fov) + private var rightFov by setting("Right FOV", 70, 10..180, 1) { splitFov }.group(Group.Fov) + private var rightFovAnchorDistance by setting("Right Anchor Distance", 0.5f, 0.0f..1.0f, 0.01f, "The distance to anchor the right FOV transformation from") { splitFov }.group(Group.Fov) + + private val enableHand by setting("Hand", false, "Enables settings for the players hand").group(Group.Hand) + private val handXScale by setting("Hand X Scale", 1.0f, 0.0f..2.0f, 0.025f) { enableHand }.group(Group.Hand) + private val handYScale by setting("Hand Y Scale", 1.0f, 0.0f..2.0f, 0.025f) { enableHand }.group(Group.Hand) + private val handZScale by setting("Hand Z Scale", 1.0f, 0.0f..2.0f, 0.025f) { enableHand }.group(Group.Hand) + private val handXPosition by setting("Hand X Position", 0.0f, -1.0f..1.0f, 0.025f) { enableHand }.group(Group.Hand) + private val handYPosition by setting("Hand Y Position", 0.0f, -1.0f..1.0f, 0.025f) { enableHand }.group(Group.Hand) + private val handZPosition by setting("Hand Z Position", 0.0f, -1.0f..1.0f, 0.025f) { enableHand }.group(Group.Hand) + private val handXRotation by setting("Hand X Rotation", 0, -180..180, 1) { enableHand }.group(Group.Hand) + private val handYRotation by setting("Hand Y Rotation", 0, -180..180, 1) { enableHand }.group(Group.Hand) + private val handZRotation by setting("Hand Z Rotation", 0, -180..180, 1) { enableHand }.group(Group.Hand) + private val handFov by setting("Hand FOV", 70, 10..180, 1) { enableHand }.group(Group.Hand) + private val handFovAnchorDistance by setting("Hand FOV Anchor Distance", 0.5f, 0.0f..1.0f, 0.01f, "The distance to anchor the hands FOV transformation from") { enableHand }.group(Group.Hand) + + private var attackKeyTicksPressed = -1 + + init { + listen { event -> + if (event.button == mc.options.attackKey.boundKey.code) + attackKeyTicksPressed = if (event.action == 0) -1 else 0 + } + + listen { event -> + if (event.keyCode == mc.options.attackKey.boundKey.code) { + if (event.isPressed) attackKeyTicksPressed = 0 + else if (event.isReleased) attackKeyTicksPressed = -1 + } + } + + listen { + if (attackKeyTicksPressed != -1) attackKeyTicksPressed++ + } + } + + fun transform(itemStack: ItemStack, hand: Hand, matrices: MatrixStack) { + val side = if (mc.options.mainArm.value == Arm.LEFT) { + if (hand == Hand.MAIN_HAND) Side.Left else Side.Right + } else { + if (hand == Hand.MAIN_HAND) Side.Right else Side.Left + } + + val emptyHand = itemStack.isEmpty + if (!enableHand && emptyHand) return + + applyItemFov(matrices, side, emptyHand) + scale(side, matrices, emptyHand) + position(side, matrices, emptyHand) + rotate(side, matrices, emptyHand) + } + + private fun applyItemFov(matrices: MatrixStack, side: Side, emptyHand: Boolean) { + val fov = when { + side == Side.Left -> leftFov + emptyHand -> handFov + else -> rightFov + }.toFloat() + + if (fov == 70f) return + + val fovRatio = tan(Math.toRadians(fov.toDouble() / 2)).toFloat() / tan(Math.toRadians(70.0 / 2)).toFloat() + + val matrix = matrices.peek().positionMatrix + + val distance = if (emptyHand) handFovAnchorDistance + else when (side) { + Side.Left -> leftFovAnchorDistance + Side.Right -> rightFovAnchorDistance + } + + val warpMatrix = Matrix4f() + .translate(0f, 0f, -distance) + .scale(1f, 1f, fovRatio) + .translate(0f, 0f, distance) + + matrix.mul(warpMatrix) + } + + private fun scale(side: Side, matrices: MatrixStack, emptyHand: Boolean) { + val scaleVec = getScaleVec(side, emptyHand) + matrices.scale(scaleVec.x, scaleVec.y, scaleVec.z) + } + + private fun getScaleVec(side: Side, emptyHand: Boolean): Vector3f = + if (emptyHand) Vector3f(handXScale, handYScale, handZScale) + else when (side) { + Side.Left -> Vector3f(leftXScale, leftYScale, leftZScale) + Side.Right -> Vector3f(rightXScale, rightYScale, rightZScale) + } + + private fun position(side: Side, matrices: MatrixStack, emptyHand: Boolean) { + val positionVec = getPositionVec(side, emptyHand) + matrices.translate(positionVec.x, positionVec.y, positionVec.z) + } + + private fun getPositionVec(side: Side, emptyHand: Boolean) = + when (side) { + Side.Left -> + if (emptyHand) Vector3f(-handXPosition, handYPosition, handZPosition) + else Vector3f(-leftXPosition, leftYPosition, leftZPosition) + Side.Right -> + if (emptyHand) Vector3f(handXPosition, handYPosition, handZPosition) + else Vector3f(rightXPosition, rightYPosition, rightZPosition) + } + + private fun rotate(side: Side, matrices: MatrixStack, emptyHand: Boolean) { + val rotationVec = getRotationVec(side, emptyHand) + matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(rotationVec.x.toFloat())) + matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(rotationVec.y.toFloat())) + matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(rotationVec.z.toFloat())) + } + + private fun getRotationVec(side: Side, emptyHand: Boolean) = + when (side) { + Side.Left -> + if (emptyHand) Vector3i(handXRotation, -handYRotation, -handZRotation) + else Vector3i(leftXRotation, -leftYRotation, -leftZRotation) + Side.Right -> + if (emptyHand) Vector3i(handXRotation, handYRotation, handZRotation) + else Vector3i(rightXRotation, rightYRotation, rightZRotation) + } + + fun adjustSwing(hand: Hand, player: AbstractClientPlayerEntity) = + when (swingMode) { + SwingMode.Standard -> swingHand(hand, player) + SwingMode.Opposites -> + if (hand == Hand.MAIN_HAND) swingHand(Hand.OFF_HAND, player) + else swingHand(Hand.MAIN_HAND, player) + SwingMode.MainHand -> swingHand(Hand.MAIN_HAND, player) + SwingMode.OffHand -> swingHand(Hand.OFF_HAND, player) + SwingMode.None -> {} + } + + private fun swingHand(hand: Hand, player: AbstractClientPlayerEntity) = + with(player) { + if ( + (!handSwinging || handSwingTicks >= handSwingDuration / 2) || + handSwingTicks < 0 || + (noSwingDelay && attackKeyTicksPressed <= 1)) + { + handSwingTicks = -1 + handSwinging = true + preferredHand = hand + } + } + + private enum class Group(override val displayName: String): NamedEnum { + General("General"), + Scale("Scale"), + Position("Position"), + Rotation("Rotation"), + Fov("FOV"), + Hand("Hand") + } + + private enum class Side { + Left, Right + } + + private enum class SwingMode { + Standard, Opposites, MainHand, OffHand, None + } } diff --git a/src/main/kotlin/com/lambda/module/modules/render/Weather.kt b/src/main/kotlin/com/lambda/module/modules/render/Weather.kt index 0f05d3aeb..893004fc0 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Weather.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Weather.kt @@ -24,35 +24,35 @@ import com.lambda.util.NamedEnum import net.minecraft.world.World object Weather : Module( - name = "Weather", - description = "Modifies the client side weather", - tag = ModuleTag.RENDER + name = "Weather", + description = "Modifies the client side weather", + tag = ModuleTag.RENDER ) { - private enum class Group(override val displayName: String) : NamedEnum { - Overworld("Overworld"), - Nether("Nether"), - End("End") - } + private enum class Group(override val displayName: String) : NamedEnum { + Overworld("Overworld"), + Nether("Nether"), + End("End") + } - @JvmStatic val overworldMode by setting("Overworld Mode", WeatherMode.Clear).group(Group.Overworld) - @JvmStatic val overrideSnow by setting("Override Snow", false) { overworldMode == WeatherMode.Rain }.group(Group.Overworld) - @JvmStatic val netherMode by setting("Nether Mode", WeatherMode.Clear).group(Group.Nether) - @JvmStatic val endMode by setting("End Mode", WeatherMode.Clear).group(Group.End) + @JvmStatic val overworldMode by setting("Overworld Mode", WeatherMode.Clear).group(Group.Overworld) + @JvmStatic val overrideSnow by setting("Override Snow", false) { overworldMode == WeatherMode.Rain }.group(Group.Overworld) + @JvmStatic val netherMode by setting("Nether Mode", WeatherMode.Clear).group(Group.Nether) + @JvmStatic val endMode by setting("End Mode", WeatherMode.Clear).group(Group.End) - @JvmStatic fun getWeatherMode() = - runSafe { - val dimension = world.registryKey - when (dimension) { - World.OVERWORLD -> overworldMode - World.NETHER -> netherMode - else -> endMode - } - } ?: WeatherMode.Clear + @JvmStatic fun getWeatherMode() = + runSafe { + val dimension = world.registryKey + when (dimension) { + World.OVERWORLD -> overworldMode + World.NETHER -> netherMode + else -> endMode + } + } ?: WeatherMode.Clear - enum class WeatherMode { - Clear, - Rain, - Thunder, - Snow - } + enum class WeatherMode { + Clear, + Rain, + Thunder, + Snow + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/module/modules/render/XRay.kt b/src/main/kotlin/com/lambda/module/modules/render/XRay.kt index 5fc7ed8d3..3ef5278b6 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/XRay.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/XRay.kt @@ -23,42 +23,44 @@ import net.minecraft.block.BlockState import net.minecraft.block.Blocks object XRay : Module( - name = "XRay", - description = "Allows you to see ores through walls", - tag = ModuleTag.RENDER, + name = "XRay", + description = "Allows you to see ores through walls", + tag = ModuleTag.RENDER, ) { - val defaultBlocks = setOf( - Blocks.COAL_ORE, Blocks.DEEPSLATE_COAL_ORE, - Blocks.IRON_ORE, Blocks.DEEPSLATE_IRON_ORE, - Blocks.GOLD_ORE, Blocks.DEEPSLATE_GOLD_ORE, - Blocks.LAPIS_ORE, Blocks.DEEPSLATE_LAPIS_ORE, - Blocks.REDSTONE_ORE, Blocks.DEEPSLATE_REDSTONE_ORE, - Blocks.DIAMOND_ORE, Blocks.DEEPSLATE_DIAMOND_ORE, - Blocks.EMERALD_ORE, Blocks.DEEPSLATE_EMERALD_ORE, - Blocks.COPPER_ORE, Blocks.DEEPSLATE_COPPER_ORE, - Blocks.NETHER_GOLD_ORE, Blocks.NETHER_QUARTZ_ORE, - Blocks.ANCIENT_DEBRIS - ) + val defaultBlocks = setOf( + Blocks.COAL_ORE, Blocks.DEEPSLATE_COAL_ORE, + Blocks.IRON_ORE, Blocks.DEEPSLATE_IRON_ORE, + Blocks.GOLD_ORE, Blocks.DEEPSLATE_GOLD_ORE, + Blocks.LAPIS_ORE, Blocks.DEEPSLATE_LAPIS_ORE, + Blocks.REDSTONE_ORE, Blocks.DEEPSLATE_REDSTONE_ORE, + Blocks.DIAMOND_ORE, Blocks.DEEPSLATE_DIAMOND_ORE, + Blocks.EMERALD_ORE, Blocks.DEEPSLATE_EMERALD_ORE, + Blocks.COPPER_ORE, Blocks.DEEPSLATE_COPPER_ORE, + Blocks.NETHER_GOLD_ORE, Blocks.NETHER_QUARTZ_ORE, + Blocks.ANCIENT_DEBRIS + ) - @JvmStatic - val opacity by setting("Opacity", 40, 0..100, 1, "Opacity of the non x-rayed blocks, (automatically overridden as 0 when running Sodium)") - .onValueChange { _, _ -> if (isEnabled) mc.worldRenderer.reload() } + @JvmStatic + val opacity by setting("Opacity", 40, 1..100, 1, "Opacity of the non x-rayed blocks, (automatically overridden as 0 when running Sodium)") + .onValueChange { _, _ -> if (isEnabled) mc.worldRenderer.reload() } + private val selection by setting("Block Selection", defaultBlocks, description = "Block selection that will be shown (whitelist) or hidden (blacklist)") + .onValueChange { _, _ -> if (isEnabled) mc.worldRenderer.reload() } - private val selection by setting("Block Selection", defaultBlocks, description = "Block selection that will be shown (whitelist) or hidden (blacklist)") - .onValueChange { _, _ -> if (isEnabled) mc.worldRenderer.reload() } - private val mode by setting("Selection Mode", Selection.Whitelist, "The mode of the block selection") + // ToDo: Blacklist causes huge performance issues due to many single faces being rendered + private val mode by setting("Selection Mode", Selection.Whitelist, "The mode of the block selection") + .onValueChange { _, _ -> if (isEnabled) mc.worldRenderer.reload() } - @JvmStatic - fun isSelected(blockState: BlockState) = mode.select(blockState) + @JvmStatic + fun isSelected(blockState: BlockState) = mode.select(blockState) - enum class Selection(val select: (BlockState) -> Boolean) { - Whitelist({ it.block in selection }), - Blacklist({ it.block !in selection }) - } + enum class Selection(val select: (BlockState) -> Boolean) { + Whitelist({ it.block in selection }), + Blacklist({ it.block !in selection }) + } - init { - onToggle { - mc.worldRenderer.reload() - } - } + init { + onToggle { + mc.worldRenderer.reload() + } + } } diff --git a/src/main/kotlin/com/lambda/module/modules/render/Zoom.kt b/src/main/kotlin/com/lambda/module/modules/render/Zoom.kt index 25e3cdc39..26b7a44e7 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Zoom.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Zoom.kt @@ -17,7 +17,7 @@ package com.lambda.module.modules.render -import com.lambda.event.events.MouseEvent +import com.lambda.event.events.ButtonEvent import com.lambda.event.events.RenderEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module @@ -26,73 +26,73 @@ import com.lambda.util.NamedEnum import java.lang.Math.clamp object Zoom : Module( - name = "Zoom", - description = "Zooms the current view", - tag = ModuleTag.RENDER, + name = "Zoom", + description = "Zooms the current view", + tag = ModuleTag.RENDER, ) { - private var zoom by setting("Zoom", 2f, 1f..10f, 0.1f) - private val style by setting("Style", ZoomStyle.EaseOut) - private val animationDuration by setting("Animation Duration", 200, 40..1500, 20, unit = "ms") { style != ZoomStyle.Instant } - private val disableDuration by setting("Disable Duration", 100, 0..1500, 20, unit = "ms") { style != ZoomStyle.Instant } - private val scroll by setting("Scroll", true) - private val persistentScroll by setting("Persistent Scroll", false) { scroll } - private val sensitivity by setting("Sensitivity", 0.2f, 0.1f..1f, 0.1f) { scroll } - @JvmStatic val smoothMovement by setting("Smooth Movement", false) + private var zoom by setting("Zoom", 2f, 1f..10f, 0.1f) + private val style by setting("Style", ZoomStyle.EaseOut) + private val animationDuration by setting("Animation Duration", 200, 40..1500, 20, unit = "ms") { style != ZoomStyle.Instant } + private val disableDuration by setting("Disable Duration", 100, 0..1500, 20, unit = "ms") { style != ZoomStyle.Instant } + private val scroll by setting("Scroll", true) + private val persistentScroll by setting("Persistent Scroll", false) { scroll } + private val sensitivity by setting("Sensitivity", 0.2f, 0.1f..1f, 0.1f) { scroll } + @JvmStatic val smoothMovement by setting("Smooth Movement", false) - private var extraZoom = 0f - set(value) { - field = value.coerceAtLeast(-zoom + 1) - } - @JvmStatic val targetZoom: Float - get() = zoom + extraZoom - private var currentZoom = 1f - @JvmStatic var lerpedZoom = 1f; private set - private var lastZoomTime = 1L - private val zoomProgress - get() = clamp((System.currentTimeMillis() - lastZoomTime) / (if (isEnabled) animationDuration else disableDuration).toDouble(), 0.0, 1.0).toFloat() + private var extraZoom = 0f + set(value) { + field = value.coerceAtLeast(-zoom + 1) + } + @JvmStatic val targetZoom: Float + get() = zoom + extraZoom + private var currentZoom = 1f + @JvmStatic var lerpedZoom = 1f; private set + private var lastZoomTime = 1L + private val zoomProgress + get() = clamp((System.currentTimeMillis() - lastZoomTime) / (if (isEnabled) animationDuration else disableDuration).toDouble(), 0.0, 1.0).toFloat() - init { - listen { event -> - val yDelta = event.delta.y.toFloat() - val delta = (yDelta * sensitivity) + (((zoom + extraZoom) * sensitivity) * yDelta) - if (persistentScroll) zoom += delta - else extraZoom += delta - updateZoomTime() - event.cancel() - } + init { + listen { event -> + val yDelta = event.delta.y.toFloat() + val delta = (yDelta * sensitivity) + (((zoom + extraZoom) * sensitivity) * yDelta) + if (persistentScroll) zoom += delta + else extraZoom += delta + updateZoomTime() + event.cancel() + } - listen(alwaysListen = true) { - updateCurrentZoom() - } + listen(alwaysListen = true) { + updateCurrentZoom() + } - onEnable { - updateZoomTime() - } + onEnable { + updateZoomTime() + } - onDisable { - extraZoom = 0f - updateZoomTime() - } - } + onDisable { + extraZoom = 0f + updateZoomTime() + } + } - private fun updateZoomTime() { - currentZoom = lerpedZoom - lastZoomTime = System.currentTimeMillis() - } + private fun updateZoomTime() { + currentZoom = lerpedZoom + lastZoomTime = System.currentTimeMillis() + } - @JvmStatic - fun updateCurrentZoom() { - val target = if (isEnabled) targetZoom else 1f - lerpedZoom = style.apply(currentZoom, target, zoomProgress) - if (lerpedZoom == targetZoom) lerpedZoom = targetZoom - } + @JvmStatic + fun updateCurrentZoom() { + val target = if (isEnabled) targetZoom else 1f + lerpedZoom = style.apply(currentZoom, target, zoomProgress) + if (lerpedZoom == targetZoom) lerpedZoom = targetZoom + } - private enum class ZoomStyle( - override val displayName: String, - val apply: (Float, Float, Float) -> Float, - ) : NamedEnum { - Instant("Instant", { _, v, _ -> v }), - EaseOut("Ease Out", { start, end, progress -> start + ((end - start) * (1f - ((1f - progress) * (1f - progress) * (1f - progress)))) }), - EaseIn("Ease In", { start, end, progress -> start + ((end - start) * (progress * progress * progress)) }) - } + private enum class ZoomStyle( + override val displayName: String, + val apply: (Float, Float, Float) -> Float, + ) : NamedEnum { + Instant("Instant", { _, v, _ -> v }), + EaseOut("Ease Out", { start, end, progress -> start + ((end - start) * (1f - ((1f - progress) * (1f - progress) * (1f - progress)))) }), + EaseIn("Ease In", { start, end, progress -> start + ((end - start) * (progress * progress * progress)) }) + } } diff --git a/src/main/kotlin/com/lambda/module/tag/ModuleTag.kt b/src/main/kotlin/com/lambda/module/tag/ModuleTag.kt index 6124b260c..499f56d07 100644 --- a/src/main/kotlin/com/lambda/module/tag/ModuleTag.kt +++ b/src/main/kotlin/com/lambda/module/tag/ModuleTag.kt @@ -40,12 +40,13 @@ data class ModuleTag(override val name: String) : Nameable { val MOVEMENT = ModuleTag("Movement") val RENDER = ModuleTag("Render") val PLAYER = ModuleTag("Player") + val CHAT = ModuleTag("Chat") val CLIENT = ModuleTag("Client") val NETWORK = ModuleTag("Network") val DEBUG = ModuleTag("Debug") val HUD = ModuleTag("Hud") - val defaults = setOf(COMBAT, MOVEMENT, RENDER, PLAYER, NETWORK, CLIENT, HUD) + val defaults = setOf(COMBAT, MOVEMENT, RENDER, PLAYER, NETWORK, CHAT, CLIENT, HUD) val shownTags = defaults.toMutableSet() diff --git a/src/main/kotlin/com/lambda/network/LambdaAPI.kt b/src/main/kotlin/com/lambda/network/LambdaAPI.kt index f86586403..7ff773e6b 100644 --- a/src/main/kotlin/com/lambda/network/LambdaAPI.kt +++ b/src/main/kotlin/com/lambda/network/LambdaAPI.kt @@ -36,6 +36,7 @@ import net.minecraft.client.network.ClientLoginNetworkHandler import net.minecraft.client.network.ServerAddress import net.minecraft.network.ClientConnection import net.minecraft.network.NetworkSide.CLIENTBOUND +import net.minecraft.network.NetworkingBackend import net.minecraft.network.packet.c2s.login.LoginHelloC2SPacket import net.minecraft.text.Text import java.math.BigInteger @@ -51,9 +52,7 @@ object LambdaAPI : Configurable(LambdaConfig) { val mappings get() = "$assets/mappings" // Folder containing mappings for our dynamic serializer val capes get() = "$assets/capes" // Folder containing all the capes, add .txt to get the list of available capes - - @Suppress("Deprecation") - const val GAME_VERSION = SharedConstants.VERSION_NAME + val gameVersion: String = SharedConstants.getGameVersion().name() private var hash: String? = null @@ -89,10 +88,11 @@ object LambdaAPI : Configurable(LambdaConfig) { val resolved = AllowedAddressResolver.DEFAULT.resolve(address) .map { it.inetSocketAddress }.getOrElse { return } - ClientConnection.connect(resolved, mc.options.shouldUseNativeTransport(), connection) + val backend = NetworkingBackend.remote(mc.options.shouldUseNativeTransport()) + ClientConnection.connect(resolved, backend, connection) .syncUninterruptibly() - val handler = ClientLoginNetworkHandler(connection, mc, null, null, false, null, { Text.empty() }, null) + val handler = ClientLoginNetworkHandler(connection, mc, null, null, false, null, { Text.empty() }, null, null) connection.connect(resolved.hostName, resolved.port, handler) connection.send(LoginHelloC2SPacket(mc.session.username, mc.session.uuidOrNull)) diff --git a/src/main/kotlin/com/lambda/task/tasks/AcquireMaterial.kt b/src/main/kotlin/com/lambda/task/tasks/AcquireMaterialTask.kt similarity index 69% rename from src/main/kotlin/com/lambda/task/tasks/AcquireMaterial.kt rename to src/main/kotlin/com/lambda/task/tasks/AcquireMaterialTask.kt index 60d70f2b4..95472a839 100644 --- a/src/main/kotlin/com/lambda/task/tasks/AcquireMaterial.kt +++ b/src/main/kotlin/com/lambda/task/tasks/AcquireMaterialTask.kt @@ -22,9 +22,11 @@ import com.lambda.context.SafeContext import com.lambda.interaction.material.StackSelection import com.lambda.interaction.material.container.ContainerManager import com.lambda.interaction.material.container.ContainerManager.findContainerWithMaterial +import com.lambda.interaction.material.container.containers.HotbarContainer import com.lambda.task.Task +import com.lambda.threading.runSafeAutomated -class AcquireMaterial @Ta5kBuilder constructor( +class AcquireMaterialTask @Ta5kBuilder constructor( val selection: StackSelection, automated: Automated ) : Task(), Automated by automated { @@ -32,17 +34,19 @@ class AcquireMaterial @Ta5kBuilder constructor( get() = "Acquiring $selection" override fun SafeContext.onStart() { - selection.findContainerWithMaterial() - ?.withdraw(selection) - ?.finally { - success(selection) - }?.execute(this@AcquireMaterial) - ?: failure(ContainerManager.NoContainerFound(selection)) // ToDo: Create crafting path + runSafeAutomated { + selection.findContainerWithMaterial() + ?.transferByTask(selection, HotbarContainer) + ?.finally { + success(selection) + }?.execute(this@AcquireMaterialTask) + ?: failure(ContainerManager.NoContainerFound(selection)) // ToDo: Create crafting path + } } companion object { @Ta5kBuilder fun Automated.acquire(selection: () -> StackSelection) = - AcquireMaterial(selection(), this) + AcquireMaterialTask(selection(), this) } } diff --git a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index fc0c24fc4..70ed2ec50 100644 --- a/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -22,6 +22,7 @@ import com.lambda.Lambda.LOG import com.lambda.config.AutomationConfig.Companion.DEFAULT import com.lambda.config.groups.EatConfig.Companion.reasonEating import com.lambda.context.Automated +import com.lambda.context.AutomatedSafeContext import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen @@ -54,9 +55,8 @@ import com.lambda.task.tasks.EatTask.Companion.eat import com.lambda.threading.runSafeAutomated import com.lambda.util.Formatting.format import com.lambda.util.extension.Structure -import com.lambda.util.extension.inventorySlots -import com.lambda.util.item.ItemUtils.block -import com.lambda.util.player.SlotUtils.hotbarAndStorage +import com.lambda.util.extension.playerSlots +import com.lambda.util.player.SlotUtils.hotbarAndInventoryStacks import net.minecraft.entity.ItemEntity import net.minecraft.util.math.BlockPos import java.util.concurrent.ConcurrentLinkedQueue @@ -107,7 +107,7 @@ class BuildTask private constructor( if (collectDrops()) return@listen - simulateAndProcess() + runSafeAutomated { simulateAndProcess() } } listen { @@ -118,24 +118,31 @@ class BuildTask private constructor( } } - private fun SafeContext.simulateAndProcess() { + private fun AutomatedSafeContext.simulateAndProcess() { val results = runSafeAutomated { blueprint.structure.simulate() } DEFAULT.drawables = results .filterIsInstance() .plus(pendingInteractions.toList()) - val resultsNotBlocked = results - .filter { result -> pendingInteractions.none { it.blockPos == result.pos } } + val viableResults = results + .filter { result -> + val finalResult = (result as? Dependent)?.lastDependency ?: result + pendingInteractions.none { it.blockPos == finalResult.pos } && + (finalResult !is Contextual || + when (finalResult) { + is BreakResult -> buildConfig.breakBlocks + else -> buildConfig.interactBlocks + }) + } .sorted() - val bestResult = resultsNotBlocked.firstOrNull() ?: return - handleResult(bestResult, resultsNotBlocked) + val bestResult = viableResults.firstOrNull() ?: return + handleResult(bestResult, viableResults) } - private fun SafeContext.handleResult(result: BuildResult, allResults: List) { - if (result !is Dependent && result !is Contextual && pendingInteractions.isNotEmpty()) - return + private fun AutomatedSafeContext.handleResult(result: BuildResult, allResults: List) { + if (result !is Dependent && result !is Contextual && pendingInteractions.isNotEmpty()) return when (result) { is PreSimResult.Done, @@ -186,7 +193,7 @@ class BuildTask private constructor( is Resolvable -> { LOG.info("Resolving: ${result.name}") - result.resolve()?.execute(this@BuildTask) + result.resolve() } } } @@ -203,9 +210,9 @@ class BuildTask private constructor( return@let true } - if (player.hotbarAndStorage.none { it.isEmpty }) { - val stackToThrow = player.currentScreenHandler.inventorySlots.firstOrNull { - it.stack.item.block in inventoryConfig.disposables + if (player.hotbarAndInventoryStacks.none { it.isEmpty }) { + val stackToThrow = player.currentScreenHandler.playerSlots.firstOrNull { + it.stack.item in inventoryConfig.disposables } ?: run { failure("No item in inventory to throw but inventory is full and cant pick up item drop") return@let true @@ -255,11 +262,10 @@ class BuildTask private constructor( fun Automated.breakAndCollectBlock( blockPos: BlockPos, finishOnDone: Boolean = true, - collectDrops: Boolean = true, lifeMaintenance: Boolean = false ) = BuildTask( blockPos.toStructure(TargetState.Air).toBlueprint(), - finishOnDone, collectDrops, lifeMaintenance, this + finishOnDone, true, lifeMaintenance, this ) } } diff --git a/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt new file mode 100644 index 000000000..4d92b7202 --- /dev/null +++ b/src/main/kotlin/com/lambda/task/tasks/ContainerTransferTask.kt @@ -0,0 +1,105 @@ +/* + * Copyright 2026 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.task.tasks + +import com.lambda.context.Automated +import com.lambda.event.EventFlow.post +import com.lambda.event.events.ContainerEvent +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest +import com.lambda.interaction.material.StackSelection +import com.lambda.interaction.material.container.ExternalContainer +import com.lambda.interaction.material.container.MaterialContainer +import com.lambda.interaction.material.container.containers.InventoryContainer +import com.lambda.task.Task +import com.lambda.threading.runSafeAutomated + +class ContainerTransferTask( + private var fromContainer: MaterialContainer, + private val toContainer: MaterialContainer, + private val stackSelection: StackSelection, + automated: Automated, + private val failIfNoMaterial: Boolean = false +) : Task(), Automated by automated { + override val name = "Transferring $stackSelection from $fromContainer to $toContainer" + + private var delegateTask: Task<*>? = null + + init { + listen { + if (delegateTask?.isCompleted == true) { + success() + return@listen + } + runSafeAutomated { + val fromExternal = fromContainer as? ExternalContainer + val slots = fromContainer.slots + val toSlots = toContainer.slots + fromExternal.takeIf { slots.isEmpty() }?.let { fromExternal -> + if (toContainer is ExternalContainer && toSlots.isEmpty()) { + fromContainer.transferByTask(stackSelection, InventoryContainer).finally { + fromContainer = InventoryContainer + }.execute(this@ContainerTransferTask) + return@listen + } + delegateTask = fromExternal.accessThen { + fromContainer.transferByTask(stackSelection, toContainer) + }?.execute(this@ContainerTransferTask) ?: run { + checkFail() + return@listen + } + return@listen + } + if (toContainer is ExternalContainer && toSlots.isEmpty()) { + delegateTask = toContainer.accessThen { + fromContainer.transferByTask(stackSelection, toContainer) + }?.execute(this@ContainerTransferTask) ?: run { + checkFail() + return@listen + } + return@listen + } + + fromContainer.getSlot(stackSelection)?.let { fromSlot -> + toContainer.getReplaceableSlot()?.let { toSlot -> + val transferEvent = ContainerEvent.Transfer(fromSlot, toSlot, fromContainer, toContainer) + if (transferEvent.post().isCanceled()) failure("Transfer prevented by an internal interruption") + inventoryRequest { + if (fromContainer.swapMethodPriority > toContainer.swapMethodPriority) + with(fromContainer) { transfer(fromSlot, toSlot) } + else with(toContainer) { transfer(toSlot, fromSlot) } + onComplete { success() } + }.submit() + return@listen + } + } + + checkFail() + } + } + } + + + private fun checkFail(): Boolean = + failIfNoMaterial.also { + failure(NoMaterialAccessException(stackSelection)) + } + + private class NoMaterialAccessException(stackSelection: StackSelection) : IllegalStateException("Unable to access $stackSelection.") +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt index b9e6c4f37..31c2deaf9 100644 --- a/src/main/kotlin/com/lambda/task/tasks/EatTask.kt +++ b/src/main/kotlin/com/lambda/task/tasks/EatTask.kt @@ -69,8 +69,9 @@ class EatTask @Ta5kBuilder constructor( mc.options.useKey.isPressed = false holdingUse = false } - foodFinder.transfer(MainHandContainer) - ?.execute(this@EatTask) ?: failure("No food found") + runSafeAutomated { + foodFinder.transfer(MainHandContainer) + } return@listen } eatStack = player.mainHandStack diff --git a/src/main/kotlin/com/lambda/task/tasks/OpenContainer.kt b/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt similarity index 78% rename from src/main/kotlin/com/lambda/task/tasks/OpenContainer.kt rename to src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt index 2492c38c4..734ae9d05 100644 --- a/src/main/kotlin/com/lambda/task/tasks/OpenContainer.kt +++ b/src/main/kotlin/com/lambda/task/tasks/OpenContainerTask.kt @@ -17,11 +17,13 @@ package com.lambda.task.tasks +import baritone.api.pathing.goals.GoalNear import com.lambda.context.Automated import com.lambda.event.events.InventoryEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.managers.rotating.RotationRequest +import com.lambda.interaction.BaritoneManager +import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest import com.lambda.interaction.managers.rotating.visibilty.lookAtBlock import com.lambda.task.Task import com.lambda.threading.runSafeAutomated @@ -31,7 +33,7 @@ import net.minecraft.util.Hand import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction -class OpenContainer @Ta5kBuilder constructor( +class OpenContainerTask @Ta5kBuilder constructor( private val blockPos: BlockPos, private val automated: Automated, private val waitForSlotLoad: Boolean = true, @@ -44,9 +46,10 @@ class OpenContainer @Ta5kBuilder constructor( private var inScope = 0 enum class State { - Scoping, Opening, SlotLoading; + Pathing, Scoping, Opening, SlotLoading; fun description(inScope: Int) = when (this) { + Pathing -> "Pathing closer" Scoping -> "Waiting for scope ($inScope)" Opening -> "Opening container" SlotLoading -> "Waiting for slots to load" @@ -79,10 +82,15 @@ class OpenContainer @Ta5kBuilder constructor( } listen { - if (containerState != State.Scoping) return@listen - - val checkedHit = runSafeAutomated { lookAtBlock(blockPos, sides) } ?: return@listen - if (interactConfig.rotate && !RotationRequest(checkedHit.rotation, this@OpenContainer).submit().done) return@listen + if (containerState != State.Scoping && containerState != State.Pathing) return@listen + + val checkedHit = runSafeAutomated { lookAtBlock(blockPos, sides) } + ?: run { + containerState = State.Pathing + if (!BaritoneManager.isActive) BaritoneManager.setGoalAndPath(GoalNear(blockPos, 3)) + return@listen + } + if (interactConfig.rotate && !rotationRequest { rotation(checkedHit.rotation) }.submit().done) return@listen interaction.interactBlock(player, Hand.MAIN_HAND, checkedHit.hit.blockResult ?: return@listen) diff --git a/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt b/src/main/kotlin/com/lambda/task/tasks/PlaceContainerTask.kt similarity index 88% rename from src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt rename to src/main/kotlin/com/lambda/task/tasks/PlaceContainerTask.kt index 2274eec30..5cef9c8fe 100644 --- a/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt +++ b/src/main/kotlin/com/lambda/task/tasks/PlaceContainerTask.kt @@ -36,15 +36,15 @@ import net.minecraft.block.ChestBlock import net.minecraft.entity.mob.ShulkerEntity import net.minecraft.item.ItemStack import net.minecraft.item.Items +import net.minecraft.screen.slot.Slot import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction -class PlaceContainer @Ta5kBuilder constructor( - val stack: ItemStack, +class PlaceContainerTask @Ta5kBuilder constructor( + val slot: Slot, automated: Automated ) : Task(), Automated by automated { - private val startStack: ItemStack = stack.copy() - override val name: String get() = "Placing container ${startStack.name.string}" + override val name: String get() = "Placing container ${slot.stack.name.string}" override fun SafeContext.onStart() { val results = runSafeAutomated { @@ -53,29 +53,29 @@ class PlaceContainer @Ta5kBuilder constructor( .asSequence() .filter { !ManagerUtils.isPosBlocked(it) } .flatMap { - it.toStructure(TargetState.Stack(startStack)) + it.toStructure(TargetState.Stack(slot.stack)) .simulate() } } val options = results.filterIsInstance().filter { - canBeOpened(startStack, it.pos, it.context.hitResult.side) + canBeOpened(slot.stack, it.pos, it.context.hitResult.side) } + results.filterIsInstance() val containerPosition = options.filter { // ToDo: Check based on if we can move the player close enough rather than y level once the custom pathfinder is merged it.pos.y == player.blockPos.y }.minByOrNull { it.pos distSq player.pos }?.pos ?: run { - failure("Couldn't find a valid container placement position for ${startStack.name.string}") + failure("Couldn't find a valid container placement position for ${slot.stack.name.string}") return@onStart } containerPosition - .toStructure(TargetState.Stack(startStack)) + .toStructure(TargetState.Stack(slot.stack)) .toBlueprint() .build(finishOnDone = true, collectDrops = false) .finally { success(containerPosition) } - .execute(this@PlaceContainer) + .execute(this@PlaceContainerTask) } private fun SafeContext.canBeOpened( diff --git a/src/main/kotlin/com/lambda/threading/Threading.kt b/src/main/kotlin/com/lambda/threading/Threading.kt index a23a200c6..b1020869c 100644 --- a/src/main/kotlin/com/lambda/threading/Threading.kt +++ b/src/main/kotlin/com/lambda/threading/Threading.kt @@ -106,7 +106,7 @@ inline fun runSafeConcurrent(crossinline block: suspend SafeContext.() -> Unit) * to OpenGL. */ inline fun recordRenderCall(crossinline block: () -> Unit) { - mc.renderTaskQueue.add { block() } + mc.execute { block() } } /** diff --git a/src/main/kotlin/com/lambda/util/BlockUtils.kt b/src/main/kotlin/com/lambda/util/BlockUtils.kt index 4d3cfdbf6..a857db754 100644 --- a/src/main/kotlin/com/lambda/util/BlockUtils.kt +++ b/src/main/kotlin/com/lambda/util/BlockUtils.kt @@ -53,11 +53,11 @@ import net.minecraft.block.DecoratedPotBlock import net.minecraft.block.DispenserBlock import net.minecraft.block.DoorBlock import net.minecraft.block.DragonEggBlock +import net.minecraft.block.DropperBlock import net.minecraft.block.EnchantingTableBlock import net.minecraft.block.EnderChestBlock import net.minecraft.block.FenceBlock import net.minecraft.block.FenceGateBlock -import net.minecraft.block.FletchingTableBlock import net.minecraft.block.FlowerPotBlock import net.minecraft.block.GrindstoneBlock import net.minecraft.block.HopperBlock @@ -227,13 +227,13 @@ object BlockUtils { DaylightDetectorBlock::class, DecoratedPotBlock::class, DispenserBlock::class, + DropperBlock::class, DoorBlock::class, DragonEggBlock::class, EnchantingTableBlock::class, EnderChestBlock::class, FenceBlock::class, FenceGateBlock::class, - FletchingTableBlock::class, FlowerPotBlock::class, GrindstoneBlock::class, HopperBlock::class, diff --git a/src/main/kotlin/com/lambda/util/ChatUtils.kt b/src/main/kotlin/com/lambda/util/ChatUtils.kt new file mode 100644 index 000000000..e07726e7c --- /dev/null +++ b/src/main/kotlin/com/lambda/util/ChatUtils.kt @@ -0,0 +1,174 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.util + +object ChatUtils { + val slurs = sequenceOf("\\bch[i1l]nks?\\b", "\\bc[o0]{2}ns?\\b", "f[a@4](g{1,2}|qq)([e3il1o0]t{1,2}(ry|r[i1l]e)?)?\\b", "\\bk[il1y]k[e3](ry|r[i1l]e)?s?\\b", "\\b(s[a4]nd)?n[ila4o10][gq]{1,2}(l[e3]t|[e3]r|[a4]|n[o0]g)?s?\\b", "\\btr[a4]n{1,2}([il1][e3]|y|[e3]r)s?\\b").map { Regex(it, RegexOption.IGNORE_CASE) } + val swears = sequenceOf("fuck(er)?", "shit", "cunt", "puss(ie|y)", "bitch", "twat").map { Regex(it, RegexOption.IGNORE_CASE) } + val sexual = sequenceOf("^cum[s\\$]?$", "cumm?[i1]ng", "h[o0]rny", "mast(e|ur)b(8|ait|ate)").map { Regex(it, RegexOption.IGNORE_CASE) } + val discord = sequenceOf("(http(s)?:\\/\\/)?(discord)?(\\.)?gg(\\/| ).\\S{1,25}", "(http(s)?:\\/\\/)?(discord)?(\\.)?com\\/invite(\\/| ).\\S{1,25}", "(dsc)?(\\.)?gg(\\/| ).\\S{1,25}").map { Regex(it, RegexOption.IGNORE_CASE) } + val addresses = sequenceOf("^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}(:\\d{1,5}$)?", "^(\\[?)(\\:\\:)?[0-9a-fA-F]{1,4}(\\:\\:?[0-9a-fA-F]{1,4}){0,7}(\\:\\:)?(\\])?(:\\d{1,5}$)?$").map { Regex(it, RegexOption.IGNORE_CASE) } + val hex = sequenceOf("\\s([A-Fa-f0-9]+){5,10}$").map { Regex(it) } + val colors = sequenceOf(">", "`").map { Regex(it) } + + val fancyToAscii = mapOf( + '!' to '!', + '"' to '"', + '#' to '#', + '$' to '$', + '%' to '%', + '&' to '&', + ''' to '\'', + '(' to '(', + ')' to ')', + '*' to '*', + '+' to '+', + ',' to ',', + '-' to '-', + '.' to '.', + '/' to '/', + '\' to '\\', + '|' to '|', + ':' to ':', + ';' to ';', + '<' to '<', + '=' to '=', + '>' to '>', + '?' to '?', + '@' to '@', + '[' to '[', + ']' to ']', + '{' to '{', + '}' to '}', + '~' to '~', + '^' to '^', + '_' to '_', + '`' to '`', + '0' to '0', + '1' to '1', + '2' to '2', + '3' to '3', + '4' to '4', + '5' to '5', + '6' to '6', + '7' to '7', + '8' to '8', + '9' to '9', + 'A' to 'A', + 'B' to 'B', + 'C' to 'C', + 'D' to 'D', + 'E' to 'E', + 'F' to 'F', + 'G' to 'G', + 'H' to 'H', + 'I' to 'I', + 'J' to 'J', + 'K' to 'K', + 'L' to 'L', + 'M' to 'M', + 'N' to 'N', + 'O' to 'O', + 'P' to 'P', + 'Q' to 'Q', + 'R' to 'R', + 'S' to 'S', + 'T' to 'T', + 'U' to 'U', + 'V' to 'V', + 'W' to 'W', + 'X' to 'X', + 'Y' to 'Y', + 'Z' to 'Z', + 'ᴀ' to 'a', + 'ʙ' to 'b', + 'c' to 'c', + 'ᴅ' to 'd', + 'ᴇ' to 'e', + 'ꜰ' to 'f', + 'ɢ' to 'g', + 'ʜ' to 'h', + 'ɪ' to 'i', + 'ᴊ' to 'j', + 'ᴋ' to 'k', + 'ʟ' to 'l', + 'ᴍ' to 'm', + 'ɴ' to 'n', + 'ᴏ' to 'o', + 'ᴩ' to 'p', + 'q' to 'q', + 'ʀ' to 'r', + 'ꜱ' to 's', + 'ᴛ' to 't', + 'ᴜ' to 'u', + 'ᴠ' to 'v', + 'ᴡ' to 'w', + 'x' to 'x', + 'y' to 'y', + 'ᴢ' to 'z', + 'a' to 'a', + 'b' to 'b', + 'c' to 'c', + 'd' to 'd', + 'e' to 'e', + 'f' to 'f', + 'g' to 'g', + 'h' to 'h', + 'i' to 'i', + 'j' to 'j', + 'k' to 'k', + 'l' to 'l', + 'm' to 'm', + 'n' to 'n', + 'o' to 'o', + 'p' to 'p', + 'q' to 'q', + 'r' to 'r', + 's' to 's', + 't' to 't', + 'u' to 'u', + 'v' to 'v', + 'w' to 'w', + 'x' to 'x', + 'y' to 'y', + 'z' to 'z', + ) + val asciiToFancy = fancyToAscii.entries.associate { (key, value) -> value to key } + val asciiToLeet = mapOf('a' to '4', 'e' to '3', 'g' to '6', 'l' to '1', 'i' to '1', 'o' to '0', 's' to '$', 't' to '7') + + val String.toFancy get() = buildString { this@toFancy.forEach { append(asciiToFancy.getOrDefault(it, it)) } } + val String.toAscii get() = buildString { this@toAscii.forEach { append(fancyToAscii.getOrDefault(it, it)) } } + val String.toLeet get() = buildString { this@toLeet.forEach { append(asciiToLeet.getOrDefault(it, it)) } } + val String.toGreen get() = ">$this" + val String.toBlue get() = "`$this" + + val String.toUwu get() = + replace("my", "mai") + .replace("friend", "fwend") + .replace("small", "smol") + .replace("cute", "cyute") + .replace("very", "vewy") + .replace("ove", "uv") + .replace("no", "nu") + .replace("you", "yew") + .replace("the", "da") + .replace("is", "ish") + .replace('r', 'w') + .replace("ve", "v") + .replace('l', 'w') +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/util/DynamicReflectionSerializer.kt b/src/main/kotlin/com/lambda/util/DynamicReflectionSerializer.kt index 9164bed03..f80564603 100644 --- a/src/main/kotlin/com/lambda/util/DynamicReflectionSerializer.kt +++ b/src/main/kotlin/com/lambda/util/DynamicReflectionSerializer.kt @@ -80,8 +80,8 @@ object DynamicReflectionSerializer : Loadable { private const val INDENT = 2 private val qualifiedMappings = runBlocking { - cache.resolveFile(LambdaAPI.GAME_VERSION) - .downloadIfNotPresent("${LambdaAPI.mappings}/${LambdaAPI.GAME_VERSION}") + cache.resolveFile(LambdaAPI.gameVersion) + .downloadIfNotPresent("${LambdaAPI.mappings}/${LambdaAPI.gameVersion}") .map(::buildMappingsMap) .getOrElse { LOG.error("Unable to download simplified deobfuscated qualifiers", it) diff --git a/src/main/kotlin/com/lambda/util/EnchantmentUtils.kt b/src/main/kotlin/com/lambda/util/EnchantmentUtils.kt index 4308ba7f2..c01e2570d 100644 --- a/src/main/kotlin/com/lambda/util/EnchantmentUtils.kt +++ b/src/main/kotlin/com/lambda/util/EnchantmentUtils.kt @@ -40,6 +40,9 @@ object EnchantmentUtils { fun ItemStack.getEnchantment(key: RegistryKey) = enchantments.enchantmentEntries.find { it.key?.matchesKey(key) == true }?.intValue ?: 0 + fun ItemStack.getEnchantment(entry: RegistryEntry) = + enchantments.enchantmentEntries.find { it == entry }?.intValue ?: 0 + fun ItemStack.forEachEnchantment(block: (RegistryEntry, Int) -> T) = enchantments.enchantmentEntries.map { block(it.key, it.intValue) } diff --git a/src/main/kotlin/com/lambda/util/InputUtils.kt b/src/main/kotlin/com/lambda/util/InputUtils.kt index ed97f8e3a..92d843683 100644 --- a/src/main/kotlin/com/lambda/util/InputUtils.kt +++ b/src/main/kotlin/com/lambda/util/InputUtils.kt @@ -21,8 +21,7 @@ import com.lambda.Lambda.mc import com.lambda.config.settings.complex.Bind import com.lambda.context.SafeContext import com.lambda.core.Loadable -import com.lambda.event.events.KeyboardEvent -import com.lambda.event.events.MouseEvent +import com.lambda.event.events.ButtonEvent import it.unimi.dsi.fastutil.ints.Int2IntArrayMap import org.lwjgl.glfw.GLFW import org.lwjgl.glfw.GLFW.GLFW_KEY_LEFT_ALT @@ -53,20 +52,13 @@ object InputUtils : Loadable { * Note: This function is extremely expensive to execute, it is recommended to not use * it unless you absolutely need to. Additionally, you might screw with the key cache. */ - fun newKeyboardEvent(): KeyboardEvent.Press? { + fun newKeyboardEvent(): ButtonEvent.Keyboard.Press? { val pressedKeys = keys .associateWith { glfwGetKey(mc.window.handle, it) } .filter { (key, state) -> state >= GLFW_PRESS || lastPressedKeys[key] >= GLFW_PRESS } .also { lastPressedKeys.clear() } .onEach { (key, state) -> lastPressedKeys[key] = state } - // FixMe: If you are pressing two or more keys considered 'modifier' keys, you must release both of them at the - // same time in order to receive an update stipulating that the last key (not actually a modifier) was released alongside its modifiers. - // For the time being, I will allow this as players can still bind unique 'modifier' keys with no issues. - - // FixMe: The order in which modifier keys are ordered is wrong. When a user presses Left Control + Left Alt, the user must - // press Left Alt + Left Control as the modifier key in order for the event to satisfies the bind - val mods = pressedKeys.keys .filter { it in GLFW_KEY_LEFT_SHIFT..GLFW_KEY_RIGHT_SUPER && lastPressedKeys.keys.firstOrNull()?.equals(it) == false } .foldRight(0) { v, acc -> acc or modMap.getValue(v) } @@ -76,13 +68,13 @@ object InputUtils : Loadable { val scancode = scancodes.getOrElse(key.first) { 0 } - return KeyboardEvent.Press(key.first, scancode, key.second, mods) + return ButtonEvent.Keyboard.Press(key.first, scancode, key.second, mods) } /** * Creates a new mouse event from the current glfw states. */ - fun newMouseEvent(): MouseEvent.Click? { + fun newMouseEvent(): ButtonEvent.Mouse.Click? { val mods = (GLFW_KEY_LEFT_SHIFT..GLFW_KEY_RIGHT_SUPER) .filter { glfwGetKey(mc.window.handle, it) >= GLFW_PRESS } .foldRight(0) { v, acc -> acc or modMap.getValue(v) } @@ -91,15 +83,13 @@ object InputUtils : Loadable { .firstOrNull { glfwGetMouseButton(mc.window.handle, it) == GLFW_PRESS } ?: return null - return MouseEvent.Click(mouse, GLFW_PRESS, mods) + return ButtonEvent.Mouse.Click(mouse, GLFW_PRESS, mods) } fun Bind.isSatisfied(): Boolean = (key == -1 || glfwGetKey(mc.window.handle, key).pressedOrRepeated) && (mouse == -1 || glfwGetMouseButton(mc.window.handle, mouse).pressedOrRepeated) && - truemods.all { - glfwGetKey(mc.window.handle, it.code).pressedOrRepeated - } + truemods.all { glfwGetKey(mc.window.handle, it.code).pressedOrRepeated } private val Int.pressedOrRepeated get() = this == 1 || this == 2 diff --git a/src/main/kotlin/com/lambda/util/extension/Screen.kt b/src/main/kotlin/com/lambda/util/extension/Screen.kt index 8fda215ea..1b6546832 100644 --- a/src/main/kotlin/com/lambda/util/extension/Screen.kt +++ b/src/main/kotlin/com/lambda/util/extension/Screen.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 Lambda + * Copyright 2026 Lambda * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,8 +25,8 @@ import net.minecraft.screen.ScreenHandler import net.minecraft.screen.slot.Slot val ScreenHandler.containerSlots: List get() = slots.filter { it.inventory is SimpleInventory } -val ScreenHandler.inventorySlots: List get() = slots.filter { it.inventory is PlayerInventory } +val ScreenHandler.playerSlots: List get() = slots.filter { it.inventory is PlayerInventory } val ScreenHandler.craftingSlots: List get() = slots.filter { it.inventory is CraftingInventory } val ScreenHandler.containerStacks: List get() = containerSlots.map { it.stack } -val ScreenHandler.inventoryStacks: List get() = inventorySlots.map { it.stack } +val ScreenHandler.playerStacks: List get() = playerSlots.map { it.stack } diff --git a/src/main/kotlin/com/lambda/util/extension/World.kt b/src/main/kotlin/com/lambda/util/extension/World.kt index b053dd8a6..a1b2f5124 100644 --- a/src/main/kotlin/com/lambda/util/extension/World.kt +++ b/src/main/kotlin/com/lambda/util/extension/World.kt @@ -53,8 +53,15 @@ fun SafeContext.collisionShape(state: BlockState, pos: BlockPos): VoxelShape = fun SafeContext.outlineShape(state: BlockState, pos: BlockPos) = state.getOutlineShape(world, pos).offset(pos) -fun SafeContext.blockColor(state: BlockState, pos: BlockPos) = - Color(state.getMapColor(world, pos).color) +fun SafeContext.blockColor(state: BlockState, pos: BlockPos): Color { + return when (state.block) { + Blocks.ENDER_CHEST -> Color(0xFF00FF) + Blocks.NETHER_PORTAL -> Color(0xaa00aa) + Blocks.END_PORTAL -> Color(0xFF00FF) + else -> + Color(state.getMapColor(world, pos).color, false) + } +} fun World.getBlockState(x: Int, y: Int, z: Int): BlockState { if (isOutOfHeightLimit(y)) return Blocks.VOID_AIR.defaultState diff --git a/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt b/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt index 0e6efa63c..df5745f2b 100644 --- a/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt +++ b/src/main/kotlin/com/lambda/util/item/ItemStackUtils.kt @@ -28,6 +28,7 @@ import net.minecraft.entity.attribute.EntityAttributeModifier import net.minecraft.entity.attribute.EntityAttributes import net.minecraft.item.ItemStack import net.minecraft.registry.entry.RegistryEntry +import net.minecraft.screen.slot.Slot object ItemStackUtils { // FixMe: Change this fucking retarded stuff when mojang wake up from their coma and realize they fucked this shit up @@ -50,7 +51,6 @@ object ItemStackUtils { } } - /** * Returns the attack damage for the given [stack], the value is affected by potion effects and enchantments */ @@ -75,12 +75,15 @@ object ItemStackUtils { val ItemStack.spaceLeft get() = maxCount - count val ItemStack.hasSpace get() = spaceLeft > 0 val List.spaceLeft get() = sumOf { it.spaceLeft } + val List.spaceLeft @JvmName("slotSpaceLeft") get() = sumOf { it.stack.spaceLeft } val List.empty: Int get() = count { it.isEmpty } + val List.empty: Int @JvmName("slotEmpty") get() = count { it.stack.isEmpty } val List.count: Int get() = if (isEmpty()) -1 else sumOf { it.count } + val List.count: Int @JvmName("slotCount") get() = if (isEmpty()) -1 else sumOf { it.stack.count } val List.copy: List get() = map { it.copy() } context(safeContext: SafeContext) - val ItemStack.slotId get() = safeContext.player.currentScreenHandler.slots.find { it.stack.equal(this) }?.id ?: -1 + val ItemStack.slotId get() = safeContext.player.currentScreenHandler.slots.find { it.stack === this }?.id ?: -1 context(safeContext: SafeContext) val ItemStack.inventoryIndex get() = safeContext.player.inventory.getSlotWithStack(this) context(safeContext: SafeContext) diff --git a/src/main/kotlin/com/lambda/util/item/ItemUtils.kt b/src/main/kotlin/com/lambda/util/item/ItemUtils.kt index c91b3776a..1d60c10ec 100644 --- a/src/main/kotlin/com/lambda/util/item/ItemUtils.kt +++ b/src/main/kotlin/com/lambda/util/item/ItemUtils.kt @@ -18,7 +18,6 @@ package com.lambda.util.item import net.minecraft.block.Block -import net.minecraft.block.Blocks import net.minecraft.component.DataComponentTypes import net.minecraft.item.BlockItem import net.minecraft.item.Item @@ -106,24 +105,24 @@ object ItemUtils { ) val defaultDisposables = setOf( - Blocks.DIRT, - Blocks.GRASS_BLOCK, - Blocks.COBBLESTONE, - Blocks.GRANITE, - Blocks.DIORITE, - Blocks.ANDESITE, - Blocks.SANDSTONE, - Blocks.RED_SANDSTONE, - Blocks.NETHERRACK, - Blocks.END_STONE, - Blocks.STONE, - Blocks.BASALT, - Blocks.BLACKSTONE, - Blocks.COBBLED_DEEPSLATE + Items.DIRT, + Items.GRASS_BLOCK, + Items.COBBLESTONE, + Items.GRANITE, + Items.DIORITE, + Items.ANDESITE, + Items.SANDSTONE, + Items.RED_SANDSTONE, + Items.NETHERRACK, + Items.END_STONE, + Items.STONE, + Items.BASALT, + Items.BLACKSTONE, + Items.COBBLED_DEEPSLATE ) val Item.block: Block get() = Block.getBlockFromItem(this) - val ItemStack.blockItem: BlockItem get() = (item as? BlockItem ?: Items.AIR) as BlockItem + val ItemStack.blockItem get() = item as? BlockItem val Item.nutrition: Int get() = components.get(DataComponentTypes.FOOD)?.nutrition ?: 0 diff --git a/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt b/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt index 3913cad33..aaf13a7ea 100644 --- a/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt +++ b/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt @@ -29,11 +29,13 @@ import net.minecraft.network.packet.c2s.play.HandSwingC2SPacket import net.minecraft.util.Hand import net.minecraft.world.GameMode +const val FakePlayerId = -2024-4-20 + val SafeContext.gamemode: GameMode get() = interaction.currentGameMode fun SafeContext.copyPlayer(entity: ClientPlayerEntity) = - ClientPlayerEntity(mc, world, mc.networkHandler, null, null, entity.isSneaking, entity.isSprinting).apply { + ClientPlayerEntity(mc, world, mc.networkHandler, null, null, entity.lastPlayerInput, entity.isSprinting).apply { setPos(entity.x, entity.y, entity.z) setExperience(entity.experienceProgress, entity.totalExperience, entity.experienceLevel) health = entity.health @@ -59,7 +61,7 @@ fun SafeContext.spawnFakePlayer( copyFrom(reference) playerListEntry = PlayerListEntry(profile, false) - id = -2024 - 4 - 20 + id = FakePlayerId } if (addToWorld) world.addEntity(entity) diff --git a/src/main/kotlin/com/lambda/util/player/SlotUtils.kt b/src/main/kotlin/com/lambda/util/player/SlotUtils.kt index d20786aaa..71815f3d5 100644 --- a/src/main/kotlin/com/lambda/util/player/SlotUtils.kt +++ b/src/main/kotlin/com/lambda/util/player/SlotUtils.kt @@ -18,16 +18,37 @@ package com.lambda.util.player import com.lambda.context.SafeContext +import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen import net.minecraft.client.network.ClientPlayerEntity +import net.minecraft.entity.player.PlayerInventory import net.minecraft.item.ItemStack +import net.minecraft.screen.slot.ArmorSlot +import net.minecraft.screen.slot.Slot import net.minecraft.screen.slot.SlotActionType object SlotUtils { - val ClientPlayerEntity.hotbar: List get() = inventory.mainStacks.slice(0..8) - val ClientPlayerEntity.storage: List get() = inventory.mainStacks.slice(9..35) - val ClientPlayerEntity.equipment: List get() = inventory.equipment.map.map { it.value } - val ClientPlayerEntity.hotbarAndStorage: List get() = inventory.mainStacks - val ClientPlayerEntity.combined: List get() = hotbarAndStorage + equipment + val ClientPlayerEntity.allSlots get() = currentScreenHandler.slots.filter { it.inventory is PlayerInventory } + val ClientPlayerEntity.armorSlots get() = allSlots.filterIsInstance() + val ClientPlayerEntity.inventorySlots: List get() = + if (currentScreenHandler is CreativeInventoryScreen.CreativeScreenHandler) emptyList() + else if (currentScreenHandler === playerScreenHandler) allSlots.subList(4, 31) + else allSlots.subList(0, 27) + val ClientPlayerEntity.hotbarSlots: List get() = + if (currentScreenHandler is CreativeInventoryScreen.CreativeScreenHandler) allSlots + else if (currentScreenHandler === playerScreenHandler) allSlots.subList(31, 40) + else allSlots.subList(27, 36) + val ClientPlayerEntity.hotbarAndInventorySlots: List get() = + if (currentScreenHandler is CreativeInventoryScreen.CreativeScreenHandler) allSlots + else if (currentScreenHandler === playerScreenHandler) allSlots.subList(4, 40) + else allSlots.subList(0, 36) + val ClientPlayerEntity.mainHandSlots: List get() = listOf(hotbarSlots[inventory.selectedSlot]) + val ClientPlayerEntity.offHandSlots: List get() = if (currentScreenHandler === playerScreenHandler) listOf(allSlots.last()) else emptyList() + + val ClientPlayerEntity.allStacks: List get() = hotbarAndInventoryStacks + equipmentStacks + val ClientPlayerEntity.equipmentStacks: List get() = inventory.equipment.map.map { it.value } + val ClientPlayerEntity.hotbarStacks: List get() = inventory.mainStacks.slice(0..8) + val ClientPlayerEntity.inventoryStacks: List get() = inventory.mainStacks.slice(9..35) + val ClientPlayerEntity.hotbarAndInventoryStacks: List get() = inventory.mainStacks fun SafeContext.clickSlot(slotId: Int, button: Int, actionType: SlotActionType) { val syncId = player.currentScreenHandler?.syncId ?: return diff --git a/src/main/kotlin/com/lambda/util/text/Detections.kt b/src/main/kotlin/com/lambda/util/text/Detections.kt new file mode 100644 index 000000000..4d9bab24f --- /dev/null +++ b/src/main/kotlin/com/lambda/util/text/Detections.kt @@ -0,0 +1,95 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.util.text + +import com.lambda.Lambda.mc + +val playerRegex = "^<(.+)>".toRegex() + +interface Detector { + fun matches(input: CharSequence): Boolean +} + +interface RemovableDetector { + fun removedOrNull(input: CharSequence): CharSequence? +} + +interface PlayerDetector { + fun playerName(input: CharSequence): String? +} + +interface RegexDetector : Detector, RemovableDetector { + val regexes: Array + + fun result(input: CharSequence) = regexes.find { it.containsMatchIn(input) } + + override fun matches(input: CharSequence) = regexes.any { it.containsMatchIn(input) } + + override fun removedOrNull(input: CharSequence) = + result(input) + ?.replace(input, "") + ?.takeIf { it.isNotBlank() } +} + +object MessageParser : PlayerDetector, RemovableDetector { + override fun playerName(input: CharSequence) = + playerRegex.find(input)?.value?.drop(1)?.dropLast(1) + + override fun removedOrNull(input: CharSequence) = + input.replace(playerRegex, "") +} + + +enum class MessageType : Detector, PlayerDetector, RemovableDetector { + Self { + override fun matches(input: CharSequence) = + input.startsWith("<${mc.gameProfile.name}>") + + override fun playerName(input: CharSequence): String? = + mc.gameProfile.name + }, + Others { + override fun matches(input: CharSequence) = playerName(input) != null + + override fun playerName(input: CharSequence) = + playerRegex.find(input)?.groupValues?.getOrNull(1)?.takeIf { it.isNotBlank() && it != name } + }, + Both { + override fun matches(input: CharSequence) = input.contains(playerRegex) + + override fun playerName(input: CharSequence) = + playerRegex.find(input)?.groupValues?.getOrNull(1)?.takeIf { it.isNotBlank() } + }; + + override fun removedOrNull(input: CharSequence) = + playerName(input)?.let { input.removePrefix("<$it>") } +} + +enum class DirectMessage(override vararg val regexes: Regex) : RegexDetector, PlayerDetector { + Send("^To (.+?): ".toRegex(RegexOption.IGNORE_CASE)), + Receive( + "^(.+?) whispers( to you)?: ".toRegex(), + "^\\[?(.+?)( )?->( )?.+?]?( )?:? ".toRegex(), + "^From (.+?): ".toRegex(RegexOption.IGNORE_CASE), + "^. (.+?) » .w+? » ".toRegex() + ), + Both(*Send.regexes, *Receive.regexes); + + override fun playerName(input: CharSequence) = + result(input)?.find(input)?.groupValues?.getOrNull(1)?.takeIf { it.isNotBlank() } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/util/text/StyleDsl.kt b/src/main/kotlin/com/lambda/util/text/StyleDsl.kt index 91fa10f20..7c1027e98 100644 --- a/src/main/kotlin/com/lambda/util/text/StyleDsl.kt +++ b/src/main/kotlin/com/lambda/util/text/StyleDsl.kt @@ -23,8 +23,8 @@ import net.minecraft.text.ClickEvent import net.minecraft.text.HoverEvent import net.minecraft.text.MutableText import net.minecraft.text.Style +import net.minecraft.text.StyleSpriteSource import net.minecraft.text.TextColor -import net.minecraft.util.Identifier import java.awt.Color /** @@ -149,9 +149,9 @@ class StyleBuilder { } /** - * An [Identifier] for the Minecraft font that would like to be used. + * A [StyleSpriteSource] for the Minecraft font that would like to be used. */ - var font: Identifier? = null + var font: StyleSpriteSource? = null set(value) { if (field != value) { cachedStyle = null diff --git a/src/main/kotlin/com/lambda/util/text/TextDsl.kt b/src/main/kotlin/com/lambda/util/text/TextDsl.kt index cbf885c13..6a8f3892e 100644 --- a/src/main/kotlin/com/lambda/util/text/TextDsl.kt +++ b/src/main/kotlin/com/lambda/util/text/TextDsl.kt @@ -23,8 +23,8 @@ import net.minecraft.text.HoverEvent import net.minecraft.text.MutableText import net.minecraft.text.NbtDataSource import net.minecraft.text.Style +import net.minecraft.text.StyleSpriteSource import net.minecraft.text.Text -import net.minecraft.util.Identifier import java.awt.Color import java.util.* @@ -296,7 +296,7 @@ inline fun TextBuilder.insertion(insertion: String?, action: TextBuilder.() -> U * Applies the [TextBuilder] [action] with [font] set to the provided value. */ @TextDsl -inline fun TextBuilder.font(font: Identifier?, action: TextBuilder.() -> Unit) { +inline fun TextBuilder.font(font: StyleSpriteSource?, action: TextBuilder.() -> Unit) { withProp(font, { this.font }, { this.font = it }, action) } @@ -314,7 +314,7 @@ fun TextBuilder.styled( clickEvent: ClickEvent? = style.clickEvent, hoverEvent: HoverEvent? = style.hoverEvent, insertion: String? = style.insertion, - font: Identifier? = style.font, + font: StyleSpriteSource? = style.font, action: TextBuilder.() -> Unit, ) { color(color) { diff --git a/src/main/resources/assets/lambda/shaders/core/advanced_lines.fsh b/src/main/resources/assets/lambda/shaders/core/advanced_lines.fsh new file mode 100644 index 000000000..7727eca9d --- /dev/null +++ b/src/main/resources/assets/lambda/shaders/core/advanced_lines.fsh @@ -0,0 +1,50 @@ +#version 330 + +#moj_import +#moj_import + +in vec4 vertexColor; +noperspective in float v_LineDist; +noperspective in float v_LineWidth; +noperspective in vec2 v_DistPixels; +noperspective in float v_LineLength; +in float sphericalVertexDistance; +in float cylindricalVertexDistance; + +out vec4 fragColor; + +void main() { + // Closest point on the center line segment [0, L] + float closestX = clamp(v_DistPixels.x, 0.0, v_LineLength); + vec2 closestPoint = vec2(closestX, 0.0); + + // Pixel distance from the closest point (Round Capsule SDF) + float dist = length(v_DistPixels - closestPoint); + + // SDF value: distance from the capsule edge + float sdf = dist - (v_LineWidth / 2.0); + + // Ultra-sharp edges (AA transition of 0.3 pixels total) + float alpha; + if (v_LineWidth >= 1.0) { + alpha = smoothstep(0.15, -0.15, sdf); + } else { + // Super thin lines: reduce opacity instead of shrinking width + float transverseAlpha = (1.0 - smoothstep(0.0, 1.0, abs(v_DistPixels.y))) * v_LineWidth; + alpha = transverseAlpha; + } + + // Aggressive fade for tiny segments far away to prevent blobbing + // If a segment is less than 0.8px on screen, fade it out to nothing + float lengthFade = clamp(v_LineLength / 0.8, 0.0, 1.0); + alpha *= lengthFade * lengthFade; // Quadratic falloff for tiny segments + + if (alpha <= 0.0) { + discard; + } + + vec4 color = vertexColor * ColorModulator; + color.a *= alpha; + + fragColor = apply_fog(color, sphericalVertexDistance, cylindricalVertexDistance, FogEnvironmentalStart, FogEnvironmentalEnd, FogRenderDistanceStart, FogRenderDistanceEnd, FogColor); +} diff --git a/src/main/resources/assets/lambda/shaders/core/advanced_lines.vsh b/src/main/resources/assets/lambda/shaders/core/advanced_lines.vsh new file mode 100644 index 000000000..46e84da2a --- /dev/null +++ b/src/main/resources/assets/lambda/shaders/core/advanced_lines.vsh @@ -0,0 +1,74 @@ +#version 330 + +#moj_import +#moj_import +#moj_import +#moj_import + +in vec3 Position; +in vec4 Color; +in vec3 Normal; +in float LineWidth; + +out vec4 vertexColor; +noperspective out float v_LineDist; +noperspective out float v_LineWidth; +noperspective out vec2 v_DistPixels; +noperspective out float v_LineLength; +out float sphericalVertexDistance; +out float cylindricalVertexDistance; + +const float VIEW_SHRINK = 1.0 - (1.0 / 256.0); + +void main() { + int vertexIndex = gl_VertexID % 4; + bool isStart = (vertexIndex < 2); + + float actualWidth = max(LineWidth, 0.1); + float padding = 0.5; // AA padding + float halfWidthExtended = actualWidth / 2.0 + padding; + + // Transform start and end + vec4 posStart = ProjMat * ModelViewMat * vec4(isStart ? Position : Position - Normal, 1.0); + vec4 posEnd = ProjMat * ModelViewMat * vec4(isStart ? Position + Normal : Position, 1.0); + + vec3 ndcStart = posStart.xyz / posStart.w; + vec3 ndcEnd = posEnd.xyz / posEnd.w; + + // Screen space coordinates + vec2 screenStart = (ndcStart.xy * 0.5 + 0.5) * ScreenSize; + vec2 screenEnd = (ndcEnd.xy * 0.5 + 0.5) * ScreenSize; + + vec2 delta = screenEnd - screenStart; + float lenPixels = length(delta); + + // Stable direction + vec2 lineDir = (lenPixels > 0.001) ? delta / lenPixels : vec2(1.0, 0.0); + vec2 lineNormal = vec2(-lineDir.y, lineDir.x); + + // Quad vertex layout + float side = (vertexIndex == 0 || vertexIndex == 3) ? -1.0 : 1.0; + float longitudinalSide = isStart ? -1.0 : 1.0; + + // Expansion in pixels: full radius + padding to contain capsule end + vec2 offsetPixels = lineNormal * side * halfWidthExtended + lineDir * longitudinalSide * halfWidthExtended; + + // Current point NDC + vec3 ndcThis = isStart ? ndcStart : ndcEnd; + float wThis = isStart ? posStart.w : posEnd.w; + + // Convert pixel offset back to NDC + vec2 offsetNDC = (offsetPixels / ScreenSize) * 2.0; + gl_Position = vec4((ndcThis + vec3(offsetNDC, 0.0)) * wThis, wThis); + + vertexColor = Color; + + // Pass coordinates for SDF + v_LineDist = side; + v_DistPixels = vec2(isStart ? -halfWidthExtended : lenPixels + halfWidthExtended, side * halfWidthExtended); + v_LineWidth = actualWidth; + v_LineLength = lenPixels; + + sphericalVertexDistance = fog_spherical_distance(Position); + cylindricalVertexDistance = fog_cylindrical_distance(Position); +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 50e5c4edf..d04b387dc 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -21,7 +21,7 @@ ] }, "mixins": [ - "${modId}.mixins.common.json" + "${modId}.mixins.json" ], "accessWidener": "${modId}.accesswidener", "depends": { @@ -29,7 +29,9 @@ "minecraft": "~$minecraftVersion", "fabric-api": ">=$fabricApiVersion+$minecraftVersion", "fabric-language-kotlin": ">=$kotlinFabricVersion", - "java": ">=$javaVersion", + "java": ">=$javaVersion" + }, + "recommends": { "baritone": "*" } } diff --git a/src/main/resources/lambda.accesswidener b/src/main/resources/lambda.accesswidener index 1914bc8c9..484d80718 100644 --- a/src/main/resources/lambda.accesswidener +++ b/src/main/resources/lambda.accesswidener @@ -6,7 +6,7 @@ transitive-accessible field net/minecraft/client/MinecraftClient paused Z transitive-accessible field net/minecraft/client/MinecraftClient thread Ljava/lang/Thread; transitive-accessible field net/minecraft/client/MinecraftClient uptimeInTicks J transitive-accessible field net/minecraft/client/input/Input movementVector Lnet/minecraft/util/math/Vec2f; -transitive-accessible field net/minecraft/client/MinecraftClient renderTaskQueue Ljava/util/Queue; +# REMOVED in 1.21.11: renderTaskQueue - use mc.execute {} instead (inherited from ReentrantThreadExecutor) transitive-accessible field net/minecraft/client/option/KeyBinding boundKey Lnet/minecraft/client/util/InputUtil$Key; transitive-accessible method net/minecraft/client/MinecraftClient getWindowTitle ()Ljava/lang/String; transitive-accessible field net/minecraft/client/option/KeyBinding KEYS_BY_ID Ljava/util/Map; @@ -26,13 +26,19 @@ transitive-accessible method net/minecraft/item/BlockItem placeFromNbt (Lnet/min transitive-accessible method net/minecraft/item/BlockItem postPlacement (Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/world/World;Lnet/minecraft/entity/player/PlayerEntity;Lnet/minecraft/item/ItemStack;Lnet/minecraft/block/BlockState;)Z transitive-accessible method net/minecraft/item/BlockItem getPlaceSound (Lnet/minecraft/block/BlockState;)Lnet/minecraft/sound/SoundEvent; transitive-accessible field net/minecraft/state/State owner Ljava/lang/Object; -transitive-accessible class net/minecraft/client/render/BackgroundRenderer$StatusEffectFogModifier +# MOVED in 1.21.11: BackgroundRenderer$StatusEffectFogModifier -> net/minecraft/client/render/fog/StatusEffectFogModifier +transitive-accessible class net/minecraft/client/render/fog/StatusEffectFogModifier +transitive-accessible field net/minecraft/client/particle/NoRenderParticleRenderer EMPTY Lnet/minecraft/client/render/Submittable; +transitive-accessible field net/minecraft/entity/Entity GLIDING_FLAG_INDEX I +transitive-accessible method net/minecraft/entity/Entity getFlag (I)Z +transitive-accessible method net/minecraft/block/ChestBlock getChestType (Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/math/Direction;)Lnet/minecraft/block/enums/ChestType; +transitive-accessible method net/minecraft/block/ChestBlock getNeighborChestDirection (Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/math/Direction;)Lnet/minecraft/util/math/Direction; # Entity transitive-accessible field net/minecraft/entity/projectile/FireworkRocketEntity shooter Lnet/minecraft/entity/LivingEntity; transitive-accessible method net/minecraft/entity/Entity movementInputToVelocity (Lnet/minecraft/util/math/Vec3d;FF)Lnet/minecraft/util/math/Vec3d; transitive-accessible method net/minecraft/entity/passive/AbstractHorseEntity setHorseFlag (IZ)V -transitive-accessible field net/minecraft/entity/LivingEntity lastAttackedTicks I +transitive-accessible field net/minecraft/entity/LivingEntity lastAttackedTime I transitive-accessible method net/minecraft/entity/Entity setFlag (IZ)V transitive-accessible field net/minecraft/client/network/AbstractClientPlayerEntity playerListEntry Lnet/minecraft/client/network/PlayerListEntry; transitive-accessible field net/minecraft/entity/LivingEntity jumpingCooldown I @@ -50,10 +56,15 @@ transitive-accessible method net/minecraft/util/math/Direction listClosest (Lnet transitive-accessible field net/minecraft/client/network/ClientPlayerInteractionManager gameMode Lnet/minecraft/world/GameMode; transitive-accessible method net/minecraft/entity/player/PlayerEntity updatePose ()V transitive-accessible field net/minecraft/screen/ScreenHandler revision I +transitive-accessible field net/minecraft/entity/Entity world Lnet/minecraft/world/World; +transitive-accessible class net/minecraft/screen/slot/ArmorSlot +transitive-accessible field net/minecraft/screen/slot/ArmorSlot equipmentSlot Lnet/minecraft/entity/EquipmentSlot; +transitive-accessible field net/minecraft/entity/Entity FLAGS Lnet/minecraft/entity/data/TrackedData; # Camera transitive-accessible method net/minecraft/client/render/Camera setPos (DDD)V transitive-accessible method net/minecraft/client/render/Camera setRotation (FF)V +transitive-accessible field net/minecraft/client/render/Camera pos Lnet/minecraft/util/math/Vec3d; # Renderer transitive-accessible field net/minecraft/client/texture/NativeImage pointer J @@ -71,8 +82,10 @@ transitive-accessible field net/minecraft/text/Style obfuscated Ljava/lang/Boole transitive-accessible field net/minecraft/text/Style clickEvent Lnet/minecraft/text/ClickEvent; transitive-accessible field net/minecraft/text/Style hoverEvent Lnet/minecraft/text/HoverEvent; transitive-accessible field net/minecraft/text/Style insertion Ljava/lang/String; -transitive-accessible field net/minecraft/text/Style font Lnet/minecraft/util/Identifier; -transitive-accessible method net/minecraft/text/Style (Lnet/minecraft/text/TextColor;Ljava/lang/Integer;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Lnet/minecraft/text/ClickEvent;Lnet/minecraft/text/HoverEvent;Ljava/lang/String;Lnet/minecraft/util/Identifier;)V +# CHANGED in 1.21.11: font type changed from Identifier to StyleSpriteSource +transitive-accessible field net/minecraft/text/Style font Lnet/minecraft/text/StyleSpriteSource; +# CHANGED in 1.21.11: constructor added shadowColor parameter, font type changed to StyleSpriteSource +transitive-accessible method net/minecraft/text/Style (Lnet/minecraft/text/TextColor;Ljava/lang/Integer;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Lnet/minecraft/text/ClickEvent;Lnet/minecraft/text/HoverEvent;Ljava/lang/String;Lnet/minecraft/text/StyleSpriteSource;)V # Network transitive-accessible field net/minecraft/client/network/ClientPlayNetworkHandler playerListEntries Ljava/util/Map; @@ -98,12 +111,11 @@ transitive-accessible method net/minecraft/util/math/Vec3i setY (I)Lnet/minecraf transitive-accessible method net/minecraft/util/math/Vec3i setZ (I)Lnet/minecraft/util/math/Vec3i; # Debug -transitive-accessible field net/minecraft/client/gui/hud/DebugHud showDebugHud Z +# REMOVED in 1.21.11: showDebugHud - use DebugHud.shouldShowDebugHud() method instead transitive-accessible field net/minecraft/client/gui/hud/DebugHud renderingChartVisible Z transitive-accessible field net/minecraft/client/gui/hud/DebugHud renderingAndTickChartsVisible Z transitive-accessible field net/minecraft/client/gui/hud/DebugHud packetSizeAndPingChartsVisible Z -transitive-accessible field net/minecraft/client/render/debug/DebugRenderer showChunkBorder Z -transitive-accessible field net/minecraft/client/render/debug/DebugRenderer showOctree Z +# REMOVED in 1.21.11: showChunkBorder/showOctree - now controlled via debugHudEntryList.isEntryVisible(DebugHudEntries.CHUNK_BORDERS/CHUNK_SECTION_OCTREE) # Other transitive-accessible field net/minecraft/structure/StructureTemplate blockInfoLists Ljava/util/List; diff --git a/src/main/resources/lambda.mixins.common.json b/src/main/resources/lambda.mixins.json similarity index 79% rename from src/main/resources/lambda.mixins.common.json rename to src/main/resources/lambda.mixins.json index 0a70bc0ad..4c1ec3587 100644 --- a/src/main/resources/lambda.mixins.common.json +++ b/src/main/resources/lambda.mixins.json @@ -29,12 +29,9 @@ "network.HandshakeC2SPacketMixin", "network.LoginHelloC2SPacketMixin", "network.LoginKeyC2SPacketMixin", - "render.AbstractSignBlockEntityRendererMixin", "render.AbstractTerrainRenderContextMixin", "render.ArmorFeatureRendererMixin", "render.BackgroundRendererMixin", - "render.BeaconBlockEntityRendererMixin", - "render.BlockEntityRenderDispatcherMixin", "render.BlockMixin", "render.BlockModelRendererMixin", "render.BlockRenderManagerMixin", @@ -45,24 +42,23 @@ "render.ChatInputSuggestorMixin", "render.ChatScreenMixin", "render.ChunkOcclusionDataBuilderMixin", + "render.DarknessEffectFogMixin", "render.DebugHudMixin", "render.DebugRendererMixin", "render.DrawContextMixin", "render.ElytraFeatureRendererMixin", - "render.EnchantingTableBlockEntityRendererMixin", "render.EntityRendererMixin", "render.FluidRendererMixin", + "render.FogRendererMixin", "render.GameRendererMixin", - "render.HandledScreenMixin", "render.GlStateManagerMixin", + "render.HandledScreenMixin", "render.HeadFeatureRendererMixin", "render.HeldItemRendererMixin", "render.InGameHudMixin", "render.InGameOverlayRendererMixin", "render.LightmapTextureManagerMixin", "render.LivingEntityRendererMixin", - "render.MobSpawnerBlockEntityRendererMixin", - "render.ParticleManagerMixin", "render.PlayerListHudMixin", "render.RenderLayersMixin", "render.ScreenHandlerMixin", @@ -71,20 +67,34 @@ "render.SodiumBlockRendererMixin", "render.SodiumFluidRendererImplMixin", "render.SodiumLightDataAccessMixin", + "render.SodiumWorldRendererMixin", "render.SplashOverlayMixin", "render.SplashOverlayMixin$LogoTextureMixin", "render.TooltipComponentMixin", "render.WeatherRenderingMixin", "render.WorldBorderRenderingMixin", "render.WorldRendererMixin", + "render.blockentity.AbstractSignBlockEntityRendererMixin", + "render.blockentity.BeaconBlockEntityRendererMixin", + "render.blockentity.BlockEntityRenderDispatcherMixin", + "render.blockentity.EnchantingTableBlockEntityRendererMixin", + "render.blockentity.MobSpawnerBlockEntityRendererMixin", + "render.particle.BillboardParticleMixin", + "render.particle.ElderGuardianParticleRendererMixin", + "render.particle.ItemPickupParticleRendererMixin", "world.AbstractBlockMixin", "world.BlockCollisionSpliteratorMixin", "world.ClientChunkManagerMixin", "world.ClientWorldMixin", + "world.DirectionMixin", "world.StructureTemplateMixin", "world.WorldMixin" ], "injectors": { - "defaultRequire": 1 - } + "defaultRequire": 0 + }, + "overwrites": { + "conformVisibility": true + }, + "mixinextras": {"minVersion": "0.5.0"} }