diff --git a/common/src/main/java/com/lambda/mixin/baritone/MixinBaritonePlayerContext.java b/common/src/main/java/com/lambda/mixin/baritone/MixinBaritonePlayerContext.java index f2f15593b..0c81c8144 100644 --- a/common/src/main/java/com/lambda/mixin/baritone/MixinBaritonePlayerContext.java +++ b/common/src/main/java/com/lambda/mixin/baritone/MixinBaritonePlayerContext.java @@ -20,7 +20,7 @@ import baritone.Baritone; import baritone.api.utils.Rotation; import baritone.utils.player.BaritonePlayerContext; -import com.lambda.interaction.RotationManager; +import com.lambda.interaction.request.rotation.RotationManager; import com.lambda.util.BaritoneUtils; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; diff --git a/common/src/main/java/com/lambda/mixin/baritone/MixinLookBehavior.java b/common/src/main/java/com/lambda/mixin/baritone/MixinLookBehavior.java index 7688253a4..62246595b 100644 --- a/common/src/main/java/com/lambda/mixin/baritone/MixinLookBehavior.java +++ b/common/src/main/java/com/lambda/mixin/baritone/MixinLookBehavior.java @@ -21,7 +21,7 @@ import baritone.api.event.events.RotationMoveEvent; import baritone.api.utils.Rotation; import baritone.behavior.LookBehavior; -import com.lambda.interaction.RotationManager; +import com.lambda.interaction.request.rotation.RotationManager; import com.lambda.util.BaritoneUtils; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; diff --git a/common/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java b/common/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java index bbf0ad18e..e856e9ff9 100644 --- a/common/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java +++ b/common/src/main/java/com/lambda/mixin/entity/ClientPlayInteractionManagerMixin.java @@ -18,11 +18,13 @@ package com.lambda.mixin.entity; import com.lambda.event.EventFlow; +import com.lambda.event.events.InventoryEvent; import com.lambda.event.events.PlayerEvent; import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.client.network.ClientPlayerInteractionManager; 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.util.ActionResult; import net.minecraft.util.Hand; @@ -34,6 +36,7 @@ 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.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @@ -83,6 +86,11 @@ public void clickSlotHead(int syncId, int slotId, int button, SlotActionType act if (EventFlow.post(click).isCanceled()) ci.cancel(); } + @Redirect(method = "syncSelectedSlot", at = @At(value = "FIELD", target = "Lnet/minecraft/entity/player/PlayerInventory;selectedSlot:I")) + public int overrideSelectedSlotSync(PlayerInventory instance) { + return EventFlow.post(new InventoryEvent.HotbarSlot.Update(instance.selectedSlot)).getSlot(); + } + @Inject(method = "updateBlockBreakingProgress", at = @At("HEAD"), cancellable = true) private void updateBlockBreakingProgressPre(BlockPos pos, Direction side, CallbackInfoReturnable cir) { var event = EventFlow.post(new PlayerEvent.Breaking.Update(pos, side, currentBreakingProgress)); diff --git a/common/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java b/common/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java index 9b0c3aad4..853362ca8 100644 --- a/common/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java +++ b/common/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java @@ -23,7 +23,7 @@ import com.lambda.event.events.PlayerEvent; import com.lambda.event.events.TickEvent; import com.lambda.interaction.PlayerPacketManager; -import com.lambda.interaction.RotationManager; +import com.lambda.interaction.request.rotation.RotationManager; import net.minecraft.client.input.Input; import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.entity.MovementType; @@ -97,8 +97,6 @@ void sendBegin(CallbackInfo ci) { ci.cancel(); PlayerPacketManager.sendPlayerPackets(); autoJumpEnabled = Lambda.getMc().options.getAutoJump().getValue(); - - RotationManager.update(); } @Inject(method = "tick", at = @At(value = "HEAD")) diff --git a/common/src/main/java/com/lambda/mixin/entity/EntityMixin.java b/common/src/main/java/com/lambda/mixin/entity/EntityMixin.java index e5732ef2d..8cdbe3c8d 100644 --- a/common/src/main/java/com/lambda/mixin/entity/EntityMixin.java +++ b/common/src/main/java/com/lambda/mixin/entity/EntityMixin.java @@ -21,7 +21,7 @@ import com.lambda.event.EventFlow; import com.lambda.event.events.EntityEvent; import com.lambda.event.events.PlayerEvent; -import com.lambda.interaction.RotationManager; +import com.lambda.interaction.request.rotation.RotationManager; import com.lambda.util.math.Vec2d; import net.minecraft.entity.Entity; import net.minecraft.entity.MovementType; diff --git a/common/src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java b/common/src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java index 60d805113..02354fa3a 100644 --- a/common/src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java +++ b/common/src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java @@ -20,7 +20,7 @@ import com.lambda.Lambda; import com.lambda.event.EventFlow; import com.lambda.event.events.MovementEvent; -import com.lambda.interaction.RotationManager; +import com.lambda.interaction.request.rotation.RotationManager; import net.minecraft.entity.LivingEntity; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; diff --git a/common/src/main/java/com/lambda/mixin/entity/PlayerEntityMixin.java b/common/src/main/java/com/lambda/mixin/entity/PlayerEntityMixin.java index e4aca2cdc..a9a3dd519 100644 --- a/common/src/main/java/com/lambda/mixin/entity/PlayerEntityMixin.java +++ b/common/src/main/java/com/lambda/mixin/entity/PlayerEntityMixin.java @@ -19,7 +19,7 @@ import com.lambda.event.EventFlow; import com.lambda.event.events.MovementEvent; -import com.lambda.interaction.RotationManager; +import com.lambda.interaction.request.rotation.RotationManager; import net.minecraft.client.MinecraftClient; import net.minecraft.entity.player.PlayerEntity; import org.spongepowered.asm.mixin.Mixin; diff --git a/common/src/main/java/com/lambda/mixin/entity/PlayerInventoryMixin.java b/common/src/main/java/com/lambda/mixin/entity/PlayerInventoryMixin.java new file mode 100644 index 000000000..6c42a71db --- /dev/null +++ b/common/src/main/java/com/lambda/mixin/entity/PlayerInventoryMixin.java @@ -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.mixin.entity; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerInteractionManager; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.ItemStack; +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 static net.minecraft.entity.player.PlayerInventory.isValidHotbarIndex; + +@Mixin(PlayerInventory.class) +public class PlayerInventoryMixin { + @Inject(method = "getMainHandStack", at = @At(value = "HEAD"), cancellable = true) + public void handleSpoofedMainHandStack(CallbackInfoReturnable cir) { + PlayerInventory instance = (PlayerInventory) (Object) this; + MinecraftClient mc = MinecraftClient.getInstance(); + ClientPlayerInteractionManager interaction = mc.interactionManager; + + if (instance.player != mc.player || interaction == null) return; + + int actualSlot = interaction.lastSelectedSlot; + + cir.setReturnValue( + isValidHotbarIndex(actualSlot) ? instance.main.get(actualSlot) : ItemStack.EMPTY + ); + } +} diff --git a/common/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java b/common/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java index 523276933..3db1ca635 100644 --- a/common/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java +++ b/common/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java @@ -29,9 +29,9 @@ @Mixin(ClientPlayNetworkHandler.class) public class ClientPlayNetworkHandlerMixin { - @Inject(method = "onUpdateSelectedSlot", at = @At("TAIL")) + @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) private void onUpdateSelectedSlot(UpdateSelectedSlotS2CPacket packet, CallbackInfo ci) { - EventFlow.post(new InventoryEvent.SelectedHotbarSlotUpdate(packet.getSlot())); + if (EventFlow.post(new InventoryEvent.HotbarSlot.Sync(packet.getSlot())).isCanceled()) ci.cancel(); } @Inject(method = "onScreenHandlerSlotUpdate", at = @At("TAIL")) diff --git a/common/src/main/java/com/lambda/mixin/render/CameraMixin.java b/common/src/main/java/com/lambda/mixin/render/CameraMixin.java index 8aa9ecb58..58269ecad 100644 --- a/common/src/main/java/com/lambda/mixin/render/CameraMixin.java +++ b/common/src/main/java/com/lambda/mixin/render/CameraMixin.java @@ -17,7 +17,7 @@ package com.lambda.mixin.render; -import com.lambda.interaction.RotationManager; +import com.lambda.interaction.request.rotation.RotationManager; import com.lambda.module.modules.player.Freecam; import com.lambda.module.modules.render.CameraTweaks; import net.minecraft.client.render.Camera; diff --git a/common/src/main/java/com/lambda/mixin/render/DebugHudMixin.java b/common/src/main/java/com/lambda/mixin/render/DebugHudMixin.java index 2d256d60b..883924688 100644 --- a/common/src/main/java/com/lambda/mixin/render/DebugHudMixin.java +++ b/common/src/main/java/com/lambda/mixin/render/DebugHudMixin.java @@ -17,7 +17,7 @@ package com.lambda.mixin.render; -import com.lambda.task.TaskFlow; +import com.lambda.task.RootTask; import com.lambda.util.DebugInfoHud; import net.minecraft.client.gui.hud.DebugHud; import org.spongepowered.asm.mixin.Mixin; @@ -36,6 +36,6 @@ private void onGetRightText(CallbackInfoReturnable> cir) { @Inject(method = "getLeftText", at = @At("TAIL")) private void onGetLeftText(CallbackInfoReturnable> cir) { - cir.getReturnValue().addAll(List.of(TaskFlow.INSTANCE.toString().split("\n"))); + cir.getReturnValue().addAll(List.of(RootTask.INSTANCE.toString().split("\n"))); } } diff --git a/common/src/main/java/com/lambda/mixin/render/LivingEntityRendererMixin.java b/common/src/main/java/com/lambda/mixin/render/LivingEntityRendererMixin.java index f77f55ebe..e79400612 100644 --- a/common/src/main/java/com/lambda/mixin/render/LivingEntityRendererMixin.java +++ b/common/src/main/java/com/lambda/mixin/render/LivingEntityRendererMixin.java @@ -18,7 +18,7 @@ package com.lambda.mixin.render; import com.lambda.Lambda; -import com.lambda.interaction.RotationManager; +import com.lambda.interaction.request.rotation.RotationManager; import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.entity.LivingEntityRenderer; import net.minecraft.client.util.math.MatrixStack; diff --git a/common/src/main/java/com/lambda/mixin/world/ClientWorldMixin.java b/common/src/main/java/com/lambda/mixin/world/ClientWorldMixin.java index 664997d34..877548369 100644 --- a/common/src/main/java/com/lambda/mixin/world/ClientWorldMixin.java +++ b/common/src/main/java/com/lambda/mixin/world/ClientWorldMixin.java @@ -19,10 +19,13 @@ import com.lambda.event.EventFlow; import com.lambda.event.events.EntityEvent; +import com.lambda.event.events.WorldEvent; import com.lambda.module.modules.render.WorldColors; import com.lambda.util.math.ColorKt; +import net.minecraft.block.BlockState; import net.minecraft.client.world.ClientWorld; import net.minecraft.entity.Entity; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -33,10 +36,17 @@ @Mixin(ClientWorld.class) public class ClientWorldMixin { @Inject(method = "addEntity", at = @At("HEAD"), cancellable = true) - private void addEntity(Entity entity, CallbackInfo ci) { + private void onAddEntity(Entity entity, CallbackInfo ci) { if (EventFlow.post(new EntityEvent.EntitySpawn(entity)).isCanceled()) ci.cancel(); } + @Inject(method = "removeEntity", at = @At("HEAD")) + private void onRemoveEntity(int entityId, Entity.RemovalReason removalReason, CallbackInfo ci) { + Entity entity = ((ClientWorld) (Object) this).getEntityById(entityId); + if (entity == null) return; + EventFlow.post(new EntityEvent.EntityRemoval(entity, removalReason)); + } + @Inject(method = "getCloudsColor", at = @At("HEAD"), cancellable = true) private void getCloudsColorInject(float tickDelta, CallbackInfoReturnable cir) { if (WorldColors.INSTANCE.isEnabled() && WorldColors.getCustomClouds()) { @@ -50,4 +60,9 @@ private void getSkyColorInject(Vec3d cameraPos, float tickDelta, CallbackInfoRet cir.setReturnValue(ColorKt.getVec3d(WorldColors.getSkyColor())); } } + + @Inject(method = "handleBlockUpdate", at = @At("HEAD"), cancellable = true) + private void handleBlockUpdateInject(BlockPos pos, BlockState newState, int flags, CallbackInfo ci) { + if (EventFlow.post(new WorldEvent.BlockUpdate.Server(pos, newState)).isCanceled()) ci.cancel(); + } } diff --git a/common/src/main/java/com/lambda/mixin/world/WorldMixin.java b/common/src/main/java/com/lambda/mixin/world/WorldMixin.java index bc809a5e8..48d82c2ba 100644 --- a/common/src/main/java/com/lambda/mixin/world/WorldMixin.java +++ b/common/src/main/java/com/lambda/mixin/world/WorldMixin.java @@ -31,6 +31,6 @@ public abstract class WorldMixin { @Inject(method = "onBlockChanged", at = @At("TAIL")) void onBlockChanged(BlockPos pos, BlockState oldBlock, BlockState newBlock, CallbackInfo ci) { - EventFlow.post(new WorldEvent.BlockChange(pos, oldBlock, newBlock)); + EventFlow.post(new WorldEvent.BlockUpdate.Client(pos, oldBlock, newBlock)); } } diff --git a/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt index 4f1cfecf4..12a1dcb87 100644 --- a/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt +++ b/common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt @@ -28,7 +28,7 @@ import com.lambda.command.LambdaCommand import com.lambda.interaction.construction.StructureRegistry import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint -import com.lambda.task.TaskFlow.run +import com.lambda.task.RootTask.run import com.lambda.task.tasks.BuildTask import com.lambda.task.tasks.BuildTask.Companion.build import com.lambda.threading.runSafe diff --git a/common/src/main/kotlin/com/lambda/command/commands/TaskCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/TaskCommand.kt index 97c33383a..c4aa27a48 100644 --- a/common/src/main/kotlin/com/lambda/command/commands/TaskCommand.kt +++ b/common/src/main/kotlin/com/lambda/command/commands/TaskCommand.kt @@ -21,7 +21,7 @@ import com.lambda.brigadier.argument.literal import com.lambda.brigadier.execute import com.lambda.brigadier.required import com.lambda.command.LambdaCommand -import com.lambda.task.TaskFlow +import com.lambda.task.RootTask import com.lambda.util.Communication.info import com.lambda.util.extension.CommandBuilder @@ -34,15 +34,15 @@ object TaskCommand : LambdaCommand( required(literal("cancel")) { execute { this@TaskCommand.info("Cancelling all tasks") - TaskFlow.cancel() + RootTask.cancel() } } required(literal("clear")) { execute { this@TaskCommand.info("Clearing all tasks") - TaskFlow.cancel() - TaskFlow.clear() + RootTask.cancel() + RootTask.clear() } } } diff --git a/common/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt b/common/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt index 3f9eeef79..367c288c8 100644 --- a/common/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt +++ b/common/src/main/kotlin/com/lambda/command/commands/TransferCommand.kt @@ -28,7 +28,7 @@ import com.lambda.interaction.material.container.ContainerManager import com.lambda.interaction.material.container.ContainerManager.containerWithMaterial import com.lambda.interaction.material.container.ContainerManager.containerWithSpace import com.lambda.interaction.material.transfer.TransferResult -import com.lambda.task.TaskFlow.run +import com.lambda.task.RootTask.run import com.lambda.util.Communication.info import com.lambda.util.extension.CommandBuilder diff --git a/common/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt b/common/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt index 5b3aa74b0..65590984f 100644 --- a/common/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt +++ b/common/src/main/kotlin/com/lambda/config/groups/BuildConfig.kt @@ -24,11 +24,12 @@ interface BuildConfig { val pathing: Boolean val stayInRange: Boolean val collectDrops: Boolean + val maxPendingInteractions: Int + val interactionTimeout: Int // Breaking val rotateForBreak: Boolean val breakConfirmation: Boolean - val maxPendingBreaks: Int val breaksPerTick: Int val breakWeakBlocks: Boolean val forceSilkTouch: Boolean @@ -37,7 +38,5 @@ interface BuildConfig { // Placing val rotateForPlace: Boolean val placeConfirmation: Boolean - val placeTimeout: Int - val maxPendingPlacements: Int val placementsPerTick: Int } diff --git a/common/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt b/common/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt index 89a6deabf..88251114b 100644 --- a/common/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt +++ b/common/src/main/kotlin/com/lambda/config/groups/BuildSettings.kt @@ -18,7 +18,6 @@ package com.lambda.config.groups import com.lambda.config.Configurable -import com.lambda.module.modules.client.TaskFlowModule.setting import com.lambda.util.BlockUtils.allSigns class BuildSettings( @@ -26,29 +25,29 @@ class BuildSettings( vis: () -> Boolean = { true } ) : BuildConfig { enum class Page { - GENERAL, BREAK, PLACE + General, Break, Place } - private val page by c.setting("Build Page", Page.GENERAL, "Current page", vis) + private val page by c.setting("Build Page", Page.General, "Current page", vis) // General - override val pathing by c.setting("Pathing", true, "Path to blocks") { vis() && page == Page.GENERAL } - override val stayInRange by c.setting("Stay In Range", true, "Stay in range of blocks") { vis() && page == Page.GENERAL && pathing } - override val collectDrops by c.setting("Collect All Drops", false, "Collect all drops when breaking blocks") { vis() && page == Page.GENERAL } + override val pathing by c.setting("Pathing", true, "Path to blocks") { vis() && page == Page.General } + override val stayInRange by c.setting("Stay In Range", true, "Stay in range of blocks") { vis() && page == Page.General && pathing } + override val collectDrops by c.setting("Collect All Drops", false, "Collect all drops when breaking blocks") { vis() && page == Page.General } + override val maxPendingInteractions by c.setting("Max Pending Interactions", 1, 1..10, 1, "Dont wait for this many interactions for the server response") { vis() && page == Page.General } // Breaking - override val rotateForBreak by c.setting("Rotate For Break", true, "Rotate towards block while breaking") { vis() && page == Page.BREAK } - override val breakConfirmation by c.setting("Break Confirmation", false, "Wait for block break confirmation") { vis() && page == Page.BREAK } - override val maxPendingBreaks by c.setting("Max Pending Breaks", 1, 1..10, 1, "Maximum pending block breaks") { vis() && page == Page.BREAK } - override val breaksPerTick by c.setting("Instant Breaks Per Tick", 5, 1..30, 1, "Maximum instant block breaks per tick") { vis() && page == Page.BREAK } - override val breakWeakBlocks by c.setting("Break Weak Blocks", false, "Break blocks that dont have structural integrity (e.g: grass)") { vis() && page == Page.BREAK } - override val forceSilkTouch by c.setting("Force Silk Touch", false, "Force silk touch when breaking blocks") { vis() && page == Page.BREAK } - override val ignoredBlocks by setting("Ignored Blocks", allSigns, "Blocks that wont be broken") { vis() && page == Page.BREAK } + override val rotateForBreak by c.setting("Rotate For Break", true, "Rotate towards block while breaking") { vis() && page == Page.Break } + override val breakConfirmation by c.setting("Break Confirmation", false, "Wait for block break confirmation") { vis() && page == Page.Break } + override val breaksPerTick by c.setting("Instant Breaks Per Tick", 5, 1..30, 1, "Maximum instant block breaks per tick") { vis() && page == Page.Break } + override val breakWeakBlocks by c.setting("Break Weak Blocks", false, "Break blocks that dont have structural integrity (e.g: grass)") { vis() && page == Page.Break } + override val forceSilkTouch by c.setting("Force Silk Touch", false, "Force silk touch when breaking blocks") { vis() && page == Page.Break } + override val ignoredBlocks by c.setting("Ignored Blocks", allSigns, "Blocks that wont be broken") { vis() && page == Page.Break } // Placing - override val rotateForPlace by c.setting("Rotate For Place", true, "Rotate towards block while placing") { vis() && page == Page.PLACE } - override val placeConfirmation by c.setting("Place Confirmation", true, "Wait for block placement confirmation") { vis() && page == Page.PLACE } - override val placeTimeout by c.setting("Place Timeout", 10, 1..30, 1, "Timeout for block placement in ticks", unit = " ticks") { vis() && page == Page.PLACE && placeConfirmation } - override val maxPendingPlacements by c.setting("Max Pending Places", 1, 1..10, 1, "Maximum pending block places") { vis() && page == Page.PLACE } - override val placementsPerTick by c.setting("Instant Places Per Tick", 1, 1..30, 1, "Maximum instant block places per tick") { vis() && page == Page.PLACE } + override val rotateForPlace by c.setting("Rotate For Place", true, "Rotate towards block while placing") { vis() && page == Page.Place } + override val placeConfirmation by c.setting("Place Confirmation", true, "Wait for block placement confirmation") { vis() && page == Page.Place } + override val placementsPerTick by c.setting("Instant Places Per Tick", 1, 1..30, 1, "Maximum instant block places per tick") { vis() && page == Page.Place } + + override val interactionTimeout by c.setting("Interaction Timeout", 10, 1..30, 1, "Timeout for block breaks in ticks", unit = " ticks") { vis() && (page == Page.Place && placeConfirmation || page == Page.Break && breakConfirmation) } } diff --git a/common/src/main/kotlin/com/lambda/config/groups/HotbarSettings.kt b/common/src/main/kotlin/com/lambda/config/groups/HotbarSettings.kt new file mode 100644 index 000000000..5d7391bcd --- /dev/null +++ b/common/src/main/kotlin/com/lambda/config/groups/HotbarSettings.kt @@ -0,0 +1,31 @@ +/* + * 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.config.Configurable +import com.lambda.interaction.request.Priority +import com.lambda.interaction.request.hotbar.HotbarConfig + +class HotbarSettings( + c: Configurable, + priority: Priority = 0, + vis: () -> Boolean = { true } +) : HotbarConfig(priority) { + override val keepTicks by c.setting("Keep Ticks", 3, 0..20, 1, "The number of ticks to keep the current hotbar selection active", " ticks", vis) + override var switchPause by c.setting("Switch Pause", 0, 0..20, 1, "The delay in ticks to pause actions after switching to the slot", " ticks", vis) +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/config/groups/InteractionConfig.kt b/common/src/main/kotlin/com/lambda/config/groups/InteractionConfig.kt index 4b1a946fb..04e28b4c2 100644 --- a/common/src/main/kotlin/com/lambda/config/groups/InteractionConfig.kt +++ b/common/src/main/kotlin/com/lambda/config/groups/InteractionConfig.kt @@ -17,28 +17,50 @@ package com.lambda.config.groups -import com.lambda.core.PingManager +import com.lambda.interaction.request.rotation.visibilty.PointSelection interface InteractionConfig { /** - * Maximum distance to interact. + * Maximum entity interaction distance */ - val reach: Double + val attackReach: Double - val visibilityCheck: Boolean + /** + * Maximum block interaction distance + */ + val interactReach: Double + + /** + * Maximum possible interaction distance + * + * Equals to `max(attackReach, placeReach)` if both are present. Equals to one of them otherwise + */ + val scanReach: Double /** - * Will check `resolution squared` many points on a grid on each visible surface of the hit box. + * Whether to include the environment to the ray cast context. + * + * if false: skips walls for entities, skips entities for blocks. + */ + val strictRayCast: Boolean + + /** + * Whether to check if an AABB side is visible. + */ + val checkSideVisibility: Boolean + + /** + * Grid divisions count per surface of the hit box. */ val resolution: Int - val useRayCast: Boolean - val swingHand: Boolean - val inScopeThreshold: Int - val pingTimeout: Boolean + /** + * The way to select the best point. + */ + val pointSelection: PointSelection - val scopeThreshold: Int - get() = - if (pingTimeout) PingManager.lastPing.toInt() / 50 - else inScopeThreshold + /** + * Whether to swing the hand when interacting. + */ + val swingHand: Boolean } diff --git a/common/src/main/kotlin/com/lambda/config/groups/InteractionSettings.kt b/common/src/main/kotlin/com/lambda/config/groups/InteractionSettings.kt index e528607af..02615efeb 100644 --- a/common/src/main/kotlin/com/lambda/config/groups/InteractionSettings.kt +++ b/common/src/main/kotlin/com/lambda/config/groups/InteractionSettings.kt @@ -18,19 +18,53 @@ package com.lambda.config.groups import com.lambda.config.Configurable +import com.lambda.interaction.request.rotation.visibilty.PointSelection +import com.lambda.util.world.raycast.InteractionMask +import kotlin.math.max class InteractionSettings( c: Configurable, - defaultReach: Double = 4.6, + private val usage: InteractionMask, vis: () -> Boolean = { true }, ) : InteractionConfig { - override val reach by c.setting("Reach", defaultReach, 0.1..10.0, 0.1, "Players reach / range", " blocks", vis) - override val useRayCast by c.setting("Raycast", true, "Verify hit vector with ray casting (for very strict ACs)", vis) - override val visibilityCheck by c.setting("Visibility Check", true, "Check if target is visible", vis) - override val resolution by c.setting("Resolution", 4, 1..40, 1, "How many raycast checks per surface (will be squared)") { vis() && useRayCast } - override val swingHand by c.setting("Swing Hand", true, "Swing hand on interactions", vis) - override val pingTimeout by c.setting("Ping Timeout", false, "Timeout on high ping", vis) - override val inScopeThreshold by c.setting("Constant Timeout", 1, 0..20, 1, "How many ticks to wait after target box is in rotation scope"," ticks") { - vis() && !pingTimeout + // Reach + private val useDefaultReach by c.setting("Default Reach", true, "Whether to use vanilla interaction ranges", vis) + private val attackReachSetting = if (usage.entity) c.setting("Attack Reach", DEFAULT_ATTACK_REACH, 1.0..10.0, 0.01, "Maximum entity interaction distance") { vis() && !useDefaultReach } else null + private val interactReachSetting = if (usage.block) c.setting("Interact Reach", DEFAULT_INTERACT_REACH, 1.0..10.0, 0.01, "Maximum block interaction distance") { vis() && !useDefaultReach } else null + + override val attackReach: Double get() { + check(usage.entity) { + "Given interaction config has no attack reach implementation" + } + + return if (useDefaultReach) DEFAULT_ATTACK_REACH else attackReachSetting!!.value + } + + override val interactReach: Double get() { + check(usage.block) { + "Given interaction config has no place reach implementation" + } + + return if (useDefaultReach) DEFAULT_INTERACT_REACH else interactReachSetting!!.value + } + + override val scanReach: Double get() = when (usage) { + InteractionMask.ENTITY -> attackReach + InteractionMask.BLOCK -> interactReach + InteractionMask.BOTH -> max(attackReach, interactReach) + } + + // Point scan + override val strictRayCast by c.setting("Strict Raycast", true, "Whether to include the environment to the ray cast context", vis) + override val checkSideVisibility by c.setting("Visibility Check", true, "Whether to check if an AABB side is visible", vis) + override val resolution by c.setting("Resolution", 5, 1..20, 1, "The amount of grid divisions per surface of the hit box", "", vis) + override val pointSelection by c.setting("Point Selection", PointSelection.Optimum, "The strategy to select the best hit point", vis) + + // Swing + override val swingHand by c.setting("Swing Hand", true, "Whether to swing hand on interactions", vis) + + companion object { + const val DEFAULT_ATTACK_REACH = 3.0 + const val DEFAULT_INTERACT_REACH = 4.5 } } diff --git a/common/src/main/kotlin/com/lambda/config/groups/InventoryConfig.kt b/common/src/main/kotlin/com/lambda/config/groups/InventoryConfig.kt index 26c04d38e..2fc413450 100644 --- a/common/src/main/kotlin/com/lambda/config/groups/InventoryConfig.kt +++ b/common/src/main/kotlin/com/lambda/config/groups/InventoryConfig.kt @@ -32,27 +32,27 @@ interface InventoryConfig { val storePriority: Priority enum class Priority { - WITH_MIN_ITEMS, - WITH_MAX_ITEMS; + WithMinItems, + WithMaxItems; fun materialComparator(selection: StackSelection) = when (this) { - WITH_MAX_ITEMS -> compareBy { it.rank } + WithMaxItems -> compareBy { it.rank } .thenByDescending { it.materialAvailable(selection) } .thenBy { it.name } - WITH_MIN_ITEMS -> compareBy { it.rank } + WithMinItems -> compareBy { it.rank } .thenBy { it.materialAvailable(selection) } .thenBy { it.name } } fun spaceComparator(selection: StackSelection) = when (this) { - WITH_MAX_ITEMS -> compareBy { it.rank } + WithMaxItems -> compareBy { it.rank } .thenByDescending { it.spaceAvailable(selection) } .thenBy { it.name } - WITH_MIN_ITEMS -> compareBy { it.rank } + WithMinItems -> compareBy { it.rank } .thenBy { it.spaceAvailable(selection) } .thenBy { it.name } } diff --git a/common/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt b/common/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt index 74f962802..01dfc1a76 100644 --- a/common/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt +++ b/common/src/main/kotlin/com/lambda/config/groups/InventorySettings.kt @@ -28,6 +28,6 @@ class InventorySettings( override val accessEnderChest by c.setting("Access Ender Chest", false, "Allow access to the player's ender chest", vis) override val actionTimout by c.setting("Action Timeout", 10, 0..100, 1, "How long to wait for after each inventory action", " ticks", vis) override val swapWithDisposables by c.setting("Swap With Disposables", true, "Swap items with disposable ones", vis) - override val providerPriority by c.setting("Provider Priority", InventoryConfig.Priority.WITH_MIN_ITEMS, "What container to prefer when retrieving the item from", vis) - override val storePriority by c.setting("Store Priority", InventoryConfig.Priority.WITH_MIN_ITEMS, "What container to prefer when storing the item to", vis) + override val providerPriority by c.setting("Provider Priority", InventoryConfig.Priority.WithMinItems, "What container to prefer when retrieving the item from", vis) + override val storePriority by c.setting("Store Priority", InventoryConfig.Priority.WithMinItems, "What container to prefer when storing the item to", vis) } \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/config/groups/RotationConfig.kt b/common/src/main/kotlin/com/lambda/config/groups/RotationConfig.kt deleted file mode 100644 index a0e4d4b81..000000000 --- a/common/src/main/kotlin/com/lambda/config/groups/RotationConfig.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2024 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.interaction.rotation.RotationMode - -interface RotationConfig { - /** - * - [RotationMode.SILENT] Spoofing server-side rotation. - * - [RotationMode.SYNC] Spoofing server-side rotation and adjusting client-side movement based on reported rotation (for Grim). - * - [RotationMode.LOCK] Locks the camera client-side. - */ - val rotationMode: RotationMode - - /** - * The rotation speed (in degrees). - */ - val turnSpeed: Double - - /** - * Ticks the rotation should not be changed. - */ - val keepTicks: Int - - /** - * Ticks to rotate back to the actual rotation. - */ - val resetTicks: Int - - interface Instant : RotationConfig { - override val turnSpeed get() = 360.0 - override val keepTicks get() = 1 - override val resetTicks get() = 1 - } -} diff --git a/common/src/main/kotlin/com/lambda/config/groups/RotationSettings.kt b/common/src/main/kotlin/com/lambda/config/groups/RotationSettings.kt index 8981c468c..129490058 100644 --- a/common/src/main/kotlin/com/lambda/config/groups/RotationSettings.kt +++ b/common/src/main/kotlin/com/lambda/config/groups/RotationSettings.kt @@ -18,74 +18,47 @@ package com.lambda.config.groups import com.lambda.config.Configurable -import com.lambda.interaction.rotation.RotationMode +import com.lambda.interaction.request.Priority +import com.lambda.interaction.request.rotation.RotationConfig +import com.lambda.interaction.request.rotation.RotationMode import kotlin.math.* import kotlin.random.Random class RotationSettings( c: Configurable, - vis: () -> Boolean = { true }, -) : RotationConfig { - /** - * The rotation mode - */ - override var rotationMode by c.setting( - "Mode", - RotationMode.SYNC, - "SILENT - server-side rotation, SYNC - server-side rotation; client-side movement, LOCK - Lock camera", - vis - ) + priority: Priority = 0, + vis: () -> Boolean = { true } +) : RotationConfig(priority) { + override var rotationMode by c.setting("Mode", RotationMode.Sync, "SILENT - server-side rotation, SYNC - server-side rotation; client-side movement, LOCK - Lock camera, NONE - No rotation", vis) - /** - * How many ticks to keep the rotation before resetting - */ - override val keepTicks by c.setting("Keep Rotation", 3, 0..10, 1, "Ticks to keep rotation", " ticks", vis) + /** How many ticks to keep the rotation before resetting */ + override val keepTicks by c.setting("Keep Rotation", 3, 0..10, 1, "Ticks to keep rotation", " ticks") { rotate && vis() } - /** - * How many ticks to wait before resetting the rotation - */ - override val resetTicks by c.setting("Reset Rotation", 3, 1..10, 1, "Ticks before rotation is reset", " ticks", vis) + /** How many ticks to wait before resetting the rotation */ + override val decayTicks by c.setting("Reset Rotation", 3, 1..10, 1, "Ticks before rotation is reset", " ticks") { rotate && vis() } - /** - * Whether the rotation is instant - */ - var instant by c.setting("Instant Rotation", true, "Instantly rotate", vis) + /** Whether the rotation is instant */ + var instant by c.setting("Instant Rotation", true, "Instantly rotate") { rotate && vis() } /** * The mean (average/base) value used to calculate rotation speed. * This value represents the center of the distribution. */ - private var mean by c.setting( - "Mean", - 40.0, - 1.0..120.0, - 0.1, - "Average rotation speed", - unit = "°" - ) { vis() && !instant } + var mean by c.setting("Mean", 40.0, 1.0..120.0, 0.1, "Average rotation speed", unit = "°") { rotate && vis() && !instant } /** * The standard deviation for the Gaussian distribution used to calculate rotation speed. * This value represents the spread of rotation speed. */ - private var spread by c.setting( - "Spread", - 10.0, - 0.0..60.0, - 0.1, - "Spread of rotation speeds", - unit = "°" - ) { vis() && !instant } + var spread by c.setting("Spread", 10.0, 0.0..60.0, 0.1, "Spread of rotation speeds", unit = "°") { rotate && vis() && !instant } /** * We must always provide turn speed to the interpolator because the player's yaw might exceed the -180 to 180 range. * Therefore, we cannot simply assign new angles to the player's rotation without getting flagged by Grim's AimModulo360 check. */ - override val turnSpeed get() = if (instant) 180.0 else abs(mean + spread * nextRandom()) - - var speedMultiplier = 1.0 + override val turnSpeed get() = if (instant) 180.0 else abs(mean + spread * nextGaussian()) - private fun nextRandom(): Double { + private fun nextGaussian(): Double { val u1 = Random.nextDouble() val u2 = Random.nextDouble() diff --git a/common/src/main/kotlin/com/lambda/config/groups/Targeting.kt b/common/src/main/kotlin/com/lambda/config/groups/Targeting.kt index dfe49c340..8fe0c4bde 100644 --- a/common/src/main/kotlin/com/lambda/config/groups/Targeting.kt +++ b/common/src/main/kotlin/com/lambda/config/groups/Targeting.kt @@ -20,9 +20,9 @@ package com.lambda.config.groups import com.lambda.config.Configurable import com.lambda.context.SafeContext import com.lambda.friend.FriendManager.isFriend -import com.lambda.interaction.rotation.Rotation.Companion.dist -import com.lambda.interaction.rotation.Rotation.Companion.rotation -import com.lambda.interaction.rotation.Rotation.Companion.rotationTo +import com.lambda.interaction.request.rotation.Rotation.Companion.dist +import com.lambda.interaction.request.rotation.Rotation.Companion.rotation +import com.lambda.interaction.request.rotation.Rotation.Companion.rotationTo import com.lambda.threading.runSafe import com.lambda.util.math.distSq import com.lambda.util.world.fastEntitySearch @@ -32,6 +32,7 @@ import net.minecraft.entity.LivingEntity import net.minecraft.entity.decoration.ArmorStandEntity import net.minecraft.entity.mob.MobEntity import net.minecraft.entity.passive.PassiveEntity +import java.util.UUID /** * Abstract class representing a targeting mechanism for entities in the game. @@ -112,7 +113,7 @@ abstract class Targeting( * @return `true` if the entity is valid for targeting, `false` otherwise. */ open fun validate(player: ClientPlayerEntity, entity: LivingEntity) = when { - !players && (entity is OtherClientPlayerEntity && entity.isFriend) -> false + !players || (entity is OtherClientPlayerEntity && entity.isFriend) -> false !animals && entity is PassiveEntity -> false !hostiles && entity is MobEntity -> false entity is ArmorStandEntity -> false @@ -131,8 +132,10 @@ abstract class Targeting( */ class Combat( owner: Configurable, + defaultRange: Double = 5.0, + maxRange: Double = 16.0, predicate: () -> Boolean = { true }, - ) : Targeting(owner, predicate, 5.0, 16.0) { + ) : Targeting(owner, predicate, defaultRange, maxRange) { /** * The field of view limit for targeting entities. Configurable between 5 and 180 degrees. @@ -153,6 +156,7 @@ abstract class Targeting( */ override fun validate(player: ClientPlayerEntity, entity: LivingEntity): Boolean { if (fov < 180 && player.rotation dist player.eyePos.rotationTo(entity.pos) > fov) return false + if (entity.uuid in illegalTargets) return false return super.validate(player, entity) } @@ -162,16 +166,16 @@ abstract class Targeting( * @return The best [LivingEntity] target, or `null` if no valid target is found. */ fun target(): LivingEntity? = runSafe { - val predicate = { entity: LivingEntity -> - validate(player, entity) - } - return@runSafe fastEntitySearch(targetingRange) { - predicate(it) + validate(player, it) }.minByOrNull { priority.factor(this, it) } } + + private val illegalTargets = setOf( + UUID(5706954458220675710, -6736729783554821869) + ) } /** diff --git a/common/src/main/kotlin/com/lambda/core/Loader.kt b/common/src/main/kotlin/com/lambda/core/Loader.kt index f408518d2..6dde94862 100644 --- a/common/src/main/kotlin/com/lambda/core/Loader.kt +++ b/common/src/main/kotlin/com/lambda/core/Loader.kt @@ -22,14 +22,13 @@ import com.lambda.Lambda.LOG import com.lambda.util.Communication.ascii import com.lambda.util.reflections.getInstances import kotlin.system.measureTimeMillis -import kotlin.time.DurationUnit -import kotlin.time.toDuration +import kotlin.time.Duration.Companion.milliseconds object Loader { private val started = System.currentTimeMillis() val runtime: String - get() = "${(System.currentTimeMillis() - started).toDuration(DurationUnit.MILLISECONDS)}" + get() = "${(System.currentTimeMillis() - started).milliseconds}" private val loadables = getInstances { forPackages("com.lambda") } diff --git a/common/src/main/kotlin/com/lambda/event/events/EntityEvent.kt b/common/src/main/kotlin/com/lambda/event/events/EntityEvent.kt index 219c5397e..fc608b700 100644 --- a/common/src/main/kotlin/com/lambda/event/events/EntityEvent.kt +++ b/common/src/main/kotlin/com/lambda/event/events/EntityEvent.kt @@ -54,6 +54,17 @@ sealed class EntityEvent { val entity: Entity, ) : ICancellable by Cancellable() + /** + * Represents an event triggered when an entity is removed from the game world. + * + * @property entity The entity being removed from the world. + * @property removalReason The reason for the removal of the entity. + */ + data class EntityRemoval( + val entity: Entity, + val removalReason: Entity.RemovalReason, + ) : Event + /** * Represents an event triggered when an entity's tracked data is updated. * diff --git a/common/src/main/kotlin/com/lambda/event/events/InventoryEvent.kt b/common/src/main/kotlin/com/lambda/event/events/InventoryEvent.kt index 026998137..8c4299c90 100644 --- a/common/src/main/kotlin/com/lambda/event/events/InventoryEvent.kt +++ b/common/src/main/kotlin/com/lambda/event/events/InventoryEvent.kt @@ -18,6 +18,9 @@ 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.request.hotbar.HotbarRequest import net.minecraft.item.ItemStack import net.minecraft.screen.ScreenHandler @@ -80,10 +83,33 @@ sealed class InventoryEvent { val stack: ItemStack, ) : Event - /** - * Represents an event triggered when a player switches their selected hotbar slot. - * - * @property slot The index of the newly selected hotbar slot. - */ - data class SelectedHotbarSlotUpdate(val slot: Int) : 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() + + /** + * Represents an event triggered when the server forces the player to change active hotbar slot. + * (world load or anticheat flag). + * + * This event is [Cancellable], you may ignore the server with risk + * of unexpected behaviour depending on the strictness of the server/anticheat. + * + * @property slot The index of the newly selected hotbar slot. + */ + data class Sync(val slot: Int) : HotbarSlot(), ICancellable by Cancellable() + } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/event/events/PlayerPacketEvent.kt b/common/src/main/kotlin/com/lambda/event/events/PlayerPacketEvent.kt index 6bccf9eea..6025f04ab 100644 --- a/common/src/main/kotlin/com/lambda/event/events/PlayerPacketEvent.kt +++ b/common/src/main/kotlin/com/lambda/event/events/PlayerPacketEvent.kt @@ -20,7 +20,7 @@ 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.rotation.Rotation +import com.lambda.interaction.request.rotation.Rotation import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket import net.minecraft.util.math.Vec3d diff --git a/common/src/main/kotlin/com/lambda/event/events/RotationEvent.kt b/common/src/main/kotlin/com/lambda/event/events/RotationEvent.kt index 708c231ed..6fe769595 100644 --- a/common/src/main/kotlin/com/lambda/event/events/RotationEvent.kt +++ b/common/src/main/kotlin/com/lambda/event/events/RotationEvent.kt @@ -20,20 +20,10 @@ 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.rotation.RotationRequest +import com.lambda.interaction.request.rotation.RotationRequest import net.minecraft.client.input.Input sealed class RotationEvent { - /** - * This event allows listeners to register a rotation request to be executed that tick. - * - * Only one rotation can "win" each tick - * - * CAUTION: The listener with the LOWEST priority will win as it is the last to override the context - * - * @property request The rotation context that listeners can set. Only one rotation can "win" each tick - */ - data class Update(var request: RotationRequest?) : ICancellable by Cancellable() /** * This event allows listeners to modify the yaw relative to which the movement input is going to be constructed diff --git a/common/src/main/kotlin/com/lambda/event/events/WorldEvent.kt b/common/src/main/kotlin/com/lambda/event/events/WorldEvent.kt index a853183bd..202ab03f9 100644 --- a/common/src/main/kotlin/com/lambda/event/events/WorldEvent.kt +++ b/common/src/main/kotlin/com/lambda/event/events/WorldEvent.kt @@ -18,7 +18,12 @@ 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.threading.runSafe +import com.lambda.util.BlockUtils.blockState import net.minecraft.block.BlockState +import net.minecraft.block.Blocks import net.minecraft.util.math.BlockPos import net.minecraft.util.shape.VoxelShape import net.minecraft.world.chunk.WorldChunk @@ -41,7 +46,7 @@ sealed class WorldEvent { * These events can be used to listen for and respond to changes in the state * of chunks within the game world, providing contextual data for the operations. */ - sealed class ChunkEvent : Event { + sealed class ChunkEvent { /** * Event triggering upon chunk loading */ @@ -59,17 +64,36 @@ sealed class WorldEvent { } /** - * Represents a block state change event within the world. + * Represents events related to block updates in the world. * - * @property pos The position of the block within the world where the change occurred. - * @property oldState The block state prior to the change. - * @property newState The block state after the change. + * This sealed class encapsulates different types of block update events, + * distinguishing between client-side state changes and server-side updates. */ - data class BlockChange( - val pos: BlockPos, - val oldState: BlockState, - val newState: BlockState, - ) : Event + sealed class BlockUpdate { + /** + * Represents a client side block state change event within the world. + * + * @property pos The position of the block within the world where the change occurred. + * @property oldState The block state prior to the change. + * @property newState The block state after the change. + */ + data class Client( + val pos: BlockPos, + val oldState: BlockState, + val newState: BlockState, + ) : Event + + /** + * Represents a server block update event in the world. + * + * @property pos The position of the block in the world. + * @property newState The new state of the block after the update. + */ + data class Server( + val pos: BlockPos, + val newState: BlockState, + ) : ICancellable by Cancellable() + } /** * Represents a collision event in the game world. diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt index 8e5919078..b17471abc 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt @@ -53,7 +53,7 @@ class ChunkedESP private constructor( } init { - listenConcurrently { event -> + listenConcurrently { event -> world.getWorldChunk(event.pos).renderer.notifyChunks() } diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/DynamicESPBuilders.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/DynamicESPBuilders.kt index 1966ea40e..749648149 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/DynamicESPBuilders.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/DynamicESPBuilders.kt @@ -25,7 +25,7 @@ import com.lambda.util.extension.max import com.lambda.util.extension.min import java.awt.Color -fun DynamicESPRenderer.build( +fun DynamicESPRenderer.ofBox( box: DynamicAABB, filledColor: Color, outlineColor: Color, diff --git a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/StaticESPBuilders.kt b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/StaticESPBuilders.kt index c6ea98193..ad227bd39 100644 --- a/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/StaticESPBuilders.kt +++ b/common/src/main/kotlin/com/lambda/graphics/renderer/esp/builders/StaticESPBuilders.kt @@ -30,42 +30,43 @@ import net.minecraft.util.math.Box import net.minecraft.util.shape.VoxelShape import java.awt.Color -fun StaticESPRenderer.buildMesh( +fun StaticESPRenderer.ofShape( pos: BlockPos, - state: BlockState, filledColor: Color, outlineColor: Color, sides: Int = DirectionMask.ALL, outlineMode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.OR ) = runSafe { - val shape = state.getOutlineShape(world, pos) - buildMesh(shape, filledColor, outlineColor, sides, outlineMode) + val shape = pos.blockState(world).getOutlineShape(world, pos) + ofShape(pos, shape, filledColor, outlineColor, sides, outlineMode) } -fun StaticESPRenderer.buildMesh( +fun StaticESPRenderer.ofShape( pos: BlockPos, + state: BlockState, filledColor: Color, outlineColor: Color, sides: Int = DirectionMask.ALL, outlineMode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.OR ) = runSafe { - val shape = pos.blockState(world).getOutlineShape(world, pos) - buildMesh(shape, filledColor, outlineColor, sides, outlineMode) + val shape = state.getOutlineShape(world, pos) + ofShape(pos, shape, filledColor, outlineColor, sides, outlineMode) } -fun StaticESPRenderer.buildMesh( +fun StaticESPRenderer.ofShape( + pos: BlockPos, shape: VoxelShape, filledColor: Color, outlineColor: Color, sides: Int = DirectionMask.ALL, outlineMode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.OR ) { - shape.boundingBoxes - .forEach { build(it,filledColor, outlineColor, sides, outlineMode) } + shape.boundingBoxes.forEach { + ofBox(it.offset(pos), filledColor, outlineColor, sides, outlineMode) + } } - -fun StaticESPRenderer.build( +fun StaticESPRenderer.ofBox( box: Box, filledColor: Color, outlineColor: Color, @@ -76,26 +77,26 @@ fun StaticESPRenderer.build( buildOutline(box, outlineColor, sides, outlineMode) } -fun StaticESPRenderer.buildFilledMesh( +fun StaticESPRenderer.buildFilledShape( pos: BlockPos, state: BlockState, color: Color, sides: Int = DirectionMask.ALL, ) = runSafe { val shape = state.getOutlineShape(world, pos) - buildFilledMesh(shape, color, sides) + buildFilledShape(shape, color, sides) } -fun StaticESPRenderer.buildFilledMesh( +fun StaticESPRenderer.buildFilledShape( pos: BlockPos, color: Color, sides: Int = DirectionMask.ALL, ) = runSafe { val shape = pos.blockState(world).getOutlineShape(world, pos) - buildFilledMesh(shape, color, sides) + buildFilledShape(shape, color, sides) } -fun StaticESPRenderer.buildFilledMesh( +fun StaticESPRenderer.buildFilledShape( shape: VoxelShape, color: Color, sides: Int = DirectionMask.ALL, @@ -111,7 +112,7 @@ fun StaticESPRenderer.buildFilled( sides: Int = DirectionMask.ALL ) = buildFilled(box, color, color, sides) -fun StaticESPRenderer.buildOutlineMesh( +fun StaticESPRenderer.buildOutlineShape( pos: BlockPos, state: BlockState, color: Color, @@ -119,20 +120,20 @@ fun StaticESPRenderer.buildOutlineMesh( outlineMode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.OR ) = runSafe { val shape = state.getOutlineShape(world, pos) - buildOutlineMesh(shape, color, sides, outlineMode) + buildOutlineShape(shape, color, sides, outlineMode) } -fun StaticESPRenderer.buildOutlineMesh( +fun StaticESPRenderer.buildOutlineShape( pos: BlockPos, color: Color, sides: Int = DirectionMask.ALL, outlineMode: DirectionMask.OutlineMode = DirectionMask.OutlineMode.OR ) = runSafe { val shape = pos.blockState(world).getOutlineShape(world, pos) - buildOutlineMesh(shape, color, sides, outlineMode) + buildOutlineShape(shape, color, sides, outlineMode) } -fun StaticESPRenderer.buildOutlineMesh( +fun StaticESPRenderer.buildOutlineShape( shape: VoxelShape, color: Color, sides: Int = DirectionMask.ALL, diff --git a/common/src/main/kotlin/com/lambda/interaction/MovementConfiguration.kt b/common/src/main/kotlin/com/lambda/interaction/MovementConfiguration.kt index 0d741bb35..4974998c4 100644 --- a/common/src/main/kotlin/com/lambda/interaction/MovementConfiguration.kt +++ b/common/src/main/kotlin/com/lambda/interaction/MovementConfiguration.kt @@ -17,7 +17,7 @@ package com.lambda.interaction -import com.lambda.interaction.rotation.Rotation +import com.lambda.interaction.request.rotation.Rotation import net.minecraft.util.math.Vec3d data class MovementConfiguration( diff --git a/common/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt b/common/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt index 6dc00a86f..11ccdbed8 100644 --- a/common/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt +++ b/common/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt @@ -21,7 +21,8 @@ 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.rotation.Rotation +import com.lambda.interaction.request.rotation.Rotation +import com.lambda.interaction.request.rotation.RotationManager import com.lambda.threading.runSafe import com.lambda.util.collections.LimitedOrderedSet import com.lambda.util.math.approximate diff --git a/common/src/main/kotlin/com/lambda/interaction/blockplace/PlaceFinder.kt b/common/src/main/kotlin/com/lambda/interaction/blockplace/PlaceFinder.kt index fbcfd5297..48bca00e2 100644 --- a/common/src/main/kotlin/com/lambda/interaction/blockplace/PlaceFinder.kt +++ b/common/src/main/kotlin/com/lambda/interaction/blockplace/PlaceFinder.kt @@ -20,7 +20,7 @@ package com.lambda.interaction.blockplace import com.lambda.context.SafeContext import com.lambda.interaction.blockplace.PlaceInteraction.canPlaceAt import com.lambda.interaction.blockplace.PlaceInteraction.isClickable -import com.lambda.interaction.visibilty.VisibilityChecker.getVisibleSurfaces +import com.lambda.interaction.request.rotation.visibilty.VisibilityChecker.getVisibleSurfaces import com.lambda.util.BlockUtils.blockState import com.lambda.util.math.distSq import com.lambda.util.math.getHitVec diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/Blueprint.kt b/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/Blueprint.kt index cf459f0e9..0b3133c20 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/Blueprint.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/Blueprint.kt @@ -19,17 +19,17 @@ package com.lambda.interaction.construction.blueprint import com.lambda.interaction.construction.verify.TargetState import com.lambda.util.BlockUtils.blockPos -import com.lambda.util.collections.ResettableLazy +import com.lambda.util.collections.updatableLazy import com.lambda.util.extension.Structure -import com.lambda.util.math.blockPos +import com.lambda.util.math.roundedBlockPos import net.minecraft.structure.StructureTemplate import net.minecraft.util.math.* abstract class Blueprint { abstract val structure: Structure - val bounds = ResettableLazy { - if (structure.isEmpty()) return@ResettableLazy null + val bounds = updatableLazy { + if (structure.isEmpty()) return@updatableLazy null val maxX = structure.keys.maxOf { it.x } val maxY = structure.keys.maxOf { it.y } val maxZ = structure.keys.maxOf { it.z } @@ -47,7 +47,7 @@ abstract class Blueprint { return Vec3d(d, e, f) } - fun isOutOfBounds(vec3d: Vec3d): Boolean = bounds.value?.contains(vec3d.blockPos) == false + fun isOutOfBounds(vec3d: Vec3d): Boolean = bounds.value?.contains(vec3d.roundedBlockPos) == false val center get() = bounds.value?.center?.blockPos diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/PropagatingBlueprint.kt b/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/PropagatingBlueprint.kt new file mode 100644 index 000000000..854600855 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/PropagatingBlueprint.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2024 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.blueprint + +import com.lambda.context.SafeContext +import com.lambda.threading.runSafe +import com.lambda.util.extension.Structure +import net.minecraft.util.math.Vec3i + +data class PropagatingBlueprint( + val onFinish: SafeContext.(Structure) -> Structure = { it }, +) : Blueprint() { + fun next() { + runSafe { + structure = onFinish(structure) + } + } + + override var structure: Structure = emptyMap() + private set(value) { + field = value + bounds.update() + } + + override fun toString() = "Propagating Blueprint at ${center?.toShortString()}" + + companion object { + fun offset(offset: Vec3i): SafeContext.(Structure) -> Structure = { + it.map { (pos, state) -> + pos.add(offset) to state + }.toMap() + } + + fun propagatingBlueprint( + onFinish: SafeContext.(Structure) -> Structure, + ) = PropagatingBlueprint(onFinish) + } +} diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/DynamicBlueprint.kt b/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/TickingBlueprint.kt similarity index 73% rename from common/src/main/kotlin/com/lambda/interaction/construction/blueprint/DynamicBlueprint.kt rename to common/src/main/kotlin/com/lambda/interaction/construction/blueprint/TickingBlueprint.kt index e121aaf91..67b8a4863 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/DynamicBlueprint.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/blueprint/TickingBlueprint.kt @@ -22,26 +22,19 @@ import com.lambda.threading.runSafe import com.lambda.util.extension.Structure import net.minecraft.util.math.Vec3i -data class DynamicBlueprint( - val init: SafeContext.(Structure) -> Structure = { emptyMap() }, - val update: SafeContext.(Structure) -> Structure = { it }, +data class TickingBlueprint( + val onTick: SafeContext.(Structure) -> Structure = { it }, ) : Blueprint() { - fun update() { + fun tick() { runSafe { - structure = update(structure) - } - } - - fun create() { - runSafe { - structure = init(structure) + structure = onTick(structure) } } override var structure: Structure = emptyMap() private set(value) { field = value - bounds.reset() + bounds.update() } override fun toString() = "Dynamic Blueprint at ${center?.toShortString()}" @@ -53,9 +46,8 @@ data class DynamicBlueprint( }.toMap() } - fun Structure.toBlueprint( - init: SafeContext.(Structure) -> Structure = { this@toBlueprint }, + fun tickingBlueprint( onTick: SafeContext.(Structure) -> Structure, - ) = DynamicBlueprint(init = init, update = onTick) + ) = TickingBlueprint(onTick) } } diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt b/common/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt index 682e7585f..eb7b15677 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/context/BreakContext.kt @@ -17,8 +17,13 @@ package com.lambda.interaction.construction.context +import com.lambda.config.groups.BuildConfig import com.lambda.context.SafeContext -import com.lambda.interaction.rotation.RotationRequest +import com.lambda.graphics.renderer.esp.DirectionMask +import com.lambda.graphics.renderer.esp.DirectionMask.exclude +import com.lambda.interaction.construction.verify.TargetState +import com.lambda.interaction.request.rotation.RotationRequest +import com.lambda.threading.runSafe import com.lambda.util.world.raycast.RayCastUtils.distanceTo import net.minecraft.block.BlockState import net.minecraft.util.Hand @@ -26,6 +31,7 @@ import net.minecraft.util.hit.BlockHitResult import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction import net.minecraft.util.math.Vec3d +import java.awt.Color data class BreakContext( override val pov: Vec3d, @@ -35,6 +41,19 @@ data class BreakContext( override var hand: Hand, val instantBreak: Boolean, ) : BuildContext { + override val targetState = TargetState.Air + private val baseColor = Color(222, 0, 0, 25) + private val sideColor = Color(222, 0, 0, 100) + + override fun interact(swingHand: Boolean) { + runSafe { + if (interaction.updateBlockBreakingProgress(result.blockPos, result.side)) { + if (player.isCreative) interaction.blockBreakingCooldown = 0 + if (swingHand) player.swingHand(hand) + } + } + } + override val expectedPos: BlockPos get() = result.blockPos @@ -52,14 +71,17 @@ data class BreakContext( override fun compareTo(other: BuildContext): Int { return when (other) { is BreakContext -> compareBy { - it.distance + it.rotation.target.angleDistance }.compare(this, other) else -> 1 } } - override fun SafeContext.buildRenderer() { + override fun shouldRotate(config: BuildConfig) = config.rotateForBreak + override fun SafeContext.buildRenderer() { + withState(checkedState, expectedPos, baseColor, DirectionMask.ALL.exclude(result.side)) + withState(checkedState, expectedPos, sideColor, result.side) } } diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt b/common/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt index 6065dfc6a..7ff9d5209 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/context/BuildContext.kt @@ -17,8 +17,10 @@ package com.lambda.interaction.construction.context +import com.lambda.config.groups.BuildConfig import com.lambda.interaction.construction.result.Drawable -import com.lambda.interaction.rotation.RotationRequest +import com.lambda.interaction.construction.verify.TargetState +import com.lambda.interaction.request.rotation.RotationRequest import net.minecraft.block.BlockState import net.minecraft.util.Hand import net.minecraft.util.hit.BlockHitResult @@ -30,14 +32,12 @@ interface BuildContext : Comparable, Drawable { val result: BlockHitResult val distance: Double val expectedState: BlockState + val targetState: TargetState val expectedPos: BlockPos val checkedState: BlockState val hand: Hand val rotation: RotationRequest - override fun compareTo(other: BuildContext): Int { - return compareBy { - it.distance - }.compare(this, other) - } + fun interact(swingHand: Boolean) + fun shouldRotate(config: BuildConfig): Boolean } diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt b/common/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt index 8f24285b7..d76beb3a3 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/context/PlaceContext.kt @@ -17,11 +17,12 @@ package com.lambda.interaction.construction.context +import com.lambda.config.groups.BuildConfig import com.lambda.context.SafeContext import com.lambda.graphics.renderer.esp.DirectionMask import com.lambda.graphics.renderer.esp.DirectionMask.exclude import com.lambda.interaction.construction.verify.TargetState -import com.lambda.interaction.rotation.RotationRequest +import com.lambda.interaction.request.rotation.RotationRequest import com.lambda.threading.runSafe import com.lambda.util.BlockUtils import com.lambda.util.Communication.warn @@ -42,16 +43,15 @@ data class PlaceContext( override val checkedState: BlockState, override val hand: Hand, override val expectedPos: BlockPos, - val targetState: TargetState, + override val targetState: TargetState, val sneak: Boolean, val insideBlock: Boolean, val primeDirection: Direction?, ) : BuildContext { - var placeTick = 0L private val baseColor = Color(35, 188, 254, 25) private val sideColor = Color(35, 188, 254, 100) - fun place(swingHand: Boolean) { + override fun interact(swingHand: Boolean) { runSafe { val actionResult = interaction.interactBlock( player, hand, result @@ -65,7 +65,6 @@ data class PlaceContext( if (!player.getStackInHand(hand).isEmpty && interaction.hasCreativeInventory()) { mc.gameRenderer.firstPersonRenderer.resetEquipProgress(hand) } - placeTick = mc.uptimeInTicks } else { warn("Internal interaction failed with $actionResult") } @@ -82,6 +81,8 @@ data class PlaceContext( it.hand }.thenBy { it.sneak + }.thenBy { + it.rotation.target.angleDistance }.thenBy { it.distance }.thenBy { @@ -95,4 +96,6 @@ data class PlaceContext( withState(expectedState, expectedPos, baseColor, DirectionMask.ALL.exclude(result.side.opposite)) withState(expectedState, expectedPos, sideColor, result.side.opposite) } + + override fun shouldRotate(config: BuildConfig) = config.rotateForPlace } diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt index c1cb63755..183f27117 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/BreakResult.kt @@ -19,6 +19,7 @@ package com.lambda.interaction.construction.result import baritone.api.pathing.goals.GoalBlock import baritone.api.pathing.goals.GoalInverted +import com.lambda.config.groups.InventoryConfig import com.lambda.context.SafeContext import com.lambda.interaction.construction.context.BreakContext import com.lambda.interaction.material.StackSelection.Companion.select @@ -27,7 +28,6 @@ import com.lambda.interaction.material.container.ContainerManager.findBestAvaila import com.lambda.interaction.material.container.ContainerManager.transfer import com.lambda.interaction.material.container.MaterialContainer import com.lambda.interaction.material.container.containers.MainHandContainer -import com.lambda.task.tasks.BreakBlock import net.minecraft.block.BlockState import net.minecraft.item.Item import net.minecraft.util.math.BlockPos @@ -42,18 +42,12 @@ sealed class BreakResult : BuildResult() { */ data class Break( override val blockPos: BlockPos, - val context: BreakContext, - ) : Drawable, Resolvable, BreakResult() { + override val context: BreakContext, + ) : Drawable, Contextual, BreakResult() { override val rank = Rank.BREAK_SUCCESS - private val color = Color(222, 0, 0, 100) - - var collectDrop = false - override val pausesParent get() = collectDrop - - override fun resolve() = BreakBlock(context, collectDrop) override fun SafeContext.buildRenderer() { - withPos(context.expectedPos, color, context.result.side) + with(context) { buildRenderer() } } override fun compareTo(other: ComparableResult): Int { @@ -97,6 +91,7 @@ sealed class BreakResult : BuildResult() { override val blockPos: BlockPos, val blockState: BlockState, val badItem: Item, + val inventory: InventoryConfig ) : Drawable, Resolvable, BreakResult() { override val rank = Rank.BREAK_ITEM_CANT_MINE private val color = Color(255, 0, 0, 100) @@ -104,13 +99,13 @@ sealed class BreakResult : BuildResult() { override val pausesParent get() = true override fun resolve() = - findBestAvailableTool(blockState) + findBestAvailableTool(blockState, inventory = inventory) ?.select() - ?.transfer(MainHandContainer) + ?.transfer(MainHandContainer, inventory) ?: selectStack { isItem(badItem).not() - }.transfer(MainHandContainer) - ?: MaterialContainer.Nothing("Couldn't find a tool for ${blockState.block.name.string} with $badItem in main hand.") + }.transfer(MainHandContainer, inventory) + ?: MaterialContainer.FailureTask("Couldn't find a tool for ${blockState.block.name.string} with $badItem in main hand.") override fun SafeContext.buildRenderer() { withPos(blockPos, color) diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt index 59ac1bca9..2f892faa0 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/BuildResult.kt @@ -19,6 +19,7 @@ package com.lambda.interaction.construction.result import baritone.api.pathing.goals.GoalBlock import baritone.api.pathing.goals.GoalNear +import com.lambda.config.groups.InventoryConfig import com.lambda.context.SafeContext import com.lambda.interaction.construction.context.BuildContext import com.lambda.interaction.material.StackSelection.Companion.select @@ -41,6 +42,10 @@ abstract class BuildResult : ComparableResult, Nameable { open val pausesParent = false override val name: String get() = "${this::class.simpleName} at ${blockPos.toShortString()}" + interface Contextual { + val context: BuildContext + } + /** * The build action is done. */ @@ -192,15 +197,16 @@ abstract class BuildResult : ComparableResult, Nameable { val context: BuildContext, val neededItem: Item, val currentItem: ItemStack, + val inventory: InventoryConfig ) : Drawable, Resolvable, BuildResult() { override val name: String get() = "Wrong item ($currentItem) for ${blockPos.toShortString()} need ${neededItem.name.string}" override val rank = Rank.WRONG_ITEM - private val color = Color(3, 252, 169, 100) + private val color = Color(3, 252, 169, 25) override val pausesParent get() = true override fun resolve() = neededItem.select() - .transfer(MainHandContainer) ?: MaterialContainer.Nothing() + .transfer(MainHandContainer, inventory) ?: MaterialContainer.FailureTask("Couldn't find ${neededItem.name.string} anywhere.") override fun SafeContext.buildRenderer() { if (blockPos.blockState(world).isAir) { @@ -227,15 +233,16 @@ abstract class BuildResult : ComparableResult, Nameable { override val blockPos: BlockPos, val context: BuildContext, val neededStack: ItemStack, + val inventory: InventoryConfig ) : Drawable, Resolvable, BuildResult() { override val name: String get() = "Wrong stack for $blockPos need $neededStack." override val rank = Rank.WRONG_ITEM - private val color = Color(3, 252, 169, 100) + private val color = Color(3, 252, 169, 25) override val pausesParent get() = true override fun resolve() = - neededStack.select().transfer(MainHandContainer) ?: MaterialContainer.Nothing() + neededStack.select().transfer(MainHandContainer, inventory) ?: MaterialContainer.FailureTask("Couldn't find ${neededStack.item.name.string} anywhere.") override fun SafeContext.buildRenderer() { if (blockPos.blockState(world).isAir) { @@ -266,7 +273,7 @@ abstract class BuildResult : ComparableResult, Nameable { ) : Navigable, Drawable, BuildResult() { override val name: String get() = "Out of reach at $blockPos." override val rank = Rank.OUT_OF_REACH - private val color = Color(252, 3, 207, 100) + private val color = Color(252, 3, 207, 25) val distance: Double by lazy { misses.minOfOrNull { pov.distanceTo(it) } ?: 0.0 diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/Drawable.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/Drawable.kt index cec234965..c5fedf9b7 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/result/Drawable.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/Drawable.kt @@ -56,7 +56,10 @@ interface Drawable { } fun SafeContext.withShape(shape: VoxelShape, offset: BlockPos, color: Color, mask: Int = DirectionMask.ALL) { - if (shape.isEmpty) return + if (shape.isEmpty) { + withBox(Box(offset), color, mask) + return + } shape.boundingBoxes.forEach { box -> withBox(box.offset(offset), color, mask) } diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt b/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt index 1ff9a7352..b1eb8067e 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/result/PlaceResult.kt @@ -42,10 +42,14 @@ sealed class PlaceResult : BuildResult() { */ data class Place( override val blockPos: BlockPos, - val context: PlaceContext, - ) : PlaceResult() { + override val context: PlaceContext, + ) : Contextual, Drawable, PlaceResult() { override val rank = Rank.PLACE_SUCCESS + override fun SafeContext.buildRenderer() { + with(context) { buildRenderer() } + } + override fun compareTo(other: ComparableResult): Int { return when (other) { is Place -> context.compareTo(other.context) @@ -80,11 +84,11 @@ sealed class PlaceResult : BuildResult() { } /** - * Represents a scenario where block placement is obstructed by a player. + * Represents a scenario where block placement is obstructed by an entity. * * @property blockPos The position of the block that was attempted to be placed. */ - data class BlockedByPlayer( + data class BlockedByEntity( override val blockPos: BlockPos, ) : Navigable, PlaceResult() { override val rank = Rank.PLACE_BLOCKED_BY_PLAYER diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildGoal.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildGoal.kt index b1259b228..e4d868965 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildGoal.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildGoal.kt @@ -19,11 +19,19 @@ package com.lambda.interaction.construction.simulation import baritone.api.pathing.goals.Goal import com.lambda.util.world.fastVectorOf +import com.lambda.util.world.toFastVec +import net.minecraft.util.math.BlockPos -class BuildGoal(private val sim: Simulation) : Goal { - override fun isInGoal(x: Int, y: Int, z: Int) = - sim.simulate(fastVectorOf(x, y, z)) - .any { it.rank.ordinal < 4 } +class BuildGoal( + private val sim: Simulation, + blocked: BlockPos +) : Goal { + private val blockedVec = blocked.toFastVec() + + override fun isInGoal(x: Int, y: Int, z: Int): Boolean { + val pos = fastVectorOf(x, y, z) + return sim.simulate(pos).any { it.rank.ordinal < 4 } && blockedVec != pos + } override fun heuristic(x: Int, y: Int, z: Int): Double { val bestRank = sim.simulate(fastVectorOf(x, y, z)) diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt index 270b395d1..e68e29f7e 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/BuildSimulator.kt @@ -17,8 +17,10 @@ package com.lambda.interaction.construction.simulation +import com.lambda.config.groups.BuildConfig +import com.lambda.config.groups.InteractionConfig +import com.lambda.config.groups.InventoryConfig import com.lambda.context.SafeContext -import com.lambda.interaction.RotationManager import com.lambda.interaction.construction.blueprint.Blueprint import com.lambda.interaction.construction.context.BreakContext import com.lambda.interaction.construction.context.PlaceContext @@ -28,13 +30,15 @@ import com.lambda.interaction.construction.result.BuildResult import com.lambda.interaction.construction.result.PlaceResult import com.lambda.interaction.construction.verify.TargetState import com.lambda.interaction.material.container.ContainerManager.findBestAvailableTool -import com.lambda.interaction.rotation.Rotation.Companion.rotation -import com.lambda.interaction.rotation.Rotation.Companion.rotationTo -import com.lambda.interaction.rotation.RotationRequest -import com.lambda.interaction.visibilty.VisibilityChecker.getVisibleSurfaces -import com.lambda.interaction.visibilty.VisibilityChecker.optimum -import com.lambda.interaction.visibilty.VisibilityChecker.scanSurfaces -import com.lambda.interaction.visibilty.VisibilityChecker.visibleSides +import com.lambda.interaction.request.rotation.Rotation.Companion.rotation +import com.lambda.interaction.request.rotation.Rotation.Companion.rotationTo +import com.lambda.interaction.request.rotation.RotationConfig +import com.lambda.interaction.request.rotation.RotationManager +import com.lambda.interaction.request.rotation.RotationRequest +import com.lambda.interaction.request.rotation.visibilty.* +import com.lambda.interaction.request.rotation.visibilty.VisibilityChecker.CheckedHit +import com.lambda.interaction.request.rotation.visibilty.VisibilityChecker.getVisibleSurfaces +import com.lambda.interaction.request.rotation.visibilty.VisibilityChecker.scanSurfaces import com.lambda.module.modules.client.TaskFlowModule import com.lambda.threading.runSafe import com.lambda.util.BlockUtils @@ -55,7 +59,6 @@ import net.minecraft.registry.RegistryKeys import net.minecraft.state.property.Properties import net.minecraft.util.Hand import net.minecraft.util.hit.BlockHitResult -import net.minecraft.util.hit.HitResult import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Box import net.minecraft.util.math.Direction @@ -64,27 +67,35 @@ import kotlin.jvm.optionals.getOrNull import kotlin.math.pow object BuildSimulator { - fun Blueprint.simulate(eye: Vec3d, reach: Double = TaskFlowModule.interact.reach) = - runSafe { - structure.entries.flatMap { (pos, target) -> - checkRequirements(pos, target)?.let { - return@flatMap setOf(it) - } - checkPlaceResults(pos, target, eye, reach).let { - if (it.isEmpty()) return@let - return@flatMap it - } - checkBreakResults(pos, eye, reach).let { - if (it.isEmpty()) return@let - return@flatMap it - } - warn("Nothing matched $pos $target") - emptySet() - }.toSet() - } ?: emptySet() + fun Blueprint.simulate( + eye: Vec3d, + interact: InteractionConfig = TaskFlowModule.interact, + rotation: RotationConfig = TaskFlowModule.rotation, + inventory: InventoryConfig = TaskFlowModule.inventory, + build: BuildConfig = TaskFlowModule.build, + ) = runSafe { + structure.entries.flatMap { (pos, target) -> + checkRequirements(pos, target, build)?.let { + return@flatMap setOf(it) + } + checkPlaceResults(pos, target, eye, interact, rotation, inventory).let { + if (it.isEmpty()) return@let + return@flatMap it + } + checkBreakResults(pos, eye, interact, rotation, inventory, build).let { + if (it.isEmpty()) return@let + return@flatMap it + } + warn("Nothing matched $pos $target") + emptySet() + }.toSet() + } ?: emptySet() - private fun SafeContext.checkRequirements(pos: BlockPos, target: TargetState): BuildResult? { - /* the chunk is not loaded */ + private fun SafeContext.checkRequirements( + pos: BlockPos, + target: TargetState, + build: BuildConfig + ): BuildResult? {/* the chunk is not loaded */ if (!world.isChunkLoaded(pos)) { return BuildResult.ChunkNotLoaded(pos) } @@ -97,7 +108,7 @@ object BuildSimulator { } /* block should be ignored */ - if (state.block in TaskFlowModule.build.ignoredBlocks && target.type == TargetState.Type.AIR) { + if (state.block in build.ignoredBlocks && target.type == TargetState.Type.AIR) { return BuildResult.Ignored(pos) } @@ -128,57 +139,59 @@ object BuildSimulator { pos: BlockPos, target: TargetState, eye: Vec3d, - reach: Double, + interact: InteractionConfig, + rotation: RotationConfig, + inventory: InventoryConfig ): Set { val acc = mutableSetOf() val targetPosState = pos.blockState(world) if (target.isAir() || !targetPosState.isReplaceable) return acc - val interact = TaskFlowModule.interact - val rotation = TaskFlowModule.rotation - val preprocessing = target.findProcessorForState() preprocessing.sides.forEach { neighbor -> - val hitPos = if (targetPosState.isAir) pos.offset(neighbor) else pos + val hitPos = if (targetPosState.isAir || targetPosState.isLiquid) pos.offset(neighbor) else pos val hitSide = neighbor.opposite val voxelShape = hitPos.blockState(world).getOutlineShape(world, hitPos) if (voxelShape.isEmpty) return@forEach val boxes = voxelShape.boundingBoxes.map { it.offset(hitPos) } - val verify: HitResult.() -> Boolean = { - blockResult?.blockPos == hitPos && blockResult?.side == hitSide + val verify: CheckedHit.() -> Boolean = { + hit.blockResult?.blockPos == hitPos && hit.blockResult?.side == hitSide } - val validHits = mutableMapOf() + + val validHits = mutableListOf() val misses = mutableSetOf() - val reachSq = reach.pow(2) + val reachSq = interact.interactReach.pow(2) boxes.forEach { box -> - val res = if (TaskFlowModule.interact.useRayCast) interact.resolution else 4 - val sides = if (TaskFlowModule.interact.visibilityCheck) { + val sides = if (interact.checkSideVisibility) { box.getVisibleSurfaces(eye).intersect(setOf(hitSide)) } else { Direction.entries.toSet() } - scanSurfaces(box, sides, res, preprocessing.surfaceScan) { side, vec -> + scanSurfaces(box, sides, interact.resolution, preprocessing.surfaceScan) { _, vec -> if (eye distSq vec > reachSq) { misses.add(vec) return@scanSurfaces } - validHits[vec] = - if (TaskFlowModule.interact.useRayCast && TaskFlowModule.interact.visibilityCheck) { - val cast = eye.rotationTo(vec) - .rayCast(reach, eye) ?: return@scanSurfaces - if (!cast.verify()) return@scanSurfaces + val newRotation = eye.rotationTo(vec) + + val hit = if (interact.strictRayCast) { + newRotation.rayCast(interact.interactReach, eye)?.blockResult + } else { + val hitVec = newRotation.castBox(box, interact.interactReach, eye) + BlockHitResult(hitVec, hitSide, hitPos, false) + } ?: return@scanSurfaces - cast - } else { - BlockHitResult(vec, side, hitPos, false) - } + val checked = CheckedHit(hit, newRotation, interact.interactReach) + if (!checked.verify()) return@scanSurfaces + + validHits.add(checked) } } @@ -192,29 +205,24 @@ object BuildSimulator { return@forEach } - validHits.keys.optimum?.let { optimum -> - validHits.minByOrNull { optimum distSq it.key }?.let { closest -> - val optimumRotation = eye.rotationTo(closest.key) - RotationRequest(optimumRotation, rotation, closest.value, verify) - } - }?.let { rotation -> + interact.pointSelection.select(validHits)?.let { checkedHit -> val optimalStack = target.getStack(world, pos) // ToDo: For each hand and sneak or not? val fakePlayer = copyPlayer(player).apply { setPos(eye.x, eye.y - standingEyeHeight, eye.z) - this.rotation = rotation.rotation + this.rotation = checkedHit.targetRotation } + val checkedResult = checkedHit.hit + val usageContext = ItemUsageContext( fakePlayer, Hand.MAIN_HAND, - rotation.checkedResult?.blockResult, + checkedResult.blockResult, ) val cachePos = CachedBlockPosition( - usageContext.world, - usageContext.blockPos, - false + usageContext.world, usageContext.blockPos, false ) val canBePlacedOn = optimalStack.canPlaceOn( usageContext.world.registryManager.get(RegistryKeys.BLOCK), @@ -256,7 +264,7 @@ object BuildSimulator { } val resultState = blockItem.getPlacementState(context) ?: run { -// acc.add(PlaceResult.BlockedByPlayer(pos)) + acc.add(PlaceResult.BlockedByEntity(pos)) return@forEach } @@ -269,19 +277,17 @@ object BuildSimulator { return@forEach } - val blockHit = rotation.checkedResult?.blockResult ?: return@forEach + val blockHit = checkedResult.blockResult ?: return@forEach val hitBlock = blockHit.blockPos.blockState(world).block val shouldSneak = hitBlock in BlockUtils.interactionBlacklist - val primeDirection = (target as? TargetState.State) - ?.blockState - ?.getOrEmpty(Properties.HORIZONTAL_FACING) - ?.getOrNull() + val primeDirection = + (target as? TargetState.State)?.blockState?.getOrEmpty(Properties.HORIZONTAL_FACING)?.getOrNull() val placeContext = PlaceContext( eye, blockHit, - rotation, + RotationRequest(lookAt(checkedHit.targetRotation, 0.001), rotation), eye.distanceTo(blockHit.pos), resultState, blockHit.blockPos.blockState(world), @@ -295,12 +301,12 @@ object BuildSimulator { val currentHandStack = player.getStackInHand(Hand.MAIN_HAND) if (target is TargetState.Stack && !target.itemStack.equal(currentHandStack)) { - acc.add(BuildResult.WrongStack(pos, placeContext, target.itemStack)) + acc.add(BuildResult.WrongStack(pos, placeContext, target.itemStack, inventory)) return@forEach } if (optimalStack.item != currentHandStack.item) { - acc.add(BuildResult.WrongItem(pos, placeContext, optimalStack.item, currentHandStack)) + acc.add(BuildResult.WrongItem(pos, placeContext, optimalStack.item, currentHandStack, inventory)) return@forEach } @@ -314,13 +320,16 @@ object BuildSimulator { private fun SafeContext.checkBreakResults( pos: BlockPos, eye: Vec3d, - reach: Double, + interact: InteractionConfig, + rotation: RotationConfig, + inventory: InventoryConfig, + build: BuildConfig ): Set { val acc = mutableSetOf() val state = pos.blockState(world) /* is a block that will be destroyed by breaking adjacent blocks */ - if (TaskFlowModule.build.breakWeakBlocks && state.block.hardness == 0f && !state.isAir) { + if (build.breakWeakBlocks && state.block.hardness == 0f && !state.isAir) { acc.add(BuildResult.Ignored(pos)) return acc } @@ -338,7 +347,7 @@ object BuildSimulator { /* liquid needs to be submerged first to be broken */ if (!state.fluidState.isEmpty && state.isReplaceable) { - val submerge = checkPlaceResults(pos, TargetState.Solid, eye, reach) + val submerge = checkPlaceResults(pos, TargetState.Solid, eye, interact, rotation, inventory) acc.add(BreakResult.Submerge(pos, state, submerge)) acc.addAll(submerge) return acc @@ -352,7 +361,7 @@ object BuildSimulator { if (adjacentLiquids.isNotEmpty()) { acc.add(BreakResult.BlockedByLiquid(pos, state)) adjacentLiquids.forEach { - val submerge = checkPlaceResults(pos.offset(it), TargetState.Solid, eye, reach) + val submerge = checkPlaceResults(pos.offset(it), TargetState.Solid, eye, interact, rotation, inventory) acc.addAll(submerge) } return acc @@ -363,14 +372,12 @@ object BuildSimulator { val stack = player.getStackInHand(it) if (stack.isEmpty) return@forEach if (stack.item.canMine(state, world, pos, player)) return@forEach - acc.add(BreakResult.ItemCantMine(pos, state, stack.item)) + acc.add(BreakResult.ItemCantMine(pos, state, stack.item, inventory)) return acc } - val interact = TaskFlowModule.interact - val rotation = TaskFlowModule.rotation val currentRotation = RotationManager.currentRotation - val currentCast = currentRotation.rayCast(reach, eye) + val currentCast = currentRotation.rayCast(interact.interactReach, eye) val voxelShape = state.getOutlineShape(world, pos) voxelShape.getClosestPointTo(eye).ifPresent { @@ -378,46 +385,48 @@ object BuildSimulator { } val boxes = voxelShape.boundingBoxes.map { it.offset(pos) } - val verify: HitResult.() -> Boolean = { blockResult?.blockPos == pos } + val verify: CheckedHit.() -> Boolean = { + hit.blockResult?.blockPos == pos + } + /* the player is buried inside the block */ if (boxes.any { it.contains(eye) }) { currentCast?.blockResult?.let { blockHit -> - val rotationRequest = RotationRequest(currentRotation, rotation, currentCast, verify) + val rotationRequest = RotationRequest( + lookAtBlock(pos, config = interact), rotation + ) val breakContext = BreakContext( - eye, - blockHit, - rotationRequest, - state, - player.activeHand, - instantBreakable(state, pos) + eye, blockHit, rotationRequest, state, player.activeHand, instantBreakable(state, pos) ) acc.add(BreakResult.Break(pos, breakContext)) return acc } } - val validHits = mutableMapOf() + val validHits = mutableListOf() val misses = mutableSetOf() - val reachSq = reach.pow(2) + val reachSq = interact.interactReach.pow(2) boxes.forEach { box -> - val res = if (TaskFlowModule.interact.useRayCast) interact.resolution else 2 - val sides = visibleSides(box, eye, TaskFlowModule.interact) - scanSurfaces(box, sides, res) { side, vec -> + scanSurfaces(box, Direction.entries.toSet(), interact.resolution) { side, vec -> if (eye distSq vec > reachSq) { misses.add(vec) return@scanSurfaces } - validHits[vec] = if (TaskFlowModule.interact.useRayCast && TaskFlowModule.interact.visibilityCheck) { - val cast = eye.rotationTo(vec) - .rayCast(reach, eye) ?: return@scanSurfaces - if (!cast.verify()) return@scanSurfaces + val newRotation = eye.rotationTo(vec) - cast + val hit = if (interact.strictRayCast) { + newRotation.rayCast(interact.interactReach, eye)?.blockResult } else { - BlockHitResult(vec, side, pos, false) - } + val hitVec = newRotation.castBox(box, interact.interactReach, eye) + BlockHitResult(hitVec, side, pos, false) + } ?: return@scanSurfaces + + val checked = CheckedHit(hit, newRotation, interact.interactReach) + if (!checked.verify()) return@scanSurfaces + + validHits.add(checked) } } @@ -427,21 +436,13 @@ object BuildSimulator { return acc } - validHits.keys.optimum?.let { optimum -> - validHits.minByOrNull { optimum distSq it.key }?.let { closest -> - val optimumRotation = eye.rotationTo(closest.key) - RotationRequest(optimumRotation, rotation, closest.value, verify) - } - }?.let { bestRotation -> - val blockHit = bestRotation.checkedResult?.blockResult ?: return@let + interact.pointSelection.select(validHits)?.let { checkedHit -> + val blockHit = checkedHit.hit.blockResult ?: return@let val breakContext = BreakContext( - eye, - blockHit, - bestRotation, - state, - player.activeHand, - instantBreakable(state, pos) + eye, blockHit, RotationRequest( + lookAtBlock(blockHit.blockPos, config = interact), rotation + ), state, player.activeHand, instantBreakable(state, pos) ) /* player has a better tool for the job available */ @@ -454,7 +455,7 @@ object BuildSimulator { acc.add(BreakResult.Break(pos, breakContext)) return acc } ?: run { - acc.add(BuildResult.WrongItem(pos, breakContext, bestTool, player.activeItem)) + acc.add(BuildResult.WrongItem(pos, breakContext, bestTool, player.activeItem, inventory)) return acc } } diff --git a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt index bcab47775..f1b0bb024 100644 --- a/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt +++ b/common/src/main/kotlin/com/lambda/interaction/construction/simulation/Simulation.kt @@ -17,10 +17,15 @@ package com.lambda.interaction.construction.simulation +import com.lambda.config.groups.BuildConfig +import com.lambda.config.groups.InteractionConfig +import com.lambda.config.groups.InventoryConfig import com.lambda.context.SafeContext import com.lambda.interaction.construction.blueprint.Blueprint import com.lambda.interaction.construction.result.BuildResult +import com.lambda.interaction.construction.result.Drawable import com.lambda.interaction.construction.simulation.BuildSimulator.simulate +import com.lambda.interaction.request.rotation.RotationConfig import com.lambda.module.modules.client.TaskFlowModule import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState @@ -28,37 +33,59 @@ import com.lambda.util.world.FastVector import com.lambda.util.world.toBlockPos import com.lambda.util.world.toVec3d import net.minecraft.client.network.ClientPlayerEntity +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 -data class Simulation(val blueprint: Blueprint) { +data class Simulation( + val blueprint: Blueprint, + val interact: InteractionConfig = TaskFlowModule.interact, + val rotation: RotationConfig = TaskFlowModule.rotation, + val inventory: InventoryConfig = TaskFlowModule.inventory, + val build: BuildConfig = TaskFlowModule.build, +) { private val cache: MutableMap> = mutableMapOf() private fun FastVector.toView(): Vec3d = toVec3d().add(0.5, ClientPlayerEntity.DEFAULT_EYE_HEIGHT.toDouble(), 0.5) - fun simulate(pos: FastVector) = - cache.getOrPut(pos) { - val view = pos.toView() - runSafe { - if (blueprint.isOutOfBounds(view) && blueprint.getClosestPointTo(view) - .distanceTo(view) > 10.0 - ) return@getOrPut emptySet() - val blockPos = pos.toBlockPos() - if (!playerFitsIn(Vec3d.ofBottomCenter(blockPos))) return@getOrPut emptySet() - if (!blockPos.down().blockState(world) - .isSideSolidFullSquare(world, blockPos, Direction.UP) - ) return@getOrPut emptySet() - } - blueprint.simulate(view, reach = TaskFlowModule.interact.reach - 1) + fun simulate( + pos: FastVector, + ) = cache.getOrPut(pos) { + val view = pos.toView() + val isOutOfBounds = blueprint.isOutOfBounds(view) + val isTooFar = blueprint.getClosestPointTo(view).distanceTo(view) > 10.0 + runSafe { + if (isOutOfBounds && isTooFar) return@getOrPut emptySet() + val blockPos = pos.toBlockPos() + val isWalkable = blockPos.down().blockState(world).isSideSolidFullSquare(world, blockPos, Direction.UP) + if (!isWalkable) return@getOrPut emptySet() + if (!playerFitsIn(blockPos)) return@getOrPut emptySet() } - private fun SafeContext.playerFitsIn(pos: Vec3d): Boolean { - val pBox = player.boundingBox - val aabb = Box(pBox.minX, pBox.minY - 1.0E-6, pBox.minZ, pBox.maxX, pBox.minY, pBox.maxZ) - return world.isSpaceEmpty(aabb.offset(pos)) + blueprint.simulate(view, interact, rotation, inventory, build) + } + + fun goodPositions() = cache.filter { it.value.any { it.rank.ordinal < 4 } }.map { PossiblePos(it.key.toBlockPos()) } + + class PossiblePos(val pos: BlockPos): Drawable { + override fun SafeContext.buildRenderer() { + withBox(Vec3d.ofBottomCenter(pos).playerBox(), Color(0, 255, 0, 50)) + } + } + + private fun SafeContext.playerFitsIn(pos: BlockPos): Boolean { + return world.isSpaceEmpty(Vec3d.ofBottomCenter(pos).playerBox()) } companion object { - fun Blueprint.simulation() = Simulation(this) + fun Vec3d.playerBox(): Box = Box(x - 0.3, y, z - 0.3, x + 0.3, y + 1.8, z + 0.3).contract(1.0E-6) + + fun Blueprint.simulation( + interact: InteractionConfig = TaskFlowModule.interact, + rotation: RotationConfig = TaskFlowModule.rotation, + inventory: InventoryConfig = TaskFlowModule.inventory, + build: BuildConfig = TaskFlowModule.build, + ) = Simulation(this, interact, rotation, inventory, build) } } diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt index 26ddf85b4..0b0da6b13 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/ContainerManager.kt @@ -17,6 +17,7 @@ package com.lambda.interaction.material.container +import com.lambda.config.groups.InventoryConfig import com.lambda.core.Loadable import com.lambda.event.events.InventoryEvent import com.lambda.event.events.PlayerEvent @@ -91,8 +92,8 @@ object ContainerManager : Loadable { fun container() = container.flatMap { setOf(it) + it.shulkerContainer }.sorted() - fun StackSelection.transfer(destination: MaterialContainer) = - findContainerWithMaterial(this)?.transfer(this, destination) + fun StackSelection.transfer(destination: MaterialContainer, inventory: InventoryConfig = TaskFlowModule.inventory) = + findContainerWithMaterial(this, inventory)?.transfer(this, destination) fun findContainer( block: (MaterialContainer) -> Boolean, @@ -100,8 +101,9 @@ object ContainerManager : Loadable { fun findContainerWithMaterial( selection: StackSelection, + inventory: InventoryConfig ): MaterialContainer? = - containerWithMaterial(selection).firstOrNull() + containerWithMaterial(selection, inventory).firstOrNull() fun findContainerWithSpace( selection: StackSelection, @@ -110,33 +112,36 @@ object ContainerManager : Loadable { fun containerWithMaterial( selection: StackSelection, + inventory: InventoryConfig = TaskFlowModule.inventory, ): List = container() - .sortedWith(TaskFlowModule.inventory.providerPriority.materialComparator(selection)) + .sortedWith(inventory.providerPriority.materialComparator(selection)) .filter { it.materialAvailable(selection) >= selection.count } fun containerWithSpace( selection: StackSelection, + inventory: InventoryConfig = TaskFlowModule.inventory, ): List = container() - .sortedWith(TaskFlowModule.inventory.providerPriority.spaceComparator(selection)) + .sortedWith(inventory.providerPriority.spaceComparator(selection)) .filter { it.spaceAvailable(selection) >= selection.count } fun findBestAvailableTool( blockState: BlockState, availableTools: Set = ItemUtils.tools, + inventory: InventoryConfig = TaskFlowModule.inventory, ) = availableTools.map { it to it.getMiningSpeedMultiplier(it.defaultStack, blockState) }.filter { (item, speed) -> speed > 1.0 && item.isSuitableFor(blockState) - && containerWithMaterial(item.select()).isNotEmpty() + && containerWithMaterial(item.select(), inventory).isNotEmpty() }.maxByOrNull { it.second }?.first - fun findDisposable() = container().find { container -> - TaskFlowModule.inventory.disposables.any { container.materialAvailable(it.item.select()) >= 0 } + fun findDisposable(inventory: InventoryConfig = TaskFlowModule.inventory) = container().find { container -> + inventory.disposables.any { container.materialAvailable(it.item.select()) >= 0 } } class NoContainerFound(selection: StackSelection) : Exception("No container found matching $selection") diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/MaterialContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/MaterialContainer.kt index d5fdd33b8..233337b83 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/MaterialContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/MaterialContainer.kt @@ -80,9 +80,9 @@ abstract class MaterialContainer( this.stacks = stacks } - class Nothing(override val name: String = "Nothing") : Task() { + class FailureTask(override val name: String) : Task() { override fun SafeContext.onStart() { - success() + failure(name) } } @@ -90,13 +90,13 @@ abstract class MaterialContainer( * Withdraws items from the container to the player's inventory. */ @Task.Ta5kBuilder - open fun withdraw(selection: StackSelection): Task<*> = Nothing(name) + open fun withdraw(selection: StackSelection): Task<*>? = null /** * Deposits items from the player's inventory into the container. */ @Task.Ta5kBuilder - open fun deposit(selection: StackSelection): Task<*> = Nothing(name) + open fun deposit(selection: StackSelection): Task<*>? = null open fun matchingStacks(selection: StackSelection) = selection.filterStacks(stacks) diff --git a/common/src/main/kotlin/com/lambda/interaction/material/container/containers/StashContainer.kt b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/StashContainer.kt index c75c6649b..763d8ac52 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/container/containers/StashContainer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/container/containers/StashContainer.kt @@ -19,7 +19,7 @@ package com.lambda.interaction.material.container.containers import com.lambda.interaction.material.StackSelection import com.lambda.interaction.material.container.MaterialContainer -import com.lambda.util.math.blockPos +import com.lambda.util.math.roundedBlockPos import com.lambda.util.text.buildText import com.lambda.util.text.highlighted import com.lambda.util.text.literal @@ -36,7 +36,7 @@ data class StashContainer( override val description = buildText { literal("Stash at ") - highlighted(pos.center.blockPos.toShortString()) + highlighted(pos.center.roundedBlockPos.toShortString()) } override fun materialAvailable(selection: StackSelection): Int = diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/SlotTransfer.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/SlotTransfer.kt index fffc3a84c..b93326f3a 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/transfer/SlotTransfer.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/SlotTransfer.kt @@ -56,7 +56,7 @@ class SlotTransfer @Ta5kBuilder constructor( listen { val current = player.currentScreenHandler if (current != screen) { - failure("Screen has changed. Expected ${screen::class.simpleName} (revision ${screen.revision}, got ${current::class.simpleName} (revision ${current.revision})") + failure("Screen has changed. Expected ${screen::class.simpleName} (revision ${screen.revision}) but got ${current::class.simpleName} (revision ${current.revision})") return@listen } diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferResult.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferResult.kt index 0cfef9a6c..776dcdf4e 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferResult.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/TransferResult.kt @@ -31,11 +31,25 @@ abstract class TransferResult : Task() { override val name = "Container Transfer of [$selection] from [${from.name}] to [${to.name}]" override fun SafeContext.onStart() { - from.withdraw(selection).then { - to.deposit(selection).finally { - success() + 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() } } - }.execute(this@ContainerTransfer) + deposit != null -> { + deposit.finally { success() } + } + else -> null + } + + task?.execute(this@ContainerTransfer) } } diff --git a/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/PickFromInventoryTransaction.kt b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/PickFromInventoryTransaction.kt index 9197741fe..5c8e7d429 100644 --- a/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/PickFromInventoryTransaction.kt +++ b/common/src/main/kotlin/com/lambda/interaction/material/transfer/transaction/PickFromInventoryTransaction.kt @@ -36,7 +36,7 @@ class PickFromInventoryTransaction @Ta5kBuilder constructor( confirming = true } - listen { + listen { finish() } } diff --git a/common/src/main/kotlin/com/lambda/interaction/request/Priority.kt b/common/src/main/kotlin/com/lambda/interaction/request/Priority.kt new file mode 100644 index 000000000..26d6f21e7 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/request/Priority.kt @@ -0,0 +1,20 @@ +/* + * 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.request + +typealias Priority = Int \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/request/Request.kt b/common/src/main/kotlin/com/lambda/interaction/request/Request.kt new file mode 100644 index 000000000..49e253ad2 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/request/Request.kt @@ -0,0 +1,24 @@ +/* + * 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.request + +abstract class Request ( + val priority: Priority +) { + abstract val done: Boolean +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/util/collections/ResettableLazy.kt b/common/src/main/kotlin/com/lambda/interaction/request/RequestConfig.kt similarity index 69% rename from common/src/main/kotlin/com/lambda/util/collections/ResettableLazy.kt rename to common/src/main/kotlin/com/lambda/interaction/request/RequestConfig.kt index 465939fcd..ee906c748 100644 --- a/common/src/main/kotlin/com/lambda/util/collections/ResettableLazy.kt +++ b/common/src/main/kotlin/com/lambda/interaction/request/RequestConfig.kt @@ -15,18 +15,13 @@ * along with this program. If not, see . */ -package com.lambda.util.collections +package com.lambda.interaction.request -class ResettableLazy(private val initializer: () -> T) { - private var _value: T? = null +abstract class RequestConfig ( + val priority: Priority +) { + protected abstract fun requestInternal(request: R) - val value: T? - get() { - if (_value == null) _value = initializer() - return _value - } - - fun reset() { - _value = null - } + fun request(request: R): R = + request.apply(::requestInternal) } \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/request/RequestHandler.kt b/common/src/main/kotlin/com/lambda/interaction/request/RequestHandler.kt new file mode 100644 index 000000000..d09e6213b --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/request/RequestHandler.kt @@ -0,0 +1,69 @@ +/* + * 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.request + +import java.util.concurrent.ConcurrentHashMap + +/** + * This class manages a collection of requests, each associated with a `RequestConfig`. + * It provides a mechanism to register requests and select the highest priority request + * for processing. + */ +abstract class RequestHandler { + + private val requestMap = ConcurrentHashMap, R>() + + /** + * The currently active request. + */ + var currentRequest: R? = null; protected set + + /** + * Registers a new request with the given configuration. + * + * @param config The configuration for the request. + * @param request The request to register. + * @return The registered request. + */ + fun registerRequest(config: RequestConfig, request: R): R { + requestMap[config] = request + return request + } + + /** + * Updates the current request to the highest priority registered request. + * Clears the internal request map after updating. + * + * @return True, if the request was updated. + */ + protected fun updateRequest( + keepIfNull: Boolean = false, + filter: (Map.Entry, R>) -> Boolean = { true } + ): Boolean { + val prev = currentRequest + + currentRequest = requestMap.entries + .filter(filter) + .maxByOrNull { it.key.priority }?.value + + if (keepIfNull && currentRequest == null) currentRequest = prev + + requestMap.clear() + return prev != currentRequest + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarConfig.kt b/common/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarConfig.kt new file mode 100644 index 000000000..140ec6d0f --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarConfig.kt @@ -0,0 +1,52 @@ +/* + * 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.request.hotbar + +import com.lambda.interaction.request.Priority +import com.lambda.interaction.request.RequestConfig + +/* + * Abstract base class for configuring hotbar slot switch behavior. + * + * @param priority The priority of this configuration. + */ +abstract class HotbarConfig( + priority: Priority +) : RequestConfig(priority) { + + /** + * The number of ticks to keep the current hotbar selection active. + */ + abstract val keepTicks: Int + + /** + * The delay in ticks to pause actions after switching to the slot. + * + * Affects the validity state of the request + */ + abstract var switchPause: Int + + /** + * Registers a hotbar request with the HotbarManager. + * + * @param request The hotbar request to register. + */ + override fun requestInternal(request: HotbarRequest) { + HotbarManager.registerRequest(this, request) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarManager.kt b/common/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarManager.kt new file mode 100644 index 000000000..ed8419418 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarManager.kt @@ -0,0 +1,54 @@ +/* + * 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.request.hotbar + +import com.lambda.core.Loadable +import com.lambda.event.events.InventoryEvent +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.request.RequestHandler +import com.lambda.threading.runSafe + +object HotbarManager : RequestHandler(), Loadable { + val serverSlot get() = runSafe { + interaction.lastSelectedSlot + } ?: -1 + + override fun load() = "Loaded Hotbar Manager" + + init { + listen { + it.slot = currentRequest?.slot ?: return@listen + } + + listen { + updateRequest() + } + + listen { + val request = currentRequest ?: return@listen + + request.keepTicks-- + request.switchPause-- + + if (request.keepTicks <= 0) { + currentRequest = null + } + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarRequest.kt b/common/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarRequest.kt new file mode 100644 index 000000000..dc297aeee --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/request/hotbar/HotbarRequest.kt @@ -0,0 +1,33 @@ +/* + * 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.request.hotbar + +import com.lambda.interaction.request.Priority +import com.lambda.interaction.request.Request + +class HotbarRequest( + val slot: Int, + priority: Priority, + var keepTicks: Int = 3, + var switchPause: Int = 0, +) : Request(priority) { + override val done: Boolean get() = + // The request has to be valid at least for 1 tick + // (if for some dumb reason the switch pause is bigger than the decay time) + HotbarManager.serverSlot == slot && (switchPause <= 0 || keepTicks <= 0) +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/rotation/Rotation.kt b/common/src/main/kotlin/com/lambda/interaction/request/rotation/Rotation.kt similarity index 94% rename from common/src/main/kotlin/com/lambda/interaction/rotation/Rotation.kt rename to common/src/main/kotlin/com/lambda/interaction/request/rotation/Rotation.kt index 3a078551b..eadaf72e8 100644 --- a/common/src/main/kotlin/com/lambda/interaction/rotation/Rotation.kt +++ b/common/src/main/kotlin/com/lambda/interaction/request/rotation/Rotation.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024 Lambda + * 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 @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.lambda.interaction.rotation +package com.lambda.interaction.request.rotation import com.lambda.Lambda.mc import com.lambda.threading.runSafe @@ -24,7 +24,7 @@ import com.lambda.util.math.MathUtils.toRadian import com.lambda.util.math.Vec2d import com.lambda.util.math.plus import com.lambda.util.math.times -import com.lambda.util.world.raycast.RayCastMask +import com.lambda.util.world.raycast.InteractionMask import com.lambda.util.world.raycast.RayCastUtils.rayCast import net.minecraft.entity.Entity import net.minecraft.util.math.Box @@ -42,6 +42,10 @@ data class Rotation(val yaw: Double, val pitch: Double) { fun equalFloat(other: Rotation): Boolean = yawF == other.yawF && pitchF == other.pitchF + /** + * Returns the player's rotation vector + * Same as [net.minecraft.entity.Entity.getRotationVec] + */ val vector: Vec3d get() { val yawRad = -yaw.toRadian() @@ -58,7 +62,7 @@ data class Rotation(val yaw: Double, val pitch: Double) { reach: Double, eye: Vec3d? = null, fluids: Boolean = false, - mask: RayCastMask = RayCastMask.BOTH, + mask: InteractionMask = InteractionMask.BOTH, ) = runSafe { rayCast(eye ?: player.eyePos, vector, reach, mask, fluids) } diff --git a/common/src/main/kotlin/com/lambda/interaction/request/rotation/RotationConfig.kt b/common/src/main/kotlin/com/lambda/interaction/request/rotation/RotationConfig.kt new file mode 100644 index 000000000..67d2bd924 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/request/rotation/RotationConfig.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.interaction.request.rotation + +import com.lambda.interaction.request.Priority +import com.lambda.interaction.request.RequestConfig + +/* + * Abstract base class for configuring rotation behavior. + * + * @param priority The priority of this configuration. + */ +abstract class RotationConfig(priority: Priority) : RequestConfig(priority) { + /** + * - [RotationMode.Silent] Spoofing server-side rotation. + * - [RotationMode.Sync] Spoofing server-side rotation and adjusting client-side movement based on reported rotation (for Grim). + * - [RotationMode.Lock] Locks the camera client-side. + * - [RotationMode.None] No rotation. + */ + abstract val rotationMode: RotationMode + + /** + * The rotation speed (in degrees). + */ + abstract val turnSpeed: Double + + /** + * Ticks the rotation should not be changed. + */ + abstract val keepTicks: Int + + /** + * Ticks to rotate back to the actual rotation. + */ + abstract val decayTicks: Int + + val rotate: Boolean get() = rotationMode != RotationMode.None + + override fun requestInternal(request: RotationRequest) { + RotationManager.registerRequest(this, request) + } + + open class Instant(mode: RotationMode, priority: Priority = 0) : RotationConfig(priority) { + override val turnSpeed get() = 360.0 + override val keepTicks get() = 1 + override val decayTicks get() = 1 + override val rotationMode = mode + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt b/common/src/main/kotlin/com/lambda/interaction/request/rotation/RotationManager.kt similarity index 52% rename from common/src/main/kotlin/com/lambda/interaction/RotationManager.kt rename to common/src/main/kotlin/com/lambda/interaction/request/rotation/RotationManager.kt index e2cc6c413..6937f7210 100644 --- a/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt +++ b/common/src/main/kotlin/com/lambda/interaction/request/rotation/RotationManager.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024 Lambda + * 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 @@ -15,24 +15,19 @@ * along with this program. If not, see . */ -package com.lambda.interaction +package com.lambda.interaction.request.rotation -import com.lambda.Lambda.mc -import com.lambda.config.groups.RotationSettings +import com.lambda.Lambda import com.lambda.context.SafeContext import com.lambda.core.Loadable import com.lambda.event.EventFlow.post -import com.lambda.event.events.ConnectionEvent -import com.lambda.event.events.PacketEvent -import com.lambda.event.events.RotationEvent +import com.lambda.event.events.* import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe -import com.lambda.interaction.rotation.Rotation -import com.lambda.interaction.rotation.Rotation.Companion.angleDifference -import com.lambda.interaction.rotation.Rotation.Companion.fixSensitivity -import com.lambda.interaction.rotation.Rotation.Companion.slerp -import com.lambda.interaction.rotation.RotationRequest -import com.lambda.interaction.rotation.RotationMode +import com.lambda.interaction.request.RequestHandler +import com.lambda.interaction.request.rotation.Rotation.Companion.fixSensitivity +import com.lambda.interaction.request.rotation.Rotation.Companion.slerp +import com.lambda.interaction.request.rotation.visibilty.lookAt import com.lambda.module.modules.client.Baritone import com.lambda.threading.runGameScheduled import com.lambda.threading.runSafe @@ -48,73 +43,74 @@ import kotlin.math.round import kotlin.math.sign import kotlin.math.sin -object RotationManager : Loadable { +object RotationManager : RequestHandler(), Loadable { var currentRotation = Rotation.ZERO private var prevRotation = Rotation.ZERO - var currentContext: RotationRequest? = null - - private var keepTicks = 0 - private var pauseTicks = 0 - override fun load() = "Loaded Rotation Manager" - @RotationDsl - fun Any.rotate( - priority: Int = 0, + /** + * Registers a listener called immediately before [RotationManager] handles its context and applies rotation updates. + * + * This is useful if you need to synchronize with the latest player state (including inputs and movement), + * or if you must place your rotation requests as the tick is completed and just before the new rotation is processed. + * + * @param alwaysListen Whether to keep this listener active at all times. Defaults to false. + * @param block A callback providing a [SafeContext] where you can place rotation-related operations or other logic. + */ + fun Any.onRotate( alwaysListen: Boolean = false, - block: RotationRequestBuilder.() -> Unit, - ) { - val builder = RotationRequestBuilder().apply(block) - var lastRequest: RotationRequest? = null - - listen(priority, alwaysListen) { event -> - val rotationRequest = builder.request?.invoke(this, event.request) + block: SafeContext.() -> Unit, + ) = this.listen(0, alwaysListen) { + block() + } - rotationRequest?.let { - event.request = it + init { + // For some reason we have to update AFTER sending player packets + // instead of updating on TickEvent.Pre (am I doing something wrong?) + listen(Int.MIN_VALUE) { + // Update the request + val changed = updateRequest(true) { entry -> + // skip requests that have failed to build the rotation + // to free the request place for others + entry.value.target.targetRotation.value != null } - lastRequest = rotationRequest - } - - listen { event -> - if (event.request == lastRequest) { - builder.onFinish?.invoke(this, event.request) + if (!changed) { // rebuild the rotation if the same context gets used again + currentRequest?.target?.targetRotation?.update() } - } - } - @DslMarker - annotation class RotationDsl + // Calculate the target rotation + val targetRotation = currentRequest?.let { request -> + val rotationTo = if (request.keepTicks >= 0) + request.target.targetRotation.value + ?: currentRotation // same context gets used again && the rotation is null this tick + else player.rotation - class RotationRequestBuilder { - var request: (SafeContext.(lastContext: RotationRequest?) -> RotationRequest?)? = null - var onFinish: (SafeContext.(context: RotationRequest) -> Unit)? = null + val speedMultiplier = if (request.keepTicks < 0) 1.0 else request.speedMultiplier + val turnSpeed = request.turnSpeed() * speedMultiplier - @RotationDsl - fun request(block: SafeContext.(lastContext: RotationRequest?) -> RotationRequest?) { - request = block - } + currentRotation.slerp(rotationTo, turnSpeed) + } ?: player.rotation - @RotationDsl - fun finished(block: SafeContext.(context: RotationRequest) -> Unit) { - onFinish = block - } - } + // Update the current rotation + prevRotation = currentRotation + currentRotation = targetRotation/*.fixSensitivity(prevRotation)*/ - @JvmStatic - fun update() = runSafe { - RotationEvent.Update(BaritoneProcessor.poolContext()).post { - rotate(request) + // Handle LOCK mode + if (currentRequest?.mode == RotationMode.Lock) { + player.yaw = currentRotation.yawF + player.pitch = currentRotation.pitchF + } - currentContext?.let { - RotationEvent.Post(it).post() + // Tick and reset the context + currentRequest?.let { + if (--it.keepTicks > 0) return@let + if (--it.decayTicks >= 0) return@let + currentRequest = null } } - } - init { listen { event -> val packet = event.packet if (packet !is PlayerPositionLookS2CPacket) return@listen @@ -124,101 +120,63 @@ object RotationManager : Loadable { } } - listenUnsafe { + listenUnsafe { reset(Rotation.ZERO) } } - private fun rotate(newContext: RotationRequest?) = runSafe { - prevRotation = currentRotation - - keepTicks-- - pauseTicks-- - - currentContext?.let { current -> - if (keepTicks + current.config.resetTicks < 0 || pauseTicks >= 0) { - currentContext = null - } - } - - newContext?.let { request -> - currentContext = request - keepTicks = request.config.keepTicks - } - - currentRotation = currentContext?.let { context -> - val rotationTo = if (keepTicks >= 0) context.rotation else player.rotation - - var speedMultiplier = (context.config as? RotationSettings)?.speedMultiplier ?: 1.0 - if (keepTicks < 0) speedMultiplier = 1.0 - - val turnSpeed = context.config.turnSpeed * speedMultiplier - - currentRotation - .slerp(rotationTo, turnSpeed) - .fixSensitivity(prevRotation) - .apply { - if (context.config.rotationMode != RotationMode.LOCK) return@apply - player.yaw = this.yawF - player.pitch = this.pitchF - } - } ?: player.rotation - } - private fun reset(rotation: Rotation) { prevRotation = rotation currentRotation = rotation - - currentContext = null - pauseTicks = 3 + currentRequest = null } private val smoothRotation get() = - lerp(mc.partialTicks, prevRotation, currentRotation) + lerp(Lambda.mc.partialTicks, prevRotation, currentRotation) @JvmStatic val lockRotation get() = - if (currentContext?.config?.rotationMode == RotationMode.LOCK) smoothRotation else null + if (currentRequest?.mode == RotationMode.Lock) smoothRotation else null @JvmStatic val renderYaw get() = - if (currentContext?.config == null) null else smoothRotation.yaw.toFloat() + if (currentRequest == null) null else smoothRotation.yaw.toFloat() @JvmStatic val renderPitch get() = - if (currentContext?.config == null) null else smoothRotation.pitch.toFloat() + if (currentRequest == null) null else smoothRotation.pitch.toFloat() @JvmStatic val handYaw get() = - if (currentContext?.config?.rotationMode == RotationMode.LOCK) currentRotation.yaw.toFloat() else null + if (currentRequest?.mode == RotationMode.Lock) currentRotation.yaw.toFloat() else null @JvmStatic val handPitch get() = - if (currentContext?.config?.rotationMode == RotationMode.LOCK) currentRotation.pitch.toFloat() else null + if (currentRequest?.mode == RotationMode.Lock) currentRotation.pitch.toFloat() else null @JvmStatic val movementYaw: Float? get() { - if (currentContext?.config?.rotationMode == RotationMode.SILENT) return null + if (currentRequest?.mode == RotationMode.Silent) return null return currentRotation.yaw.toFloat() } @JvmStatic val movementPitch: Float? get() { - if (currentContext?.config?.rotationMode == RotationMode.SILENT) return null + if (currentRequest?.mode == RotationMode.Silent) return null return currentRotation.pitch.toFloat() } @JvmStatic fun getRotationForVector(deltaTime: Double): Vec2d? { - if (currentContext?.config?.rotationMode == RotationMode.SILENT) return null + if (currentRequest?.mode == RotationMode.Silent) return null val rot = lerp(deltaTime, prevRotation, currentRotation) return Vec2d(rot.yaw, rot.pitch) @@ -227,12 +185,6 @@ object RotationManager : Loadable { object BaritoneProcessor { private var baritoneContext: RotationRequest? = null - fun poolContext(): RotationRequest? { - val ctx = baritoneContext - baritoneContext = null - return ctx - } - private val movementYawList = arrayOf( 0.0, 45.0, 90.0, 135.0, @@ -242,16 +194,19 @@ object RotationManager : Loadable { @JvmStatic fun handleBaritoneRotation(yaw: Float, pitch: Float) { - baritoneContext = RotationRequest(Rotation(yaw, pitch), Baritone.rotation.apply { - if (rotationMode != RotationMode.SILENT) return@apply - rotationMode = RotationMode.SYNC - }) + baritoneContext = lookAt(Rotation(yaw, pitch)).requestBy(Baritone.rotation) + } + + init { + listenUnsafe { + baritoneContext = null + } } @JvmStatic fun processPlayerMovement(input: Input, slowDown: Boolean, slowDownFactor: Float) = runSafe { // The yaw relative to which the movement was constructed - val baritoneYaw = baritoneContext?.rotation?.yaw + val baritoneYaw = baritoneContext?.target?.targetRotation?.value?.yaw val strafeEvent = RotationEvent.StrafeInput(baritoneYaw ?: player.yaw.toDouble(), input) val movementYaw = strafeEvent.post().strafeYaw @@ -269,7 +224,7 @@ object RotationManager : Loadable { // Actual yaw used by the physics engine var actualYaw = currentRotation.yaw - if (currentContext?.config?.rotationMode == RotationMode.SILENT) { + if (currentRequest?.mode == RotationMode.Silent) { actualYaw = player.yaw.toDouble() } @@ -296,7 +251,7 @@ object RotationManager : Loadable { // when yaw difference is too big to compensate it by modifying keyboard input val minYawDist = movementYawList .map { currentRotation.yaw + it } // all possible movement directions (including diagonals) - .minOf { angleDifference(it, baritoneYaw) } + .minOf { Rotation.angleDifference(it, baritoneYaw) } if (minYawDist > 5.0) { input.movementSideways = 0f @@ -304,4 +259,4 @@ object RotationManager : Loadable { } } } -} +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/rotation/RotationMode.kt b/common/src/main/kotlin/com/lambda/interaction/request/rotation/RotationMode.kt similarity index 73% rename from common/src/main/kotlin/com/lambda/interaction/rotation/RotationMode.kt rename to common/src/main/kotlin/com/lambda/interaction/request/rotation/RotationMode.kt index 133f9b24a..2eafbd919 100644 --- a/common/src/main/kotlin/com/lambda/interaction/rotation/RotationMode.kt +++ b/common/src/main/kotlin/com/lambda/interaction/request/rotation/RotationMode.kt @@ -15,15 +15,17 @@ * along with this program. If not, see . */ -package com.lambda.interaction.rotation +package com.lambda.interaction.request.rotation /** - * @property SILENT Spoofing server-side rotation. - * @property SYNC Spoofing server-side rotation and adjusting client-side movement based on reported rotation (for Grim). - * @property LOCK Locks the camera client-side. + * @property Silent Spoofing server-side rotation. + * @property Sync Spoofing server-side rotation and adjusting client-side movement based on reported rotation (for Grim). + * @property Lock Locks the camera client-side. + * @property None No rotation. */ enum class RotationMode { - SILENT, - SYNC, - LOCK + Silent, + Sync, + Lock, + None } diff --git a/common/src/main/kotlin/com/lambda/interaction/request/rotation/RotationRequest.kt b/common/src/main/kotlin/com/lambda/interaction/request/rotation/RotationRequest.kt new file mode 100644 index 000000000..0525a85ae --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/request/rotation/RotationRequest.kt @@ -0,0 +1,46 @@ +/* + * 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.request.rotation + +import com.lambda.interaction.request.Priority +import com.lambda.interaction.request.Request +import com.lambda.interaction.request.rotation.Rotation.Companion.dist +import com.lambda.interaction.request.rotation.visibilty.RotationTarget +import com.lambda.threading.runSafe + +data class RotationRequest( + val target: RotationTarget, + val prio: Priority, + val mode: RotationMode, + var keepTicks: Int = 3, + var decayTicks: Int = 0, + val turnSpeed: () -> Double = { 180.0 }, + val speedMultiplier: Double = 1.0 +) : Request(prio) { + + constructor( + target: RotationTarget, + config: RotationConfig, + speedMultiplier: Double = 1.0 + ) : this(target, config.priority, config.rotationMode, config.keepTicks, config.decayTicks, config::turnSpeed, speedMultiplier) + + override val done: Boolean get() = + mode == RotationMode.None || runSafe { + target.verify(target) + } == true +} diff --git a/common/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/PointSelection.kt b/common/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/PointSelection.kt new file mode 100644 index 000000000..39caf5c0c --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/PointSelection.kt @@ -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.interaction.request.rotation.visibilty + +import com.lambda.interaction.request.rotation.Rotation.Companion.dist +import com.lambda.interaction.request.rotation.RotationManager +import com.lambda.util.math.distSq + +enum class PointSelection(val select: (MutableList) -> VisibilityChecker.CheckedHit?) { + ByRotation({ hits -> + hits.minByOrNull { + RotationManager.currentRotation dist it.targetRotation + } + }), + Optimum( optimum@ { hits -> + val optimum = hits.map { it.hit.pos }.reduceOrNull { acc, vec3d -> + acc.add(vec3d) + }?.multiply(1.0 / hits.size.toDouble()) ?: return@optimum null + + hits.minByOrNull { + it.hit.pos distSq optimum + } + }) +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/RequestedHit.kt b/common/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/RequestedHit.kt new file mode 100644 index 000000000..c1077abaa --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/RequestedHit.kt @@ -0,0 +1,117 @@ +/* + * 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.request.rotation.visibilty + +import com.lambda.interaction.request.rotation.Rotation +import com.lambda.interaction.request.rotation.RotationManager +import com.lambda.interaction.request.rotation.visibilty.VisibilityChecker.ALL_SIDES +import com.lambda.threading.runSafe +import com.lambda.util.BlockUtils.blockState +import com.lambda.util.world.raycast.RayCastUtils.blockResult +import com.lambda.util.world.raycast.RayCastUtils.entityResult +import net.minecraft.entity.LivingEntity +import net.minecraft.util.hit.HitResult +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Box +import net.minecraft.util.math.Direction + +/** + * An abstract class representing a rotation target, which can be either a block or an entity. + */ +abstract class RequestedHit { + abstract val sides: Set + abstract val reach: Double + + /** + * Verifies if the given [HitResult] satisfies the criteria of this + * requested hit. + * + * @param hit The [HitResult] to be verified. + * @return True if the hit satisfies the criteria, false otherwise. + */ + abstract fun verifyHit(hit: HitResult): Boolean + + /** + * Gets a list of bounding boxes associated with this requested hit. + * + * @return A list of [Box] objects representing the bounding boxes. + */ + abstract fun getBoundingBoxes(): List + + /** + * Validates the hit based on the current rotation and reach. + * + * @param rotation The rotation to be checked. + * + * @return True if the hit is valid, false otherwise. + */ + fun verifyRotation(rotation: Rotation = RotationManager.currentRotation) = + rotation.rayCast(reach)?.let { verifyHit(it) } ?: false + + /** + * Validates the hit based on the current rotation and reach and returns the hit result if passed. + * + * @return [HitResult] if passed, null otherwise. + */ + fun hitIfValid() = + RotationManager.currentRotation.rayCast(reach)?.let { + if (!verifyHit(it)) null else it + } + + /** + * Represents an entity hit request. + * + * @param entity The [LivingEntity] to be hit. + * @param reach The maximum distance for the hit. + */ + data class Entity( + val entity: LivingEntity, + override val reach: Double, + override val sides: Set = ALL_SIDES + ) : RequestedHit() { + override fun getBoundingBoxes() = + listOf(entity.boundingBox) + + override fun verifyHit(hit: HitResult) = + hit.entityResult?.entity == entity + } + + /** + * Represents a block hit request + * + * @param blockPos The position of the block. + * @param sides The set of directions for which the hit is requested. + * @param reach The maximum distance for the hit. + */ + data class Block( + val blockPos: BlockPos, + override val sides: Set, + override val reach: Double + ) : RequestedHit() { + override fun getBoundingBoxes(): List = runSafe { + val state = blockPos.blockState(world) + val voxelShape = state.getOutlineShape(world, blockPos) + voxelShape.boundingBoxes.map { it.offset(blockPos) } + } ?: listOf(Box(blockPos)) + + override fun verifyHit(hit: HitResult) = + hit.blockResult?.let { + it.blockPos == blockPos && (sides.isEmpty() || it.side in sides) + } ?: false + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/RequestedHitDsl.kt b/common/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/RequestedHitDsl.kt new file mode 100644 index 000000000..2638b787b --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/RequestedHitDsl.kt @@ -0,0 +1,45 @@ +/* + * 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.request.rotation.visibilty + +import net.minecraft.entity.LivingEntity +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction + +@DslMarker +annotation class RequestedHitDsl + +@RequestedHitDsl +fun blockHit( + blockPos: BlockPos, + sides: Set, + reach: Double +) = RequestedHit.Block(blockPos, sides, reach) + +@RequestedHitDsl +fun blockHit( + blockPos: BlockPos, + side: Direction, + reach: Double +) = RequestedHit.Block(blockPos, setOf(side), reach) + +@RequestedHitDsl +fun entityHit( + entity: LivingEntity, + reach: Double +) = RequestedHit.Entity(entity, reach) \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/RotationTarget.kt b/common/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/RotationTarget.kt new file mode 100644 index 000000000..f77f2915a --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/RotationTarget.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.interaction.request.rotation.visibilty + +import com.lambda.context.SafeContext +import com.lambda.interaction.request.rotation.Rotation +import com.lambda.interaction.request.rotation.Rotation.Companion.dist +import com.lambda.interaction.request.rotation.RotationConfig +import com.lambda.interaction.request.rotation.RotationManager +import com.lambda.interaction.request.rotation.RotationRequest +import com.lambda.threading.runSafe +import com.lambda.util.collections.updatableLazy + +/** + * Represents a target for rotation. + * + * @param hit The requested hit to look at. + * @param verify A lambda to check the active rotation. + * @param buildRotation A lambda that builds the rotation. + */ +data class RotationTarget( + val hit: RequestedHit? = null, + val verify: RotationTarget.() -> Boolean = { hit?.verifyRotation() ?: true }, + private val buildRotation: SafeContext.() -> Rotation?, +) { + val targetRotation = updatableLazy { + runSafe { buildRotation() } + } + + val angleDistance get() = runSafe { + targetRotation.value?.dist(RotationManager.currentRotation) + } ?: 1000.0 + + /** + * Requests a rotation based on the given configuration. + * + * @param config The rotation configuration. + * @return [RotationRequest] containing this [RotationTarget]. + */ + fun requestBy(config: RotationConfig) = + config.request(RotationRequest(this, config)) +} diff --git a/common/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/RotationTargets.kt b/common/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/RotationTargets.kt new file mode 100644 index 000000000..44f0dec78 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/RotationTargets.kt @@ -0,0 +1,140 @@ +/* + * 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.request.rotation.visibilty + +import com.lambda.config.groups.InteractionConfig +import com.lambda.context.SafeContext +import com.lambda.interaction.construction.verify.SurfaceScan +import com.lambda.interaction.request.rotation.Rotation +import com.lambda.interaction.request.rotation.Rotation.Companion.dist +import com.lambda.interaction.request.rotation.RotationManager +import com.lambda.interaction.request.rotation.visibilty.VisibilityChecker.ALL_SIDES +import com.lambda.interaction.request.rotation.visibilty.VisibilityChecker.findRotation +import com.lambda.module.modules.client.TaskFlowModule +import com.lambda.util.world.raycast.InteractionMask +import net.minecraft.entity.LivingEntity +import net.minecraft.util.hit.BlockHitResult +import net.minecraft.util.hit.EntityHitResult +import net.minecraft.util.hit.HitResult +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction + +@DslMarker +annotation class RotationDsl + +/** + * Creates a [RotationTarget] based on a specific angle. + * + * @param angle The target rotation. + * @param maxAngleDistance The maximum allowed distance between the current rotation and the target angle. Defaults to 10.0. + * @return A [RotationTarget] instance. + */ +@RotationDsl +fun lookAt(angle: Rotation, maxAngleDistance: Double = 10.0) = + RotationTarget(null, { + RotationManager.currentRotation dist angle < maxAngleDistance + }) { angle } + +/** + * Creates a [RotationTarget] based on a requested hit, but doesn't build the rotation. + * + * Use this, if you need to build the rotation by yourself. + * + * @param rotation The target rotation. + * @param hit [RequestedHit] to look at. + * @param rotation Custom rotation builder. + * @return A [RotationTarget] instance. + */ +@RotationDsl +fun lookAtHit(hit: RequestedHit, rotation: SafeContext.() -> Rotation?) = + RotationTarget(hit, buildRotation = rotation) + +/** + * Creates a [RotationTarget] based on a [HitResult]. + * + * @param hit [HitResult] to look at. + * @return A [RotationTarget] instance. + */ +@RotationDsl +fun lookAtHit( + hit: HitResult, + config: InteractionConfig = TaskFlowModule.interact, +): RotationTarget? { + return when (hit) { + is BlockHitResult -> lookAtBlock(hit.blockPos, setOf(hit.side), SurfaceScan.DEFAULT, config) + is EntityHitResult -> lookAtEntity(hit.entity as? LivingEntity ?: return null, config) + else -> null + } +} + +/** + * Creates a [RotationTarget] based on an entity. + * + * @param entity The target entity. + * @param config The interaction configuration. Defaults to [TaskFlowModule.interact]. + * @return A [RotationTarget] instance. + */ +@RotationDsl +fun lookAtEntity( + entity: LivingEntity, + config: InteractionConfig = TaskFlowModule.interact +): RotationTarget { + val requestedHit = entityHit(entity, config.attackReach) + + return RotationTarget(requestedHit) { + findRotation( + requestedHit.getBoundingBoxes(), + config.attackReach, + player.eyePos, + ALL_SIDES, + SurfaceScan.DEFAULT, + InteractionMask.ENTITY, + config + ) { requestedHit.verifyHit(hit) }?.targetRotation + } +} + +/** + * Creates a [RotationTarget] based on a block. + * + * @param pos The position of the block. + * @param sides The set of sides to consider for the hit. Defaults to [ALL_SIDES]. + * @param config The interaction configuration. Defaults to [TaskFlowModule.interact]. + * @return A [RotationTarget] instance. + */ +@RotationDsl +fun lookAtBlock( + pos: BlockPos, + sides: Set = ALL_SIDES, + surfaceScan: SurfaceScan = SurfaceScan.DEFAULT, + config: InteractionConfig = TaskFlowModule.interact, +): RotationTarget { + val requestedHit = blockHit(pos, sides, config.interactReach) + + return RotationTarget(requestedHit) { + findRotation( + requestedHit.getBoundingBoxes(), + config.interactReach, + player.eyePos, + sides, + surfaceScan, + InteractionMask.BLOCK, + config + ) { requestedHit.verifyHit(hit) }?.targetRotation + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/visibilty/VisibilityChecker.kt b/common/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/VisibilityChecker.kt similarity index 60% rename from common/src/main/kotlin/com/lambda/interaction/visibilty/VisibilityChecker.kt rename to common/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/VisibilityChecker.kt index 9604f3b48..02ec11dca 100644 --- a/common/src/main/kotlin/com/lambda/interaction/visibilty/VisibilityChecker.kt +++ b/common/src/main/kotlin/com/lambda/interaction/request/rotation/visibilty/VisibilityChecker.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024 Lambda + * 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 @@ -15,25 +15,22 @@ * along with this program. If not, see . */ -package com.lambda.interaction.visibilty +package com.lambda.interaction.request.rotation.visibilty import com.lambda.config.groups.InteractionConfig -import com.lambda.config.groups.RotationConfig +import com.lambda.config.groups.InteractionSettings import com.lambda.context.SafeContext -import com.lambda.interaction.RotationManager +import com.lambda.interaction.construction.processing.PreprocessingStep import com.lambda.interaction.construction.verify.ScanMode import com.lambda.interaction.construction.verify.SurfaceScan -import com.lambda.interaction.rotation.Rotation.Companion.rotationTo -import com.lambda.interaction.rotation.RotationRequest -import com.lambda.module.modules.client.TaskFlowModule -import com.lambda.util.BlockUtils.blockState +import com.lambda.interaction.request.rotation.* +import com.lambda.interaction.request.rotation.Rotation.Companion.rotationTo import com.lambda.util.extension.component6 import com.lambda.util.math.distSq -import com.lambda.util.world.raycast.RayCastUtils.blockResult -import com.lambda.util.world.raycast.RayCastUtils.entityResult -import net.minecraft.entity.Entity +import com.lambda.util.world.raycast.InteractionMask +import net.minecraft.entity.LivingEntity +import net.minecraft.util.hit.EntityHitResult import net.minecraft.util.hit.HitResult -import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Box import net.minecraft.util.math.Direction import net.minecraft.util.math.Vec3d @@ -45,106 +42,116 @@ import kotlin.math.pow */ object VisibilityChecker { - /** - * Attempts to rotate the player to look at a specified entity. - * The function calculates the best rotation to center the player's view on the bounding box of the given entity. - * - * @param rotationConfig Specifies the rotation configuration settings. - * @param interactionConfig Specifies interaction settings, such as range and resolution. - * @param entity The entity to be looked at. - * @return A [RotationRequest] if a valid rotation was found; otherwise, null. - */ - fun SafeContext.lookAtEntity( - rotationConfig: RotationConfig, - interactionConfig: InteractionConfig, - entity: Entity, - ) = findRotation(listOf(entity.boundingBox), rotationConfig, interactionConfig) { - entityResult?.entity == entity - } - - /** - * Attempts to rotate the player to look at a specific block position. - * The function computes the best rotation to focus on a target block's position and side. - * - * @param blockPos The position of the block to look at. - * @param rotationConfig Specifies rotation configuration settings. - * @param interactionConfig Specifies interaction settings, such as range and resolution. - * @param sides Specifies the set of block sides to consider for targeting. - * @return A [RotationRequest] if a valid rotation was found; otherwise, null. - */ - fun SafeContext.lookAtBlock( - blockPos: BlockPos, - rotationConfig: RotationConfig = TaskFlowModule.rotation, - interactionConfig: InteractionConfig = TaskFlowModule.interact, - sides: Set = Direction.entries.toSet(), - ): RotationRequest? { - val state = blockPos.blockState(world) - val voxelShape = state.getOutlineShape(world, blockPos) - val boundingBoxes = voxelShape.boundingBoxes.map { it.offset(blockPos) } - return findRotation(boundingBoxes, rotationConfig, interactionConfig, sides) { - blockResult?.blockPos == blockPos && (blockResult?.side in sides || sides.isEmpty()) - } - } - /** * 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 boxes List of bounding boxes for potential targets. - * @param rotationConfig Specifies rotation configuration settings. - * @param interact Specifies interaction settings, such as range and resolution. - * @param sides Set of block sides to consider for targeting. * @param reach The maximum reach distance for the interaction. * @param eye The player's eye position. - * @param verify A lambda to verify if a [HitResult] meets the desired criteria. - * @return A [RotationRequest] if a valid rotation was found; otherwise, null. + * @param sides Set of block sides to consider for targeting. + * @param interaction Specifies interaction settings, such as side visibility and resolution. + * @param verify A lambda to verify if a [CheckedHit] meets the desired criteria. + * + * @return A [CheckedHit] if a valid rotation was found; otherwise, null. */ fun SafeContext.findRotation( boxes: List, - rotationConfig: RotationConfig, - interact: InteractionConfig, - sides: Set = Direction.entries.toSet(), - reach: Double = interact.reach, - eye: Vec3d = player.getCameraPosVec(1f), - verify: HitResult.() -> Boolean, - ): RotationRequest? { + reach: Double, + eye: Vec3d, + sides: Set, + scan: SurfaceScan, + targetType: InteractionMask, + interaction: InteractionConfig, + verify: CheckedHit.() -> Boolean + ): CheckedHit? { val currentRotation = RotationManager.currentRotation - val currentCast = currentRotation.rayCast(reach, eye) if (boxes.any { it.contains(eye) }) { - return RotationRequest(currentRotation, rotationConfig, currentCast, verify) + currentRotation.rayCast(reach, eye)?.let { hit -> + return CheckedHit(hit, currentRotation, reach) + } } - val validHits = mutableMapOf() - val reachSq = reach.pow(2) + return interaction.pointSelection.select( + collectHitsFor(boxes, reach, eye, sides, scan, targetType, interaction, verify) + ) + } + + /** + * Finds a collection of [CheckedHit] that intersect with one of the specified bounding boxes, allowing the player to look at entities or blocks. + * + * @param boxes List of bounding boxes for potential targets. + * @param reach The maximum reach distance for the interaction. + * @param eye The player's eye position. + * @param sides Set of block sides to consider for targeting. + * @param scan Configuration specifying the axis and mode of the scan (default is `SurfaceScan.DEFAULT`). + * @param interaction Specifies interaction settings, such as side visibility and resolution. + * @param verify A lambda to verify if a [CheckedHit] meets the desired criteria. + * + * @return A collection of [CheckedHit] with valid angles found + */ + fun SafeContext.collectHitsFor( + boxes: List, + reach: Double, + eye: Vec3d = player.eyePos, + sides: Set = ALL_SIDES, + scan: SurfaceScan = SurfaceScan.DEFAULT, + targetType: InteractionMask, + interaction: InteractionConfig, + verify: CheckedHit.() -> Boolean, + ) = mutableListOf().apply { + val reachSq = interaction.scanReach.pow(2) boxes.forEach { box -> - val visible = visibleSides(box, eye, interact) + val visible = visibleSides(box, eye, interaction.checkSideVisibility) - scanSurfaces(box, visible.intersect(sides), interact.resolution) { _, vec -> + scanSurfaces(box, visible.intersect(sides), interaction.resolution, scan) { _, vec -> if (eye distSq vec > reachSq) return@scanSurfaces val newRotation = eye.rotationTo(vec) - val cast = newRotation.rayCast(reach, eye) ?: return@scanSurfaces - if (!cast.verify()) return@scanSurfaces + val mask = if (interaction.strictRayCast) InteractionMask.BOTH else targetType + val hit = newRotation.rayCast(reach, eye, mask = mask) ?: return@scanSurfaces + + val checked = CheckedHit(hit, newRotation, reach) + if (!checked.verify()) return@scanSurfaces - validHits[vec] = cast + add(checked) } } + } + + private fun SafeContext.collectHitsInternal( + boxes: List, + reach: Double, + eye: Vec3d, + sides: Set, + scan: SurfaceScan, + targetType: InteractionMask, + entity: LivingEntity?, + interaction: InteractionConfig, + verify: CheckedHit.() -> Boolean, + ) = mutableListOf().apply { + val reachSq = interaction.scanReach.pow(2) + + boxes.forEach { box -> + val visible = visibleSides(box, eye, interaction.checkSideVisibility) - // Way stable - /*validHits.minByOrNull { eye.rotationTo(it.key) dist currentRotation }?.let { closest -> - return RotationContext(eye.rotationTo(closest.key), rotationConfig, closest.value, verify) - }*/ + scanSurfaces(box, visible.intersect(sides), interaction.resolution, scan) { _, vec -> + if (eye distSq vec > reachSq) return@scanSurfaces + + val newRotation = eye.rotationTo(vec) - validHits.keys.optimum?.let { optimum -> - validHits.minByOrNull { optimum distSq it.key }?.let { closest -> - val optimumRotation = eye.rotationTo(closest.key) - return RotationRequest(optimumRotation, rotationConfig, closest.value, verify) + val mask = if (interaction.strictRayCast || entity == null) InteractionMask.BOTH else targetType + val hit = newRotation.rayCast(reach, eye, mask = mask) ?: return@scanSurfaces + + val checked = CheckedHit(hit, newRotation, reach) + if (!checked.verify()) return@scanSurfaces + + add(checked) } } - - return null } /** @@ -157,7 +164,7 @@ object VisibilityChecker { * @param scan Configuration specifying the axis and mode of the scan (default is `SurfaceScan.DEFAULT`). * @param check A callback function that performs an action for each surface point, receiving the direction of the surface and the current 3D vector. */ - inline fun scanSurfaces( + fun scanSurfaces( box: Box, excludedSides: Set = emptySet(), resolution: Int = 5, @@ -166,7 +173,7 @@ object VisibilityChecker { ) { excludedSides.forEach { side -> if (excludedSides.isNotEmpty() && side !in excludedSides) return@forEach - val (minX, minY, minZ, maxX, maxY, maxZ) = box.shrink(0.01, 0.01, 0.01).bounds(side) + val (minX, minY, minZ, maxX, maxY, maxZ) = box.contract(1.0E-3).bounds(side) val stepX = (maxX - minX) / resolution val stepY = (maxY - minY) / resolution val stepZ = (maxZ - minZ) / resolution @@ -213,24 +220,16 @@ object VisibilityChecker { } } - /** - * Determines the approximate central point (optimum) of a set of 3D vectors. - */ - val Set.optimum: Vec3d? - get() = reduceOrNull { acc, vec3d -> - acc.add(vec3d) - }?.multiply(1.0 / size.toDouble()) - /** * 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. - * @param interactionSettings The settings that define how visibility checks are handled. + * @param visibilityCheck Whether to check the visibility of the side. * @return A set of directions corresponding to the visible sides of the box. */ - fun visibleSides(box: Box, eye: Vec3d, interactionSettings: InteractionConfig) = - if (interactionSettings.visibilityCheck) { + private fun visibleSides(box: Box, eye: Vec3d, visibilityCheck: Boolean) = + if (visibilityCheck) { box.getVisibleSurfaces(eye) } else Direction.entries.toSet() @@ -240,7 +239,7 @@ object VisibilityChecker { * @param side The side of the box to calculate bounds for. * @return An array of doubles representing the side's bounds. */ - fun Box.bounds(side: Direction) = + 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) @@ -276,4 +275,12 @@ object VisibilityChecker { diff > limit -> add(positiveSide) } } + + val ALL_SIDES = Direction.entries.toSet() + + class CheckedHit( + val hit: HitResult, + val targetRotation: Rotation, + val reach: Double + ) } diff --git a/common/src/main/kotlin/com/lambda/interaction/rotation/RotationRequest.kt b/common/src/main/kotlin/com/lambda/interaction/rotation/RotationRequest.kt deleted file mode 100644 index 5116fa6e8..000000000 --- a/common/src/main/kotlin/com/lambda/interaction/rotation/RotationRequest.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2024 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.rotation - -import com.lambda.config.groups.RotationConfig -import com.lambda.interaction.RotationManager -import com.lambda.threading.runSafe -import com.lambda.util.world.raycast.RayCastUtils.orMiss -import net.minecraft.util.hit.HitResult - -data class RotationRequest( - val rotation: Rotation, - val config: RotationConfig, - val checkedResult: HitResult? = null, - val verify: HitResult.() -> Boolean = { true }, -) { - val isValid: Boolean get() = runSafe { - // ToDo: Use proper reach - val result = RotationManager.currentRotation.rayCast(10.0, player.eyePos) - verify(result.orMiss) - } ?: false -} diff --git a/common/src/main/kotlin/com/lambda/module/hud/Coordinates.kt b/common/src/main/kotlin/com/lambda/module/hud/Coordinates.kt new file mode 100644 index 000000000..c966b61bf --- /dev/null +++ b/common/src/main/kotlin/com/lambda/module/hud/Coordinates.kt @@ -0,0 +1,61 @@ +/* + * 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.context.SafeContext +import com.lambda.module.HudModule +import com.lambda.module.tag.ModuleTag +import com.lambda.threading.runSafe +import com.lambda.util.Formatting.asString +import com.lambda.util.Formatting.string +import com.lambda.util.extension.dimensionName +import com.lambda.util.math.netherCoord +import com.lambda.util.math.overworldCoord +import net.minecraft.registry.RegistryKey +import net.minecraft.world.World + +object Coordinates : HudModule( + name = "Coordinates", + description = "Show your coordinates", + defaultTags = setOf(ModuleTag.CLIENT), +) { + private val showDimension by setting("Show Dimension", true) + private val decimals by setting("Decimals", 2, 0..4, 1) + + private val SafeContext.text: String + get() = "XYZ ${if (showDimension) dimensionName else ""} ${positionForDimension(world.registryKey)}" + + // TODO: Replace by LambdaAtlas height cache and actually build a proper text with highlighted parameters + + override val height: Double get() = 20.0 + override val width: Double get() = 50.0 + + init { + onRender { + runSafe { + font.build(text, position) + } + } + } + + private fun SafeContext.positionForDimension(dimension: RegistryKey) = + when (dimension) { + World.NETHER -> "[${player.netherCoord.asString(decimals)}, ${player.netherCoord.z.string}] ${player.overworldCoord.asString(decimals)}" + else -> "${player.overworldCoord.asString(decimals)} [${player.netherCoord.x.string}, ${player.netherCoord.z.string}]" + } +} diff --git a/common/src/main/kotlin/com/lambda/module/hud/TPS.kt b/common/src/main/kotlin/com/lambda/module/hud/TPS.kt new file mode 100644 index 000000000..fa5b0b683 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/module/hud/TPS.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.hud + +import com.lambda.module.HudModule +import com.lambda.module.tag.ModuleTag +import com.lambda.util.Formatting.string +import com.lambda.util.NamedEnum +import com.lambda.util.ServerTPS.averageMSPerTick + +object TPS : HudModule( + name = "TPS", + description = "Display the server's tick rate", + defaultTags = setOf(ModuleTag.CLIENT, ModuleTag.NETWORK), +) { + private val format by setting("Tick format", TickFormat.TPS) + + private val text: String get() = "${format.displayName}: ${format.output().string}${format.unit}" + + // TODO: Replace by LambdaAtlas height cache and actually build a proper text with highlighted parameters + + override val height: Double get() = 20.0 + override val width: Double get() = 50.0 + + init { + onRender { + font.build(text, position) + } + } + + @Suppress("unused") + private enum class TickFormat( + val output: () -> Double, + override val displayName: String, + val unit: String = "" + ) : NamedEnum { + TPS({ 1000 / averageMSPerTick }, "TPS"), + MSPT({ averageMSPerTick }, "MSPT", " ms"), + Normalized({ 50 / averageMSPerTick }, "TPS"), + Percentage({ 5000 / averageMSPerTick }, "TPS", "%") + } +} diff --git a/common/src/main/kotlin/com/lambda/module/hud/TaskFlowHUD.kt b/common/src/main/kotlin/com/lambda/module/hud/TaskFlowHUD.kt index 1e741fea7..b8c41ed20 100644 --- a/common/src/main/kotlin/com/lambda/module/hud/TaskFlowHUD.kt +++ b/common/src/main/kotlin/com/lambda/module/hud/TaskFlowHUD.kt @@ -19,7 +19,7 @@ package com.lambda.module.hud import com.lambda.module.HudModule import com.lambda.module.tag.ModuleTag -import com.lambda.task.TaskFlow +import com.lambda.task.RootTask import com.lambda.util.math.Vec2d object TaskFlowHUD : HudModule( @@ -31,7 +31,7 @@ object TaskFlowHUD : HudModule( init { onRender { - TaskFlow.toString().lines().forEachIndexed { index, line -> + RootTask.toString().lines().forEachIndexed { index, line -> font.build(line, Vec2d(position.x, position.y + index * (font.getHeight(font.scaleMultiplier) + 2.0))) } } diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/Baritone.kt b/common/src/main/kotlin/com/lambda/module/modules/client/Baritone.kt index a6e7ac9ae..8ac615b68 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/Baritone.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/Baritone.kt @@ -26,5 +26,5 @@ object Baritone : Module( description = "Baritone configuration", defaultTags = setOf(ModuleTag.CLIENT) ) { - val rotation = RotationSettings(this) + val rotation = RotationSettings(this, Int.MAX_VALUE) } diff --git a/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt b/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt index 616f1d628..d381e52e5 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/client/TaskFlowModule.kt @@ -21,11 +21,13 @@ import com.lambda.config.groups.BuildSettings import com.lambda.config.groups.InteractionSettings import com.lambda.config.groups.InventorySettings import com.lambda.config.groups.RotationSettings +import com.lambda.core.PingManager import com.lambda.event.events.RenderEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.construction.result.Drawable import com.lambda.module.Module import com.lambda.module.tag.ModuleTag +import com.lambda.util.world.raycast.InteractionMask object TaskFlowModule : Module( name = "TaskFlow", @@ -39,7 +41,7 @@ object TaskFlowModule : Module( private val page by setting("Page", Page.BUILD) val build = BuildSettings(this) { page == Page.BUILD } val rotation = RotationSettings(this) { page == Page.ROTATION } - val interact = InteractionSettings(this) { page == Page.INTERACTION } + val interact = InteractionSettings(this, InteractionMask.BOTH) { page == Page.INTERACTION } val inventory = InventorySettings(this) { page == Page.INVENTORY } val showAllEntries by setting("Show All Entries", false, "Show all entries in the task tree") { page == Page.DEBUG } @@ -54,4 +56,6 @@ object TaskFlowModule : Module( } } } + + } diff --git a/common/src/main/kotlin/com/lambda/module/modules/combat/Criticals.kt b/common/src/main/kotlin/com/lambda/module/modules/combat/Criticals.kt index a46b24721..d06862dc0 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/combat/Criticals.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/combat/Criticals.kt @@ -20,8 +20,8 @@ package com.lambda.module.modules.combat import com.lambda.context.SafeContext import com.lambda.event.events.PlayerEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.rotation.Rotation -import com.lambda.interaction.rotation.Rotation.Companion.rotationTo +import com.lambda.interaction.request.rotation.Rotation +import com.lambda.interaction.request.rotation.Rotation.Companion.rotationTo import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.extension.rotation diff --git a/common/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt b/common/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt index 76725d472..ecb3491b7 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/combat/CrystalAura.kt @@ -17,13 +17,48 @@ package com.lambda.module.modules.combat -import com.lambda.config.groups.InteractionSettings import com.lambda.config.groups.RotationSettings +import com.lambda.config.groups.Targeting +import com.lambda.context.SafeContext +import com.lambda.event.events.EntityEvent +import com.lambda.event.events.RenderEvent import com.lambda.event.events.TickEvent -import com.lambda.event.listener.SafeListener.Companion.listenConcurrently +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.graphics.gl.Matrices +import com.lambda.graphics.gl.Matrices.buildWorldProjection +import com.lambda.graphics.gl.Matrices.withVertexTransform +import com.lambda.graphics.renderer.gui.font.FontRenderer +import com.lambda.graphics.renderer.gui.font.LambdaEmoji +import com.lambda.graphics.renderer.gui.font.LambdaFont +import com.lambda.interaction.request.rotation.Rotation.Companion.rotationTo +import com.lambda.interaction.request.rotation.RotationManager +import com.lambda.interaction.request.rotation.visibilty.VisibilityChecker.getVisibleSurfaces +import com.lambda.interaction.request.rotation.visibilty.lookAt import com.lambda.module.Module import com.lambda.module.tag.ModuleTag +import com.lambda.threading.runSafe +import com.lambda.threading.runSafeGameScheduled +import com.lambda.util.BlockUtils.blockState +import com.lambda.util.Communication.info +import com.lambda.util.Timer +import com.lambda.util.collections.LimitedDecayQueue +import com.lambda.util.combat.CombatUtils.crystalDamage +import com.lambda.util.math.* +import com.lambda.util.math.MathUtils.ceilToInt +import com.lambda.util.math.MathUtils.roundToStep +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.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.* +import kotlin.concurrent.fixedRateTimer +import kotlin.math.max +import kotlin.time.Duration.Companion.milliseconds object CrystalAura : Module( name = "CrystalAura", @@ -32,43 +67,528 @@ object CrystalAura : Module( ) { private val page by setting("Page", Page.General) + /* General */ + private val placeRange by setting("Place Range", 4.6, 1.0..7.0, 0.1, "Range to place crystals", " blocks") { page == Page.General } + private val explodeRange by setting("Explode Range", 3.0, 1.0..7.0, 0.1, "Range to explode crystals", " blocks") { page == Page.General } + private val placeDelay by setting("Place Delay", 50L, 0L..1000L, 1L, "Delay between placement attempts", " ms") { page == Page.General } + private val explodeDelay by setting("Explode Delay", 10L, 0L..1000L, 1L, "Delay between explosion attempts", " ms") { page == Page.General } + private val updateMode by setting("Update Mode", UpdateMode.Async) { page == Page.General } + private val updateDelaySetting by setting("Update Delay", 25L, 5L..200L, 5L, unit = " ms") { page == Page.General && updateMode == UpdateMode.Async } + private val maxUpdatesPerFrame by setting("Max Updates Per Frame", 5, 1..20, 1) { page == Page.General && updateMode == UpdateMode.Async } + private val updateDelay get() = if (updateMode == UpdateMode.Async) updateDelaySetting else 0L + private val debug by setting("Debug", false) { page == Page.General } + + /* Placement */ + private val priorityMode by setting("Crystal Priority", Priority.Damage) { page == Page.Placement } + private val minDamageAdvantage by setting("Min Damage Advantage", 4.0, 1.0..10.0, 0.5) { page == Page.Placement && priorityMode == Priority.Advantage } + private val minTargetDamage by setting("Min Target Damage", 6.0, 0.0..20.0, 0.5, "Minimum target damage to use crystals") { page == Page.Placement } + private val maxSelfDamage by setting("Max Self Damage", 8.0, 0.0..36.0, 0.5, "Maximum self damage to use crystals") { page == Page.Placement } + //private val minHealth by setting("Min Health", 10.0, 0.0..36.0, 0.5, "Minimum player health to use crystals") { page == Page.General } + private val oldPlace by setting("1.12 Placement", false) { page == Page.Placement } + + /* Prediction */ + private val prediction by setting("Prediction", PredictionMode.None) { page == Page.Prediction } + + private val packetPredictions by setting("Packet Predictions", 1, 0..20, 1) { page == Page.Prediction && prediction.onPacket } + private val placePostPause by setting("Place Post Pause", true) { page == Page.Prediction && prediction.onPacket } + + private val placePredictions by setting("Place Predictions", 4, 1..20, 1) { page == Page.Prediction && prediction.onPlace } + private val packetLifetime by setting("Packet Lifetime", 500L, 50L..1000L) { page == Page.Prediction && prediction.onPlace } + + /* Targeting */ + private val targeting = Targeting.Combat(this, 10.0) { page == Page.Targeting } + /* Rotation */ - private val rotation = RotationSettings(this) { page == Page.Targeting } + private val rotation = RotationSettings(this) { page == Page.Rotation } - /* Placing */ - private val swap by setting("Swap", Hand.MAIN_HAND, "Automatically swap to crystals") { page == Page.Placing } - private val multiPlace by setting("Multi Place", true, "Place multiple crystals") { page == Page.Placing } - private val placeDelay by setting("Place Delay", 0, 0..20, 1, "Delay between crystal placements", unit = "ticks", visibility = { page == Page.Placing }) - private val placeRange by setting("Place Range", 5.0, 0.1..7.0, 0.1, "Range to place crystals from the player eyes", visibility = { page == Page.Placing }) - private val placeRangeWalls by setting("Place Range Walls", 3.5, 0.1..7.0, 0.1, "Range to place crystals through walls", visibility = { page == Page.Placing }) - private val placeMinHealth by setting("Place Min Health", 10.0, 0.0..20.0, 0.5, "Minimum health to place a crystal", visibility = { page == Page.Placing }) - private val placeMaxSelfDamage by setting("Place Max Self Damage", 8.0, 0.0..20.0, 0.5, "Maximum self damage to place a crystal", visibility = { page == Page.Placing }) - private val placeMinDamage by setting("Place Min Damage", 6.0, 0.0..20.0, 0.5, "Minimum damage to place a crystal", visibility = { page == Page.Placing }) + private val blueprint = mutableMapOf() + private var activeOpportunity: Opportunity? = null + private var currentTarget: LivingEntity? = null - /* Exploding */ - private val explode by setting("Explode", true, "Explode crystals") { page == Page.Exploding } - private val explodeDelay by setting("Explode Delay", 0, 0..20, 1, "Delay between crystal explosions", unit = "ticks", visibility = { page == Page.Exploding }) - private val explodeRange by setting("Explode Range", 5.0, 0.1..7.0, 0.1, "Range to explode crystals", visibility = { page == Page.Exploding }) - private val explodeRangeWalls by setting("Explode Range Walls", 3.5, 0.1..7.0, 0.1, "Range to explode crystals through walls", visibility = { page == Page.Exploding }) - private val preventDeath by setting("Prevent Death", true, "Prevent death from crystal explosions", visibility = { page == Page.Exploding }) - private val explodeMinDamage by setting("Explode Min Damage", 6.0, 0.0..20.0, 0.5, "Minimum damage to explode a crystal", visibility = { page == Page.Exploding }) - private val noWeakness by setting("No Weakness", true, "Switch to a weapon when you have a weakness effect", visibility = { page == Page.Exploding }) + private val damage = mutableListOf() + private val actionMap = mutableMapOf>() + private var actionType = ActionType.Normal - /* Rendering */ + private val updateTimer = Timer() + private var updatesThisFrame = 0 + private val placeTimer = Timer() + private val explodeTimer = Timer() - /* Interaction */ - private val interac = InteractionSettings(this) // Canadian interbank meme + private val predictionTimer = Timer() + private var lastEntityId = 0 - private enum class Page { - General, Targeting, Placing, Exploding, Rendering + private val decay = LimitedDecayQueue(10000, 3000L) + + private val collidingOffsets = mutableListOf().apply { + for (x in -1..1) { + for (z in -1..1) { + for (y in 0..1) { + if (x != 0 && y != 0 && z != 0) add(BlockPos(x, y, z)) + } + } + } } + private val font = FontRenderer(LambdaFont.FiraSansRegular, LambdaEmoji.Twemoji) + init { - listenConcurrently {} + // Async ticking + fixedRateTimer( + name = "Crystal Aura Thread", + daemon = true, + initialDelay = 0L, + period = 1L + ) { + if (CrystalAura.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 (CrystalAura.isDisabled || !debug) return@fixedRateTimer + + runSafeGameScheduled { + info((decay.size.toDouble() * 0.3333).roundToStep(0.1).toString()) + } + } + + // Ticking with alignment + listen { + if (updateMode == UpdateMode.Ticked) tick() + } + + listen { + updatesThisFrame = 0 + } + + listen { + if (!debug) return@listen + + // Build the buffer + blueprint.values.forEach { + it.buildDebug() + } + + // Draw the font + Matrices.push { + val c = mc.gameRenderer.camera.pos.negate() + translate(c.x, c.y, c.z) + font.render() + } + } + + // Update last received entity spawn + listen(alwaysListen = true) { event -> + lastEntityId = event.entity.id + predictionTimer.reset() + } + + // Prediction + listen { event -> + val crystal = event.entity as? EndCrystalEntity ?: return@listen + val pos = crystal.baseBlockPos + + // Update crystal + val opportunity = blueprint[pos] ?: return@listen + opportunity.crystal = crystal + + // Run packet prediction + if (!prediction.isActive || activeOpportunity != opportunity) return@listen + + explodeInternal(lastEntityId) + + if (!prediction.onPacket) return@listen + + repeat(packetPredictions) { + placeInternal(opportunity, Hand.MAIN_HAND) + explodeInternal(++lastEntityId) + } + + if (placePostPause) placeTimer.reset() + } + + listen { event -> + val crystal = event.entity as? EndCrystalEntity ?: return@listen + val pos = crystal.baseBlockPos + + // Invalidate crystal entity + val opportunity = blueprint[pos] ?: return@listen + opportunity.crystal = null + decay += crystal.id + } + + onEnable { + currentTarget = null + resetBlueprint() + } + } + + private fun SafeContext.tick() { + // Update the target + currentTarget = targeting.target() + + // Update the blueprint + currentTarget?.let { + updateBlueprint(it) + } ?: resetBlueprint() + + // Choosing and running the best opportunity + activeOpportunity?.let { + tickInteraction(it) + } + } + + private fun tickInteraction(best: Opportunity) { + if (!best.blocked) { + best.explode() + best.place() + return + } + + val mutableBlockPos = BlockPos.Mutable() + + // Break crystals nearby if the best crystal placement is blocked by other crystals + collidingOffsets.mapNotNull { + mutableBlockPos.set( + best.blockPos.x + it.x, + best.blockPos.y + it.y, + best.blockPos.z + it.z + ) + + blueprint[mutableBlockPos] + }.filter { it.hasCrystal }.maxByOrNull { it.priority }?.explode() + + best.place() + } + + private fun SafeContext.placeInternal(opportunity: Opportunity, hand: Hand) { + connection.sendPacket( + PlayerInteractBlockC2SPacket( + hand, BlockHitResult(opportunity.crystalPosition, opportunity.side, opportunity.blockPos, false), 0 + ) + ) + + player.swingHand(hand) + } + + 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() + + // Build damage info + fun info( + pos: BlockPos, target: LivingEntity, + blocked: Boolean, + crystal: EndCrystalEntity? = null + ): Opportunity? { + val crystalPos = pos.crystalPosition + + // Calculate the damage to the target from the explosion of the crystal + val targetDamage = crystalDamage(crystalPos, target) + if (targetDamage < minTargetDamage) return null + + // Calculate the self-damage for the player + val selfDamage = crystalDamage(crystalPos, player) + if (selfDamage > maxSelfDamage) return null + + if (priorityMode == Priority.Advantage && priorityMode.factor(targetDamage, selfDamage) < minDamageAdvantage) return null + + // Return the calculated damage info if conditions are met + return Opportunity( + pos.toImmutable(), + targetDamage, + selfDamage, + blocked, + crystal + ) + } + + // Extra checks for placement, because you may explode but not place in special cases(crystal in the air) + @Suppress("ConvertArgumentToSet") + fun placeInfo( + pos: BlockPos, + target: LivingEntity + ): Opportunity? { + // Check if crystals could be placed on the base block + val state = pos.blockState(world) + val isOfBlock = state.isOf(Blocks.OBSIDIAN) || state.isOf(Blocks.BEDROCK) + if (!isOfBlock) return null + + // Check if the block above is air and other conditions for valid crystal placement + val above = pos.up() + if (!world.isAir(above)) return null + if (oldPlace && !world.isAir(above.up())) return null + + // Exclude blocks blocked by entities + val crystalBox = pos.crystalBox + + val entitiesNearby = fastEntitySearch(3.5, pos) + val crystals = entitiesNearby.filterIsInstance() as MutableList + val otherEntities = entitiesNearby - crystals + player + + if (otherEntities.any { + it.boundingBox.intersects(crystalBox) + }) return null + + // Placement collision checks + val baseCrystal = crystals.firstOrNull { + it.baseBlockPos == pos + } + + val crystalPlaceBox = pos.crystalPlaceHitBox + val blocked = baseCrystal == null && crystals.any { + it.boundingBox.intersects(crystalPlaceBox) + } + + return info( + pos, + target, + blocked, + baseCrystal + ) + } + + val range = max(placeRange, explodeRange) + 1 + val rangeInt = range.ceilToInt() + + // Iterate through existing crystals + val crystalBase = BlockPos.Mutable() + fastEntitySearch(range).forEach { crystal -> + crystalBase.set(crystal.x, crystal.y - 0.5, crystal.z) + damage += info(crystalBase, target, false, crystal) ?: return@forEach + } + + // Iterate through possible place positions and calculate damage information for each + BlockPos.iterateOutwards(player.blockPos.up(), rangeInt, rangeInt, rangeInt).forEach { pos -> + if (pos distSq player.pos > range * range) return@forEach + if (damage.any { info -> info.blockPos == pos }) return@forEach + + damage += placeInfo(pos, target) ?: return@forEach + } + + // Map opportunities + damage.forEach { + blueprint[it.blockPos] = it + } + + // 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 { + it.priority + } + } + + private fun resetBlueprint() { + blueprint.clear() + damage.clear() + actionMap.clear() + 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.currentRotation + } + + /** + * Places the crystal on [blockPos] + * @return Whether the delay passed, null if the interaction failed + */ + fun place() { + if (rotation.rotate && !lookAt(placeRotation).requestBy(rotation).done) return + + placeTimer.runSafeIfPassed(placeDelay.milliseconds) { + placeInternal(this@Opportunity, Hand.MAIN_HAND) + + if (prediction.onPlace) predictionTimer.runIfNotPassed(packetLifetime.milliseconds, false) { + val last = lastEntityId + + repeat(placePredictions) { + explodeInternal(++lastEntityId) + } + + lastEntityId = last + 1 + crystal = null + } + } + } + + /** + * Explodes a crystal that is on [blockPos] + * @return Whether the delay passed, null if the interaction failed or no crystal found + */ + fun explode() { + if (rotation.rotate && !lookAt(placeRotation).requestBy(rotation).done) return + + explodeTimer.runSafeIfPassed(explodeDelay.milliseconds) { + crystal?.let { crystal -> + explodeInternal(crystal.id) + explodeTimer.reset() + } + } + } + + fun buildDebug() { + withVertexTransform(buildWorldProjection(blockPos.crystalPosition, 0.4, Matrices.ProjRotationMode.TO_CAMERA)) { + val lines = arrayOf( + "Decision: ${actionType.name} ${priority.roundToStep(0.01)}", + "", + "Blocked: $blocked", + "Crystal: $hasCrystal", + "", + "Target Damage: ${target.roundToStep(0.01)}", + "Self Damage: ${self.roundToStep(0.01)}" + ) + + var height = -0.5 * lines.size * (font.getHeight() + 2) + + lines.forEach { + font.build(it, Vec2d(-font.getWidth(it) * 0.5, height)) + height += font.getHeight() + 2 + } + } + } + } + + private val EndCrystalEntity.baseBlockPos get() = + (pos - Vec3d(0.0, 0.5, 0.0)).flooredBlockPos + + private 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), + ) + } + + 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), + ) + } + + private enum class Page { + General, + Placement, + Prediction, + Targeting, + Rotation + } + + private enum class UpdateMode { + Async, + Ticked + } + + @Suppress("Unused") + private enum class PredictionMode(val onPacket: Boolean, val onPlace: Boolean) { + // Prediction disable + None(false, false), + + // Predict on packet receive + Packet(true, false), + + // Predict on place + Deferred(false, true), + + // Predict on both timings + Mixed(true, true); + + val isActive = onPacket || onPlace + } + + private enum class Priority(val factor: (targetDamage: Double, selfDamage: Double) -> Double) { + Damage({ target, _ -> + target + }), + Advantage({ target, self -> + target - self + }) + } - /*listener { event -> - event.lookAtEntity(rotation, interac, getClosestEntity(player.eyePos, placeRange) ?: return@listener) - }*/ + // ToDo: implement actions + @Suppress("Unused") + private enum class ActionType(val priority: Int) { + Normal(0), + ForcePlace(1), + SlowBreak(2) } } diff --git a/common/src/main/kotlin/com/lambda/module/modules/combat/FakePlayer.kt b/common/src/main/kotlin/com/lambda/module/modules/combat/FakePlayer.kt index 025c1e8f8..349859704 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/combat/FakePlayer.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/combat/FakePlayer.kt @@ -17,17 +17,23 @@ package com.lambda.module.modules.combat +import com.lambda.event.events.ConnectionEvent +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.http.Method import com.lambda.http.request import com.lambda.module.Module import com.lambda.module.tag.ModuleTag -import com.lambda.threading.runSafeConcurrent +import com.lambda.threading.onShutdown +import com.lambda.threading.runGameScheduled +import com.lambda.threading.runSafe import com.lambda.util.player.spawnFakePlayer import com.mojang.authlib.GameProfile import net.minecraft.client.network.OtherClientPlayerEntity import net.minecraft.client.network.PlayerListEntry import net.minecraft.entity.Entity import java.util.* +import kotlin.concurrent.fixedRateTimer object FakePlayer : Module( name = "FakePlayer", @@ -35,41 +41,90 @@ object FakePlayer : Module( defaultTags = setOf(ModuleTag.COMBAT, ModuleTag.RENDER) ) { private val playerName by setting("Name", "Steve") + private val fetchKey get() = playerName.lowercase() // Nicknames aren't case-sensitive - private var fakePlayer: OtherClientPlayerEntity? = null + private var fakePlayer: OtherClientPlayerEntity? = null; set(value) { + runSafe { + field?.let { + world.removeEntity(it.id, Entity.RemovalReason.DISCARDED) + } + value?.let { + world.addEntity(it) + } + } + + field = value + } + + private val nilUuid = UUID(0, 0) + private val cachedProfiles = hashMapOf() init { - onEnable { - // Avoid multiple api requests - if (fakePlayer?.gameProfile?.name == playerName) { - fakePlayer = spawnFakePlayer(fakePlayer!!.gameProfile) - return@onEnable - } + listen { + fakePlayer = cachedProfiles[fetchKey]?.let { cached -> + // Keep fetched fake player + fakePlayer?.gameProfile?.also { profile -> + if (profile is FetchedGameProfile && profile.name == cached.name) return@let fakePlayer + } + + // Spawn fetched fake player + spawnFakePlayer( + profile = cached, + reference = fakePlayer ?: player, + addToWorld = false + ) + } ?: fakePlayer?.takeIf { it.gameProfile.name == playerName } ?: spawnFakePlayer( + // Spawn offline fake player while fetching + profile = GameProfile(nilUuid, playerName), + reference = fakePlayer ?: player, + addToWorld = false + ) + } - runSafeConcurrent { - val uuid = - request("https://api.mojang.com/users/profiles/minecraft/$playerName") { + fixedRateTimer( + name = "FakePlayer profile fetcher", + daemon = true, + initialDelay = 0L, + period = 2000L + ) { + cachedProfiles[fetchKey] ?: runSafe { + val requestedProfile = + request("https://api.mojang.com/users/profiles/minecraft/$fetchKey") { method(Method.GET) - }.json().data?.id ?: UUID(0, 0) + }.json().data + + val uuid = requestedProfile?.id ?: nilUuid val fetchedProperties = mc.sessionService.fetchProfile(uuid, true)?.profile?.properties - val profile = GameProfile(UUID(0, 0), playerName).apply { + val profile = FetchedGameProfile(nilUuid, playerName).apply { fetchedProperties?.forEach { key, value -> properties.put(key, value) } } - // This is the cache that mc pulls profile data from when it fetches skins. - mc.networkHandler?.playerListEntries?.put(profile.id, PlayerListEntry(profile, false)) - spawnFakePlayer(profile) + runGameScheduled { + // This is the cache that mc pulls profile data from when it fetches skins. + mc.networkHandler?.playerListEntries?.put(profile.id, PlayerListEntry(profile, false)) + cachedProfiles[fetchKey] = profile + } } } onDisable { - deletePlayer() + fakePlayer = null + } + + onShutdown { + disable() } - } - private fun deletePlayer() { - fakePlayer?.setRemoved(Entity.RemovalReason.DISCARDED) + listen { + disable() + } + + listen { + disable() + } } + + private class FetchedGameProfile(id: UUID, name: String) : GameProfile(id, name) } diff --git a/common/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt b/common/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt index 8c9850a59..5e55e14c6 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/combat/KillAura.kt @@ -25,27 +25,13 @@ import com.lambda.event.events.PacketEvent import com.lambda.event.events.PlayerPacketEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.RotationManager -import com.lambda.interaction.RotationManager.rotate -import com.lambda.interaction.rotation.Rotation -import com.lambda.interaction.rotation.Rotation.Companion.dist -import com.lambda.interaction.rotation.Rotation.Companion.rotationTo -import com.lambda.interaction.rotation.RotationRequest -import com.lambda.interaction.visibilty.VisibilityChecker.scanSurfaces -import com.lambda.interaction.visibilty.VisibilityChecker.visibleSides +import com.lambda.interaction.request.rotation.RotationManager +import com.lambda.interaction.request.rotation.visibilty.lookAtEntity import com.lambda.module.Module import com.lambda.module.tag.ModuleTag -import com.lambda.threading.runConcurrent -import com.lambda.threading.runSafe import com.lambda.util.math.MathUtils.random -import com.lambda.util.math.distSq -import com.lambda.util.math.plus -import com.lambda.util.math.times -import com.lambda.util.math.lerp -import com.lambda.util.player.MovementUtils.moveDiff -import com.lambda.util.player.prediction.buildPlayerPrediction +import com.lambda.util.world.raycast.InteractionMask import com.lambda.util.world.raycast.RayCastUtils.entityResult -import kotlinx.coroutines.delay import net.minecraft.entity.EquipmentSlot import net.minecraft.entity.LivingEntity import net.minecraft.entity.attribute.EntityAttributeModifier @@ -55,8 +41,8 @@ import net.minecraft.network.packet.c2s.play.PlayerInteractEntityC2SPacket import net.minecraft.network.packet.c2s.play.UpdateSelectedSlotC2SPacket import net.minecraft.util.Hand import net.minecraft.util.math.Vec3d -import kotlin.math.pow +// ToDo: Rewrite me plz object KillAura : Module( name = "KillAura", description = "Attacks entities", @@ -65,7 +51,7 @@ object KillAura : Module( private val page by setting("Page", Page.Interact) // Interact - private val interactionSettings = InteractionSettings(this, 3.0) { page == Page.Interact } + private val interactionSettings = InteractionSettings(this, InteractionMask.ENTITY) { page == Page.Interact } private val attackMode by setting("Attack Mode", AttackMode.Cooldown) { page == Page.Interact } private val delaySync by setting("Client-side Delay", true) { page == Page.Interact && attackMode == AttackMode.Cooldown } private val cooldownSync by setting("Client-side Cooldown", true) { page == Page.Interact && attackMode == AttackMode.Cooldown } @@ -89,9 +75,11 @@ object KillAura : Module( private val selfPredict by setting("Self Predict", 1.0, 0.0..2.0, 0.1) { page == Page.Aiming && rotate } private val targetPredict by setting("Target Predict", 0.0, 0.0..2.0, 0.1) { page == Page.Aiming && rotate } - var target: LivingEntity? = null; private set + val target: LivingEntity? + get() = targeting.target() private var shakeRandom = Vec3d.ZERO + private var speedMultiplier = 1.0 private var attackTicks = 0 private var lastAttackTime = 0L @@ -114,16 +102,6 @@ object KillAura : Module( } init { - rotate { - request { - if (!rotate) return@request null - - target?.let { target -> - buildRotation(target) - } - } - } - listen(Int.MIN_VALUE) { event -> prevY = lastY lastY = event.position.y @@ -131,20 +109,12 @@ object KillAura : Module( } listen { - target = targeting.target() if (!timerSync) attackTicks++ - target?.let { entity -> - runAttack(entity) - } - } - runConcurrent { - while (true) { - delay(50) // ToDo: tps sync - - runSafe { - if (timerSync && isEnabled) attackTicks++ + target?.let { entity -> + if (lookAtEntity(entity).requestBy(rotation).done) { + runAttack(entity) } } } @@ -162,7 +132,7 @@ object KillAura : Module( onDisable(::reset) } - private fun SafeContext.buildRotation(target: LivingEntity): RotationRequest? { + /*private fun SafeContext.buildRotation(target: LivingEntity) { val currentRotation = RotationManager.currentRotation val prediction = buildPlayerPrediction() @@ -187,15 +157,9 @@ object KillAura : Module( val box = target.boundingBox val reach = targeting.targetingRange + 2.0 - val reachSq = reach.pow(2) - - // Do not rotate if the eyes are inside the target's AABB - if (box.contains(eye)) { - return RotationRequest(currentRotation, rotation) - } // Rotation stabilizer - rotation.speedMultiplier = if (stabilize && !rotation.instant) { + speedMultiplier = if (stabilize && !rotation.instant) { val slowDown = currentRotation.castBox(box, reach, eye) != null with(rotation) { @@ -242,28 +206,27 @@ object KillAura : Module( if (vecRotation.rayCast(reach, eye)?.entityResult?.entity == target) return@run // Get visible point set - val validHits = mutableMapOf() - - val sides = visibleSides(box, eye, interactionSettings) - - scanSurfaces(box, sides, resolution = interactionSettings.resolution) { _, vec -> - if (eye distSq vec > reachSq) return@scanSurfaces - - val newRotation = eye.rotationTo(vec) - - val cast = newRotation.rayCast(reach, eye) ?: return@scanSurfaces - if (cast.entityResult?.entity != target) return@scanSurfaces - - validHits[vec] = newRotation + val validHits = collectHitsFor( + listOf(target.boundingBox), + reach + ) { + hit.entityResult?.entity == target } // Switch to the closest visible point - vec = validHits.minByOrNull { vecRotation dist it.value }?.key ?: return null + //vec = validHits.minByOrNull { vecRotation dist it.value }?.key ?: return null } val predictOffset = target.moveDiff * targetPredict - return RotationRequest(eye.rotationTo(vec + predictOffset), rotation) - } + + return RotationRequest( + eye.rotationTo(), + rotation, + speedMultiplier + ) { + rayCast(reach, eye)?.entityResult == target + } + }*/ private fun SafeContext.runAttack(target: LivingEntity) { // Critical hit check @@ -296,13 +259,13 @@ object KillAura : Module( if (!rotate) return@run val angle = RotationManager.currentRotation - if (interactionSettings.useRayCast) { - val cast = angle.rayCast(interactionSettings.reach) + if (interactionSettings.strictRayCast) { + val cast = angle.rayCast(interactionSettings.attackReach) if (cast?.entityResult?.entity != target) return } // Perform a raycast without checking the environment - angle.castBox(target.boundingBox, interactionSettings.reach) ?: return + angle.castBox(target.boundingBox, interactionSettings.attackReach) ?: return } // Attack @@ -327,9 +290,8 @@ object KillAura : Module( } private fun reset(ctx: SafeContext) = ctx.apply { - target = null attackTicks = player.lastAttackedTicks - rotation.speedMultiplier = 1.0 + speedMultiplier = 1.0 shakeRandom = Vec3d.ZERO lastY = 0.0 diff --git a/common/src/main/kotlin/com/lambda/module/modules/debug/BlockTest.kt b/common/src/main/kotlin/com/lambda/module/modules/debug/BlockTest.kt index 380aafca0..1b051f8fc 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/debug/BlockTest.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/debug/BlockTest.kt @@ -19,7 +19,7 @@ package com.lambda.module.modules.debug import com.lambda.event.events.RenderEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.graphics.renderer.esp.builders.build +import com.lambda.graphics.renderer.esp.builders.ofBox import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.world.blockSearch @@ -54,7 +54,7 @@ object BlockTest : Module( state.isOf(Blocks.DIAMOND_BLOCK) }.forEach { (pos, state) -> state.getOutlineShape(world, pos).boundingBoxes.forEach { box -> - it.renderer.build(box.offset(pos), filledColor, outlineColor) + it.renderer.ofBox(box.offset(pos), filledColor, outlineColor) } } } diff --git a/common/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt b/common/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt index 719724949..a6208e029 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/debug/ContainerTest.kt @@ -22,7 +22,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.material.StackSelection.Companion.select import com.lambda.module.Module import com.lambda.module.tag.ModuleTag -import com.lambda.task.TaskFlow.run +import com.lambda.task.RootTask.run import com.lambda.task.tasks.AcquireMaterial.Companion.acquire import net.minecraft.item.Items diff --git a/common/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt b/common/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt index 7a4b168f3..dc52b3bea 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/debug/RenderTest.kt @@ -20,7 +20,7 @@ package com.lambda.module.modules.debug import com.lambda.event.events.RenderEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.graphics.renderer.esp.DynamicAABB.Companion.dynamicBox -import com.lambda.graphics.renderer.esp.builders.build +import com.lambda.graphics.renderer.esp.builders.ofBox import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.math.setAlpha @@ -48,12 +48,12 @@ object RenderTest : Module( listen { entitySearch(8.0) .forEach { entity -> - it.renderer.build(entity.dynamicBox, filledColor, outlineColor) + it.renderer.ofBox(entity.dynamicBox, filledColor, outlineColor) } } listen { - it.renderer.build(Box.of(player.pos, 0.3, 0.3, 0.3), filledColor, outlineColor) + it.renderer.ofBox(Box.of(player.pos, 0.3, 0.3, 0.3), filledColor, outlineColor) } } } diff --git a/common/src/main/kotlin/com/lambda/module/modules/movement/BackTrack.kt b/common/src/main/kotlin/com/lambda/module/modules/movement/BackTrack.kt index 0a0ab8957..55847529c 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/movement/BackTrack.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/movement/BackTrack.kt @@ -24,7 +24,7 @@ import com.lambda.event.events.RenderEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.graphics.renderer.esp.DynamicAABB -import com.lambda.graphics.renderer.esp.builders.build +import com.lambda.graphics.renderer.esp.builders.ofBox import com.lambda.module.Module import com.lambda.module.modules.client.GuiSettings import com.lambda.module.modules.combat.KillAura @@ -110,7 +110,7 @@ object BackTrack : Module( val p = target.hurtTime / 10.0 val c = lerp(p, c1, c2) - it.renderer.build(box, c.multAlpha(0.3), c.multAlpha(0.8)) + it.renderer.ofBox(box, c.multAlpha(0.3), c.multAlpha(0.8)) } listen { event -> diff --git a/common/src/main/kotlin/com/lambda/module/modules/movement/Blink.kt b/common/src/main/kotlin/com/lambda/module/modules/movement/Blink.kt index 67dd99510..35b33ed7e 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/movement/Blink.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/movement/Blink.kt @@ -22,7 +22,7 @@ import com.lambda.event.events.PacketEvent import com.lambda.event.events.RenderEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.graphics.renderer.esp.DynamicAABB -import com.lambda.graphics.renderer.esp.builders.build +import com.lambda.graphics.renderer.esp.builders.ofBox import com.lambda.module.Module import com.lambda.module.modules.client.GuiSettings import com.lambda.module.modules.combat.KillAura @@ -69,7 +69,7 @@ object Blink : Module( listen { event -> val color = GuiSettings.primaryColor - event.renderer.build(box.update(lastBox), color.setAlpha(0.3), color) + event.renderer.ofBox(box.update(lastBox), color.setAlpha(0.3), color) } listen { event -> diff --git a/common/src/main/kotlin/com/lambda/module/modules/movement/Jesus.kt b/common/src/main/kotlin/com/lambda/module/modules/movement/Jesus.kt index 4eefe996d..26cf8be3e 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/movement/Jesus.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/movement/Jesus.kt @@ -25,7 +25,7 @@ import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module import com.lambda.module.tag.ModuleTag -import com.lambda.util.Nameable +import com.lambda.util.NamedEnum import com.lambda.util.math.MathUtils.toInt import com.lambda.util.math.minus import com.lambda.util.player.MovementUtils.isInputting @@ -53,7 +53,7 @@ object Jesus : Module( private var goUp = true private var swimmingTicks = 0 - enum class Mode(override val displayName: String, val collision: Boolean) : Nameable.NamedEnum { + enum class Mode(override val displayName: String, val collision: Boolean) : NamedEnum { NCP("NCP", true), NCP_DOLPHIN("NCP Dolphin", false), NCP_NEW("NCP New", true) diff --git a/common/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt b/common/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt index d08644129..93394908a 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt @@ -17,18 +17,16 @@ package com.lambda.module.modules.movement -import com.lambda.config.groups.RotationConfig import com.lambda.context.SafeContext import com.lambda.event.events.ClientEvent import com.lambda.event.events.MovementEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.RotationManager.rotate -import com.lambda.interaction.rotation.Rotation -import com.lambda.interaction.rotation.RotationRequest -import com.lambda.interaction.rotation.RotationMode +import com.lambda.interaction.request.rotation.* +import com.lambda.interaction.request.rotation.RotationManager.onRotate +import com.lambda.interaction.request.rotation.visibilty.lookAt import com.lambda.module.Module import com.lambda.module.tag.ModuleTag -import com.lambda.util.Nameable +import com.lambda.util.NamedEnum import com.lambda.util.extension.contains import com.lambda.util.player.MovementUtils.addSpeed import com.lambda.util.player.MovementUtils.calcMoveYaw @@ -53,7 +51,7 @@ object Speed : Module( @JvmStatic val mode by setting("Mode", Mode.GRIM_STRAFE).apply { onValueChange { _, _ -> - reset() + Speed.reset() } } @@ -71,9 +69,7 @@ object Speed : Module( private val ncpTimerBoost by setting("Timer Boost", 1.08, 1.0..1.1, 0.01) { mode == Mode.NCP_STRAFE } // Grim - private val rotationConfig = object : RotationConfig.Instant { - override val rotationMode = RotationMode.SYNC - } + private val rotationConfig = RotationConfig.Instant(RotationMode.Sync, Int.MIN_VALUE + 1) private var prevTickJumping = false @@ -85,7 +81,7 @@ object Speed : Module( private var ncpSpeed = NCP_BASE_SPEED private var lastDistance = 0.0 - enum class Mode(override val displayName: String) : Nameable.NamedEnum { + enum class Mode(override val displayName: String) : NamedEnum { GRIM_STRAFE("Grim Strafe"), NCP_STRAFE("NCP Strafe"), } @@ -134,34 +130,33 @@ object Speed : Module( } } - rotate(100, alwaysListen = false) { - request { lastContext -> - if (mode != Mode.GRIM_STRAFE) return@request null - if (!shouldWork()) return@request null + onRotate { + if (mode != Mode.GRIM_STRAFE) return@onRotate + if (!shouldWork()) return@onRotate - var yaw = player.yaw - val input = newMovementInput() + var yaw = player.yaw + val input = newMovementInput() - if (!input.isInputting) return@request null + if (!input.isInputting) return@onRotate - run { - if (!diagonal) return@run - if (player.isOnGround && input.jumping) return@run + run { + if (!diagonal) return@run + if (player.isOnGround && input.jumping) return@run - val forward = input.roundedForward.toFloat() - var strafe = input.roundedStrafing.toFloat() + val forward = input.roundedForward.toFloat() + var strafe = input.roundedStrafing.toFloat() - if (strafe == 0f) strafe = -1f - if (forward == 0f) strafe *= -1 + if (strafe == 0f) strafe = -1f + if (forward == 0f) strafe *= -1 - yaw -= 45 * strafe - } + yaw -= 45 * strafe + } - val moveYaw = calcMoveYaw(yaw, input.roundedForward, input.roundedStrafing) - val rotation = Rotation(moveYaw, lastContext?.rotation?.pitch ?: player.pitch.toDouble()) + val moveYaw = calcMoveYaw(yaw, input.roundedForward, input.roundedStrafing) - RotationRequest(rotation, rotationConfig) - } + lookAt( + Rotation(moveYaw, 0.0) + ).requestBy(rotationConfig) } onEnable { diff --git a/common/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt b/common/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt index b80138325..4c118ce55 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/movement/TargetStrafe.kt @@ -20,7 +20,7 @@ package com.lambda.module.modules.movement import com.lambda.event.events.RotationEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.rotation.Rotation.Companion.rotationTo +import com.lambda.interaction.request.rotation.Rotation.Companion.rotationTo import com.lambda.module.Module import com.lambda.module.modules.combat.KillAura import com.lambda.module.tag.ModuleTag diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/Freecam.kt b/common/src/main/kotlin/com/lambda/module/modules/player/Freecam.kt index 2698d4fda..53bbd0c5b 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/Freecam.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/Freecam.kt @@ -18,13 +18,13 @@ package com.lambda.module.modules.player import com.lambda.Lambda.mc -import com.lambda.config.groups.RotationConfig +import com.lambda.interaction.request.rotation.RotationConfig import com.lambda.event.events.* import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.rotation.Rotation -import com.lambda.interaction.rotation.Rotation.Companion.rotationTo -import com.lambda.interaction.rotation.RotationRequest -import com.lambda.interaction.rotation.RotationMode +import com.lambda.interaction.request.rotation.Rotation +import com.lambda.interaction.request.rotation.RotationManager.onRotate +import com.lambda.interaction.request.rotation.RotationMode +import com.lambda.interaction.request.rotation.visibilty.lookAtHit import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.extension.partialTicks @@ -42,7 +42,6 @@ import com.lambda.util.player.MovementUtils.roundedForward import com.lambda.util.player.MovementUtils.roundedStrafing import com.lambda.util.player.MovementUtils.verticalMovement import com.lambda.util.world.raycast.RayCastUtils.orMiss -import com.lambda.util.world.raycast.RayCastUtils.orNull import net.minecraft.client.option.Perspective import net.minecraft.util.math.Vec3d @@ -56,9 +55,7 @@ object Freecam : Module( private val reach by setting("Reach", 10.0, 1.0..100.0, 1.0, "Freecam reach distance") private val rotateToTarget by setting("Rotate to target", true) - private val rotationConfig = object : RotationConfig.Instant { - override val rotationMode = RotationMode.LOCK - } + private val rotationConfig = RotationConfig.Instant(RotationMode.Lock) private var lastPerspective = Perspective.FIRST_PERSON private var prevPosition: Vec3d = Vec3d.ZERO @@ -94,12 +91,12 @@ object Freecam : Module( mc.options.perspective = lastPerspective } - listen(Int.MAX_VALUE) { event -> - if (!rotateToTarget) return@listen - val target = mc.crosshairTarget?.orNull ?: return@listen + onRotate { + if (!rotateToTarget) return@onRotate - val rotation = player.eyePos.rotationTo(target.pos) - event.request = RotationRequest(rotation, rotationConfig) + mc.crosshairTarget?.let { + lookAtHit(it)?.requestBy(rotationConfig) + } } listen { diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt b/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt index 7a69b28eb..23a34c667 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/HighwayTools.kt @@ -17,12 +17,17 @@ package com.lambda.module.modules.player -import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint +import com.lambda.config.groups.BuildSettings +import com.lambda.config.groups.InteractionSettings +import com.lambda.config.groups.InventorySettings +import com.lambda.config.groups.RotationSettings +import com.lambda.interaction.construction.blueprint.Blueprint.Companion.emptyStructure +import com.lambda.interaction.construction.blueprint.PropagatingBlueprint.Companion.propagatingBlueprint import com.lambda.interaction.construction.verify.TargetState import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.task.Task -import com.lambda.task.TaskFlow.run +import com.lambda.task.RootTask.run import com.lambda.task.tasks.BuildTask.Companion.build import com.lambda.util.BaritoneUtils import com.lambda.util.Communication.info @@ -32,6 +37,7 @@ import com.lambda.util.math.MathUtils.floorToInt import com.lambda.util.math.rotateClockwise import com.lambda.util.player.MovementUtils.octant import com.lambda.util.world.StructureUtils.generateDirectionalTube +import com.lambda.util.world.raycast.InteractionMask import net.minecraft.block.Blocks import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction @@ -43,20 +49,27 @@ object HighwayTools : Module( description = "Auto highway builder", defaultTags = setOf(ModuleTag.PLAYER, ModuleTag.AUTOMATION) ) { - private val height by setting("Height", 4, 2..10, 1) - private val width by setting("Width", 6, 1..30, 1) - private val pavement by setting("Pavement", Material.Block, description = "Material for the pavement") - private val rimHeight by setting("Pavement Rim Height", 1, 0..6, 1) { pavement != Material.None } - private val cornerBlock by setting("Corner", Corner.None, description = "Include corner blocks in the highway") { pavement != Material.None } - private val pavementMaterial by setting("Pavement Material", Blocks.OBSIDIAN, description = "Material to build the highway with") { pavement == Material.Block } - private val floor by setting("Floor", Material.None, description = "Material for the floor") - private val floorMaterial by setting("Floor Material", Blocks.NETHERRACK, description = "Material to build the floor with") { floor == Material.Block } - private val walls by setting("Walls", Material.None, description = "Material for the walls") - private val wallMaterial by setting("Wall Material", Blocks.NETHERRACK, description = "Material to build the walls with") { walls == Material.Block } - private val ceiling by setting("Ceiling", Material.None, description = "Material for the ceiling") - private val ceilingMaterial by setting("Ceiling Material", Blocks.OBSIDIAN, description = "Material to build the ceiling with") { ceiling == Material.Block } - private val distance by setting("Distance", -1, -1..1000000, 1, description = "Distance to build the highway/tunnel (negative for infinite)") - private val sliceSize by setting("Slice Size", 3, 1..5, 1, description = "Number of slices to build at once") + private val page by setting("Page", Page.Structure) + + private val height by setting("Height", 4, 2..10, 1) { page == Page.Structure } + private val width by setting("Width", 6, 1..30, 1) { page == Page.Structure } + private val pavement by setting("Pavement", Material.Block, description = "Material for the pavement") { page == Page.Structure } + private val rimHeight by setting("Pavement Rim Height", 1, 0..6, 1) { page == Page.Structure && pavement != Material.None } + private val cornerBlock by setting("Corner", Corner.None, description = "Include corner blocks in the highway") { page == Page.Structure && pavement != Material.None } + private val pavementMaterial by setting("Pavement Material", Blocks.OBSIDIAN, description = "Material to build the highway with") { page == Page.Structure && pavement == Material.Block } + private val floor by setting("Floor", Material.None, description = "Material for the floor") { page == Page.Structure } + private val floorMaterial by setting("Floor Material", Blocks.NETHERRACK, description = "Material to build the floor with") { page == Page.Structure && floor == Material.Block } + private val walls by setting("Walls", Material.None, description = "Material for the walls") { page == Page.Structure } + private val wallMaterial by setting("Wall Material", Blocks.NETHERRACK, description = "Material to build the walls with") { page == Page.Structure && walls == Material.Block } + private val ceiling by setting("Ceiling", Material.None, description = "Material for the ceiling") { page == Page.Structure } + private val ceilingMaterial by setting("Ceiling Material", Blocks.OBSIDIAN, description = "Material to build the ceiling with") { page == Page.Structure && ceiling == Material.Block } + private val distance by setting("Distance", -1, -1..1000000, 1, description = "Distance to build the highway/tunnel (negative for infinite)") { page == Page.Structure } + private val sliceSize by setting("Slice Size", 3, 1..5, 1, description = "Number of slices to build at once") { page == Page.Structure } + + private val build = BuildSettings(this) { page == Page.Build } + private val rotation = RotationSettings(this) { page == Page.Rotation } + private val interact = InteractionSettings(this, InteractionMask.BLOCK) { page == Page.Interaction } + private val inventory = InventorySettings(this) { page == Page.Inventory } private var octant = EightWayDirection.NORTH private var distanceMoved = 0 @@ -65,14 +78,15 @@ object HighwayTools : Module( private var runningTask: Task<*>? = null enum class Material { - None, - Solid, - Block, + None, Solid, Block, } enum class Corner { - None, - Solid, + None, Solid, + } + + enum class Page { + Structure, Build, Rotation, Interaction, Inventory } init { @@ -80,7 +94,7 @@ object HighwayTools : Module( octant = player.octant startPos = player.blockPos currentPos = startPos - buildSlice() + buildHighway() } onDisable { runningTask?.cancel() @@ -90,25 +104,29 @@ object HighwayTools : Module( } } - private fun buildSlice() { - distanceMoved += sliceSize - - var structure: Structure = mutableMapOf() - val slice = generateSlice() - repeat(sliceSize) { - val vec = Vec3i(octant.offsetX, 0, octant.offsetZ) - currentPos = currentPos.add(vec) - structure = structure.plus(slice.map { it.key.add(currentPos) to it.value }) - } - - runningTask = structure.toBlueprint().build().finally { + private fun buildHighway() { + runningTask = propagatingBlueprint { if (distanceMoved < distance || distance < 0) { - buildSlice() + var structure = emptyStructure() + val slice = generateSlice() + repeat(sliceSize) { + structure = structure.plus(slice.map { it.key.add(currentPos) to it.value }) + val vec = Vec3i(octant.offsetX, 0, octant.offsetZ) + currentPos = currentPos.add(vec) + } + distanceMoved += sliceSize + structure } else { this@HighwayTools.info("Highway built") disable() + emptyStructure() } - }.run() + }.build( + build = build, + rotation = rotation, + interact = interact, + inventory = inventory, + ).run() } private fun generateSlice(): Structure { @@ -184,22 +202,24 @@ object HighwayTools : Module( } if (walls != Material.None) { + val wallElevation = rimHeight + if (pavement != Material.None) 1 else 0 + // Left wall structure += generateDirectionalTube( orthogonal, 1, - height, + height - wallElevation, -center + width, - 0, + wallElevation, ).associateWith { target(walls, wallMaterial) } // Right wall structure += generateDirectionalTube( orthogonal, 1, - height, + height - wallElevation, -center - 1, - 0, + wallElevation, ).associateWith { target(walls, wallMaterial) } } diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt b/common/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt index 50ec1bd43..173879f32 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/InventoryTweaks.kt @@ -24,7 +24,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.task.Task -import com.lambda.task.TaskFlow.run +import com.lambda.task.RootTask.run import com.lambda.task.tasks.BuildTask.Companion.breakAndCollectBlock import com.lambda.task.tasks.OpenContainer import com.lambda.task.tasks.PlaceContainer diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt b/common/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt index 606d3618f..c885f7236 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/Nuker.kt @@ -17,13 +17,12 @@ package com.lambda.module.modules.player -import com.lambda.interaction.construction.blueprint.Blueprint.Companion.emptyStructure -import com.lambda.interaction.construction.blueprint.DynamicBlueprint.Companion.toBlueprint +import com.lambda.interaction.construction.blueprint.TickingBlueprint.Companion.tickingBlueprint import com.lambda.interaction.construction.verify.TargetState import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.task.Task -import com.lambda.task.TaskFlow.run +import com.lambda.task.RootTask.run import com.lambda.task.tasks.BuildTask.Companion.build import com.lambda.util.BlockUtils.blockPos import com.lambda.util.BlockUtils.blockState @@ -44,8 +43,7 @@ object Nuker : Module( init { onEnable { - task = emptyStructure() - .toBlueprint { + task = tickingBlueprint { val selection = BlockPos.iterateOutwards(player.blockPos, width, height, width) .asSequence() .map { it.blockPos } @@ -59,7 +57,7 @@ object Nuker : Module( val floor = BlockPos.iterateOutwards(player.blockPos.down(), width, 0, width) .map { it.blockPos } .associateWith { TargetState.Solid } - return@toBlueprint selection + floor + return@tickingBlueprint selection + floor } selection diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt b/common/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt index 8e32368d9..947878925 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/PacketMine.kt @@ -25,9 +25,10 @@ import com.lambda.graphics.renderer.esp.DynamicAABB import com.lambda.graphics.renderer.esp.builders.buildFilled import com.lambda.graphics.renderer.esp.builders.buildOutline import com.lambda.graphics.renderer.esp.global.DynamicESP -import com.lambda.interaction.RotationManager -import com.lambda.interaction.rotation.RotationRequest -import com.lambda.interaction.visibilty.VisibilityChecker.findRotation +import com.lambda.interaction.request.rotation.RotationManager +import com.lambda.interaction.request.rotation.RotationManager.onRotate +import com.lambda.interaction.request.rotation.RotationRequest +import com.lambda.interaction.request.rotation.visibilty.lookAtBlock import com.lambda.module.Module import com.lambda.module.modules.client.TaskFlowModule import com.lambda.module.tag.ModuleTag @@ -513,7 +514,7 @@ object PacketMine : Module( } } - listen { + listen { currentMiningBlock.forEach { ctx -> ctx?.apply { if (it.pos != pos || !isStateBroken(pos.blockState(world), it.newState)) return@forEach @@ -531,24 +532,18 @@ object PacketMine : Module( } } - listen { - if (!rotate.isEnabled()) return@listen + onRotate { + if (!rotate.isEnabled()) return@onRotate rotationPosition?.let { pos -> lastNonEmptyState?.let { state -> - val boxList = state.getOutlineShape(world, pos).boundingBoxes.map { it.offset(pos) } - val rotationRequest = - findRotation(boxList, TaskFlowModule.rotation, TaskFlowModule.interact, emptySet()) { true } - rotationRequest?.let { request -> - it.request = request - expectedRotation = request - } + expectedRotation = lookAtBlock(pos).requestBy(TaskFlowModule.rotation) } } ?: run { expectedRotation = null } - if (!rotated || !waitingToReleaseRotation) return@listen + if (!rotated || !waitingToReleaseRotation) return@onRotate releaseRotateDelayCounter-- @@ -573,7 +568,7 @@ object PacketMine : Module( if (verifyRotation( boxList, RotationManager.currentRotation.vector, - RotationManager.currentContext?.checkedResult + RotationManager.currentRequest?.target?.hit?.hitIfValid() ) ) { onRotationComplete?.run() @@ -899,7 +894,7 @@ object PacketMine : Module( if (!verifyRotation( lastNonEmptyState?.getOutlineShape(world, pos)?.boundingBoxes?.map { it.offset(pos) }, RotationManager.currentRotation.vector, - RotationManager.currentContext?.checkedResult + RotationManager.currentRequest?.target?.hit?.hitIfValid() ) ) { pausedForRotation = true diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/Replay.kt b/common/src/main/kotlin/com/lambda/module/modules/player/Replay.kt index 023675037..ab67218a5 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/Replay.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/Replay.kt @@ -19,17 +19,17 @@ package com.lambda.module.modules.player import com.google.gson.* import com.lambda.brigadier.CommandResult -import com.lambda.config.groups.RotationConfig +import com.lambda.interaction.request.rotation.RotationConfig 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.RotationEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.rotation.Rotation -import com.lambda.interaction.rotation.RotationRequest -import com.lambda.interaction.rotation.RotationMode +import com.lambda.interaction.request.rotation.Rotation +import com.lambda.interaction.request.rotation.RotationManager.onRotate +import com.lambda.interaction.request.rotation.RotationMode +import com.lambda.interaction.request.rotation.visibilty.lookAt import com.lambda.module.Module import com.lambda.module.modules.client.GuiSettings import com.lambda.module.modules.player.Replay.InputAction.Companion.toAction @@ -56,6 +56,7 @@ import java.lang.reflect.Type import java.time.format.DateTimeFormatter import kotlin.io.path.pathString import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds import kotlin.time.DurationUnit import kotlin.time.toDuration @@ -79,8 +80,8 @@ object Replay : Module( private val deviationThreshold by setting("Deviation threshold", 0.1, 0.1..5.0, 0.1, description = "The threshold for the deviation to cancel the replay.") { cancelOnDeviation } private val lockCamera by setting("Lock Camera", true) - private val rotationConfig = object : RotationConfig.Instant { - override val rotationMode = if (lockCamera) RotationMode.LOCK else RotationMode.SYNC + private val rotationConfig = object : RotationConfig.Instant(RotationMode.Sync) { + override val rotationMode = if (lockCamera) RotationMode.Lock else RotationMode.Sync } enum class State { @@ -157,7 +158,7 @@ object Replay : Module( } } - listen { event -> + onRotate { when (state) { State.RECORDING -> { buffer?.rotation?.add(player.rotation) @@ -165,7 +166,7 @@ object Replay : Module( State.PLAYING -> { buffer?.rotation?.removeFirstOrNull()?.let { rot -> - event.request = RotationRequest(rot, rotationConfig) + lookAt(rot).requestBy(rotationConfig) } } @@ -571,7 +572,7 @@ object Replay : Module( val endPos: Vec3d get() = position.lastOrNull() ?: Vec3d.ZERO val pruneTimesave: Duration - get() = (position.findCyclicPaths(5).size * 50L).toDuration(DurationUnit.MILLISECONDS) + get() = (position.findCyclicPaths(5).size * 50L).milliseconds fun duplicate() = Recording( input.take(size).toMutableList(), diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt b/common/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt index 13e5acbe2..fd993d3c6 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt @@ -26,20 +26,22 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.graphics.renderer.esp.DirectionMask import com.lambda.graphics.renderer.esp.DirectionMask.buildSideMesh -import com.lambda.graphics.renderer.esp.builders.build -import com.lambda.interaction.RotationManager.currentRotation -import com.lambda.interaction.RotationManager.rotate +import com.lambda.graphics.renderer.esp.builders.ofBox +import com.lambda.interaction.request.rotation.RotationManager.currentRotation +import com.lambda.interaction.request.rotation.RotationManager.onRotate import com.lambda.interaction.blockplace.PlaceFinder.Companion.buildPlaceInfo import com.lambda.interaction.blockplace.PlaceInfo import com.lambda.interaction.blockplace.PlaceInteraction.placeBlock -import com.lambda.interaction.rotation.Rotation -import com.lambda.interaction.rotation.Rotation.Companion.angleDifference -import com.lambda.interaction.rotation.Rotation.Companion.dist -import com.lambda.interaction.rotation.Rotation.Companion.rotationTo -import com.lambda.interaction.rotation.Rotation.Companion.wrap -import com.lambda.interaction.rotation.RotationRequest -import com.lambda.interaction.visibilty.VisibilityChecker.getVisibleSurfaces -import com.lambda.interaction.visibilty.VisibilityChecker.scanSurfaces +import com.lambda.interaction.request.rotation.Rotation +import com.lambda.interaction.request.rotation.Rotation.Companion.angleDifference +import com.lambda.interaction.request.rotation.Rotation.Companion.dist +import com.lambda.interaction.request.rotation.Rotation.Companion.rotationTo +import com.lambda.interaction.request.rotation.Rotation.Companion.wrap +import com.lambda.interaction.request.rotation.RotationRequest +import com.lambda.interaction.request.rotation.visibilty.VisibilityChecker.getVisibleSurfaces +import com.lambda.interaction.request.rotation.visibilty.VisibilityChecker.scanSurfaces +import com.lambda.interaction.request.rotation.visibilty.blockHit +import com.lambda.interaction.request.rotation.visibilty.lookAtHit import com.lambda.module.Module import com.lambda.module.modules.client.GuiSettings import com.lambda.module.modules.client.TaskFlowModule @@ -55,6 +57,7 @@ import com.lambda.util.player.MovementUtils.isInputting import com.lambda.util.player.MovementUtils.newMovementInput import com.lambda.util.player.MovementUtils.roundedForward import com.lambda.util.player.MovementUtils.roundedStrafing +import com.lambda.util.world.raycast.InteractionMask import com.lambda.util.world.raycast.RayCastUtils.blockResult import com.lambda.util.world.toFastVec import net.minecraft.util.Hand @@ -83,7 +86,7 @@ object Scaffold : Module( private val direction by setting("Direction", LookingDirection.FREE) { page == Page.ROTATION } private val optimalPitch by setting("Optimal Pitch", 81.0, 70.0..85.0, 0.05) { page == Page.ROTATION } - private val interactionConfig = InteractionSettings(this) { page == Page.INTERACTION } + private val interactionConfig = InteractionSettings(this, InteractionMask.BLOCK) { page == Page.INTERACTION } // Placement private var placeInfo: PlaceInfo? = null @@ -91,6 +94,8 @@ object Scaffold : Module( private var lastRotation: Rotation? = null private var edjeDistance = 0.0 + private var lastRequest: RotationRequest? = null + // Sneaking private var placeInfoAge = 0 private var sneakTicks = 0 @@ -123,14 +128,14 @@ object Scaffold : Module( } init { - rotate { - request { - lastRotation = null - val info = updatePlaceInfo() ?: return@request null - val rotation = rotate(info) ?: return@request null - - RotationRequest(rotation, rotationConfig) - } + onRotate { + lastRotation = null + lastRequest = null + val info = updatePlaceInfo() ?: return@onRotate + + lastRequest = lookAtHit( + blockHit(info.clickPos, info.clickSide, interactionConfig.interactReach) + ) { rotate(info) }.requestBy(rotationConfig) } listen { @@ -154,6 +159,7 @@ object Scaffold : Module( renderInfo.clear() keepLevel = null + lastRequest = null sneakTicks = 0 } } @@ -172,7 +178,7 @@ object Scaffold : Module( // Getting the latest block of the placement sequence placeInfo = buildPlaceInfo( basePos = BlockPos(player.pos.x.floorToInt(), y, player.pos.z.floorToInt()), - range = interactionConfig.reach + 2, + range = interactionConfig.interactReach + 2, sides = builderSideMask ) @@ -195,7 +201,7 @@ object Scaffold : Module( private fun SafeContext.rotate(info: PlaceInfo): Rotation? { val eye = player.eyePos - val reach = interactionConfig.reach + val reach = interactionConfig.interactReach val reachSq = reach.pow(2) val input = newMovementInput() @@ -217,7 +223,7 @@ object Scaffold : Module( // Dividing the surface by segments and iterating through them val pointScan = mutableSetOf().apply { val box = Box(info.clickPos) - val sides = if (TaskFlowModule.interact.visibilityCheck) { + val sides = if (TaskFlowModule.interact.checkSideVisibility) { box.getVisibleSurfaces(eye) } else Direction.entries.toSet() scanSurfaces( @@ -287,10 +293,10 @@ object Scaffold : Module( if (edjeDistance > 0 && edjeDistance < minPlaceDist) return // Raycast the rotation - var blockResult: BlockHitResult? = castRotation(currentRotation, info) + var blockResult: BlockHitResult? = lastRequest?.target?.hit?.hitIfValid()?.blockResult // Use fallback hit vec for nonstrict ac's - if (!interactionConfig.useRayCast && blockResult == null) { + if (!interactionConfig.strictRayCast && blockResult == null) { blockResult = BlockHitResult(info.hitVec, info.clickSide, info.clickPos, false) } @@ -332,7 +338,7 @@ object Scaffold : Module( val box = Box(info.placedPos) val alpha = transform(seconds, 0.0, 0.5, 1.0, 0.0).coerceIn(0.0, 1.0) - event.renderer.build( + event.renderer.ofBox( box, c.multAlpha(0.3 * alpha), c.multAlpha(alpha), @@ -367,7 +373,7 @@ object Scaffold : Module( // Checks if the rotation matches the placement requirements private fun castRotation(rotation: Rotation, info: PlaceInfo): BlockHitResult? { - val blockResult = rotation.rayCast(interactionConfig.reach)?.blockResult ?: return null + val blockResult = rotation.rayCast(interactionConfig.interactReach)?.blockResult ?: return null if (blockResult.blockPos != info.clickPos || blockResult.side != info.clickSide) return null return blockResult } diff --git a/common/src/main/kotlin/com/lambda/module/modules/player/WorldEater.kt b/common/src/main/kotlin/com/lambda/module/modules/player/WorldEater.kt index 2384413b3..c3303369a 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/player/WorldEater.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/player/WorldEater.kt @@ -26,7 +26,7 @@ import com.lambda.interaction.construction.verify.TargetState import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.task.Task -import com.lambda.task.TaskFlow.run +import com.lambda.task.RootTask.run import com.lambda.task.tasks.BuildTask.Companion.build import com.lambda.util.BaritoneUtils import net.minecraft.util.math.BlockBox diff --git a/common/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt b/common/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt index 09a849f2e..bb2694b45 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/render/BlockESP.kt @@ -21,8 +21,8 @@ import com.lambda.Lambda.mc import com.lambda.graphics.renderer.esp.ChunkedESP.Companion.newChunkedESP import com.lambda.graphics.renderer.esp.DirectionMask import com.lambda.graphics.renderer.esp.DirectionMask.buildSideMesh -import com.lambda.graphics.renderer.esp.builders.buildFilledMesh -import com.lambda.graphics.renderer.esp.builders.buildOutlineMesh +import com.lambda.graphics.renderer.esp.builders.buildFilledShape +import com.lambda.graphics.renderer.esp.builders.buildOutlineShape import com.lambda.graphics.renderer.esp.impl.StaticESPRenderer import com.lambda.module.Module import com.lambda.module.tag.ModuleTag @@ -93,8 +93,8 @@ object BlockESP : Module( val shape = outlineShape(state, pos) val blockColor = blockColor(state, pos) - if (drawFaces) buildFilledMesh(shape, if (useBlockColor) blockColor else faceColor, sides) - if (drawOutlines) buildOutlineMesh(shape, if (useBlockColor) blockColor else outlineColor, sides, outlineMode) + if (drawFaces) buildFilledShape(shape, if (useBlockColor) blockColor else faceColor, sides) + if (drawOutlines) buildOutlineShape(shape, if (useBlockColor) blockColor else outlineColor, sides, outlineMode) } private fun rebuildMesh(from: Any, to: Any): Unit = esp.rebuild() diff --git a/common/src/main/kotlin/com/lambda/module/modules/render/Particles.kt b/common/src/main/kotlin/com/lambda/module/modules/render/Particles.kt index b27fb7989..2ba776ca7 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/render/Particles.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/render/Particles.kt @@ -33,7 +33,7 @@ import com.lambda.graphics.gl.Matrices import com.lambda.graphics.gl.Matrices.buildWorldProjection import com.lambda.graphics.gl.Matrices.withVertexTransform import com.lambda.graphics.shader.Shader -import com.lambda.interaction.rotation.Rotation +import com.lambda.interaction.request.rotation.Rotation import com.lambda.module.Module import com.lambda.module.modules.client.GuiSettings import com.lambda.module.modules.client.GuiSettings.colorSpeed @@ -48,7 +48,7 @@ import com.lambda.util.math.lerp import com.lambda.util.math.multAlpha import com.lambda.util.math.transform import com.lambda.util.player.MovementUtils.moveDelta -import com.lambda.util.world.raycast.RayCastMask +import com.lambda.util.world.raycast.InteractionMask import net.minecraft.entity.Entity import net.minecraft.util.math.Vec3d import org.lwjgl.opengl.GL11.GL_ONE @@ -135,7 +135,7 @@ object Particles : Module( repeat(environmentSpawnAmount) { var particlePos = player.pos + Rotation(random(-180.0, 180.0), 0.0).vector * random(0.0, environmentRange) - Rotation.DOWN.rayCast(6.0, particlePos + UP * 2.0, true, RayCastMask.BLOCK)?.pos?.let { + Rotation.DOWN.rayCast(6.0, particlePos + UP * 2.0, true, InteractionMask.BLOCK)?.pos?.let { particlePos = it + UP * 0.03 } ?: return@repeat diff --git a/common/src/main/kotlin/com/lambda/module/modules/render/StorageESP.kt b/common/src/main/kotlin/com/lambda/module/modules/render/StorageESP.kt index 5fac5a4de..c21df9c9e 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/render/StorageESP.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/render/StorageESP.kt @@ -23,9 +23,9 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.graphics.renderer.esp.DirectionMask import com.lambda.graphics.renderer.esp.DirectionMask.buildSideMesh import com.lambda.graphics.renderer.esp.builders.buildFilled -import com.lambda.graphics.renderer.esp.builders.buildFilledMesh +import com.lambda.graphics.renderer.esp.builders.buildFilledShape import com.lambda.graphics.renderer.esp.builders.buildOutline -import com.lambda.graphics.renderer.esp.builders.buildOutlineMesh +import com.lambda.graphics.renderer.esp.builders.buildOutlineShape import com.lambda.graphics.renderer.esp.impl.StaticESPRenderer import com.lambda.module.Module import com.lambda.module.tag.ModuleTag @@ -152,8 +152,8 @@ object StorageESP : Module( } else getBlockEntityColor(block) ?: return@runSafe val shape = outlineShape(block.cachedState, pos) - if (drawFaces) buildFilledMesh(shape, color.setAlpha(alpha), sides) - if (drawOutlines) buildOutlineMesh(shape, color, sides, outlineMode) + if (drawFaces) buildFilledShape(shape, color.setAlpha(alpha), sides) + if (drawOutlines) buildOutlineShape(shape, color, sides, outlineMode) } private fun StaticESPRenderer.build( diff --git a/common/src/main/kotlin/com/lambda/task/TaskFlow.kt b/common/src/main/kotlin/com/lambda/task/RootTask.kt similarity index 90% rename from common/src/main/kotlin/com/lambda/task/TaskFlow.kt rename to common/src/main/kotlin/com/lambda/task/RootTask.kt index 30690075d..9899a8e65 100644 --- a/common/src/main/kotlin/com/lambda/task/TaskFlow.kt +++ b/common/src/main/kotlin/com/lambda/task/RootTask.kt @@ -19,12 +19,12 @@ package com.lambda.task import com.lambda.threading.runSafe -object TaskFlow : Task() { - override val name get() = "TaskFlow" +object RootTask : Task() { + override val name get() = "Root Task" @Ta5kBuilder inline fun > T.run(): T { - execute(this@TaskFlow) + execute(this@RootTask) return this } diff --git a/common/src/main/kotlin/com/lambda/task/Task.kt b/common/src/main/kotlin/com/lambda/task/Task.kt index 557b247df..de3fd2362 100644 --- a/common/src/main/kotlin/com/lambda/task/Task.kt +++ b/common/src/main/kotlin/com/lambda/task/Task.kt @@ -28,6 +28,8 @@ import com.lambda.threading.runSafe import com.lambda.util.Communication.logError import com.lambda.util.Nameable import com.lambda.util.StringUtils.capitalize +import kotlin.time.DurationUnit +import kotlin.time.toDuration typealias TaskGenerator = SafeContext.(R) -> Task<*> typealias TaskGeneratorOrNull = SafeContext.(R) -> Task<*>? @@ -43,8 +45,6 @@ abstract class Task : Nameable, Muteable { val isCompleted get() = state == State.COMPLETED val size: Int get() = subTasks.sumOf { it.size } + 1 - open var unpausable = false - private var nextTask: TaskGenerator? = null private var nextTaskOrNull: TaskGeneratorOrNull? = null private var onFinish: TaskGeneratorUnit? = null @@ -113,9 +113,9 @@ abstract class Task : Nameable, Muteable { owner.subTasks.add(this) parent = owner LOG.info("${owner.name} started $name") - if (!unpausable || pauseParent) { - LOG.info("$name deactivating parent ${owner.name}") - if (owner !is TaskFlow) owner.deactivate() + if (pauseParent) { + LOG.info("$name pausing parent ${owner.name}") + if (owner !is RootTask) owner.pause() } state = State.RUNNING runSafe { runCatching { onStart() }.onFailure { failure(it) } } @@ -143,9 +143,8 @@ abstract class Task : Nameable, Muteable { } @Ta5kBuilder - fun deactivate() { + fun pause() { if (state != State.RUNNING) return - if (unpausable) return state = State.PAUSED } @@ -168,7 +167,7 @@ abstract class Task : Nameable, Muteable { @Ta5kBuilder fun cancel() { cancelSubTasks() - if (this is TaskFlow) return + if (this is RootTask) return if (state == State.COMPLETED || state == State.CANCELLED) return state = State.CANCELLED unsubscribe() @@ -306,14 +305,22 @@ abstract class Task : Nameable, Muteable { return this } + val duration: String get() = + (age * 50).toDuration(DurationUnit.MILLISECONDS).toComponents { days, hours, minutes, seconds, nanoseconds -> + "${"%03d".format(days)}:${"%02d".format(hours)}:${"%02d".format(minutes)}:${"%02d".format(seconds)}.${"${nanoseconds / 1_000_000}".take(2)}" + } + override fun toString() = buildString { appendTaskTree(this@Task) } - private fun StringBuilder.appendTaskTree(task: Task<*>, level: Int = 0) { - appendLine("${" ".repeat(level * 4)}${task.name}" + if (task !is TaskFlow) " [${task.state.display}]" else "") - if (!TaskFlowModule.showAllEntries && (task.state == State.COMPLETED || task.state == State.CANCELLED)) return - task.subTasks.forEach { - if (!TaskFlowModule.showAllEntries && task is TaskFlow && (it.state == State.COMPLETED || it.state == State.CANCELLED)) return@forEach + private fun StringBuilder.appendTaskTree(task: Task<*>, level: Int = 0, maxEntries: Int = 10) { + if (task.state == State.CANCELLED) return + appendLine("${" ".repeat(level * 4)}${task.name}" + if (task !is RootTask) " [${task.state.display}] ${task.duration}" else "") + val left = task.subTasks.size - maxEntries + if (left > 0) { + appendLine("${" ".repeat((level + 1) * 4)}...and $left more tasks") + } + task.subTasks.takeLast(maxEntries).forEach { appendTaskTree(it, level + 1) } } diff --git a/common/src/main/kotlin/com/lambda/task/tasks/AcquireMaterial.kt b/common/src/main/kotlin/com/lambda/task/tasks/AcquireMaterial.kt index b5af8ceb7..24852f94e 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/AcquireMaterial.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/AcquireMaterial.kt @@ -17,20 +17,23 @@ package com.lambda.task.tasks +import com.lambda.config.groups.InventoryConfig 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.module.modules.client.TaskFlowModule import com.lambda.task.Task class AcquireMaterial @Ta5kBuilder constructor( val selection: StackSelection, + val inventory: InventoryConfig ) : Task() { override val name: String get() = "Acquiring $selection" override fun SafeContext.onStart() { - findContainerWithMaterial(selection) + findContainerWithMaterial(selection, inventory) ?.withdraw(selection) ?.finally { success(selection) @@ -41,6 +44,6 @@ class AcquireMaterial @Ta5kBuilder constructor( companion object { @Ta5kBuilder fun acquire(selection: () -> StackSelection) = - AcquireMaterial(selection()) + AcquireMaterial(selection(), TaskFlowModule.inventory) } } diff --git a/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt b/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt index 2388f16e1..b7bf49f12 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/BreakBlock.kt @@ -19,14 +19,13 @@ package com.lambda.task.tasks import baritone.api.pathing.goals.GoalBlock import com.lambda.config.groups.InteractionConfig -import com.lambda.config.groups.RotationConfig +import com.lambda.interaction.request.rotation.RotationConfig 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.RotationManager.rotate import com.lambda.interaction.construction.context.BreakContext -import com.lambda.interaction.visibilty.VisibilityChecker.lookAtBlock +import com.lambda.interaction.request.rotation.visibilty.lookAtBlock import com.lambda.module.modules.client.TaskFlowModule import com.lambda.task.Task import com.lambda.util.BaritoneUtils @@ -60,7 +59,6 @@ class BreakBlock @Ta5kBuilder constructor( private var drop: ItemEntity? = null private var state = State.BREAKING - private var isValid = false enum class State { BREAKING, COLLECTING @@ -80,18 +78,6 @@ class BreakBlock @Ta5kBuilder constructor( } init { - rotate { - request { - if (state != State.BREAKING) return@request null - if (!rotate || ctx.instantBreak) return@request null - - lookAtBlock(blockPos, rotation, interact, sides) - } - finished { context -> - isValid = context.isValid - } - } - listen { drop?.let { itemDrop -> if (!world.entities.contains(itemDrop)) { @@ -113,8 +99,12 @@ class BreakBlock @Ta5kBuilder constructor( return@listen } ?: BaritoneUtils.cancel() - if (isValid || !rotate || ctx.instantBreak) { - hitBlock(ctx.result.side) + if (rotate && !ctx.instantBreak && state == State.BREAKING) { + lookAtBlock(blockPos, sides, config = interact).let { + if (it.requestBy(rotation).done) { + hitBlock(ctx.result.side) + } + } } if (done()) { diff --git a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt index db16e9e14..51ab9721a 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/BuildTask.kt @@ -20,18 +20,21 @@ package com.lambda.task.tasks import com.lambda.Lambda.LOG import com.lambda.config.groups.BuildConfig import com.lambda.config.groups.InteractionConfig -import com.lambda.config.groups.RotationConfig +import com.lambda.config.groups.InventoryConfig +import com.lambda.interaction.request.rotation.RotationConfig import com.lambda.context.SafeContext import com.lambda.event.events.MovementEvent -import com.lambda.event.events.PacketEvent import com.lambda.event.events.TickEvent +import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.RotationManager.rotate +import com.lambda.interaction.request.rotation.RotationManager.onRotate import com.lambda.interaction.construction.blueprint.Blueprint import com.lambda.interaction.construction.blueprint.Blueprint.Companion.toStructure -import com.lambda.interaction.construction.blueprint.DynamicBlueprint +import com.lambda.interaction.construction.blueprint.PropagatingBlueprint +import com.lambda.interaction.construction.blueprint.TickingBlueprint import com.lambda.interaction.construction.blueprint.StaticBlueprint.Companion.toBlueprint import com.lambda.interaction.construction.context.BreakContext +import com.lambda.interaction.construction.context.BuildContext import com.lambda.interaction.construction.context.PlaceContext import com.lambda.interaction.construction.result.* import com.lambda.interaction.construction.simulation.BuildGoal @@ -43,10 +46,10 @@ import com.lambda.task.Task import com.lambda.util.BaritoneUtils import com.lambda.util.BlockUtils import com.lambda.util.BlockUtils.blockState +import com.lambda.util.Communication.info import com.lambda.util.Formatting.string import com.lambda.util.collections.LimitedDecayQueue import com.lambda.util.extension.Structure -import net.minecraft.network.packet.s2c.play.BlockUpdateS2CPacket import net.minecraft.util.math.BlockPos class BuildTask @Ta5kBuilder constructor( @@ -56,121 +59,140 @@ class BuildTask @Ta5kBuilder constructor( private val build: BuildConfig = TaskFlowModule.build, private val rotation: RotationConfig = TaskFlowModule.rotation, private val interact: InteractionConfig = TaskFlowModule.interact, + private val inventory: InventoryConfig = TaskFlowModule.inventory, ) : Task() { - override val name: String get() = "Building $blueprint with ${(placements / (age / 20.0 + 0.001)).string} p/s" + override val name: String get() = "Building $blueprint with ${(breaks / (age / 20.0 + 0.001)).string} b/s ${(placements / (age / 20.0 + 0.001)).string} p/s" - private val pendingPlacements = LimitedDecayQueue(20, 1000) - private val pendingBreaks = LimitedDecayQueue(20, 1000) + private val pendingInteractions = LimitedDecayQueue( + build.maxPendingInteractions, build.interactionTimeout * 50L + ) { info("${it::class.simpleName} at ${it.expectedPos.toShortString()} timed out") } + private var currentInteraction: BuildContext? = null + private val instantBreaks = mutableSetOf() - private var currentPlacement: PlaceContext? = null private var placements = 0 private var breaks = 0 + private var goodPositions = setOf() override fun SafeContext.onStart() { - (blueprint as? DynamicBlueprint)?.create() + (blueprint as? PropagatingBlueprint)?.next() } init { listen { - currentPlacement?.let { context -> - if (!context.rotation.isValid) return@let - context.place(interact.swingHand) - pendingPlacements.add(context) - currentPlacement = null + currentInteraction?.let { context -> + if (context.shouldRotate(build) && !context.rotation.done) return@let + context.interact(interact.swingHand) } + instantBreaks.forEach { context -> + context.interact(interact.swingHand) + pendingInteractions.add(context) + } + instantBreaks.clear() + } - (blueprint as? DynamicBlueprint)?.update() + listen { + (blueprint as? TickingBlueprint)?.tick() if (finishOnDone && blueprint.structure.isEmpty()) { failure("Structure is empty") return@listen } + } + + onRotate { +// val sim = blueprint.simulation(interact, rotation, inventory) +// BlockPos.iterateOutwards(player.blockPos, 5, 5, 5).forEach { pos -> +// sim.simulate(pos.toFastVec()) +// } // ToDo: Simulate for each pair player positions that work - val results = blueprint.simulate(player.getCameraPosVec(mc.tickDelta)) - TaskFlowModule.drawables = results.filterIsInstance().plus(pendingPlacements) + val results = blueprint.simulate(player.eyePos, interact, rotation, inventory, build) + TaskFlowModule.drawables = results.filterIsInstance() + .plus(pendingInteractions.toList()) +// .plus(sim.goodPositions()) - val instantResults = results.filterIsInstance() - .filter { it.context.instantBreak } - .sorted() - .take(build.breaksPerTick) + if (build.breaksPerTick > 1) { + val instantResults = results.filterIsInstance() + .filter { it.context.instantBreak } + .sorted() + .take(build.breaksPerTick) - if (build.breaksPerTick > 1 && instantResults.isNotEmpty()) { - instantResults.forEach { - it.resolve().execute(this@BuildTask, pauseParent = false) - } - return@listen + instantBreaks.addAll(instantResults.map { it.context }) + + if (instantResults.isNotEmpty()) return@onRotate } - val resultsWithoutPending = results.filterNot { res -> - val blockedPositions = pendingPlacements.map { it.expectedPos } - res is PlaceResult.Place && res.context.expectedPos in blockedPositions + val resultsWithoutPending = results.filterNot { result -> + result.blockPos in pendingInteractions.map { it.expectedPos } } - val result = resultsWithoutPending.minOrNull() ?: return@listen - when (result) { + val bestResult = resultsWithoutPending.minOrNull() ?: return@onRotate + when (bestResult) { is BuildResult.Done, is BuildResult.Ignored, is BuildResult.Unbreakable, is BuildResult.Restricted, - is BuildResult.NoPermission, - -> { + is BuildResult.NoPermission -> { + if (pendingInteractions.isNotEmpty()) return@onRotate + if (blueprint is PropagatingBlueprint) { + blueprint.next() + return@onRotate + } if (finishOnDone) success() } - is BuildResult.NotVisible, is PlaceResult.NoIntegrity -> { - if (build.pathing) BaritoneUtils.setGoalAndPath(BuildGoal(blueprint.simulation())) + is BuildResult.NotVisible, + is PlaceResult.NoIntegrity -> { + if (!build.pathing) return@onRotate + val goal = BuildGoal(blueprint.simulation(interact, rotation, inventory, build), player.blockPos) + BaritoneUtils.setGoalAndPath(goal) } is Navigable -> { - if (build.pathing) BaritoneUtils.setGoalAndPath(result.goal) + if (build.pathing) BaritoneUtils.setGoalAndPath(bestResult.goal) } - is PlaceResult.Place -> { - if (pendingPlacements.size >= build.maxPendingPlacements) return@listen + is BuildResult.Contextual -> { + if (pendingInteractions.size >= build.maxPendingInteractions) return@onRotate -// if (!result.context.rotation.isValid) { -// currentPlacement = result.context -// return@listen -// } - - currentPlacement = result.context + currentInteraction = bestResult.context } is Resolvable -> { - LOG.info("Resolving: ${result.name}") - - if (result is BreakResult.Break) { - result.collectDrop = collectDrops - } + LOG.info("Resolving: ${bestResult.name}") - result.resolve().execute(this@BuildTask, pauseParent = result.pausesParent) + bestResult.resolve().execute(this@BuildTask) } } - } - rotate { - request { - if (!build.rotateForPlace) return@request null - currentPlacement?.rotation - } + if (!build.rotateForPlace) return@onRotate + val rotateTo = currentInteraction?.rotation ?: return@onRotate + + rotation.request(rotateTo) } listen { - val context = currentPlacement ?: return@listen + val context = currentInteraction ?: return@listen + if (context !is PlaceContext) return@listen val hitBlock = context.result.blockPos.blockState(world).block if (hitBlock in BlockUtils.interactionBlacklist) { it.input.sneaking = true } } - listen { event -> - val packet = event.packet - if (packet !is BlockUpdateS2CPacket) return@listen + listen { event -> + val context = currentInteraction ?: return@listen + if (context.expectedPos != event.pos) return@listen + currentInteraction = null + pendingInteractions.add(context) + } - pendingPlacements.firstOrNull { it.expectedPos == packet.pos }?.let { - if (it.targetState.matches(packet.state, packet.pos, world)) { - pendingPlacements.remove(it) - placements++ + listen(alwaysListen = true) { event -> + pendingInteractions.firstOrNull { it.expectedPos == event.pos }?.let { context -> + pendingInteractions.remove(context) + if (!context.targetState.matches(event.newState, event.pos, world)) return@let + when (context) { + is BreakContext -> breaks++ + is PlaceContext -> placements++ } } } @@ -194,7 +216,8 @@ class BuildTask @Ta5kBuilder constructor( build: BuildConfig = TaskFlowModule.build, rotation: RotationConfig = TaskFlowModule.rotation, interact: InteractionConfig = TaskFlowModule.interact, - ) = BuildTask(toBlueprint(), finishOnDone, collectDrops, build, rotation, interact) + inventory: InventoryConfig = TaskFlowModule.inventory, + ) = BuildTask(toBlueprint(), finishOnDone, collectDrops, build, rotation, interact, inventory) @Ta5kBuilder fun Blueprint.build( @@ -203,7 +226,8 @@ class BuildTask @Ta5kBuilder constructor( build: BuildConfig = TaskFlowModule.build, rotation: RotationConfig = TaskFlowModule.rotation, interact: InteractionConfig = TaskFlowModule.interact, - ) = BuildTask(this, finishOnDone, collectDrops, build, rotation, interact) + inventory: InventoryConfig = TaskFlowModule.inventory, + ) = BuildTask(this, finishOnDone, collectDrops, build, rotation, interact, inventory) @Ta5kBuilder fun breakAndCollectBlock( @@ -213,9 +237,10 @@ class BuildTask @Ta5kBuilder constructor( build: BuildConfig = TaskFlowModule.build, rotation: RotationConfig = TaskFlowModule.rotation, interact: InteractionConfig = TaskFlowModule.interact, + inventory: InventoryConfig = TaskFlowModule.inventory, ) = BuildTask( blockPos.toStructure(TargetState.Air).toBlueprint(), - finishOnDone, collectDrops, build, rotation, interact + finishOnDone, collectDrops, build, rotation, interact, inventory ) @Ta5kBuilder @@ -226,9 +251,10 @@ class BuildTask @Ta5kBuilder constructor( build: BuildConfig = TaskFlowModule.build, rotation: RotationConfig = TaskFlowModule.rotation, interact: InteractionConfig = TaskFlowModule.interact, + inventory: InventoryConfig = TaskFlowModule.inventory, ) = BuildTask( blockPos.toStructure(TargetState.Air).toBlueprint(), - finishOnDone, collectDrops, build, rotation, interact + finishOnDone, collectDrops, build, rotation, interact, inventory ) } } diff --git a/common/src/main/kotlin/com/lambda/task/tasks/OpenContainer.kt b/common/src/main/kotlin/com/lambda/task/tasks/OpenContainer.kt index 7336a7b8d..9c222b169 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/OpenContainer.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/OpenContainer.kt @@ -18,11 +18,11 @@ package com.lambda.task.tasks import com.lambda.config.groups.InteractionConfig -import com.lambda.config.groups.RotationConfig +import com.lambda.interaction.request.rotation.RotationConfig import com.lambda.event.events.InventoryEvent -import com.lambda.event.events.RotationEvent +import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen -import com.lambda.interaction.visibilty.VisibilityChecker.lookAtBlock +import com.lambda.interaction.request.rotation.visibilty.lookAtBlock import com.lambda.module.modules.client.TaskFlowModule import com.lambda.task.Task import com.lambda.util.world.raycast.RayCastUtils.blockResult @@ -80,22 +80,16 @@ class OpenContainer @Ta5kBuilder constructor( } } - listen { event -> - if (!rotate) return@listen - event.request = lookAtBlock(blockPos, rotation, interact, sides) - } - - listen { - if (!rotate) return@listen + listen { if (state != State.SCOPING) return@listen - if (!it.request.isValid) return@listen - if (inScope++ >= interact.scopeThreshold) { - val hitResult = it.request.checkedResult?.blockResult ?: return@listen - interaction.interactBlock(player, Hand.MAIN_HAND, hitResult) + val target = lookAtBlock(blockPos, sides, config = interact) + if (rotate && !target.requestBy(rotation).done) return@listen - state = State.OPENING - } + val hitResult = target.hit?.hitIfValid()?.blockResult ?: return@listen + interaction.interactBlock(player, Hand.MAIN_HAND, hitResult) + + state = State.OPENING } } } diff --git a/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt b/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt index 390017ad6..a159fbd48 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt @@ -26,8 +26,11 @@ import com.lambda.event.events.RotationEvent import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.construction.context.PlaceContext -import com.lambda.interaction.rotation.Rotation.Companion.rotation -import com.lambda.interaction.rotation.RotationRequest +import com.lambda.interaction.request.rotation.Rotation.Companion.rotation +import com.lambda.interaction.request.rotation.RotationConfig +import com.lambda.interaction.request.rotation.RotationManager.onRotate +import com.lambda.interaction.request.rotation.RotationRequest +import com.lambda.interaction.request.rotation.visibilty.lookAt import com.lambda.module.modules.client.TaskFlowModule import com.lambda.task.Task import com.lambda.util.BlockUtils @@ -40,6 +43,7 @@ import net.minecraft.network.packet.s2c.play.BlockUpdateS2CPacket class PlaceBlock @Ta5kBuilder constructor( private val ctx: PlaceContext, private val rotate: Boolean = TaskFlowModule.build.rotateForPlace, + private val rotation: RotationConfig = TaskFlowModule.rotation, private val interact: InteractionConfig = TaskFlowModule.interact, private val waitForConfirmation: Boolean = TaskFlowModule.build.placeConfirmation, ) : Task() { @@ -69,10 +73,7 @@ class PlaceBlock @Ta5kBuilder constructor( state = State.ROTATING } else { state = State.PRIME_ROTATION - primeContext = RotationRequest( - ctx.primeDirection.rotation, - ctx.rotation.config, - ) + primeContext = ctx.rotation } if (matches) { @@ -85,25 +86,26 @@ class PlaceBlock @Ta5kBuilder constructor( } init { - listen { event -> - if (!rotate) return@listen - event.request = if (state == State.PRIME_ROTATION) { - primeContext - } else ctx.rotation + onRotate { + rotation.request( + if (state == State.PRIME_ROTATION) { + primeContext ?: return@onRotate + } else ctx.rotation + ) } listen { event -> if (!rotate) return@listen - if (!event.request.isValid) return@listen + if (!event.request.done) return@listen + when (state) { State.PRIME_ROTATION -> { - if (event.request.rotation != primeContext?.rotation) return@listen + if (event.request.target != primeContext?.target) return@listen state = State.ROTATING } State.ROTATING -> { - if (event.request.rotation != ctx.rotation.rotation) return@listen - if (!event.request.isValid) return@listen + if (event.request.target != ctx.rotation.target) return@listen state = State.PLACING } diff --git a/common/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt b/common/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt index 1fe67d663..9d17df571 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/PlaceContainer.kt @@ -48,7 +48,7 @@ class PlaceContainer @Ta5kBuilder constructor( it.blockPos .toStructure(TargetState.Stack(startStack)) .toBlueprint() - .simulate(player.getCameraPosVec(mc.tickDelta)) + .simulate(player.eyePos) } val succeeds = results.filterIsInstance().filter { diff --git a/common/src/main/kotlin/com/lambda/threading/Threading.kt b/common/src/main/kotlin/com/lambda/threading/Threading.kt index bb329a6be..50a06eb2a 100644 --- a/common/src/main/kotlin/com/lambda/threading/Threading.kt +++ b/common/src/main/kotlin/com/lambda/threading/Threading.kt @@ -126,7 +126,7 @@ inline fun runGameScheduled(crossinline block: () -> Unit) { * * @param block The task to be executed on the game's main thread within a safe context. */ -inline fun runSafeGameConcurrent(crossinline block: SafeContext.() -> Unit) { +inline fun runSafeGameScheduled(crossinline block: SafeContext.() -> Unit) { runGameScheduled { runSafe { block() } } } diff --git a/common/src/main/kotlin/com/lambda/util/Communication.kt b/common/src/main/kotlin/com/lambda/util/Communication.kt index 36804a262..687dd42cd 100644 --- a/common/src/main/kotlin/com/lambda/util/Communication.kt +++ b/common/src/main/kotlin/com/lambda/util/Communication.kt @@ -28,7 +28,7 @@ import com.lambda.module.Module import com.lambda.module.ModuleRegistry import com.lambda.module.modules.client.GuiSettings import com.lambda.threading.runSafe -import com.lambda.threading.runSafeGameConcurrent +import com.lambda.threading.runSafeGameScheduled import com.lambda.util.StringUtils.capitalize import com.lambda.util.text.* import net.minecraft.client.toast.SystemToast @@ -74,7 +74,7 @@ object Communication { buildText { text(this@toast.source(logLevel, color = Color.YELLOW)) }.let { title -> - runSafeGameConcurrent { + runSafeGameScheduled { mc.toastManager.add(logLevel.toast(title, message)) } } @@ -99,7 +99,7 @@ object Communication { text(this@log.source(logLevel, source, textSource)) text(message) }.let { log -> - runSafeGameConcurrent { + runSafeGameScheduled { player.sendMessage(log) } } diff --git a/common/src/main/kotlin/com/lambda/util/Nameable.kt b/common/src/main/kotlin/com/lambda/util/Nameable.kt index 277454421..196eb1e52 100644 --- a/common/src/main/kotlin/com/lambda/util/Nameable.kt +++ b/common/src/main/kotlin/com/lambda/util/Nameable.kt @@ -23,8 +23,8 @@ package com.lambda.util interface Nameable { val name: String val commandName get() = name.trim().replace(' ', '_') +} - interface NamedEnum { - val displayName: String - } +interface NamedEnum { + val displayName: String } diff --git a/common/src/main/kotlin/com/lambda/util/ServerTPS.kt b/common/src/main/kotlin/com/lambda/util/ServerTPS.kt new file mode 100644 index 000000000..b29903082 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/ServerTPS.kt @@ -0,0 +1,51 @@ +/* + * 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 + +import com.lambda.event.events.ConnectionEvent +import com.lambda.event.events.PacketEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.util.collections.LimitedDecayQueue +import net.minecraft.network.packet.s2c.play.WorldTimeUpdateS2CPacket + +object ServerTPS { + // Server sends exactly one world time update every 20 server ticks (one per second). + private val updateHistory = LimitedDecayQueue(61, 60000) + private var lastUpdate = 0L + + val averageMSPerTick: Double + get() = (if (updateHistory.isEmpty()) 1000.0 else updateHistory.average()) / 20 + + init { + listen(priority = 10000) { + if (it.packet !is WorldTimeUpdateS2CPacket) return@listen + val currentTime = System.currentTimeMillis() + + if (lastUpdate != 0L) { + updateHistory.add(currentTime - lastUpdate) + } + + lastUpdate = currentTime + } + + listen { + updateHistory.clear() + lastUpdate = 0 + } + } +} diff --git a/common/src/main/kotlin/com/lambda/util/Timer.kt b/common/src/main/kotlin/com/lambda/util/Timer.kt new file mode 100644 index 000000000..e92f3c054 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/Timer.kt @@ -0,0 +1,130 @@ +/* + * 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 + +import com.lambda.context.SafeContext +import com.lambda.threading.runSafe +import kotlin.time.Duration +import kotlin.time.TimeSource + +/** + * A utility class to manage time-based operations, such as delays and periodic tasks. + */ +class Timer { + /** + * Records the timestamp of the last timing event using a monotonic clock. + */ + private var lastTiming = TimeSource.Monotonic.markNow() + + /** + * Checks if the specified amount of time has passed since the last timing event. + * + * @param duration the time interval to check. + * @return `true` if the current time exceeds `lastTiming + duration`, `false` otherwise. + */ + fun timePassed(duration: Duration): Boolean = + lastTiming.elapsedNow() > duration + + /** + * Checks if the specified duration has passed and resets the timer if true. + * + * @param duration the time interval to check. + * @return `true` if the duration has passed and the timer was reset, `false` otherwise. + */ + fun delayIfPassed(duration: Duration): Boolean = + timePassed(duration).apply { + if (this) reset() + } + + /** + * Executes a given block of code if the specified duration has passed since the last timing event. + * Optionally resets the timer after execution. + * + * @param duration the time interval to check. + * @param reset whether to reset the timer after running the block. Defaults to `true`. + * @param block the code block to execute if the time has passed. + */ + fun runIfPassed(duration: Duration, reset: Boolean = true, block: () -> Unit) = + timePassed(duration).apply { + if (!this) return@apply + if (reset) reset() + + block() + } + + /** + * Executes a given block of code if the specified duration has not passed yet since the last timing event. + * Optionally resets the timer after execution. + * + * @param duration the time interval to check. + * @param reset whether to reset the timer after running the block. Defaults to `true`. + * @param block the code block to execute if the duration has not passed. + */ + fun runIfNotPassed(duration: Duration, reset: Boolean = true, block: () -> Unit) = + timePassed(duration).also { passed -> + if (passed) return@also + if (reset) reset() + + block() + } + + /** + * Executes a given block of code in safe context if the specified duration has passed since the last timing event. + * Optionally resets the timer after execution. + * + * @param duration the time interval to check. + * @param reset whether to reset the timer after running the block. Defaults to `true`. + * @param block the code block to execute if the duration has passed. + */ + fun runSafeIfPassed(duration: Duration, reset: Boolean = true, block: SafeContext.() -> Unit) = + timePassed(duration).also { passed -> + if (!passed) return@also + + runSafe { + if (reset) reset() + block() + } + } + + /** + * Executes a given block of code in safe context if the specified duration has not passed yet since the last timing event. + * Optionally resets the timer after execution. + * + * @param duration the time interval to check. + * @param reset whether to reset the timer after running the block. Defaults to `true`. + * @param block the code block to execute if the duration has not passed. + */ + fun runSafeIfNotPassed(duration: Duration, reset: Boolean = true, block: SafeContext.() -> Unit) = + timePassed(duration).also { passed -> + if (passed) return@also + + runSafe { + if (reset) reset() + block() + } + } + + /** + * Resets the timer by updating the last timing event to the current time. + * + * @param additionalDelay an additional delay to add to the current time. Defaults to `Duration.ZERO`. + */ + fun reset(additionalDelay: Duration = Duration.ZERO) { + lastTiming = TimeSource.Monotonic.markNow() + additionalDelay + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/util/collections/LimitedDecayQueue.kt b/common/src/main/kotlin/com/lambda/util/collections/LimitedDecayQueue.kt index 60760ace6..9dceafcb6 100644 --- a/common/src/main/kotlin/com/lambda/util/collections/LimitedDecayQueue.kt +++ b/common/src/main/kotlin/com/lambda/util/collections/LimitedDecayQueue.kt @@ -27,10 +27,12 @@ import java.util.concurrent.ConcurrentLinkedQueue * @param E The type of elements held in this collection. * @property sizeLimit The maximum number of elements the queue can hold at any given time. * @property maxAge The age (in milliseconds) after which elements are considered expired and are removed from the queue. + * @property onDecay Lambda function that is executed on decay of element [E]. */ class LimitedDecayQueue( private var sizeLimit: Int, private var maxAge: Long, + private val onDecay: (E) -> Unit = {} ) : AbstractMutableCollection() { private val queue: ConcurrentLinkedQueue> = ConcurrentLinkedQueue() @@ -44,7 +46,17 @@ class LimitedDecayQueue( @Synchronized override fun iterator(): MutableIterator { cleanUp() - return queue.map { it.first }.iterator() as MutableIterator + return object : MutableIterator { + private val delegate = queue.iterator() + + override fun hasNext(): Boolean = delegate.hasNext() + + override fun next(): E = delegate.next().first + + override fun remove() { + delegate.remove() // This affects the underlying queue directly + } + } } @Synchronized @@ -116,7 +128,7 @@ class LimitedDecayQueue( private fun cleanUp() { val now = Instant.now() while (queue.isNotEmpty() && now.minusMillis(maxAge).isAfter(queue.peek().second)) { - queue.poll() + onDecay(queue.poll().first) } } -} +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/util/collections/UpdatableLazy.kt b/common/src/main/kotlin/com/lambda/util/collections/UpdatableLazy.kt new file mode 100644 index 000000000..f70260392 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/collections/UpdatableLazy.kt @@ -0,0 +1,71 @@ +/* + * 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.collections + +/** + * A lazy-initialized value holder that allows the stored value to be reset and re-initialized on demand. + * + * This class supports lazy initialization for a value of type [T] using a provided initializer function. + * Once the value is accessed, it is initialized and stored. The `update` function can be called to reset + * the value, allowing it to be re-initialized using the same initializer function. + * + * @param T The type of the value being lazily initialized and managed. + * @constructor Accepts an initializer function that defines how the value should be created. + */ +class UpdatableLazy(private val initializer: () -> T) { + private var _value: T? = null + + /** + * Lazily initializes and retrieves a value of type [T] using the provided initializer function. + * If the value has not been initialized previously, the initializer function is called + * to generate the value, which is then cached for subsequent accesses. + * + * This property ensures that the value is only initialized when it is first accessed, + * and maintains its state until explicitly updated or reset. + * + * @return The lazily initialized value, or `null` if the initializer function + * is designed to produce a `null` result or has not been called yet. + */ + val value: T? + get() { + if (_value == null) _value = initializer() + return _value + } + + /** + * Resets the current value to a new value generated by the initializer function. + * + * This function effectively re-initializes the stored value by discarding the existing value, + * if any, and calling the initializer function to compute a new value. + * It is useful for scenarios where the lazily computed value needs to be refreshed or updated + * explicitly. + */ + fun update() { + _value = initializer() + } +} + +/** + * Provides a lazily-initialized value that can be reset and re-initialized on demand. + * This function utilizes an `UpdatableLazy` to manage the lazy initialization and allow updates. + * + * @param T The type of the value to be lazily initialized. + * @param initializer A lambda function that defines how the value should be computed. + * @return An `UpdatableLazy` instance capable of managing a lazily-initialized value. + */ +fun updatableLazy(initializer: () -> T) = UpdatableLazy(initializer) \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/util/combat/CombatUtils.kt b/common/src/main/kotlin/com/lambda/util/combat/CombatUtils.kt index 56d86a32c..e3bd840f2 100644 --- a/common/src/main/kotlin/com/lambda/util/combat/CombatUtils.kt +++ b/common/src/main/kotlin/com/lambda/util/combat/CombatUtils.kt @@ -70,6 +70,14 @@ object CombatUtils { fastEntitySearch(12.0) .any { player.health - explosionDamage(it.pos, player, 6.0) <= minHealth} + /** + * Calculates the damage dealt by an explosion to a living entity + * @param position The position of the explosion + * @param entity The entity to calculate the damage for + */ + fun SafeContext.crystalDamage(position: Vec3d, entity: LivingEntity) = + explosionDamage(position, entity, 6.0) + /** * Calculates the damage dealt by an explosion to a living entity * @@ -113,7 +121,7 @@ object CombatUtils { * @param entity The entity to calculate the velocity for * @param explosion The explosion to calculate the velocity for */ - fun SafeContext.explosionVelocity(entity: LivingEntity, explosion: Explosion) = + fun explosionVelocity(entity: LivingEntity, explosion: Explosion) = explosionVelocity(entity, explosion.position, explosion.power.toDouble()) /** diff --git a/common/src/main/kotlin/com/lambda/util/extension/Entity.kt b/common/src/main/kotlin/com/lambda/util/extension/Entity.kt index a58b76cb2..638986ffb 100644 --- a/common/src/main/kotlin/com/lambda/util/extension/Entity.kt +++ b/common/src/main/kotlin/com/lambda/util/extension/Entity.kt @@ -17,10 +17,8 @@ package com.lambda.util.extension -import com.lambda.interaction.rotation.Rotation -import com.lambda.util.math.lerp +import com.lambda.interaction.request.rotation.Rotation import net.minecraft.entity.Entity -import net.minecraft.entity.LivingEntity import net.minecraft.util.math.Vec3d val Entity.prevPos diff --git a/common/src/main/kotlin/com/lambda/util/extension/Enum.kt b/common/src/main/kotlin/com/lambda/util/extension/Enum.kt index b0fd3c90e..b3e3e32a5 100644 --- a/common/src/main/kotlin/com/lambda/util/extension/Enum.kt +++ b/common/src/main/kotlin/com/lambda/util/extension/Enum.kt @@ -17,12 +17,11 @@ package com.lambda.util.extension -import com.lambda.util.Nameable +import com.lambda.util.NamedEnum +import com.lambda.util.StringUtils.capitalize val Enum<*>.displayValue get() = - (this as? Nameable.NamedEnum)?.displayName ?: name.split('_').joinToString(" ") { low -> - low.lowercase().replaceFirstChar { - if (it.isLowerCase()) it.titlecase() else it.toString() - } - } + (this as? NamedEnum)?.displayName ?: name.split('_').joinToString(" ") { low -> + low.capitalize() + } \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/util/extension/World.kt b/common/src/main/kotlin/com/lambda/util/extension/World.kt index 03e6dc789..34ab2dc54 100644 --- a/common/src/main/kotlin/com/lambda/util/extension/World.kt +++ b/common/src/main/kotlin/com/lambda/util/extension/World.kt @@ -27,6 +27,17 @@ import net.minecraft.util.math.BlockPos import net.minecraft.world.World import java.awt.Color +val SafeContext.isOverworld: Boolean get() = world.registryKey == World.OVERWORLD +val SafeContext.isNether: Boolean get() = world.registryKey == World.NETHER +val SafeContext.isEnd: Boolean get() = world.registryKey == World.END +val SafeContext.dimensionName: String + get() = when { + isOverworld -> "Overworld" + isNether -> "Nether" + isEnd -> "End" + else -> "Unknown" + } + fun SafeContext.collisionShape(state: BlockState, pos: BlockPos) = state.getCollisionShape(world, pos).offset(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()) diff --git a/common/src/main/kotlin/com/lambda/util/math/Linear.kt b/common/src/main/kotlin/com/lambda/util/math/Linear.kt index f4fb37922..def44e228 100644 --- a/common/src/main/kotlin/com/lambda/util/math/Linear.kt +++ b/common/src/main/kotlin/com/lambda/util/math/Linear.kt @@ -17,7 +17,7 @@ package com.lambda.util.math -import com.lambda.interaction.rotation.Rotation +import com.lambda.interaction.request.rotation.Rotation import net.minecraft.util.math.Box import net.minecraft.util.math.Vec3d import java.awt.Color diff --git a/common/src/main/kotlin/com/lambda/util/math/Vectors.kt b/common/src/main/kotlin/com/lambda/util/math/Vectors.kt index 823b54328..8784cf45b 100644 --- a/common/src/main/kotlin/com/lambda/util/math/Vectors.kt +++ b/common/src/main/kotlin/com/lambda/util/math/Vectors.kt @@ -17,6 +17,7 @@ package com.lambda.util.math +import com.lambda.util.math.MathUtils.floorToInt import com.lambda.util.math.MathUtils.sq import net.minecraft.entity.Entity import net.minecraft.util.math.* @@ -24,8 +25,12 @@ import kotlin.math.pow import kotlin.math.roundToInt import kotlin.math.sqrt +fun BlockPos.getHitVec(side: Direction): Vec3d = + side.hitVecOffset + this + /* Direction */ -val Direction.hitVecOffset: Vec3d get() = CENTER + vector * 0.5 +val Direction.hitVecOffset: Vec3d get() = + CENTER + vector.vec3d * 0.5 fun EightWayDirection.rotateClockwise(steps: Int) = EightWayDirection.entries[(ordinal + steps) % 8] @@ -38,9 +43,15 @@ operator fun Vec2f.component2() = y fun Vec3d.approximate(other: Vec3d, precision: Double = 2.0E-4): Boolean = (subtract(other) distSq Vec3d.ZERO) > precision.pow(2) -val Vec3d.blockPos: BlockPos +val Vec3d.roundedBlockPos: BlockPos get() = BlockPos(x.roundToInt(), y.roundToInt(), z.roundToInt()) +val Vec3d.flooredBlockPos: BlockPos + get() = BlockPos(x.floorToInt(), y.floorToInt(), z.floorToInt()) + +val Entity.netherCoord: Vec3d get() = pos.multiply(0.125, 1.0, 0.125) +val Entity.overworldCoord: Vec3d get() = pos.multiply(8.0, 1.0, 8.0) + fun Vec3d.interpolate(value: Double, max: Vec3d) = lerp(value, this, max) operator fun Vec3d.component1() = x @@ -58,16 +69,19 @@ infix operator fun Vec3d.plus(other: Vec3i): Vec3d = Vec3d(x + other.x, y + othe infix operator fun Vec3d.plus(other: Double): Vec3d = add(other, other, other) infix operator fun Vec3d.plus(other: Float): Vec3d = add(other.toDouble(), other.toDouble(), other.toDouble()) infix operator fun Vec3d.plus(other: Int): Vec3d = add(other.toDouble(), other.toDouble(), other.toDouble()) + infix operator fun Vec3d.minus(other: Vec3d): Vec3d = subtract(other) infix operator fun Vec3d.minus(other: Vec3i): Vec3d = Vec3d(x - other.x, y - other.y, z - other.z) infix operator fun Vec3d.minus(other: Double): Vec3d = subtract(other, other, other) infix operator fun Vec3d.minus(other: Float): Vec3d = subtract(other.toDouble(), other.toDouble(), other.toDouble()) infix operator fun Vec3d.minus(other: Int): Vec3d = subtract(other.toDouble(), other.toDouble(), other.toDouble()) + infix operator fun Vec3d.times(other: Vec3d): Vec3d = multiply(other) infix operator fun Vec3d.times(other: Vec3i): Vec3d = Vec3d(x * other.x, y * other.y, z * other.z) infix operator fun Vec3d.times(other: Double): Vec3d = multiply(other) infix operator fun Vec3d.times(other: Float): Vec3d = multiply(other.toDouble()) infix operator fun Vec3d.times(other: Int): Vec3d = multiply(other.toDouble()) + infix operator fun Vec3d.div(other: Vec3d): Vec3d = multiply(1.0 / other.x, 1.0 / other.y, 1.0 / other.z) infix operator fun Vec3d.div(other: Vec3i): Vec3d = Vec3d(x / other.x, y / other.y, z / other.z) infix operator fun Vec3d.div(other: Double): Vec3d = times(1 / other) @@ -75,33 +89,24 @@ infix operator fun Vec3d.div(other: Float): Vec3d = times(1 / other) infix operator fun Vec3d.div(other: Int): Vec3d = times(1 / other) /* Vec3i */ -fun BlockPos.getHitVec(side: Direction): Vec3d = - side.hitVecOffset + this +val Vec3i.vec3d get() = + Vec3d(x.toDouble(), y.toDouble(), z.toDouble()) infix fun Vec3i.dist(other: Vec3d): Double = sqrt(this distSq other) infix fun Vec3i.dist(other: Vec3i): Double = sqrt((this distSq other).toDouble()) infix fun Vec3i.distSq(other: Vec3d): Double = getSquaredDistance(other) infix fun Vec3i.distSq(other: Vec3i): Int = (x - other.x).sq + (y - other.y).sq + (z - other.z).sq -infix operator fun Vec3i.plus(other: Vec3d): Vec3i = Vec3i(x + other.x.toInt(), y + other.y.toInt(), z + other.z.toInt()) infix operator fun Vec3i.plus(other: Vec3i): Vec3i = add(other) -infix operator fun Vec3i.plus(other: Double): Vec3i = add(other.toInt(), other.toInt(), other.toInt()) -infix operator fun Vec3i.plus(other: Float): Vec3i = add(other.toInt(), other.toInt(), other.toInt()) infix operator fun Vec3i.plus(other: Int): Vec3i = add(other, other, other) -infix operator fun Vec3i.minus(other: Vec3d): Vec3i = Vec3i(x - other.x.toInt(), y - other.y.toInt(), z - other.z.toInt()) + infix operator fun Vec3i.minus(other: Vec3i): Vec3i = subtract(other) -infix operator fun Vec3i.minus(other: Double): Vec3i = add(-other.toInt(), -other.toInt(), -other.toInt()) -infix operator fun Vec3i.minus(other: Float): Vec3i = add(-other.toInt(), -other.toInt(), -other.toInt()) infix operator fun Vec3i.minus(other: Int): Vec3i = add(-other, -other, -other) -infix operator fun Vec3i.times(other: Vec3d): Vec3i = Vec3i(x * other.x.toInt(), y * other.y.toInt(), z * other.z.toInt()) + infix operator fun Vec3i.times(other: Vec3i): Vec3i = Vec3i(x * other.x, y * other.y, z * other.z) -infix operator fun Vec3i.times(other: Double): Vec3i = multiply(other.toInt()) -infix operator fun Vec3i.times(other: Float): Vec3i = multiply(other.toInt()) infix operator fun Vec3i.times(other: Int): Vec3i = multiply(other) -infix operator fun Vec3i.div(other: Vec3d): Vec3i = Vec3i((x / other.x).toInt(), (y / other.y).toInt(), (z / other.z).toInt()) + infix operator fun Vec3i.div(other: Vec3i): Vec3i = Vec3i(x / other.x, y / other.y, z / other.z) -infix operator fun Vec3i.div(other: Double): Vec3i = times(1 / other) -infix operator fun Vec3i.div(other: Float): Vec3i = times(1 / other) infix operator fun Vec3i.div(other: Int): Vec3i = times(1 / other) /* Entity */ diff --git a/common/src/main/kotlin/com/lambda/util/player/MovementUtils.kt b/common/src/main/kotlin/com/lambda/util/player/MovementUtils.kt index 7231c501d..d3eeb33d6 100644 --- a/common/src/main/kotlin/com/lambda/util/player/MovementUtils.kt +++ b/common/src/main/kotlin/com/lambda/util/player/MovementUtils.kt @@ -18,7 +18,7 @@ package com.lambda.util.player import com.lambda.context.SafeContext -import com.lambda.interaction.RotationManager +import com.lambda.interaction.request.rotation.RotationManager import com.lambda.util.math.MathUtils.toDegree import com.lambda.util.math.MathUtils.toInt import com.lambda.util.math.MathUtils.toRadian diff --git a/common/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt b/common/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt index c71a44700..66bb329df 100644 --- a/common/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt +++ b/common/src/main/kotlin/com/lambda/util/player/PlayerUtils.kt @@ -5,6 +5,7 @@ import com.mojang.authlib.GameProfile import net.minecraft.client.network.ClientPlayerEntity import net.minecraft.client.network.OtherClientPlayerEntity import net.minecraft.client.network.PlayerListEntry +import net.minecraft.entity.player.PlayerEntity fun SafeContext.copyPlayer(entity: ClientPlayerEntity) = ClientPlayerEntity(mc, world, mc.networkHandler, null, null, entity.isSneaking, entity.isSprinting).apply { @@ -22,15 +23,19 @@ fun SafeContext.copyPlayer(entity: ClientPlayerEntity) = isOnGround = entity.isOnGround } -fun SafeContext.spawnFakePlayer(profile: GameProfile): OtherClientPlayerEntity { +fun SafeContext.spawnFakePlayer( + profile: GameProfile, + reference: PlayerEntity = player, + addToWorld: Boolean = true +): OtherClientPlayerEntity { val entity = OtherClientPlayerEntity(world, profile).apply { - copyFrom(player) + copyFrom(reference) playerListEntry = PlayerListEntry(profile, false) id = -2024 - 4 - 20 } - world.addEntity(entity) + if (addToWorld) world.addEntity(entity) return entity } diff --git a/common/src/main/kotlin/com/lambda/util/player/prediction/PredictionEntity.kt b/common/src/main/kotlin/com/lambda/util/player/prediction/PredictionEntity.kt index 7ce175c09..4e7438e64 100644 --- a/common/src/main/kotlin/com/lambda/util/player/prediction/PredictionEntity.kt +++ b/common/src/main/kotlin/com/lambda/util/player/prediction/PredictionEntity.kt @@ -19,7 +19,7 @@ package com.lambda.util.player.prediction import com.lambda.Lambda.mc import com.lambda.context.SafeContext -import com.lambda.interaction.rotation.Rotation +import com.lambda.interaction.request.rotation.Rotation import com.lambda.threading.runSafe import com.lambda.util.BlockUtils.blockState import com.lambda.util.BlockUtils.flooredPos diff --git a/common/src/main/kotlin/com/lambda/util/player/prediction/PredictionTick.kt b/common/src/main/kotlin/com/lambda/util/player/prediction/PredictionTick.kt index 972f50133..3b8f66d0c 100644 --- a/common/src/main/kotlin/com/lambda/util/player/prediction/PredictionTick.kt +++ b/common/src/main/kotlin/com/lambda/util/player/prediction/PredictionTick.kt @@ -17,7 +17,7 @@ package com.lambda.util.player.prediction -import com.lambda.interaction.rotation.Rotation +import com.lambda.interaction.request.rotation.Rotation import net.minecraft.util.math.Box import net.minecraft.util.math.Vec3d diff --git a/common/src/main/kotlin/com/lambda/util/world/raycast/RayCastMask.kt b/common/src/main/kotlin/com/lambda/util/world/raycast/InteractionMask.kt similarity index 91% rename from common/src/main/kotlin/com/lambda/util/world/raycast/RayCastMask.kt rename to common/src/main/kotlin/com/lambda/util/world/raycast/InteractionMask.kt index c9fa40a47..070d29ab8 100644 --- a/common/src/main/kotlin/com/lambda/util/world/raycast/RayCastMask.kt +++ b/common/src/main/kotlin/com/lambda/util/world/raycast/InteractionMask.kt @@ -17,7 +17,7 @@ package com.lambda.util.world.raycast -enum class RayCastMask(val block: Boolean, val entity: Boolean) { +enum class InteractionMask(val block: Boolean, val entity: Boolean) { BOTH(true, true), BLOCK(true, false), ENTITY(false, true) diff --git a/common/src/main/kotlin/com/lambda/util/world/raycast/RayCastUtils.kt b/common/src/main/kotlin/com/lambda/util/world/raycast/RayCastUtils.kt index b523f3361..f3f202f6f 100644 --- a/common/src/main/kotlin/com/lambda/util/world/raycast/RayCastUtils.kt +++ b/common/src/main/kotlin/com/lambda/util/world/raycast/RayCastUtils.kt @@ -19,7 +19,7 @@ package com.lambda.util.world.raycast import com.lambda.Lambda.mc import com.lambda.context.SafeContext -import com.lambda.interaction.rotation.Rotation +import com.lambda.interaction.request.rotation.Rotation import com.lambda.threading.runSafe import com.lambda.util.math.distSq import net.minecraft.client.network.ClientPlayerEntity @@ -40,12 +40,12 @@ object RayCastUtils { fun SafeContext.rayCast( start: Vec3d, - end: Vec3d, + direction: Vec3d, reach: Double, - mask: RayCastMask, + mask: InteractionMask, fluids: Boolean = false, ): HitResult? { - val vec = end.multiply(reach) + val vec = direction.multiply(reach) val point = start.add(vec) val block = run { @@ -73,7 +73,7 @@ object RayCastUtils { // ToDo: Should rather move player hitbox down and check collision fun distanceToGround(maxDist: Double = 100.0) = runSafe { val pos = player.pos.add(0.0, 0.1, 0.0) - val cast = Rotation.DOWN.rayCast(maxDist, pos, false, RayCastMask.BLOCK) ?: return@runSafe maxDist + val cast = Rotation.DOWN.rayCast(maxDist, pos, false, InteractionMask.BLOCK) ?: return@runSafe maxDist return@runSafe max(0.0, pos.y - cast.pos.y) } diff --git a/common/src/main/resources/lambda.accesswidener b/common/src/main/resources/lambda.accesswidener index e916c247e..bb35ed9b5 100644 --- a/common/src/main/resources/lambda.accesswidener +++ b/common/src/main/resources/lambda.accesswidener @@ -27,6 +27,7 @@ accessible method net/minecraft/entity/Entity setFlag (IZ)V accessible field net/minecraft/client/network/AbstractClientPlayerEntity playerListEntry Lnet/minecraft/client/network/PlayerListEntry; accessible field net/minecraft/entity/LivingEntity jumpingCooldown I accessible field net/minecraft/entity/Entity pos Lnet/minecraft/util/math/Vec3d; +accessible field net/minecraft/client/network/ClientPlayerInteractionManager lastSelectedSlot I # Camera accessible method net/minecraft/client/render/Camera setPos (DDD)V @@ -63,6 +64,8 @@ accessible field net/minecraft/network/ClientConnection packetsSentCounter I accessible field net/minecraft/network/ClientConnection packetsReceivedCounter I accessible field net/minecraft/network/packet/c2s/login/LoginKeyC2SPacket encryptedSecretKey [B accessible field net/minecraft/network/packet/c2s/login/LoginKeyC2SPacket nonce [B +accessible method net/minecraft/network/packet/c2s/play/PlayerInteractEntityC2SPacket (IZLnet/minecraft/network/packet/c2s/play/PlayerInteractEntityC2SPacket$InteractTypeHandler;)V +accessible field net/minecraft/network/packet/c2s/play/PlayerInteractEntityC2SPacket ATTACK Lnet/minecraft/network/packet/c2s/play/PlayerInteractEntityC2SPacket$InteractTypeHandler; # Other accessible field net/minecraft/world/explosion/Explosion behavior Lnet/minecraft/world/explosion/ExplosionBehavior; diff --git a/common/src/main/resources/lambda.mixins.common.json b/common/src/main/resources/lambda.mixins.common.json index aac98e584..dbc6369e0 100644 --- a/common/src/main/resources/lambda.mixins.common.json +++ b/common/src/main/resources/lambda.mixins.common.json @@ -54,5 +54,8 @@ ], "injectors": { "defaultRequire": 1 - } + }, + "mixins": [ + "entity.PlayerInventoryMixin" + ] } diff --git a/gradle.properties b/gradle.properties index bcdb3613c..e6b81c37b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -51,6 +51,6 @@ kotlinForgeVersionMax=4.11.0 kotlin.code.style=official # Gradle https://gradle.org/ -org.gradle.jvmargs=-Xmx8192M \ +org.gradle.jvmargs=-Xmx4096M \ -XX:+HeapDumpOnOutOfMemoryError org.gradle.parallel=true