From a11561a7ac03a15e6413d83ef59f4c7130c34572 Mon Sep 17 00:00:00 2001 From: Matias Date: Thu, 20 Aug 2026 03:23:21 +0300 Subject: [PATCH 1/7] Fix CrystalAura flagging BadPacketsH on Grim and add a very simple renderer for it --- .../module/modules/combat/CrystalAura.kt | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) 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 9bfa55a20..a89d749bd 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt @@ -27,6 +27,7 @@ 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.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRenderer import com.lambda.interaction.handlers.ContainerHandler.transfer import com.lambda.interaction.managers.hotbar.HotbarRequest import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest @@ -47,13 +48,9 @@ import com.lambda.util.Timer import com.lambda.util.collections.LimitedDecayQueue import com.lambda.util.combat.CombatUtils.crystalDamage import com.lambda.util.extension.fullHealth +import com.lambda.util.math.* import com.lambda.util.math.MathUtils.ceilToInt import com.lambda.util.math.MathUtils.roundToStep -import com.lambda.util.math.distSq -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.RotationUtils.getVisibleSurfaces import com.lambda.util.player.SlotUtils.hotbarStacks import com.lambda.util.world.fastEntitySearch @@ -135,6 +132,7 @@ object CrystalAura : Module( private val decay = LimitedDecayQueue(10000, 3000L) + private var lastPlace: Pair? = null private val collidingOffsets = mutableListOf().apply { for (x in -1..1) { for (z in -1..1) { @@ -240,6 +238,20 @@ object CrystalAura : Module( decay += crystal.id } + immediateRenderer("CrystalAura Immediate Renderer") { + runSafe { + if (lastPlace != null) { + if (lastPlace!!.second + 50 < System.currentTimeMillis()) { + return@runSafe + } + + box( + lastPlace!!.first, + ) + } + } + } + onEnable { currentTarget = null resetBlueprint() @@ -285,12 +297,16 @@ object CrystalAura : Module( } private fun SafeContext.placeInternal(opportunity: Opportunity, hand: Hand) { + runSafe { + lastPlace = Pair(opportunity.blockPos, System.currentTimeMillis()) + } interaction.syncSelectedSlot() - connection.sendPacket { - PlayerInteractBlockC2SPacket( - hand, BlockHitResult(opportunity.crystalPosition, opportunity.side, opportunity.blockPos, false), 0 - ) - } + mc.interactionManager!!.sendSequencedPacket(world, { sequence -> + + PlayerInteractBlockC2SPacket( + hand, BlockHitResult(opportunity.crystalPosition, opportunity.side, opportunity.blockPos, false), sequence + ) + }); player.swingHand(hand) } From 31514f76962a4cafa76e6247ed072eacbca559eb Mon Sep 17 00:00:00 2001 From: Matias Date: Thu, 20 Aug 2026 17:31:27 +0300 Subject: [PATCH 2/7] improve rendering --- .../module/modules/combat/CrystalAura.kt | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) 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 a89d749bd..cd1f3ad4f 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt @@ -19,14 +19,18 @@ package com.lambda.module.modules.combat import com.lambda.config.Tab import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig +import com.lambda.config.blocks.LineConfig import com.lambda.config.blocks.TargetingSettings import com.lambda.config.hide import com.lambda.config.hideAllExcept +import com.lambda.config.blocks.WorldLineSettings +import com.lambda.config.forEachSetting import com.lambda.config.withEdits 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.graphics.mc.BoxBuilder import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRenderer import com.lambda.interaction.handlers.ContainerHandler.transfer import com.lambda.interaction.managers.hotbar.HotbarRequest @@ -67,6 +71,7 @@ import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Box import net.minecraft.util.math.Direction import net.minecraft.util.math.Vec3d +import java.awt.Color import kotlin.concurrent.fixedRateTimer import kotlin.math.max import kotlin.time.Duration.Companion.milliseconds @@ -82,6 +87,7 @@ object CrystalAura : Module( private const val EXPLODING_TAB = "Exploding" private const val PREDICTION_TAB = "Prediction" private const val TARGETING_TAB = "Targeting" + private const val RENDERING_TAB = "Rendering" @Tab(GENERAL_TAB) private val rotate by setting("Rotate", true) @Tab(GENERAL_TAB) private val updateMode by setting("Update Mode", UpdateMode.Async) @@ -113,6 +119,23 @@ object CrystalAura : Module( @Tab(PREDICTION_TAB) private val targetingSettings by configBlock(TargetingSettings.CombatSettings(this, 10.0)) + @Tab(RENDERING_TAB) private val render by setting("Rendering", true) + @Tab(RENDERING_TAB) private val renderPlacements by setting("Placement Rendering", true) + + @Tab(RENDERING_TAB) private val renderLineSettings by configBlock(WorldLineSettings(this)) + .withEdits { + hideAllExcept ( + ::worldWidthSetting + ) + forEachSetting { + visibility { old -> { old() && render }} + } + } + @Tab(RENDERING_TAB) val primaryColorLine by setting("Primary Color (outline)", Color(130, 200, 255, 200), visibility = { render }) + @Tab(RENDERING_TAB) val secondaryColorLine by setting("Secondary Color (outline)", Color(130, 130, 255, 200), visibility = { render }) + @Tab(RENDERING_TAB) val primaryColor by setting("Primary Color", Color(225, 130, 225, 100), visibility = { render }) + @Tab(RENDERING_TAB) val secondaryColor by setting("Secondary Color", Color(170, 60, 170, 100), visibility = { render }) + private val blueprint = mutableMapOf() private var activeOpportunity: Opportunity? = null private var currentTarget: LivingEntity? = null @@ -247,7 +270,11 @@ object CrystalAura : Module( box( lastPlace!!.first, - ) + renderLineSettings + ) { + outlineGradientY(secondaryColorLine, primaryColorLine) + fillGradientY(secondaryColor, primaryColor) + } } } } From b250f6f23549fd7968e97dff9af842ed8193cab4 Mon Sep 17 00:00:00 2001 From: Matias Date: Thu, 20 Aug 2026 22:28:27 +0300 Subject: [PATCH 3/7] Fixed main hand not swapping because the HotbarRequest was being submitted as now or nothing. Offhand still does not work correctly. --- .../module/modules/combat/CrystalAura.kt | 71 +++++++++++++++---- 1 file changed, 56 insertions(+), 15 deletions(-) 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 cd1f3ad4f..4c27f8a30 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt @@ -19,7 +19,6 @@ package com.lambda.module.modules.combat import com.lambda.config.Tab import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig -import com.lambda.config.blocks.LineConfig import com.lambda.config.blocks.TargetingSettings import com.lambda.config.hide import com.lambda.config.hideAllExcept @@ -30,7 +29,6 @@ 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.graphics.mc.BoxBuilder import com.lambda.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRenderer import com.lambda.interaction.handlers.ContainerHandler.transfer import com.lambda.interaction.managers.hotbar.HotbarRequest @@ -47,21 +45,25 @@ import com.lambda.threading.runSafeAutomated import com.lambda.threading.runSafeGameScheduled import com.lambda.util.BlockUtils.blockState import com.lambda.util.CommunicationUtils.info +import com.lambda.util.CommunicationUtils.log import com.lambda.util.PacketUtils.sendPacket import com.lambda.util.Timer import com.lambda.util.collections.LimitedDecayQueue import com.lambda.util.combat.CombatUtils.crystalDamage import com.lambda.util.extension.fullHealth +import com.lambda.util.item.ItemStackUtils.slotId import com.lambda.util.math.* import com.lambda.util.math.MathUtils.ceilToInt import com.lambda.util.math.MathUtils.roundToStep import com.lambda.util.player.RotationUtils.getVisibleSurfaces +import com.lambda.util.player.SlotUtils.hotbarSlots import com.lambda.util.player.SlotUtils.hotbarStacks import com.lambda.util.world.fastEntitySearch import net.minecraft.block.Blocks import net.minecraft.entity.Entity import net.minecraft.entity.LivingEntity import net.minecraft.entity.decoration.EndCrystalEntity +import net.minecraft.item.ItemStack import net.minecraft.item.Items import net.minecraft.network.packet.c2s.play.PlayerInteractBlockC2SPacket import net.minecraft.network.packet.c2s.play.PlayerInteractEntityC2SPacket @@ -264,7 +266,7 @@ object CrystalAura : Module( immediateRenderer("CrystalAura Immediate Renderer") { runSafe { if (lastPlace != null) { - if (lastPlace!!.second + 50 < System.currentTimeMillis()) { + if (lastPlace!!.second + 100 < System.currentTimeMillis()) { return@runSafe } @@ -327,7 +329,7 @@ object CrystalAura : Module( runSafe { lastPlace = Pair(opportunity.blockPos, System.currentTimeMillis()) } - interaction.syncSelectedSlot() + interaction.syncSelectedSlot() // TODO: when server only hotbar swap gets implemented, this will be removed mc.interactionManager!!.sendSequencedPacket(world, { sequence -> PlayerInteractBlockC2SPacket( @@ -404,7 +406,7 @@ object CrystalAura : Module( val entitiesNearby = fastEntitySearch(3.5, pos) val crystals = entitiesNearby.filterIsInstance() - val otherEntities = entitiesNearby - crystals + player + val otherEntities = entitiesNearby - crystals.toSet() + player if (otherEntities.any { it.boundingBox.intersects(crystalBox) @@ -454,14 +456,13 @@ object CrystalAura : Module( // Associate by actions blueprint.values.forEach { opportunity -> actionMap.getOrPut(opportunity.actionType, ::mutableListOf) += opportunity - if (opportunity.actionType.priority > actionType.priority) { actionType = opportunity.actionType } } // Select best action - activeOpportunity = actionMap[actionType]?.maxByOrNull { + activeOpportunity = actionMap[actionType]?.filter { !it.blocked }?.maxByOrNull { it.priority } } @@ -524,26 +525,66 @@ object CrystalAura : Module( fun place() = runSafe { if (rotate && !rotationRequest { rotation(placeRotation) }.submit().done) return@runSafe - + var crystalHand: Hand? = null 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) + if ((swapHand == Hand.MAIN_HAND && player.mainHandStack.item != Items.END_CRYSTAL) || + (swapHand == Hand.OFF_HAND && player.offHandStack.item != Items.END_CRYSTAL) ) runSafeAutomated { if (!swap) return@runSafe - var crystalSlot = player.hotbarStacks.indexOfFirst { selection.filterStack(it) } - if (crystalSlot < 0) { + var hotbarStack: ItemStack? = null + + fun findInHotbar(): Pair { + hotbarStack = selection.bestItemMatch(player.hotbarStacks)?: return (false to -1) + val hotbarSlot = hotbarStack.slotId + return (hotbarSlot >= 0) to hotbarSlot + } + + fun findInOffhand(): Pair { + return (player.offHandStack.item == selection.item) to player.offHandStack.slotId + } + + val crystalSearch = when (swapHand) { + Hand.MAIN_HAND -> listOf( + Hand.MAIN_HAND to ::findInHotbar, + Hand.OFF_HAND to ::findInOffhand + ) + Hand.OFF_HAND -> listOf( + Hand.OFF_HAND to ::findInOffhand, + Hand.MAIN_HAND to ::findInHotbar + ) + } + + val searchResult = crystalSearch + .asSequence() + .map { (hand, finder) -> + val (found, slot) = finder() + Triple(found, slot, hand) + } + .firstOrNull { (found) -> found } + + crystalHand = searchResult?.third + + if (crystalHand == null) { // no crystal stack found in main or offhand val swapTo = when (swapHand) { + //TODO: add support for server side hotbar switching Hand.MAIN_HAND -> HotbarContainer Hand.OFF_HAND -> OffHandContainer } + + // retrieve from inventory, return if the task is not ready if (!selection.transfer(swapTo)) return@runSafe - crystalSlot = player.hotbarStacks.indexOfFirst { selection.filterStack(it) } } - if (!HotbarRequest(crystalSlot, this).submit().done) return@runSafe + if (crystalHand == Hand.MAIN_HAND && swapHand == Hand.MAIN_HAND) { + val s = player.hotbarStacks.indexOf(hotbarStack?: return@runSafe) + Lambda.LOG.info("$s, $swapHand, $crystalHand, ${player.hotbarStacks.size}") + if (!HotbarRequest(s, this@CrystalAura, nowOrNothing = false).submit().done) return@runSafe + } else if (crystalHand == null) { + return@runSafe + } } placeTimer.runSafeIfPassed(placeDelay.milliseconds) { - placeInternal(this@Opportunity, swapHand) + placeInternal(this@Opportunity, crystalHand?: swapHand) // we should not be here without a crystal in hand but ig better to check than not to if (prediction.onPlace) predictionTimer.runIfNotPassed(packetLifetime.milliseconds, false) { From f0f62aeae4f9aee66472f75d2f0e9cf15c84f9ca Mon Sep 17 00:00:00 2001 From: Matias Date: Fri, 21 Aug 2026 20:27:02 +0300 Subject: [PATCH 4/7] New prediction mode that does not flag grim, set the default update mode to ticked as async was being weird. Fixed hotbar logic. --- .../module/modules/combat/CrystalAura.kt | 134 ++++++++---------- .../module/modules/movement/TargetStrafe.kt | 4 +- 2 files changed, 65 insertions(+), 73 deletions(-) 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 4c27f8a30..03c0927d1 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt @@ -45,18 +45,15 @@ import com.lambda.threading.runSafeAutomated import com.lambda.threading.runSafeGameScheduled import com.lambda.util.BlockUtils.blockState import com.lambda.util.CommunicationUtils.info -import com.lambda.util.CommunicationUtils.log import com.lambda.util.PacketUtils.sendPacket import com.lambda.util.Timer import com.lambda.util.collections.LimitedDecayQueue import com.lambda.util.combat.CombatUtils.crystalDamage import com.lambda.util.extension.fullHealth -import com.lambda.util.item.ItemStackUtils.slotId import com.lambda.util.math.* import com.lambda.util.math.MathUtils.ceilToInt import com.lambda.util.math.MathUtils.roundToStep import com.lambda.util.player.RotationUtils.getVisibleSurfaces -import com.lambda.util.player.SlotUtils.hotbarSlots import com.lambda.util.player.SlotUtils.hotbarStacks import com.lambda.util.world.fastEntitySearch import net.minecraft.block.Blocks @@ -92,7 +89,7 @@ object CrystalAura : Module( private const val RENDERING_TAB = "Rendering" @Tab(GENERAL_TAB) private val rotate by setting("Rotate", true) - @Tab(GENERAL_TAB) private val updateMode by setting("Update Mode", UpdateMode.Async) + @Tab(GENERAL_TAB) private val updateMode by setting("Update Mode", UpdateMode.Ticked) @Tab(GENERAL_TAB) private val updateDelaySetting by setting("Update Delay", 25L, 5L..200L, 5L, unit = " ms") { updateMode == UpdateMode.Async } @Tab(GENERAL_TAB) private val maxUpdatesPerFrame by setting("Max Updates Per Frame", 5, 1..20, 1) { updateMode == UpdateMode.Async } @Tab(GENERAL_TAB) private val updateDelay get() = if (updateMode == UpdateMode.Async) updateDelaySetting else 0L @@ -114,8 +111,10 @@ object CrystalAura : Module( @Tab(EXPLODING_TAB) private val explodeDelay by setting("Explode Delay", 10L, 0L..1000L, 1L, "Delay between explosion attempts", " ms") @Tab(PREDICTION_TAB) private val prediction by setting("Prediction", PredictionMode.None) - @Tab(PREDICTION_TAB) private val packetPredictions by setting("Packet Predictions", 1, 0..20, 1) { prediction.onPacket } - @Tab(PREDICTION_TAB) private val placePostPause by setting("Place Post Pause", true) { prediction.onPacket } + @Tab(PREDICTION_TAB) private val packetPredictions by setting("Packet Predictions", 1, 0..20, 1, "Flags grim") { prediction.onPacket } + @Tab(PREDICTION_TAB) private val explodeOnPacket by setting("Explode On Packet", false, "Explodes the received crystal on packet") { prediction != PredictionMode.None } + @Tab(PREDICTION_TAB) private val placePostPause by setting("Place Post Pause", false, "Resets the place delay timer after receiving a entity spawn packet (adds a delay)") { prediction != PredictionMode.None } + @Tab(PREDICTION_TAB) private val postPacketPlace by setting("Post Packet Place", true, "Places the crystal on the next tick from the entity spawn packet") { prediction == PredictionMode.Tick && !placePostPause } @Tab(PREDICTION_TAB) private val placePredictions by setting("Place Predictions", 4, 1..20, 1) { prediction.onPlace } @Tab(PREDICTION_TAB) private val packetLifetime by setting("Packet Lifetime", 500L, 50L..1000L) { prediction.onPlace } @@ -154,6 +153,8 @@ object CrystalAura : Module( private val predictionTimer = Timer() private var lastEntityId = 0 + private var waitingForCrystal = false + private var safeToPlaceInstantly = false private val decay = LimitedDecayQueue(10000, 3000L) @@ -241,16 +242,20 @@ object CrystalAura : Module( // Run packet prediction if (!prediction.isActive || activeOpportunity != opportunity) return@listen - explodeInternal(lastEntityId) - - if (!prediction.onPacket) return@listen + if (explodeOnPacket) { + explodeInternal(lastEntityId) + } - repeat(packetPredictions) { - placeInternal(opportunity, swapHand) - explodeInternal(++lastEntityId) + if (!prediction.onPacket) { + // this only works in vanilla + repeat(packetPredictions) { + placeInternal(opportunity, swapHand) + explodeInternal(++lastEntityId) + } } if (placePostPause) placeTimer.reset() + if (postPacketPlace) safeToPlaceInstantly = true } listen { event -> @@ -303,9 +308,8 @@ object CrystalAura : Module( } private fun tickInteraction(best: Opportunity) { - if (!best.blocked) { + if (!best.blocked && best.crystal != null) { best.explode() - best.place() return } @@ -338,6 +342,7 @@ object CrystalAura : Module( }); player.swingHand(hand) + waitingForCrystal = true } private fun SafeContext.explodeInternal(id: Int) { @@ -531,72 +536,50 @@ object CrystalAura : Module( (swapHand == Hand.OFF_HAND && player.offHandStack.item != Items.END_CRYSTAL) ) runSafeAutomated { if (!swap) return@runSafe - var hotbarStack: ItemStack? = null - - fun findInHotbar(): Pair { - hotbarStack = selection.bestItemMatch(player.hotbarStacks)?: return (false to -1) - val hotbarSlot = hotbarStack.slotId - return (hotbarSlot >= 0) to hotbarSlot - } - - fun findInOffhand(): Pair { - return (player.offHandStack.item == selection.item) to player.offHandStack.slotId - } - - val crystalSearch = when (swapHand) { - Hand.MAIN_HAND -> listOf( - Hand.MAIN_HAND to ::findInHotbar, - Hand.OFF_HAND to ::findInOffhand - ) - Hand.OFF_HAND -> listOf( - Hand.OFF_HAND to ::findInOffhand, - Hand.MAIN_HAND to ::findInHotbar - ) + val itemToUse: ItemStack? = if (player.mainHandStack.item == Items.END_CRYSTAL) { + crystalHand = Hand.MAIN_HAND + player.mainHandStack + } else if (player.offHandStack.item == Items.END_CRYSTAL) { + crystalHand = Hand.OFF_HAND + player.offHandStack + } else null + + val swapTo = when (swapHand) { + Hand.MAIN_HAND -> HotbarContainer + Hand.OFF_HAND -> OffHandContainer } - val searchResult = crystalSearch - .asSequence() - .map { (hand, finder) -> - val (found, slot) = finder() - Triple(found, slot, hand) + if (itemToUse == null || swapHand == Hand.MAIN_HAND) { + if (swapHand == Hand.MAIN_HAND) { + val itemStack = selection.bestItemMatch(player.hotbarStacks) + if (itemStack == null) { // retrieve to hotbar + if (!selection.transfer(swapTo)) return@runSafe + } + val s = player.hotbarStacks.indexOf(itemStack) + if ((!HotbarRequest(s, this@CrystalAura, nowOrNothing = false).submit().done) && itemToUse == null) return@runSafe + } else { // retrieve to offhand + if (!selection.transfer(swapTo)) return@runSafe } - .firstOrNull { (found) -> found } - - crystalHand = searchResult?.third - - if (crystalHand == null) { // no crystal stack found in main or offhand - val swapTo = when (swapHand) { - //TODO: add support for server side hotbar switching - Hand.MAIN_HAND -> HotbarContainer - Hand.OFF_HAND -> OffHandContainer - } - - // retrieve from inventory, return if the task is not ready - if (!selection.transfer(swapTo)) return@runSafe - } - if (crystalHand == Hand.MAIN_HAND && swapHand == Hand.MAIN_HAND) { - val s = player.hotbarStacks.indexOf(hotbarStack?: return@runSafe) - Lambda.LOG.info("$s, $swapHand, $crystalHand, ${player.hotbarStacks.size}") - if (!HotbarRequest(s, this@CrystalAura, nowOrNothing = false).submit().done) return@runSafe - } else if (crystalHand == null) { - return@runSafe } } - placeTimer.runSafeIfPassed(placeDelay.milliseconds) { - placeInternal(this@Opportunity, crystalHand?: swapHand) // we should not be here without a crystal in hand but ig better to check than not to + if (placeTimer.timePassed(placeDelay.milliseconds) || (postPacketPlace && safeToPlaceInstantly && !placePostPause)) { + runSafe { + placeInternal(this@Opportunity, crystalHand?: swapHand) // we should not be here without a crystal in hand but ig better to check than not to + safeToPlaceInstantly = false + if (prediction.onPlace) + predictionTimer.runIfNotPassed(packetLifetime.milliseconds, false) { + val last = lastEntityId - if (prediction.onPlace) - predictionTimer.runIfNotPassed(packetLifetime.milliseconds, false) { - val last = lastEntityId + repeat(placePredictions) { + explodeInternal(++lastEntityId) + } - repeat(placePredictions) { - explodeInternal(++lastEntityId) + lastEntityId = last + 1 + crystal = null } - - lastEntityId = last + 1 - crystal = null - } + placeTimer.reset() + } } } @@ -607,6 +590,13 @@ object CrystalAura : Module( fun explode() { if (rotate && !rotationRequest { rotation(placeRotation) }.submit().done) return + if (waitingForCrystal && crystal == null && prediction == PredictionMode.Tick) { + runSafe { + explodeInternal(lastEntityId++) + waitingForCrystal = false + } + } + explodeTimer.runSafeIfPassed(explodeDelay.milliseconds) { crystal?.let { crystal -> explodeInternal(crystal.id) @@ -654,6 +644,8 @@ object CrystalAura : Module( // Predict on place Deferred(false, true), + Tick(false, false), + // Predict on both timings Mixed(true, true); diff --git a/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt b/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt index 116e68521..ae1cae803 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt @@ -17,8 +17,8 @@ package com.lambda.module.modules.movement -import com.lambda.config.ConfigEditor.forEachSetting -import com.lambda.config.ConfigEditor.hideAllExcept +import com.lambda.config.forEachSetting +import com.lambda.config.hideAllExcept import com.lambda.config.blocks.WorldLineSettings import com.lambda.config.withEdits import com.lambda.context.SafeContext From 5967eeb4c1066de1fc7b2f68b0dbf1484fccb1d5 Mon Sep 17 00:00:00 2001 From: Matias Date: Tue, 25 Aug 2026 17:51:49 +0300 Subject: [PATCH 5/7] Refactored Crystalaura to use multiple files in a separate package under modules, added docs for HotbarRequest --- .../managers/hotbar/HotbarRequest.kt | 12 + .../combat/{ => crystalaura}/CrystalAura.kt | 386 ++++++------------ .../crystalaura/CrystalAuraExploding.kt | 36 ++ .../combat/crystalaura/CrystalAuraPlacing.kt | 38 ++ .../modules/combat/crystalaura/Opportunity.kt | 193 +++++++++ 5 files changed, 397 insertions(+), 268 deletions(-) rename src/main/kotlin/com/lambda/module/modules/combat/{ => crystalaura}/CrystalAura.kt (56%) create mode 100644 src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAuraExploding.kt create mode 100644 src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAuraPlacing.kt create mode 100644 src/main/kotlin/com/lambda/module/modules/combat/crystalaura/Opportunity.kt 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 331086e9e..af9a22ef4 100644 --- a/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarRequest.kt +++ b/src/main/kotlin/com/lambda/interaction/managers/hotbar/HotbarRequest.kt @@ -20,6 +20,18 @@ package com.lambda.interaction.managers.hotbar import com.lambda.context.Automated import com.lambda.interaction.managers.Request +/** + * A request to change the hotbar slot. + * If you are having an issue with the slot not changing, try setting [nowOrNothing] to false. + * + * Example usage: + * + * val slot = 0 // 0-8, 0 = left-most slot + * // ModuleObjectName is the name of the module class basically, so for example, in CrystalAura it is this@CrystalAura + * // nowOrNothing is true by default, silent swapping requires it to be true, normal swapping does not + * // .done checks if the request is done so if it is false you don't want to continue execution + * if (!HotbarRequest(slot, this@ModuleObjectName, nowOrNothing = false).submit().done) return@runSafe + */ class HotbarRequest( val slot: Int, automated: Automated, diff --git a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAura.kt similarity index 56% rename from src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt rename to src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAura.kt index 03c0927d1..e79f854a0 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAura.kt @@ -15,33 +15,26 @@ * along with this program. If not, see . */ -package com.lambda.module.modules.combat +package com.lambda.module.modules.combat.crystalaura import com.lambda.config.Tab import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig import com.lambda.config.blocks.TargetingSettings -import com.lambda.config.hide -import com.lambda.config.hideAllExcept import com.lambda.config.blocks.WorldLineSettings import com.lambda.config.forEachSetting +import com.lambda.config.hide +import com.lambda.config.hideAllExcept import com.lambda.config.withEdits 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.graphics.mc.renderer.ImmediateRenderer.Companion.immediateRenderer -import com.lambda.interaction.handlers.ContainerHandler.transfer -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.material.StackSelection.Companion.selectStack -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.modules.combat.crystalaura.CrystalAuraExploding.explodeInternal +import com.lambda.module.modules.combat.crystalaura.CrystalAuraPlacing.placeInternal import com.lambda.module.tag.ModuleTag 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.CommunicationUtils.info @@ -50,22 +43,20 @@ import com.lambda.util.Timer import com.lambda.util.collections.LimitedDecayQueue import com.lambda.util.combat.CombatUtils.crystalDamage import com.lambda.util.extension.fullHealth -import com.lambda.util.math.* import com.lambda.util.math.MathUtils.ceilToInt import com.lambda.util.math.MathUtils.roundToStep -import com.lambda.util.player.RotationUtils.getVisibleSurfaces -import com.lambda.util.player.SlotUtils.hotbarStacks +import com.lambda.util.math.distSq +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.world.fastEntitySearch import net.minecraft.block.Blocks import net.minecraft.entity.Entity import net.minecraft.entity.LivingEntity import net.minecraft.entity.decoration.EndCrystalEntity -import net.minecraft.item.ItemStack -import net.minecraft.item.Items -import net.minecraft.network.packet.c2s.play.PlayerInteractBlockC2SPacket import net.minecraft.network.packet.c2s.play.PlayerInteractEntityC2SPacket import net.minecraft.util.Hand -import net.minecraft.util.hit.BlockHitResult import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Box import net.minecraft.util.math.Direction @@ -88,7 +79,8 @@ object CrystalAura : Module( private const val TARGETING_TAB = "Targeting" private const val RENDERING_TAB = "Rendering" - @Tab(GENERAL_TAB) private val rotate by setting("Rotate", true) + @Tab(GENERAL_TAB) + val rotate by setting("Rotate", true) @Tab(GENERAL_TAB) private val updateMode by setting("Update Mode", UpdateMode.Ticked) @Tab(GENERAL_TAB) private val updateDelaySetting by setting("Update Delay", 25L, 5L..200L, 5L, unit = " ms") { updateMode == UpdateMode.Async } @Tab(GENERAL_TAB) private val maxUpdatesPerFrame by setting("Max Updates Per Frame", 5, 1..20, 1) { updateMode == UpdateMode.Async } @@ -96,10 +88,14 @@ object CrystalAura : Module( @Tab(GENERAL_TAB) private val debug by setting("Debug", false) @Tab(PLACEMENT_TAB) private val placeRange by setting("Place Range", 4.6, 1.0..7.0, 0.1, "Range to place crystals", " blocks") - @Tab(PLACEMENT_TAB) private val placeDelay by setting("Place Delay", 50L, 0L..1000L, 1L, "Delay between placement attempts", " ms") - @Tab(PLACEMENT_TAB) private val swap by setting("Swap", true, "Swaps to crystals") - @Tab(PLACEMENT_TAB) private val swapHand by setting("Swap Hand", Hand.MAIN_HAND, "Which hand to swap the crystal to") { swap } - @Tab(PLACEMENT_TAB) private val priorityMode by setting("Crystal Priority", Priority.Damage) + @Tab(PLACEMENT_TAB) + val placeDelay by setting("Place Delay", 50L, 0L..1000L, 1L, "Delay between placement attempts", " ms") + @Tab(PLACEMENT_TAB) + val swap by setting("Swap", true, "Swaps to crystals") + @Tab(PLACEMENT_TAB) + val swapHand by setting("Swap Hand", Hand.MAIN_HAND, "Which hand to swap the crystal to") { swap } + @Tab(PLACEMENT_TAB) + val priorityMode by setting("Crystal Priority", Priority.Damage) @Tab(PLACEMENT_TAB) private val minDamageAdvantage by setting("Min Damage Advantage", 4.0, 1.0..10.0, 0.5) { priorityMode == Priority.Advantage } @Tab(PLACEMENT_TAB) private val minTargetDamage by setting("Min Target Damage", 8.0, 0.0..20.0, 0.5, "Minimum target damage to use crystals") @Tab(PLACEMENT_TAB) private val maxSelfDamage by setting("Max Self Damage", 8.0, 0.0..36.0, 0.5, "Maximum self damage to use crystals") @@ -108,15 +104,21 @@ object CrystalAura : Module( @Tab(PLACEMENT_TAB) private val oldPlace by setting("1.12 Placement", false) @Tab(EXPLODING_TAB) private val explodeRange by setting("Explode Range", 3.0, 1.0..7.0, 0.1, "Range to explode crystals", " blocks") - @Tab(EXPLODING_TAB) private val explodeDelay by setting("Explode Delay", 10L, 0L..1000L, 1L, "Delay between explosion attempts", " ms") + @Tab(EXPLODING_TAB) + val explodeDelay by setting("Explode Delay", 10L, 0L..1000L, 1L, "Delay between explosion attempts", " ms") - @Tab(PREDICTION_TAB) private val prediction by setting("Prediction", PredictionMode.None) + @Tab(PREDICTION_TAB) + val prediction by setting("Prediction", PredictionMode.None) @Tab(PREDICTION_TAB) private val packetPredictions by setting("Packet Predictions", 1, 0..20, 1, "Flags grim") { prediction.onPacket } @Tab(PREDICTION_TAB) private val explodeOnPacket by setting("Explode On Packet", false, "Explodes the received crystal on packet") { prediction != PredictionMode.None } - @Tab(PREDICTION_TAB) private val placePostPause by setting("Place Post Pause", false, "Resets the place delay timer after receiving a entity spawn packet (adds a delay)") { prediction != PredictionMode.None } - @Tab(PREDICTION_TAB) private val postPacketPlace by setting("Post Packet Place", true, "Places the crystal on the next tick from the entity spawn packet") { prediction == PredictionMode.Tick && !placePostPause } - @Tab(PREDICTION_TAB) private val placePredictions by setting("Place Predictions", 4, 1..20, 1) { prediction.onPlace } - @Tab(PREDICTION_TAB) private val packetLifetime by setting("Packet Lifetime", 500L, 50L..1000L) { prediction.onPlace } + @Tab(PREDICTION_TAB) + val placePostPause by setting("Place Post Pause", false, "Resets the place delay timer after receiving a entity spawn packet (adds a delay)") { prediction != PredictionMode.None } + @Tab(PREDICTION_TAB) + val postPacketPlace by setting("Post Packet Place", true, "Places the crystal on the next tick from the entity spawn packet") { prediction == PredictionMode.Tick && !placePostPause } + @Tab(PREDICTION_TAB) + val placePredictions by setting("Place Predictions", 4, 1..20, 1) { prediction.onPlace } + @Tab(PREDICTION_TAB) + val packetLifetime by setting("Packet Lifetime", 500L, 50L..1000L) { prediction.onPlace } @Tab(PREDICTION_TAB) private val targetingSettings by configBlock(TargetingSettings.CombatSettings(this, 10.0)) @@ -125,12 +127,10 @@ object CrystalAura : Module( @Tab(RENDERING_TAB) private val renderLineSettings by configBlock(WorldLineSettings(this)) .withEdits { - hideAllExcept ( - ::worldWidthSetting - ) - forEachSetting { - visibility { old -> { old() && render }} - } + hideAllExcept(::worldWidthSetting) + forEachSetting { + visibility { old -> { old() && render } } + } } @Tab(RENDERING_TAB) val primaryColorLine by setting("Primary Color (outline)", Color(130, 200, 255, 200), visibility = { render }) @Tab(RENDERING_TAB) val secondaryColorLine by setting("Secondary Color (outline)", Color(130, 130, 255, 200), visibility = { render }) @@ -138,6 +138,7 @@ object CrystalAura : Module( @Tab(RENDERING_TAB) val secondaryColor by setting("Secondary Color", Color(170, 60, 170, 100), visibility = { render }) private val blueprint = mutableMapOf() + private var lastOpportunity: Opportunity? = null private var activeOpportunity: Opportunity? = null private var currentTarget: LivingEntity? = null @@ -148,17 +149,17 @@ object CrystalAura : Module( private val updateTimer = Timer() private var updatesThisFrame = 0 - private val placeTimer = Timer() - private val explodeTimer = Timer() + val placeTimer = Timer() + val explodeTimer = Timer() - private val predictionTimer = Timer() - private var lastEntityId = 0 - private var waitingForCrystal = false - private var safeToPlaceInstantly = false + val predictionTimer = Timer() + var lastEntityId = 0 + var waitingForCrystal = false + var safeToPlaceInstantly = false private val decay = LimitedDecayQueue(10000, 3000L) - private var lastPlace: Pair? = null + var lastPlace: Pair? = null private val collidingOffsets = mutableListOf().apply { for (x in -1..1) { for (z in -1..1) { @@ -174,46 +175,46 @@ object CrystalAura : Module( .withEdits { hideAllExcept(::buildConfig, ::rotationConfig, ::hotbarConfig, ::inventoryConfig) buildConfig.apply { - hide( - ::pathing, ::collectDrops, ::spleefEntities, - ::maxPendingActions, ::actionTimeout, ::maxBuildDependencies, ::breakBlocks, ::interactBlocks, ::placeBlocks - ) + hide( + ::pathing, ::collectDrops, ::spleefEntities, + ::maxPendingActions, ::actionTimeout, ::maxBuildDependencies, ::breakBlocks, ::interactBlocks, ::placeBlocks + ) } } // Async ticking - fixedRateTimer( - name = "Crystal Aura Thread", - daemon = true, - initialDelay = 0L, - period = 1L - ) { - if (isDisabled || updateMode != UpdateMode.Async) return@fixedRateTimer - - runSafe { - // timer may spam faster than main thread computes (game freezes completely at the beginning of the frame) - if (updatesThisFrame > maxUpdatesPerFrame) return@runSafe - updatesThisFrame++ - - // run this safely again to ensure that the context will stay safe at the next frame - runSafeGameScheduled { - tick() - } - } - } - - fixedRateTimer( - name = "CA Counter", - daemon = true, - initialDelay = 0L, - period = 1000L - ) { - if (isDisabled || !debug) return@fixedRateTimer - - runSafeGameScheduled { - info((decay.size.toDouble() * 0.3333).roundToStep(0.1).toString()) - } - } + fixedRateTimer( + name = "Crystal Aura Thread", + daemon = true, + initialDelay = 0L, + period = 1L + ) { + if (isDisabled || updateMode != UpdateMode.Async) return@fixedRateTimer + + runSafe { + // timer may spam faster than main thread computes (game freezes completely at the beginning of the frame) + if (updatesThisFrame > maxUpdatesPerFrame) return@runSafe + updatesThisFrame++ + + // run this safely again to ensure that the context will stay safe at the next frame + runSafeGameScheduled { + tick() + } + } + } + + fixedRateTimer( + name = "CA Counter", + daemon = true, + initialDelay = 0L, + period = 1000L + ) { + if (isDisabled || !debug) return@fixedRateTimer + + runSafeGameScheduled { + info((decay.size.toDouble() * 0.3333).roundToStep(0.1).toString()) + } + } // Ticking with alignment listen { @@ -240,10 +241,12 @@ object CrystalAura : Module( opportunity.crystal = crystal // Run packet prediction - if (!prediction.isActive || activeOpportunity != opportunity) return@listen + if (!prediction.isActive || opportunity.priority < minDamageAdvantage) { + return@listen + } if (explodeOnPacket) { - explodeInternal(lastEntityId) + explodeInternal(++lastEntityId) } if (!prediction.onPacket) { @@ -269,21 +272,20 @@ object CrystalAura : Module( } immediateRenderer("CrystalAura Immediate Renderer") { - runSafe { - if (lastPlace != null) { - if (lastPlace!!.second + 100 < System.currentTimeMillis()) { - return@runSafe - } - - box( - lastPlace!!.first, - renderLineSettings - ) { - outlineGradientY(secondaryColorLine, primaryColorLine) - fillGradientY(secondaryColor, primaryColor) - } - } - } + runSafe { + val place = lastPlace ?: return@runSafe + if (place.second + 100 < System.currentTimeMillis()) { + return@runSafe + } + + box( + place.first, + renderLineSettings + ) { + outlineGradientY(secondaryColorLine, primaryColorLine) + fillGradientY(secondaryColor, primaryColor) + } + } } onEnable { @@ -329,41 +331,15 @@ object CrystalAura : Module( best.place() } - private fun SafeContext.placeInternal(opportunity: Opportunity, hand: Hand) { - runSafe { - lastPlace = Pair(opportunity.blockPos, System.currentTimeMillis()) - } - interaction.syncSelectedSlot() // TODO: when server only hotbar swap gets implemented, this will be removed - mc.interactionManager!!.sendSequencedPacket(world, { sequence -> - - PlayerInteractBlockC2SPacket( - hand, BlockHitResult(opportunity.crystalPosition, opportunity.side, opportunity.blockPos, false), sequence - ) - }); - - player.swingHand(hand) - waitingForCrystal = true - } - - private fun SafeContext.explodeInternal(id: Int) { - connection.sendPacket { - PlayerInteractEntityC2SPacket( - id, player.isSneaking, PlayerInteractEntityC2SPacket.ATTACK - ) - } - - player.swingHand(Hand.MAIN_HAND) - } - private fun SafeContext.updateBlueprint(target: LivingEntity) = updateTimer.runIfPassed(updateDelay.milliseconds) { resetBlueprint() fun info( - pos: BlockPos, - target: LivingEntity, - blocked: Boolean, - crystal: EndCrystalEntity? = null + pos: BlockPos, + target: LivingEntity, + blocked: Boolean, + crystal: EndCrystalEntity? = null ): Opportunity? { val crystalPos = pos.crystalPosition @@ -383,6 +359,7 @@ object CrystalAura : Module( ) return null return Opportunity( + this@CrystalAura, pos.toImmutable(), targetDamage, selfDamage, @@ -393,8 +370,8 @@ object CrystalAura : Module( // Extra checks for placement, because you may explode but not place in special cases(crystal in the air) fun placeInfo( - pos: BlockPos, - target: LivingEntity + pos: BlockPos, + target: LivingEntity ): Opportunity? { // Check if crystals could be placed on the base block val state = blockState(pos) @@ -479,153 +456,26 @@ object CrystalAura : Module( activeOpportunity = null } - /** - * Represents the damage information resulting from placing an end crystal on a given [blockPos] - * and causing an explosion that targets current target entity. - * - * @property blockPos The position of the base block where the crystal is placed. - * @property target The amount of damage inflicted on the target. - * @property self The amount of damage inflicted on the player. - * @property blocked Whether the placement on [blockPos] is blocked by other crystals. - * @property crystal A crystal that is placed on [blockPos]. - */ - private class Opportunity( - val blockPos: BlockPos, - val target: Double, - val self: Double, - var blocked: Boolean, - var crystal: EndCrystalEntity? // ToDo: packet-based update wisely - ) { - var actionType = ActionType.Normal - val priority = priorityMode.factor(target, self) - val hasCrystal get() = crystal != null - - val crystalPosition by lazy { - blockPos.crystalPosition - } - - val side by lazy { - runSafe { - val visibleSides = Box(blockPos).getVisibleSurfaces(player.eyePos) - if (visibleSides.contains(Direction.UP)) Direction.UP else visibleSides.minByOrNull { - blockPos.getHitVec(it) distSq player.eyePos - } - } ?: Direction.UP - } - - val placeRotation by lazy { - runSafe { - var vec = blockPos.getHitVec(side) - - // look at the top part of the side - if (side.axis != Direction.Axis.Y) vec += Vec3d(0.0, 0.45, 0.0) - - player.eyePos.rotationTo(vec) - } ?: RotationManager.activeRotation - } - - /** - * Places the crystal on [blockPos] - */ - fun place() = runSafe { - if (rotate && !rotationRequest { rotation(placeRotation) }.submit().done) - return@runSafe - var crystalHand: Hand? = null - val selection = selectStack { isItem(Items.END_CRYSTAL) } - if ((swapHand == Hand.MAIN_HAND && player.mainHandStack.item != Items.END_CRYSTAL) || - (swapHand == Hand.OFF_HAND && player.offHandStack.item != Items.END_CRYSTAL) - ) runSafeAutomated { - if (!swap) return@runSafe - val itemToUse: ItemStack? = if (player.mainHandStack.item == Items.END_CRYSTAL) { - crystalHand = Hand.MAIN_HAND - player.mainHandStack - } else if (player.offHandStack.item == Items.END_CRYSTAL) { - crystalHand = Hand.OFF_HAND - player.offHandStack - } else null - - val swapTo = when (swapHand) { - Hand.MAIN_HAND -> HotbarContainer - Hand.OFF_HAND -> OffHandContainer - } - - if (itemToUse == null || swapHand == Hand.MAIN_HAND) { - if (swapHand == Hand.MAIN_HAND) { - val itemStack = selection.bestItemMatch(player.hotbarStacks) - if (itemStack == null) { // retrieve to hotbar - if (!selection.transfer(swapTo)) return@runSafe - } - val s = player.hotbarStacks.indexOf(itemStack) - if ((!HotbarRequest(s, this@CrystalAura, nowOrNothing = false).submit().done) && itemToUse == null) return@runSafe - } else { // retrieve to offhand - if (!selection.transfer(swapTo)) return@runSafe - } - } - } - - if (placeTimer.timePassed(placeDelay.milliseconds) || (postPacketPlace && safeToPlaceInstantly && !placePostPause)) { - runSafe { - placeInternal(this@Opportunity, crystalHand?: swapHand) // we should not be here without a crystal in hand but ig better to check than not to - safeToPlaceInstantly = false - if (prediction.onPlace) - predictionTimer.runIfNotPassed(packetLifetime.milliseconds, false) { - val last = lastEntityId - - repeat(placePredictions) { - explodeInternal(++lastEntityId) - } - - lastEntityId = last + 1 - crystal = null - } - placeTimer.reset() - } - } - } - - /** - * Explodes a crystal that is on [blockPos] - * @return Whether the delay passed, null if the interaction failed or no crystal found - */ - fun explode() { - if (rotate && !rotationRequest { rotation(placeRotation) }.submit().done) return - - if (waitingForCrystal && crystal == null && prediction == PredictionMode.Tick) { - runSafe { - explodeInternal(lastEntityId++) - waitingForCrystal = false - } - } - - explodeTimer.runSafeIfPassed(explodeDelay.milliseconds) { - crystal?.let { crystal -> - explodeInternal(crystal.id) - explodeTimer.reset() - } - } - } - } - private val EndCrystalEntity.baseBlockPos get() = (pos - Vec3d(0.0, 0.5, 0.0)).flooredBlockPos - private val BlockPos.crystalPosition get() = + val BlockPos.crystalPosition get() = this.getHitVec(Direction.UP) private val BlockPos.crystalPlaceHitBox get() = crystalPosition.let { base -> - Box( - base - Vec3d(1.0, 0.0, 1.0), - base + Vec3d(1.0, 2.0, 1.0), - ) + Box( + base - Vec3d(1.0, 0.0, 1.0), + base + Vec3d(1.0, 2.0, 1.0), + ) } private val BlockPos.crystalBox get() = crystalPosition.let { base -> - Box( - base - Vec3d(0.5, 0.0, 0.5), - base + Vec3d(0.5, 2.0, 0.5), - ) + Box( + base - Vec3d(0.5, 0.0, 0.5), + base + Vec3d(0.5, 2.0, 0.5), + ) } private enum class UpdateMode { @@ -634,7 +484,7 @@ object CrystalAura : Module( } @Suppress("Unused") - private enum class PredictionMode(val onPacket: Boolean, val onPlace: Boolean) { + enum class PredictionMode(val onPacket: Boolean, val onPlace: Boolean) { // Prediction disable None(false, false), @@ -652,7 +502,7 @@ object CrystalAura : Module( val isActive = onPacket || onPlace } - private enum class Priority(val factor: (targetDamage: Double, selfDamage: Double) -> Double) { + enum class Priority(val factor: (targetDamage: Double, selfDamage: Double) -> Double) { Damage({ target, _ -> target }), @@ -663,9 +513,9 @@ object CrystalAura : Module( // ToDo: implement actions @Suppress("Unused") - private enum class ActionType(val priority: Int) { + enum class ActionType(val priority: Int) { Normal(0), ForcePlace(1), SlowBreak(2) } -} +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAuraExploding.kt b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAuraExploding.kt new file mode 100644 index 000000000..90b16b7b4 --- /dev/null +++ b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAuraExploding.kt @@ -0,0 +1,36 @@ +/* + * 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.combat.crystalaura + +import com.lambda.context.SafeContext +import com.lambda.threading.runSafe +import com.lambda.util.PacketUtils.sendPacket +import net.minecraft.network.packet.c2s.play.PlayerInteractEntityC2SPacket +import net.minecraft.util.Hand + +object CrystalAuraExploding { + fun CrystalAura.explodeInternal(id: Int) = runSafe { + connection.sendPacket { + PlayerInteractEntityC2SPacket( + id, player.isSneaking, PlayerInteractEntityC2SPacket.ATTACK + ) + } + + player.swingHand(Hand.MAIN_HAND) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAuraPlacing.kt b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAuraPlacing.kt new file mode 100644 index 000000000..533e81abb --- /dev/null +++ b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAuraPlacing.kt @@ -0,0 +1,38 @@ +/* + * 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.combat.crystalaura + +import com.lambda.threading.runSafe +import net.minecraft.network.packet.c2s.play.PlayerInteractBlockC2SPacket +import net.minecraft.util.Hand +import net.minecraft.util.hit.BlockHitResult + +object CrystalAuraPlacing { + fun CrystalAura.placeInternal(opportunity: Opportunity, hand: Hand) = runSafe { + lastPlace = Pair(opportunity.blockPos, System.currentTimeMillis()) + interaction.syncSelectedSlot() // TODO: when server only hotbar swap gets implemented, this will be removed + interaction.sendSequencedPacket(world) { sequence -> + PlayerInteractBlockC2SPacket( + hand, BlockHitResult(opportunity.crystalPosition, opportunity.side, opportunity.blockPos, false), sequence + ) + } + + player.swingHand(hand) + waitingForCrystal = true + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/Opportunity.kt b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/Opportunity.kt new file mode 100644 index 000000000..96f88c33f --- /dev/null +++ b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/Opportunity.kt @@ -0,0 +1,193 @@ +/* + * 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.combat.crystalaura + +import com.lambda.context.Automated +import com.lambda.interaction.handlers.ContainerHandler.transfer +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.material.StackSelection +import com.lambda.interaction.material.container.containers.HotbarContainer +import com.lambda.interaction.material.container.containers.OffHandContainer +import com.lambda.module.modules.combat.crystalaura.CrystalAura.ActionType +import com.lambda.module.modules.combat.crystalaura.CrystalAura.PredictionMode +import com.lambda.module.modules.combat.crystalaura.CrystalAura.crystalPosition +import com.lambda.module.modules.combat.crystalaura.CrystalAura.explodeDelay +import com.lambda.module.modules.combat.crystalaura.CrystalAuraExploding.explodeInternal +import com.lambda.module.modules.combat.crystalaura.CrystalAura.explodeTimer +import com.lambda.module.modules.combat.crystalaura.CrystalAura.lastEntityId +import com.lambda.module.modules.combat.crystalaura.CrystalAura.packetLifetime +import com.lambda.module.modules.combat.crystalaura.CrystalAura.placeDelay +import com.lambda.module.modules.combat.crystalaura.CrystalAura.placePostPause +import com.lambda.module.modules.combat.crystalaura.CrystalAura.placePredictions +import com.lambda.module.modules.combat.crystalaura.CrystalAura.placeTimer +import com.lambda.module.modules.combat.crystalaura.CrystalAura.postPacketPlace +import com.lambda.module.modules.combat.crystalaura.CrystalAura.prediction +import com.lambda.module.modules.combat.crystalaura.CrystalAura.predictionTimer +import com.lambda.module.modules.combat.crystalaura.CrystalAura.priorityMode +import com.lambda.module.modules.combat.crystalaura.CrystalAura.rotate +import com.lambda.module.modules.combat.crystalaura.CrystalAura.safeToPlaceInstantly +import com.lambda.module.modules.combat.crystalaura.CrystalAura.swap +import com.lambda.module.modules.combat.crystalaura.CrystalAura.swapHand +import com.lambda.module.modules.combat.crystalaura.CrystalAura.waitingForCrystal +import com.lambda.module.modules.combat.crystalaura.CrystalAuraPlacing.placeInternal +import com.lambda.threading.runSafe +import com.lambda.threading.runSafeAutomated +import com.lambda.util.math.distSq +import com.lambda.util.math.getHitVec +import com.lambda.util.math.plus +import com.lambda.util.player.RotationUtils.getVisibleSurfaces +import com.lambda.util.player.SlotUtils.hotbarStacks +import net.minecraft.entity.decoration.EndCrystalEntity +import net.minecraft.item.ItemStack +import net.minecraft.item.Items +import net.minecraft.util.Hand +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Box +import net.minecraft.util.math.Direction +import net.minecraft.util.math.Vec3d +import kotlin.time.Duration.Companion.milliseconds + + +/** + * Represents the damage information resulting from placing an end crystal on a given [blockPos] + * and causing an explosion that targets current target entity. + * + * @property blockPos The position of the base block where the crystal is placed. + * @property target The amount of damage inflicted on the target. + * @property self The amount of damage inflicted on the player. + * @property blocked Whether the placement on [blockPos] is blocked by other crystals. + * @property crystal A crystal that is placed on [blockPos]. + */ +class Opportunity( + private val automated: Automated, + val blockPos: BlockPos, + val target: Double, + val self: Double, + var blocked: Boolean, + var crystal: EndCrystalEntity? // ToDo: packet-based update wisely +) { + var actionType = ActionType.Normal + val priority = priorityMode.factor(target, self) + val hasCrystal get() = crystal != null + + val crystalPosition by lazy { + blockPos.crystalPosition + } + + val side by lazy { + runSafe { + val visibleSides = Box(blockPos).getVisibleSurfaces(player.eyePos) + if (visibleSides.contains(Direction.UP)) Direction.UP else visibleSides.minByOrNull { + blockPos.getHitVec(it) distSq player.eyePos + } + } ?: Direction.UP + } + + val placeRotation by lazy { + runSafe { + var vec = blockPos.getHitVec(side) + + // look at the top part of the side + if (side.axis != Direction.Axis.Y) vec + Vec3d(0.0, 0.45, 0.0) + + player.eyePos.rotationTo(vec) + } ?: RotationManager.activeRotation + } + + /** + * Places the crystal on [blockPos] + */ + fun place() = runSafe { + if (rotate && !automated.rotationRequest { rotation(placeRotation) }.submit().done) + return@runSafe + var crystalHand: Hand? = null + val selection = StackSelection.selectStack { isItem(Items.END_CRYSTAL) } + if ((swapHand == Hand.MAIN_HAND && player.mainHandStack.item != Items.END_CRYSTAL) || + (swapHand == Hand.OFF_HAND && player.offHandStack.item != Items.END_CRYSTAL) + ) automated.runSafeAutomated { + if (!swap) return@runSafe + val itemToUse: ItemStack? = if (player.mainHandStack.item == Items.END_CRYSTAL) { + crystalHand = Hand.MAIN_HAND + player.mainHandStack + } else if (player.offHandStack.item == Items.END_CRYSTAL) { + crystalHand = Hand.OFF_HAND + player.offHandStack + } else null + + val swapTo = when (swapHand) { + Hand.MAIN_HAND -> HotbarContainer + Hand.OFF_HAND -> OffHandContainer + } + + if (itemToUse == null || swapHand == Hand.MAIN_HAND) { + if (swapHand == Hand.MAIN_HAND) { + val itemStack = selection.bestItemMatch(player.hotbarStacks) + if (itemStack == null) { // retrieve to hotbar + if (!selection.transfer(swapTo)) return@runSafe + } + val s = player.hotbarStacks.indexOf(itemStack) + if ((!HotbarRequest(s, automated, nowOrNothing = false).submit().done) && itemToUse == null) return@runSafe + } else { // retrieve to offhand + if (!selection.transfer(swapTo)) return@runSafe + } + } + } + + if (placeTimer.timePassed(placeDelay.milliseconds) || (postPacketPlace && safeToPlaceInstantly && !placePostPause)) { + CrystalAura.placeInternal(this@Opportunity, crystalHand ?: swapHand) // we should not be here without a crystal in hand but ig better to check than not to + safeToPlaceInstantly = false + if (prediction.onPlace) + predictionTimer.runIfNotPassed(packetLifetime.milliseconds, false) { + val last = lastEntityId + + repeat(placePredictions) { + CrystalAura.explodeInternal(++lastEntityId) + } + + lastEntityId = last + 1 + crystal = null + } + placeTimer.reset() + } + } + + /** + * Explodes a crystal that is on [blockPos] + * @return Whether the delay passed, null if the interaction failed or no crystal found + */ + fun explode() { + if (rotate && !automated.rotationRequest { rotation(placeRotation) }.submit().done) return + + if (waitingForCrystal && crystal == null && prediction == PredictionMode.Tick) { + runSafe { + CrystalAura.explodeInternal(++lastEntityId) + waitingForCrystal = false + } + } + + explodeTimer.runSafeIfPassed(explodeDelay.milliseconds) { + crystal?.let { crystal -> + CrystalAura.explodeInternal(crystal.id) + explodeTimer.reset() + } + } + } +} \ No newline at end of file From 4817473240da0e39b30f2407073e588bde2d701f Mon Sep 17 00:00:00 2001 From: Matias Date: Sun, 20 Sep 2026 14:53:40 +0300 Subject: [PATCH 6/7] - Made it instantly place a crystal when the old one was broken no even if the old one has not yet been deleted by the server - Tick predictions are now handled in tickInteraction() - Added a second non-working check that should break blocking crystals if it worked --- .../modules/combat/crystalaura/CrystalAura.kt | 88 ++++++++++++++----- .../crystalaura/CrystalAuraExploding.kt | 1 - .../modules/combat/crystalaura/Opportunity.kt | 24 ++--- 3 files changed, 81 insertions(+), 32 deletions(-) diff --git a/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAura.kt index e79f854a0..37c52b22e 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAura.kt @@ -17,6 +17,7 @@ package com.lambda.module.modules.combat.crystalaura +import com.lambda.Lambda import com.lambda.config.Tab import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig import com.lambda.config.blocks.TargetingSettings @@ -38,7 +39,6 @@ import com.lambda.threading.runSafe import com.lambda.threading.runSafeGameScheduled import com.lambda.util.BlockUtils.blockState import com.lambda.util.CommunicationUtils.info -import com.lambda.util.PacketUtils.sendPacket import com.lambda.util.Timer import com.lambda.util.collections.LimitedDecayQueue import com.lambda.util.combat.CombatUtils.crystalDamage @@ -55,7 +55,6 @@ import net.minecraft.block.Blocks import net.minecraft.entity.Entity import net.minecraft.entity.LivingEntity import net.minecraft.entity.decoration.EndCrystalEntity -import net.minecraft.network.packet.c2s.play.PlayerInteractEntityC2SPacket import net.minecraft.util.Hand import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Box @@ -138,6 +137,8 @@ object CrystalAura : Module( @Tab(RENDERING_TAB) val secondaryColor by setting("Secondary Color", Color(170, 60, 170, 100), visibility = { render }) private val blueprint = mutableMapOf() + var lastHit: BlockPos? = null + var blockingCrystal: BlockPos? = null private var lastOpportunity: Opportunity? = null private var activeOpportunity: Opportunity? = null private var currentTarget: LivingEntity? = null @@ -241,12 +242,16 @@ object CrystalAura : Module( opportunity.crystal = crystal // Run packet prediction - if (!prediction.isActive || opportunity.priority < minDamageAdvantage) { + if (!prediction.isActive || (opportunity.priority < minDamageAdvantage && opportunity.blockPos.distSq(lastPlace?.first ?: pos) > 2.5)) { return@listen } + lastEntityId = crystal.id + if (explodeOnPacket) { - explodeInternal(++lastEntityId) + repeat(packetPredictions) { + explodeInternal(++lastEntityId) + } } if (!prediction.onPacket) { @@ -290,6 +295,7 @@ object CrystalAura : Module( onEnable { currentTarget = null + lastHit = null resetBlueprint() } } @@ -310,8 +316,21 @@ object CrystalAura : Module( } private fun tickInteraction(best: Opportunity) { + fun handleExplosion(opportunity: Opportunity) { + opportunity.explode() + if (prediction.onTick) { + explodeTimer.runSafeIfPassed(explodeDelay.milliseconds) { + if (waitingForCrystal) { + CrystalAura.explodeInternal(++lastEntityId) + waitingForCrystal = false + lastHit = opportunity.blockPos + explodeTimer.reset() + } + } + } + } if (!best.blocked && best.crystal != null) { - best.explode() + handleExplosion(best) return } @@ -326,8 +345,10 @@ object CrystalAura : Module( ) blueprint[mutableBlockPos] - }.filter { it.hasCrystal }.maxByOrNull { it.priority }?.explode() - + }.filter { it.hasCrystal }.maxByOrNull { it.priority }?.let { + handleExplosion(it) + return@tickInteraction + } best.place() } @@ -339,7 +360,8 @@ object CrystalAura : Module( pos: BlockPos, target: LivingEntity, blocked: Boolean, - crystal: EndCrystalEntity? = null + crystal: EndCrystalEntity? = null, + blockingCrystal: EndCrystalEntity? = null ): Opportunity? { val crystalPos = pos.crystalPosition @@ -364,7 +386,8 @@ object CrystalAura : Module( targetDamage, selfDamage, blocked, - crystal + crystal, + blockingCrystal ) } @@ -388,6 +411,7 @@ object CrystalAura : Module( val entitiesNearby = fastEntitySearch(3.5, pos) val crystals = entitiesNearby.filterIsInstance() + // why does the player get removed from this? val otherEntities = entitiesNearby - crystals.toSet() + player if (otherEntities.any { @@ -400,15 +424,17 @@ object CrystalAura : Module( } val crystalPlaceBox = pos.crystalPlaceHitBox - val blocked = baseCrystal == null && crystals.any { + val blockingCrystal = crystals.firstOrNull { it.boundingBox.intersects(crystalPlaceBox) } + val blocked = baseCrystal == null && blockingCrystal != null return info( pos, target, blocked, - baseCrystal + baseCrystal, + blockingCrystal ) } @@ -419,6 +445,7 @@ object CrystalAura : Module( val crystalBase = BlockPos.Mutable() fastEntitySearch(range).forEach { crystal -> crystalBase.set(crystal.x, crystal.y - 0.5, crystal.z) + //if (crystalBase == lastHit) return@forEach damage += info(crystalBase, target, false, crystal) ?: return@forEach } @@ -442,10 +469,26 @@ object CrystalAura : Module( actionType = opportunity.actionType } } - + // todo: optimize this + val blocked = mutableSetOf() // Select best action - activeOpportunity = actionMap[actionType]?.filter { !it.blocked }?.maxByOrNull { + activeOpportunity = actionMap[actionType]?.filter { + !it.blocked || lastHit == it.blockPos + }?.maxByOrNull { it.priority + }.also { + if (it?.blocked ?: false) { + blocked.add(it) + } + } + if (activeOpportunity != null) { + return@runIfPassed + } + val best = blocked.maxByOrNull { + it.priority + } + activeOpportunity = actionMap[actionType]?.firstOrNull { + it.blockingCrystal == best?.crystal } } @@ -484,22 +527,27 @@ object CrystalAura : Module( } @Suppress("Unused") - enum class PredictionMode(val onPacket: Boolean, val onPlace: Boolean) { + enum class PredictionMode(val onPacket: Boolean, val onPlace: Boolean, val onTick: Boolean) { // Prediction disable - None(false, false), + None(false, false, false), // Predict on packet receive - Packet(true, false), + Packet(true, false, false), // Predict on place - Deferred(false, true), + Deferred(false, true, false), - Tick(false, false), + // Predict on tick (functionality is in tickInteraction()) + // sends a burst of break packets every time the break cooldown has expired + // so it is kinda weird but may help with bypassing anticheats + // for grim on anarchy servers Packet is fine as BadPacketsW does not + // cancel the next packets even if we guess the crystal's id wrong + Tick(false, false, true), // Predict on both timings - Mixed(true, true); + Mixed(true, true, false); - val isActive = onPacket || onPlace + val isActive = onPacket || onPlace || onTick } enum class Priority(val factor: (targetDamage: Double, selfDamage: Double) -> Double) { diff --git a/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAuraExploding.kt b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAuraExploding.kt index 90b16b7b4..976670176 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAuraExploding.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAuraExploding.kt @@ -17,7 +17,6 @@ package com.lambda.module.modules.combat.crystalaura -import com.lambda.context.SafeContext import com.lambda.threading.runSafe import com.lambda.util.PacketUtils.sendPacket import net.minecraft.network.packet.c2s.play.PlayerInteractEntityC2SPacket diff --git a/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/Opportunity.kt b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/Opportunity.kt index 96f88c33f..a8827f757 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/Opportunity.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/Opportunity.kt @@ -17,6 +17,7 @@ package com.lambda.module.modules.combat.crystalaura +import com.lambda.Lambda import com.lambda.context.Automated import com.lambda.interaction.handlers.ContainerHandler.transfer import com.lambda.interaction.managers.hotbar.HotbarRequest @@ -33,6 +34,7 @@ import com.lambda.module.modules.combat.crystalaura.CrystalAura.explodeDelay import com.lambda.module.modules.combat.crystalaura.CrystalAuraExploding.explodeInternal import com.lambda.module.modules.combat.crystalaura.CrystalAura.explodeTimer import com.lambda.module.modules.combat.crystalaura.CrystalAura.lastEntityId +import com.lambda.module.modules.combat.crystalaura.CrystalAura.lastHit import com.lambda.module.modules.combat.crystalaura.CrystalAura.packetLifetime import com.lambda.module.modules.combat.crystalaura.CrystalAura.placeDelay import com.lambda.module.modules.combat.crystalaura.CrystalAura.placePostPause @@ -82,7 +84,8 @@ class Opportunity( val target: Double, val self: Double, var blocked: Boolean, - var crystal: EndCrystalEntity? // ToDo: packet-based update wisely + var crystal: EndCrystalEntity?, // ToDo: packet-based update wisely + var blockingCrystal: EndCrystalEntity? = null ) { var actionType = ActionType.Normal val priority = priorityMode.factor(target, self) @@ -116,8 +119,10 @@ class Opportunity( * Places the crystal on [blockPos] */ fun place() = runSafe { - if (rotate && !automated.rotationRequest { rotation(placeRotation) }.submit().done) + if (rotate && !automated.rotationRequest { rotation(placeRotation) }.submit().done) { + Lambda.LOG.info("Not yet rotated") return@runSafe + } var crystalHand: Hand? = null val selection = StackSelection.selectStack { isItem(Items.END_CRYSTAL) } if ((swapHand == Hand.MAIN_HAND && player.mainHandStack.item != Items.END_CRYSTAL) || @@ -154,17 +159,19 @@ class Opportunity( if (placeTimer.timePassed(placeDelay.milliseconds) || (postPacketPlace && safeToPlaceInstantly && !placePostPause)) { CrystalAura.placeInternal(this@Opportunity, crystalHand ?: swapHand) // we should not be here without a crystal in hand but ig better to check than not to safeToPlaceInstantly = false - if (prediction.onPlace) + lastHit = null + if (prediction.onPlace) { predictionTimer.runIfNotPassed(packetLifetime.milliseconds, false) { val last = lastEntityId repeat(placePredictions) { CrystalAura.explodeInternal(++lastEntityId) } - + lastHit = blockPos lastEntityId = last + 1 crystal = null } + } placeTimer.reset() } } @@ -176,16 +183,11 @@ class Opportunity( fun explode() { if (rotate && !automated.rotationRequest { rotation(placeRotation) }.submit().done) return - if (waitingForCrystal && crystal == null && prediction == PredictionMode.Tick) { - runSafe { - CrystalAura.explodeInternal(++lastEntityId) - waitingForCrystal = false - } - } - explodeTimer.runSafeIfPassed(explodeDelay.milliseconds) { crystal?.let { crystal -> CrystalAura.explodeInternal(crystal.id) + lastEntityId = crystal.id + lastHit = blockPos explodeTimer.reset() } } From 851dd99931cfd819c80cd30f5b076375975f35f0 Mon Sep 17 00:00:00 2001 From: Matias Date: Sun, 20 Sep 2026 16:29:36 +0300 Subject: [PATCH 7/7] Fixed hitting blocking crystals and cleaned up code --- .../modules/combat/crystalaura/CrystalAura.kt | 51 +++++++++++++------ 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAura.kt b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAura.kt index 37c52b22e..7ab76388f 100644 --- a/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAura.kt +++ b/src/main/kotlin/com/lambda/module/modules/combat/crystalaura/CrystalAura.kt @@ -17,7 +17,6 @@ package com.lambda.module.modules.combat.crystalaura -import com.lambda.Lambda import com.lambda.config.Tab import com.lambda.config.automation.AutomationConfig.Companion.setDefaultAutomationConfig import com.lambda.config.blocks.TargetingSettings @@ -138,7 +137,7 @@ object CrystalAura : Module( private val blueprint = mutableMapOf() var lastHit: BlockPos? = null - var blockingCrystal: BlockPos? = null + var blockingCrystal: EndCrystalEntity? = null private var lastOpportunity: Opportunity? = null private var activeOpportunity: Opportunity? = null private var currentTarget: LivingEntity? = null @@ -329,6 +328,12 @@ object CrystalAura : Module( } } } + if (best.blocked && blockingCrystal != null) { + handleExplosion(blueprint[blockingCrystal?.baseBlockPos?: return]?: return) + + blockingCrystal = null + return + } if (!best.blocked && best.crystal != null) { handleExplosion(best) return @@ -357,14 +362,26 @@ object CrystalAura : Module( resetBlueprint() fun info( - pos: BlockPos, - target: LivingEntity, - blocked: Boolean, - crystal: EndCrystalEntity? = null, - blockingCrystal: EndCrystalEntity? = null + pos: BlockPos, + target: LivingEntity, + blocked: Boolean, + crystal: EndCrystalEntity? = null, + _blockingCrystal: EndCrystalEntity? = null ): Opportunity? { val crystalPos = pos.crystalPosition + if (blockingCrystal != null && crystal == blockingCrystal) { + return Opportunity( + this@CrystalAura, + pos.toImmutable(), + Double.MAX_VALUE, // always prioritize the crystal that is blocking the placement + 0.0, + false, + crystal, + null + ) + } + val targetDamage = crystalDamage(crystalPos, target) if (targetDamage < minTargetDamage) return null @@ -387,7 +404,7 @@ object CrystalAura : Module( selfDamage, blocked, crystal, - blockingCrystal + _blockingCrystal ) } @@ -423,9 +440,9 @@ object CrystalAura : Module( it.baseBlockPos == pos } - val crystalPlaceBox = pos.crystalPlaceHitBox - val blockingCrystal = crystals.firstOrNull { - it.boundingBox.intersects(crystalPlaceBox) + val crystalPlaceBox = pos.up().crystalPlaceHitBox + blockingCrystal = crystals.firstOrNull { + it.blockPos.crystalBox.intersects(crystalPlaceBox) } val blocked = baseCrystal == null && blockingCrystal != null @@ -476,19 +493,21 @@ object CrystalAura : Module( !it.blocked || lastHit == it.blockPos }?.maxByOrNull { it.priority - }.also { - if (it?.blocked ?: false) { - blocked.add(it) - } } + if (activeOpportunity != null) { return@runIfPassed } + + actionMap[actionType]?.filter { + it.blocked + }?.toCollection(blocked) + val best = blocked.maxByOrNull { it.priority } activeOpportunity = actionMap[actionType]?.firstOrNull { - it.blockingCrystal == best?.crystal + it.crystal == best?.blockingCrystal } }