From the reference libraryRedM

discoveries/databindings/uiapps/map/map.lua

discoveries/databindings/uiapps/map/map.lua

discoveries/databindings/uiapps/map/map.lua

lines 1-200

--[[
--------------------------------------------------------------------------------------------------------------------------------------------
--                                                                                                                                        --
--                                                                EXAMPLE USAGE                                                           --
--                                                                                                                                        --
--------------------------------------------------------------------------------------------------------------------------------------------
]]

-- The keys here have been left on purpose to show you how the cards are linked to blips
-- See the bottom of this file for how to properly add blips and associate them with these cards
local MAP_CARDS <const> = {
    [1] = {
        type = "map_card_mp_playlist",
        title = "STABLE",
        subtitle = "Valentine",
        description = "You can buy some horses here!",
        footer = "Custom footer", -- Optional, can remove to use dynamic footer
        txd = "map_card_val_horseshop",
        txn = "image",

        -- Anything from here is only available for map_card_mp_playlist
        prompt = "Custom Prompt",
        stickerA = "stamp_xp",   -- See ui_startup_textures => elements_stamps_icons
        stickerB = "stamp_cash", -- See ui_startup_textures => elements_stamps_icons
    },
    [2] = {
        type = "map_card_mission",
        title = "GENERAL STORE",
        subtitle = "Valentine",
        description = "You can buy some goods here!",
        footer = nil,
        txd = "map_card_val_generalstore",
        txn = "image",
    },
    [3] = {
        type = "map_card_mp_mission",
        title = "BARBER",
        subtitle = "Valentine",
        description = "You can change your hairstyle here!",
        footer = nil,
        txd = "map_card_val_saloon_barber",
        txn = "image",
    }
}

-- The region keys of districts and towns don't directly correspond to in-game text
-- However, the keys of TEXT_WRITTEN and TEXT_PRINTED do, so they don't need these tables
local DISTRICT_FOOTERS <const> = {
    [`BayouNwa`] = "Bayou Nwa, Lemoyne",
    [`BigValley`] = "Big Valley, West Elizabeth",
    [`BluewaterMarsh`] = "Bluewater Marsh, Lemoyne",
    [`ChollaSprings`] = "Cholla Springs, New Austin",
    [`Cumberland`] = "Cumberland Forest, New Hanover",
    [`GaptoothRidge`] = "Gaptooth Ridge, New Austin",
    [`GreatPlains`] = "Great Plains, West Elizabeth",
    [`GrizzliesEast`] = "Grizzlies East, Ambarino",
    [`GrizzliesWest`] = "Grizzlies West, Ambarino",
    [`Guarmad`] = "Guarma, Guarma",
    [`Heartlands`] = "Heartlands, New Hanover",
    [`HennigansStead`] = "Hennigan's Stead, New Austin",
    [`RioBravo`] = "Rio Bravo, New Austin",
    [`Roanoke`] = "Roanoke Ridge, New Hanover",
    [`ScarlettMeadows`] = "Scarlett Meadows, Lemoyne",
    [`TallTrees`] = "Tall Trees, West Elizabeth",
}

local TOWN_FOOTERS <const> = {
    [`AguasdulcesFarm`] = "Aguasdulces, Guarma",
    [`AguasdulcesRuins`] = "Aguasdulces, Guarma",
    [`AguasdulcesVilla`] = "Aguasdulces, Guarma",
    [`Annesburg`] = "Annesburg, Roanoke Ridge, NH",
    [`Armadillo`] = "Armadillo, Cholla Springs, NA",
    [`BeechersHope`] = "Beecher's Hope, Great Plains, WE",
    [`Blackwater`] = "Blackwater, Great Plains, WE",
    [`Braithwaite`] = "Braithwaite Manor, Scarlett Meadows, LE",
    [`Butcher`] = "Butcher Creek, Roanoke Ridge, NH",
    [`Caliga`] = "Caliga Hall, Bayou Nwa, LE",
    [`Cornwall`] = "Cornwall Kerosene & Tar, The Heartlands, NH",
    [`Emerald`] = "Emerald Ranch, The Heartlands, NH",
    [`Lagras`] = "Lagras, Bayou Nwa, LE",
    [`Manicato`] = "Manicato, Guarma",
    [`Manzanita`] = "Manzanita Post, Tall Trees, WE",
    [`Rhodes`] = "Rhodes, Scarlett Meadows, LE",
    [`Siska`] = "Sisika Penitentiary, Bluewater Marsh, LE",
    [`StDenis`] = "Saint Denis, Bayou Nwa, LE",
    [`Strawberry`] = "Strawberry, Big Valley, WE",
    [`Tumbleweed`] = "Tumbleweed, Gaptooth Ridge, NA",
    [`Valentine`] = "Valentine, The Heartlands, NH",
    [`Vanhorn`] = "Van Horn Trading Post, Roanoke Ridge, NH",
    [`Wallace`] = "Fort Wallace, Cumberland Forest, NH",
    [`Wapiti`] = "Wapiti Indian Reservation, Grizzlies, AM",
}

