diff --git a/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java b/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java index 809a077c4..73a885d2b 100644 --- a/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java +++ b/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java @@ -2,11 +2,7 @@ import com.lambda.Lambda; import com.lambda.event.EventFlow; -import com.lambda.event.events.ClientEvent; -import com.lambda.event.events.ScreenEvent; -import com.lambda.event.events.ScreenHandlerEvent; -import com.lambda.event.events.TickEvent; -import com.lambda.interaction.RotationManager; +import com.lambda.event.events.*; import com.lambda.module.modules.player.Interact; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.Screen; @@ -27,7 +23,6 @@ public class MinecraftClientMixin { @Inject(method = "tick", at = @At("HEAD")) void onTickPre(CallbackInfo ci) { EventFlow.post(new TickEvent.Pre()); - RotationManager.update(); } @Inject(method = "tick", at = @At("RETURN")) 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 4517a9aca..5d6ef5862 100644 --- a/common/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java +++ b/common/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java @@ -18,6 +18,7 @@ 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; import java.util.Objects; @@ -67,11 +68,22 @@ boolean isSprinting(ClientPlayerEntity entity) { return EventFlow.post(new MovementEvent.Sprint(entity.isSprinting())).getSprint(); } + @Inject(method = "isSneaking", at = @At(value = "HEAD"), cancellable = true) + void redirectSneaking(CallbackInfoReturnable cir) { + ClientPlayerEntity self = (ClientPlayerEntity) (Object) this; + if (self != Lambda.getMc().player) return; + + if (self.input == null) return; + cir.setReturnValue(EventFlow.post(new MovementEvent.Sneak(self.input.sneaking)).getSneak()); + } + @Inject(method = "sendMovementPackets", at = @At(value = "HEAD"), cancellable = true) 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/kotlin/com/lambda/config/groups/InteractionSettings.kt b/common/src/main/kotlin/com/lambda/config/groups/InteractionSettings.kt index bcaf64897..e90ce0028 100644 --- a/common/src/main/kotlin/com/lambda/config/groups/InteractionSettings.kt +++ b/common/src/main/kotlin/com/lambda/config/groups/InteractionSettings.kt @@ -8,7 +8,7 @@ class InteractionSettings( 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", false, "Verify hit vector with ray casting (for very strict ACs)", vis) - override val resolution by c.setting("Resolution", 10, 1..30, 1, "How many raycast checks per surface (will be squared)") { vis() && useRayCast } + override val useRayCast by c.setting("Raycast", true, "Verify hit vector with ray casting (for very strict ACs)", vis) + override val resolution by c.setting("Resolution", 20, 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) } \ No newline at end of file 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 acaaeabd5..d6f2bd1fe 100644 --- a/common/src/main/kotlin/com/lambda/config/groups/Targeting.kt +++ b/common/src/main/kotlin/com/lambda/config/groups/Targeting.kt @@ -10,6 +10,7 @@ import com.lambda.util.math.VecUtils.distSq import com.lambda.util.world.entitySearch import net.minecraft.client.network.ClientPlayerEntity import net.minecraft.entity.LivingEntity +import net.minecraft.entity.decoration.ArmorStandEntity import net.minecraft.entity.mob.MobEntity import net.minecraft.entity.passive.PassiveEntity @@ -89,6 +90,7 @@ abstract class Targeting( !players && entity.isPlayer -> false !animals && entity is PassiveEntity -> false !hostiles && entity is MobEntity -> false + entity is ArmorStandEntity -> false !invisible && entity.isInvisibleTo(player) -> false !dead && entity.isDead -> false diff --git a/common/src/main/kotlin/com/lambda/event/events/MovementEvent.kt b/common/src/main/kotlin/com/lambda/event/events/MovementEvent.kt index 4087c26eb..57b42d626 100644 --- a/common/src/main/kotlin/com/lambda/event/events/MovementEvent.kt +++ b/common/src/main/kotlin/com/lambda/event/events/MovementEvent.kt @@ -21,6 +21,7 @@ abstract class MovementEvent : Event { ) : MovementEvent() class Sprint(var sprint: Boolean) : MovementEvent() + class Sneak(var sneak: Boolean) : MovementEvent() class ClipAtLedge( var clip: Boolean, 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 a60a9c5d1..c30b85e7d 100644 --- a/common/src/main/kotlin/com/lambda/event/events/PlayerPacketEvent.kt +++ b/common/src/main/kotlin/com/lambda/event/events/PlayerPacketEvent.kt @@ -16,7 +16,9 @@ abstract class PlayerPacketEvent : Event { var isSneaking: Boolean, ) : PlayerPacketEvent(), ICancellable by Cancellable() - class Post( + class Post : PlayerPacketEvent() + + class Send( val packet: PlayerMoveC2SPacket, ) : PlayerPacketEvent(), ICancellable by Cancellable() } \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt b/common/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt index 1a6e95e6d..58012ed71 100644 --- a/common/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt +++ b/common/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt @@ -5,6 +5,7 @@ import com.lambda.core.Loadable 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.threading.runSafe import com.lambda.util.collections.LimitedOrderedSet import com.lambda.util.math.VecUtils.approximate @@ -15,9 +16,17 @@ import com.lambda.util.extension.component2 import com.lambda.util.extension.component3 import net.minecraft.network.packet.c2s.play.ClientCommandC2SPacket import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket.* +import net.minecraft.util.math.Vec3d object PlayerPacketManager : Loadable { val configurations = LimitedOrderedSet(100) + + var lastPosition = Vec3d.ZERO + var lastRotation = Rotation.ZERO + var lastSprint = false + var lastSneak = false + var lastOnGround = false + private var sendTicks = 0 @JvmStatic @@ -36,11 +45,10 @@ object PlayerPacketManager : Loadable { } private fun SafeContext.updatePlayerPackets(new: PlayerPacketEvent.Pre) { - val previous = configurations.lastOrNull() ?: new configurations.add(new) - reportSprint(previous, new) - reportSneak(previous, new) + reportSprint(lastSprint, new.isSprinting) + reportSneak(lastSneak, new.isSneaking) if (mc.cameraEntity != player) return @@ -63,9 +71,9 @@ object PlayerPacketManager : Loadable { return } - val updatePosition = position.approximate(previous.position, 2.0E-4) || ++sendTicks >= 20 + val updatePosition = position.approximate(lastPosition, 2.0E-4) || ++sendTicks >= 20 // has to be different in float precision - val updateRotation = !rotation.equalFloat(previous.rotation) + val updateRotation = !rotation.equalFloat(lastRotation) val (x, y, z) = position @@ -82,46 +90,57 @@ object PlayerPacketManager : Loadable { LookAndOnGround(yaw, pitch, onGround) } - previous.onGround != onGround -> { + lastOnGround != onGround -> { OnGroundOnly(onGround) } else -> null } - if (updatePosition) { - sendTicks = 0 - } - packet?.let { - PlayerPacketEvent.Post(it).postChecked { + PlayerPacketEvent.Send(it).postChecked { connection.sendPacket(this.packet) + + if (updatePosition) { + sendTicks = 0 + lastPosition = position + } + + if (updateRotation) { + lastRotation = rotation + } + + lastOnGround = onGround } } + + PlayerPacketEvent.Post().post() } - private fun SafeContext.reportSprint(previous: PlayerPacketEvent.Pre, new: PlayerPacketEvent.Pre) { - if (previous.isSprinting == new.isSprinting) return + fun SafeContext.reportSprint(previous: Boolean, new: Boolean) { + if (previous == new) return - val state = if (new.isSprinting) { + val state = if (new) { ClientCommandC2SPacket.Mode.START_SPRINTING } else { ClientCommandC2SPacket.Mode.STOP_SPRINTING } connection.sendPacket(ClientCommandC2SPacket(player, state)) + lastSprint = new } - private fun SafeContext.reportSneak(previous: PlayerPacketEvent.Pre, new: PlayerPacketEvent.Pre) { - if (previous.isSneaking == new.isSneaking) return + fun SafeContext.reportSneak(previous: Boolean, new: Boolean) { + if (previous == new) return - val state = if (new.isSneaking) { + val state = if (new) { ClientCommandC2SPacket.Mode.PRESS_SHIFT_KEY } else { ClientCommandC2SPacket.Mode.RELEASE_SHIFT_KEY } connection.sendPacket(ClientCommandC2SPacket(player, state)) + lastSneak = new } } diff --git a/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt b/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt index b8d0a65c9..3ed70854e 100644 --- a/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt +++ b/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt @@ -38,13 +38,13 @@ object RotationManager : Loadable { fun Any.requestRotation( priority: Int = 0, alwaysListen: Boolean = false, - onUpdate: SafeContext.() -> RotationContext?, - onReceive: SafeContext.() -> Unit + onUpdate: SafeContext.(lastContext: RotationContext?) -> RotationContext?, + onReceive: SafeContext.() -> Unit = {} ) { var lastCtx: RotationContext? = null this.listener(priority, alwaysListen) { event -> - val rotationContext = onUpdate() + val rotationContext = onUpdate(event.context) rotationContext?.let { event.context = it @@ -162,23 +162,20 @@ object RotationManager : Loadable { @JvmStatic val movementYaw: Float? get() { - val config = currentContext?.config ?: return null - if (config.rotationMode == RotationMode.SILENT) return null + if (currentContext?.config?.rotationMode == RotationMode.SILENT) return null return currentRotation.yaw.toFloat() } @JvmStatic val movementPitch: Float? get() { - val config = currentContext?.config ?: return null - if (config.rotationMode == RotationMode.SILENT) return null + if (currentContext?.config?.rotationMode == RotationMode.SILENT) return null return currentRotation.pitch.toFloat() } @JvmStatic fun getRotationForVector(deltaTime: Double): Vec2d? { - val config = currentContext?.config ?: return null - if (config.rotationMode == RotationMode.SILENT) return null + if (currentContext?.config?.rotationMode == RotationMode.SILENT) return null val rot = lerp(deltaTime, prevRotation, currentRotation) return Vec2d(rot.yaw, rot.pitch) diff --git a/common/src/main/kotlin/com/lambda/interaction/blockplace/PlaceFinder.kt b/common/src/main/kotlin/com/lambda/interaction/blockplace/PlaceFinder.kt new file mode 100644 index 000000000..5db3da3e6 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/blockplace/PlaceFinder.kt @@ -0,0 +1,82 @@ +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.util.BlockUtils.blockState +import com.lambda.util.math.VecUtils.distSq +import com.lambda.util.math.VecUtils.getHitVec +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.util.* +import kotlin.collections.Collection + +class PlaceFinder( + private val basePos: BlockPos, + private val maxAttempts: Int, + range: Double, + private val eyes: Vec3d, + private val visibleCheck: Boolean, + private val sides: Set +) { + private val rangeSq = range * range + + private val Collection.selectClosest get() = minByOrNull { it.eyeDistanceSq } + private val Collection.selectBest get() = this.let { infos -> + if (infos.isEmpty()) return@let null + val lowestStepAmount = infos.minOf { it.placeSteps } + infos.filter { it.placeSteps == lowestStepAmount }.selectClosest + } + + companion object { + fun SafeContext.buildPlaceInfo( + basePos: BlockPos, + maxAttempts: Int = 4, + range: Double = 3.25, + eyes: Vec3d = player.eyePos, + visibleCheck: Boolean = true, + sides: Set = EnumSet.allOf(Direction::class.java) + ) = PlaceFinder(basePos, maxAttempts, range, eyes, visibleCheck, sides).build(this) + } + + private fun build( + ctx: SafeContext, + pos: BlockPos = basePos, + attempts: Int = 0 + ): PlaceInfo? = with(ctx) { + if (sides.isEmpty()) return null + if (!canPlaceAt(pos)) return null + + sides.mapNotNull { checkSide(pos, it, attempts) }.selectClosest?.let { + return it + } + + if (attempts > maxAttempts) return null + + return sides.mapNotNull { side -> + build(this, pos.offset(side), attempts + 1) + }.selectBest + } + + private fun SafeContext.checkSide(pos: BlockPos, side: Direction, attempts: Int): PlaceInfo? { + val clickPos = pos.offset(side) + val clickSide = side.opposite + + val hitVec = clickPos.getHitVec(clickSide) + val distSq = eyes distSq hitVec + + if (distSq > rangeSq) return null + if (clickPos.blockState(world).isClickable) return null + + if (visibleCheck) { + val box = Box(clickPos) + val visible = box.getVisibleSurfaces(eyes) + if (clickSide !in visible) return null + } + + return PlaceInfo(clickPos, clickSide, pos, hitVec, distSq, attempts) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/blockplace/PlaceInfo.kt b/common/src/main/kotlin/com/lambda/interaction/blockplace/PlaceInfo.kt new file mode 100644 index 000000000..e21d38428 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/blockplace/PlaceInfo.kt @@ -0,0 +1,15 @@ +package com.lambda.interaction.blockplace + +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction +import net.minecraft.util.math.Vec3d + +data class PlaceInfo( + val clickPos: BlockPos, + val clickSide: Direction, + val placedPos: BlockPos, + val hitVec: Vec3d, + + val eyeDistanceSq: Double, + val placeSteps: Int +) \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/blockplace/PlaceInteraction.kt b/common/src/main/kotlin/com/lambda/interaction/blockplace/PlaceInteraction.kt new file mode 100644 index 000000000..4a2302b9e --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/blockplace/PlaceInteraction.kt @@ -0,0 +1,51 @@ +package com.lambda.interaction.blockplace + +import com.lambda.context.SafeContext +import com.lambda.util.Communication.info +import net.minecraft.block.* +import net.minecraft.util.ActionResult +import net.minecraft.util.Hand +import net.minecraft.util.hit.BlockHitResult +import net.minecraft.util.math.BlockPos +import net.minecraft.world.World + +object PlaceInteraction { + fun SafeContext.placeBlock(result: BlockHitResult, hand: Hand, swing: Boolean) { + val actionResult = interaction.interactBlock(player, hand, result) + + when (actionResult) { + ActionResult.PASS -> info("Internal interaction skipped") + ActionResult.FAIL -> info("Internal interaction failed") + else -> {} + } + + if (!swing) return + + if (actionResult.shouldSwingHand()) { + player.swingHand(hand) + } + + if (!player.getStackInHand(hand).isEmpty && interaction.hasCreativeInventory()) { + mc.gameRenderer.firstPersonRenderer.resetEquipProgress(hand) + } + } + + fun SafeContext.canPlaceAt(blockPos: BlockPos, state: BlockState = Blocks.OBSIDIAN.defaultState): Boolean { + if (!World.isValid(blockPos)) return false + if (!world.getBlockState(blockPos).isReplaceable) return false + + return world.canPlace(state, blockPos, ShapeContext.absent()) + } + + val BlockState.isClickable get() = isReplaceable || + block is CraftingTableBlock || + block is AnvilBlock || + block is ButtonBlock || + block is AbstractPressurePlateBlock || + block is BlockWithEntity || + block is BedBlock || + block is FenceGateBlock || + block is DoorBlock || + block is NoteBlock || + block is TrapdoorBlock +} \ 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/rotation/Rotation.kt index 321dac45f..5d21f1adb 100644 --- a/common/src/main/kotlin/com/lambda/interaction/rotation/Rotation.kt +++ b/common/src/main/kotlin/com/lambda/interaction/rotation/Rotation.kt @@ -69,7 +69,7 @@ data class Rotation(val yaw: Double, val pitch: Double) { val DOWN = Rotation(0.0, 90.0) val Entity.rotation get() = Rotation(yaw, pitch) - private fun wrap(deg: Double) = MathHelper.wrapDegrees(deg) + fun wrap(deg: Double) = MathHelper.wrapDegrees(deg) fun Rotation.lerp(other: Rotation, delta: Double): Rotation { val yaw = this.yaw + delta * (other.yaw - this.yaw) diff --git a/common/src/main/kotlin/com/lambda/interaction/visibilty/EntityInteraction.kt b/common/src/main/kotlin/com/lambda/interaction/visibilty/EntityInteraction.kt deleted file mode 100644 index c9098e5bc..000000000 --- a/common/src/main/kotlin/com/lambda/interaction/visibilty/EntityInteraction.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.lambda.interaction.visibilty - -object EntityInteraction { -} \ 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/visibilty/VisibilityChecker.kt index e10472668..cbc5beacb 100644 --- a/common/src/main/kotlin/com/lambda/interaction/visibilty/VisibilityChecker.kt +++ b/common/src/main/kotlin/com/lambda/interaction/visibilty/VisibilityChecker.kt @@ -95,8 +95,8 @@ object VisibilityChecker { inline fun scanVisibleSurfaces( eyes: Vec3d, box: Box, - sides: Set, - resolution: Int, + sides: Set = emptySet(), + resolution: Int = 30, check: (Direction, Vec3d) -> Unit, ) { box.getVisibleSurfaces(eyes) 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 157977e01..315c186fa 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 @@ -22,10 +22,10 @@ import com.lambda.threading.runSafe import com.lambda.util.math.lerp import com.lambda.util.math.MathUtils.random import com.lambda.util.math.VecUtils.distSq -import com.lambda.util.math.VecUtils.minus import com.lambda.util.math.VecUtils.plus import com.lambda.util.math.VecUtils.times import com.lambda.util.player.MovementUtils.moveDiff +import com.lambda.util.player.prediction.buildPlayerPrediction import com.lambda.util.world.raycast.RayCastUtils.entityResult import kotlinx.coroutines.delay import net.minecraft.entity.EquipmentSlot @@ -64,6 +64,7 @@ object KillAura : Module( private val rotate by setting("Rotate", true) { page == Page.Aiming } private val rotation = RotationSettings(this) { page == Page.Aiming && rotate } private val stabilize by setting("Stabilize", true) { page == Page.Aiming && !rotation.instant && rotate } + private val stabilizationSpeed by setting("Stabilization Speed", 1.0, 0.1..3.0, 0.01) { page == Page.Aiming && !rotation.instant && rotate && stabilize } private val centerFactor by setting("Center Factor", 0.4, 0.0..1.0, 0.01) { page == Page.Aiming && rotate } private val shakeFactor by setting("Shake Factor", 0.4, 0.0..1.0, 0.01) { page == Page.Aiming && rotate } private val shakeChance by setting("Shake Chance", 0.2, 0.05..1.0, 0.01) { page == Page.Aiming && shakeFactor > 0.0 && rotate } @@ -102,11 +103,6 @@ object KillAura : Module( target?.let { target -> buildRotation(target) } - }, - onReceive = { - target?.let { entity -> - runAttack(entity) - } } ) @@ -120,10 +116,8 @@ object KillAura : Module( target = targeting.getTarget() if (!timerSync) attackTicks++ - if (!rotate) { - target?.let { entity -> - runAttack(entity) - } + target?.let { entity -> + runAttack(entity) } } @@ -153,7 +147,25 @@ object KillAura : Module( private fun SafeContext.buildRotation(target: LivingEntity): RotationContext? { val currentRotation = RotationManager.currentRotation - val eye = player.getCameraPosVec(1f) + val prediction = buildPlayerPrediction() + + val eye = when { + selfPredict < 1 -> { + lerp(selfPredict, player.eyePos, prediction.next().eyePos) + } + + selfPredict < 2 -> { + val pos1 = prediction.next().eyePos + val pos2 = prediction.next().eyePos + + lerp(selfPredict - 1, pos1, pos2) + } + + else -> { + prediction.next().next().eyePos + } + } + val box = target.boundingBox val reach = targeting.targetingRange + 2.0 @@ -166,11 +178,11 @@ object KillAura : Module( // Rotation stabilizer rotation.speedMultiplier = if (stabilize && !rotation.instant) { - val slowDown = currentRotation.castBox(box, reach) != null + val slowDown = currentRotation.castBox(box, reach, eye) != null with(rotation) { val targetSpeed = if (slowDown) 0.0 else 1.0 - val acceleration = if (slowDown) 0.2 else 0.1 + val acceleration = if (slowDown) 0.2 * stabilizationSpeed else 0.1 / stabilizationSpeed targetSpeed.coerceIn( speedMultiplier - acceleration, @@ -214,7 +226,7 @@ object KillAura : Module( // Get visible point set val validHits = mutableMapOf() - scanVisibleSurfaces(eye, box, emptySet(), interactionSettings.resolution) { _, vec -> + scanVisibleSurfaces(eye, box, resolution = interactionSettings.resolution) { _, vec -> if (eye distSq vec > reachSq) return@scanVisibleSurfaces val newRotation = eye.rotationTo(vec) @@ -229,7 +241,7 @@ object KillAura : Module( vec = validHits.minByOrNull { vecRotation dist it.value }?.key ?: return null } - val predictOffset = target.moveDiff * targetPredict - player.moveDiff * Vec3d(1.0, -0.5, 1.0) * selfPredict + val predictOffset = target.moveDiff * targetPredict return RotationContext(eye.rotationTo(vec + predictOffset), rotation) } diff --git a/common/src/main/kotlin/com/lambda/module/modules/movement/SafeWalk.kt b/common/src/main/kotlin/com/lambda/module/modules/movement/SafeWalk.kt index de109ee47..a9032a950 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/movement/SafeWalk.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/movement/SafeWalk.kt @@ -32,7 +32,7 @@ object SafeWalk : Module( var dx = deltaX var dz = deltaZ while (dx != 0.0 || dz != 0.0) { - if (world.isSpaceEmpty(this, boundingBox.offset(dx, -stepHeight, dz))) { + if (world.isBlockSpaceEmpty(this, boundingBox.offset(dx, -stepHeight, dz))) { return true } if (dx != 0.0) dx = adjustDelta(dx) 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 0ba412a2f..805d25cc7 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 @@ -4,14 +4,15 @@ import com.lambda.config.groups.IRotationConfig import com.lambda.context.SafeContext import com.lambda.event.events.ClientEvent import com.lambda.event.events.MovementEvent -import com.lambda.event.events.RotationEvent import com.lambda.event.listener.SafeListener.Companion.listener +import com.lambda.interaction.RotationManager.requestRotation import com.lambda.interaction.rotation.Rotation import com.lambda.interaction.rotation.RotationContext import com.lambda.interaction.rotation.RotationMode import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.Nameable +import com.lambda.util.extension.contains import com.lambda.util.player.MovementUtils.addSpeed import com.lambda.util.player.MovementUtils.calcMoveYaw import com.lambda.util.player.MovementUtils.handledByBaritone @@ -40,8 +41,9 @@ object Speed : Module( } // Grim + private val diagonal by setting("Diagonal", true) { mode == Mode.GRIM_STRAFE } private val grimEntityBoost by setting("Entity Boost", 1.0, 0.0..2.0, 0.01) { mode == Mode.GRIM_STRAFE } - private val grimCollideMultiplier by setting("Entity Collide Multiplier", 0.5, 0.0..1.0, 0.01) { mode == Mode.GRIM_STRAFE && grimEntityBoost > 0.0} + private val grimCollideMultiplier by setting("Entity Collide Multiplier", 0.5, 0.0..1.0, 0.01) { mode == Mode.GRIM_STRAFE && grimEntityBoost > 0.0 } private val grimBoatBoost by setting("Boat Boost", 0.4, 0.0..1.0, 0.01) { mode == Mode.GRIM_STRAFE } private val grimMaxSpeed by setting("Max Speed", 1.0, 0.2..1.0, 0.01) { mode == Mode.GRIM_STRAFE } @@ -56,6 +58,8 @@ object Speed : Module( override val rotationMode = RotationMode.SYNC } + private var prevTickJumping = false + // NCP const val NCP_BASE_SPEED = 0.2873 private const val NCP_AIR_DECAY = 0.9937 @@ -99,27 +103,49 @@ object Speed : Module( } listener { - if (!shouldWork()) return@listener + if (mode == Mode.NCP_STRAFE && shouldWork()) it.cancel() + } - when (mode) { - Mode.NCP_STRAFE -> it.cancel() - else -> {} + listener(Int.MIN_VALUE) { + if (mode != Mode.GRIM_STRAFE || !shouldWork()) return@listener + + // Delay jumping key state by 1 tick to let the rotation predict jump timing + it.input.apply { + val jump = jumping + jumping = prevTickJumping + prevTickJumping = jump } } - // TODO: Diagonal movement when not jumping - // needs movement prediction engine or a workaround to detect jumping 1 tick before - listener { event -> - if (mode != Mode.GRIM_STRAFE) return@listener - if (!shouldWork() || !isInputting) return@listener - if (player.input.handledByBaritone || TargetStrafe.isActive) return@listener + requestRotation(100, alwaysListen = false, + onUpdate = { lastContext -> + if (mode != Mode.GRIM_STRAFE) return@requestRotation null + if (!shouldWork()) return@requestRotation null - val input = newMovementInput() - val yaw = calcMoveYaw(player.yaw, input.roundedForward, input.roundedStrafing) - val rotation = Rotation(yaw, event.context?.rotation?.pitch ?: player.pitch.toDouble()) + var yaw = player.yaw + val input = newMovementInput() - event.context = RotationContext(rotation, rotationConfig) - } + if (!input.isInputting) return@requestRotation null + + run { + if (!diagonal) return@run + if (player.isOnGround && input.jumping) return@run + + val forward = input.roundedForward.toFloat() + var strafe = input.roundedStrafing.toFloat() + + if (strafe == 0f) strafe = -1f + if (forward == 0f) strafe *= -1 + + yaw -= 45 * strafe + } + + val moveYaw = calcMoveYaw(yaw, input.roundedForward, input.roundedStrafing) + val rotation = Rotation(moveYaw, lastContext?.rotation?.pitch ?: player.pitch.toDouble()) + + RotationContext(rotation, rotationConfig) + }, {} + ) onEnable { reset() @@ -188,12 +214,18 @@ object Speed : Module( setSpeed(moveSpeed) } - private fun SafeContext.shouldWork() = - !player.abilities.flying - && !player.isFallFlying - && !player.input.sneaking - && !player.isTouchingWater - && !player.isInLava + private fun SafeContext.shouldWork(): Boolean { + if (player.abilities.flying || player.isFallFlying || player.isTouchingWater || player.isInLava) return false + + return when (mode) { + Mode.GRIM_STRAFE -> { + !player.input.handledByBaritone && !TargetStrafe.isActive + } + Mode.NCP_STRAFE -> { + !player.isSneaking + } + } + } private fun reset() { ncpPhase = NCPPhase.SLOWDOWN 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 new file mode 100644 index 000000000..e6eb62fd3 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt @@ -0,0 +1,348 @@ +package com.lambda.module.modules.player + +import com.lambda.config.groups.InteractionSettings +import com.lambda.config.groups.RotationSettings +import com.lambda.context.SafeContext +import com.lambda.event.events.* +import com.lambda.event.listener.SafeListener.Companion.listener +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.requestRotation +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.RotationContext +import com.lambda.interaction.visibilty.VisibilityChecker.scanVisibleSurfaces +import com.lambda.module.Module +import com.lambda.module.modules.client.GuiSettings +import com.lambda.module.tag.ModuleTag +import com.lambda.util.math.MathUtils.floorToInt +import com.lambda.util.math.VecUtils.dist +import com.lambda.util.math.VecUtils.distSq +import com.lambda.util.math.multAlpha +import com.lambda.util.math.step +import com.lambda.util.math.transform +import com.lambda.util.player.MovementUtils.calcMoveYaw +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.RayCastUtils.blockResult +import net.minecraft.util.Hand +import net.minecraft.util.hit.BlockHitResult +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Box +import net.minecraft.util.math.Direction +import net.minecraft.util.math.Vec3d +import java.util.* +import kotlin.math.floor +import kotlin.math.pow + +object Scaffold : Module( + name = "Scaffold", + description = "Places blocks under the player", + defaultTags = setOf(ModuleTag.PLAYER) +) { + private val page by setting("Page", Page.GENERAL) + + private val keepY by setting("Keep Y", true) { page == Page.GENERAL } + private val minPlaceDist by setting("Min Place Dist", 0.0, 0.0..0.2, 0.01) { page == Page.GENERAL } + private val minRotateDist by setting("Min Rotate Dist", 0.10, 0.0..0.2, 0.01) { page == Page.GENERAL } + + private val rotationConfig = RotationSettings(this) { page == Page.ROTATION } + private val safeWalk by setting("Sneak Before Rotation", true) { page == Page.ROTATION } + 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 } + + // Placement + private var placeInfo: PlaceInfo? = null + private var keepLevel: Int? = null + private var lastRotation: Rotation? = null + private var edjeDistance = 0.0 + + // Sneaking + private var placeInfoAge = 0 + private var sneakTicks = 0 + + // Rendering + private val renderInfo = HashSet>() + private val currentTime get() = System.currentTimeMillis() + + // Other + private val yawList = listOf(0.0, 90.0, 180.0, 270.0) + private val diagonalYawList = yawList.map { it + 45 } + private val builderSideMask = EnumSet.allOf(Direction::class.java).apply { + remove(Direction.UP) + } + + // Yaw values within this range will not make your movement unstable + private const val YAW_THRESHOLD = 15.0 + + private enum class Page { + GENERAL, + ROTATION, + INTERACTION + } + + private enum class LookingDirection { + FREE, + ClAMPED, + STRAIGHT, + DIAGONAL + } + + init { + requestRotation( + onUpdate = { + lastRotation = null + val info = updatePlaceInfo() ?: return@requestRotation null + val rotation = rotate(info) ?: return@requestRotation null + + RotationContext(rotation, rotationConfig) + } + ) + + listener { + if (sneakTicks > 0) it.sneak = true + } + + listener { + placeInfo?.let { info -> + tickPlacement(info) + } + + updateSneaking() + } + + listener { event -> + buildRenderer(event) + } + + onEnable { + placeInfo = null + renderInfo.clear() + + keepLevel = null + sneakTicks = 0 + } + } + + private fun SafeContext.updatePlaceInfo(): PlaceInfo? { + // Feet placing blockpos + var y = (floor(player.pos.y) - 0.00001).floorToInt() + + // KeepY update + if (keepY && isInputting) { + keepLevel?.let { + y = it + } + } + + // 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, + sides = builderSideMask + ) + + placeInfo?.let { info -> + placeInfoAge = 0 + + // Ignore supporting blocks + if (info.placeSteps != 0) return@let + + // Updating keep level + keepLevel = info.placedPos.y + + edjeDistance = distanceToEdge(info.clickPos) + if (info.clickSide.axis == Direction.Axis.Y) edjeDistance = -1.0 + } + + return placeInfo + } + + private fun SafeContext.rotate(info: PlaceInfo): Rotation? { + val eye = player.eyePos + + val reach = interactionConfig.reach + val reachSq = reach.pow(2) + + val input = newMovementInput() + val moveYaw = calcMoveYaw(player.yaw, input.roundedForward, input.roundedStrafing) + + // Checking whether the player is moving diagonally + val isDiagonal = diagonalYawList.any { + angleDifference(moveYaw, it) < YAW_THRESHOLD + } + + // Assumed yaw values + val assumedYaw = assumeYawByDirection(moveYaw) + + // No need to rotate, already looking correctly + val lookingCorrectly = castRotation(currentRotation, info) != null + val isYawStable = angleDifference(currentRotation.yaw, assumedYaw) < YAW_THRESHOLD + if (lookingCorrectly && isYawStable) return currentRotation + + // Dividing the surface by segments and iterating through them + val pointScan = mutableSetOf().apply { + scanVisibleSurfaces( + eyes = eye, + box = Box(info.clickPos), + resolution = interactionConfig.resolution + ) { _, vec -> + if (eye distSq vec > reachSq) return@scanVisibleSurfaces + + val rotation = eye.rotationTo(vec) + castRotation(rotation, info) ?: return@scanVisibleSurfaces + + add(rotation) + } + } + + // Iterating through assumed angle ranges + val angleScan = mutableSetOf().apply { + val pitchRange = 55.0..85.0 + val pitchList = pitchRange.step(0.1) + + pitchList.forEach { pitch -> + val rotation = Rotation(assumedYaw, pitch) + castRotation(rotation, info) ?: return@forEach + + add(rotation) + } + } + + var optimalPitch = optimalPitch + if (isDiagonal) optimalPitch++ + + val assumedRotation = Rotation(assumedYaw, optimalPitch) + + // Check if the assumed rotation is ok + if (castRotation(assumedRotation, info) != null) { + return assumedRotation + } + + val optimalRotation = when { + // Placing supporting block + info.placeSteps > 0 && !isDiagonal -> currentRotation + + // Placing base block + else -> assumedRotation + } + + // Otherwise selecting the most similar rotation + val rotation = (angleScan + pointScan).minByOrNull { rotation -> + optimalRotation dist rotation + }.also { + lastRotation = it + } + + if (isDiagonal) { + edjeDistance = -1.0 + } + + // Check the distance to the edge (stabilizes rotation) + if (edjeDistance > 0 && edjeDistance < minRotateDist) return null + + return rotation + } + + private fun SafeContext.tickPlacement(info: PlaceInfo) { + // Check the distance to the edge + if (edjeDistance > 0 && edjeDistance < minPlaceDist) return + + // Raycast the rotation + var blockResult: BlockHitResult? = castRotation(currentRotation, info) + + // Use fallback hit vec for nonstrict ac's + if (!interactionConfig.useRayCast && blockResult == null) { + blockResult = BlockHitResult(info.hitVec, info.clickSide, info.clickPos, false) + } + + // Run placement + placeBlock(blockResult ?: return, Hand.MAIN_HAND, interactionConfig.swingHand) + renderInfo.add(info to currentTime) + } + + private fun SafeContext.updateSneaking() { + sneakTicks-- + placeInfoAge++ + + if (!safeWalk) return + + /*val vec = movementVector(y = -0.5) * player.moveDelta + val predictedBox = player.boundingBox.offset(vec) + val isNearLedge = world.isBlockSpaceEmpty(player, predictedBox)*/ + + val sneak = lastRotation?.let { + currentRotation dist it > YAW_THRESHOLD && player.isOnGround + } ?: (sneakTicks > 0 && placeInfoAge < 4) + + if (sneak) sneakTicks = 3 + } + + private fun buildRenderer(event: RenderEvent.StaticESP) { + val c = GuiSettings.primaryColor + + renderInfo.removeIf { + val (info, time) = it + + val pos = info.placedPos + val seconds = (currentTime - time) / 1000.0 + + val sides = buildSideMesh(pos) { meshPos -> + renderInfo.any { it.first.placedPos == meshPos } + } + + 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( + box, + c.multAlpha(0.3 * alpha), + c.multAlpha(alpha), + sides, + DirectionMask.OutlineMode.AND + ) + + seconds > 1 + } + } + + private fun assumeYawByDirection(moveYaw: Double): Double { + val moveYawReversed = wrap(moveYaw + 180) + + return when (direction) { + LookingDirection.FREE -> listOf(moveYawReversed) + LookingDirection.ClAMPED -> yawList + diagonalYawList + LookingDirection.STRAIGHT -> yawList + LookingDirection.DIAGONAL -> diagonalYawList + }.minBy { angleDifference(moveYawReversed, it) } + } + + // Calculates the distance from the player to the edge of the block + private fun SafeContext.distanceToEdge(pos: BlockPos, from: Vec3d = player.pos) = + edgeOf(pos) dist Vec3d(from.x, pos.y.toDouble(), from.z) + + private fun SafeContext.edgeOf(pos: BlockPos): Vec3d { + val x = player.pos.x.coerceIn(pos.x.toDouble(), pos.x.toDouble() + 1) + val z = player.pos.z.coerceIn(pos.z.toDouble(), pos.z.toDouble() + 1) + return Vec3d(x, pos.y.toDouble(), z) + } + + // 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 + if (blockResult.blockPos != info.clickPos || blockResult.side != info.clickSide) return null + return blockResult + } +} 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 5359feaf8..65a5c550d 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/PlaceBlock.kt @@ -6,11 +6,11 @@ import com.lambda.core.PingManager import com.lambda.event.events.RotationEvent import com.lambda.event.events.WorldEvent import com.lambda.event.listener.SafeListener.Companion.listener +import com.lambda.interaction.blockplace.PlaceInteraction.placeBlock import com.lambda.interaction.construction.context.PlaceContext import com.lambda.module.modules.client.TaskFlow import com.lambda.task.Task import com.lambda.util.BlockUtils.blockState -import com.lambda.util.Communication.info import net.minecraft.block.BlockState class PlaceBlock @Ta5kBuilder constructor( @@ -72,27 +72,7 @@ class PlaceBlock @Ta5kBuilder constructor( } private fun SafeContext.placeBlock() { - val actionResult = interaction.interactBlock( - player, - ctx.hand, - ctx.result - ) - - if (actionResult.isAccepted) { - if (actionResult.shouldSwingHand() && swingHand) { - player.swingHand(ctx.hand) - } - - if (!player.getStackInHand(ctx.hand).isEmpty && interaction.hasCreativeInventory()) { - mc.gameRenderer.firstPersonRenderer.resetEquipProgress(ctx.hand) - } - - if (matches) { - if (!waitForConfirmation) finish() - } - } else { - info("Internal interaction failed with $actionResult") - } + placeBlock(ctx.result, ctx.hand, swingHand) } private fun SafeContext.finish() { diff --git a/common/src/main/kotlin/com/lambda/util/BlockUtils.kt b/common/src/main/kotlin/com/lambda/util/BlockUtils.kt index b60bbd435..6741d5cc5 100644 --- a/common/src/main/kotlin/com/lambda/util/BlockUtils.kt +++ b/common/src/main/kotlin/com/lambda/util/BlockUtils.kt @@ -3,6 +3,7 @@ package com.lambda.util import com.lambda.context.SafeContext import com.lambda.util.item.ItemUtils.block import com.lambda.util.item.ItemUtils.shulkerBoxes +import com.lambda.util.math.MathUtils.floorToInt import net.minecraft.block.Block import net.minecraft.block.BlockState import net.minecraft.block.Blocks @@ -11,7 +12,6 @@ import net.minecraft.fluid.Fluids import net.minecraft.item.Item import net.minecraft.util.math.* import net.minecraft.world.BlockView -import kotlin.math.floor object BlockUtils { private val shulkerBlocks = shulkerBoxes.map { it.block } @@ -106,7 +106,7 @@ object BlockUtils { } val Vec3i.blockPos: BlockPos get() = BlockPos(this) val Block.item: Item get() = asItem() - val Vec3d.flooredPos: BlockPos get() = BlockPos(floor(x).toInt(), floor(y).toInt(), floor(z).toInt()) + val Vec3d.flooredPos: BlockPos get() = BlockPos(x.floorToInt(), y.floorToInt(), z.floorToInt()) fun BlockPos.vecOf(direction: Direction): Vec3d = toCenterPos().add(Vec3d.of(direction.vector).multiply(0.5)) fun BlockPos.offset(eightWayDirection: EightWayDirection, amount: Int): BlockPos = add(eightWayDirection.offsetX * amount, 0, eightWayDirection.offsetZ * amount) diff --git a/common/src/main/kotlin/com/lambda/util/math/VecUtils.kt b/common/src/main/kotlin/com/lambda/util/math/VecUtils.kt index bc22e2911..e77da651a 100644 --- a/common/src/main/kotlin/com/lambda/util/math/VecUtils.kt +++ b/common/src/main/kotlin/com/lambda/util/math/VecUtils.kt @@ -7,6 +7,7 @@ import com.lambda.util.world.y import com.lambda.util.world.z import net.minecraft.entity.Entity import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction import net.minecraft.util.math.Vec3d import net.minecraft.util.math.Vec3i import kotlin.math.pow @@ -16,8 +17,13 @@ object VecUtils { val Vec3d.blockPos: BlockPos get() = BlockPos(x.roundToInt(), y.roundToInt(), z.roundToInt()) - val Vec3i.asVec3d: Vec3d - get() = Vec3d(x.toDouble(), y.toDouble(), z.toDouble()) + val Vec3i.vec3d get() = Vec3d.of(this) + + fun BlockPos.getHitVec(side: Direction) = + vec3d + side.hitVecOffset + + val Direction.hitVecOffset get() = + CENTER + vector.vec3d * 0.5 infix fun Vec3d.dist(other: Vec3d): Double = this.distanceTo(other) @@ -47,4 +53,5 @@ object VecUtils { val UP = Vec3d(0.0, 1.0, 0.0) val DOWN = Vec3d(0.0, -1.0, 0.0) + val CENTER = Vec3d(0.5, 0.5, 0.5) } 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 7186654bc..c32d88061 100644 --- a/common/src/main/kotlin/com/lambda/util/player/MovementUtils.kt +++ b/common/src/main/kotlin/com/lambda/util/player/MovementUtils.kt @@ -108,8 +108,6 @@ object MovementUtils { moveStrafe: Double = player.input.roundedStrafing ) = yawIn.toRadian() + inputMoveOffset(moveForward, moveStrafe) - fun randomDirection() = random(-180.0, 180.0).toRadian() - fun SafeContext.movementVector(radDir: Double = calcMoveRad(), y: Double = 0.0) = Vec3d(-sin(radDir), y, cos(radDir)) @@ -135,12 +133,14 @@ object MovementUtils { val Entity.moveDiff get() = Vec3d(this.pos.x - this.prevX, this.pos.y - this.prevY, this.pos.z - this.prevZ) val Entity.moveDelta get() = moveDiff.let { hypot(it.x, it.z) } - val Entity.velocityDelta get() = hypot(this.velocity.x, this.velocity.z) val Entity.octant: EightWayDirection + get() = yaw.octant + + val Float.octant: EightWayDirection get() { // Normalize the yaw to be within the range of -180 to 179 degrees - var normalizedYaw = (yaw + 180.0) % 360.0 + var normalizedYaw = (this + 180.0) % 360.0 if (normalizedYaw < 0) { normalizedYaw += 360.0 } diff --git a/common/src/main/kotlin/com/lambda/util/player/prediction/MovementPrediction.kt b/common/src/main/kotlin/com/lambda/util/player/prediction/MovementPrediction.kt new file mode 100644 index 000000000..4a8e1d9c4 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/player/prediction/MovementPrediction.kt @@ -0,0 +1,18 @@ +package com.lambda.util.player.prediction + +import com.lambda.context.SafeContext + +/** + * Builds the player movement prediction engine based on minecraft physics logic + * + * Currently not implemented: + * - Elytra movement + * - Movement in liquids + * - Ladder climbing + * - Movement in webs + * - Sneaking safewalk + * + * And im fucking tired of merging all shit from minecraft + */ +fun SafeContext.buildPlayerPrediction(): PredictionTick = + PredictionEntity(player).lastTick \ No newline at end of file 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 new file mode 100644 index 000000000..ff43930b8 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/player/prediction/PredictionEntity.kt @@ -0,0 +1,239 @@ +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.threading.runSafe +import com.lambda.util.BlockUtils.blockState +import com.lambda.util.BlockUtils.flooredPos +import com.lambda.util.math.MathUtils.toIntSign +import com.lambda.util.math.MathUtils.toRadian +import com.lambda.util.math.VecUtils +import com.lambda.util.math.VecUtils.plus +import com.lambda.util.math.VecUtils.times +import com.lambda.util.player.MovementUtils.motion +import com.lambda.util.player.MovementUtils.moveYaw +import com.lambda.util.player.MovementUtils.movementVector +import net.minecraft.client.input.KeyboardInput +import net.minecraft.client.network.ClientPlayerEntity +import net.minecraft.enchantment.EnchantmentHelper.getSwiftSneakSpeedBoost +import net.minecraft.entity.Entity +import net.minecraft.entity.effect.StatusEffects +import net.minecraft.util.math.MathHelper +import net.minecraft.util.math.Vec3d +import kotlin.math.abs + +// Todo: any player entity support +class PredictionEntity(val player: ClientPlayerEntity) { + val lastTick get() = PredictionTick( + position, + rotation, + motion, + boundingBox, + eyePos, + isOnGround, + isJumping, + this + ) + + // Basics + private var position = player.pos + private var motion = player.motion + private var boundingBox = player.boundingBox + + val eyePos get() = position + Vec3d(0.0, player.standingEyeHeight.toDouble(), 0.0) + private var rotation = Rotation(player.moveYaw, player.pitch) + + // Secondary movement-related fields + private var isOnGround = player.isOnGround + private val isSprinting = player.isSprinting + private val isSneaking = player.isSneaking + private var isJumping = false + + // Movement input + private val input = KeyboardInput(mc.options).apply { + tick(true, 1f) + } + + private val pressingJump = input.jumping + private val forwardMovement = input.movementForward.toDouble() + private val strafeMovement = input.movementSideways.toDouble() + private val verticalMovement = pressingJump.toIntSign().toDouble() + + private var forwardSpeed = forwardMovement + private var strafeSpeed = strafeMovement + + // Other shit + private var jumpingCooldown = player.jumpingCooldown + private var velocityAffectingPos = player.supportingBlockPos.orElse((position + VecUtils.DOWN * 0.001).flooredPos) + + private var horizontalCollision = player.horizontalCollision + private var verticalCollision = player.verticalCollision + + /** @see net.minecraft.client.network.ClientPlayerEntity.tickMovement */ + fun tickMovement() = runSafe { + forwardSpeed = forwardMovement + strafeSpeed = strafeMovement + + if (player.isUsingItem) { + forwardSpeed *= 0.2 + strafeSpeed *= 0.2 + } + + if (isSneaking) { + val mod = 0.3f + getSwiftSneakSpeedBoost(player) + forwardSpeed *= mod + strafeSpeed *= mod + } + + if (jumpingCooldown > 0) { + --jumpingCooldown + } + + val reduceX = abs(motion.x) < 0.03 + val reduceY = abs(motion.y) < 0.03 + val reduceZ = abs(motion.z) < 0.03 + + if (reduceX || reduceY || reduceZ) { + motion = Vec3d( + if (reduceX) 0.0 else motion.x, + if (reduceY) 0.0 else motion.y, + if (reduceZ) 0.0 else motion.z + ) + } + + isJumping = false + + if (pressingJump && isOnGround && jumpingCooldown == 0) { + jumpingCooldown = 10 + isJumping = true + jump() + } + + forwardSpeed *= 0.98 + strafeSpeed *= 0.98 + + travel() + } + + /** @see net.minecraft.entity.LivingEntity.travel */ + private fun SafeContext.travel() { + val travelVec = Vec3d(strafeSpeed, verticalMovement, forwardSpeed) + + val gravity = when { + motion.y < 0.0 && player.hasStatusEffect(StatusEffects.SLOW_FALLING) -> 0.01 + else -> 0.08 + } + + val slipperiness = velocityAffectingPos.blockState(world).block.slipperiness.toDouble() + var friction = 0.91 + + if (isOnGround) { + friction *= slipperiness + } + + applyMovementInput(travelVec, slipperiness) + move() + + motion += VecUtils.DOWN * gravity + motion *= Vec3d(friction, 0.98, friction) + } + + /** @see net.minecraft.entity.LivingEntity.applyMovementInput */ + private fun SafeContext.applyMovementInput(travelVec: Vec3d, slipperiness: Double) { + val movementSpeed = run { + /** @see net.minecraft.entity.LivingEntity.getMovementSpeed */ + + val slipperinessCubed = slipperiness * slipperiness * slipperiness + val movementSpeed = player.movementSpeed.toDouble() + + val groundSpeed = movementSpeed * (0.216 / slipperinessCubed) + val airSpeed = if (isSprinting) 0.026 else 0.02 + + if (isOnGround) groundSpeed else airSpeed + }.toFloat() + + /** @see net.minecraft.entity.Entity.updateVelocity */ + motion += Entity.movementInputToVelocity(travelVec, movementSpeed, rotation.yawF) + } + + /** @see net.minecraft.entity.Entity.move */ + private fun SafeContext.move() { + var movement = motion + movement = adjustMovementForCollisions(movement) + + if (movement.lengthSquared() > 1.0E-7) { + position += movement + } + + val xCollide = !MathHelper.approximatelyEquals(movement.x, motion.x) + val yCollide = !MathHelper.approximatelyEquals(movement.y, motion.y) + val zCollide = !MathHelper.approximatelyEquals(movement.z, motion.z) + + horizontalCollision = xCollide || zCollide + verticalCollision = yCollide + + isOnGround = yCollide && movement.y < 0.0 + + if (horizontalCollision) { + motion = Vec3d( + if (xCollide) 0.0 else motion.x, + motion.y, + if (zCollide) 0.0 else motion.z + ) + } + + val velocityMultiplier = run { + val f = position.flooredPos.blockState(world).block.velocityMultiplier.toDouble() + val g = velocityAffectingPos.blockState(world).block.velocityMultiplier.toDouble() + if (f == 1.0) g else f + } + + motion *= Vec3d(velocityMultiplier, 1.0, velocityMultiplier) + + boundingBox.apply { + val normalized = offset(-minX, -minY, -minZ).offset(-lengthX * 0.5, 0.0, -lengthZ * 0.5) + boundingBox = normalized.offset(position) + } + + velocityAffectingPos = (position + VecUtils.DOWN * 0.001).flooredPos + } + + /** @see net.minecraft.entity.LivingEntity.jump */ + private fun SafeContext.jump() { + if (isSprinting) { + val yawRad = rotation.yaw.toRadian() + motion += movementVector(yawRad, 0.0) * 0.2 + } + + /** @see net.minecraft.entity.Entity.getJumpVelocityMultiplier */ + val jumpHeight = run { + val f = position.flooredPos.blockState(world).block.jumpVelocityMultiplier.toDouble() + val g = velocityAffectingPos.blockState(world).block.jumpVelocityMultiplier.toDouble() + if (f == 1.0) g else f + } * 0.42 + player.jumpBoostVelocityModifier + + motion += Vec3d(0.0, jumpHeight, 0.0) + } + + /** @see net.minecraft.entity.Entity.adjustMovementForCollisions */ + private fun SafeContext.adjustMovementForCollisions(movement: Vec3d): Vec3d { + if (movement.lengthSquared() == 0.0) { + return movement + } + + val prevPos = player.pos + val prevBox = player.boundingBox + + player.pos = position + player.boundingBox = boundingBox + + val list = world.getEntityCollisions(player, boundingBox.stretch(movement)) + val processed = Entity.adjustMovementForCollisions(player, movement, boundingBox, world, list) + + player.pos = prevPos + player.boundingBox = prevBox + + return processed + } +} \ No newline at end of file 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 new file mode 100644 index 000000000..eeb85e9c7 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/player/prediction/PredictionTick.kt @@ -0,0 +1,37 @@ +package com.lambda.util.player.prediction + +import com.lambda.interaction.rotation.Rotation +import net.minecraft.util.math.Box +import net.minecraft.util.math.Vec3d + +/** + * Data class representing a movement prediction tick for an entity. + * + * @property position The current position of the entity. + * @property rotation The current rotation (yaw, pitch) of the entity. + * @property velocity The current velocity vector of the entity. + * @property boundingBox The bounding box that defines the entity's space in the world. + * @property eyePos The position of the entity's eyes, typically used for raycasting or viewing purposes. + * @property onGround Indicates whether the entity is currently touching the ground. + * @property isJumping Indicates whether the entity is currently jumping. + */ +data class PredictionTick( + val position: Vec3d, + val rotation: Rotation, + val velocity: Vec3d, + val boundingBox: Box, + val eyePos: Vec3d, + val onGround: Boolean, + val isJumping: Boolean, + val predictionEntity: PredictionEntity +) { + fun next() = skipTicks(1) + + fun skipTicks(amount: Int) = with(predictionEntity) { + repeat(amount) { + tickMovement() + } + + lastTick + } +} \ No newline at end of file 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 54c8746e1..0dce21481 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 @@ -5,6 +5,7 @@ import com.lambda.context.SafeContext import com.lambda.interaction.rotation.Rotation import com.lambda.threading.runSafe import com.lambda.util.math.VecUtils.distSq +import net.minecraft.client.network.ClientPlayerEntity import net.minecraft.entity.Entity import net.minecraft.entity.projectile.ProjectileUtil import net.minecraft.util.hit.BlockHitResult @@ -16,7 +17,9 @@ import kotlin.math.max import kotlin.math.pow object RayCastUtils { - private val entityPredicate = { entity: Entity -> !entity.isSpectator && entity.canHit() } + private val entityPredicate = { entity: Entity -> + !entity.isSpectator && entity.canHit() && entity !is ClientPlayerEntity + } fun SafeContext.rayCast( start: Vec3d, diff --git a/common/src/main/resources/lambda.accesswidener b/common/src/main/resources/lambda.accesswidener index 0e9417f35..ec7219d67 100644 --- a/common/src/main/resources/lambda.accesswidener +++ b/common/src/main/resources/lambda.accesswidener @@ -24,6 +24,8 @@ accessible method net/minecraft/entity/passive/AbstractHorseEntity updateSaddle accessible field net/minecraft/entity/LivingEntity lastAttackedTicks I 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; # Camera accessible method net/minecraft/client/render/Camera setPos (DDD)V