local socket = require("socket")

local maf = require 'maf'
local vec3 = maf.vec3
local quat = maf.quat

local samem = require 'SAMemory'

samem.require 'CVehicle'
samem.require 'tHandlingData'
samem.require 'CPlane'
samem.require 'CTransmission'

-- Constants for controlling plane steering behavior
local PITCH_UP_THRESHOLD = -15 -- Pitch angle threshold in degrees
local FINE_STEERING_ANGLE = 135 -- Angle for fine steering adjustments in degrees
local YAW_FACTOR = 0.5 -- Multiplicative factor for yaw adjustment
local ROLL_FACTOR = 1 -- Multiplicative factor for roll adjustment
local STEERING_SPEED = 5.0 -- Speed of steering control in units per second

-- Direction vectors
local DIRECTION = {
FORWARD = vec3(0, 1, 0),
BACKWARD = vec3(0, -1, 0),
RIGHT = vec3(1, 0, 0),
LEFT = vec3(-1, 0, 0),
UP = vec3(0, 0, 1),
DOWN = vec3(0, 0, -1)
}

-- Calculate the signed angle between two vectors with respect to a reference axis
local function calculateSignedAngle(from, to, axis)
local fromNormalized = from:clone():normalize()
local toNormalized = to:clone():normalize()
local dotProduct = fromNormalized:dot(toNormalized)
local angle = math.acos(math.max(-1, math.min(1, dotProduct))) -- Clamp the dot product
local crossProduct = fromNormalized:cross(toNormalized)
local sign = crossProduct:dot(axis) < 0 and -1 or 1
return angle * sign
end

-- Calculate the angle between two vectors in degrees
local function calculateAngleBetweenVectors(v1, v2)
local normV1, normV2 = v1:normalize(), v2:normalize()
local dotProduct = math.max(-1, math.min(1, normV1:dot(normV2))) -- Clamp the dot product
return math.deg(math.acos(dotProduct))
end

-- Obtain the conjugate of a quaternion
local function conjugateQuaternion(q)
return quat(-q.x, -q.y, -q.z, q.w)
end

-- Compute the steering input for the plane to move towards a target position
local function computeSteeringInput(plane, dt, targetPosition)
local planePosition = vec3(getCarCoordinates(plane))
local planeRotation = quat(getVehicleQuaternion(plane))


local positionError = targetPosition - planePosition
local localError = conjugateQuaternion(planeRotation) * positionError

local errorDir = localError:normalize()
local pitchError, rollError, yawError
pitchError = vec3(0, localError.y, localError.z):normalize()
rollError = vec3(localError.x, localError.y, 0):normalize()
yawError = vec3(localError.x, 0, localError.z):normalize()

local targetInput = vec3()
local pitch = calculateSignedAngle(DIRECTION.FORWARD, pitchError, DIRECTION.RIGHT)

if -pitch < PITCH_UP_THRESHOLD then
    pitch = (pitch + 360) % 360
end
targetInput.x = pitch

    local angleError = calculateAngleBetweenVectors(DIRECTION.FORWARD, rollError)
    printStringNow(angleError - (angleError % 1), 10)

    if angleError < FINE_STEERING_ANGLE then
            local yaw = -calculateSignedAngle(DIRECTION.FORWARD, yawError, DIRECTION.UP)
            targetInput.y = yaw * YAW_FACTOR
            
    else
            local roll = calculateSignedAngle(DIRECTION.UP, rollError, DIRECTION.FORWARD)
            targetInput.z = roll * ROLL_FACTOR
    end

targetInput.x = math.max(-1, math.min(1, targetInput.x))
targetInput.y = math.max(-1, math.min(1, targetInput.y))
targetInput.z = math.max(-1, math.min(1, targetInput.z))

local input = vec3()
input.x = math.max(-1, math.min(1, input.x + (targetInput.x - input.x) * STEERING_SPEED * dt))
input.y = math.max(-1, math.min(1, input.y + (targetInput.y - input.y) * STEERING_SPEED * dt))
input.z = math.max(-1, math.min(1, input.z + (targetInput.z - input.z) * STEERING_SPEED * dt))

return input
end

function main()
local lastTime = socket.gettime() -- Initial time setup


while true do
    wait(0)  -- Wait time before loop reiteration

    local currentTime = socket.gettime()
    local dt = currentTime - lastTime
    lastTime = currentTime

    local plane = storeCarCharIsInNoSave(playerPed)
    local planePosition = vec3(getCarCoordinates(plane))
    local camPosition = vec3(getActiveCameraCoordinates())
    local camDirection = (vec3(getActiveCameraPointAt()) - camPosition):normalize() * 10
    local targetPosition = planePosition + camDirection
            
            local wposX, wposY = convert3DCoordsToScreen(targetPosition:unpack())
            --dxDrawRing(posX, posY, radius, width, startAngle, amount, color, absoluteAmount, anglesPerLine)
            dxDrawRing(wposX, wposY, 15, 0.75, 0, 1, 285212671, false, 1 )

    local pitchInput, yawInput, rollInput = computeSteeringInput(plane, dt * 10, targetPosition):unpack()

    if doesVehicleExist(plane) then
        local carPointer = getCarPointer(plane)
        local vehicle = samem.cast("CVehicle *", carPointer)

        if vehicle ~= samem.nullptr and vehicle.nVehicleClass == 0 then
            local plane = samem.cast('CPlane *', vehicle)
            plane.field_988 = yawInput * 2^30
                            print(yawInput)
            --plane.field_98C = 0 * 2^30
            -- plane.field_990 = 0.8 * 2^30
            -- plane.field_9C4 = 1 * 2^30
        --print(yawInput, pitchInput, math.sign(rollInput))
                    end
    end
end
end

function math.sign(x)
if x > 0 then
return 1
elseif x < 0 then
return -1
else
return 0
end
end

function dxDrawRing(posX, posY, radius, width, startAngle, amount, color, absoluteAmount, anglesPerLine)
if type(posX) ~= "number" or type(posY) ~= "number" or type(startAngle) ~= "number" or type(amount) ~= "number" then
print("Error: One of the required parameters (posX, posY, startAngle, amount) is not a number.")
return false
end


radius = type(radius) == "number" and radius or 50
width = type(width) == "number" and width or 5
color = color or 0xFFFFFFFF
absoluteAmount = type(absoluteAmount) == "boolean" and absoluteAmount or false
anglesPerLine = type(anglesPerLine) == "number" and anglesPerLine or 1

local stopAngle = absoluteAmount and (amount + startAngle) or ((amount * 360) + startAngle)

for angle = startAngle, stopAngle, anglesPerLine do
    local radAngle = math.rad(angle)
    local startX = math.cos(radAngle) * (radius - width)
    local startY = math.sin(radAngle) * (radius - width)
    local endX = math.cos(radAngle) * (radius + width)
    local endY = math.sin(radAngle) * (radius + width)

    renderDrawLine(startX + posX, startY + posY, endX + posX, endY + posY, width, color)
end

return math.floor((stopAngle - startAngle) / anglesPerLine)
end

когда самолет близок к таргету - он сильно дергается, пытаясь попасть в таргет, потому что слишком сильно поворачивает. когда самолет слишком далеко от таргета - он поворачивает слишком медленно.

речь про targetInput.y

как это исправить?