-- Bindings
local mapFocusContainer = nil
local mapRegion = nil
local stickerAVisible = nil
local stickerATexture = nil
local stickerBVisible = nil
local stickerBTexture = nil

-- State
local mapCardOpen = false
local mapFooterOverride = nil
local lastFocusDistrict = nil
local lastFocusWrittenText = nil
local lastFocusPrintedText = nil
local lastFocusTown = nil
local lastFocusBlip = nil
local lastBlipTypeId = nil

function UpdateMapRegion()
    if mapCardOpen then return end
    if not mapRegion and not mapFooterOverride then return end

    local regionText = ""
    if mapFooterOverride then
        regionText = mapFooterOverride
    elseif lastFocusWrittenText and lastFocusWrittenText ~= 0 then
        regionText = GetStringFromHashKey(lastFocusWrittenText)
    elseif lastFocusPrintedText and lastFocusPrintedText ~= 0 then
        regionText = GetStringFromHashKey(lastFocusPrintedText)
    else
        regionText = lastFocusTown or lastFocusDistrict or ""
    end

    DatabindingWriteDataString(mapRegion, regionText)
end

function OnBlipFocused(card, typeId)
    if not mapFocusContainer then return end

    while IsUiappTransitioningByHash("map") do
        Wait(0)
    end

    if DoesStreamedTxdExist(card.txd) then
        RequestStreamedTxd(card.txd, false)

        while HasStreamedTxdLoaded(card.txd) ~= 1 do
            Wait(0)
        end
    end

    local blipContainer <const> = DatabindingAddDataContainer(mapFocusContainer, "BLIP")
    DatabindingAddDataHash(blipContainer, "imageTXD", card.txd)
    DatabindingAddDataHash(blipContainer, "imageTX", card.txn)

## lines 1-200

    local titleKey = string.format("BLIP_%d_TITLE", typeId)
    AddTextEntry(titleKey, card.title)
    DatabindingAddDataHash(blipContainer, "name", titleKey)

    if card.type == "map_card_player" or card.type == "map_card_mp_mission" then
        DatabindingAddDataString(blipContainer, "description_title", card.subtitle)
    else
        local subtitleKey = string.format("BLIP_%d_SUBTITLE", typeId)
        AddTextEntry(subtitleKey, card.subtitle)
        DatabindingAddDataString(blipContainer, "description_title", subtitleKey)
    end

    if card.type == "map_card_mp_mission" then
        DatabindingAddDataString(blipContainer, "description", card.description)
    else
        local descriptionKey = string.format("BLIP_%d_DESCRIPTION", typeId)
        AddTextEntry(descriptionKey, card.description)
        DatabindingAddDataString(blipContainer, "description", descriptionKey)
    end

    if card.type == "map_card_mp_playlist" then
        if card.prompt and card.prompt ~= "" then
            local promptKey = string.format("BLIP_%d_START_PROMPT", typeId)
            AddTextEntry(promptKey, card.prompt)

            DatabindingAddDataBool(blipContainer, "startable", true)
            DatabindingAddDataHash(blipContainer, "prompt", promptKey)
        else
            DatabindingAddDataBool(blipContainer, "startable", false)
            DatabindingAddDataHash(blipContainer, "prompt", 0)
        end

        DatabindingWriteDataBool(stickerAVisible, card.stickerA and card.stickerA ~= "" or false)
        DatabindingWriteDataHashString(stickerATexture, card.stickerA or 0)

        DatabindingWriteDataBool(stickerBVisible, card.stickerB and card.stickerB ~= "" or false)
        DatabindingWriteDataHashString(stickerBTexture, card.stickerB or 0)
    end

    local cardType = card.type or "map_card_mission"
    DatabindingInsertUiItemToListFromContextStringAlias(mapFocusContainer, -1, cardType, blipContainer)
