🔌 04 — Esport e Import

INFO

FiveM permette di esporre funzioni da una risorsa e usarle da altre tramite il sistema exports. Questo meccanismo è fondamentale per creare librerie riutilizzabili e separare il codice in moduli indipendenti.

ObiettivoDettagli
Cosa imparerai- Esporre funzioni tra risorse con exports
- Importare funzioni da altre risorse
- Gestire versioning, dipendenze e async readiness
PrerequisitiCompletamento file 02 e 03, familiarità con risorse FiveM

Condividere Funzioni tra Risorse

FiveM permette di esporre funzioni da una risorsa e usarle da altre tramite exports.

flowchart LR
    R1[Risorsa A\nutility] -->|Esporta| E[exports.utility.function()]
    R2[Risorsa B\nmain] -->|Importa| E
    R3[Risorsa C\nadmin] -->|Importa| E
    E -->|Esegue| F[Funzione in utility]

Esport

Per esporre funzioni da una risorsa, usa exports nel file server.lua o client.lua.

-- utility/server.lua
-- Questa funzione sarà accessibile da altre risorse
 
local function Saluta(nome)
    return "Ciao " .. nome .. "!"
end
 
local function SommaNumeri(a, b)
    return (a or 0) + (b or 0)
end
 
-- Esponi le funzioni
exports("Saluta", Saluta)
exports("SommaNumeri", SommaNumeri)
-- utility/client.lua
local function GetPlayerStatus()
    local ped = PlayerPedId()
    local salute = GetEntityHealth(ped)
    local maxSalute = GetEntityMaxHealth(ped)
    local armatura = GetPedArmour(ped)
    local pos = GetEntityCoords(ped)
 
    return {
        hp = salute,
        maxHp = maxSalute,
        armour = armatura,
        inVehicle = IsPedInAnyVehicle(ped, false),
        coords = pos
    }
end
 
exports("GetPlayerStatus", GetPlayerStatus)

Pattern con tabella di funzioni

-- utility/client.lua
local Utility = {}
 
function Utility.GetDistance(p1, p2)
    return #(vector3(p1.x, p1.y, p1.z) - vector3(p2.x, p2.y, p2.z))
end
 
function Utility.Notifica(tipo, testo, durata)
    durata = durata or 3000
    SetNotificationTextEntry("STRING")
    AddTextComponentSubstringPlayerName(testo)
    if tipo == "error" then
        DrawNotification(true, true)
    else
        DrawNotification(false, false)
    end
end
 
function Utility.FormatDenaro(importo)
    local formatted = tostring(math.floor(importo))
    -- Inserisci punti ogni 3 cifre
    local k = 1
    while k < #formatted do
        formatted = formatted:sub(1, -k - 3) .. "." .. formatted:sub(-k - 2)
        k = k + 4
    end
    return "$" .. formatted
end
 
-- Esportazione iterativa (più pulita per molti exports)
for k, v in pairs(Utility) do
    exports(k, v)
end

Import

Per importare funzioni da un’altra risorsa, usa exports["nome_risorsa"].

-- main/server.lua
 
-- Chiamare funzione esportata da "utility"
local messaggio = exports["utility"]:Saluta("Marco")
print(messaggio) --> "Ciao Marco!"
 
-- Con parametri multipli
local somma = exports["utility"]:SommaNumeri(10, 20)
print(somma) --> 30
-- main/client.lua
Citizen.CreateThread(function()
    Citizen.Wait(2000) -- aspetta che utility sia pronta
 
    -- Ottieni stato giocatore
    local status = exports["utility"]:GetPlayerStatus()
    print("HP:", status.hp, "Armatura:", status.armour)
    print("In veicolo:", status.inVehicle)
 
    -- Usa utility di distanza
    local pos1 = vector3(0, 0, 0)
    local pos2 = vector3(10, 10, 0)
    local dist = exports["utility"]:GetDistance(pos1, pos2)
    print("Distanza:", dist)
end)

Sintassi alternativa con dot

-- Entrambi funzionano:
exports["utility"]:Saluta("Marco")
exports.utility.Saluta("Marco")  -- equivalente se il nome risorsa non ha caratteri speciali

Esempio Completo: Libreria di Utility

fxmanifest.lua

