Every docs URL answers with Markdown when you add .md. Site map: llms.txt
The navmesh at runtime
What a resource can and cannot do to the GTA V navmesh: the 17 navmesh natives, blocking objects, disabling areas, streaming regions, and why peds refuse to walk on a custom map.
Documentația este deocamdată în engleză.
Peds walk on a navmesh. It is not the same thing as the path node network that traffic drives on, it is not collision, and it is not something a script can build. This page is about what your resource can actually do to it while the server is running, and about the question everybody hits eventually: why peds refuse to walk on a custom map.
The whole native surface is 17 natives, and every write is subtractive
An exhaustive search of the Cfx.re native list for NAVMESH or NAV_MESH returns exactly 17 natives. Here they are, grouped by what they do.
Masking what is already there
| Native | What it does |
|---|---|
ADD_NAVMESH_BLOCKING_OBJECT |
Adds a box peds and vehicles route around. Returns a handle. |
UPDATE_NAVMESH_BLOCKING_OBJECT |
Moves or resizes one you added, by that handle. |
REMOVE_NAVMESH_BLOCKING_OBJECT |
Removes it. |
DOES_NAVMESH_BLOCKING_OBJECT_EXIST |
Asks whether a handle is still live. |
DISABLE_NAVMESH_IN_AREA |
Turns the navmesh off over a box outright. |
Streaming, not content
| Native | What it does |
|---|---|
ADD_NAVMESH_REQUIRED_REGION |
Asks the streamer to load the navmesh around a point. |
REMOVE_NAVMESH_REQUIRED_REGIONS |
Drops every request you made. |
ARE_ALL_NAVMESH_REGIONS_LOADED |
Are the requested regions in yet. |
_IS_NAVMESH_REQUIRED_REGION_OWNED_BY_ANY_THREAD |
Unnamed native, takes no arguments. |
Asking questions
| Native | What it does |
|---|---|
IS_NAVMESH_LOADED_IN_AREA |
Is navmesh streamed in over this box. |
GET_NUM_NAVMESHES_EXISTING_IN_AREA |
How many navmesh files cover this box, loaded or not. |
GET_NAVMESH_ROUTE_RESULT |
The state of the route a ped is following. |
GET_NAVMESH_ROUTE_DISTANCE_REMAINING |
How far is left, and whether the path is ready. |
Issuing tasks
| Native | What it does |
|---|---|
TASK_FOLLOW_NAV_MESH_TO_COORD |
Walk a ped to a point, on the navmesh. |
TASK_FOLLOW_NAV_MESH_TO_COORD_ADVANCED |
The same with more arguments. |
TASK_VEHICLE_GOTO_NAVMESH |
Drive a vehicle to a point using the navmesh. |
SET_VEHICLE_ACTIVE_FOR_PED_NAVIGATION |
Whether peds treat a vehicle as something to path around. |
Read that list again and notice what is not in it. Nothing creates a polygon. Nothing moves one. Nothing retags one. Not one of these natives takes a vertex, a triangle or a flag word. Every write among them takes a box and subtracts.
Note
Two natives often listed as navmesh natives are not. _ADD_TACTICAL_ANALYSIS_POINT and _CLEAR_TACTICAL_ANALYSIS_POINTS are in the MISC namespace and belong to CTacticalAnalysis, which is combat cover scoring. Neither has NAVMESH in its name and neither touches the navmesh.
Blocking objects: the tool you usually want
If peds are walking through your new building, or across a bridge you removed, a blocking object is the answer. It is a box, it takes effect immediately and it needs no file.
local blocker = 0
AddEventHandler('onClientResourceStart', function(resource)
if resource ~= GetCurrentResourceName() then return end
-- x, y, z is the centre. width, length, height are the full extents.
blocker = AddNavmeshBlockingObject(
-820.0, -1075.0, 11.0, -- centre
24.0, 18.0, 6.0, -- width, length, height
0.0, -- heading, in degrees
false, -- bPermanent
7 -- eBlockingObjectFlags
)
end)
AddEventHandler('onResourceStop', function(resource)
if resource ~= GetCurrentResourceName() then return end
if blocker ~= 0 then
RemoveNavmeshBlockingObject(blocker)
blocker = 0
end
end)The last argument is a flag set, documented on the native itself:
| Value | Name | Blocks |
|---|---|---|
| 0 | BLOCKING_OBJECT_DEFAULT |
The default. |
| 1 | BLOCKING_OBJECT_WANDERPATH |
Wander paths. |
| 2 | BLOCKING_OBJECT_SHORTESTPATH |
Regular shortest paths. |
| 4 | BLOCKING_OBJECT_FLEEPATH |
Flee paths. |
| 7 | BLOCKING_OBJECT_ALLPATHS |
All paths. |
Warning
Only 32 blocking objects can exist at once, and nothing manages them for you. Keep every handle, remove them in onResourceStop, and do not add one per frame or per spawned prop. Once you are past 32 the next call quietly gives you nothing.
bPermanent is the eighth argument. False lets the game clean the object up on a streaming pass; true keeps it until you remove it.
Disabling a whole area
For anything larger than a handful of boxes, switch the area off instead of stacking blockers.
local x1, y1, z1 = -900.0, -900.0, 0.0
local x2, y2, z2 = -750.0, -750.0, 80.0
DisableNavmeshInArea(x1, y1, z1, x2, y2, z2, true)
-- Ped paths are a separate switch. Flip both, or peds keep spawning on
-- the pavement you just made unwalkable.
SetPedPathsInArea(x1, y1, z1, x2, y2, z2, false, 0)Put both back when you are done, and from onResourceStop as well, otherwise the area stays off for everyone who was already connected:
DisableNavmeshInArea(x1, y1, z1, x2, y2, z2, false)
SetPedPathsBackToOriginal(x1, y1, z1, x2, y2, z2, 0)The native documentation for DISABLE_NAVMESH_IN_AREA says it in one line: use this for a large area, use ADD_NAVMESH_BLOCKING_OBJECT for a small one.
Is there a navmesh here at all?
This is the check worth running before you debug anything else. The two natives answer different questions and both matter.
RegisterCommand('navcheck', function()
local p = GetEntityCoords(PlayerPedId())
local x1, y1, z1 = p.x - 40.0, p.y - 40.0, p.z - 20.0
local x2, y2, z2 = p.x + 40.0, p.y + 40.0, p.z + 20.0
-- Is it streamed in right now?
local loaded = IsNavmeshLoadedInArea(x1, y1, z1, x2, y2, z2)
-- How many navmesh files cover this box at all, loaded or not?
local existing = GetNumNavmeshesExistingInArea(x1, y1, z1, x2, y2, z2)
print(('navmesh loaded: %s, files covering this box: %d'):format(tostring(loaded), existing))
end, false)GET_NUM_NAVMESHES_EXISTING_IN_AREA returning 0 is the real answer for a custom map built out over the sea: the game ships no navmesh there, so there is nothing to stream and nothing to load. IS_NAVMESH_LOADED_IN_AREA returning false with a non zero count just means it has not streamed in yet, which you can fix:
AddNavmeshRequiredRegion(x, y, 150.0)
while not AreAllNavmeshRegionsLoaded() do
Wait(0)
end
-- ... do the thing that needed it ...
RemoveNavmeshRequiredRegions()That streams a file the game already has. It does not make one.
Why peds will not walk on your custom map
Because there is no navmesh under it, and no native can make one.
TASK_FOLLOW_NAV_MESH_TO_COORD needs navmesh to path on. Its own documentation is explicit about what happens when it cannot find a route: “Sometimes a path may not be able to be found. This could happen because there simply isn’t any way to get there… In this case the ped will simply stand still.” There is no error, no event and no return value on the task itself. The ped stands there.
To find out rather than guess, read the route back:
local ped = PlayerPedId()
TaskFollowNavMeshToCoord(ped, x, y, z, 1.0, 20000, 1.0, 0, 0.0)
Wait(500)
local state, remaining, ready = GetNavmeshRouteDistanceRemaining(ped)
print(('route state %s, ready %s, %s m left'):format(state, tostring(ready), remaining))
print('route result ' .. GetNavmeshRouteResult(ped))There is one flag worth knowing in the task’s own flag set, ENAV_GO_FAR_AS_POSSIBLE_IF_TARGET_NAVMESH_NOT_LOADED = 4. Its documentation says it makes the ped get as close as possible on whatever navmesh is loaded, and then adds the line that matters: the navmesh must still be loaded at the path start. It does not rescue a ped standing on a platform with no navmesh under it.
So the honest options for a custom map are:
- Build it over ground the game already has navmesh for, which is most of the map.
- Ship a
.ynvfor it, with everything below in mind. - Move peds with something that is not the pathfinder:
TASK_GO_STRAIGHT_TO_COORD, a sequence of waypoints you author yourself, or attaching them to something.
Streaming a .ynv, and the trap in it
A .ynv in a resource’s stream/ folder needs no data_file line, no files{} entry and no script. The client takes the extension, looks up the streaming module for it, takes the file name without the extension as a slot name, and swaps the slot’s handle to point at your file. That is the same generic path every streamed asset goes through.
A stock name is the clean case. Ship navmesh[123][102].ynv and it replaces the game’s file of that name. The original handle is pushed onto a stack first, so when the resource stops the game’s own file comes back, and restarting the resource works.
A new name is not the clean case. Two things happen:
- A new slot gets created in the navmesh store, and navmesh slots are never removed. FiveM’s own source skips them on cleanup with a comment saying that re-registration crashes. So the slot name leaks for the rest of the game session. Restarting the resource works, precisely because nothing tried to remove the slot.
- Nothing is going to ask for it. The game requests navmesh by region, through the
navmesh[x][y]naming and the grid behind it. A slot calledmymap_navmeshis registered and never requested.
Caution
Treat “stream a .ynv under a new name” as unproven rather than as a feature. The registration is real and is in the source. That the game ever loads it is not something the source shows, and the naming scheme argues against it.
One more thing about timing: the cleanup that would release a slot only runs at session shutdown, not at resource stop. So a server that restarts a navmesh resource repeatedly during a session is accumulating state either way.
The file itself
If you do need to look at a .ynv, the YNV Navmesh Viewer opens one over the real map and takes it apart: the polygons coloured by flag, the full flag set of any polygon with the real bit names, the sector tree, the portals and cover points, and a count of which neighbouring cells the file’s edges point into.
That last number is why there is a viewer and not an editor. An edge in a .ynv names the polygon on the other side by an area id and a polygon index, and where that area is a different cell, the index is a position in the neighbouring file’s own polygon list. Across the stock build that is 2.75% of edge parts, roughly 350 per file. Add or remove one polygon and everything after it renumbers, which silently repoints the edges of up to four files you do not have open.
For the road network that traffic drives on, which is a completely separate set of files, see the YND Traffic Path Editor. Path nodes can be edited one file at a time, and that tool writes them.
