Ace_Gambit

[Code Snippet] Scripted vehicle paths

Recommended Posts

Some functions require DM 1.0 Nightly build.

1234721739.jpg

A lot of people have been wondering about scripted traffic and there is more than one way of doing it. With the new ped functions (DM 1.0 Nightly build) a convenient method is to set control states. However, setting control states is client-sided and no one except you will be able to see the vehicle moving unless you tell all clients to do the same. This code snippet will demonstrate how to move vehicles along scripted paths by using server-side velocity changes. It is very basic and probably not the most efficient way of moving vehicles but it may help people with their scripts.

Also note that it is recommended to do all this client-side. Timers do not provide fast enough intervals to make the vehicle rotate smoothly. This sample is just to show how it could be done server-side.

Server script:

  
ROOT = getRootElement (); 
RES = getThisResource (); 
PATHS = "paths.xml"; 
  
local pPaths = {}; 
local iPath = 1; 
local bHandleSpawn = true; 
local bCreateMarkers = true; -- Just for testing. 
local pMover = nil; 
  
function getTargetAngle ( pSource, fTargetX, fTargetY, fTmp ) 
  local fSourceX, fSourceY, fSourceZ = getElementPosition( pSource ); 
  local _, _, fRot = getVehicleRotation ( pSource ); 
  local fRelPosX = fTargetX - fSourceX; 
  local fRelPosY = fTargetY - fSourceY; 
  local fDist = math.sqrt ( ( fRelPosX * fRelPosX ) + ( fRelPosY * fRelPosY ) ); 
  local fDot = 0; 
  fRot = fRot + fTmp; 
  fRelPosX = fRelPosX / fDist; 
  fRelPosY = fRelPosY / fDist; 
  fDot = ( ( math.sin ( math.rad ( 360 - fRot ) ) ) * fRelPosX ) + ( ( math.cos ( math.rad ( 360 - fRot ) ) ) * fRelPosY ); 
  return math.deg ( math.acos ( fDot ) ); 
end; 
  
