Skip to content

feat(natives): Runtime map load / unload#3939

Open
gizzdev wants to merge 1 commit into
citizenfx:masterfrom
gizzdev:feature/runtime-map-load
Open

feat(natives): Runtime map load / unload#3939
gizzdev wants to merge 1 commit into
citizenfx:masterfrom
gizzdev:feature/runtime-map-load

Conversation

@gizzdev
Copy link
Copy Markdown

@gizzdev gizzdev commented Apr 20, 2026

Goal of this PR

To introduce the ability to dynamically unload/reload the base GTA map and manage custom streaming sets at runtime. This allows creators to completely clear the default world map (UNLOAD_MAP) and conditionally load custom, isolated assets or map sets (REQUEST_STREAM_SET) on the fly without requiring a server restart or mandatory global loading.

⚠️ This does not magically allow asset hot-swapping

How is this PR achieving the goal

This PR achieves dynamic map and stream set management through several key additions and internal enhancements:

  • Map Natives (UNLOAD_MAP, RELOAD_MAP): Introduces an emptymap/dlc.rpf and hooks into the game's ExecuteContentChangeSet / RevertContentChangeSet functions. UNLOAD_MAP forces the game to mount the empty map Content Change Set (CCS), effectively removing the base map, while RELOAD_MAP restores the default GROUP_MAP / GROUP_MAP_SP.

  • Stream Set Natives (REQUEST_STREAM_SET, RELEASE_STREAM_SET): Allows dynamically mounting and unmounting streamed overlays packaged within resources (defined via the stream_set metadata).

  • Resource Mounter Upgrades: Adds UnmountOverlay to CachedResourceMounter to support safely detaching file sets. Additionally, MountOverlay now mounts to overlay:/[overlayName]/[resourceName]/ instead of the root resources:/ to prevent VFS conflicts.

  • VFS Enhancements: Adds a recursive ForEachEntry method to VFSRagePackfile to allow iterating over files and directories inside the .rpf archives.

  • Event & Streaming Exports: Exposes required functions and events (e.g., GetCurrentMapGroup, CleanupStreaming, OnLoadContentXML, OnReloadMapStore)

  • EventCore Addition: Adds a ConnectOnce template to EventCore.h for event listeners that automatically disconnect after their first execution.

This PR applies to the following area(s)

FiveM, Client, Natives, GTA5

Successfully tested on

Game builds: 3258, 3570

Platforms: Windows

Checklist

  • Code compiles and has been tested successfully.
  • Code explains itself well and/or is documented.
  • My commit message explains what the changes do and what they are for.
  • No extra compilation warnings are added by these changes.

Fixes issues

  • None

fxmanifest sample

fx_version 'cerulean'
game 'gta5'

author 'showcase'
description 'Runtime map load/unload example'

-- Define and register a file set named 'my_map'
file_set 'my_map' {
    'stream_sets/my_map/**/*'
}

-- Register 'my_map' file set as stream set
stream_set 'my_map'

client_script 'client.lua'

client sample

local STREAM_SET_NAME = 'my_map'
local WATER_RESOURCE  = 'my_map_resource'
local WATER_PATH      = 'data/water.xml'

local isCustomMapActive = false

--- Wait for the screen to be fully faded out (blocking).
local function WaitForFadeOut()
    DoScreenFadeOut(0)
    while not IsScreenFadedOut() do
        Wait(1)
    end
end

--- Wait for a stream set to be fully loaded (blocking).
---@param name string
local function WaitForStreamSet(name)
    while not RequestStreamSet(name) do
        Wait(0)
    end
end

--- Load a custom map: fade out, unload the default GTA map,
--- load custom water data, request the stream set, then fade back in.
---@param cb? fun() Optional callback once the map is loaded.
local function LoadCustomMap(cb)
    if isCustomMapActive then
        return
    end

    isCustomMapActive = true

    WaitForFadeOut()
    UnloadMap()
    LoadWaterFromPath(WATER_RESOURCE, WATER_PATH)
    WaitForStreamSet(STREAM_SET_NAME)
    DoScreenFadeIn(0)

    if cb then
        cb()
    end
end

--- Unload the custom map: fade out, release the stream set,
--- reset water, reload the default GTA map, then fade back in.
---@param cb? fun() Optional callback once the default map is restored.
local function UnloadCustomMap(cb)
    if not isCustomMapActive then
        return
    end

    isCustomMapActive = false

    WaitForFadeOut()
    ReleaseStreamSet(STREAM_SET_NAME)
    ResetWater()
    ReloadMap()
    DoScreenFadeIn(0)

    if cb then
        cb()
    end
end

--- Toggle between the custom map and the default GTA map.
---@param cb? fun() Optional callback once the switch is complete.
local function SwitchMap(cb)
    if isCustomMapActive then
        UnloadCustomMap(cb)
    else
        LoadCustomMap(cb)
    end
end

-- Debug: log stream set events
AddEventHandler('onStreamSetLoad', function(name)
    print(('[map] Stream set loaded: %s'):format(name))
end)

AddEventHandler('onStreamSetUnload', function(name)
    print(('[map] Stream set unloaded: %s'):format(name))
end)

-- Command to toggle between maps
RegisterCommand('switch_map', function()
    SwitchMap()
end, false)

@github-actions github-actions Bot added the invalid Requires changes before it's considered valid and can be (re)triaged label Apr 20, 2026
@gizzdev gizzdev force-pushed the feature/runtime-map-load branch from 767788b to a9c70d1 Compare April 20, 2026 01:37
@gizzdev gizzdev changed the title (feat) Runtime map load / unload feat(natives): Runtime map load / unload Apr 20, 2026
@forzayt
Copy link
Copy Markdown

forzayt commented Apr 21, 2026

Yo thats a lot of stuffs

@gizzdev
Copy link
Copy Markdown
Author

gizzdev commented Apr 21, 2026

Yo thats a lot of stuffs

Yeah 😄

I had same thoughts before posting but it is the very minimal changes needed to achieve the map replacement goal while keeping FiveM streaming mechanics intact.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

invalid Requires changes before it's considered valid and can be (re)triaged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants