AI Racing with Recovering

Discussion in 'Programming' started by frieder_marek, May 25, 2025.

  1. frieder_marek

    frieder_marek
    Expand Collapse

    Joined:
    Feb 25, 2024
    Messages:
    5
    Hello everyone,
    I spend my main time in BeamNG.drive with experimenting with setting up and spectating AI races. I'd also like those cars to recover, when they're stuck on the track.
    - Is there a way to track the progress of the AI vehicles on the calculated path?
    - And is there a way to recover the vehicle based on the path it needs to follow?
     
  2. THEARTH

    THEARTH
    Expand Collapse

    Joined:
    Jul 7, 2020
    Messages:
    48
    not sure if this helps, but maybe you could use the flowgraph to reset vehicles?
     
  3. frieder_marek

    frieder_marek
    Expand Collapse

    Joined:
    Feb 25, 2024
    Messages:
    5
    Hello there!
    I've taken a look at the flowgraph editor. Flowgraph is great to create missions and integrate them into the BeamNG maps. But I don't want to create a classical "mission" to play for other players; my goal is to have a function, UI app or something similar to quickly set up AI races to spectate. So I can spontaneously set up a route for all vehicles spawned in the scene (including the player vehicle) to race along. And if a vehicle gets stuck, it should recover and continue along its route instead of starting all over from the first node of the list.
    In other words: When I reset a broken vehicle using Recover, Reset, Load Home, recovery.recoverInPlace() or ai.setRecoverOnCrash(true) the path progress, as well as the number of laps already passed, gets lost. So I have to manually bring the car back into the race - starting it all over again instead of continuing the route it's been on.
    And if 10 or more vehicles are involved within the race, I spend more time resetting vehicles and estimating their progress than spectating them...
     
  4. frieder_marek

    frieder_marek
    Expand Collapse

    Joined:
    Feb 25, 2024
    Messages:
    5
    Hello again,
    I managed to get started with a Lua script that almost does what I want. But it still has some bugs I couldn't fix until now. The following script is a Vehicle Extension script with which I can quickly start AI races from the console.
    There are three race start functions that assign a list of map nodes as a checkpoint route for the vehicle. When it reaches the next checkpoint of the list, it will be saved as the home point. When the car gets stuck on the path, it should reset itself on the last saved home point.
    After resetting three times at the same home point, the vehicle should "teleport" to the next checkpoint of the series and continue its race from there.
    Once a series of 5 checkpoints is passed, the vehicle should also perform a recovery in place and continue its journey along the checkpoint route.
    While developing the extension I try not to modify the main vehicle lua files - as recommended by the documentation.
    Here's the script. Copy it into lua\vehicle\extensions and load the extension from the console.

    local M = {}

    -- AI Parameters
    M.risk = 0.5
    M.inLane = 'on'
    M.avoid = 'on'

    -- Path data
    local nodePos = {}
    local radius = {}
    local racePath = {}
    local restPath = {}
    local currentPos, currentRadius
    local cp = 0

    -- Racing system

    local lifes = 3 -- determines how often it tries a route from one checkpoint to another. Afterwards it should teleport to the next checkpoint.
    local recoverTimer = 0 -- If this vehicle is slow, this timer starts.
    local recoverTime = 60 -- If the recoverTimer gets above this, the vehicle will reset at the last checkpoint and try again.
    local startTimer = 0 -- After recovering, it needs some time to reset.
    local racing = false -- is true if the vehicle is currently racing.
    local driving = false -- is true if the vehicle is currently actually driving.


    local function onExtensionLoaded()
    ai.setMode("manual")
    print("Vehicle "..obj:getId()..": Ready to go")
    end

    local function getNodes() -- get all nodes from map
    local nodes = {}
    nodePos = mapmgr.mapData.positions
    radius = mapmgr.mapData.radius
    for k, v in pairs(nodePos) do
    table.insert(nodes, k)
    end
    return nodes
    end

    local function getDR() -- get all map nodes starting with "DR" (DecalRoad)
    local nodes = getNodes()
    local roads = {}
    for k, v in pairs(nodes) do
    if string.sub(v, 1, 2) == "DR" then
    table.insert(roads, v)
    end
    end
    return roads
    end

    local function initRace() -- activates the in-race function after a race start function was called.
    lifes = 3
    recoverTimer = 0
    racing = true
    end

    local function reset() -- reset the racing functions
    racePath = {}
    racing = false
    driving = false
    end

    local function span() -- start a race through all map nodes.
    racePath = getNodes()
    initRace()
    print("Vehicle "..obj:getId()..": Starting Race")
    end

    local function spanDR() -- start a race through all DecalRoads (DR nodes)
    racePath = getDR()
    initRace()
    print("Vehicle "..obj:getId()..": Starting Race")
    end

    local function route(path, laps) -- start a race along a determined path.
    racePath = path
    initRace()
    print("Fahrzeug "..obj:getId()..": Starting Race")
    end

    local function updateGFX(dtGFX) -- called each frame
    local dt = dtGFX -- time between two frames
    local speed = vec3(obj:getSmoothRefVelocityXYZ()):length() -- copied from AI script to get the car's speed
    local pos = obj:getPosition()

    if racing then

    -- handle start timer if active (above 0)
    if startTimer > 0 then
    startTimer = startTimer - dt
    end

    -- get the AI driving again if it's not
    if not driving and startTimer <= 0 then
    if #restPath == 0 then
    for i = 1, 5 do
    if racePath ~= nil then
    table.insert(restPath, racePath[1])
    table.remove(racePath, 1)
    end
    end
    end
    ai.driveUsingPath{wpTargetList = restPath, aggression = M.risk, driveInLane = M.inLane, avoidCars = M.avoid}
    driving = true
    end
    currentPos = nodePos[restPath[1]]
    currentRadius = radius[restPath[1]]


    -- Track route progress
    if pos:squaredDistance(currentPos) < currentRadius * currentRadius * 10 then
    cp = cp + 1
    print("Vehicle "..obj:getId()..": Checkpoint "..cp.." reached.")
    table.remove(restPath, 1)
    currentPos = nodePos[restPath[1]]
    currentRadius = radius[restPath[1]]
    recovery.saveHome()
    end

    if #restPath == 0 then
    if driving and speed < 0.1 then
    if #racePath > 0 then
    startTimer = 1
    recovery.recoverInPlace()
    print("Vehicle "..obj:getId()..": Stage finished. Recovering for next stage")
    lifes = 3
    driving = false;
    end
    else
    print("Vehicle "..obj:getId()..": Race finished")
    racing = false
    end
    end

    -- Recover if necessary

    if driving and speed < 3 then
    recoverTimer = recoverTimer + dtGFX
    else
    recoverTimer = 0
    end

    if recoverTimer > recoverTime then
    lifes = lifes - 1
    recoverTimer = 0
    if lifes > 0 then
    print("Vehicle "..obj:getId()..": Stuck on track, resetting to previous checkpoint.")
    startTimer = 1
    driving = false
    recovery.loadHome()
    else
    print("Vehicle "..obj:getId()..": Section seems to be impossible. Teleporting to next checkpoint.")
    table.remove(restPath, 1)
    local newHome = {
    pos = restPath[1],
    dirFront = obj:getDirectionVector(),
    dirUp = vec3(0, 0, 1)}
    print(newHome.pos)
    print(newHome.dirFront)
    print(newHome.dirUp)
    recovery.saveHome(newHome)
    startTimer = 1
    driving = false
    recovery.loadHome()
    end
    end

    -- Debug

    M.distance = pos:squaredDistance(currentPos)
    M.currentPos = currentPos
    M.pos = pos
    M.speed = speed
    M.timer = recoverTimer
    M.restPath = restPath
    M.lifes = lifes
    end
    end


    M.onExtensionLoaded = onExtensionLoaded
    M.span = span
    M.spanDR = spanDR
    M.route = route
    M.getNodes = getNodes
    M.getDR = getDR
    M.updateGFX = updateGFX
    M.reset = reset

    return M

    It resets the car very well, if it gets stuck on the track, but the teleportation to the next point causes the car to disable for some reason...
    Also, the fifth and final point of the route part list is not detected by the script, while all previous ones work fine.
    Do you have any idea how to fix these bugs?
     
  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice