diff --git a/common/src/main/java/com/lambda/mixin/ClientConnectionMixin.java b/common/src/main/java/com/lambda/mixin/ClientConnectionMixin.java index 8552a272d..1c8daf224 100644 --- a/common/src/main/java/com/lambda/mixin/ClientConnectionMixin.java +++ b/common/src/main/java/com/lambda/mixin/ClientConnectionMixin.java @@ -1,11 +1,15 @@ package com.lambda.mixin; import com.lambda.event.EventFlow; +import com.lambda.event.events.ConnectionEvent; import com.lambda.event.events.PacketEvent; import io.netty.channel.ChannelHandlerContext; import net.minecraft.network.ClientConnection; import net.minecraft.network.NetworkSide; +import net.minecraft.network.listener.PacketListener; import net.minecraft.network.packet.Packet; +import net.minecraft.network.packet.c2s.handshake.ConnectionIntent; +import net.minecraft.text.Text; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -53,4 +57,20 @@ private void receivingPacketPost( EventFlow.post(new PacketEvent.Receive.Post(packet)); } + + @Inject(method = "connect(Ljava/lang/String;ILnet/minecraft/network/listener/PacketListener;Lnet/minecraft/network/packet/c2s/handshake/ConnectionIntent;)V", at = @At("HEAD")) + private void onConnect( + String address, + int port, + PacketListener listener, + ConnectionIntent intent, + CallbackInfo ci + ) { + EventFlow.post(new ConnectionEvent.Connect(address, port, listener, intent)); + } + + @Inject(method = "disconnect(Lnet/minecraft/text/Text;)V", at = @At("HEAD")) + private void onDisconnect(Text reason, CallbackInfo ci) { + EventFlow.post(new ConnectionEvent.Disconnect(reason)); + } } diff --git a/common/src/main/java/com/lambda/mixin/DebugHudMixin.java b/common/src/main/java/com/lambda/mixin/DebugHudMixin.java new file mode 100644 index 000000000..c41410e6f --- /dev/null +++ b/common/src/main/java/com/lambda/mixin/DebugHudMixin.java @@ -0,0 +1,18 @@ +package com.lambda.mixin; + +import com.lambda.util.DebugInfoHud; +import net.minecraft.client.gui.hud.DebugHud; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.List; + +@Mixin(DebugHud.class) +public class DebugHudMixin { + @Inject(method = "getRightText", at = @At(value = "TAIL")) + private void onGetRightText(CallbackInfoReturnable> cir) { + DebugInfoHud.addDebugInfo(cir.getReturnValue()); + } +} diff --git a/common/src/main/java/com/lambda/mixin/GameRendererMixin.java b/common/src/main/java/com/lambda/mixin/GameRendererMixin.java new file mode 100644 index 000000000..6b54c3945 --- /dev/null +++ b/common/src/main/java/com/lambda/mixin/GameRendererMixin.java @@ -0,0 +1,16 @@ +package com.lambda.mixin; + +import com.lambda.interaction.RotationManager; +import net.minecraft.client.render.GameRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(GameRenderer.class) +public class GameRendererMixin { + @Inject(method = "updateTargetedEntity", at = @At("HEAD")) + private void onUpdateTargetedEntity(CallbackInfo ci) { + RotationManager.updateInterpolated(); + } +} diff --git a/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java b/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java index 37300aff9..464834014 100644 --- a/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java +++ b/common/src/main/java/com/lambda/mixin/MinecraftClientMixin.java @@ -51,6 +51,6 @@ boolean injectMultiActon(ClientPlayerInteractionManager instance) { void injectFastPlace(CallbackInfo ci) { if (!Interact.INSTANCE.isEnabled()) return; - Lambda.INSTANCE.getMc().itemUseCooldown = Interact.getPlaceDelay(); + Lambda.getMc().itemUseCooldown = Interact.getPlaceDelay(); } } diff --git a/common/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java b/common/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java new file mode 100644 index 000000000..e4883ad8e --- /dev/null +++ b/common/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java @@ -0,0 +1,76 @@ +package com.lambda.mixin.entity; + +import com.lambda.Lambda; +import com.lambda.event.EventFlow; +import com.lambda.event.events.MovementEvent; +import com.lambda.interaction.PlayerPacketManager; +import com.lambda.interaction.RotationManager; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.entity.MovementType; +import net.minecraft.util.math.Vec3d; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.Objects; + +@Mixin(value = ClientPlayerEntity.class, priority = Integer.MAX_VALUE) +public abstract class ClientPlayerEntityMixin extends EntityMixin { + + @Shadow protected abstract void autoJump(float dx, float dz); + + @Shadow public abstract boolean isUsingItem(); + + @Shadow private boolean autoJumpEnabled; + + @Inject(method = "move", at = @At("HEAD"), cancellable = true) + void onMove(MovementType movementType, Vec3d movement, CallbackInfo ci) { + ClientPlayerEntity self = (ClientPlayerEntity) (Object) this; + if (self != Lambda.getMc().player) return; + + ci.cancel(); + + float prevX = (float) self.getX(); + float prevZ = (float) self.getZ(); + + EventFlow.post(new MovementEvent.Pre()); + super.move(movementType, self.getVelocity()); + EventFlow.post(new MovementEvent.Post()); + + float currX = (float) self.getX(); + float currZ = (float) self.getZ(); + + this.autoJump(currX - prevX, currZ - prevZ); + } + + @Redirect(method = "tickMovement", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;isUsingItem()Z")) + boolean onSlowDown(ClientPlayerEntity entity) { + if (EventFlow.post(new MovementEvent.SlowDown()).isCanceled()) return false; + return this.isUsingItem(); + } + + @Inject(method = "tickMovement", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/input/Input;tick(ZF)V", shift = At.Shift.AFTER)) + void processMovement(CallbackInfo ci) { + RotationManager.BaritoneProcessor.processPlayerMovement(); + } + + @Inject(method = "sendMovementPackets", at = @At(value = "HEAD"), cancellable = true) + void sendBegin(CallbackInfo ci) { + ci.cancel(); + PlayerPacketManager.sendPlayerPackets(); + autoJumpEnabled = Lambda.getMc().options.getAutoJump().getValue(); + } + + @Redirect(method = "tickNewAi", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;getYaw()F")) + float fixHeldItemYaw(ClientPlayerEntity instance) { + return Objects.requireNonNullElse(RotationManager.getHandYaw(), instance.getYaw()); + } + + @Redirect(method = "tickNewAi", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;getPitch()F")) + float fixHeldItemPitch(ClientPlayerEntity instance) { + return Objects.requireNonNullElse(RotationManager.getHandPitch(), instance.getPitch()); + } +} diff --git a/common/src/main/java/com/lambda/mixin/entity/EntityMixin.java b/common/src/main/java/com/lambda/mixin/entity/EntityMixin.java new file mode 100644 index 000000000..d81219cf9 --- /dev/null +++ b/common/src/main/java/com/lambda/mixin/entity/EntityMixin.java @@ -0,0 +1,61 @@ +package com.lambda.mixin.entity; + +import com.lambda.Lambda; +import com.lambda.interaction.RotationManager; +import com.lambda.util.math.Vec2d; +import net.minecraft.entity.Entity; +import net.minecraft.entity.MovementType; +import net.minecraft.util.math.Vec3d; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(Entity.class) +public abstract class EntityMixin { + @Shadow public void move(MovementType movementType, Vec3d movement) {} + + @Shadow public abstract float getYaw(); + + @Redirect(method = "updateVelocity", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;getYaw()F")) + public float velocityYaw(Entity entity) { + if ((Object) this != Lambda.getMc().player) return getYaw(); + + Float y = RotationManager.getMovementYaw(); + if (y == null) return getYaw(); + + return y; + } + + @Redirect(method = "getRotationVec", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;getYaw(F)F")) + float fixDirectionYaw(Entity entity, float tickDelta) { + Vec2d rot = RotationManager.getRotationForVector(tickDelta); + if (entity != Lambda.getMc().player || rot == null) return entity.getYaw(tickDelta); + + return (float) rot.getX(); + } + + @Redirect(method = "getRotationVec", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;getPitch(F)F")) + float fixDirectionPitch(Entity entity, float tickDelta) { + Vec2d rot = RotationManager.getRotationForVector(tickDelta); + if (entity != Lambda.getMc().player || rot == null) return entity.getPitch(tickDelta); + + return (float) rot.getY(); + } + + @Redirect(method = "getRotationVector()Lnet/minecraft/util/math/Vec3d;", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;getYaw()F")) + float fixDirectionYaw2(Entity entity) { + Vec2d rot = RotationManager.getRotationForVector(1.0); + if (entity != Lambda.getMc().player || rot == null) return entity.getYaw(); + + return (float) rot.getX(); + } + + @Redirect(method = "getRotationVector()Lnet/minecraft/util/math/Vec3d;", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;getPitch()F")) + float fixDirectionPitch2(Entity entity) { + Vec2d rot = RotationManager.getRotationForVector(1.0); + if (entity != Lambda.getMc().player || rot == null) return entity.getPitch(); + + return (float) rot.getY(); + } +} \ No newline at end of file diff --git a/common/src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java b/common/src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java new file mode 100644 index 000000000..b84c9e8d2 --- /dev/null +++ b/common/src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java @@ -0,0 +1,83 @@ +package com.lambda.mixin.entity; + +import com.lambda.Lambda; +import com.lambda.event.EventFlow; +import com.lambda.event.events.MovementEvent; +import com.lambda.interaction.RotationManager; +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.Slice; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(LivingEntity.class) +public abstract class LivingEntityMixin extends EntityMixin { + + @Shadow + protected abstract float getJumpVelocity(); + + @Inject(method = "jump", at = @At("HEAD"), cancellable = true) + void onJump(CallbackInfo ci) { + LivingEntity self = (LivingEntity) (Object) this; + if (self != Lambda.getMc().player) return; + ci.cancel(); + + float height = this.getJumpVelocity(); + MovementEvent.Jump event = EventFlow.post(new MovementEvent.Jump(height)); + + if (event.isCanceled()) return; + + Vec3d vec3d = self.getVelocity(); + self.setVelocity(vec3d.x, event.getHeight(), vec3d.z); + + if (self.isSprinting()) { + Float yaw = RotationManager.getMovementYaw(); + float f = ((yaw != null) ? yaw : self.getYaw()) * ((float)Math.PI / 180); + self.setVelocity(self.getVelocity().add(-MathHelper.sin(f) * 0.2f, 0.0, MathHelper.cos(f) * 0.2f)); + } + + self.velocityDirty = true; + } + + @Inject(method = "tickMovement", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/LivingEntity;isImmobile()Z")) + void onTravelH(CallbackInfo ci) { + Entity self = (Entity) (Object) this; + if (self != Lambda.getMc().player) return; + + RotationManager.update(); + } + + @Redirect(method = "travel", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/LivingEntity;getPitch()F")) + private float hookModifyFallFlyingPitch(LivingEntity entity) { + Float pitch = RotationManager.getMovementPitch(); + if (entity != Lambda.getMc().player || pitch == null) return entity.getPitch(); + + return pitch; + } + + @Redirect(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/LivingEntity;getYaw()F"), slice = @Slice(to = @At(value = "INVOKE", target = "Lnet/minecraft/entity/LivingEntity;getYaw()F", ordinal = 1))) + private float rotBody(LivingEntity entity) { + if ((Object) this != Lambda.getMc().player) { + return entity.getYaw(); + } + + Float yaw = RotationManager.getRenderYaw(); + return (yaw == null) ? entity.getYaw() : yaw; + } + + @Redirect(method = "turnHead", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/LivingEntity;getYaw()F")) + private float rotHead(LivingEntity entity) { + if ((Object) this != Lambda.getMc().player) { + return entity.getYaw(); + } + + Float yaw = RotationManager.getRenderYaw(); + return (yaw == null) ? entity.getYaw() : yaw; + } +} diff --git a/common/src/main/kotlin/com/lambda/Loadable.kt b/common/src/main/kotlin/com/lambda/Loadable.kt index f18bd8bba..23218fd0c 100644 --- a/common/src/main/kotlin/com/lambda/Loadable.kt +++ b/common/src/main/kotlin/com/lambda/Loadable.kt @@ -1,5 +1,5 @@ package com.lambda interface Loadable { - fun load(): String + fun load() = this::class.simpleName?.let { "Loaded $it" } ?: "Loaded" } \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/Loader.kt b/common/src/main/kotlin/com/lambda/Loader.kt index fb804ca99..4784f7428 100644 --- a/common/src/main/kotlin/com/lambda/Loader.kt +++ b/common/src/main/kotlin/com/lambda/Loader.kt @@ -2,13 +2,15 @@ package com.lambda import com.lambda.Lambda.LOG import com.lambda.command.CommandManager +import com.lambda.interaction.RotationManager import com.lambda.module.ModuleRegistry import kotlin.system.measureTimeMillis object Loader { private val loadables = listOf( ModuleRegistry, - CommandManager + CommandManager, + RotationManager ) fun initialize() { diff --git a/common/src/main/kotlin/com/lambda/command/CommandManager.kt b/common/src/main/kotlin/com/lambda/command/CommandManager.kt index 6c8fd8233..75730d610 100644 --- a/common/src/main/kotlin/com/lambda/command/CommandManager.kt +++ b/common/src/main/kotlin/com/lambda/command/CommandManager.kt @@ -29,7 +29,7 @@ object CommandManager : Configurable(LambdaConfig), Loadable { val prefix by setting("prefix", ';') - private val commands = mutableSetOf() + val commands = mutableSetOf() private val dispatcher by lazy { CommandDispatcher() } private const val ERROR_PADDING = 10 diff --git a/common/src/main/kotlin/com/lambda/config/InteractionSettings.kt b/common/src/main/kotlin/com/lambda/config/InteractionSettings.kt new file mode 100644 index 000000000..0c7aa5e71 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/config/InteractionSettings.kt @@ -0,0 +1,13 @@ +package com.lambda.config + +import com.lambda.interaction.InteractionConfig +import com.lambda.util.world.raycast.RayCastMask + +class InteractionSettings( + c: Configurable, + vis: () -> Boolean = { true } +) : InteractionConfig { + override val reach by c.setting("Reach", 5.0, 0.1..10.0, 0.1, "Players reach / range", vis) + override val resolution by c.setting("Resolution", 10, 1..100, 1, "Raycast resolution", vis) + override val rayCastMask by c.setting("Raycast Mask", RayCastMask.BOTH, "What to raycast against", vis) +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/config/RotationSettings.kt b/common/src/main/kotlin/com/lambda/config/RotationSettings.kt new file mode 100644 index 000000000..b76deb647 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/config/RotationSettings.kt @@ -0,0 +1,29 @@ +package com.lambda.config + +import com.lambda.interaction.rotation.IRotationConfig +import com.lambda.interaction.rotation.RotationMode +import kotlin.random.Random + +class RotationSettings( + c: Configurable, + vis: () -> Boolean = { true } +) : IRotationConfig { + override val rotationMode by c.setting("Mode", RotationMode.LOCK, "SILENT - server-side rotation, SYNC - server-side rotation; client-side movement, LOCK - Lock camera", vis) + override val keepTicks by c.setting("Keep Rotation", 3, 1..10, 1, "Ticks to keep rotation", vis) + override val resetTicks by c.setting("Reset Rotation", 3, 1..10, 1, "Ticks before rotation is reset", vis) + + private val r1 by c.setting("Turn Speed 1", 70.0, 1.0..180.0, 0.1, "Rotation Speed 1", vis) + private val r2 by c.setting("Turn Speed 2", 110.0, 1.0..180.0, 0.1, "Rotation Speed 2", vis) + + override val turnSpeed get() = Random.nextDouble(r1, r2) + + var speedMultiplier = 1.0 + + fun slowdownIf(flag: Boolean) { + speedMultiplier = (if (flag) 0.0 else 1.0) + .coerceIn( + speedMultiplier - 0.3, // slowdown faster + speedMultiplier + 0.15 // accelerate slower + ) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/config/serializer/KeyCodeSerializer.kt b/common/src/main/kotlin/com/lambda/config/serializer/KeyCodeSerializer.kt index 01454940c..9cee9ff17 100644 --- a/common/src/main/kotlin/com/lambda/config/serializer/KeyCodeSerializer.kt +++ b/common/src/main/kotlin/com/lambda/config/serializer/KeyCodeSerializer.kt @@ -4,6 +4,7 @@ import com.google.gson.* import com.lambda.util.KeyCode import java.lang.reflect.Type +// ToDo: Use key lookup table to store actual key names object KeyCodeSerializer : JsonSerializer, JsonDeserializer { override fun serialize( src: KeyCode?, diff --git a/common/src/main/kotlin/com/lambda/config/settings/NumericSetting.kt b/common/src/main/kotlin/com/lambda/config/settings/NumericSetting.kt index 710388a4e..50553e401 100644 --- a/common/src/main/kotlin/com/lambda/config/settings/NumericSetting.kt +++ b/common/src/main/kotlin/com/lambda/config/settings/NumericSetting.kt @@ -1,7 +1,7 @@ package com.lambda.config.settings import com.lambda.config.AbstractSetting -import kotlin.math.round +import com.lambda.util.math.MathUtils.roundToStep import kotlin.reflect.KProperty /** @@ -33,19 +33,4 @@ abstract class NumericSetting( override operator fun setValue(thisRef: Any?, property: KProperty<*>, valueIn: T) { value = valueIn.coerceIn(range).roundToStep(step) } - - private fun T.roundToStep(step: T): T { - val doubleValue = this.toDouble() - val doubleStep = step.toDouble() - val result = round(doubleValue / doubleStep) * doubleStep - return when (this) { - is Byte -> result.toInt().toByte() - is Short -> result.toInt().toShort() - is Int -> result.toInt() - is Long -> result.toLong() - is Float -> result.toFloat() - is Double -> result - else -> throw IllegalArgumentException("Unsupported number type") - } as T - } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/event/EventFlow.kt b/common/src/main/kotlin/com/lambda/event/EventFlow.kt index f4de48cc5..9e6f8705b 100644 --- a/common/src/main/kotlin/com/lambda/event/EventFlow.kt +++ b/common/src/main/kotlin/com/lambda/event/EventFlow.kt @@ -1,6 +1,7 @@ package com.lambda.event import com.lambda.Lambda.LOG +import com.lambda.event.cancellable.Cancellable import com.lambda.event.cancellable.ICancellable import com.lambda.event.listener.Listener import com.lambda.threading.runConcurrent @@ -85,6 +86,18 @@ object EventFlow { return cancellable } + @JvmStatic + fun post(cancellable: T, process: T.() -> Unit) where T : Event, T : ICancellable { + val event = post(cancellable) + process(event) + } + + @JvmStatic + fun postChecked(cancellable: T, process: T.() -> Unit) where T : Event, T : ICancellable { + val event = post(cancellable) + if (!event.isCanceled()) process(event) + } + /** * Unsubscribes from both synchronous and concurrent event flows for a specific [Event] type [T]. * diff --git a/common/src/main/kotlin/com/lambda/event/events/ConnectionEvent.kt b/common/src/main/kotlin/com/lambda/event/events/ConnectionEvent.kt new file mode 100644 index 000000000..1be50ae82 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/event/events/ConnectionEvent.kt @@ -0,0 +1,16 @@ +package com.lambda.event.events + +import com.lambda.event.Event +import net.minecraft.network.listener.PacketListener +import net.minecraft.network.packet.c2s.handshake.ConnectionIntent +import net.minecraft.text.Text + +abstract class ConnectionEvent : Event { + class Connect( + val host: String, + port: Int, + listener: PacketListener, + intent: ConnectionIntent + ) : ConnectionEvent() + class Disconnect(val reason: Text) : ConnectionEvent() +} \ No newline at end of file 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 c5397f07d..34d2df099 100644 --- a/common/src/main/kotlin/com/lambda/event/events/MovementEvent.kt +++ b/common/src/main/kotlin/com/lambda/event/events/MovementEvent.kt @@ -5,5 +5,9 @@ import com.lambda.event.cancellable.Cancellable import com.lambda.event.cancellable.ICancellable abstract class MovementEvent : Event { + class Pre : MovementEvent() + class Post : MovementEvent() class ClipAtLedge : MovementEvent(), ICancellable by Cancellable() + class Jump(var height: Double) : MovementEvent(), ICancellable by Cancellable() + class SlowDown : Event, 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 new file mode 100644 index 000000000..eaa1172f5 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/event/events/PlayerPacketEvent.kt @@ -0,0 +1,20 @@ +package com.lambda.event.events + +import com.lambda.event.Event +import com.lambda.event.cancellable.Cancellable +import com.lambda.event.cancellable.ICancellable +import com.lambda.interaction.rotation.Rotation +import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket +import net.minecraft.util.math.Vec3d + +abstract class PlayerPacketEvent : Event { + class Pre( + var position: Vec3d, + var rotation: Rotation, + var onGround: Boolean, + var isSprinting: Boolean + ) : PlayerPacketEvent(), ICancellable by Cancellable() + class Post( + val packet: PlayerMoveC2SPacket + ) : PlayerPacketEvent(), ICancellable by Cancellable() +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/event/events/RotationEvent.kt b/common/src/main/kotlin/com/lambda/event/events/RotationEvent.kt new file mode 100644 index 000000000..b83e3e7bc --- /dev/null +++ b/common/src/main/kotlin/com/lambda/event/events/RotationEvent.kt @@ -0,0 +1,66 @@ +package com.lambda.event.events + +import com.lambda.event.Event +import com.lambda.event.cancellable.Cancellable +import com.lambda.event.cancellable.ICancellable +import com.lambda.interaction.InteractionConfig +import com.lambda.interaction.RotationManager +import com.lambda.interaction.rotation.IRotationConfig +import com.lambda.interaction.rotation.RotationRequest +import com.lambda.interaction.visibilty.VisibilityChecker.findRotation +import com.lambda.threading.runSafe +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.math.BlockPos +import net.minecraft.util.math.Direction +import java.util.* + +abstract class RotationEvent : Event { + class Pre : RotationEvent(), ICancellable by Cancellable() { + // Only one rotation can "win" each tick + val requests = TreeSet(Comparator.reverseOrder()) + + init { + // Always check if baritone wants to rotate as well + RotationManager.BaritoneProcessor.baritoneContext?.let { context -> + requests.add(RotationRequest(-1, context.config, context.rotation)) + } + } + + fun lookAtEntity( + rotationConfig: IRotationConfig, + interactionConfig: InteractionConfig, + entity: LivingEntity, + priority: Int = 0, + ) { + runSafe { + findRotation(rotationConfig, interactionConfig, listOf(entity.boundingBox), priority) { + entityResult?.entity == entity + }?.let { + requests.add(it) + } + } + } + + fun lookAtBlock( + rotationConfig: IRotationConfig, + interactionConfig: InteractionConfig, + blockPos: BlockPos, + sides: Set = emptySet(), + priority: Int = 0, + ) { + runSafe { + val state = world.getBlockState(blockPos) + val voxelShape = state.getOutlineShape(world, blockPos) + val boundingBoxes = voxelShape.boundingBoxes.map { it.offset(blockPos) } + findRotation(rotationConfig, interactionConfig, boundingBoxes, priority, sides) { + blockResult?.blockPos == blockPos && (blockResult?.side in sides || sides.isEmpty()) + }?.let { + requests.add(it) + } + } + } + } + class Post(val request: RotationRequest) : RotationEvent() +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/InteractionConfig.kt b/common/src/main/kotlin/com/lambda/interaction/InteractionConfig.kt new file mode 100644 index 000000000..3eb58020f --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/InteractionConfig.kt @@ -0,0 +1,17 @@ +package com.lambda.interaction + +import com.lambda.util.world.raycast.RayCastMask + +interface InteractionConfig { + /** + * Maximum distance to interact. + */ + val reach: Double + + /** + * Will check `resolution squared` many points on a grid on each visible surface of the hit box. + */ + val resolution: Int + + val rayCastMask: RayCastMask +} \ 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 new file mode 100644 index 000000000..57f284a00 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt @@ -0,0 +1,134 @@ +package com.lambda.interaction + +import com.lambda.Loadable +import com.lambda.context.SafeContext +import com.lambda.event.EventFlow +import com.lambda.event.events.PlayerPacketEvent +import com.lambda.interaction.rotation.Rotation +import com.lambda.threading.runSafe +import com.lambda.util.math.VecUtils.distSq +import com.lambda.util.player.MovementUtils.motionX +import com.lambda.util.player.MovementUtils.motionZ +import com.lambda.util.primitives.extension.component1 +import com.lambda.util.primitives.extension.component2 +import com.lambda.util.primitives.extension.component3 +import net.minecraft.network.packet.c2s.play.ClientCommandC2SPacket +import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket.* +import net.minecraft.util.math.MathHelper.square +import net.minecraft.util.math.Vec3d + +object PlayerPacketManager : Loadable { + private var prevPosition: Vec3d = Vec3d.ZERO + private var lastPosition = Vec3d(0.0, -1000.0, 0.0) + private var lastRotation = Rotation(0.0, -10000.0) + private var lastOnGround: Boolean? = null + private var lastSprinting: Boolean? = null + private var lastSneaking: Boolean? = null + private var sendTicks = 0 + + @JvmStatic + fun sendPlayerPackets() { + runSafe { + EventFlow.post(PlayerPacketEvent.Pre( + player.pos, + RotationManager.currentRotation, + player.isOnGround, + player.isSprinting + )) { + updatePlayerPackets(this) + } + } + } + + private fun SafeContext.updatePlayerPackets(event: PlayerPacketEvent.Pre) { + reportSprint(event.isSprinting) + reportSneak(player.isSneaking) + + if (mc.cameraEntity != player) return + + val position = event.position + val rotation = event.rotation + val ground = event.onGround + + RotationManager.currentRotation = rotation + + if (player.hasVehicle()) { + connection.sendPacket(Full( + player.motionX, + -999.0, + player.motionZ, + rotation.yaw.toFloat(), + rotation.pitch.toFloat(), + ground + )) + lastRotation = rotation + return + } + + val updatePosition = (position.subtract(lastPosition) distSq Vec3d.ZERO) > square(2.0E-4) || ++sendTicks >= 20 + val updateRotation = rotation != lastRotation + + val (x, y, z) = position + + val (yawD, pitchD) = rotation + val (yaw, pitch) = yawD.toFloat() to pitchD.toFloat() + + val packet = when { + updatePosition && updateRotation -> { + Full(x, y, z, yaw, pitch, ground) + } + updatePosition -> { + PositionAndOnGround(x, y, z, ground) + } + updateRotation -> { + LookAndOnGround(yaw, pitch, ground) + } + lastOnGround != ground -> { + OnGroundOnly(ground) + } + else -> null + } + + if (updatePosition) { + prevPosition = lastPosition + lastPosition = position + sendTicks = 0 + } + if (updateRotation) { + lastRotation = rotation + } + + lastOnGround = ground + + packet?.let { + EventFlow.postChecked(PlayerPacketEvent.Post(it)) { + connection.sendPacket(this.packet) + } + } + } + + private fun SafeContext.reportSprint(isSprinting: Boolean) { + if (lastSprinting == isSprinting) return + lastSprinting = isSprinting + + val state = if (isSprinting) { + ClientCommandC2SPacket.Mode.START_SPRINTING + } else { + ClientCommandC2SPacket.Mode.STOP_SPRINTING + } + + connection.sendPacket(ClientCommandC2SPacket(player, state)) + } + + private fun SafeContext.reportSneak(flag: Boolean) { + if (lastSneaking == flag) return + lastSneaking = flag + + val state = if (flag) ClientCommandC2SPacket.Mode.PRESS_SHIFT_KEY + else ClientCommandC2SPacket.Mode.RELEASE_SHIFT_KEY + + connection.sendPacket(ClientCommandC2SPacket(player, state)) + } +} + + diff --git a/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt b/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt new file mode 100644 index 000000000..3aebab7a6 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/RotationManager.kt @@ -0,0 +1,252 @@ +package com.lambda.interaction + +import com.lambda.Lambda.LOG +import com.lambda.Lambda.mc +import com.lambda.Loadable +import com.lambda.config.RotationSettings +import com.lambda.event.EventFlow +import com.lambda.event.events.ConnectionEvent +import com.lambda.event.events.PacketEvent +import com.lambda.event.events.RotationEvent +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listener +import com.lambda.event.listener.UnsafeListener.Companion.unsafeListener +import com.lambda.interaction.rotation.* +import com.lambda.interaction.rotation.Rotation.Companion.angleDifference +import com.lambda.interaction.rotation.Rotation.Companion.fixSensitivity +import com.lambda.interaction.rotation.Rotation.Companion.interpolate +import com.lambda.module.modules.client.Baritone +import com.lambda.threading.runOnGameThread +import com.lambda.threading.runSafe +import com.lambda.util.Communication.info +import com.lambda.util.math.MathUtils.lerp +import com.lambda.util.math.MathUtils.toRadian +import com.lambda.util.math.Vec2d +import com.lambda.util.primitives.extension.partialTicks +import net.minecraft.client.input.KeyboardInput +import net.minecraft.enchantment.EnchantmentHelper +import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket +import net.minecraft.util.math.MathHelper +import kotlin.math.* + +object RotationManager : Loadable { + var currentRotation = Rotation.ZERO + private var prevRotation = Rotation.ZERO + + var currentRequest: RotationRequest? = null + + private var keepTicks = 0 + private var pauseTicks = 0 + + @JvmStatic + fun update() = + runSafe { + EventFlow.post(RotationEvent.Pre()) { + rotate()?.let { + EventFlow.post(RotationEvent.Post(it)) + } + } + } + + @JvmStatic + fun updateInterpolated() = runSafe { +// if (currentRequest == null) return@runSafe +// val interpolation = interpolate(prevRotation, currentRotation, mc.tickDelta.toDouble()) +// +// val rot = interpolation.fixSensitivity(prevRotation) +// +// if (currentRequest?.config?.rotationMode == RotationMode.LOCK) { +// player.yaw = rot.yaw.toFloat() +// player.pitch = rot.pitch.toFloat() +// } + } + + init { + listener { event -> + val packet = event.packet + if (packet !is PlayerPositionLookS2CPacket) return@listener + + runOnGameThread { + reset(Rotation(packet.yaw, packet.pitch)) + } + } + + unsafeListener { + reset(Rotation.ZERO) + } + } + + private fun RotationEvent.Pre.rotate() = runSafe { + prevRotation = currentRotation + + (keepTicks--).coerceAtLeast(0) + (pauseTicks--).coerceAtLeast(0) + + val resetTicks = currentRequest?.config?.resetTicks ?: 0 + if (keepTicks + resetTicks < 0 || pauseTicks >= 0) { + currentRequest = null + } + + var chosenRequest: RotationRequest? = null + + requests.firstOrNull()?.let { request -> + chosenRequest = request + currentRequest = request + keepTicks = request.config.keepTicks + } + + currentRotation = Rotation(player.yaw, player.pitch) + + val context = currentRequest ?: return@runSafe chosenRequest + val rotationTo = if (keepTicks >= 0) context.rotation else currentRotation + + var speedMultiplier = (context.config as? RotationSettings)?.speedMultiplier ?: 1.0 + if (keepTicks < 0) speedMultiplier = 1.0 + + val turnSpeed = context.config.turnSpeed * speedMultiplier + + val interpolation = interpolate(prevRotation, rotationTo, turnSpeed) + + currentRotation = interpolation.fixSensitivity(prevRotation) + + if (context.config.rotationMode == RotationMode.LOCK) { + player.yaw = currentRotation.yaw.toFloat() + player.pitch = currentRotation.pitch.toFloat() + } + + chosenRequest?.let { request -> + if (request.rotation.fixSensitivity(prevRotation) == currentRotation) { + request.isPending = false + } + } + + return@runSafe chosenRequest + } + + private fun reset(rotation: Rotation) { + prevRotation = rotation + currentRotation = rotation + + currentRequest = null + pauseTicks = 3 + } + + private val smoothRotation get() = + lerp(prevRotation, currentRotation, mc.partialTicks) + + @JvmStatic val lockRotation get() = + if (currentRequest?.config?.rotationMode == RotationMode.LOCK) smoothRotation else null + + @JvmStatic val renderYaw get() = + if (currentRequest?.config == null) null else smoothRotation.yaw.toFloat() + + @JvmStatic val renderPitch get() = + if (currentRequest?.config == null) null else smoothRotation.pitch.toFloat() + + @JvmStatic val handYaw get() = + if (currentRequest?.config?.rotationMode == RotationMode.LOCK) currentRotation.yaw.toFloat() else null + + @JvmStatic val handPitch get() = + if (currentRequest?.config?.rotationMode == RotationMode.LOCK) currentRotation.pitch.toFloat() else null + + @JvmStatic val movementYaw: Float? get() { + val config = currentRequest?.config ?: return null + if (config.rotationMode == RotationMode.SILENT) return null + return currentRotation.yaw.toFloat() + } + + @JvmStatic val movementPitch: Float? get() { + val config = currentRequest?.config ?: return null + if (config.rotationMode == RotationMode.SILENT) return null + return currentRotation.pitch.toFloat() + } + + @JvmStatic fun getRotationForVector(deltaTime: Double): Vec2d? { + val config = currentRequest?.config ?: return null + if (config.rotationMode == RotationMode.SILENT) return null + + val rot = lerp(prevRotation, currentRotation, deltaTime) + return Vec2d(rot.yaw, rot.pitch) + } + + object BaritoneProcessor { + var baritoneContext: RotationContext? = null; private set + + private val movementYawList = arrayOf( + 0.0, 45.0, + 90.0, 135.0, + 180.0, 225.0, + 270.0, 315.0, + ) + + init { + listener { + baritoneContext = null + } + } + + @JvmStatic + fun handleBaritoneRotation(yaw: Float, pitch: Float) = runSafe { + baritoneContext = RotationContext(Baritone.rotation, Rotation(yaw, pitch)) + } + + @JvmStatic + fun processPlayerMovement() = runSafe { + val config = currentRequest?.config ?: return@runSafe + + val input = player.input + val handledByBaritone = input !is KeyboardInput + + // Sign it to remove previous speed modifier + val signForward = sign(input.movementForward) + val signStrafe = sign(input.movementSideways) + + // No changes are needed when no inputs are pressed + if (signForward == 0f && signStrafe == 0f) return@runSafe + + // Movement speed modifier + val multiplier = if (!player.shouldSlowDown()) 1f else + (0.3f + EnchantmentHelper.getSwiftSneakSpeedBoost(player)).coerceIn(0f, 1f) + + // No changes are needed, when we don't modify the yaw used to move the player + if (config.rotationMode == RotationMode.SILENT) return@runSafe + + val modifyMovement = config.rotationMode == RotationMode.SYNC || handledByBaritone + if (!modifyMovement) return@runSafe + + val playerYaw = player.yaw.toDouble() + val baritoneYaw = if (handledByBaritone) baritoneContext?.rotation?.yaw else null + + // The yaw relative to which the movement was constructed + val movementYaw = baritoneYaw ?: playerYaw + + // Actual yaw used to move the player + val actualYaw = currentRotation.yaw + + val yawRad = (movementYaw - actualYaw).toRadian().toFloat() + + val cosDelta = MathHelper.cos(yawRad) + val sinDelta = MathHelper.sin(yawRad) + + val newX = signStrafe * cosDelta - signForward * sinDelta + val newZ = signForward * cosDelta + signStrafe * sinDelta + + // Apply new movement + input.movementSideways = newX.roundToInt().toFloat() * multiplier + input.movementForward = newZ.roundToInt().toFloat() * multiplier + + baritoneYaw ?: return@runSafe + + // Makes baritone movement safe + // 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) } + + if (minYawDist > 5.0) { + input.movementSideways = 0f + input.movementForward = 0f + } + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/rotation/IRotationConfig.kt b/common/src/main/kotlin/com/lambda/interaction/rotation/IRotationConfig.kt new file mode 100644 index 000000000..c3593654d --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/rotation/IRotationConfig.kt @@ -0,0 +1,25 @@ +package com.lambda.interaction.rotation + +interface IRotationConfig { + /** + * - [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 +} \ 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 new file mode 100644 index 000000000..dbb459d73 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/rotation/Rotation.kt @@ -0,0 +1,103 @@ +package com.lambda.interaction.rotation + +import com.lambda.Lambda.mc +import com.lambda.threading.runSafe +import com.lambda.util.math.MathUtils.toDegree +import com.lambda.util.math.MathUtils.toRadian +import com.lambda.util.world.raycast.RayCastMask +import com.lambda.util.world.raycast.RayCastUtils.rayCast +import net.minecraft.util.math.Direction +import net.minecraft.util.math.MathHelper +import net.minecraft.util.math.Vec3d +import kotlin.math.* + +data class Rotation(val yaw: Double, val pitch: Double) { + constructor(yaw: Float, pitch: Float) : this(yaw.toDouble(), pitch.toDouble()) + + private val rotationVector: Vec3d get() { + val yawRad = -yaw.toRadian() + val pitchRad = pitch.toRadian() + + return Vec3d(sin(yawRad), -1.0, cos(yawRad)) + .multiply(Vec3d(cos(pitchRad), sin(pitchRad), cos(pitchRad))) + } + + fun rayCast( + reach: Double, + mask: RayCastMask = RayCastMask.BOTH, + eye: Vec3d? = null, + fluids: Boolean = false + ) = runSafe { + rayCast(eye ?: player.eyePos, rotationVector, reach, mask, fluids) + } + + val Direction.yaw: Float + get() = when (this) { + Direction.NORTH -> -180.0f + Direction.SOUTH -> 0.0f + Direction.EAST -> -90.0f + Direction.WEST -> 90.0f + else -> 0.0f + } + + companion object { + val ZERO = Rotation(0.0, 0.0) + val DOWN = Rotation(0.0, 90.0) + + private fun wrap(deg: Double) = MathHelper.wrapDegrees(deg) + + fun interpolate(a: Rotation, b: Rotation, speed: Double): Rotation { + val yawDiff = wrap(b.yaw - a.yaw) + val pitchDiff = wrap(b.pitch - a.pitch) + + val diff = hypot(yawDiff, pitchDiff) + + val yawSpeed = abs(yawDiff / diff) * speed + val pitchSpeed = abs(pitchDiff / diff) * speed + + val yaw = a.yaw + yawDiff.coerceIn(-yawSpeed, yawSpeed) + val pitch = a.pitch + pitchDiff.coerceIn(-pitchSpeed, pitchSpeed) + + return Rotation(yaw, pitch) + } + + fun Rotation.fixSensitivity(last: Rotation): Rotation { + val f = mc.options.mouseSensitivity.value * 0.6 + 0.2 + val step = f * f * f * 8.0 * 0.15F + + val deltaYaw = yaw - last.yaw + var fixedYaw = (deltaYaw / step).roundToInt() * step + fixedYaw += last.yaw + + val deltaPitch = pitch - last.pitch + var fixedPitch = (deltaPitch / step).roundToInt() * step + fixedPitch += last.pitch + fixedPitch = fixedPitch.coerceIn(-90.0, 90.0) + + return Rotation(fixedYaw, fixedPitch) + } + + fun Vec3d.rotationTo(vec: Vec3d): Rotation { + val diffX = vec.x - x + val diffY = vec.y - y + val diffZ = vec.z - z + + val yawRad = atan2(diffZ, diffX) + val pitchRad = -atan2(diffY, hypot(diffX, diffZ)) + + val yaw = wrap(yawRad.toDegree() - 90.0) + val pitch = wrap(pitchRad.toDegree()) + + return Rotation(yaw, pitch) + } + + fun Rotation.distance(b: Rotation) = + hypot( + wrap(yaw - b.yaw), + wrap(pitch - b.pitch) + ) + + fun angleDifference(a: Double, b: Double) = + abs(wrap(a - b)) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/rotation/RotationContext.kt b/common/src/main/kotlin/com/lambda/interaction/rotation/RotationContext.kt new file mode 100644 index 000000000..b9cd04cc6 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/rotation/RotationContext.kt @@ -0,0 +1,6 @@ +package com.lambda.interaction.rotation + +data class RotationContext( + val config: IRotationConfig, + val rotation: Rotation +) \ 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/rotation/RotationMode.kt new file mode 100644 index 000000000..9fb5a7900 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/rotation/RotationMode.kt @@ -0,0 +1,12 @@ +package com.lambda.interaction.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. + */ +enum class RotationMode { + SILENT, + SYNC, + LOCK +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/rotation/RotationRequest.kt b/common/src/main/kotlin/com/lambda/interaction/rotation/RotationRequest.kt new file mode 100644 index 000000000..27282e1e1 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/rotation/RotationRequest.kt @@ -0,0 +1,11 @@ +package com.lambda.interaction.rotation + +data class RotationRequest( + val priority: Int = 1, + val config: IRotationConfig, + val rotation: Rotation, + var isPending: Boolean = true +) : Comparable { + override fun compareTo(other: RotationRequest) = + priority.compareTo(other.priority) +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/interaction/visibilty/EntityInteraction.kt b/common/src/main/kotlin/com/lambda/interaction/visibilty/EntityInteraction.kt new file mode 100644 index 000000000..c9098e5bc --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/visibilty/EntityInteraction.kt @@ -0,0 +1,4 @@ +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 new file mode 100644 index 000000000..72a87f787 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/interaction/visibilty/VisibilityChecker.kt @@ -0,0 +1,144 @@ +package com.lambda.interaction.visibilty + +import com.lambda.config.RotationSettings +import com.lambda.context.SafeContext +import com.lambda.interaction.InteractionConfig +import com.lambda.interaction.RotationManager +import com.lambda.interaction.rotation.IRotationConfig +import com.lambda.interaction.rotation.Rotation +import com.lambda.interaction.rotation.Rotation.Companion.distance +import com.lambda.interaction.rotation.Rotation.Companion.rotationTo +import com.lambda.interaction.rotation.RotationRequest +import com.lambda.util.math.VecUtils.distSq +import com.lambda.util.primitives.extension.component6 +import net.minecraft.util.hit.HitResult +import net.minecraft.util.math.* +import java.util.* + +object VisibilityChecker { + fun SafeContext.findRotation( + rotationConfig: IRotationConfig, + interact: InteractionConfig, + boxes: List, + priority: Int = 0, + sides: Set = emptySet(), + hitCheck: HitResult.() -> Boolean, + ) : RotationRequest? { + val eye = player.getCameraPosVec(mc.tickDelta) + + if (boxes.any { it.contains(eye) }) { + return stay(priority, rotationConfig) + } + + val currentRotation = RotationManager.currentRotation + val currentCast = currentRotation.rayCast( + interact.reach, + interact.rayCastMask, + eye + ) + val check = currentCast?.let { it.hitCheck() } ?: false + + // Slowdown or freeze if looking correct + (rotationConfig as? RotationSettings)?.slowdownIf(check) ?: run { + if (check) { + return stay(priority, rotationConfig) + } + } + + val reachSq = interact.reach * interact.reach + + var closestRotation: Rotation? = null + var rotationDist = 0.0 + + boxes.forEach { box -> + scanVisibleSurfaces(box, sides, interact.resolution) { vec -> + if (eye distSq vec > reachSq) return@scanVisibleSurfaces + + val newRotation = eye.rotationTo(vec) + + val cast = newRotation.rayCast( + interact.reach, + interact.rayCastMask, + eye + ) ?: return@scanVisibleSurfaces + if (!cast.hitCheck()) return@scanVisibleSurfaces + + val dist = newRotation.distance(currentRotation) + if (dist >= rotationDist && closestRotation != null) return@scanVisibleSurfaces + + rotationDist = dist + closestRotation = newRotation + } + } + + // Rotate to selected point + closestRotation?.let { rotation -> + return RotationRequest(priority, rotationConfig, rotation) + } + + return null + } + + private fun stay(priority: Int = 0, config: IRotationConfig) = + RotationRequest(priority, config, RotationManager.currentRotation) + + inline fun SafeContext.scanVisibleSurfaces(box: Box, sides: Set, resolution: Int, check: (Vec3d) -> Unit) { + val shrunk = box.expand(-0.005) + getVisibleSides(box) + .forEach { side -> + if (sides.isNotEmpty() && side !in sides) { + return@forEach + } + val (minX, minY, minZ, maxX, maxY, maxZ) = shrunk.bounds(side) + val stepX = (maxX - minX) / resolution + val stepY = (maxY - minY) / resolution + val stepZ = (maxZ - minZ) / resolution + for (i in 0 .. resolution) { + val x = if (stepX != 0.0) minX + stepX * i else minX + for (j in 0 .. resolution) { + val y = if (stepY != 0.0) minY + stepY * j else minY + val z = if (stepZ != 0.0) minZ + stepZ * ((if (stepX != 0.0) j else i)) else minZ + check(Vec3d(x, y, z)) + } + } + } + } + + fun Box.bounds(side: Direction) = + when (side) { + Direction.DOWN -> doubleArrayOf(minX, minY, minZ, maxX, minY, maxZ) + Direction.UP -> doubleArrayOf(minX, maxY, minZ, maxX, maxY, maxZ) + Direction.NORTH -> doubleArrayOf(minX, minY, minZ, maxX, maxY, minZ) + Direction.SOUTH -> doubleArrayOf(minX, minY, maxZ, maxX, maxY, maxZ) + Direction.WEST -> doubleArrayOf(minX, minY, minZ, minX, maxY, maxZ) + Direction.EAST -> doubleArrayOf(maxX, minY, minZ, maxX, maxY, maxZ) + } + + fun SafeContext.getVisibleSides(box: Box): Set { + val visibleSides = EnumSet.noneOf(Direction::class.java) + + val eyePos = player.eyePos + val center = box.center + + return visibleSides + .checkAxis(eyePos.x - center.x, box.lengthX / 2, Direction.WEST, Direction.EAST) + .checkAxis(eyePos.y - center.y, box.lengthY / 2, Direction.DOWN, Direction.UP) + .checkAxis(eyePos.z - center.z, box.lengthZ / 2, Direction.NORTH, Direction.SOUTH) + } + + private fun EnumSet.checkAxis( + diff: Double, + limit: Double, + negativeSide: Direction, + positiveSide: Direction + ) = apply { + when { + diff < -limit -> { + add(negativeSide) + } + diff > limit -> { + add(positiveSide) + } + } + } +} diff --git a/common/src/main/kotlin/com/lambda/module/Module.kt b/common/src/main/kotlin/com/lambda/module/Module.kt index 33eb8d60f..c26364ada 100644 --- a/common/src/main/kotlin/com/lambda/module/Module.kt +++ b/common/src/main/kotlin/com/lambda/module/Module.kt @@ -6,6 +6,7 @@ import com.lambda.config.Configuration import com.lambda.config.configurations.ModuleConfig import com.lambda.config.settings.comparable.BooleanSetting import com.lambda.config.settings.numeric.DoubleSetting +import com.lambda.context.SafeContext import com.lambda.event.Muteable import com.lambda.event.events.KeyPressEvent import com.lambda.event.listener.Listener @@ -123,19 +124,19 @@ abstract class Module( isEnabled = !isEnabled } - protected fun onEnable(block: () -> Unit) { + protected fun onEnable(block: SafeContext.() -> Unit) { isEnabledSetting.listener { from, to -> if (!from && to) block() } } - protected fun onDisable(block: () -> Unit) { + protected fun onDisable(block: SafeContext.() -> Unit) { isEnabledSetting.listener { from, to -> if (from && !to) block() } } - protected fun onToggle(block: (to: Boolean) -> Unit) { + protected fun onToggle(block: SafeContext.(to: Boolean) -> Unit) { isEnabledSetting.listener { from, to -> if (from != to) block(to) } diff --git a/common/src/main/kotlin/com/lambda/module/modules/BoringModule.kt b/common/src/main/kotlin/com/lambda/module/modules/BoringModule.kt index 2bd6b90fe..12261ffbf 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/BoringModule.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/BoringModule.kt @@ -5,8 +5,6 @@ import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listener import com.lambda.module.Module import com.lambda.module.tag.ModuleTag -import com.lambda.util.Communication.info -import com.lambda.util.Communication.toast import com.lambda.util.KeyCode import net.minecraft.util.math.BlockPos import java.awt.Color diff --git a/common/src/main/kotlin/com/lambda/module/modules/RotationTest.kt b/common/src/main/kotlin/com/lambda/module/modules/RotationTest.kt new file mode 100644 index 000000000..2efdabd38 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/module/modules/RotationTest.kt @@ -0,0 +1,54 @@ +package com.lambda.module.modules + +import com.lambda.config.InteractionSettings +import com.lambda.config.RotationSettings +import com.lambda.event.events.RotationEvent +import com.lambda.event.listener.SafeListener.Companion.listener +import com.lambda.module.Module +import net.minecraft.util.Hand +import net.minecraft.util.hit.BlockHitResult +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction + +object RotationTest : Module( + name = "RotationTest", + description = "Test rotation", + defaultTags = setOf() +) { + private val rotationConfig = RotationSettings(this) + private val interactionConfig = InteractionSettings(this) + + private var pos: BlockPos = BlockPos.ORIGIN + private var side = Direction.UP + + init { + onEnable { + val hit = mc.crosshairTarget as? BlockHitResult ?: return@onEnable + pos = hit.blockPos + side = hit.side + } + + listener { event -> +// val target = getClosestEntity( +// player.eyePos, interaction.reachDistance.toDouble() +// ) ?: return@listener + + event.lookAtBlock(rotationConfig, interactionConfig, pos, setOf(side)) + } + + listener { + (mc.crosshairTarget as? BlockHitResult)?.let { hit -> + if (hit.blockPos != pos || hit.side != side) { + interaction.cancelBlockBreaking() + return@listener + } + + interaction.updateBlockBreakingProgress(pos, side) + player.swingHand(Hand.MAIN_HAND) + return@listener + } + + interaction.cancelBlockBreaking() + } + } +} \ No newline at end of file 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 new file mode 100644 index 000000000..892478d99 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/module/modules/client/Baritone.kt @@ -0,0 +1,23 @@ +package com.lambda.module.modules.client + +import com.lambda.interaction.rotation.IRotationConfig +import com.lambda.interaction.rotation.RotationMode +import com.lambda.module.Module +import com.lambda.module.tag.ModuleTag +import com.lambda.util.math.MathUtils.random + +object Baritone : Module( + name = "Baritone", + description = "Baritone configuration", + defaultTags = setOf(ModuleTag.CLIENT) +) { + private val r1 by setting("Turn Speed 1", 70.0, 1.0..180.0, 0.1) + private val r2 by setting("Turn Speed 2", 110.0, 1.0..180.0, 0.1) + + val rotation = object : IRotationConfig { + override val rotationMode = RotationMode.SYNC + override val turnSpeed get() = random(r1, r2) + override val keepTicks = 3 + override val resetTicks = 3 + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/module/modules/movement/RocketExtend.kt b/common/src/main/kotlin/com/lambda/module/modules/movement/RocketExtend.kt index 281e63ac6..95feef00d 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/movement/RocketExtend.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/movement/RocketExtend.kt @@ -39,7 +39,9 @@ object RocketExtend : Module( event.cancel() } - onDisable(::reset) + onDisable { + reset() + } } private fun reset() = runSafe { diff --git a/common/src/main/kotlin/com/lambda/util/DebugInfoHud.kt b/common/src/main/kotlin/com/lambda/util/DebugInfoHud.kt new file mode 100644 index 000000000..1de451594 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/DebugInfoHud.kt @@ -0,0 +1,41 @@ +package com.lambda.util + +import com.lambda.Lambda +import com.lambda.Lambda.mc +import com.lambda.command.CommandManager +import com.lambda.event.EventFlow +import com.lambda.module.ModuleRegistry +import net.minecraft.util.Formatting +import net.minecraft.util.hit.BlockHitResult +import net.minecraft.util.hit.EntityHitResult + +object DebugInfoHud { + @JvmStatic + fun MutableList.addDebugInfo() { + add("") + add("" + Formatting.UNDERLINE + "Lambda ${Lambda.VERSION}+${mc.versionType}") + add("Modules: ${ModuleRegistry.modules.size} with ${ModuleRegistry.modules.sumOf { it.settings.size }} settings") + add("Commands: ${CommandManager.commands.size}") + add("Synchronous Listeners: ${EventFlow.syncListeners.size}") + add("Concurrent Listeners: ${EventFlow.concurrentListeners.size}") + + when (val hit = mc.crosshairTarget) { + is BlockHitResult -> { + add("Crosshair Target: Block") + add(" Vec3d: %.5f, %.5f, %.5f".format(hit.pos.x, hit.pos.y, hit.pos.z)) + add(" BlockPos: ${hit.blockPos.toShortString()}") + add(" Side: ${hit.side}") + } + + is EntityHitResult -> { + add("Crosshair Target: Entity") + add(" Vec3d: ${hit.pos}") + add(" Entity: ${hit.entity}") + } + + null -> add("Crosshair Target: None") + } + + return + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/util/math/ColorUtils.kt b/common/src/main/kotlin/com/lambda/util/math/ColorUtils.kt new file mode 100644 index 000000000..4e8b10f1e --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/math/ColorUtils.kt @@ -0,0 +1,16 @@ +package com.lambda.util.math + +import java.awt.Color + +object ColorUtils { + fun Color.setAlpha(value: Double) = + Color(red, green, blue, (value * 255.0).coerceIn(0.0, 255.0).toInt()) + + fun Color.multAlpha(value: Double) = + Color(red, green, blue, (value * alpha).coerceIn(0.0, 255.0).toInt()) + + val Color.r get() = red.toDouble() / 255.0 + val Color.g get() = green.toDouble() / 255.0 + val Color.b get() = blue.toDouble() / 255.0 + val Color.a get() = alpha.toDouble() / 255.0 +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/util/math/MathUtils.kt b/common/src/main/kotlin/com/lambda/util/math/MathUtils.kt new file mode 100644 index 000000000..f1e15087c --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/math/MathUtils.kt @@ -0,0 +1,142 @@ +package com.lambda.util.math + +import com.lambda.interaction.rotation.Rotation +import com.lambda.util.math.ColorUtils.a +import com.lambda.util.math.ColorUtils.b +import com.lambda.util.math.ColorUtils.g +import com.lambda.util.math.ColorUtils.r +import net.minecraft.util.math.Vec3d +import java.awt.Color +import java.math.BigDecimal +import java.math.RoundingMode +import kotlin.math.* +import kotlin.random.Random.Default.nextDouble + +object MathUtils { + private const val PI_FLOAT = 3.141593f + + fun Float.toRadian() = this / 180.0f * PI_FLOAT + + fun Double.toRadian() = this / 180.0 * PI + + fun Float.toDegree() = this * 180.0f / PI_FLOAT + + fun Double.toDegree() = this * 180.0 / PI + + fun Boolean.toInt() = if (this) 1 else 0 + + fun Boolean.toIntSign() = if (this) 1 else -1 + + fun Double.floorToInt() = floor(this).toInt() + + fun Double.ceilToInt() = ceil(this).toInt() + + fun T.roundToStep(step: T): T { + val stepD = step.toDouble() + var value = round(toDouble() / stepD) * stepD + value = value.roundToPlaces(decimalPlaces(stepD)) + if (abs(value) == 0.0) value = 0.0 + + return typeConvert(value) + } + + fun Double.roundToPlaces(places: Int) = + BigDecimal(this).setScale(places, RoundingMode.HALF_EVEN).toDouble() + + fun T.typeConvert(valueIn: Double): T { + @Suppress("UNCHECKED_CAST") + return when(this) { + is Byte -> valueIn.toInt().toByte() + is Short -> valueIn.toInt().toShort() + is Double -> valueIn + is Float -> valueIn.toFloat() + is Int -> valueIn.toInt() + is Long -> valueIn.toLong() + else -> throw IllegalArgumentException("Unsupported number type") + } as T + } + + private fun decimalPlaces(value: Double) = BigDecimal.valueOf(value).scale() + + fun random(v1: Double, v2: Double): Double { + val min = min(v1, v2) + val max = max(v1, v2) + return nextDouble(min, max) + } + + /** + * @return The smallest power of two that is greater than or equal to the input integer. + */ + fun Int.ceilToPOT(): Int { + var i = this + i-- + i = i or (i shr 1) + i = i or (i shr 2) + i = i or (i shr 4) + i = i or (i shr 8) + i = i or (i shr 16) + return ++i + } + + inline val Int.sq: Int get() = this * this + + /** + * Performs linear interpolation between two Float values. + * + * This function calculates the value at a specific point + * between [start] and [end] based on the interpolation factor [factor]. + * The interpolation factor [factor] is clamped between zero + * and one to ensure the result stays within the range of [start] and [end]. + * + * @param start The start value. + * @param end The end value. + * @param factor The interpolation factor, typically between 0 (representing [start]) and 1 (representing [end]). + * @return The interpolated value between [start] and [end]. + */ + fun lerp(start: Float, end: Float, factor: Float) = + start + ((end - start) * factor.coerceIn(0f, 1f)) + + /** + * Performs linear interpolation between two Double values. + * + * This function calculates the value at a specific point + * between [start] and [end] based on the interpolation factor [factor]. + * The interpolation factor [factor] is clamped between zero + * and one to ensure the result stays within the range of [start] and [end]. + * + * @param start The start value. + * @param end The end value. + * @param factor The interpolation factor, typically between 0 (representing [start]) and 1 (representing [end]). + * @return The interpolated value between [start] and [end]. + */ + fun lerp(start: Double, end: Double, factor: Double) = + start + ((end - start) * factor.coerceIn(0.0, 1.0)) + + fun lerp(start: Vec3d, end: Vec3d, factor: Double) = + Vec3d( + lerp(start.x, end.x, factor), + lerp(start.y, end.y, factor), + lerp(start.z, end.z, factor) + ) + + fun lerp(start: Vec2d, end: Vec2d, factor: Double) = + Vec2d( + lerp(start.x, end.x, factor), + lerp(start.y, end.y, factor) + ) + + + fun lerp(start: Rotation, end: Rotation, factor: Double) = + Rotation( + lerp(start.yaw, end.yaw, factor), + lerp(start.pitch, end.pitch, factor) + ) + + fun lerp(c1: Color, c2: Color, p: Double) = + Color( + lerp(c1.r, c2.r, p).toFloat(), + lerp(c1.g, c2.g, p).toFloat(), + lerp(c1.b, c2.b, p).toFloat(), + lerp(c1.a, c2.a, p).toFloat() + ) +} diff --git a/common/src/main/kotlin/com/lambda/util/math/Range.kt b/common/src/main/kotlin/com/lambda/util/math/Range.kt new file mode 100644 index 000000000..3e11ab6ea --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/math/Range.kt @@ -0,0 +1,101 @@ +package com.lambda.util.math + +import net.minecraft.util.math.Box +import java.util.Random +import java.util.concurrent.ThreadLocalRandom +import kotlin.random.Random.Default.nextDouble + +class DoubleRange( + override val start: Double, + override val endInclusive: Double +) : ClosedRange { + infix fun step(step: Double): DoubleIterator { + return object : DoubleIterator() { + private var next = start + override fun hasNext() = next <= endInclusive + override fun nextDouble() = next.also { next += step } + } + } + + /** + * Returns a random value within the range. + */ + fun random() = nextDouble(start, endInclusive) + + /** + * Returns a bounding box from two additional ranges. + * @param y The second range. + * @param z The third range. + * @return The bounding box. + */ + fun box(y: DoubleRange, z: DoubleRange) = + Box(this.start, y.start, z.start, this.endInclusive, y.endInclusive, z.endInclusive) +} + +infix fun Double.to(that: Double) = DoubleRange(this, that) + +/** + * Converts a value from one range to a normalized value between 0 and 1. + */ +fun ClosedRange.normalized(value: T): T where T : Comparable, T : Number = scale(value, 0.0 as T, 1.0 as T) // hacky + +/** + * Inverts the range. + */ +fun ClosedRange.inverted(): ClosedRange where T : Comparable, T : Number = endInclusive..start + +/** + * Converts a value from one range to another while keeping the ratio using exponential interpolation. + * @param value The value to convert. + * @param minIn The minimum of the new range. + * @param maxIn The maximum of the new range. + * @return The converted value. + */ +fun ClosedRange.scale(value: T, minIn: T, maxIn: T): T where T : Comparable, T : Number = + transform(value, start, endInclusive, minIn, maxIn) + +/** + * Converts a value from one range to another while keeping the ratio using exponential interpolation. + * @param value The value to convert. + * @param x1 The minimum of the old range. + * @param y1 The maximum of the old range. + * @param x2 The minimum of the new range. + * @param y2 The maximum of the new range. + * @return The converted value. + * @see Linear Interpolation + */ +fun transform(value: T, x1: T, y1: T, x2: T, y2: T): T where T : Comparable, T : Number = + x2 + (value - x1) * ((y2 - x2) / (y1 - x1)) + + +/** + * Clamps a value to the range. + * @param value The value to clamp. + * @return The clamped value. + */ +fun > ClosedRange.coerceIn(value: T) = value.coerceIn(start, endInclusive) +fun > T.coerceIn(range: ClosedRange) = range.coerceIn(this) + +private operator fun T.minus(oldMin: T): T where T : Comparable, T : Number { + return (this.toDouble() - oldMin.toDouble()) as T +} + +private operator fun T.div(other: T): T where T : Comparable, T : Number { + return (this.toDouble() / other.toDouble()) as T +} + +private operator fun T.times(other: T): T where T : Comparable, T : Number { + return (this.toDouble() * other.toDouble()) as T +} + +private operator fun T.plus(other: T): T where T : Comparable, T : Number { + return (this.toDouble() + other.toDouble()) as T +} + +fun min(newMin: T, newMax: T): T where T : Comparable, T : Number { + return if (newMin < newMax) newMin else newMax +} + +fun max(newMin: T, newMax: T): T where T : Comparable, T : Number { + return if (newMin > newMax) newMin else newMax +} diff --git a/common/src/main/kotlin/com/lambda/util/math/Vec2d.kt b/common/src/main/kotlin/com/lambda/util/math/Vec2d.kt new file mode 100644 index 000000000..a88e90ea0 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/math/Vec2d.kt @@ -0,0 +1,47 @@ +package com.lambda.util.math + +import net.minecraft.util.math.Vec2f +import kotlin.math.roundToInt + +data class Vec2d(val x: Double, val y: Double) { + constructor(x: Int, y: Int) : this(x.toDouble(), y.toDouble()) + constructor(x: Float, y: Float) : this(x.toDouble(), y.toDouble()) + constructor(x: Short, y: Short) : this(x.toDouble(), y.toDouble()) + + operator fun plus(vec2d: Vec2d) = plus(vec2d.x, vec2d.y) + + operator fun plus(add: Double) = plus(add, add) + + fun plus(x: Double, y: Double) = Vec2d(this.x + x, this.y + y) + + operator fun minus(vec2d: Vec2d) = minus(vec2d.x, vec2d.y) + + operator fun minus(sub: Double) = minus(sub, sub) + + fun minus(x: Double, y: Double) = plus(-x, -y) + + operator fun times(vec2d: Vec2d) = times(vec2d.x, vec2d.y) + + operator fun times(multiplier: Double) = times(multiplier, multiplier) + + fun times(x: Double, y: Double) = Vec2d(this.x * x, this.y * y) + + operator fun div(vec2d: Vec2d) = div(vec2d.x, vec2d.y) + + operator fun div(divider: Double) = div(divider, divider) + + fun div(x: Double, y: Double) = Vec2d(this.x / x, this.y / y) + + val vec2f = Vec2f(x.toFloat(), y.toFloat()) + val rounded get() = Vec2d(x.roundToInt(), y.roundToInt()) + + companion object { + val ZERO: Vec2d = Vec2d(0.0, 0.0) + val ONE: Vec2d = Vec2d(1.0, 1.0) + + val RIGHT: Vec2d = Vec2d(1.0, 0.0) + val LEFT: Vec2d = Vec2d(-1.0, 0.0) + val TOP: Vec2d = Vec2d(0.0, 1.0) + val BOTTOM: Vec2d = Vec2d(0.0, -1.0) + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/util/math/VecUtils.kt b/common/src/main/kotlin/com/lambda/util/math/VecUtils.kt new file mode 100644 index 000000000..9f320d1a0 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/math/VecUtils.kt @@ -0,0 +1,42 @@ +package com.lambda.util.math + +import com.lambda.context.SafeContext +import com.lambda.util.math.MathUtils.sq +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 net.minecraft.util.math.Vec3i +import java.util.* + +object VecUtils { + infix fun Vec3d.dist(other: Vec3d) = + this.distanceTo(other) + + infix fun Vec3d.distSq(other: Vec3d) = + this.squaredDistanceTo(other) + + infix fun Vec3i.distSq(other: Vec3d) = + Vec3d.of(this) distSq other + + infix fun Vec3i.distSq(other: Vec3i) = + (this.x - other.x).sq + (this.y - other.y).sq + (this.z - other.z).sq + + infix operator fun Vec3d.plus(other: Vec3d) = + this.add(other) + + infix operator fun Vec3d.minus(other: Vec3d) = + this.subtract(other) + + infix operator fun Vec3d.times(other: Vec3d) = + this.multiply(other) + + infix operator fun Vec3d.div(other: Vec3d) = + this.multiply(1.0 / other.x, 1.0 / other.y, 1.0 / other.z) + + infix operator fun Vec3d.times(other: Double) = + this.multiply(other) + + infix operator fun Vec3d.div(other: Double) = + this.multiply(1.0 / other) +} diff --git a/common/src/main/kotlin/com/lambda/util/player/MovementUtils.kt b/common/src/main/kotlin/com/lambda/util/player/MovementUtils.kt new file mode 100644 index 000000000..7d2542469 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/player/MovementUtils.kt @@ -0,0 +1,78 @@ +package com.lambda.util.player + +import com.lambda.context.SafeContext +import com.lambda.interaction.RotationManager +import com.lambda.util.math.MathUtils.random +import com.lambda.util.math.MathUtils.toInt +import com.lambda.util.math.MathUtils.toRadian +import net.minecraft.client.input.Input +import net.minecraft.client.network.ClientPlayerEntity +import net.minecraft.entity.Entity +import net.minecraft.util.math.Vec3d +import kotlin.math.cos +import kotlin.math.hypot +import kotlin.math.sign +import kotlin.math.sin + +object MovementUtils { + private val SafeContext.roundedForward get() = sign(player.input.movementForward) + private val SafeContext.roundedStrafing get() = sign(player.input.movementSideways) + + fun Input.cancel() { + movementForward = 0f + movementSideways = 0f + + pressingForward = false + pressingBack = false + pressingLeft = false + pressingRight = false + } + + val SafeContext.isInputting: Boolean get() = + roundedForward != 0f || roundedStrafing != 0f + + val SafeContext.verticalMovement get() = + player.input.jumping.toInt() - player.input.sneaking.toInt() + + fun SafeContext.calcMoveYaw(yawIn: Float = player.moveYaw, moveForward: Float = roundedForward, moveStrafe: Float = roundedStrafing): Double { + var strafe = 90 * moveStrafe + strafe *= if (moveForward != 0F) moveForward * 0.5F else 1F + + var yaw = yawIn - strafe + yaw -= if (moveForward < 0F) 180 else 0 + + return yaw.toDouble() + } + + fun SafeContext.calcMoveRad() = calcMoveYaw().toRadian() + + fun randomDirection() = random(-180.0, 180.0).toRadian() + + fun SafeContext.movementDirection(radDir: Double = calcMoveRad()) = + Vec3d(-sin(radDir), 0.0, cos(radDir)) + + var Entity.motionX get() = velocity.x; set(value) = setVelocity(value, velocity.y, velocity.z) + var Entity.motionY get() = velocity.y; set(value) = setVelocity(velocity.x, value, velocity.z) + var Entity.motionZ get() = velocity.z; set(value) = setVelocity(velocity.x, velocity.y, value) + + fun SafeContext.setSpeed(speed: Double, direction: Double = calcMoveRad()) { + player.motionX = -sin(direction) * speed + player.motionZ = cos(direction) * speed + } + + fun SafeContext.addSpeed(speed: Double, direction: Double = calcMoveRad()) { + player.motionX -= sin(direction) * speed + player.motionZ += cos(direction) * speed + } + + fun SafeContext.mulSpeed(modifier: Double) { + player.motionX *= modifier + player.motionZ *= modifier + } + + val ClientPlayerEntity.moveYaw get() = RotationManager.movementYaw ?: yaw + + 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.motionDelta get() = hypot(this.velocity.x, this.velocity.z) +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/util/primitives/extension/Components.kt b/common/src/main/kotlin/com/lambda/util/primitives/extension/Components.kt new file mode 100644 index 000000000..c0bfea405 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/primitives/extension/Components.kt @@ -0,0 +1,11 @@ +package com.lambda.util.primitives.extension + +import net.minecraft.util.math.Vec2f +import net.minecraft.util.math.Vec3d + +operator fun Vec2f.component1() = this.x +operator fun Vec2f.component2() = this.y +operator fun Vec3d.component1() = this.x +operator fun Vec3d.component2() = this.y +operator fun Vec3d.component3() = this.z +operator fun DoubleArray.component6() = this[5] \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/util/primitives/extension/Entity.kt b/common/src/main/kotlin/com/lambda/util/primitives/extension/Entity.kt new file mode 100644 index 000000000..32fd32bfb --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/primitives/extension/Entity.kt @@ -0,0 +1,24 @@ +package com.lambda.util.primitives.extension + +import com.lambda.util.math.MathUtils.lerp +import net.minecraft.client.MinecraftClient +import net.minecraft.entity.Entity +import net.minecraft.util.math.Box +import net.minecraft.util.math.Vec3d + +val Entity.prevPos + get() = Vec3d(prevX, prevY, prevZ) + +val Entity.interpolatedPos + get() = lerp(prevPos, pos, MinecraftClient.getInstance().partialTicks) + +val Entity.interpolatedBox: Box + get() { + val box = boundingBox + val xw = (box.maxX - box.minX) * 0.5 + val yw = box.maxY - box.minY + val zw = (box.maxZ - box.minZ) * 0.5 + + val pos = interpolatedPos + return Box(pos.x - xw, pos.y, pos.z - zw, pos.x + xw, pos.y + yw, pos.z + zw) + } \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/util/primitives/extension/Mixin.kt b/common/src/main/kotlin/com/lambda/util/primitives/extension/Mixin.kt new file mode 100644 index 000000000..7bf7e675d --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/primitives/extension/Mixin.kt @@ -0,0 +1,6 @@ +package com.lambda.util.primitives.extension + +import net.minecraft.client.MinecraftClient + +val MinecraftClient.partialTicks + get() = (if (paused) pausedTickDelta else tickDelta).toDouble() \ No newline at end of file diff --git a/common/src/main/kotlin/com/lambda/util/world/EntityUtils.kt b/common/src/main/kotlin/com/lambda/util/world/EntityUtils.kt index 2732b1787..aba0bc729 100644 --- a/common/src/main/kotlin/com/lambda/util/world/EntityUtils.kt +++ b/common/src/main/kotlin/com/lambda/util/world/EntityUtils.kt @@ -44,7 +44,7 @@ object EntityUtils { // Here we iterate over all sections within the specified distance and add all entities of type [T] to the list. // We do not have to worry about performance here, as the number of sections is very limited. - // For example if the player is on the edge of a section and the distance is 16, we only have to iterate over 9 sections. + // For example, if the player is on the edge of a section and the distance is 16, we only have to iterate over 9 sections. for (x in sectionX - chunks..sectionX + chunks) { for (y in sectionY - chunks..sectionY + chunks) { for (z in sectionZ - chunks..sectionZ + chunks) { diff --git a/common/src/main/kotlin/com/lambda/util/world/raycast/RayCastMask.kt b/common/src/main/kotlin/com/lambda/util/world/raycast/RayCastMask.kt new file mode 100644 index 000000000..2f5ea39df --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/world/raycast/RayCastMask.kt @@ -0,0 +1,7 @@ +package com.lambda.util.world.raycast + +enum class RayCastMask(val block: Boolean, val entity: Boolean) { + BOTH(true, true), + BLOCK(true, false), + ENTITY(false, true) +} \ 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 new file mode 100644 index 000000000..e4f3d61c8 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/world/raycast/RayCastUtils.kt @@ -0,0 +1,67 @@ +package com.lambda.util.world.raycast + +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.entity.Entity +import net.minecraft.entity.projectile.ProjectileUtil +import net.minecraft.util.hit.BlockHitResult +import net.minecraft.util.hit.EntityHitResult +import net.minecraft.util.hit.HitResult +import net.minecraft.util.math.Vec3d +import net.minecraft.world.RaycastContext +import kotlin.math.max + +object RayCastUtils { + private val entityPredicate = { entity: Entity -> !entity.isSpectator && entity.canHit() } + + fun SafeContext.rayCast( + pos: Vec3d, + dir: Vec3d, + reach: Double, + mask: RayCastMask, + fluids: Boolean = false + ): HitResult? { + val vec = dir.multiply(reach) + val point = pos.add(vec) + + val block = run { + if (!mask.block) return@run null + + val fluidHandling = if (fluids) RaycastContext.FluidHandling.ANY else RaycastContext.FluidHandling.NONE + val context = RaycastContext(pos, point, RaycastContext.ShapeType.OUTLINE, fluidHandling, player) + val block = world.raycast(context) + + block?.blockResult + } + + val entity = run { + if (!mask.entity) return@run null + + val box = player.boundingBox.stretch(vec).expand(1.0) + val entity = ProjectileUtil.raycast(player, pos, point, box, entityPredicate, reach * reach) + + entity?.entityResult + } + + return listOfNotNull(block, entity).minByOrNull { pos distSq it.pos } + } + + fun distanceToGround(maxDist: Double = 100.0) = runSafe { + val pos = player.pos.add(0.0, 0.1, 0.0) + val cast = Rotation.DOWN.rayCast(maxDist, RayCastMask.BLOCK, pos, false) ?: return@runSafe maxDist + + return@runSafe max(0.0, pos.y - cast.pos.y) + } + + val HitResult.entityResult: EntityHitResult? get() { + if (type == HitResult.Type.MISS) return null + return this as? EntityHitResult + } + + val HitResult.blockResult: BlockHitResult? get() { + if (type == HitResult.Type.MISS) return null + return this as? BlockHitResult + } +} \ No newline at end of file diff --git a/common/src/main/resources/lambda.accesswidener b/common/src/main/resources/lambda.accesswidener index e224d5616..dc5a5cd89 100644 --- a/common/src/main/resources/lambda.accesswidener +++ b/common/src/main/resources/lambda.accesswidener @@ -2,6 +2,8 @@ accessWidener v2 named # MC accessible field net/minecraft/client/MinecraftClient itemUseCooldown I accessible field net/minecraft/client/MinecraftClient attackCooldown I +accessible field net/minecraft/client/MinecraftClient paused Z +accessible field net/minecraft/client/MinecraftClient pausedTickDelta F # World accessible field net/minecraft/client/world/ClientWorld entityManager Lnet/minecraft/client/world/ClientEntityManager; diff --git a/common/src/main/resources/lambda.mixins.common.json b/common/src/main/resources/lambda.mixins.common.json index d8d90833f..29732afc6 100644 --- a/common/src/main/resources/lambda.mixins.common.json +++ b/common/src/main/resources/lambda.mixins.common.json @@ -7,10 +7,15 @@ "ChatInputSuggestorMixin", "ChatScreenMixin", "ClientConnectionMixin", + "DebugHudMixin", + "GameRendererMixin", + "KeyBindingMixin", "KeyboardMixin", "MinecraftClientMixin", - "KeyBindingMixin", - "PlayerEntityMixin" + "PlayerEntityMixin", + "entity.ClientPlayerEntityMixin", + "entity.EntityMixin", + "entity.LivingEntityMixin" ], "injectors": { "defaultRequire": 1