-- utility/fxmanifest.lua
fx_version "cerulean"
game "gta5"
 
client_scripts {
    "client.lua"
}
 
server_scripts {
    "server.lua"
}

utility/client.lua

-- utility/client.lua
local Utility = {}
Utility.__version = "1.2.0"
 
function Utility.Notifica(messaggio, tipo)
    tipo = tipo or "info"
 
    -- Mappa colori per tipo
    local colori = {
        info = { 255, 255, 255 },
        success = { 0, 255, 0 },
        error = { 255, 0, 0 },
        warning = { 255, 255, 0 }
    }
 
    local colore = colori[tipo] or colori.info
 
    TriggerEvent("chat:addMessage", {
        color = colore,
        args = { "UTILITY", messaggio }
    })
end
 
function Utility.DrawText3D(x, y, z, testo, colore, scala)
    colore = colore or { 255, 255, 255, 215 }
    scala = scala or 0.35
 
    local onScreen, _x, _y = World3dToScreen2d(x, y, z)
    if onScreen then
        SetTextFont(4)
        SetTextProportional(1)
        SetTextScale(0.0, scala)
        SetTextColour(colore[1], colore[2], colore[3], colore[4] or 215)
        SetTextEntry("STRING")
        SetTextCentre(true)
        AddTextComponentString(testo)
        DrawText(_x, _y)
    end
end
 
function Utility.GetClientePronto()
    return true
end
 
-- Esporta tutto
for k, v in pairs(Utility) do
    exports(k, v)
end
 
print("^2[Utility] Libreria caricata (versione " .. Utility.__version .. ")")

utility/server.lua

-- utility/server.lua
local Utility = {}
 
function Utility.Log(messaggio, livello)
    livello = livello or "info"
    local prefix = {
        info = "^3[INFO]",
        warn = "^8[WARN]",
        error = "^1[ERROR]",
        debug = "^5[DEBUG]"
    }
 
    print((prefix[livello] or "^3[INFO]") .. " " .. messaggio .. "^7")
end
 
