--This script will run on all levels when mod is active.
--Modding documentation: http://teardowngame.com/modding
--API reference: http://teardowngame.com/modding/api.html

function init()
    pi = math.pi

    vehicle = FindVehicle("flyingcar")
    vehicleType = GetTagValue(vehicle, "type")

    vehicleColour = {1, 1, 1} --Thruster smoke colour
    hoverDist = 0 --How far the vehicle hovers from the ground
    maxPower = 0 --The maximum force the thrusters can use to keep vehicle at the hover height
    speed = 0 --The speed of the vehicle
    acceleration = 0 --The acceleration of the vehicle
    maxBoosts = 0 --The amount of boosts the vehicle has
    boostPower = 0 --How much the boosts affect the car (if the vehicle has a high acceleration, this will be less effective)
    boostRefill = 0 --How fast the boosts refil (in boosts per second)
    turnSpeed = 0 --How fast the vehicle turns
    damping = 0 --Vehicle's drag
    angularDamping = 0 --Vehicle's angular drag
    sideDamping = 0 --The higher this value is the less the vehicle will move sideways
    thrusterCount = 0 --How many thrusters the vehicle has

    if vehicleType == "basic" then
        vehicleColour = {0, 1, 1}
        hoverDist = 1.2
        maxPower = 5000
        speed = 0.4
        acceleration = 0.05
        maxBoosts = 4
        boostPower = 1
        boostRefill = 0.5
        turnSpeed = 0.2
        damping = 0.02
        angularDamping = 0.12
        sideDamping = 0.06
        thrusterCount = 4
    elseif vehicleType == "sports" then
        vehicleColour = {1, 1, 0}
        hoverDist = 0.95
        maxPower = 5000
        speed = 0.85
        acceleration = 0.1
        maxBoosts = 4
        boostPower = 1
        boostRefill = 0.5
        turnSpeed = 0.3
        damping = 0.02
        angularDamping = 0.12
        sideDamping = 0.03
        thrusterCount = 4
    elseif vehicleType == "saloon" then
        vehicleColour = {1, 0, 1}
        hoverDist = 0.9
        maxPower = 5000
        speed = 0.4
        acceleration = 0.04
        maxBoosts = 0
        boostPower = 1
        boostRefill = 0.5
        turnSpeed = 0.3
        damping = 0.02
        angularDamping = 0.12
        sideDamping = 0.03
        thrusterCount = 4
    elseif vehicleType == "taxi" then
        vehicleColour = {0, 1, 1}
        hoverDist = 1
        maxPower = 5500
        speed = 0.4
        acceleration = 0.04
        maxBoosts = 0
        boostPower = 1
        boostRefill = 0.5
        turnSpeed = 0.1
        damping = 0.02
        angularDamping = 0.12
        sideDamping = 0.03
        thrusterCount = 6
    end

    body = FindBody("vehiclebody")
    wheelJoint = FindJoint("wheelJoint")
    gearJointL = FindJoint("gearl")
    gearJointR = FindJoint("gearr")
    if vehicleType == "taxi" then
        gearJointLB = FindJoint("gearlb")
        gearJointRB = FindJoint("gearrb")
    end
    thrusters = {}
    thrusterJoints = {}
    thrusterPos = {}
    thrusterEnabled = {}
    for i=1, thrusterCount do 
        thrusters[i] = FindBody("thruster" .. i)
        thrusterEnabled[i] = true
        thrusterJoints[i] = FindJoint("thruster" .. i)
        thrusterPos[i] = TransformToLocalVec(GetBodyTransform(body), VecSub(GetBodyTransform(thrusters[i]).pos, GetBodyTransform(body).pos))
        SetJointMotorTarget(thrusterJoints[i], 0, 1)
    end
    meters = {}
    for i=1, maxBoosts do
        meters[i] = FindShape("meter" .. i)
    end
    active = true
    turning = 0
    driveSpeed = 0
    driveTime = -1
    boosts = maxBoosts
    activeBefore = true
    thrusterVoxCount = GetShapeVoxelCount(GetBodyShapes(thrusters[1])[1])

end

