RoadRoute is a server-side road navigation plugin for SA-MP and open.mp servers. It calculates routes over a preprocessed GTA San Andreas vehicle road graph and streams directional navigation arrows as per-player objects.
#include <RoadRoute>
public SomeFeature(playerid)
{
RoadRoute_SetDestination(playerid, 1481.0, -1770.0, 18.8);
return 1;
}RoadRoute is not a gamemode and does not know about jobs, missions, taxis, trucking, police, roleplay data, inventories, databases, commands, or command processors. Any feature can call the same generic API.
RoadRoute provides the missing navigation primitive for multiplayer servers:
- Loads a compact
roadgraph.datdatabase generated from legitimate GTA SA vehicle path node data. - Finds the nearest road node through a spatial hash instead of scanning the whole map every time.
- Calculates road-following routes with A* in a bounded reusable worker pool.
- Converts raw node paths into simplified route geometry and sampled arrows.
- Tracks progress incrementally along the route.
- Detects off-route movement with threshold, debounce, and cooldown rules.
- Recalculates routes asynchronously when players leave the route.
- Detects arrival and dispatches Pawn callbacks on the main script thread.
- Streams only a sliding window of nearby arrows.
- Reuses per-player PlayerObject slots through the Pawn include.
The full route is never rendered at once. Defaults are conservative for Android:
32 visible arrows, 8.0 unit spacing, 160.0 render distance, and throttled
updates from the gamemode's OnPlayerUpdate or another timer.
Arrow rotation follows the sampled road direction. Around corners, RoadRoute uses neighbouring arrow samples to smooth the heading, so visible arrows rotate progressively through right and left turns instead of snapping from one straight segment to the next.
include/RoadRoute.inc Pawn public API and PlayerObject pool
src/core Route ownership, progress, reroute, arrival
src/graph Compact road graph and binary format
src/spatial Spatial hash nearest-road lookup
src/routing A*, simplification, sampling, turns
src/async Bounded worker pool
src/rendering Arrow-window generation
src/pawn SA-MP/open.mp plugin ABI and natives
tools/node_converter roadgraph.dat converter
examples/roadroute_test.pwn Dependency-free example filterscript
tests Native C++ test runner
Linux:
cmake -S . -B build
cmake --build build --parallel
ctest --test-dir build --output-on-failureWindows:
cmake -S . -B build -G "Visual Studio 17 2022"
cmake --build build --config ReleaseThe build produces:
RoadRoute.soon LinuxRoadRoute.dllon WindowsRoadRouteNodeConverter
Copy the plugin binary to your server's plugins directory and add it to the
server config:
plugins RoadRoute.so
or on Windows:
plugins RoadRoute.dll
Copy include/RoadRoute.inc into your Pawn include path. Create:
scriptfiles/RoadRoute/
and place roadgraph.dat there. On startup, RoadRoute validates the file magic,
format version, counts, and checksum. If the database is missing or corrupted,
the plugin logs the problem and route requests fail instead of silently using bad
data.
RoadRoute does not ship copyrighted GTA files. Generate roadgraph.dat from
your own legitimate GTA San Andreas installation or a lawful export of vehicle
path node data.
The included converter currently consumes a normalized CSV export:
external_id,x,y,z,flags,width,link_id;link_id;...
Example:
RoadRouteNodeConverter vehicle_nodes.csv scriptfiles/RoadRoute/roadgraph.datOnly vehicle-compatible road/path nodes should be exported into the CSV. Preserve known flags and width values when your extractor can provide them, but do not invent semantics for undocumented path fields.
The simple API is designed for a few lines of Pawn:
RoadRoute_SetDestination(playerid, x, y, z);
RoadRoute_Clear(playerid);
RoadRoute_IsActive(playerid);
RoadRoute_GetProgress(playerid);
RoadRoute_GetRemainingDistance(playerid);Add the processing calls from the include:
public OnPlayerConnect(playerid)
{
RoadRoute_InitPlayer(playerid);
return 1;
}
public OnPlayerUpdate(playerid)
{
RoadRoute_ProcessPlayer(playerid);
return 1;
}
public OnPlayerDisconnect(playerid, reason)
{
RoadRoute_OnPlayerDisconnect(playerid);
return 1;
}You can call RoadRoute from any feature:
StartTruckDelivery(playerid)
{
return RoadRoute_SetDestination(playerid, DeliveryX, DeliveryY, DeliveryZ);
}
Taxi_SetTarget(playerid, Float:x, Float:y, Float:z)
{
return RoadRoute_SetDestination(playerid, x, y, z);
}
Mission_Start(playerid)
{
return RoadRoute_SetDestination(playerid, MissionX, MissionY, MissionZ);
}Configuration is per player:
RoadRoute_SetArrowModel(playerid, 19133);
RoadRoute_SetArrowRotationOffset(playerid, -90.0, 0.0, 0.0);
RoadRoute_SetArrowHeight(playerid, 0.05);
RoadRoute_SetArrowSpacing(playerid, 8.0);
RoadRoute_SetRenderDistance(playerid, 160.0);
RoadRoute_SetMaxVisibleArrows(playerid, 32);
RoadRoute_SetRerouteDistance(playerid, 25.0);
RoadRoute_SetRerouteCooldown(playerid, 3000);
RoadRoute_SetArrivalRadius(playerid, 8.0);
RoadRoute_SetAutoClear(playerid, true);Model 19133 is the default arrow candidate, but it is configurable because
different GTA models have different orientation. Runtime recoloring is not
configured by the native plugin directly; instead, the include exposes
OnRoadRouteArrowCreated so each gamemode can optionally apply object materials
or colours that match its own visual style.
RoadRoute_SetArrowRotationOffset is still applied after the automatic road
heading. For example, if your arrow model needs 0.499998, -90.500022, -2.0
when driving straight, use those values as the offset. Right turns will reduce
the final Z heading progressively, and left turns will increase it progressively
as the sampled road direction changes.
Coordinates are always supplied by RoadRoute. If you want to use a custom arrow object setup, configure only the model, rotation offset, and optional material:
#define GPS_ARROW_USE_MATERIAL (1)
#define GPS_ARROW_USE_MAT_COLOR (0)
#define GPS_ARROW_MODEL (1318)
#define GPS_ARROW_RX (0.499998)
#define GPS_ARROW_RY (-90.500022)
#define GPS_ARROW_RZ (-2.000000)
#define GPS_ARROW_MAT_INDEX (2)
#define GPS_ARROW_MAT_MODEL (18241)
#define GPS_ARROW_MAT_TXD "cw_tempstuffcs_t"
#define GPS_ARROW_MAT_TEXTURE "bluemetal03"
#define GPS_ARROW_MAT_COLOR (0x00000000)
stock ConfigureGPSArrow(playerid)
{
RoadRoute_SetArrowModel(playerid, GPS_ARROW_MODEL);
RoadRoute_SetArrowRotationOffset(playerid, GPS_ARROW_RX, GPS_ARROW_RY, GPS_ARROW_RZ);
return 1;
}
public OnRoadRouteArrowCreated(playerid, objectid, slot)
{
#if GPS_ARROW_USE_MATERIAL
#if GPS_ARROW_USE_MAT_COLOR
SetPlayerObjectMaterial(playerid, objectid, GPS_ARROW_MAT_INDEX, GPS_ARROW_MAT_MODEL, GPS_ARROW_MAT_TXD, GPS_ARROW_MAT_TEXTURE, GPS_ARROW_MAT_COLOR);
#else
SetPlayerObjectMaterial(playerid, objectid, GPS_ARROW_MAT_INDEX, GPS_ARROW_MAT_MODEL, GPS_ARROW_MAT_TXD, GPS_ARROW_MAT_TEXTURE);
#endif
#endif
return 1;
}Use SetPlayerObjectMaterial, not SetDynamicObjectMaterial, because RoadRoute
streams arrows as per-player objects created with CreatePlayerObject.
Material toggles:
| Setting | Value | Result |
|---|---|---|
GPS_ARROW_USE_MATERIAL |
0 |
Do not call SetPlayerObjectMaterial; the arrow uses the model's default texture. |
GPS_ARROW_USE_MATERIAL |
1 |
Apply GPS_ARROW_MAT_INDEX, GPS_ARROW_MAT_MODEL, GPS_ARROW_MAT_TXD, and GPS_ARROW_MAT_TEXTURE. |
GPS_ARROW_USE_MAT_COLOR |
0 |
Apply the material without a colour override. |
GPS_ARROW_USE_MAT_COLOR |
1 |
Apply the material and pass GPS_ARROW_MAT_COLOR as the material colour. |
Minimal custom model and rotation with no material:
#define GPS_ARROW_USE_MATERIAL (0)
#define GPS_ARROW_USE_MAT_COLOR (0)
#define GPS_ARROW_MODEL (1318)
#define GPS_ARROW_RX (0.499998)
#define GPS_ARROW_RY (-90.500022)
#define GPS_ARROW_RZ (-2.000000)
stock ConfigureGPSArrow(playerid)
{
RoadRoute_SetArrowModel(playerid, GPS_ARROW_MODEL);
RoadRoute_SetArrowRotationOffset(playerid, GPS_ARROW_RX, GPS_ARROW_RY, GPS_ARROW_RZ);
return 1;
}
public OnRoadRouteArrowCreated(playerid, objectid, slot)
{
return 1;
}forward OnRoadRouteCalculated(playerid, bool:success);
forward OnRoadRouteRecalculated(playerid);
forward OnRoadRouteArrived(playerid);
forward OnRoadRouteCleared(playerid);
forward OnRoadRouteTurnApproaching(playerid, turnType, Float:distance);
forward OnRoadRouteArrowCreated(playerid, objectid, slot);Worker threads never call Pawn. Completed results are queued in native code and
dispatched by RoadRoute_ProcessPlayer / RoadRoute_ProcessCallbacks on the
main script thread. OnRoadRouteArrowCreated is called by the Pawn include on
the main script thread each time it creates or recreates a visible arrow object.
The include exposes a route-handle compatibility layer:
new route = RoadRoute_Create(playerid);
RoadRoute_SetTarget(route, x, y, z);
RoadRoute_Stop(route);
RoadRoute_Destroy(route);Internally this maps to the same per-player route engine used by the simple API. The current implementation intentionally supports one active navigation route per player, which matches the visible GPS use case and keeps object counts bounded.
The plugin exports the classic SA-MP plugin entry points and registers AMX
natives. The core navigation engine is platform-independent C++17. The Pawn
include performs vanilla CreatePlayerObject, SetPlayerObjectPos,
SetPlayerObjectRot, and DestroyPlayerObject calls, which keeps route arrows
per-player and client-mod-free.
For open.mp, load the plugin through the compatibility plugin layer or adapt
src/pawn/PawnPlugin.cpp to the current open.mp component SDK while keeping the
core library unchanged.
ColAndreas and MapAndreas are not required. A future surface adapter can use ColAndreas raycasts or MapAndreas elevation as optional arrow-placement helpers, but neither should become the pathfinding graph. The route graph must remain GTA vehicle path nodes.
Road graph not loaded: ensurescriptfiles/RoadRoute/roadgraph.datexists.- Route request returns false: graph missing, queue full, or no nearby road node.
- Arrows point sideways/upward: adjust
RoadRoute_SetArrowRotationOffset. - Too many objects for mobile players: lower
RoadRoute_SetMaxVisibleArrowsor increaseRoadRoute_SetArrowSpacing. - Recalculation feels too aggressive: increase reroute distance or cooldown.
Run:
cmake -S . -B build -DROADROUTE_BUILD_PLUGIN=ON
cmake --build build --parallel
ctest --test-dir build --output-on-failureThe native tests cover graph loading, invalid graph detection, nearest-node lookup, bridge/tunnel-style vertical separation, short and unreachable routes, same-node routes, simplification, arrow sampling and orientation, turn detection, progress, arrival/clear behavior, disconnect handling, shutdown with pending jobs, and simultaneous async route calculation.
