diff --git a/build.gradle b/build.gradle index 16735ca9132..ce9673a744d 100644 --- a/build.gradle +++ b/build.gradle @@ -160,6 +160,11 @@ dependencies { // includeDependency "org.graalvm.polyglot:ruby-community:24.0.2" // includeDependency "org.graalvm.polyglot:llvm-native-community:24.0.2" + // Kotlin DL + implementation 'org.jetbrains.kotlinx:kotlin-deeplearning-tensorflow:0.5.2' + implementation 'org.jetbrains.kotlinx:kotlin-deeplearning-dataset:0.5.2' + implementation 'org.tensorflow:libtensorflow:1.15.0' + // HTTP library includeDependency ("com.squareup.okhttp3:okhttp:5.0.0-alpha.14") { exclude group: "org.jetbrains.kotlin", module: "kotlin-stdlib" diff --git a/src/main/kotlin/DataModelTrainer.kt b/src/main/kotlin/DataModelTrainer.kt new file mode 100644 index 00000000000..3dd9218e621 --- /dev/null +++ b/src/main/kotlin/DataModelTrainer.kt @@ -0,0 +1,182 @@ +import com.google.gson.Gson +import com.google.gson.annotations.SerializedName +import com.google.gson.reflect.TypeToken +import net.ccbluex.liquidbounce.utils.aiming.data.Rotation +import net.ccbluex.liquidbounce.utils.kotlin.random +import org.jetbrains.kotlinx.dl.api.core.SavingFormat +import org.jetbrains.kotlinx.dl.api.core.Sequential +import org.jetbrains.kotlinx.dl.api.core.WritingMode +import org.jetbrains.kotlinx.dl.api.core.activation.Activations +import org.jetbrains.kotlinx.dl.api.core.initializer.HeNormal +import org.jetbrains.kotlinx.dl.api.core.layer.core.Dense +import org.jetbrains.kotlinx.dl.api.core.layer.core.Input +import org.jetbrains.kotlinx.dl.api.core.loss.Losses +import org.jetbrains.kotlinx.dl.api.core.metric.Metrics +import org.jetbrains.kotlinx.dl.api.core.optimizer.Adam +import org.jetbrains.kotlinx.dl.dataset.OnHeapDataset +import java.io.File +import kotlin.math.abs + +data class Vector(val x: Double, val y: Double, val z: Double) +data class Vec2f(val x: Float, val y: Float) +data class TrainingData( + @SerializedName("c_vector") + val currentVector: Vector, + @SerializedName("w_vector") + val targetVector: Vector, + val delta: Vec2f, + val distance: Double +) + +fun main() { + val linearTrainingData = generateTrainingData() + println("Generated ${linearTrainingData.size} training samples") + val trainingData = readTrainingDataFromFolder("./LiquidBounce/debug-recorder/Aim") + println("Read ${trainingData.size} training samples") + + val dataset = prepareData(linearTrainingData + trainingData) + val modelYaw = createAndTrainModel(dataset.features, dataset.labelX) + val modelPitch = createAndTrainModel(dataset.features, dataset.labelY) + + // Save the models + saveModel(modelYaw, "yaw_model") + saveModel(modelPitch, "pitch_model") + + println("Models trained and saved successfully.") +} + +const val SAMPLES = 100 + +/** + * Linear train data generator + */ +fun generateTrainingData(): List { + val trainingData = mutableListOf() + + repeat(SAMPLES) { + val targetYaw = (-180..180).random() + val targetPitch = (-5..10).random() + + val distance = (0f..4f).random() + println("Generating training data for distance $distance, yaw $targetYaw, pitch $targetPitch") + + val targetRotation = Rotation(targetYaw.toFloat(), targetPitch.toFloat()) + + val clientYaw = (-180..180).random() + val clientPitch = (-80..80).random() + var clientRotation = Rotation(clientYaw.toFloat(), clientPitch.toFloat()) + + while (clientRotation.angleTo(targetRotation) > 1.0E-5f) { + val diff = clientRotation.rotationDeltaTo(targetRotation) + val rotationDifference = diff.length() + val factorH = (40f..60f).random() + val straightLineYaw = (abs(diff.deltaYaw / rotationDifference) * factorH).toFloat() + val factorV = (40f..60f).random() + val straightLinePitch = (abs(diff.deltaPitch / rotationDifference) * factorV).toFloat() + + val nextClientRotation = Rotation( + clientRotation.yaw + diff.deltaYaw.coerceIn(-straightLineYaw, straightLineYaw), + clientRotation.pitch + diff.deltaPitch.coerceIn(-straightLinePitch, straightLinePitch) + ) + val diff2 = clientRotation.rotationDeltaTo(nextClientRotation) + + trainingData += TrainingData( + Vector( + clientRotation.directionVector.x, + clientRotation.directionVector.y, + clientRotation.directionVector.z + ), + Vector( + targetRotation.directionVector.x, + targetRotation.directionVector.y, + targetRotation.directionVector.z + ), + Vec2f(diff2.deltaYaw, diff2.deltaPitch), + distance + ) + + clientRotation = nextClientRotation + } + } + + println("Generated ${trainingData.size} training samples") + + return trainingData +} + +fun saveModel(model: Sequential, modelName: String) { + val savePath = File("./models/$modelName") + savePath.mkdirs() + model.save( + modelDirectory = savePath, + savingFormat = SavingFormat.TF_GRAPH_CUSTOM_VARIABLES, + saveOptimizerState = true, + writingMode = WritingMode.OVERRIDE + ) + println("Model saved to ${savePath.absolutePath}") +} + + +fun parseJson(jsonString: String): List { + val gson = Gson() + val listType = object : TypeToken>() {}.type + return gson.fromJson(jsonString, listType) +} + +fun readTrainingDataFromFolder(folderPath: String): List { + val folder = File(folderPath) + require(folder.exists() && folder.isDirectory) { "Invalid folder path: $folderPath" } + + return folder.listFiles { file -> file.isFile && file.extension == "json" } + ?.flatMap { file -> + try { + parseJson(file.readText()) + } catch (e: Exception) { + println("Error parsing file ${file.name}: ${e.message}") + emptyList() + } + } ?: emptyList() +} + +data class Dataset(val features: Array, val labelX: FloatArray, val labelY: FloatArray) + +fun prepareData(trainingData: List): Dataset { + val features = trainingData.map { data -> + floatArrayOf( + data.currentVector.x.toFloat(), data.currentVector.y.toFloat(), data.currentVector.z.toFloat(), + data.targetVector.x.toFloat(), data.targetVector.y.toFloat(), data.targetVector.z.toFloat(), + data.distance.toFloat() + ) + }.toTypedArray() + + return Dataset(features, trainingData.map { data -> + data.delta.x + }.toFloatArray(), trainingData.map { data -> + data.delta.y + }.toFloatArray()) +} + +fun createAndTrainModel(features: Array, labels: FloatArray): Sequential { + val model = Sequential.of( + Input(7), + Dense(64, Activations.Relu, kernelInitializer = HeNormal()), + Dense(32, Activations.Relu, kernelInitializer = HeNormal()), + Dense(1, Activations.Linear) + ) + + val dataset = OnHeapDataset.create(features, labels) + + model.compile( + optimizer = Adam(), + loss = Losses.MSE, + metric = Metrics.MAE + ) + + model.fit( + dataset = dataset, + epochs = 100, + batchSize = 32 + ) + + return model +} diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/misc/debugrecorder/modes/AimDebugRecorder.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/misc/debugrecorder/modes/AimDebugRecorder.kt index 5e2a2d5457a..94730ab3e25 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/misc/debugrecorder/modes/AimDebugRecorder.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/misc/debugrecorder/modes/AimDebugRecorder.kt @@ -22,78 +22,58 @@ package net.ccbluex.liquidbounce.features.module.modules.misc.debugrecorder.modes import com.google.gson.JsonObject +import net.ccbluex.liquidbounce.config.gson.util.json import net.ccbluex.liquidbounce.event.tickHandler import net.ccbluex.liquidbounce.features.module.modules.misc.debugrecorder.ModuleDebugRecorder import net.ccbluex.liquidbounce.utils.aiming.data.Rotation -import net.ccbluex.liquidbounce.utils.combat.shouldBeAttacked -import net.ccbluex.liquidbounce.utils.entity.box +import net.ccbluex.liquidbounce.utils.client.FloatValueProvider +import net.ccbluex.liquidbounce.utils.combat.TargetPriority +import net.ccbluex.liquidbounce.utils.combat.TargetTracker import net.ccbluex.liquidbounce.utils.entity.lastRotation -import net.ccbluex.liquidbounce.utils.entity.prevPos import net.ccbluex.liquidbounce.utils.entity.rotation -import net.ccbluex.liquidbounce.utils.math.minus +import net.ccbluex.liquidbounce.utils.entity.squaredBoxedDistanceTo import net.minecraft.util.hit.EntityHitResult import net.minecraft.util.hit.HitResult object AimDebugRecorder : ModuleDebugRecorder.DebugRecorderMode("Aim") { - val repeatable = tickHandler { + private val targetTracker = TargetTracker(TargetPriority.DIRECTION, FloatValueProvider("Range", 7f, 4f..10f)) + + @Suppress("unused") + private val tickHandler = tickHandler { val playerRotation = player.rotation val playerLastRotation = player.lastRotation - + val target = targetTracker.selectFirst() ?: return@tickHandler val turnSpeed = playerLastRotation.rotationDeltaTo(playerRotation) - val crosshairTarget = mc.crosshairTarget - recordPacket(JsonObject().apply { - addProperty("health", player.health) - addProperty("yaw", playerRotation.yaw) - addProperty("pitch", playerRotation.pitch) - addProperty("last_yaw", playerLastRotation.yaw) - addProperty("last_pitch", playerLastRotation.pitch) - addProperty("turn_speed_h", turnSpeed.deltaYaw) - addProperty("turn_speed_v", turnSpeed.deltaPitch) - - add("velocity", JsonObject().apply { - addProperty("x", player.velocity.x) - addProperty("y", player.velocity.y) - addProperty("z", player.velocity.z) + val cDirVec = player.rotation.directionVector + add("c_vector", json { + "x" to cDirVec.x + "y" to cDirVec.y + "z" to cDirVec.z }) + val rotation = Rotation.lookingAt(point = target.eyePos, from = player.eyePos) + val wDirVec = rotation.directionVector + add("w_vector", json { + "x" to wDirVec.x + "y" to wDirVec.y + "z" to wDirVec.z + }) + add("delta", json { + "x" to turnSpeed.deltaYaw + "y" to turnSpeed.deltaPitch + }) + addProperty("distance", player.squaredBoxedDistanceTo(target)) - world.entities.filter { - it.shouldBeAttacked() && it.distanceTo(player) < 10.0f - }.minByOrNull { - it.distanceTo(player) - }?.let { - val vector = it.pos - player.pos - add("vec", JsonObject().apply { - addProperty("x", vector.x) - addProperty("y", vector.y) - addProperty("z", vector.z) - }) - val velocity = it.pos - it.prevPos - add("velocity", JsonObject().apply { - addProperty("x", velocity.x) - addProperty("y", velocity.y) - addProperty("z", velocity.z) - }) - addProperty("distance", player.distanceTo(it)) - val rotation = Rotation.lookingAt(point = it.box.center, from = player.eyePos) - - val delta = rotation.rotationDeltaTo(playerRotation) - - addProperty("diff_h", delta.deltaYaw) - addProperty("diff_v", delta.deltaPitch) - addProperty("yaw_target", rotation.yaw) - addProperty("pitch_target", rotation.pitch) - - addProperty("crosshair", - if (crosshairTarget?.type == HitResult.Type.ENTITY && crosshairTarget is EntityHitResult) { - crosshairTarget.entity.id == it.id - } else { - false - } - ) - } + val crosshairTarget = mc.crosshairTarget + addProperty("reward", + if (crosshairTarget?.type == HitResult.Type.ENTITY && crosshairTarget is EntityHitResult) { + crosshairTarget.entity.id == target.id + } else { + false + } + ) }) } diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/RotationsConfigurable.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/RotationsConfigurable.kt index 4435f459418..1bfdc9c3fbd 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/RotationsConfigurable.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/RotationsConfigurable.kt @@ -27,7 +27,8 @@ open class RotationsConfigurable( BezierAngleSmoothMode(it), SigmoidAngleSmoothMode(it), ConditionalLinearAngleSmoothMode(it), - AccelerationSmoothMode(it) + AccelerationSmoothMode(it), + TensorflowSmoothMode(it), ) } diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/features/ShortStop.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/features/ShortStop.kt index 824259bb063..7ff61786021 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/features/ShortStop.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/features/ShortStop.kt @@ -22,7 +22,7 @@ class ShortStop(owner: EventListener? = null) // The currently set transition duration, randomized within the defined range private var currentTransitionInDuration = stopDuration.random() val isInStopState: Boolean - get() = enabled && ticksElapsed < currentTransitionInDuration + get() = running && ticksElapsed < currentTransitionInDuration @Suppress("unused") private val gameHandler = handler(priority = FIRST_PRIORITY) { diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/features/anglesmooth/TensorflowSmoothMode.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/features/anglesmooth/TensorflowSmoothMode.kt new file mode 100644 index 00000000000..74d1f503771 --- /dev/null +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/aiming/features/anglesmooth/TensorflowSmoothMode.kt @@ -0,0 +1,91 @@ +package net.ccbluex.liquidbounce.utils.aiming.features.anglesmooth + +import net.ccbluex.liquidbounce.config.types.ChoiceConfigurable +import net.ccbluex.liquidbounce.features.module.modules.render.ModuleDebug +import net.ccbluex.liquidbounce.utils.aiming.data.Rotation +import net.ccbluex.liquidbounce.utils.entity.boxedDistanceTo +import net.minecraft.entity.Entity +import net.minecraft.util.math.Vec3d +import org.jetbrains.kotlinx.dl.api.core.Sequential +import org.jetbrains.kotlinx.dl.api.core.activation.Activations +import org.jetbrains.kotlinx.dl.api.core.initializer.HeNormal +import org.jetbrains.kotlinx.dl.api.core.layer.core.Dense +import org.jetbrains.kotlinx.dl.api.core.layer.core.Input +import org.jetbrains.kotlinx.dl.api.core.loss.Losses +import org.jetbrains.kotlinx.dl.api.core.metric.Metrics +import org.jetbrains.kotlinx.dl.api.core.optimizer.Adam +import java.io.File + +class TensorflowSmoothMode(override val parent: ChoiceConfigurable<*>) : AngleSmoothMode("Tensorflow") { + + companion object { + + init { + // Load TensorFlow JNI libraries from /run directory + System.load(File("./libtensorflow_framework.so.1").absolutePath) + System.load(File("./libtensorflow_jni.so").absolutePath) + } + + // TODO: How do I combine these two models into one? + // Kotlinx DL doesn't support multiple outputs in a single model? + private val yawModel: Sequential = loadModel("./models/yaw_model") + private val pitchModel: Sequential = loadModel("./models/pitch_model") + + private fun loadModel(path: String): Sequential { + return Sequential.of( + Input(7), + Dense(64, Activations.Relu, kernelInitializer = HeNormal()), + Dense(32, Activations.Relu, kernelInitializer = HeNormal()), + Dense(1, Activations.Linear) + ).also { model -> + model.compile( + optimizer = Adam(), + loss = Losses.MSE, + metric = Metrics.MAE + ) + model.loadWeights(File(path), true) + } + } + + } + + override fun limitAngleChange( + rotationFactor: Float, + currentRotation: Rotation, + targetRotation: Rotation, + vec3d: Vec3d?, + entity: Entity? + ): Rotation { + val currentDirectionVector = currentRotation.directionVector + val targetDirectionVector = targetRotation.directionVector + val distance = entity?.let { entity -> player.boxedDistanceTo(entity) } ?: 0.0 + + val predictedYaw = yawModel.predictSoftly(floatArrayOf( + currentDirectionVector.x.toFloat(), currentDirectionVector.y.toFloat(), currentDirectionVector.z.toFloat(), + targetDirectionVector.x.toFloat(), targetDirectionVector.y.toFloat(), targetDirectionVector.z.toFloat(), + distance.toFloat() + ))[0] + + val predictedPitch = pitchModel.predictSoftly(floatArrayOf( + currentDirectionVector.x.toFloat(), currentDirectionVector.y.toFloat(), currentDirectionVector.z.toFloat(), + targetDirectionVector.x.toFloat(), targetDirectionVector.y.toFloat(), targetDirectionVector.z.toFloat(), + distance.toFloat() + ))[0] + + ModuleDebug.debugParameter(this, "Yaw", predictedYaw) + ModuleDebug.debugParameter(this, "Pitch", predictedPitch) + + return Rotation( + currentRotation.yaw + predictedYaw, + currentRotation.pitch + predictedPitch + ) + } + + override fun howLongToReach( + currentRotation: Rotation, + targetRotation: Rotation + ): Int { + return 0 + } + +}