function tick(dt) 
    if active then
        local bodyTransform = GetBodyTransform(body)
        local hits = false
        if (GetVehicleHealth(vehicle) == 0 and active) then 
            SetPlayerVehicle(0)
            SetTag(vehicle, "nodrive")
            active = false 
            for i=1, thrusterCount do SetShapeEmissiveScale(GetBodyShapes(thrusters[i])[1], 0) end
            SetShapeEmissiveScale(GetBodyShapes(body)[1], 0)
            Explosion(VecAdd(bodyTransform.pos, TransformToParentVec(bodyTransform, Vec(0, 0.5, 1.8))), 0.6)
            Explosion(VecAdd(bodyTransform.pos, TransformToParentVec(bodyTransform, Vec(0, 0.8, -1.8))), 0.6)
        end
        for i=1, thrusterCount do 
            if thrusterEnabled[i] then
                if (GetShapeVoxelCount(GetBodyShapes(thrusters[i])[1]) < (thrusterVoxCount / 1.5)) or IsJointBroken(thrusterJoints[i]) then
                    thrusterEnabled[i] = false
                    SetShapeEmissiveScale(GetBodyShapes(thrusters[i])[1], 0)
                end
            end
        end
        driveSpeed = driveSpeed * (1 / (1 + acceleration))
        if driveTime > -0.5 then
            thrustMultiplier = math.min(driveTime, 1)
            if driveTime < 0 then thrustMultiplier = (0.5 - math.min(math.abs(driveTime), 1)) * 2 end
            for i=1, thrusterCount do 
                if thrusterEnabled[i] then
                    SetShapeEmissiveScale(GetBodyShapes(thrusters[i])[1], 1)
                    RejectBodies()
                    local pos = VecAdd(bodyTransform.pos, TransformToParentVec(bodyTransform, thrusterPos[i]))
                    pos = VecAdd(pos, Vec(0, 0.2, 0))
                    local dir = TransformToParentVec(bodyTransform, Vec(0, -1, 0))
                    local hit, dist, normal, shape = QueryRaycast(pos, dir, hoverDist * 1.5)
                    local inWater, depth = IsPointInWater(VecAdd(pos, VecScale(dir, hoverDist * 1.5)))
                    if inWater and ((hoverDist * 1.5) + 0.25 - depth < dist) then
                        hit = true
                        dist = (hoverDist * 1.5) + 0.25 - depth
                    else
                        inWater = false
                    end
                    local endPos = VecAdd(pos, VecScale(dir, dist))
                    if hit then 
                        hits = true
                        if dist < hoverDist then
                            ApplyBodyImpulse(body, pos, TransformToParentVec(bodyTransform, Vec(0, (hoverDist - dist) * maxPower * ((GetBodyVelocity(body)[2] - 10) / -10) * thrustMultiplier * (math.min(GetVehicleHealth(vehicle) * 2, 1)), 0)))
                        end
                    end
                    if i % 2 == 0 then
                        SetJointMotorTarget(thrusterJoints[i], driveSpeed * -20 - turning * 10, 1)
                    else
                        SetJointMotorTarget(thrusterJoints[i], driveSpeed * -20 + turning * 10, 1)
                    end



                    local transform = GetBodyTransform(thrusters[i])
                    PointLight(transform.pos, 0, 1, 1, 0.3 * thrustMultiplier)
                    Particle("thrust", 0.2)
                    SpawnParticle(transform.pos, VecAdd(TransformToParentVec(transform, Vec(Random(true), -4, Random(true))), GetBodyVelocity(body)), 0.1)
                    Particle("thrust", 0.1)
                    ParticleColor(1, 1, 1)
                    SpawnParticle(transform.pos, VecAdd(TransformToParentVec(transform, Vec(Random(true), -8, Random(true))), GetBodyVelocity(body)), 0.1)
                    Particle("spark", 0.04)
                    SpawnParticle(transform.pos, TransformToParentVec(transform, Vec(Random(true) * 10, -10, Random(true) * 10)), 0.055)
                    if (not inWater) and hit then
                        Particle("smoke", 0.35)
                        local randPos = RandomVector(10)
                        SpawnParticle(endPos, Vec(randPos[1], 0, randPos[3]), 1.5)
                    end
                    if inWater then
                        Particle("water", 0.5)
                        for i=1, 5 do
                            local randPos = RandomVector(10)
                            SpawnParticle(endPos, Vec(randPos[1], 7, randPos[3]), 0.4)
                        end
                        Particle("water", 0.6)
                        ParticleRadius(0.6, 1.2)
                        ParticleGravity(-0.1)
                        local randPos = RandomVector(0.5)
                        SpawnParticle(VecAdd(endPos, Vec(randPos[1], 0, randPos[3])), Vec(0, 0, 0), 5)
                    end
                end
            end
            SetJointMotorTarget(gearJointL, 0, 3)
            SetJointMotorTarget(gearJointR, 0, 3)
            if vehicleType == "taxi" then
                SetJointMotorTarget(gearJointLB, 0, 3)
                SetJointMotorTarget(gearJointRB, 0, 3)
            end
        end
        if driveTime < 0 then
            rotate = 110
            if vehicleType == "taxi" then
                rotate = 90
                SetJointMotorTarget(gearJointLB, rotate, 10)
                SetJointMotorTarget(gearJointRB, rotate, 10)
            end
            SetJointMotorTarget(gearJointL, rotate, 10)
            SetJointMotorTarget(gearJointR, rotate, 10)
        end

        boosts = math.min(boosts + dt * boostRefill, maxBoosts)
        for i=1, maxBoosts do
            SetShapeEmissiveScale(meters[i], math.min(boosts - (i - 0.5), 1))
        end

        if not hits then
            --local stabilisation = 5
            --local rot = bodyTransform.rot
            --reverse = -1
            --if rot[4] < 0 then reverse = 1 else
            --local correction = {rot[1] * stabilisation * reverse, rot[3] * stabilisation * reverse}
            --SetBodyAngularVelocity(body, VecAdd(Vec(correction[1], 0, correction[2]), VecScale(GetBodyAngularVelocity(body), 0.2)))
            if GetPlayerVehicle() == vehicle then
                local airMove = 0
                if InputDown("up") then airMove = 0.1 end
                if InputDown("down") then airMove = -0.1 end
                SetBodyAngularVelocity(body, VecAdd(VecScale(GetBodyAngularVelocity(body), 0.95), TransformToParentVec(bodyTransform, Vec(airMove, 0, 0))))
            end
        else
            SetBodyAngularVelocity(body, VecScale(GetBodyAngularVelocity(body), 1 - angularDamping))
            sideVel = TransformToLocalVec(bodyTransform, GetBodyVelocity(body))[1]
            SetBodyVelocity(body, VecAdd(GetBodyVelocity(body), TransformToParentVec(bodyTransform, Vec(sideVel * -sideDamping, 0, 0))))
            
            if hits and InputDown("jump") then
                SetBodyVelocity(body, VecScale(GetBodyVelocity(body), 1 - (damping * 5)))
                driveSpeed = driveSpeed * 0.8
            else
                SetBodyVelocity(body, VecScale(GetBodyVelocity(body), 1 - damping))
            end
        end
        if vehicle == GetPlayerVehicle() then 
            local invertTurn = 1
            if InputDown("up") then driveSpeed = driveSpeed + acceleration end
            if InputDown("down") then 
                driveSpeed = driveSpeed - (acceleration * 0.6)
                invertTurn = -1
            end
            if InputPressed("shift") and (boosts > 1) and hits then
                boosts = boosts - 1
                driveSpeed = driveSpeed + (boostPower * (math.min(GetVehicleHealth(vehicle) * 2, 1)))
                Particle("colour", 0.4)
                for i=1, 40 do
                    local spawnRotation = Random(true) * pi
                    local spawnPos = VecAdd(bodyTransform.pos, TransformToParentVec(bodyTransform, Vec(0, 0.8, -2.3)))
                    SpawnParticle(spawnPos, TransformToParentVec(bodyTransform, Vec(math.cos(spawnRotation) * 8, math.sin(spawnRotation) * 8, 0)), 0.3)
                end
            end
            if InputDown("right") then turning = turning - (0.1 * invertTurn) end
            if InputDown("left") then turning = turning + (0.1 * invertTurn) end
            turning = turning * 0.95

            local damaged = 0
            for i=1, thrusterCount do
                if thrusterEnabled[i] then
                    damaged = damaged + (1 / thrusterCount)
                end
            end
            damaged = damaged * (math.min(GetVehicleHealth(vehicle) * 2, 1))
            SetBodyAngularVelocity(body, VecAdd(GetBodyAngularVelocity(body), Vec(0, turning * turnSpeed * damaged, 0)))
            if hits then SetBodyVelocity(body, VecAdd(GetBodyVelocity(body), TransformToParentVec(bodyTransform, Vec(0, 0, speed * driveSpeed * damaged)))) end
            if driveTime < 0 then
                driveTime = 0
            end
            driveTime = driveTime + dt
        else
            if driveTime > 0 then
                driveTime = 0
            end
            driveTime = driveTime - dt
        end

        if GetVehicleHealth(vehicle) < 0.5 then 
            Particle("smoke", 0.2)
            ParticleRadius(0.2, 0.6)
            ParticleColor(0.7, 0.7, 0.7)
            ParticleDrag(0.1)
            SpawnParticle(VecAdd(bodyTransform.pos, TransformToParentVec(bodyTransform, Vec(0, 0.5, 1.8))), VecAdd(Vec(Random(true), 6, Random(true)), GetBodyVelocity(body)), 5)
        end
        SetJointMotorTarget(wheelJoint, turning * 20, 10)
        if vehicleType == "basic" then
            Particle("colour", 0.13)
            for i=1, 5 do
                local spawnRotation = Random(true) * pi
                local spawnPos = VecAdd(bodyTransform.pos, TransformToParentVec(bodyTransform, VecAdd(Vec(0, 0.8, -2.3), Vec(math.cos(spawnRotation) * 0.4, math.sin(spawnRotation) * 0.4, 0))))
                local spawnVel = TransformToParentVec(bodyTransform, Vec(math.cos(spawnRotation + pi), math.sin(spawnRotation + pi), -1 * math.max(driveSpeed, 0)))
                SpawnParticle(spawnPos, VecAdd(VecScale(spawnVel, 1.5), GetBodyVelocity(body)), 0.3)
            end
            Particle("colour", 0.2)
            ParticleColor(1, 1, 1)
            SpawnParticle(VecAdd(bodyTransform.pos, TransformToParentVec(bodyTransform, Vec(0, 0.8, -2.3))), VecAdd(TransformToParentVec(bodyTransform, Vec(0, 0, -6 * math.max(driveSpeed, 0))), GetBodyVelocity(body)), 0.15)
        end
        if vehicleType == "sports" then
            Particle("colour", 0.13)
            for i=1, 5 do
                local spawnRotation = Random(true) * pi
                local spawnPos = VecAdd(bodyTransform.pos, TransformToParentVec(bodyTransform, VecAdd(Vec(0, 0.9, -2.6), Vec(math.cos(spawnRotation) * 0.4, math.sin(spawnRotation) * 0.25, 0))))
                local spawnVel = TransformToParentVec(bodyTransform, Vec(math.cos(spawnRotation + pi), math.sin(spawnRotation + pi), 0))
                SpawnParticle(spawnPos, VecAdd(VecScale(spawnVel, 1.5), GetBodyVelocity(body)), 0.3)
            end
            for i=1, 2 do
                local spawnRotation = (GetTime() * pi * 2) + ((i - 1) * pi)
                local spawnPos = VecAdd(bodyTransform.pos, TransformToParentVec(bodyTransform, VecAdd(Vec(0, 0.9, -2.6), Vec(math.cos(spawnRotation) * 0.4, math.sin(spawnRotation) * 0.25, 0))))
                local spawnVel = TransformToParentVec(bodyTransform, Vec(math.cos(spawnRotation + pi), math.sin(spawnRotation + pi), -5 * driveSpeed))
                SpawnParticle(spawnPos, VecAdd(VecScale(spawnVel, 1.5), GetBodyVelocity(body)), 0.3)
            end
        end
    end
