From 5d29ed9a95521724d0ac028dedf94d34d3e6a1ef Mon Sep 17 00:00:00 2001 From: superblaubeere27 Date: Wed, 18 Dec 2024 01:30:45 +0100 Subject: [PATCH] feat: improved aiming (#4952) * Basic implementation * First acceptable implementation * Improvement for snowball * Latest code revision * Latest changes * Add DroneControl proof of work * Refactoring... --- config/detekt/detekt.yml | 2 +- .../MixinClientPlayerInteractionManager.java | 2 +- .../mixins/minecraft/render/MixinCamera.java | 11 + .../minecraft/render/MixinGameRenderer.java | 13 + .../liquidbounce/event/EventManager.kt | 1 + .../liquidbounce/event/events/WorldEvents.kt | 1 + .../features/module/ModuleManager.kt | 3 + .../module/modules/combat/ModuleAutoBow.kt | 472 ------------------ .../combat/aimbot/DroneControlScreen.kt | 203 ++++++++ .../modules/combat/aimbot/ModuleAutoBow.kt | 60 +++ .../combat/aimbot/ModuleDroneControl.kt | 69 +++ .../combat/aimbot/ModuleProjectileAimbot.kt | 50 ++ .../aimbot/autobow/AutoBowAimbotFeature.kt | 90 ++++ .../aimbot/autobow/AutoBowAutoShootFeature.kt | 147 ++++++ .../autobow/AutoBowFastChargeFeature.kt | 59 +++ .../module/modules/misc/ModuleAutoPearl.kt | 45 +- .../module/modules/player/ModuleSmartEat.kt | 6 + .../module/modules/render/ModuleDebug.kt | 13 +- .../liquidbounce/utils/aiming/PointFinding.kt | 214 ++++++++ .../utils/aiming/RotationFinding.kt | 25 +- .../utils/aiming/RotationsUtil.kt | 10 +- .../CydhranianProjectileAngleCalculator.kt | 168 +++++++ .../PolynomialProjectileAngleCalculator.kt | 57 +++ .../projectiles/ProjectileAngleCalculator.kt | 44 ++ .../ProjectileTargetPointFinder.kt | 43 ++ .../SituationalProjectileAngleCalculator.kt | 29 ++ .../liquidbounce/utils/block/ChunkScanner.kt | 2 +- .../utils/block/ImmutableBlockPos.kt | 8 + .../liquidbounce/utils/block/Region.kt | 16 + .../FaceTargetPositionFactory.kt | 34 +- .../block/targetfinding/TargetFinding.kt | 4 +- .../utils/client/MathExtensions.kt | 16 +- .../utils/entity/PositionExtrapolation.kt | 56 +++ .../ccbluex/liquidbounce/utils/math/Bisect.kt | 35 ++ .../math/geometry/{Face.kt => AlignedFace.kt} | 16 +- .../liquidbounce/utils/math/geometry/Line.kt | 9 + .../utils/math/geometry/PlaneSection.kt | 31 ++ .../utils/render/WorldToScreen.kt | 32 +- .../utils/render/trajectory/TrajectoryData.kt | 41 +- .../utils/aiming/PointFindingKtTest.kt | 34 ++ 40 files changed, 1591 insertions(+), 580 deletions(-) delete mode 100644 src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/ModuleAutoBow.kt create mode 100644 src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/DroneControlScreen.kt create mode 100644 src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/ModuleAutoBow.kt create mode 100644 src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/ModuleDroneControl.kt create mode 100644 src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/ModuleProjectileAimbot.kt create mode 100644 src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/autobow/AutoBowAimbotFeature.kt create mode 100644 src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/autobow/AutoBowAutoShootFeature.kt create mode 100644 src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/autobow/AutoBowFastChargeFeature.kt create mode 100644 src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/PointFinding.kt create mode 100644 src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/projectiles/CydhranianProjectileAngleCalculator.kt create mode 100644 src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/projectiles/PolynomialProjectileAngleCalculator.kt create mode 100644 src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/projectiles/ProjectileAngleCalculator.kt create mode 100644 src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/projectiles/ProjectileTargetPointFinder.kt create mode 100644 src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/projectiles/SituationalProjectileAngleCalculator.kt create mode 100644 src/main/kotlin/net/ccbluex/liquidbounce/utils/block/ImmutableBlockPos.kt create mode 100644 src/main/kotlin/net/ccbluex/liquidbounce/utils/entity/PositionExtrapolation.kt create mode 100644 src/main/kotlin/net/ccbluex/liquidbounce/utils/math/Bisect.kt rename src/main/kotlin/net/ccbluex/liquidbounce/utils/math/geometry/{Face.kt => AlignedFace.kt} (94%) create mode 100644 src/main/kotlin/net/ccbluex/liquidbounce/utils/math/geometry/PlaneSection.kt create mode 100644 src/test/kotlin/net/ccbluex/liquidbounce/utils/aiming/PointFindingKtTest.kt diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml index b41af4abc6c..b61de8568af 100644 --- a/config/detekt/detekt.yml +++ b/config/detekt/detekt.yml @@ -49,7 +49,7 @@ style: ForbiddenComment: active: false UnusedPrivateProperty: - allowedNames: '_|ignored|expected|serialVersionUID|.+Handler' + allowedNames: '_|ignored|expected|serialVersionUID|.+Handler\d*|repeatable' ForbiddenMethodCall: active: true methods: [ 'kotlin.io.print', 'kotlin.io.println' ] diff --git a/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/network/MixinClientPlayerInteractionManager.java b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/network/MixinClientPlayerInteractionManager.java index bcc009435f6..9127cd7b875 100644 --- a/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/network/MixinClientPlayerInteractionManager.java +++ b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/network/MixinClientPlayerInteractionManager.java @@ -21,7 +21,7 @@ import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import net.ccbluex.liquidbounce.event.EventManager; import net.ccbluex.liquidbounce.event.events.*; -import net.ccbluex.liquidbounce.features.module.modules.combat.ModuleAutoBow; +import net.ccbluex.liquidbounce.features.module.modules.combat.aimbot.ModuleAutoBow; import net.ccbluex.liquidbounce.features.module.modules.combat.ModuleAutoClicker; import net.ccbluex.liquidbounce.utils.client.SilentHotbar; import net.minecraft.client.network.ClientPlayerInteractionManager; diff --git a/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/render/MixinCamera.java b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/render/MixinCamera.java index 33c7e680ba4..340e28bb3d7 100644 --- a/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/render/MixinCamera.java +++ b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/render/MixinCamera.java @@ -20,12 +20,14 @@ import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import net.ccbluex.liquidbounce.features.module.modules.render.*; +import net.ccbluex.liquidbounce.features.module.modules.combat.aimbot.ModuleDroneControl; import net.ccbluex.liquidbounce.utils.aiming.AimPlan; import net.ccbluex.liquidbounce.utils.aiming.RotationManager; import net.minecraft.client.render.Camera; import net.minecraft.entity.Entity; import net.minecraft.entity.LivingEntity; import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.BlockView; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -54,6 +56,9 @@ public abstract class MixinCamera { @Shadow protected abstract void moveBy(float f, float g, float h); + @Shadow + public abstract void setPos(Vec3d pos); + @Inject(method = "update", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/Camera;setPos(DDD)V", shift = At.Shift.AFTER), cancellable = true) private void modifyCameraOrientation(BlockView area, Entity focusedEntity, boolean thirdPerson, boolean inverseView, float tickDelta, CallbackInfo ci) { var freeLook = ModuleFreeLook.INSTANCE.getRunning(); @@ -81,6 +86,12 @@ private void modifyCameraOrientation(BlockView area, Entity focusedEntity, boole ci.cancel(); return; } + var screen = ModuleDroneControl.INSTANCE.getScreen(); + + if (screen != null) { + this.setPos(screen.getCameraPos()); + this.setRotation(screen.getCameraRotation().x, screen.getCameraRotation().y); + } AimPlan aimPlan = RotationManager.INSTANCE.getWorkingAimPlan(); diff --git a/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/render/MixinGameRenderer.java b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/render/MixinGameRenderer.java index e7b8cfab46c..052c4095ff8 100644 --- a/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/render/MixinGameRenderer.java +++ b/src/main/java/net/ccbluex/liquidbounce/injection/mixins/minecraft/render/MixinGameRenderer.java @@ -19,6 +19,7 @@ package net.ccbluex.liquidbounce.injection.mixins.minecraft.render; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.llamalad7.mixinextras.injector.ModifyReturnValue; import com.llamalad7.mixinextras.sugar.Local; import com.mojang.blaze3d.systems.RenderSystem; import net.ccbluex.liquidbounce.LiquidBounce; @@ -27,6 +28,7 @@ import net.ccbluex.liquidbounce.event.events.PerspectiveEvent; import net.ccbluex.liquidbounce.event.events.ScreenRenderEvent; import net.ccbluex.liquidbounce.event.events.WorldRenderEvent; +import net.ccbluex.liquidbounce.features.module.modules.combat.aimbot.ModuleDroneControl; import net.ccbluex.liquidbounce.features.module.modules.fun.ModuleDankBobbing; import net.ccbluex.liquidbounce.features.module.modules.render.*; import net.ccbluex.liquidbounce.features.module.modules.world.ModuleLiquidPlace; @@ -330,4 +332,15 @@ private Perspective hookPerspectiveEventOnHand(Perspective original) { return EventManager.INSTANCE.callEvent(new PerspectiveEvent(original)).getPerspective(); } + @ModifyReturnValue(method = "getFov", at = @At("RETURN")) + private double injectShit(double original) { + var screen = ModuleDroneControl.INSTANCE.getScreen(); + + if (screen != null) { + return Math.min(120.0, original / screen.getZoomFactor()); + } + + return original; + } + } diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/event/EventManager.kt b/src/main/kotlin/net/ccbluex/liquidbounce/event/EventManager.kt index 493ccaf53bd..6f0ee0edc64 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/event/EventManager.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/event/EventManager.kt @@ -22,6 +22,7 @@ import net.ccbluex.liquidbounce.event.events.* import net.ccbluex.liquidbounce.utils.client.EventScheduler import net.ccbluex.liquidbounce.utils.client.logger import net.ccbluex.liquidbounce.utils.kotlin.sortedInsert +import net.minecraft.client.MinecraftClient import java.util.concurrent.CopyOnWriteArrayList import kotlin.reflect.KClass diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/event/events/WorldEvents.kt b/src/main/kotlin/net/ccbluex/liquidbounce/event/events/WorldEvents.kt index d39dfbe4368..2bb099fa809 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/event/events/WorldEvents.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/event/events/WorldEvents.kt @@ -29,6 +29,7 @@ import net.minecraft.client.world.ClientWorld import net.minecraft.entity.EquipmentSlot import net.minecraft.entity.player.PlayerEntity import net.minecraft.item.ItemStack +import net.minecraft.network.packet.s2c.play.ChunkDeltaUpdateS2CPacket import net.minecraft.util.math.BlockPos import net.minecraft.util.shape.VoxelShape diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/ModuleManager.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/ModuleManager.kt index 86ab7dfa979..e708ab0570c 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/ModuleManager.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/ModuleManager.kt @@ -30,6 +30,9 @@ import net.ccbluex.liquidbounce.features.module.modules.client.ModuleLiquidChat import net.ccbluex.liquidbounce.features.module.modules.client.ModuleRichPresence import net.ccbluex.liquidbounce.features.module.modules.client.ModuleTargets import net.ccbluex.liquidbounce.features.module.modules.combat.* +import net.ccbluex.liquidbounce.features.module.modules.combat.aimbot.ModuleAutoBow +import net.ccbluex.liquidbounce.features.module.modules.combat.aimbot.ModuleDroneControl +import net.ccbluex.liquidbounce.features.module.modules.combat.aimbot.ModuleProjectileAimbot import net.ccbluex.liquidbounce.features.module.modules.combat.autoarmor.ModuleAutoArmor import net.ccbluex.liquidbounce.features.module.modules.combat.criticals.ModuleCriticals import net.ccbluex.liquidbounce.features.module.modules.combat.crystalaura.ModuleCrystalAura diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/ModuleAutoBow.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/ModuleAutoBow.kt deleted file mode 100644 index bff09b1cf17..00000000000 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/ModuleAutoBow.kt +++ /dev/null @@ -1,472 +0,0 @@ -/* - * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce) - * - * Copyright (c) 2015 - 2024 CCBlueX - * - * LiquidBounce is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * LiquidBounce is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with LiquidBounce. If not, see . - */ -package net.ccbluex.liquidbounce.features.module.modules.combat - -import net.ccbluex.liquidbounce.config.types.ToggleableConfigurable -import net.ccbluex.liquidbounce.event.events.GameTickEvent -import net.ccbluex.liquidbounce.event.events.OverlayRenderEvent -import net.ccbluex.liquidbounce.event.handler -import net.ccbluex.liquidbounce.event.tickHandler -import net.ccbluex.liquidbounce.features.module.Category -import net.ccbluex.liquidbounce.features.module.ClientModule -import net.ccbluex.liquidbounce.render.renderEnvironmentForGUI -import net.ccbluex.liquidbounce.utils.aiming.Rotation -import net.ccbluex.liquidbounce.utils.aiming.RotationManager -import net.ccbluex.liquidbounce.utils.aiming.RotationsConfigurable -import net.ccbluex.liquidbounce.utils.client.Chronometer -import net.ccbluex.liquidbounce.utils.client.MovePacketType -import net.ccbluex.liquidbounce.utils.client.toRadians -import net.ccbluex.liquidbounce.utils.combat.PriorityEnum -import net.ccbluex.liquidbounce.utils.combat.TargetTracker -import net.ccbluex.liquidbounce.utils.combat.shouldBeAttacked -import net.ccbluex.liquidbounce.utils.entity.* -import net.ccbluex.liquidbounce.utils.kotlin.Priority -import net.ccbluex.liquidbounce.utils.math.geometry.Line -import net.ccbluex.liquidbounce.utils.render.OverlayTargetRenderer -import net.minecraft.client.network.AbstractClientPlayerEntity -import net.minecraft.entity.Entity -import net.minecraft.entity.effect.StatusEffects -import net.minecraft.item.BowItem -import net.minecraft.item.TridentItem -import net.minecraft.util.hit.HitResult -import net.minecraft.util.math.Box -import net.minecraft.util.math.MathHelper -import net.minecraft.util.math.Vec3d -import net.minecraft.world.RaycastContext -import java.util.* -import kotlin.math.* - -/** - * AutoBow module - * - * Automatically shoots with your bow when it's fully charged - * + and make it possible to shoot faster - */ -object ModuleAutoBow : ClientModule("AutoBow", Category.COMBAT, aliases = arrayOf("BowAssist", "BowAimbot")) { - const val ACCELERATION = -0.006 - const val REAL_ACCELERATION = -0.005 - - private val random = Random() - - /** - * Keeps track of the last bow shot that has taken place - */ - private val lastShotTimer = Chronometer() - - @JvmStatic - fun onStopUsingItem() { - if (player.activeItem.item is BowItem) { - this.lastShotTimer.reset() - } - } - - /** - * Automatically shoots with your bow when you aim correctly at an enemy or when the bow is fully charged. - */ - private object AutoShootOptions : ToggleableConfigurable(this, "AutoShoot", true) { - - val charged by int("Charged", 15, 3..20, suffix = "ticks") - - val chargedRandom by floatRange("ChargedRandom", - 0.0F..0.0F, - -10.0F..10.0F, - suffix = "ticks" - ) - val delayBetweenShots by float("DelayBetweenShots", 0.0F, 0.0F..5.0F, suffix = "s") - val aimThreshold by float("AimThreshold", 1.5F, 1.0F..4.0F, suffix = "°") - val requiresHypotheticalHit by boolean("RequiresHypotheticalHit", false) - - var currentChargeRandom: Int? = null - - fun updateChargeRandom() { - val lenHalf = (this.chargedRandom.endInclusive - this.chargedRandom.start) / 2.0F - val mid = this.chargedRandom.start + lenHalf - - currentChargeRandom = - (mid + random.nextGaussian() * lenHalf).toInt() - .coerceIn(this.chargedRandom.start.toInt()..this.chargedRandom.endInclusive.toInt()) - } - - fun getChargedRandom(): Int { - if (this.currentChargeRandom == null) { - updateChargeRandom() - } - - return currentChargeRandom!! - } - - @Suppress("unused") - val tickRepeatable = handler { - val currentItem = player.activeItem?.item - - // Should check if player is using bow - if (currentItem !is BowItem && currentItem !is TridentItem) { - return@handler - } - - if (player.itemUseTime < charged + getChargedRandom()) { // Wait until the bow is fully charged - return@handler - } - if (!lastShotTimer.hasElapsed((delayBetweenShots * 1000.0F).toLong())) { - return@handler - } - - if (requiresHypotheticalHit) { - val hypotheticalHit = getHypotheticalHit() - - if (hypotheticalHit == null || !hypotheticalHit.shouldBeAttacked()) { - return@handler - } - } else if (BowAimbotOptions.enabled) { - if (BowAimbotOptions.targetTracker.lockedOnTarget == null) { - return@handler - } - - val targetRotation = RotationManager.workingAimPlan ?: return@handler - - val aimDifference = RotationManager.rotationDifference( - RotationManager.serverRotation, targetRotation.rotation - ) - - if (aimDifference > aimThreshold) { - return@handler - } - } - - interaction.stopUsingItem(player) - updateChargeRandom() - } - - fun getHypotheticalHit(): AbstractClientPlayerEntity? { - val rotation = RotationManager.serverRotation - val yaw = rotation.yaw - val pitch = rotation.pitch - - val velocity = getHypotheticalArrowVelocity(false) - - val vX = -MathHelper.sin(yaw.toRadians()) * MathHelper.cos(pitch.toRadians()) * velocity - val vY = -MathHelper.sin(pitch.toRadians()) * velocity - val vZ = MathHelper.cos(yaw.toRadians()) * MathHelper.cos(pitch.toRadians()) * velocity - - val arrow = SimulatedArrow( - world, - player.eyes, - Vec3d(vX.toDouble(), vY.toDouble(), vZ.toDouble()), - collideEntities = false - ) - - val players = findAndBuildSimulatedPlayers() - - for (i in 0 until 40) { - val lastPos = arrow.pos - - arrow.tick() - - players.forEach { (entity, player) -> - val playerSnapshot = player.getSnapshotAt(i) - - val playerHitBox = - Box(-0.3, 0.0, -0.3, 0.3, 1.8, 0.3) - .expand(0.3) - .offset(playerSnapshot.pos) - - val raycastResult = playerHitBox.raycast(lastPos, arrow.pos) - - raycastResult.orElse(null)?.let { - return entity - } - } - } - - return null - } - - fun findAndBuildSimulatedPlayers(): List> { - return world.players.filter { - it != player && - Line(player.pos, player.rotationVector).squaredDistanceTo(it.pos) < 10.0 * 10.0 - }.map { - Pair(it, PlayerSimulationCache.getSimulationForOtherPlayers(it)) - } - } - } - - /** - * Bow aimbot automatically aims at enemy targets - */ - private object BowAimbotOptions : ToggleableConfigurable(this, "BowAimbot", true) { - - // Target - val targetTracker = TargetTracker(PriorityEnum.DISTANCE) - - // Rotation - val rotationConfigurable = RotationsConfigurable(this) - - val minExpectedPull by int("MinExpectedPull", 5, 0..20, suffix = "ticks") - - init { - tree(targetTracker) - tree(rotationConfigurable) - } - - private val targetRenderer = tree(OverlayTargetRenderer(ModuleAutoBow)) - - @Suppress("unused") - val tickRepeatable = tickHandler { - targetTracker.cleanup() - - // Should check if player is using bow - val activeItem = player.activeItem?.item - if (activeItem !is BowItem && activeItem !is TridentItem) { - return@tickHandler - } - - val eyePos = player.eyes - - var rotation: Rotation? = null - - for (enemy in targetTracker.enemies()) { - val rot = getRotationToTarget(enemy.box.center, eyePos, enemy) ?: continue - - targetTracker.lock(enemy) - rotation = rot - break - } - - if (rotation == null) { - return@tickHandler - } - - RotationManager.aimAt( - rotation, - priority = Priority.IMPORTANT_FOR_USAGE_1, - provider = ModuleAutoBow, - configurable = rotationConfigurable - ) - } - - @Suppress("unused") - val renderHandler = handler { event -> - val target = targetTracker.lockedOnTarget ?: return@handler - - renderEnvironmentForGUI { - targetRenderer.render(this, target, event.tickDelta) - } - } - - } - - private fun getRotationToTarget( - targetPos: Vec3d, - eyePos: Vec3d, - target: Entity, - ): Rotation? { - var deltaPos = targetPos.subtract(eyePos) - val basePrediction = predictBow(deltaPos, FastChargeOptions.enabled) - - val realTravelTime = - getTravelTime( - basePrediction.travelledOnX, - cos(basePrediction.rotation.pitch.toRadians()) * basePrediction.pullProgress * 3.0 * 0.7, - ) - - if (!realTravelTime.isNaN()) { - deltaPos = - deltaPos.add( - (target.x - target.prevX) * realTravelTime, - (target.y - target.prevY) * realTravelTime, - (target.z - target.prevZ) * realTravelTime, - ) - } - - val finalPrediction = predictBow(deltaPos, FastChargeOptions.enabled) - val rotation = finalPrediction.rotation - - if (rotation.yaw.isNaN() || rotation.pitch.isNaN()) { - return null - } - - val pullProgress = finalPrediction.pullProgress - val vertex = getHighestPointOfTrajectory(deltaPos, rotation, pullProgress) - - val positions = mutableListOf() - - positions.add(eyePos) - - if (vertex != null) positions.add(vertex.add(eyePos)) - - positions.add(targetPos) - - for (i in 0 until positions.lastIndex) { - val raycast = - world.raycast( - RaycastContext( - positions[i], - positions[i + 1], - RaycastContext.ShapeType.OUTLINE, - RaycastContext.FluidHandling.ANY, - player, - ), - ) - - if (raycast.type == HitResult.Type.BLOCK) return null - } - - return rotation - } - - private fun getHighestPointOfTrajectory( - deltaPos: Vec3d, - rotation: Rotation, - pullProgress: Float, - ): Vec3d? { - val v0 = pullProgress * 3.0F * 0.7f - - val v_x = cos(Math.toRadians(rotation.pitch.toDouble())) * v0 - val v_y = sin(Math.toRadians(rotation.pitch.toDouble())) * v0 - - val maxX = -(v_x * v_y) / (2.0 * ACCELERATION) // -(v_x*v_y)/a - - val maxY = -(v_y * v_y) / (4.0 * ACCELERATION) // -v_y^2/(2*a) - - val xPitch = cos(Math.toRadians(rotation.yaw.toDouble() - 90.0F)) - val zPitch = sin(Math.toRadians(rotation.yaw.toDouble() - 90.0F)) - - val vertex = - if (maxX < 0 && maxX * maxX < deltaPos.x * deltaPos.x + deltaPos.z * deltaPos.z) { - Vec3d( - xPitch * maxX, - maxY, - zPitch * maxX, - ) - } else { - null - } - return vertex - } - - fun getTravelTime( - dist: Double, - v0: Double, - ): Float { - return log((v0 / (ln(0.99)) + dist) / (v0 / (ln(0.99))), 0.99).toFloat() - } - - override fun disable() { - BowAimbotOptions.targetTracker.cleanup() - } - - @Suppress("MaxLineLength") - fun predictBow( - target: Vec3d, - assumeElongated: Boolean, - ): BowPredictionResult { - val travelledOnX = sqrt(target.x * target.x + target.z * target.z) - - val velocity = getHypotheticalArrowVelocity(assumeElongated) - - val yaw = (atan2(target.z, target.x) * 180.0f / Math.PI).toFloat() - 90.0f - val pitch = (-Math.toDegrees(atan( - (velocity * velocity - sqrt( - velocity * velocity * velocity * velocity - 0.006f * (0.006f * (travelledOnX * travelledOnX) + 2 - * target.y * (velocity * velocity))) - ) / (0.006f * travelledOnX), - ))).toFloat() - - return BowPredictionResult( - Rotation(yaw, pitch), - velocity, - travelledOnX, - ) - } - - private fun getHypotheticalArrowVelocity( - assumeElongated: Boolean, - ): Float { - var velocity: Float = - if (assumeElongated) - 1.0F - else - player.itemUseTime.coerceAtLeast(BowAimbotOptions.minExpectedPull) / 20.0F - - velocity = (velocity * velocity + velocity * 2.0f) / 3.0f - - if (velocity > 1.0f) { - velocity = 1f - } - return velocity - } - - class BowPredictionResult(val rotation: Rotation, val pullProgress: Float, val travelledOnX: Double) - - /** - * @desc Fast charge options (like FastBow) can be used to charge the bow faster. - * @warning Should only be used on vanilla minecraft. Most anti cheats patch these kinds of exploits - * - * TODO: Add version specific options - */ - private object FastChargeOptions : ToggleableConfigurable(this, "FastCharge", false) { - - private val speed by int("Speed", 20, 3..20) - - private val notInTheAir by boolean("NotInTheAir", true) - private val notDuringMove by boolean("NotDuringMove", false) - private val notDuringRegeneration by boolean("NotDuringRegeneration", false) - - private val packetType by enumChoice("PacketType", MovePacketType.FULL) - - @Suppress("unused") - val tickRepeatable = tickHandler { - val currentItem = player.activeItem - - // Should accelerated game ticks when using bow - if (currentItem?.item is BowItem) { - if (notInTheAir && !player.isOnGround) { - return@tickHandler - } - - if (notDuringMove && player.moving) { - return@tickHandler - } - - if (notDuringRegeneration && player.hasStatusEffect(StatusEffects.REGENERATION)) { - return@tickHandler - } - - repeat(speed) { - if (!player.isUsingItem) { - return@repeat - } - - // Accelerate ticks (MC 1.8) - network.sendPacket(packetType.generatePacket()) - - // Just show visual effect (not required to work - but looks better) - player.tickActiveItemStack() - } - } - } - } - - init { - tree(AutoShootOptions) - tree(BowAimbotOptions) - tree(FastChargeOptions) - } -} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/DroneControlScreen.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/DroneControlScreen.kt new file mode 100644 index 00000000000..1783f6b521d --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/DroneControlScreen.kt @@ -0,0 +1,203 @@ +package net.ccbluex.liquidbounce.features.module.modules.combat.aimbot + +import net.ccbluex.liquidbounce.features.module.modules.render.ModuleDebug +import net.ccbluex.liquidbounce.render.engine.Color4b +import net.ccbluex.liquidbounce.utils.aiming.RotationManager +import net.ccbluex.liquidbounce.utils.client.asText +import net.ccbluex.liquidbounce.utils.client.mc +import net.ccbluex.liquidbounce.utils.client.player +import net.ccbluex.liquidbounce.utils.client.toDegrees +import net.ccbluex.liquidbounce.utils.entity.box +import net.ccbluex.liquidbounce.utils.math.geometry.NormalizedPlane +import net.ccbluex.liquidbounce.utils.math.plus +import net.ccbluex.liquidbounce.utils.render.WorldToScreen +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.gui.screen.Screen +import net.minecraft.entity.Entity +import net.minecraft.entity.projectile.ProjectileUtil +import net.minecraft.util.math.Box +import net.minecraft.util.math.MathHelper +import net.minecraft.util.math.Vec2f +import net.minecraft.util.math.Vec3d +import org.joml.Vector2d +import org.lwjgl.glfw.GLFW +import java.lang.Math.pow +import kotlin.math.hypot +import kotlin.math.log + +private const val DRAG_BUTTON = 0 + +/** + * Zoom by another 25% every mouse tick. + */ +private const val ZOOM_STEP_BASE = 1.25 + +@Suppress("detekt.TooManyFunctions") +class DroneControlScreen : Screen("BowAimbot Control Panel".asText()) { + + var cameraPos = player.eyePos.add(0.0, 10.0, 0.0) + var cameraRotation = Vec2f(MathHelper.wrapDegrees(player.yaw), player.pitch.coerceIn(-90.0F, 90.0F)) + + private var focusedEntity: EntityFocusData? = null + + private var dragStartPos: Vector2d? = null + private var dragStartRottion: Vec2f = Vec2f(0.0F, 0.0F) + + private var zoomSteps = 0.0 + + fun getZoomFactor(): Double { + return pow(ZOOM_STEP_BASE, zoomSteps) + } + + override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean { + val dragStart = this.dragStartPos + + if (button != DRAG_BUTTON || dragStart == null) { + return false + } + + val prevWorldRay = WorldToScreen.calculateMouseRay(Vec2f(mouseX.toFloat(), mouseY.toFloat())) + val newWorldRay = WorldToScreen.calculateMouseRay(Vec2f(dragStart.x.toFloat(), dragStart.y.toFloat())) + + val yawDelta = Vector2d(newWorldRay.direction.x, newWorldRay.direction.z).angle( + Vector2d( + prevWorldRay.direction.x, + prevWorldRay.direction.z + ) + ).toFloat().toDegrees() + + val pitchDelta = + Vector2d(newWorldRay.direction.y, hypot(newWorldRay.direction.x, newWorldRay.direction.z)).angle( + Vector2d( + prevWorldRay.direction.y, + hypot(prevWorldRay.direction.x, prevWorldRay.direction.z) + ) + ).toFloat().toDegrees() + + this.cameraRotation = this.dragStartRottion.add(Vec2f(-yawDelta, -pitchDelta)) + + return true + } + + override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { + if (keyCode == GLFW.GLFW_KEY_SPACE) { + ModuleDroneControl.mayShoot = true + } + + return super.keyPressed(keyCode, scanCode, modifiers) + } + + override fun keyReleased(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { + return super.keyReleased(keyCode, scanCode, modifiers) + } + + override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { + if (button == DRAG_BUTTON) { + this.dragStartPos = Vector2d(mouseX, mouseY) + this.dragStartRottion = this.cameraRotation + } + + return true + } + + override fun mouseScrolled( + mouseX: Double, + mouseY: Double, + horizontalAmount: Double, + verticalAmount: Double + ): Boolean { + this.zoomSteps += verticalAmount + + return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount) + } + + override fun mouseMoved(mouseX: Double, mouseY: Double) { + val focusedEntity = this.focusedEntity + + if (mc.options.sneakKey.isPressed && focusedEntity != null) { + val rot = RotationManager.makeRotation(focusedEntity.entity.box.center, this.cameraPos) + + this.cameraRotation = Vec2f(rot.yaw, rot.pitch) + } + } + + override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean { + if (button == DRAG_BUTTON) { + this.dragStartPos = null + } + + if (button != 1) { + return true + } + + val line = WorldToScreen.calculateMouseRay(Vec2f(mouseX.toFloat(), mouseY.toFloat())) + + val startPos = line.position + val endPos = startPos + line.direction.normalize().multiply(10000.0) + + val target = ProjectileUtil.raycast( + player, + startPos, + endPos, + Box.of(startPos, 0.1, 0.1, 0.1).stretch(line.direction.normalize().multiply(10000.0)), + { true }, + 10000.0 + ) + + this.focusedEntity = target?.let { EntityFocusData(it.entity, it.pos.y, it.pos.y - it.entity.pos.y) } + + return super.mouseReleased(mouseX, mouseY, button) + } + + override fun render(context: DrawContext?, mouseX: Int, mouseY: Int, delta: Float) { + ModuleDroneControl.currentTarget = null + + this.focusedEntity?.let { + ModuleDebug.debugGeometry( + ModuleProjectileAimbot, + "focusEntity", + ModuleDebug.DebuggedBox(it.entity.box, Color4b.RED.alpha(127)) + ) + + val plane = NormalizedPlane(Vec3d(0.0, it.baseY, 0.0), Vec3d(0.0, 1.0, 0.0)) + val intersect = plane.intersection( + WorldToScreen.calculateMouseRay( + Vec2f(mouseX.toFloat(), mouseY.toFloat()), + cameraPos = this.cameraPos + ) + )!! + + val entityPos = intersect.subtract(0.0, it.hitBoxOffsetY, 0.0) + + ModuleDroneControl.currentTarget = it.entity to entityPos + + ModuleDebug.debugGeometry( + ModuleProjectileAimbot, + "focusEntity", + ModuleDebug.DebuggedBox( + it.entity.dimensions.getBoxAt(entityPos), + Color4b.RED.alpha(127) + ) + ) + } + } + + @Suppress("detekt:EmptyFunctionBlock") + override fun renderBackground(context: DrawContext?, mouseX: Int, mouseY: Int, delta: Float) { + + } + + override fun close() { + ModuleDroneControl.enabled = false + } + + override fun shouldCloseOnEsc(): Boolean { + return true + } + + override fun shouldPause(): Boolean { + return false + } + + class EntityFocusData(val entity: Entity, val baseY: Double, val hitBoxOffsetY: Double) +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/ModuleAutoBow.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/ModuleAutoBow.kt new file mode 100644 index 00000000000..255277fe28d --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/ModuleAutoBow.kt @@ -0,0 +1,60 @@ +/* + * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce) + * + * Copyright (c) 2015 - 2024 CCBlueX + * + * LiquidBounce is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiquidBounce is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LiquidBounce. If not, see . + */ +package net.ccbluex.liquidbounce.features.module.modules.combat.aimbot + +import net.ccbluex.liquidbounce.features.module.Category +import net.ccbluex.liquidbounce.features.module.ClientModule +import net.ccbluex.liquidbounce.features.module.modules.combat.aimbot.autobow.AutoBowAimbotFeature +import net.ccbluex.liquidbounce.features.module.modules.combat.aimbot.autobow.AutoBowAutoShootFeature +import net.ccbluex.liquidbounce.features.module.modules.combat.aimbot.autobow.AutoBowFastChargeFeature +import net.ccbluex.liquidbounce.utils.client.Chronometer +import net.minecraft.item.BowItem +import java.util.* + +/** + * AutoBow module + * + * Automatically shoots with your bow when it's fully charged + * + and make it possible to shoot faster + */ +object ModuleAutoBow : ClientModule("AutoBow", Category.COMBAT, aliases = arrayOf("BowAssist", "BowAimbot")) { + val random = Random() + + /** + * Keeps track of the last bow shot that has taken place + */ + val lastShotTimer = Chronometer() + + @JvmStatic + fun onStopUsingItem() { + if (player.activeItem.item is BowItem) { + lastShotTimer.reset() + } + } + + override fun disable() { + AutoBowAimbotFeature.targetTracker.cleanup() + } + + init { + tree(AutoBowAutoShootFeature) + tree(AutoBowAimbotFeature) + tree(AutoBowFastChargeFeature) + } +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/ModuleDroneControl.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/ModuleDroneControl.kt new file mode 100644 index 00000000000..f417b6b4ecc --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/ModuleDroneControl.kt @@ -0,0 +1,69 @@ +package net.ccbluex.liquidbounce.features.module.modules.combat.aimbot + +import net.ccbluex.liquidbounce.event.tickHandler +import net.ccbluex.liquidbounce.features.module.Category +import net.ccbluex.liquidbounce.features.module.ClientModule +import net.ccbluex.liquidbounce.utils.aiming.RotationManager +import net.ccbluex.liquidbounce.utils.aiming.RotationsConfigurable +import net.ccbluex.liquidbounce.utils.aiming.projectiles.SituationalProjectileAngleCalculator +import net.ccbluex.liquidbounce.utils.entity.ConstantPositionExtrapolation +import net.ccbluex.liquidbounce.utils.kotlin.Priority +import net.ccbluex.liquidbounce.utils.render.trajectory.TrajectoryInfo +import net.minecraft.client.option.KeyBinding +import net.minecraft.entity.Entity +import net.minecraft.util.Hand +import net.minecraft.util.math.Vec3d +import org.lwjgl.glfw.GLFW + +object ModuleDroneControl : ClientModule("DroneControl", Category.COMBAT) { + + private val rotationsConfigurable = tree(RotationsConfigurable(this)) + + var screen: DroneControlScreen? = null + + override fun enable() { + screen = DroneControlScreen() + + mc.setScreen(screen) + } + + override fun disable() { + if (mc.currentScreen == screen) { + mc.setScreen(null) + } + + screen = null + } + + var currentTarget: Pair? = null + var mayShoot = false + + private val repeatable = tickHandler { + val currentRotation = currentTarget?.let { (entity, pos) -> + SituationalProjectileAngleCalculator.calculateAngleFor( + TrajectoryInfo.BOW_FULL_PULL, + sourcePos = player.eyePos, + targetPosFunction = ConstantPositionExtrapolation(pos), + targetShape = entity.dimensions + ) + } + + if (currentRotation != null) { + RotationManager.aimAt( + rotation = currentRotation, + configurable = rotationsConfigurable, + priority = Priority.NORMAL, + provider = ModuleDroneControl + ) + } + + if (mayShoot) { + interaction.stopUsingItem(player) + + mayShoot = false + } else { + interaction.interactItem(player, Hand.MAIN_HAND) + } + } + +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/ModuleProjectileAimbot.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/ModuleProjectileAimbot.kt new file mode 100644 index 00000000000..208d09cba67 --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/ModuleProjectileAimbot.kt @@ -0,0 +1,50 @@ +package net.ccbluex.liquidbounce.features.module.modules.combat.aimbot + +import net.ccbluex.liquidbounce.event.tickHandler +import net.ccbluex.liquidbounce.features.module.Category +import net.ccbluex.liquidbounce.features.module.ClientModule +import net.ccbluex.liquidbounce.utils.aiming.RotationManager +import net.ccbluex.liquidbounce.utils.aiming.RotationsConfigurable +import net.ccbluex.liquidbounce.utils.aiming.projectiles.SituationalProjectileAngleCalculator +import net.ccbluex.liquidbounce.utils.combat.TargetTracker +import net.ccbluex.liquidbounce.utils.kotlin.Priority +import net.ccbluex.liquidbounce.utils.render.trajectory.TrajectoryData + +object ModuleProjectileAimbot : ClientModule("ProjectileAimbot", Category.COMBAT) { + private val targetTracker = TargetTracker() + private val rotations = RotationsConfigurable(this) + + init { + tree(targetTracker) + tree(rotations) + } + + private val tickHandler = tickHandler { + val target = targetTracker.enemies().firstOrNull() ?: return@tickHandler + + val rotation = player.handItems.firstNotNullOfOrNull { + if (it.item == null) { + return@firstNotNullOfOrNull null + } + + val trajectory = TrajectoryData.getRenderedTrajectoryInfo( + player, + it.item, + true + ) ?: return@firstNotNullOfOrNull null + + SituationalProjectileAngleCalculator.calculateAngleForEntity(trajectory, target) + } ?: return@tickHandler + + RotationManager.aimAt( + rotation, + considerInventory = false, + rotations, + Priority.IMPORTANT_FOR_USAGE_1, + ModuleProjectileAimbot + ) + } + + + +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/autobow/AutoBowAimbotFeature.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/autobow/AutoBowAimbotFeature.kt new file mode 100644 index 00000000000..404e8b588ae --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/autobow/AutoBowAimbotFeature.kt @@ -0,0 +1,90 @@ +package net.ccbluex.liquidbounce.features.module.modules.combat.aimbot.autobow + +import net.ccbluex.liquidbounce.config.types.ToggleableConfigurable +import net.ccbluex.liquidbounce.event.events.OverlayRenderEvent +import net.ccbluex.liquidbounce.event.handler +import net.ccbluex.liquidbounce.event.tickHandler +import net.ccbluex.liquidbounce.features.module.modules.combat.aimbot.ModuleAutoBow +import net.ccbluex.liquidbounce.render.renderEnvironmentForGUI +import net.ccbluex.liquidbounce.utils.aiming.Rotation +import net.ccbluex.liquidbounce.utils.aiming.RotationManager +import net.ccbluex.liquidbounce.utils.aiming.RotationsConfigurable +import net.ccbluex.liquidbounce.utils.aiming.projectiles.PolynomialProjectileAngleCalculator +import net.ccbluex.liquidbounce.utils.aiming.projectiles.SituationalProjectileAngleCalculator +import net.ccbluex.liquidbounce.utils.combat.PriorityEnum +import net.ccbluex.liquidbounce.utils.combat.TargetTracker +import net.ccbluex.liquidbounce.utils.entity.eyes +import net.ccbluex.liquidbounce.utils.kotlin.Priority +import net.ccbluex.liquidbounce.utils.render.OverlayTargetRenderer +import net.ccbluex.liquidbounce.utils.render.trajectory.TrajectoryData +import net.minecraft.item.BowItem +import net.minecraft.item.TridentItem + +/** + * Automatically shoots with your bow when you aim correctly at an enemy or when the bow is fully charged. + */ +object AutoBowAimbotFeature : ToggleableConfigurable(ModuleAutoBow, "BowAimbot", true) { + + // Target + val targetTracker = TargetTracker(PriorityEnum.DISTANCE) + + // Rotation + val rotationConfigurable = RotationsConfigurable(this) + + val minExpectedPull by int("MinExpectedPull", 5, 0..20, suffix = "ticks") + + init { + tree(targetTracker) + tree(rotationConfigurable) + } + + private val targetRenderer = tree(OverlayTargetRenderer(ModuleAutoBow)) + + @Suppress("unused") + val tickRepeatable = tickHandler { + targetTracker.cleanup() + + // Should check if player is using bow + val activeItem = player.activeItem?.item + if (activeItem !is BowItem && activeItem !is TridentItem) { + return@tickHandler + } + + val projectileInfo = TrajectoryData.getRenderedTrajectoryInfo( + player, + activeItem, + true + ) ?: return@tickHandler + + var rotation: Rotation? = null + + for (enemy in targetTracker.enemies()) { + val rot = SituationalProjectileAngleCalculator.calculateAngleForEntity(projectileInfo, enemy) ?: continue + + targetTracker.lock(enemy) + rotation = rot + break + } + + if (rotation == null) { + return@tickHandler + } + + RotationManager.aimAt( + rotation, + priority = Priority.IMPORTANT_FOR_USAGE_1, + provider = ModuleAutoBow, + configurable = rotationConfigurable + ) + } + + @Suppress("unused") + val renderHandler = handler { event -> + val target = targetTracker.lockedOnTarget ?: return@handler + + renderEnvironmentForGUI { + targetRenderer.render(this, target, event.tickDelta) + } + } + +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/autobow/AutoBowAutoShootFeature.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/autobow/AutoBowAutoShootFeature.kt new file mode 100644 index 00000000000..e155abcbb49 --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/autobow/AutoBowAutoShootFeature.kt @@ -0,0 +1,147 @@ +package net.ccbluex.liquidbounce.features.module.modules.combat.aimbot.autobow + +import net.ccbluex.liquidbounce.config.types.ToggleableConfigurable +import net.ccbluex.liquidbounce.event.events.GameTickEvent +import net.ccbluex.liquidbounce.event.handler +import net.ccbluex.liquidbounce.features.module.modules.combat.aimbot.ModuleAutoBow +import net.ccbluex.liquidbounce.utils.aiming.RotationManager +import net.ccbluex.liquidbounce.utils.client.toRadians +import net.ccbluex.liquidbounce.utils.combat.shouldBeAttacked +import net.ccbluex.liquidbounce.utils.entity.* +import net.ccbluex.liquidbounce.utils.math.geometry.Line +import net.ccbluex.liquidbounce.utils.render.trajectory.TrajectoryInfo +import net.minecraft.client.network.AbstractClientPlayerEntity +import net.minecraft.item.BowItem +import net.minecraft.item.TridentItem +import net.minecraft.util.math.Box +import net.minecraft.util.math.MathHelper +import net.minecraft.util.math.Vec3d + +object AutoBowAutoShootFeature : ToggleableConfigurable(ModuleAutoBow, "AutoShoot", true) { + + val charged by int("Charged", 15, 3..20, suffix = "ticks") + + val chargedRandom by floatRange( + "ChargedRandom", + 0.0F..0.0F, + -10.0F..10.0F, + suffix = "ticks" + ) + val delayBetweenShots by float("DelayBetweenShots", 0.0F, 0.0F..5.0F, suffix = "s") + val aimThreshold by float("AimThreshold", 1.5F, 1.0F..4.0F, suffix = "°") + val requiresHypotheticalHit by boolean("RequiresHypotheticalHit", false) + + var currentChargeRandom: Int? = null + + fun updateChargeRandom() { + val lenHalf = (chargedRandom.endInclusive - chargedRandom.start) / 2.0F + val mid = chargedRandom.start + lenHalf + + currentChargeRandom = + (mid + ModuleAutoBow.random.nextGaussian() * lenHalf).toInt() + .coerceIn(chargedRandom.start.toInt()..chargedRandom.endInclusive.toInt()) + } + + fun getChargedRandom(): Int { + if (currentChargeRandom == null) { + updateChargeRandom() + } + + return currentChargeRandom!! + } + + @Suppress("unused") + val tickRepeatable = handler { + val currentItem = player.activeItem?.item + + // Should check if player is using bow + if (currentItem !is BowItem && currentItem !is TridentItem) { + return@handler + } + + if (player.itemUseTime < charged + getChargedRandom()) { // Wait until the bow is fully charged + return@handler + } + if (!ModuleAutoBow.lastShotTimer.hasElapsed((delayBetweenShots * 1000.0F).toLong())) { + return@handler + } + + if (requiresHypotheticalHit) { + val hypotheticalHit = getHypotheticalHit() + + if (hypotheticalHit == null || !hypotheticalHit.shouldBeAttacked()) { + return@handler + } + } else if (AutoBowAimbotFeature.enabled) { + if (AutoBowAimbotFeature.targetTracker.lockedOnTarget == null) { + return@handler + } + + val targetRotation = RotationManager.workingAimPlan ?: return@handler + + val aimDifference = RotationManager.rotationDifference( + RotationManager.serverRotation, targetRotation.rotation + ) + + if (aimDifference > aimThreshold) { + return@handler + } + } + + interaction.stopUsingItem(player) + updateChargeRandom() + } + + fun getHypotheticalHit(): AbstractClientPlayerEntity? { + val rotation = RotationManager.serverRotation + val yaw = rotation.yaw + val pitch = rotation.pitch + + val velocity = (TrajectoryInfo.bowWithUsageDuration() ?: return null).initialVelocity + + val vX = -MathHelper.sin(yaw.toRadians()) * MathHelper.cos(pitch.toRadians()) * velocity + val vY = -MathHelper.sin(pitch.toRadians()) * velocity + val vZ = MathHelper.cos(yaw.toRadians()) * MathHelper.cos(pitch.toRadians()) * velocity + + val arrow = SimulatedArrow( + world, + player.eyes, + Vec3d(vX, vY, vZ), + collideEntities = false + ) + + val players = findAndBuildSimulatedPlayers() + + for (i in 0 until 40) { + val lastPos = arrow.pos + + arrow.tick() + + players.forEach { (entity, player) -> + val playerSnapshot = player.getSnapshotAt(i) + + val playerHitBox = + Box(-0.3, 0.0, -0.3, 0.3, 1.8, 0.3) + .expand(0.3) + .offset(playerSnapshot.pos) + + val raycastResult = playerHitBox.raycast(lastPos, arrow.pos) + + raycastResult.orElse(null)?.let { + return entity + } + } + } + + return null + } + + fun findAndBuildSimulatedPlayers(): List> { + return world.players.filter { + it != player && + Line(player.pos, player.rotationVector).squaredDistanceTo(it.pos) < 10.0 * 10.0 + }.map { + Pair(it, PlayerSimulationCache.getSimulationForOtherPlayers(it)) + } + } +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/autobow/AutoBowFastChargeFeature.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/autobow/AutoBowFastChargeFeature.kt new file mode 100644 index 00000000000..e45c1bc48b8 --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/combat/aimbot/autobow/AutoBowFastChargeFeature.kt @@ -0,0 +1,59 @@ +package net.ccbluex.liquidbounce.features.module.modules.combat.aimbot.autobow + + +import net.ccbluex.liquidbounce.config.types.ToggleableConfigurable +import net.ccbluex.liquidbounce.event.tickHandler +import net.ccbluex.liquidbounce.features.module.modules.combat.aimbot.ModuleAutoBow +import net.ccbluex.liquidbounce.utils.client.MovePacketType +import net.ccbluex.liquidbounce.utils.entity.moving +import net.minecraft.entity.effect.StatusEffects +import net.minecraft.item.BowItem + +/** + * @desc Fast charge options (like FastBow) can be used to charge the bow faster. + * @warning Should only be used on vanilla minecraft. Most anti cheats patch these kinds of exploits + * + * TODO: Add version specific options + */ +object AutoBowFastChargeFeature : ToggleableConfigurable(ModuleAutoBow, "FastCharge", false) { + + private val speed by int("Speed", 20, 3..20) + + private val notInTheAir by boolean("NotInTheAir", true) + private val notDuringMove by boolean("NotDuringMove", false) + private val notDuringRegeneration by boolean("NotDuringRegeneration", false) + + private val packetType by enumChoice("PacketType", MovePacketType.FULL) + + @Suppress("unused") + val tickRepeatable = tickHandler { + val currentItem = player.activeItem + + // Should speed up game ticks when using bow + if (currentItem?.item is BowItem) { + if (notInTheAir && !player.isOnGround) { + return@tickHandler + } + + if (notDuringMove && player.moving) { + return@tickHandler + } + + if (notDuringRegeneration && player.hasStatusEffect(StatusEffects.REGENERATION)) { + return@tickHandler + } + + repeat(speed) { + if (!player.isUsingItem) { + return@repeat + } + + // Speed up ticks (MC 1.8) + network.sendPacket(packetType.generatePacket()) + + // Show visual effect (not required to work - but looks better) + player.tickActiveItemStack() + } + } + } +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/misc/ModuleAutoPearl.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/misc/ModuleAutoPearl.kt index 935d86a5f43..319b9885185 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/misc/ModuleAutoPearl.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/misc/ModuleAutoPearl.kt @@ -17,6 +17,7 @@ import net.ccbluex.liquidbounce.utils.render.trajectory.TrajectoryInfoRenderer import net.ccbluex.liquidbounce.utils.aiming.Rotation import net.ccbluex.liquidbounce.utils.aiming.RotationManager import net.ccbluex.liquidbounce.utils.aiming.RotationsConfigurable +import net.ccbluex.liquidbounce.utils.aiming.projectiles.SituationalProjectileAngleCalculator import net.ccbluex.liquidbounce.utils.combat.CombatManager import net.ccbluex.liquidbounce.utils.combat.shouldBeAttacked import net.ccbluex.liquidbounce.utils.inventory.OFFHAND_SLOT @@ -24,12 +25,12 @@ import net.ccbluex.liquidbounce.utils.inventory.useHotbarSlotOrOffhand import net.ccbluex.liquidbounce.utils.item.findHotbarItemSlot import net.ccbluex.liquidbounce.utils.kotlin.Priority import net.minecraft.entity.Entity +import net.minecraft.entity.EntityDimensions import net.minecraft.entity.EntityType import net.minecraft.entity.projectile.thrown.EnderPearlEntity import net.minecraft.item.Items import net.minecraft.network.packet.s2c.play.EntitySpawnS2CPacket import net.minecraft.util.hit.HitResult -import net.minecraft.util.math.MathHelper import net.minecraft.util.math.Vec3d import kotlin.math.* @@ -159,7 +160,11 @@ object ModuleAutoPearl : ClientModule("AutoPearl", Category.MISC, aliases = arra return } - val rotation = calculatePearlTrajectory(player.eyePos, destination) ?: return + val rotation = SituationalProjectileAngleCalculator.calculateAngleForStaticTarget( + TrajectoryInfo.GENERIC, + destination, + EntityDimensions.fixed(1.0F, 0.0F) + ) ?: return if (!canThrow(rotation, destination)) { return @@ -202,42 +207,6 @@ object ModuleAutoPearl : ClientModule("AutoPearl", Category.MISC, aliases = arra return !Limits.enabled || Limits.destDistance > destination.distanceTo(simulatedDestination) } - private fun calculatePearlTrajectory(startPos: Vec3d, targetPos: Vec3d): Rotation? { - val diff: Vec3d = targetPos.subtract(startPos) - - val horizontalDistance = MathHelper.sqrt((diff.x * diff.x + diff.z * diff.z).toFloat()).toDouble() - val pearlInfo = TrajectoryInfo.GENERIC - - val velocity = pearlInfo.initialVelocity - val gravity = pearlInfo.gravity - - val velocity2 = velocity * velocity - val velocity4 = velocity2 * velocity2 - val y = diff.y - - val sqrt = velocity4 - gravity * (gravity * horizontalDistance * horizontalDistance + 2 * y * velocity2) - - if (sqrt < 0) { - return null - } - - val pitchRad = atan((velocity2 - sqrt(sqrt)) / (gravity * horizontalDistance)) - - val yawRad = atan2(diff.z, diff.x) - - val pitch = Math.toDegrees(pitchRad).toFloat() - var yaw = Math.toDegrees(yawRad).toFloat() - - yaw -= 90f - if (yaw > 180.0f) { - yaw -= 360.0f - } else if (yaw < -180.0f) { - yaw += 360.0f - } - - return Rotation(yaw, -pitch) - } - private fun runSimulation( owner: Entity, velocity: Vec3d, diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/player/ModuleSmartEat.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/player/ModuleSmartEat.kt index 15489fc7434..16cffb5ac34 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/player/ModuleSmartEat.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/player/ModuleSmartEat.kt @@ -38,6 +38,7 @@ import net.minecraft.client.option.KeyBinding import net.minecraft.entity.effect.StatusEffects import net.minecraft.item.ItemStack import net.minecraft.item.Items +import net.minecraft.item.ToolItem import net.minecraft.util.ActionResult import net.minecraft.util.Identifier import net.minecraft.util.UseAction @@ -154,6 +155,11 @@ object ModuleSmartEat : ClientModule("SmartEat", Category.PLAYER) { return@handler } + // Only use silent offhand if we have tools in hand. + if (player.mainHandStack.item !is ToolItem) { + return@handler + } + SilentHotbar.selectSlotSilently( this@SilentOffhand, currentFood.hotbarSlot, diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/ModuleDebug.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/ModuleDebug.kt index 6f60d2d1581..9a6fd4db326 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/ModuleDebug.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/ModuleDebug.kt @@ -34,9 +34,10 @@ import net.ccbluex.liquidbounce.render.* import net.ccbluex.liquidbounce.render.engine.Color4b import net.ccbluex.liquidbounce.utils.entity.PlayerSimulationCache import net.ccbluex.liquidbounce.utils.entity.eyes -import net.ccbluex.liquidbounce.utils.math.geometry.Face +import net.ccbluex.liquidbounce.utils.math.geometry.AlignedFace import net.ccbluex.liquidbounce.utils.math.geometry.Line import net.ccbluex.liquidbounce.utils.math.geometry.LineSegment +import net.ccbluex.liquidbounce.utils.math.geometry.PlaneSection import net.ccbluex.liquidbounce.utils.math.toVec3 import net.minecraft.text.OrderedText import net.minecraft.text.Text @@ -111,7 +112,7 @@ object ModuleDebug : ClientModule("Debug", Category.RENDER) { } val pos0 = Vec3d(77.0, 75.0, -52.0) - val face = Face(pos0, pos0.add(1.0, 1.0, 0.0)) + val face = AlignedFace(pos0, pos0.add(1.0, 1.0, 0.0)) ModuleDebug.debugGeometry( ModuleScaffold, @@ -269,6 +270,14 @@ object ModuleDebug : ClientModule("Debug", Category.RENDER) { } } + class DebuggedQuad(val p1: Vec3d, val p2: Vec3d, color: Color4b) : DebuggedGeometry(color) { + override fun render(env: WorldRenderEnvironment) { + env.withColor(color) { + this.drawQuad(relativeToCamera(p1).toVec3(), relativeToCamera(p2).toVec3()) + } + } + } + class DebuggedLineSegment(val from: Vec3d, val to: Vec3d, color: Color4b) : DebuggedGeometry(color) { override fun render(env: WorldRenderEnvironment) { env.withColor(color) { diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/PointFinding.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/PointFinding.kt new file mode 100644 index 00000000000..3737ad95909 --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/PointFinding.kt @@ -0,0 +1,214 @@ +package net.ccbluex.liquidbounce.utils.aiming + +import net.ccbluex.liquidbounce.features.module.modules.combat.aimbot.ModuleProjectileAimbot +import net.ccbluex.liquidbounce.features.module.modules.render.ModuleDebug +import net.ccbluex.liquidbounce.render.engine.Color4b +import net.ccbluex.liquidbounce.utils.client.world +import net.ccbluex.liquidbounce.utils.math.geometry.Line +import net.ccbluex.liquidbounce.utils.math.geometry.NormalizedPlane +import net.ccbluex.liquidbounce.utils.math.geometry.PlaneSection +import net.ccbluex.liquidbounce.utils.math.minus +import net.ccbluex.liquidbounce.utils.math.plus +import net.ccbluex.liquidbounce.utils.math.times +import net.minecraft.entity.projectile.ArrowEntity +import net.minecraft.item.ItemStack +import net.minecraft.item.Items +import net.minecraft.util.hit.HitResult +import net.minecraft.util.math.Box +import net.minecraft.util.math.Vec3d +import net.minecraft.world.RaycastContext +import org.joml.Matrix3f +import org.joml.Vector3f +import kotlin.jvm.optionals.getOrNull +import kotlin.math.* + +val Box.edgePoints: Array + get() = arrayOf( + Vec3d(minX, minY, minZ), + Vec3d(minX, minY, maxZ), + Vec3d(minX, maxY, minZ), + Vec3d(minX, maxY, maxZ), + Vec3d(maxX, minY, minZ), + Vec3d(maxX, minY, maxZ), + Vec3d(maxX, maxY, minZ), + Vec3d(maxX, maxY, maxZ), + ) + +fun Vec3d.moveTowards(otherPoint: Vec3d, fraction: Double): Vec3d { + val direction = otherPoint - this + + return this + direction.multiply(fraction) +} + +/** + * Creates rotation matrices: The first allows to turn the vec (1.0, 0.0, 0.0) into the given [vec]. + * The second allows to turn the given vec into (1.0, 0.0, 0.0). + */ +fun getRotationMatricesForVec(vec: Vec3d): Pair { + val hypotenuse = hypot(vec.x, vec.z) + + val yawAtan = atan2(vec.z, vec.x).toFloat() + val pitchAtan = atan2(vec.y, hypotenuse).toFloat() + + val toMatrix = Matrix3f().rotateY(-yawAtan).mul(Matrix3f().rotateZ(pitchAtan)) + val backMatrix = Matrix3f().rotateZ(-pitchAtan).mul(Matrix3f().rotateY(yawAtan)) + + return toMatrix to backMatrix +} + +/** + * Projects points onto the [targetBox]. The points are uniformly distributed from the perspective of [virtualEye]. + * + * @return a list of projected points, or null if the virtual eye is inside the target box. + */ +fun projectPointsOnBox(virtualEye: Vec3d, targetBox: Box, maxPoints: Int = 128): ArrayList? { + val list = ArrayList() + + val success = projectPointsOnBox(virtualEye, targetBox, maxPoints) { + list.add(it) + } + + if (!success) { + return null + } + + return list +} + +/** + * Projects points onto the [targetBox]. The points are uniformly distributed from the perspective of [virtualEye]. + * + * @return `false` if the virtual eye is inside the target box. + */ +inline fun projectPointsOnBox( + virtualEye: Vec3d, + targetBox: Box, + maxPoints: Int = 128, + consumer: (Vec3d) -> Unit +): Boolean { + if (targetBox.contains(virtualEye)) { + return false + } + + val playerToBoxLine = Line(position = virtualEye, direction = targetBox.center - virtualEye) + + // Find a point between the virtual eye and the target box such that every edge point of the box is behind it + // (from the perspective of the virtual eye). This position is used to craft a the targeting frame + val targetFrameOrigin = targetBox.edgePoints + .map { playerToBoxLine.getNearestPointTo(it) } + .minBy { it.squaredDistanceTo(virtualEye) } + .moveTowards(virtualEye, 0.1) + + val plane = NormalizedPlane(targetFrameOrigin, playerToBoxLine.direction) + val (toMatrix, backMatrix) = getRotationMatricesForVec(plane.normalVec) + + val projectedAndRotatedPoints = targetBox.edgePoints.map { + plane.intersection(Line.fromPoints(virtualEye, it))!!.subtract(targetFrameOrigin).toVector3f().mul(backMatrix) + } + + var minZ = 0.0F + var maxZ = 0.0F + var minY = 0.0F + var maxY = 0.0F + + projectedAndRotatedPoints.forEach { + minZ = min(minZ, it.z) + maxZ = max(maxZ, it.z) + minY = min(minY, it.y) + maxY = max(maxY, it.y) + } + + val posVec = + Vec3d(0.0, minY.toDouble(), minZ.toDouble()).toVector3f().mul(toMatrix).toVec3d().add(targetFrameOrigin) + val dirVecY = Vec3d(0.0, (maxY - minY).toDouble(), 0.0).toVector3f().mul(toMatrix).toVec3d() + val dirVecZ = Vec3d(0.0, 0.0, (maxZ - minZ).toDouble()).toVector3f().mul(toMatrix).toVec3d() + + val planeSection = PlaneSection(posVec, dirVecY, dirVecZ) + + planeSection.castPointsOnUniformly(maxPoints) { point -> + // Extent the point from the face on. + val pointExtended = point.moveTowards(virtualEye, -100.0) + + val pos = targetBox.raycast(virtualEye, pointExtended).getOrNull() ?: return@castPointsOnUniformly + + consumer(pos) + } + + return true +} + +/** + * Finds a point that is visible from the virtual eyes. + * + * ## Algorithm + * 1. Projects points on the box from the virtual eyes. + * 2. Sorts the points by distance to the box center. + * 3. For each point: + * - Creates a ray starting from the point, extending for twice the range. + * - Raycasts the ray against the box to find the intersection point. + * - Checks if the intersection point is within the range and satisfies the [visibilityPredicate]. + * 4. Returns the first visible point found, or null if no point is visible. + * + * @param rangeToTest The maximum distance to test for visibility. + * @param visibilityPredicate An optional predicate to determine if a given point is visible + * @return the best visible spot found or `null` + */ +@Suppress("detekt:complexity.LongParameterList") +fun findVisiblePointFromVirtualEye( + virtualEyes: Vec3d, + box: Box, + rangeToTest: Double, + visibilityPredicate: VisibilityPredicate = ArrowVisibilityPredicate, +): Vec3d? { + val points = projectPointsOnBox(virtualEyes, box) ?: return null + + val debugCollection = ModuleDebug.DebugCollection(points.map { ModuleDebug.DebuggedPoint(it, Color4b.BLUE, 0.01) }) + + ModuleDebug.debugGeometry(ModuleProjectileAimbot, "points", debugCollection) + + val rays = ArrayList() + + val center = box.center + val sortedPoints = points.sortedBy { it.distanceTo(center) } + + for (spot in sortedPoints) { + val vecFromEyes = spot - virtualEyes + val raycastTarget = vecFromEyes * 2.0 + virtualEyes + val spotOnBox = box.raycast(virtualEyes, raycastTarget).getOrNull() ?: continue + + val rayStart = spotOnBox.subtract(vecFromEyes.normalize().multiply(rangeToTest)) + + val visible = visibilityPredicate.isVisible(rayStart, spotOnBox) + + rays.add(ModuleDebug.DebuggedLineSegment(rayStart, spotOnBox, if (visible) Color4b.GREEN else Color4b.RED)) + + if (visible) { + ModuleDebug.debugGeometry(ModuleProjectileAimbot, "rays", ModuleDebug.DebugCollection(rays)) + return spotOnBox + } + } + + ModuleDebug.debugGeometry(ModuleProjectileAimbot, "rays", ModuleDebug.DebugCollection(rays)) + + return null +} + +object ArrowVisibilityPredicate : VisibilityPredicate { + override fun isVisible(eyesPos: Vec3d, targetSpot: Vec3d): Boolean { + val arrowEntity = ArrowEntity( + world, eyesPos.x, targetSpot.y, targetSpot.z, ItemStack(Items.ARROW), + null) + + return world.raycast( + RaycastContext( + eyesPos, + targetSpot, + RaycastContext.ShapeType.COLLIDER, + RaycastContext.FluidHandling.NONE, + arrowEntity + ) + )?.let { it.type == HitResult.Type.MISS } ?: true + } +} + +fun Vector3f.toVec3d(): Vec3d = Vec3d(this.x.toDouble(), this.y.toDouble(), this.z.toDouble()) diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/RotationFinding.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/RotationFinding.kt index dee3c2f18ec..a903eb75f34 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/RotationFinding.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/RotationFinding.kt @@ -346,7 +346,7 @@ fun raytraceBox( bestRotationTracker, ) - scanPositionsInBox(box) { spot -> + scanBoxPoints(eyes, box) { spot -> considerSpot( spot, box, @@ -402,12 +402,12 @@ fun canSeeBox( val rangeSquared = range * range val wallsRangeSquared = wallsRange * wallsRange - scanPositionsInBox(box) { posInBox -> + scanBoxPoints(eyes, box) { posInBox -> // skip because of out of range val distance = eyes.squaredDistanceTo(posInBox) if (distance > rangeSquared) { - return@scanPositionsInBox + return@scanBoxPoints } // check if target is visible to eyes @@ -420,7 +420,7 @@ fun canSeeBox( // skip because not visible in range if (!visible && distance > wallsRangeSquared) { - return@scanPositionsInBox + return@scanBoxPoints } return true @@ -429,10 +429,23 @@ fun canSeeBox( return false } -private inline fun scanPositionsInBox( +private inline fun scanBoxPoints( + eyes: Vec3d, box: Box, - step: Double = 0.1, fn: (Vec3d) -> Unit, +) { + val isOutsideBox = projectPointsOnBox(eyes, box, maxPoints = 256, fn) + + // We cannot project points on something if we are inside the hitbox + if (!isOutsideBox) { + scanBoxPoints3D(0.1, box, fn) + } +} + +private inline fun scanBoxPoints3D( + step: Double, + box: Box, + fn: (Vec3d) -> Unit ) { for ((x, y, z) in Vec3d(0.1, 0.1, 0.1)..Vec3d(0.9, 0.9, 0.9) step step) { val vec3 = Vec3d( diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/RotationsUtil.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/RotationsUtil.kt index cece79f7578..9ac84763966 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/RotationsUtil.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/RotationsUtil.kt @@ -230,9 +230,13 @@ object RotationManager : EventListener { } fun makeRotation(vec: Vec3d, eyes: Vec3d): Rotation { - val diffX = vec.x - eyes.x - val diffY = vec.y - eyes.y - val diffZ = vec.z - eyes.z + return makeRotation(vec.subtract(eyes)) + } + + fun makeRotation(lookVec: Vec3d): Rotation { + val diffX = lookVec.x + val diffY = lookVec.y + val diffZ = lookVec.z return Rotation( MathHelper.wrapDegrees(Math.toDegrees(atan2(diffZ, diffX)).toFloat() - 90f), diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/projectiles/CydhranianProjectileAngleCalculator.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/projectiles/CydhranianProjectileAngleCalculator.kt new file mode 100644 index 00000000000..99656004373 --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/projectiles/CydhranianProjectileAngleCalculator.kt @@ -0,0 +1,168 @@ +package net.ccbluex.liquidbounce.utils.aiming.projectiles + +import net.ccbluex.liquidbounce.features.module.modules.combat.aimbot.ModuleProjectileAimbot +import net.ccbluex.liquidbounce.features.module.modules.render.ModuleDebug +import net.ccbluex.liquidbounce.render.engine.Color4b +import net.ccbluex.liquidbounce.utils.aiming.Rotation +import net.ccbluex.liquidbounce.utils.aiming.RotationManager +import net.ccbluex.liquidbounce.utils.entity.PositionExtrapolation +import net.ccbluex.liquidbounce.utils.math.findFunctionMinimumByBisect +import net.ccbluex.liquidbounce.utils.render.trajectory.TrajectoryInfo +import net.minecraft.entity.EntityDimensions +import net.minecraft.util.math.MathHelper +import net.minecraft.util.math.Vec3d +import kotlin.math.* + +/** + * Implements the angle calculator described by Cydhra + * ([see here](https://gitlab.com/Cydhra/Vibrant/-/blob/master/doc/main.pdf)). + * + * This is currently used as the flagship implementation. When the distance between the source and target pos is low, + * this implementation often malfunctions. Use a backup calculator for low distances instead. + */ +object CydhranianProjectileAngleCalculator: ProjectileAngleCalculator() { + /** + * @param sourcePos the position the projectile originates from (usually the player's eyePos) + */ + override fun calculateAngleFor( + projectileInfo: TrajectoryInfo, + sourcePos: Vec3d, + targetPosFunction: PositionExtrapolation, + targetShape: EntityDimensions + ): Rotation? { + val calculatedLookVec = predictArrowDirection(projectileInfo, sourcePos, targetShape, targetPosFunction) + + return calculatedLookVec?.let(RotationManager::makeRotation) + } + + private fun getDirectionByTime( + trajectoryInfo: TrajectoryInfo, + enemyPosition: Vec3d, + playerHeadPosition: Vec3d, + time: Double + ): Vec3d { + val vA = trajectoryInfo.initialVelocity + val resistanceFactor = trajectoryInfo.drag + val g = trajectoryInfo.gravity + + return Vec3d( + (enemyPosition.x - playerHeadPosition.x) * (resistanceFactor - 1) + / (vA * (resistanceFactor.pow(time) - 1)), + + (enemyPosition.y - playerHeadPosition.y) * (resistanceFactor - 1) + / (vA * (resistanceFactor.pow(time) - 1)) + g * (resistanceFactor.pow(time) + - resistanceFactor * time + time - 1) + / (vA * (resistanceFactor - 1) * (resistanceFactor.pow(time) - 1)), + + (enemyPosition.z - playerHeadPosition.z) * (resistanceFactor - 1) + / (vA * (resistanceFactor.pow(time) - 1)) + ) + } + + private fun getVelocityOnImpact(trajectoryInfo: TrajectoryInfo, ticksPassed: Double, initialDir: Vec3d): Vec3d { + val d_x = initialDir.x + val d_y = initialDir.y + val d_z = initialDir.z + + val r_proj = trajectoryInfo.drag + val v_proj = trajectoryInfo.initialVelocity + val g = trajectoryInfo.gravity + val t = ticksPassed + + val fResistance = r_proj - 1 + + return Vec3d( + (d_x * r_proj.pow(t) * ln(r_proj) * v_proj) / fResistance, + (d_y * fResistance * r_proj.pow(t) * ln(r_proj) * v_proj - g * (r_proj.pow(t) * ln(r_proj) - r_proj + 1)) + / fResistance.pow(2), + (d_z * r_proj.pow(t) * ln(r_proj) * v_proj) / fResistance + ) + } + + /** + * Calculates how long the arrow would fly when hitting the target (see paper) + */ + private fun calculatePossibleTravelTimeToTarget( + trajectoryInfo: TrajectoryInfo, + playerHeadPosition: Vec3d, + positionFunction: PositionExtrapolation, + defaultBoxOffset: Vec3d, + ): Double? { + val distance = positionFunction.getPositionInTicks(0.0).subtract(playerHeadPosition).length() + + // The max travel time which we expect the arrow to fly. The bisect function expects only + // one minimum (= one solution). In most cases, there are actually two solutions. We don't want the second + // solution as involves longer flight times than the first solution. This maximum guarantees that the found + // solution may only take 75% longer than the straight line to the target. + val maxTravelTime = distance / trajectoryInfo.initialVelocity * 1.75 + + // Calculate how long the arrow would need to travel to hit the entity with the given position function. + val (ticks, delta) = findFunctionMinimumByBisect(0.0..maxTravelTime) { ticks -> + val newLimit = getDirectionByTime( + trajectoryInfo, + enemyPosition = positionFunction.getPositionInTicks(ticks).add(defaultBoxOffset), + playerHeadPosition = playerHeadPosition, + time = ticks + ) + + abs(newLimit.length() - 1) + } + + // If the length returned rotation is not close to 1.0, return (-> see document) + if (delta > 1E-1) { + return null + } + + return ticks + } + + private fun predictArrowDirection( + trajectoryInfo: TrajectoryInfo, + playerHeadPosition: Vec3d, + targetDimensions: EntityDimensions, + positionFunction: PositionExtrapolation, + ): Vec3d? { + val defaultBoxOffset = Vec3d( + targetDimensions.width * 0.5, + targetDimensions.height * 0.5, + targetDimensions.width * 0.5 + ) + + val ticksUntilImpact = calculatePossibleTravelTimeToTarget( + trajectoryInfo, + playerHeadPosition, + positionFunction, + defaultBoxOffset + ) ?: return null + + val entityPositionOnImpact = positionFunction.getPositionInTicks(ticksUntilImpact) + + val finalDirection = getDirectionByTime( + trajectoryInfo, + enemyPosition = entityPositionOnImpact.add(defaultBoxOffset), + playerHeadPosition = playerHeadPosition, + time = ticksUntilImpact + ) + + val directionOnImpact = getVelocityOnImpact(trajectoryInfo, ticksUntilImpact, finalDirection).normalize() + + ModuleDebug.debugGeometry( + ModuleProjectileAimbot, "inboundDirection", ModuleDebug.DebuggedLineSegment( + entityPositionOnImpact, + entityPositionOnImpact.add(directionOnImpact.normalize().multiply(2.0)), + Color4b.BLUE + ) + ) + + val finalTargetPos = ProjectileTargetPointFinder.findHittablePosition( + playerHeadPosition, + directionOnImpact, + entityPositionOnImpact, + targetEntityBox = targetDimensions.getBoxAt(entityPositionOnImpact) + ) ?: return null + + return getDirectionByTime(trajectoryInfo, finalTargetPos, playerHeadPosition, round(ticksUntilImpact)) + } + + +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/projectiles/PolynomialProjectileAngleCalculator.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/projectiles/PolynomialProjectileAngleCalculator.kt new file mode 100644 index 00000000000..9017caba8dc --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/projectiles/PolynomialProjectileAngleCalculator.kt @@ -0,0 +1,57 @@ +package net.ccbluex.liquidbounce.utils.aiming.projectiles + +import net.ccbluex.liquidbounce.utils.aiming.Rotation +import net.ccbluex.liquidbounce.utils.entity.PositionExtrapolation +import net.ccbluex.liquidbounce.utils.math.findFunctionMinimumByBisect +import net.ccbluex.liquidbounce.utils.render.trajectory.TrajectoryInfo +import net.minecraft.entity.EntityDimensions +import net.minecraft.util.math.MathHelper +import net.minecraft.util.math.Vec3d +import kotlin.math.abs +import kotlin.math.atan +import kotlin.math.atan2 +import kotlin.math.sqrt + +/** + * Solves this problem by approximating the trajectory as a second degree polynomial. This approximation is good for + * ~20 ticks. + * + * Currently only used as backup + */ +object PolynomialProjectileAngleCalculator: ProjectileAngleCalculator() { + override fun calculateAngleFor( + projectileInfo: TrajectoryInfo, + sourcePos: Vec3d, + targetPosFunction: PositionExtrapolation, + targetShape: EntityDimensions + ): Rotation? { + val basePos = targetPosFunction.getPositionInTicks(0.0) + val estimatedTicksUntilImpact = basePos.distanceTo(sourcePos) / projectileInfo.initialVelocity + + val diff: Vec3d = targetPosFunction.getPositionInTicks(estimatedTicksUntilImpact).subtract(sourcePos) + + val horizontalDistance = MathHelper.sqrt((diff.x * diff.x + diff.z * diff.z).toFloat()).toDouble() + val pearlInfo = TrajectoryInfo.GENERIC + + val velocity = pearlInfo.initialVelocity + val gravity = pearlInfo.gravity + + val velocity2 = velocity * velocity + val velocity4 = velocity2 * velocity2 + val y = diff.y + + val sqrt = velocity4 - gravity * (gravity * horizontalDistance * horizontalDistance + 2 * y * velocity2) + + if (sqrt < 0) { + return null + } + + val pitchRad = atan((velocity2 - sqrt(sqrt)) / (gravity * horizontalDistance)) + val yawRad = atan2(diff.z, diff.x) + + return Rotation( + MathHelper.wrapDegrees(Math.toDegrees(yawRad).toFloat() - 90f), + MathHelper.wrapDegrees((-Math.toDegrees(pitchRad)).toFloat()) + ) + } +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/projectiles/ProjectileAngleCalculator.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/projectiles/ProjectileAngleCalculator.kt new file mode 100644 index 00000000000..b85e74bd254 --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/projectiles/ProjectileAngleCalculator.kt @@ -0,0 +1,44 @@ +package net.ccbluex.liquidbounce.utils.aiming.projectiles + +import net.ccbluex.liquidbounce.utils.aiming.Rotation +import net.ccbluex.liquidbounce.utils.client.player +import net.ccbluex.liquidbounce.utils.entity.ConstantPositionExtrapolation +import net.ccbluex.liquidbounce.utils.entity.PositionExtrapolation +import net.ccbluex.liquidbounce.utils.render.trajectory.TrajectoryInfo +import net.minecraft.entity.EntityDimensions +import net.minecraft.entity.LivingEntity +import net.minecraft.util.math.Vec3d + +/** + * Calculates the shooting angle which hits the supplied target + */ +abstract class ProjectileAngleCalculator { + abstract fun calculateAngleFor( + projectileInfo: TrajectoryInfo, + sourcePos: Vec3d, + targetPosFunction: PositionExtrapolation, + targetShape: EntityDimensions, + ): Rotation? + + fun calculateAngleForStaticTarget( + projectileInfo: TrajectoryInfo, + target: Vec3d, + shape: EntityDimensions + ): Rotation? { + return this.calculateAngleFor( + projectileInfo, + sourcePos = player.eyePos, + targetPosFunction = ConstantPositionExtrapolation(target), + targetShape = shape + ) + } + + fun calculateAngleForEntity(projectileInfo: TrajectoryInfo, entity: LivingEntity): Rotation? { + return this.calculateAngleFor( + projectileInfo, + sourcePos = player.eyePos, + targetPosFunction = PositionExtrapolation.getBestForEntity(entity), + targetShape = entity.dimensions + ) + } +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/projectiles/ProjectileTargetPointFinder.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/projectiles/ProjectileTargetPointFinder.kt new file mode 100644 index 00000000000..7e11579ea13 --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/projectiles/ProjectileTargetPointFinder.kt @@ -0,0 +1,43 @@ +package net.ccbluex.liquidbounce.utils.aiming.projectiles + +import net.ccbluex.liquidbounce.features.module.modules.combat.aimbot.ModuleProjectileAimbot +import net.ccbluex.liquidbounce.features.module.modules.render.ModuleDebug +import net.ccbluex.liquidbounce.utils.aiming.findVisiblePointFromVirtualEye +import net.minecraft.util.math.Box +import net.minecraft.util.math.Vec3d +import java.text.DecimalFormat + +/** + * Utility class which finds a visible (= hittable) point on the target. + */ +object ProjectileTargetPointFinder { + fun findHittablePosition( + playerHeadPosition: Vec3d, + directionOnImpact: Vec3d, + entityPositionOnImpact: Vec3d, + targetEntityBox: Box + ): Vec3d? { + val virtualEyes = playerHeadPosition.add( + 0.0, + directionOnImpact.y * -(playerHeadPosition.distanceTo(entityPositionOnImpact)), + 0.0 + ) + val currTime = System.nanoTime() + + val bestPos = findVisiblePointFromVirtualEye(virtualEyes, targetEntityBox, 5.0) ?: run { + logRaytraceTime(currTime) + return null + } + + logRaytraceTime(currTime) + return bestPos + } + + private fun logRaytraceTime(currTime: Long) { + val deltaNanos = System.nanoTime() - currTime + + val formattedNumber = DecimalFormat("0.00").format(deltaNanos / 1E6) + + ModuleDebug.debugParameter(ModuleProjectileAimbot, "raytraceTime", "${formattedNumber}us") + } +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/projectiles/SituationalProjectileAngleCalculator.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/projectiles/SituationalProjectileAngleCalculator.kt new file mode 100644 index 00000000000..435a90b88c8 --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/projectiles/SituationalProjectileAngleCalculator.kt @@ -0,0 +1,29 @@ +package net.ccbluex.liquidbounce.utils.aiming.projectiles + +import net.ccbluex.liquidbounce.utils.aiming.Rotation +import net.ccbluex.liquidbounce.utils.entity.PositionExtrapolation +import net.ccbluex.liquidbounce.utils.render.trajectory.TrajectoryInfo +import net.minecraft.entity.EntityDimensions +import net.minecraft.util.math.Vec3d + +/** + * Uses the best available implementation of [ProjectileAngleCalculator] + */ +object SituationalProjectileAngleCalculator: ProjectileAngleCalculator() { + override fun calculateAngleFor( + projectileInfo: TrajectoryInfo, + sourcePos: Vec3d, + targetPosFunction: PositionExtrapolation, + targetShape: EntityDimensions + ): Rotation? { + val basePos = targetPosFunction.getPositionInTicks(0.0) + + val actualImplementation = when { + // Our flagship implementation is unstable at low distances... + basePos.distanceTo(sourcePos) < 5.0 -> PolynomialProjectileAngleCalculator + else -> CydhranianProjectileAngleCalculator + } + + return actualImplementation.calculateAngleFor(projectileInfo, sourcePos, targetPosFunction, targetShape) + } +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/block/ChunkScanner.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/block/ChunkScanner.kt index 98c285bf389..49c780ad56d 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/utils/block/ChunkScanner.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/block/ChunkScanner.kt @@ -291,7 +291,7 @@ object ChunkScanner : EventListener, MinecraftShortcuts { fun recordBlock(pos: BlockPos, state: BlockState, cleared: Boolean) /** - * Is called when a chunk is loaded or entirely updated. + * Is called when a chunk is initially loaded or entirely updated. */ fun chunkUpdate(x: Int, z: Int) fun clearChunk(x: Int, z: Int) diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/block/ImmutableBlockPos.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/block/ImmutableBlockPos.kt new file mode 100644 index 00000000000..729f6ff57d8 --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/block/ImmutableBlockPos.kt @@ -0,0 +1,8 @@ +package net.ccbluex.liquidbounce.utils.block + +import net.minecraft.util.math.BlockPos + +/** + * Prevents bugs where a mutable block pos is put in a position where an immutable is expected. + */ +class ImmutableBlockPos(pos: BlockPos): BlockPos(pos) diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/block/Region.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/block/Region.kt index 5794f8ca9f1..6e9663de825 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/utils/block/Region.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/block/Region.kt @@ -26,6 +26,7 @@ import net.minecraft.world.chunk.Chunk import kotlin.math.max import kotlin.math.min +@Suppress("detekt:TooManyFunctions") class Region(from: BlockPos, to: BlockPos) : ClosedRange, Iterable by BlockPos.iterate(from, to) { override val endInclusive: BlockPos @@ -157,6 +158,21 @@ class Region(from: BlockPos, to: BlockPos) : ClosedRange, Iterable [${this.to.x},${this.to.y},${this.to.z}]" } diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/block/targetfinding/FaceTargetPositionFactory.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/block/targetfinding/FaceTargetPositionFactory.kt index 2b343a08228..a2f81f46123 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/utils/block/targetfinding/FaceTargetPositionFactory.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/block/targetfinding/FaceTargetPositionFactory.kt @@ -24,7 +24,8 @@ import net.ccbluex.liquidbounce.render.engine.Color4b import net.ccbluex.liquidbounce.utils.aiming.RotationManager import net.ccbluex.liquidbounce.utils.client.player import net.ccbluex.liquidbounce.utils.client.toRadians -import net.ccbluex.liquidbounce.utils.math.geometry.Face +import net.ccbluex.liquidbounce.utils.kotlin.step +import net.ccbluex.liquidbounce.utils.math.geometry.AlignedFace import net.ccbluex.liquidbounce.utils.math.geometry.Line import net.ccbluex.liquidbounce.utils.math.geometry.NormalizedPlane import net.ccbluex.liquidbounce.utils.math.rangeTo @@ -51,16 +52,16 @@ abstract class FaceTargetPositionFactory { * Samples a position (relative to [targetPos]). * @param face is relative to origin. */ - abstract fun producePositionOnFace(face: Face, targetPos: BlockPos): Vec3d + abstract fun producePositionOnFace(face: AlignedFace, targetPos: BlockPos): Vec3d - protected fun getFaceRelativeToTargetPosition(face: Face, targetPos: BlockPos): Face { + protected fun getFaceRelativeToTargetPosition(face: AlignedFace, targetPos: BlockPos): AlignedFace { return face.offset(Vec3d.of(targetPos).negate()) } /** * Trims a face to be only as wide as the config allows it to be */ - protected fun trimFace(face: Face): Face { + protected fun trimFace(face: AlignedFace): AlignedFace { val offsets = face.dimensions.multiply(0.15) var rangeX = face.from.x + offsets.x..face.to.x - offsets.x @@ -77,7 +78,7 @@ abstract class FaceTargetPositionFactory { rangeZ = face.center.z..face.center.z } - val trimmedFace = Face( + val trimmedFace = AlignedFace( Vec3d( face.from.x.coerceIn(rangeX), face.from.y.coerceIn(rangeY), @@ -93,7 +94,7 @@ abstract class FaceTargetPositionFactory { return trimmedFace } - protected fun getPositionsOnFace(face: Face, step: Double): List { + protected fun getPositionsOnFace(face: AlignedFace, step: Double): List { // Collects all possible rotations return (face.from..face.to step step).map { Vec3d(it[0], it[1], it[2]) }.toList() } @@ -104,7 +105,7 @@ abstract class FaceTargetPositionFactory { * Always targets the point with the nearest rotation angle to the current rotation angle */ class NearestRotationTargetPositionFactory(val config: PositionFactoryConfiguration) : FaceTargetPositionFactory() { - override fun producePositionOnFace(face: Face, targetPos: BlockPos): Vec3d { + override fun producePositionOnFace(face: AlignedFace, targetPos: BlockPos): Vec3d { val trimmedFace = trimFace(face) return aimAtNearestPointToRotationLine(targetPos, trimmedFace) @@ -112,7 +113,7 @@ class NearestRotationTargetPositionFactory(val config: PositionFactoryConfigurat fun aimAtNearestPointToRotationLine( targetPos: BlockPos, - face: Face + face: AlignedFace ): Vec3d { if (MathHelper.approximatelyEquals(face.area, 0.0)) return face.from @@ -161,10 +162,10 @@ class StabilizedRotationTargetPositionFactory( val config: PositionFactoryConfiguration, private val optimalLine: Line? ) : FaceTargetPositionFactory() { - override fun producePositionOnFace(face: Face, targetPos: BlockPos): Vec3d { + override fun producePositionOnFace(face: AlignedFace, targetPos: BlockPos): Vec3d { val trimmedFace = trimFace(face).offset(Vec3d.of(targetPos)) - val targetFace = getTargetFace(player, trimmedFace, face) ?: trimmedFace + val targetFace = getTargetFace(player, trimmedFace) ?: trimmedFace return NearestRotationTargetPositionFactory(this.config).aimAtNearestPointToRotationLine( targetPos, @@ -174,9 +175,8 @@ class StabilizedRotationTargetPositionFactory( private fun getTargetFace( player: ClientPlayerEntity, - trimmedFace: Face, - face: Face - ): Face? { + trimmedFace: AlignedFace + ): AlignedFace? { val optimalLine = optimalLine ?: return null val nearsetPointToOptimalLine = optimalLine.getNearestPointTo(player.pos) @@ -207,7 +207,7 @@ class StabilizedRotationTargetPositionFactory( } class RandomTargetPositionFactory(val config: PositionFactoryConfiguration) : FaceTargetPositionFactory() { - override fun producePositionOnFace(face: Face, targetPos: BlockPos): Vec3d { + override fun producePositionOnFace(face: AlignedFace, targetPos: BlockPos): Vec3d { val trimmedFace = trimFace(face) return trimmedFace.randomPointOnFace() @@ -215,13 +215,13 @@ class RandomTargetPositionFactory(val config: PositionFactoryConfiguration) : Fa } object CenterTargetPositionFactory : FaceTargetPositionFactory() { - override fun producePositionOnFace(face: Face, targetPos: BlockPos): Vec3d { + override fun producePositionOnFace(face: AlignedFace, targetPos: BlockPos): Vec3d { return face.center } } class ReverseYawTargetPositionFactory(val config: PositionFactoryConfiguration) : FaceTargetPositionFactory() { - override fun producePositionOnFace(face: Face, targetPos: BlockPos): Vec3d { + override fun producePositionOnFace(face: AlignedFace, targetPos: BlockPos): Vec3d { val trimmedFace = trimFace(face) val reverseYawRotation = aimAtNearestPointToReverseYaw(targetPos, trimmedFace) @@ -235,7 +235,7 @@ class ReverseYawTargetPositionFactory(val config: PositionFactoryConfiguration) fun aimAtNearestPointToReverseYaw( targetPos: BlockPos, - face: Face + face: AlignedFace ): Vec3d? { if (MathHelper.approximatelyEquals(face.area, 0.0)) return face.from diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/block/targetfinding/TargetFinding.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/block/targetfinding/TargetFinding.kt index 123a6fa016f..303e3e59f79 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/utils/block/targetfinding/TargetFinding.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/block/targetfinding/TargetFinding.kt @@ -28,7 +28,7 @@ import net.ccbluex.liquidbounce.utils.client.getFace import net.ccbluex.liquidbounce.utils.client.mc import net.ccbluex.liquidbounce.utils.client.player import net.ccbluex.liquidbounce.utils.client.world -import net.ccbluex.liquidbounce.utils.math.geometry.Face +import net.ccbluex.liquidbounce.utils.math.geometry.AlignedFace import net.minecraft.block.BlockState import net.minecraft.block.ShapeContext import net.minecraft.block.SideShapeType @@ -171,7 +171,7 @@ fun getTargetPlanForPositionAndDirection( } } -class PointOnFace(val face: Face, val point: Vec3d) +class PointOnFace(val face: AlignedFace, val point: Vec3d) fun findBestBlockPlacementTarget(pos: BlockPos, options: BlockPlacementTargetFindingOptions): BlockPlacementTarget? { val state = pos.getState()!! diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/client/MathExtensions.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/client/MathExtensions.kt index 59ff17ccb0e..13f85a73c13 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/utils/client/MathExtensions.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/client/MathExtensions.kt @@ -18,7 +18,7 @@ */ package net.ccbluex.liquidbounce.utils.client -import net.ccbluex.liquidbounce.utils.math.geometry.Face +import net.ccbluex.liquidbounce.utils.math.geometry.AlignedFace import net.minecraft.util.math.Box import net.minecraft.util.math.Direction import net.minecraft.util.math.Vec3d @@ -26,34 +26,34 @@ import net.minecraft.util.math.Vec3d fun Float.toRadians() = this / 180.0F * Math.PI.toFloat() fun Float.toDegrees() = this / Math.PI.toFloat() * 180.0F -fun Box.getFace(direction: Direction): Face { +fun Box.getFace(direction: Direction): AlignedFace { return when (direction) { - Direction.DOWN -> Face( + Direction.DOWN -> AlignedFace( Vec3d(this.minX, this.minY, this.minZ), Vec3d(this.maxX, this.minY, this.maxZ) ) - Direction.UP -> Face( + Direction.UP -> AlignedFace( Vec3d(this.minX, this.maxY, this.minZ), Vec3d(this.maxX, this.maxY, this.maxZ) ) - Direction.SOUTH -> Face( + Direction.SOUTH -> AlignedFace( Vec3d(this.minX, this.minY, this.maxZ), Vec3d(this.maxX, this.maxY, this.maxZ) ) - Direction.NORTH -> Face( + Direction.NORTH -> AlignedFace( Vec3d(this.minX, this.minY, this.minZ), Vec3d(this.maxX, this.maxY, this.minZ) ) - Direction.EAST -> Face( + Direction.EAST -> AlignedFace( Vec3d(this.maxX, this.minY, this.minZ), Vec3d(this.maxX, this.maxY, this.maxZ) ) - Direction.WEST -> Face( + Direction.WEST -> AlignedFace( Vec3d(this.minX, this.minY, this.minZ), Vec3d(this.minX, this.maxY, this.maxZ) ) diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/entity/PositionExtrapolation.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/entity/PositionExtrapolation.kt new file mode 100644 index 00000000000..523d5d6f708 --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/entity/PositionExtrapolation.kt @@ -0,0 +1,56 @@ +package net.ccbluex.liquidbounce.utils.entity + +import net.ccbluex.liquidbounce.utils.math.minus +import net.ccbluex.liquidbounce.utils.math.plus +import net.ccbluex.liquidbounce.utils.math.times +import net.fabricmc.fabric.impl.`object`.builder.FabricEntityTypeImpl.Builder.Living +import net.minecraft.entity.LivingEntity +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.util.math.Vec3d +import kotlin.math.round + +/** + * A utility which predicts the position of something in n ticks. + */ +interface PositionExtrapolation { + fun getPositionInTicks(ticks: Double): Vec3d + + companion object { + fun getBestForEntity(target: LivingEntity): PositionExtrapolation { + return when (target) { + is PlayerEntity -> PlayerSimulationExtrapolation(target) + else -> LinearPositionExtrapolation(target) + } + } + } +} + +class ConstantPositionExtrapolation(private val pos: Vec3d) : PositionExtrapolation { + override fun getPositionInTicks(ticks: Double): Vec3d { + return pos + } + +} + +/** + * A utility class which assumes that the subject is moving at a specified speed. + */ +class LinearPositionExtrapolation( + private val basePosition: Vec3d, + private val velocity: Vec3d +) : PositionExtrapolation { + constructor(entity: LivingEntity) : this(entity.pos, entity.pos - entity.prevPos) + + override fun getPositionInTicks(ticks: Double): Vec3d { + return basePosition + velocity * ticks + } + +} + +class PlayerSimulationExtrapolation(private val simulation: SimulatedPlayerCache) : PositionExtrapolation { + constructor(player: PlayerEntity) : this(PlayerSimulationCache.getSimulationForOtherPlayers(player)) + + override fun getPositionInTicks(ticks: Double): Vec3d { + return this.simulation.getSnapshotAt(round(ticks.coerceAtMost(30.0)).toInt()).pos + } +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/math/Bisect.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/math/Bisect.kt new file mode 100644 index 00000000000..8ba377e6299 --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/math/Bisect.kt @@ -0,0 +1,35 @@ +package net.ccbluex.liquidbounce.utils.math + +/** + * Finds the minimum between min and max. + */ +inline fun findFunctionMinimumByBisect( + range: ClosedFloatingPointRange, + minDelta: Double = 1E-4, + function: (Double) -> Double +): Pair { + var lowerBound = range.start + var upperBound = range.endInclusive + + var t = 0 + + while (upperBound - lowerBound > minDelta) { + val mid = (lowerBound + upperBound) / 2 + + val leftValue = function((lowerBound + mid) / 2) + val rightValue = function((mid + upperBound) / 2) + + if (leftValue < rightValue) { + upperBound = mid + } else { + lowerBound = mid + } + + t++ + } + + val x = (lowerBound + upperBound) / 2 + val y = function(x) + + return x to y +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/math/geometry/Face.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/math/geometry/AlignedFace.kt similarity index 94% rename from src/main/kotlin/net/ccbluex/liquidbounce/utils/math/geometry/Face.kt rename to src/main/kotlin/net/ccbluex/liquidbounce/utils/math/geometry/AlignedFace.kt index 277c872ba12..68c3eec6f9f 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/utils/math/geometry/Face.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/math/geometry/AlignedFace.kt @@ -28,7 +28,7 @@ import kotlin.random.Random /** * A face. Axis aligned */ -class Face(from: Vec3d, to: Vec3d) { +class AlignedFace(from: Vec3d, to: Vec3d) { val from: Vec3d = Vec3d( min(from.x, to.x), min(from.y, to.y), @@ -63,7 +63,7 @@ class Face(from: Vec3d, to: Vec3d) { /** * If this face is empty, return null, otherwise return this face */ - fun requireNonEmpty(): Face? { + fun requireNonEmpty(): AlignedFace? { if (MathHelper.approximatelyEquals(this.area, 0.0)) { return null } @@ -71,8 +71,8 @@ class Face(from: Vec3d, to: Vec3d) { return this } - fun truncateY(minY: Double): Face { - val newFace = Face( + fun truncateY(minY: Double): AlignedFace { + val newFace = AlignedFace( Vec3d(this.from.x, this.from.y.coerceAtLeast(minY), this.from.z), Vec3d(this.to.x, this.to.y.coerceAtLeast(minY), this.to.z) ) @@ -80,7 +80,7 @@ class Face(from: Vec3d, to: Vec3d) { return newFace } - fun clamp(box: Box): Face { + fun clamp(box: Box): AlignedFace { val xRange = box.minX..box.maxX val yRange = box.minY..box.maxY val zRange = box.minZ..box.maxZ @@ -96,11 +96,11 @@ class Face(from: Vec3d, to: Vec3d) { this.to.z.coerceIn(zRange) ) - return Face(newFrom, newTo) + return AlignedFace(newFrom, newTo) } - fun offset(vec: Vec3d): Face { - return Face(this.from.add(vec), this.to.add(vec)) + fun offset(vec: Vec3d): AlignedFace { + return AlignedFace(this.from.add(vec), this.to.add(vec)) } diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/math/geometry/Line.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/math/geometry/Line.kt index ee26faa59ab..a639ecd861f 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/utils/math/geometry/Line.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/math/geometry/Line.kt @@ -25,6 +25,15 @@ import kotlin.math.abs open class Line(val position: Vec3d, val direction: Vec3d) { + companion object { + fun fromPoints(p1: Vec3d, p2: Vec3d, normalized: Boolean = false): Line { + val direction = p2.subtract(p1) + val finalDirection = if (normalized) direction.normalize() else direction + + return Line(p1, finalDirection) + } + } + open fun getNearestPointTo(point: Vec3d): Vec3d { val plane = NormalizedPlane(point, direction) diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/math/geometry/PlaneSection.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/math/geometry/PlaneSection.kt new file mode 100644 index 00000000000..c33c722a87a --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/math/geometry/PlaneSection.kt @@ -0,0 +1,31 @@ +package net.ccbluex.liquidbounce.utils.math.geometry + +import net.ccbluex.liquidbounce.utils.kotlin.step +import net.ccbluex.liquidbounce.utils.math.plus +import net.ccbluex.liquidbounce.utils.math.times +import net.minecraft.util.math.Vec3d +import kotlin.jvm.optionals.getOrNull +import kotlin.math.sqrt + +class PlaneSection( + val originPoint: Vec3d, + val dirVec1: Vec3d, + val dirVec2: Vec3d +) { + + inline fun castPointsOnUniformly(maxPoints: Int, consumer: (Vec3d) -> Unit) { + val nPoints = maxPoints + val aspectRatio = this.dirVec2.length() / this.dirVec1.length() + val dz = sqrt(1 / (aspectRatio * nPoints)) + val dy = sqrt(aspectRatio / nPoints) + + for (y in 0.0..1.0 step dy) { + for (z in 0.0..1.0 step dz) { + val point = this.originPoint + this.dirVec1 * y + this.dirVec2 * z + + consumer(point) + } + } + } + +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/render/WorldToScreen.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/render/WorldToScreen.kt index 64009dd9eaa..40df3078425 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/utils/render/WorldToScreen.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/render/WorldToScreen.kt @@ -22,14 +22,19 @@ import com.mojang.blaze3d.systems.RenderSystem import net.ccbluex.liquidbounce.event.EventListener import net.ccbluex.liquidbounce.event.events.WorldRenderEvent import net.ccbluex.liquidbounce.event.handler +import net.ccbluex.liquidbounce.features.module.modules.combat.aimbot.ModuleProjectileAimbot +import net.ccbluex.liquidbounce.features.module.modules.render.ModuleDebug import net.ccbluex.liquidbounce.render.engine.Vec3 import net.ccbluex.liquidbounce.utils.client.mc +import net.ccbluex.liquidbounce.utils.math.geometry.Line import net.ccbluex.liquidbounce.utils.math.minus +import net.minecraft.util.math.Vec2f import net.minecraft.util.math.Vec3d import org.joml.Matrix4f import org.joml.Vector3f +import java.text.NumberFormat -object WorldToScreen: EventListener { +object WorldToScreen : EventListener { private var mvMatrix: Matrix4f? = null private var projectionMatrix: Matrix4f? = null @@ -64,4 +69,29 @@ object WorldToScreen: EventListener { return if (transformedPos.z < 1.0F) Vec3(screenPos.x, screenPos.y, transformedPos.z) else null } + fun calculateMouseRay(posOnScreen: Vec2f, cameraPos: Vec3d = mc.gameRenderer.camera.pos): Line { + val screenVec = Vec3d(posOnScreen.x.toDouble(), posOnScreen.y.toDouble(), 1.0) + + val scaleFactor = mc.window.scaleFactor + val guiScaleMul = 0.5f / scaleFactor.toFloat() + + val transformedPos = screenVec.multiply( + 1.0 / (guiScaleMul * mc.framebuffer.viewportWidth), + 1.0 / (guiScaleMul * mc.framebuffer.viewportHeight), + 1.0 + ).subtract(1.0, 1.0, 0.0).multiply(1.0, -1.0, 1.0) + + val transformMatrix = Matrix4f(projectionMatrix).mul(mvMatrix).invert() + val relativePos = transformMatrix.transformProject( + transformedPos.x.toFloat(), + transformedPos.y.toFloat(), + transformedPos.z.toFloat(), + Vector3f() + ) + + ModuleDebug.debugParameter(ModuleProjectileAimbot, "s2w", relativePos.toString(NumberFormat.getInstance())) + + return Line(cameraPos, Vec3d(relativePos.x.toDouble(), relativePos.y.toDouble(), relativePos.z.toDouble())) + } + } diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/render/trajectory/TrajectoryData.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/render/trajectory/TrajectoryData.kt index 3a47005a9ae..3db7902e5fc 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/utils/render/trajectory/TrajectoryData.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/render/trajectory/TrajectoryData.kt @@ -1,6 +1,7 @@ package net.ccbluex.liquidbounce.utils.render.trajectory import net.ccbluex.liquidbounce.render.engine.Color4b +import net.ccbluex.liquidbounce.utils.client.player import net.minecraft.entity.Entity import net.minecraft.entity.player.PlayerEntity import net.minecraft.entity.projectile.AbstractFireballEntity @@ -17,11 +18,14 @@ object TrajectoryData { fun getRenderedTrajectoryInfo(player: PlayerEntity, item: Item, alwaysShowBow: Boolean): TrajectoryInfo? { return when (item) { is BowItem -> { - val v0 = calculateInitialVelocityOrArrow(player, alwaysShowBow) ?: return null + val useTime = if (alwaysShowBow && player.itemUseTime < 1) { + 40 + } else { + player.itemUseTime + } - TrajectoryInfo.BOW_FULL_PULL.copy(initialVelocity = v0) + TrajectoryInfo.bowWithUsageDuration(useTime) } - is CrossbowItem -> TrajectoryInfo.BOW_FULL_PULL is FishingRodItem -> TrajectoryInfo.FISHING_ROD is ThrowablePotionItem -> TrajectoryInfo.POTION @@ -75,23 +79,6 @@ object TrajectoryData { } } -private fun calculateInitialVelocityOrArrow(player: PlayerEntity, alwaysShowBow: Boolean): Double? { - val inUseTime = player.itemUseTime - - if (alwaysShowBow && inUseTime < 1.0F) { - return 1.0 - } - - // Calculate power of bow - var power = player.itemUseTime / 20f - power = (power * power + power * 2F) / 3F - - if (power < 0.1F) { - return null - } - - return power.coerceAtMost(1.0F) * TrajectoryInfo.BOW_FULL_PULL.initialVelocity -} data class TrajectoryInfo( val gravity: Double, @@ -115,5 +102,19 @@ data class TrajectoryInfo( val BOW_FULL_PULL = PERSISTENT.copy(initialVelocity = 3.0) val FIREBALL = TrajectoryInfo(gravity = 0.0, hitboxRadius = 1.0) val WIND_CHARGE = TrajectoryInfo(gravity = 0.0, hitboxRadius = 1.0, copiesPlayerVelocity = false) + + fun bowWithUsageDuration(usageDuration: Int = player.itemUseTime): TrajectoryInfo? { + // Calculate the power of bow + var power = usageDuration / 20f + power = (power * power + power * 2F) / 3F + + if (power < 0.1F) { + return null + } + + val v0 = power.coerceAtMost(1.0F) * BOW_FULL_PULL.initialVelocity + + return BOW_FULL_PULL.copy(initialVelocity = v0) + } } } diff --git a/src/test/kotlin/net/ccbluex/liquidbounce/utils/aiming/PointFindingKtTest.kt b/src/test/kotlin/net/ccbluex/liquidbounce/utils/aiming/PointFindingKtTest.kt new file mode 100644 index 00000000000..b5058ac3f41 --- /dev/null +++ b/src/test/kotlin/net/ccbluex/liquidbounce/utils/aiming/PointFindingKtTest.kt @@ -0,0 +1,34 @@ +package net.ccbluex.liquidbounce.utils.aiming + +import net.minecraft.util.math.Vec3d +import org.joml.Matrix3f +import org.junit.jupiter.api.Test +import kotlin.math.atan2 +import kotlin.math.hypot + +class PointFindingKtTest { + + @Test + fun testPlanePointConstruction() { + val normalVec = Vec3d(-1.0, 1.0, -1.0).normalize() + + val hypotenuse = hypot(normalVec.x, normalVec.z) + + val yawAtan = atan2(normalVec.z, normalVec.x).toFloat() + val pitchAtan = atan2(normalVec.y, hypotenuse).toFloat() + + val initVec = Vec3d(1.0, 0.0, 0.0) + val rotZ = initVec.rotateZ(-pitchAtan) + val rotY = rotZ.rotateY(-yawAtan) + + val rotMatrix1 = Matrix3f().rotateZ(-pitchAtan) + val rotMatrix2 = Matrix3f().rotateY(yawAtan) + + val totalMatrix = rotMatrix1.mul(rotMatrix2) + + println(rotY.dotProduct(normalVec)) + println(rotY) + println(normalVec.toVector3f().mul(totalMatrix)) + } + +}