end

function OnBlipUnfocused(card)
    if not mapFocusContainer then return end

    while IsUiappTransitioningByHash("map") do
        Wait(0)
    end

    RequestUiappTransitionByHash("map", "hide_info")
    mapFooterOverride = nil

lines 201-400

    mapCardOpen = false

    DatabindingClearBindingArray(mapFocusContainer)
    SetStreamedTxdAsNoLongerNeeded(card.txd)
end

function OnBlipSelected(card)
    while IsUiappTransitioningByHash("map") do
        Wait(0)
    end

    RequestUiappTransitionByHash("map", "show_info")

    if card.footer then
        mapFooterOverride = card.footer
        UpdateMapRegion()
    end

    mapCardOpen = true
end

function InitializeBindings()
    mapFocusContainer = DatabindingAddUiItemListFromPath("", "MapFocus")
    mapRegion = DatabindingAddDataString(mapFocusContainer, "Region", "")
    DatabindingAddDataBool(mapFocusContainer, "ItemHovered", false)
    DatabindingAddDataString(mapFocusContainer, "HoveredName", "")

    local stickerA = DatabindingAddDataContainerFromPath("", "StickerA");
    stickerAVisible = DatabindingAddDataBool(stickerA, "Visible", false);
    stickerATexture = DatabindingAddDataHash(stickerA, "Texture", 0);

    local stickerB = DatabindingAddDataContainerFromPath("", "StickerB");
    stickerBVisible = DatabindingAddDataBool(stickerB, "Visible", false);
    stickerBTexture = DatabindingAddDataHash(stickerB, "Texture", 0);
end

InitializeBindings()

--[[
--------------------------------------------------------------------------------------------------------------------------------------------
--                                                                                                                                        --
--                                                                EVENTS SYSTEM                                                           --
--                                                Requires using dataview by Gottfriedleibniz                                             --
--                                                                                                                                        --
--------------------------------------------------------------------------------------------------------------------------------------------
]]

local EVENT_CHANNEL <const> = `map_menu`

CreateThread(function()
    while true do
        if IsUiappRunning("map") ~= 1 then
            Wait(1000)
            goto continue
        end

        while EventsUiIsPending(EVENT_CHANNEL) do
            -- If you don't have DataView available, this will error
            local msg = DataView.ArrayBuffer(8 * 4)

            if Citizen.InvokeNative(0x90237103F27F7937, EVENT_CHANNEL, msg:Buffer()) ~= 0 then -- EVENTS_UI_PEEK_MESSAGE
                local eventHash = msg:GetInt32(0)
                local intParameter = msg:GetInt32(8)
                local hashParameter = msg:GetInt32(16)

                if eventHash == `ITEM_FOCUSED` then
                    if hashParameter == `DISTRICT` then
                        lastFocusDistrict = DISTRICT_FOOTERS[intParameter]
                    elseif hashParameter == `TOWN` then
                        lastFocusTown = TOWN_FOOTERS[intParameter]
                    elseif hashParameter == `TEXT_WRITTEN` then
                        lastFocusWrittenText = intParameter
                    elseif hashParameter == `TEXT_PRINTED` then
                        lastFocusPrintedText = intParameter
                    elseif hashParameter == `BLIP` then
                        lastFocusBlip = intParameter
                    end

                    TriggerEvent("map:item_focused", hashParameter, intParameter)
                end

                if hashParameter == `BLIP` then
                    -- _GET_BLIP_MAP_CARD_INFO
                    local blipTypeId, _ = Citizen.InvokeNative(0x0DF2B55F717DDB10, intParameter, Citizen.PointerValueInt(), Citizen.PointerValueInt())
                    local blipCard = MAP_CARDS[blipTypeId]

                    if eventHash == `ITEM_FOCUSED` then
                        OnBlipFocused(blipCard, blipTypeId)
                    elseif eventHash == `ITEM_UNFOCUSED` then
                        OnBlipUnfocused(blipCard)
                    elseif eventHash == `ITEM_SELECTED` then
                        OnBlipSelected(blipCard)
                    end

                    lastBlipTypeId = blipTypeId
                end

                if hashParameter == `CARD` and eventHash == `ITEM_SELECTED` then
                    local blipCard = MAP_CARDS[lastBlipTypeId]
                    TriggerEvent("map:card_selected", lastFocusBlip, blipCard)
                end
            end

            EventsUiPopMessage(EVENT_CHANNEL)
        end

        Wait(0)
        ::continue::
    end
end)