function onTimer () 
  local fPosX, fPosY = 0, 0; 
  local fRotZ = 0; 
  local fVelX, fVelY, fVelZ = 0, 0, 0; 
  local fPerc = 0; 
  if ( pMover and #pPaths > 0 ) then 
    fPosX, fPosY = getElementPosition ( pMover ); 
    if ( getDistanceBetweenPoints2D ( fPosX, fPosY, pPaths[ iPath ].fPosX, pPaths[ iPath ].fPosY ) > pPaths[ iPath ].fMaxTargetDistance ) then 
      _, _, fRotZ = getVehicleRotation ( pMover ); 
      fVelX, fVelY, fVelZ = getElementVelocity ( pMover ); 
      if ( fRotZ >= 0 and fRotZ <= 90 ) then 
        fPerc = ( fRotZ / 90 ) * 100; 
        if ( fPerc < 50 ) then 
          fVelX = -( ( fPerc / pPaths[ iPath ].fSpeedDivision ) * 1 ); 
          fVelY = ( ( 100 - fPerc ) / pPaths[ iPath ].fSpeedDivision ) * 1; 
        else 
          fVelX = -( ( fPerc / pPaths[ iPath ].fSpeedDivision ) * 1 ); 
          fVelY = ( ( 100 - fPerc ) / pPaths[ iPath ].fSpeedDivision ) * 1; 
        end; 
      end; 
      if ( fRotZ >= 90 and fRotZ <= 180 ) then 
        fRotZ = fRotZ - 90; 
        fPerc = ( fRotZ / 90 ) * 100; 
        if ( fPerc < 50 ) then 
          fVelX = -( ( ( 100 - fPerc ) / pPaths[ iPath ].fSpeedDivision ) * 1 ); 
          fVelY = -( ( fPerc / pPaths[ iPath ].fSpeedDivision ) * 1 ); 
        else 
          fVelX = -( ( ( 100 - fPerc ) / pPaths[ iPath ].fSpeedDivision ) * 1 ); 
          fVelY = -( ( fPerc / pPaths[ iPath ].fSpeedDivision ) * 1 ); 
        end; 
      end; 
      if ( fRotZ >= 180 and fRotZ <= 270 ) then 
        fRotZ = fRotZ - 180; 
        fPerc = ( fRotZ / 90 ) * 100; 
        if ( fPerc < 50 ) then 
          fVelX = ( fPerc / pPaths[ iPath ].fSpeedDivision ) * 1; 
          fVelY = -( ( ( 100 - fPerc ) / pPaths[ iPath ].fSpeedDivision ) * 1 ); 
        else 
          fVelX = ( fPerc / pPaths[ iPath ].fSpeedDivision ) * 1; 
          fVelY = -( ( ( 100 - fPerc ) / pPaths[ iPath ].fSpeedDivision ) * 1 ); 
        end; 
      end; 
      if ( fRotZ >= 270 and fRotZ <= 360 ) then 
        fRotZ = fRotZ - 270; 
        fPerc = ( fRotZ / 90 ) * 100; 
        if ( fPerc < 50 ) then 
          fVelX = ( ( 100 - fPerc ) / pPaths[ iPath ].fSpeedDivision ) * 1; 
          fVelY = ( fPerc / pPaths[ iPath ].fSpeedDivision ) * 1; 
        else 
          fVelX = ( ( 100 - fPerc ) / pPaths[ iPath ].fSpeedDivision ) * 1; 
          fVelY = ( fPerc / pPaths[ iPath ].fSpeedDivision ) * 1; 
        end; 
      end; 
      setElementVelocity ( pMover, fVelX, fVelY, fVelZ ); 
      if ( getTargetAngle ( pMover, pPaths[ iPath ].fPosX, pPaths[ iPath ].fPosY, 0 ) > pPaths[ iPath ].fMaxTargetAngle ) then 
        if ( getTargetAngle ( pMover, pPaths[ iPath ].fPosX, pPaths[ iPath ].fPosY, -1 ) < getTargetAngle ( pMover, pPaths[ iPath ].fPosX, pPaths[ iPath ].fPosY, 1 ) ) then 
          print ( "* Right turn." ); 
          setVehicleTurnVelocity ( pMover, 0, 0, -( pPaths[ iPath ].fTurnVelocity ) ); 
        else 
          print ( "* Left turn." ); 
          setVehicleTurnVelocity ( pMover, 0, 0, pPaths[ iPath ].fTurnVelocity ); 
        end; 
      else 
        setVehicleTurnVelocity ( pMover, 0, 0, 0 ); 
      end; 
    else 
      if ( iPath < #pPaths ) then 
        iPath = iPath + 1; 
      else 
        iPath = 1; 
      end; 
      print ( "* Next path." ); 
    end; 
  end; 
  setTimer ( onTimer, 50, 1 ); 
end; 
  
function onResourceStart ( pStartedResource ) 
  local nRoot = false; 
  if ( pStartedResource == RES ) then 
    nRoot = xmlLoadFile ( PATHS, RES ); 
    if ( nRoot ) then 
      for _, nPath in pairs ( xmlNodeGetChildren ( nRoot ) or {} ) do 
        table.insert ( pPaths, 
            { 
              fPosX = tonumber ( xmlNodeGetAttribute ( nPath, "posX" ) ) or -1, 
              fPosY = tonumber ( xmlNodeGetAttribute ( nPath, "posY" ) ) or -1, 
              fTurnVelocity = tonumber ( xmlNodeGetAttribute ( nPath, "turnVelocity" ) ) or -1, 
              fMaxTargetDistance = tonumber ( xmlNodeGetAttribute ( nPath, "maxTargetDistance" ) ) or -1, 
              fMaxTargetAngle = tonumber ( xmlNodeGetAttribute ( nPath, "maxTargetAngle" ) ) or -1, 
              fSpeedDivision = tonumber ( xmlNodeGetAttribute ( nPath, "speedDivision" ) ) or -1 
            } 
          ); 
      end; 
      if ( bCreateMarkers ) then 
        for _, pPath in ipairs ( pPaths ) do 
          createMarker ( pPath.fPosX, pPath.fPosY, 0, "checkpoint", 1.5, math.random ( 0, 255 ), math.random ( 0, 255 ), math.random ( 0, 255 ), 255 ); 
        end; 
      end; 
      pMover = getElementByID ( "mover" ); 
    end; 
  end; 
end; 
  
function onKeyPressed ( pPlayer, szKey, szKeyState ) 
  if ( szKey == "F1" and szKeyState == "down" ) then 
    onTimer (); 
  end; 
end; 
  
function onPlayerJoin () 
  setCameraTarget ( source, source ); 
  fadeCamera ( source, true ); 
  spawnPlayer ( source, 0, 0, 2.5 ); 
  bindKey ( source, "F1", "down", onKeyPressed ); 
end; 
  
addEventHandler ( "onResourceStart", ROOT, onResourceStart, true ); 
  
if ( bHandleSpawn ) then 
  addEventHandler ( "onPlayerJoin", ROOT, onPlayerJoin, true ); 
end; 
  

In order to make this work you must place a data file with paths in the same folder as your server script.

Example paths.xml data file:

  
<paths> 
  <path posX="45.761882781982" posY="-2.5386297702789" turnVelocity="0.01" maxTargetDistance="10" maxTargetAngle="10" speedDivision="1000" /> 
  <path posX="132.56669616699" posY="78.492034912109" turnVelocity="0.01" maxTargetDistance="10" maxTargetAngle="10" speedDivision="1000" /> 
  <path posX="228.1443939209" posY="50.259208679199" turnVelocity="0.01" maxTargetDistance="10" maxTargetAngle="10" speedDivision="1000" /> 
  <path posX="233.02215576172" posY="-70.206611633301" turnVelocity="0.01" maxTargetDistance="10" maxTargetAngle="10" speedDivision="1000" /> 
  <path posX="144.71810913086" posY="-71.875244140625" turnVelocity="0.01" maxTargetDistance="10" maxTargetAngle="10" speedDivision="1000" /> 
  <path posX="80.65113067627" posY="-91.124290466309" turnVelocity="0.01" maxTargetDistance="10" maxTargetAngle="10" speedDivision="1000" /> 
</paths> 
  

And make sure there is a vehicle with ID "mover" in your map file (or create it on the fly).

Example snippet.map file:

  
<map> 
  <info description="" type="map" gamemodes="snippet" author=""  name="" version="1.0" /> 
  <vehicle id="mover" model="427" posX="10" posY="0" posZ="5" rotX="0" rotY="0" rotZ="270" /> 
</map> 
  

I've attached all script files for quick reference.

Share this post


Link to post

Very nice but too bad it's not server-side =/.

Actually it is server-side. But like I said it runs more smoothly if you just do it client-side in onClientRender or onClientPreRender with the cost of not having it synchronized.

Share this post


Link to post

Very nice but too bad it's not server-side =/.

Actually it is server-side. But like I said it runs more smoothly if you just do it client-side in onClientRender or onClientPreRender with the cost of not having it synchronized.

Ah I see very nice! :)

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.