function Utility.GeneraID()
    -- Genera ID alfanumerico semplice
    local chars = "abcdefghijklmnopqrstuvwxyz0123456789"
    local id = ""
    for i = 1, 8 do
        id = id .. chars:sub(math.random(1, #chars), math.random(1, #chars))
    end
    return id
end
 
function Utility.GetOnlinePlayers()
    local players = {}
    for _, playerId in ipairs(GetPlayers()) do
        table.insert(players, {
            id = playerId,
            name = GetPlayerName(playerId),
            ping = GetPlayerPing(playerId)
        })
    end
    return players
end
 
for k, v in pairs(Utility) do
    exports(k, v)
end
 
print("^2[Utility Server] Libreria caricata^7")

Usare la libreria da un’altra risorsa

-- main/client.lua
Citizen.CreateThread(function()
    -- Aspetta che la risorsa utility sia pronta
    local pronto = false
    while not pronto do
        Citizen.Wait(100)
        local successo, risultato = pcall(function()
            return exports["utility"]:GetClientePronto()
        end)
        pronto = successo and risultato
    end
 
    print("Utility disponibile!")
    exports["utility"]:Notifica("Utility collegata!", "success")
end)
 
RegisterCommand("distance", function(source, args)
    if #args ~= 2 then
        exports["utility"]:Notifica("Uso: /distance [id1] [id2]", "warning")
        return
    end
 
    local id1 = tonumber(args[1])
    local id2 = tonumber(args[2])
 
    -- Calcola distanza tra due giocatori
    local ped1 = GetPlayerPed(id1)
    local ped2 = GetPlayerPed(id2)
 
    if ped1 == 0 or ped2 == 0 then
        exports["utility"]:Notifica("Giocatore non trovato", "error")
        return
    end
 
    local coords1 = GetEntityCoords(ped1)
    local coords2 = GetEntityCoords(ped2)
    local dist = #(vector3(coords1.x, coords1.y, coords1.z) -
                   vector3(coords2.x, coords2.y, coords2.z))
 
    exports["utility"]:Notifica(
        string.format("Distanza: %.1f metri", dist),
        "info"
    )
end, false)

QUESTION

Riflessione: La tua risorsa esporta funzioni con nomi abbastanza specifici da evitare collisioni? Hai considerato l’ordine di avvio e le dipendenze?


Best Practices

Namespace (prefisso)

TIP

Usa sempre un prefisso per i tuoi export. Nomi generici come GetData o SaveData collidono facilmente con altre risorse. Prefissali col nome della tua risorsa.

Usa il nome della tua risorsa o un prefisso per evitare collisioni:

-- Male: collisione probabile con altre risorse
exports("GetData", GetData)
exports("SaveData", SaveData)
 
-- Bene: namespaced
exports("myUtils_GetData", GetData)
exports("myUtils_SaveData", SaveData)

Versioning

Se la tua libreria cambia, documenta la versione:

-- utility/client.lua
local Utility = {}
Utility.__version = "2.0.0"
Utility.__compat = { "1.x" } -- compatibilità con versioni precedenti
 
function Utility.__checkVersion(richiesta)
    local major = tonumber(richiesta:match("^(%d+)"))
    local ourMajor = tonumber(Utility.__version:match("^(%d+)"))
    return major == ourMajor
end
 
exports("__checkVersion", Utility.__checkVersion)
exports("__version", Utility.__version)
 
-- main/client.lua
if exports["utility"]:__checkVersion("2.x") then
    print("Versione compatibile!")
else
    print("ATTENZIONE: utility versione " .. exports["utility"]:__version() .. " richiede aggiornamento")
end

Async readiness

WARNING

Le risorse non si avviano in ordine garantito. Se una risorsa chiama exports su un’altra non ancora avviata, ottieni attempt to call a nil value. Usa sempre un loop di attesa con pcall prima di usare un export.

Le risorse non si avviano in ordine garantito. Aspetta sempre che l’esport sia pronto:

-- main/client.lua
function AttendiEsport(nomeRisorsa, nomeExport, timeout)
    timeout = timeout or 10000
    local start = GetGameTimer()
 
    while GetGameTimer() - start < timeout do
        local successo, risultato = pcall(function()
            return exports[nomeRisorsa][nomeExport](exports[nomeRisorsa])
        end)
        if successo and risultato ~= nil then
            return true
        end
        Citizen.Wait(100)
    end
 
    print("^1ERRORE: Export " .. nomeExport .. " da " .. nomeRisorsa .. " non disponibile^7")
    return false
end
 
-- Uso
Citizen.CreateThread(function()
    if AttendiEsport("utility", "GetClientePronto") then
        exports["utility"]:Notifica("Tutto pronto!", "success")
    end
end)

Ordine fxmanifest

Il fxmanifest.lua deve dichiarare le dipendenze:

-- main/fxmanifest.lua
fx_version "cerulean"
game "gta5"
 
-- utility deve essere avviata PRIMA di main
dependencies {
    "utility"
}
 
client_scripts {
    "client.lua"
}
 
server_scripts {
    "server.lua"
}

Esport vs Altri Metodi

MetodoScopoLatenzaSicurezza
exportsCondividere funzioni tra risorseBassaNome risorsa come namespace
TriggerEvent / TriggerServerEventComunicazione event-drivenMedia (rete)Filtrabile per risorsa
Global table (es. ESX = ESX or {})Condivisione globaleBassaRischio collisioni
GetResourceState / pollingStato risorsaAlta (polling)Affidabile
flowchart TD
    A[Devo condividere codice tra risorse?]
    A -->|Sì, funzioni specifiche| B[Usa exports]
    A -->|Sì, dati/eventi| C[Usa TriggerEvent]
    A -->|Condivisione globale framework| D[Usa tabella globale]
    A -->|No| E[Tutto nella stessa risorsa]
    B --> F[Meglio: namespaced + versionato]

Troubleshooting

ProblemaCausaSoluzione
attempt to call a nil valueRisorsa esport non avviataAggiungi dependencies o attendi col loop
exports["x"]:y() is not a functionNome export erratoVerifica spelling, check exports("nome", fn)
Export funziona solo intermittenteRace condition all’avvioUsa loop di attesa con pcall
Conflitto tra risorseStesso nome exportUsa prefisso unico

TIP

Prossimo passo: 05 - Config e Database


Torna alla: Indice Generale