AddEventHandler("map:item_focused", function()
    UpdateMapRegion()
end)

AddEventHandler("map:card_selected", function(blipId, card)
    print(string.format("Blip with ID %d was selected!", blipId))
end)

--[[
--------------------------------------------------------------------------------------------------------------------------------------------
--                                                                                                                                        --
--                                                            ADDING BLIPS PROPERLY                                                       --
--         It's important that you use the native _SET_BLIP_MAP_CARD_INFO (0x02FF4CF43B7209D1) to associate blips with map cards          --
--                                 If you don't do this, no map card will show up when focusing the blip                                  --
--                                                                                                                                        --
--------------------------------------------------------------------------------------------------------------------------------------------
]]

## lines 201-400

local BLIPS <const> = {
    {
        coords = vector3(0, 0, 0),
        style = "blip_style_shop",
        sprite = "blip_stable",
        name = "Stable",
        modifiers = {
            "blip_modifier_grouping_valentine_follower", -- Grouped with the valentine town blip, see below
            "blip_modifier_tod_daytime_only",            -- Only "active" during daytime (7-17), otherwise shows time icon
        },
        card = 1,                                        -- This corresponds to the key in MAP_CARDS
    },
    {
        coords = vector3(100, 0, 0),
        style = "blip_style_shop",
        sprite = "blip_shop_store",
        name = "General Store",
        modifiers = {
            "blip_modifier_grouping_valentine_follower", -- Grouped with the valentine town blip, see below
            "blip_modifier_tod_nighttime_only",          -- Only "active" during nighttime (18-6), otherwise shows time icon
        },
        card = 2,
    },
    {
        coords = vector3(-100, 0, 0),
        style = "blip_style_shop",
        sprite = "blip_shop_barber",
        name = "Barber",
        modifiers = {
            "blip_modifier_grouping_valentine_follower", -- Grouped with the valentine town blip, see below
            "blip_modifier_locked"
        },
        card = 3,
    },

    -- Town grouping blip without map card
    {
        coords = vector3(0, 0, 0),
        style = "blip_style_town",
        sprite = "blip_town",
        name = "Example Blips",
        modifiers = { "blip_modifier_grouping_valentine_leader" }
    },
}

local createdBlips = {}

for _, blip in ipairs(BLIPS) do
    local blipId = nil
    local coords = blip.coords or vector3(0, 0, 0)
    local scale = blip.scale or vector3(0, 0, 0)

    if blip.type == "area" then
        blipId = BlipAddForArea(blip.style, coords.x, coords.y, coords.z, scale.x, scale.y, scale.z, 0)
    else
        blipId = BlipAddForCoords(blip.style, coords.x, coords.y, coords.z)
    end

    if blip.card then
        Citizen.InvokeNative(0x02FF4CF43B7209D1, blipId, blip.card, blip.name) -- _SET_BLIP_MAP_CARD_INFO
    end

    -- The order of native calls matters here, modifiers can apply names or sprites
    for _, modifier in ipairs(blip.modifiers) do
        BlipAddModifier(blipId, modifier)
    end

    if blip.sprite then SetBlipSprite(blipId, blip.sprite, true) end
    if blip.name then SetBlipName(blipId, blip.name) end

    table.insert(createdBlips, blipId)

lines 401-410

end

AddEventHandler("onResourceStop", function(resourceName)
    if resourceName ~= GetCurrentResourceName() then return end

    for _, blipId in ipairs(createdBlips) do
        RemoveBlip(blipId)
    end
end)

Back to documentation