end

function RejectBodies()
    QueryRejectBody(body) 
    for i=1, thrusterCount do
        QueryRejectBody(thrusters[i])
    end
end

function RandomVector(size)
    return VecScale(VecNormalize(Vec(Random(true), Random(true), Random(true))), size)
end

function Random(negative)
    if negative then return math.random(-1000, 1000) / 1000 end
    return math.random(0, 1000) / 1000
end

function Lerp(a, b, x)
    return (a * (1 - x)) + (b * x)
end

function Particle(particleType, size)
    if particleType == "colour" then
        ParticleReset()
        ParticleColor(vehicleColour[1], vehicleColour[2], vehicleColour[3])
        ParticleEmissive(16)
        ParticleRadius(size, size / 2)
        ParticleCollide(0)
    end
    if particleType == "thrust" then
        ParticleReset()
        ParticleColor(0, 1, 1)
        ParticleEmissive(16)
        ParticleRadius(size, size / 2)
        ParticleCollide(0)
    end
    if particleType == "spark" then
        ParticleReset()
        ParticleTile(6)
        ParticleColor(1, 0.8, 0.6)
        ParticleEmissive(2)
        ParticleRadius(size, size / 2)
        ParticleCollide(0)
    end
    if particleType == "dust" then
        ParticleReset()
        ParticleTile(6)
        ParticleColor(0, 0, 0)
        ParticleEmissive(0)
        ParticleRadius(size, 0)
        ParticleGravity(-9.81)
        ParticleCollide(1)
    end
    if particleType == "smoke" then
        ParticleReset()
        ParticleType("smoke")
        ParticleColor(1, 1, 1)
        ParticleEmissive(0)
        ParticleRadius(size, 0)
        ParticleGravity(0.4)
        ParticleDrag(0.8)
        ParticleCollide(0)
    end
        if particleType == "water" then
        ParticleReset()
        ParticleTile(1)
        ParticleColor(0.7, 0.8, 1)
        ParticleEmissive(0)
        ParticleRadius(size, 0)
        ParticleGravity(-2)
        ParticleDrag(0.2)
        ParticleCollide(0)
    end
end