Skip to content
HELIX is in Early Access Preview. Some features and documentation may be non-functional or out of date.

Port Your FiveM Script to HELIX#

A Comprehensive Tutorial for FiveM Developers coming to HELIX


Table of Contents#

  1. Introduction
  2. Core Concepts
  3. Player & Character Management
  4. Database Operations
  5. Events & Callbacks
  6. UI Systems (NUI vs WebUI)
  7. Vehicle Management
  8. Weapon Management
  9. World Objects & Entities
  10. Notifications & HUD
  11. Complete Example: Porting a Simple Script
  12. Common Pitfalls & Best Practices

Introduction#

HELIX is built on Unreal Engine 5, which means fundamental differences from FiveM's GTA V native system. This guide will help you understand how to translate your FiveM knowledge to HELIX development using the QBCore framework.

Key Differences:

  • Engine: Unreal Engine 5 vs GTA V (RAGE Engine)
  • Natives: HELIX uses custom classes instead of GTA natives
  • Coordinates: HELIX uses Unreal's coordinate system (cm-based)
  • Objects: Strongly-typed classes (HPlayer, HCharacter, etc.)

Core Concepts#

Getting the Core Object#

FiveM:

-- FiveM
local QBCore = exports['qb-core']:GetCoreObject()

HELIX:

-- HELIX
local QBCore = exports['qb-core']:GetCoreObject() -- Same!

Player Data Structure#

Both systems use similar PlayerData structures through QBCore:

-- Both FiveM and HELIX
PlayerData = {
    citizenid = 'ABC12345',
    cid = 1,
    money = { cash = 500, bank = 5000, crypto = 0 },
    job = {
        name = 'police',
        label = 'Police',
        grade = { name = 'Officer', level = 1 },
        onduty = true
    },
    gang = { ... },
    metadata = {
        hunger = 100,
        thirst = 100,
        stress = 0,
        armor = 0
    },
    charinfo = {
        firstname = 'John',
        lastname = 'Doe',
        birthdate = '01-01-1990',
        phone = '1234567890'
    }
}

Player & Character Management#

Getting Local Player#

FiveM:

-- FiveM
local ped = PlayerPedId()
local coords = GetEntityCoords(ped)

HELIX:

-- HELIX - Uses HPlayer and HCharacter classes
local player = Client.GetLocalPlayer()
local character = player:GetControlledCharacter()

-- Check if character exists 
if not character then return end

local coords = character:GetLocation()  -- Returns Vector object

Getting Player Position#

FiveM:

-- FiveM
local coords = GetEntityCoords(PlayerPedId())
local x, y, z = coords.x, coords.y, coords.z

HELIX:

-- HELIX
local player = Client.GetLocalPlayer()
local character = player:GetControlledCharacter()
if not character then return end

local location = character:GetLocation()  -- Vector object
-- Access components: location.X, location.Y, location.Z

Getting Closest Player (Client)#

FiveM:

-- FiveM
function GetClosestPlayer()
    local players = GetActivePlayers()
    local ped = PlayerPedId()
    local coords = GetEntityCoords(ped)
    local closestDistance = -1
    local closestPlayer = -1

    for _, player in pairs(players) do
        local targetPed = GetPlayerPed(player)
        if targetPed ~= ped then
            local targetCoords = GetEntityCoords(targetPed)
            local distance = #(coords - targetCoords)
            if closestDistance == -1 or distance < closestDistance then
                closestPlayer = player
                closestDistance = distance
            end
        end
    end

    return closestPlayer, closestDistance
end

HELIX:

-- HELIX - Built into QBCore.Functions
local closestPlayer, closestDistance = QBCore.Functions.GetClosestPlayer()

-- Or get the HCharacter directly
local closestCharacter, closestDistance = QBCore.Functions.GetClosestHCharacter()

Getting Closest Player (Server)#

FiveM:

-- FiveM - You'd need to write this yourself
function GetClosestPlayer(source, coords)
    -- Custom implementation needed
end

HELIX:

-- HELIX - Built-in server function
local closestPlayer, closestDistance = QBCore.Functions.GetClosestPlayer(source, coords)

-- Or get HCharacter
local closestChar, distance = QBCore.Functions.GetClosestHCharacter(source, coords)

Database Operations#

Database Initialization#

FiveM:

-- FiveM (using oxmysql or mysql-async)
MySQL.ready(function()
    print('Database ready')
end)

HELIX:

-- HELIX - Initialization in Server/database.lua
Database.Initialize('qbcore.db')

Database Queries#

FiveM (SELECT):

-- FiveM
MySQL.Async.fetchAll('SELECT * FROM players WHERE citizenid = ?', {citizenid}, function(result)
    if result[1] then
        local PlayerData = result[1]
        -- Process data
    end
end)

HELIX (SELECT):

-- HELIX - Synchronous approach
local result = [Database.Select](http://Database.Select)('SELECT * FROM players WHERE citizenid = ?', { citizenid })

-- Result is a TArray (Unreal Engine array)
if result[1] then
    local PlayerData = result[1].Columns:ToTable()
    -- PlayerData is now a Lua table
end

Database Insert/Update#

FiveM:

-- FiveM
MySQL.Async.execute('UPDATE players SET money = ? WHERE citizenid = ?', 
    {json.encode(money), citizenid})

HELIX:

-- HELIX - Using JSON.stringify for tables
local success = Database.Execute('UPDATE players SET money = ? WHERE citizenid = ?', 
    { JSON.stringify(money), citizenid })

-- success is boolean

UPSERT Operations#

FiveM:

-- FiveM (MySQL specific)
MySQL.Async.execute([[
    INSERT INTO players (citizenid, name, money) 
    VALUES (?, ?, ?)
    ON DUPLICATE KEY UPDATE
        name = VALUES(name),
        money = VALUES(money)
]], {citizenid, name, json.encode(money)})

HELIX:

-- HELIX - SQLite UPSERT syntax
Database.Execute([[
    INSERT INTO players (citizenid, name, money)
    VALUES (?, ?, ?)
    ON CONFLICT(citizenid) DO UPDATE SET
        name = [excluded.name](http://excluded.name),
        money = [excluded.money](http://excluded.money)
]], { citizenid, name, JSON.stringify(money) })

JSON Handling in Database#

FiveM:

-- FiveM
local moneyJson = json.encode([PlayerData.money](http://PlayerData.money))
local moneyData = json.decode([result.money](http://result.money))

HELIX:

-- HELIX - Uses JSON global (capital J)
local moneyJson = JSON.stringify([PlayerData.money](http://PlayerData.money))
local moneyData = JSON.parse([result.money](http://result.money))

Events & Callbacks#

Client Events#

Both systems are similar:

-- Both FiveM and HELIX
RegisterClientEvent('eventName', function(param1, param2)
    -- Handle event
end)

-- Triggering server event from client
TriggerServerEvent('serverEventName', data)

Server Events#

FiveM:

-- FiveM
RegisterServerEvent('eventName')
AddEventHandler('eventName', function(param1, param2)
    local src = source
    -- Handle event
end)

HELIX:

-- HELIX - Same pattern
RegisterServerEvent('eventName', function(source, param1, param2)
    -- Note: source is passed as first parameter
    -- Handle event
end)

Callbacks#

FiveM:

-- NO NATIVE SOLUTION, MUST USE QBCORE

-- FiveM Client
QBCore.Functions.TriggerCallback('callbackName', function(result)
    print(result)
end, arg1, arg2)

-- FiveM Server
QBCore.Functions.CreateCallback('callbackName', function(source, cb, arg1, arg2)
    cb(returnValue)
end)

HELIX:

-- NATIVE SOLUTION TO HELIX

-- HELIX Client 
TriggerCallback('callbackName', function(result)
    print(result)
end, arg1, arg2)

-- HELIX Server
RegisterCallback('callbackName', function(source, arg1, arg2)
    return returnValue
end)

UI Systems (NUI vs WebUI)#

Creating UI#

FiveM (NUI):

-- FiveM
SendNUIMessage({
    action = 'openUI',
    data = someData
})

-- In HTML/JS
window.addEventListener('message', function(event) {
    if ([event.data](http://event.data).action === 'openUI') {
        // Handle UI
    }
})

HELIX (WebUI):

-- HELIX - Creating WebUI instance
local myWebUI = WebUI('UniqueID', 'resource-name/path/to/index.html', true)

-- Wait for browser to load
myWebUI.Browser.OnLoadCompleted:Add(myWebUI.Browser, function()
    -- Now safe to call functions
    myWebUI:CallFunction('functionName', param1, param2)
end)

-- Calling JS functions from Lua
myWebUI:CallFunction('updateData', { cash = 1000, bank = 5000 })

Sending Data to UI#

FiveM:

-- FiveM
SendNUIMessage({
    type = 'updateBalance',
    cash = cash,
    bank = bank
})

HELIX:

-- HELIX - Direct function calls
myWebUI:CallFunction('updateBalances', { cash = cash, bank = bank })

Receiving Data from UI#

FiveM:

-- FiveM (Client-side)
RegisterNUICallback('buttonClicked', function(data, cb)
    print('Button clicked with data:', data)
    cb('ok')
end)

-- In JS
$.post('[https://resource-name/buttonClicked](https://resource-name/buttonClicked)', JSON.stringify({
    value: someValue
}))

HELIX:

-- HELIX - WebUI Subscribe
myWebUI:Subscribe('buttonClicked', function(data)
    print('Button clicked with data:', data)
end)

-- In JS - Using global hEvent function
hEvent('buttonClicked', { value: someValue })

Complete WebUI Example#

HELIX:

-- Client/Index.lua
local bankingUI = nil

function OpenBanking()
    if not bankingUI then
        bankingUI = WebUI('Banking', 'qb-banking/Client/html/index.html', 3)

        -- Wait for browser to load
        bankingUI.Browser.OnLoadCompleted:Add(bankingUI.Browser, function()
            -- Get player data
            local PlayerData = QBCore.Functions.GetPlayerData()

            -- Send initial data
            bankingUI:CallFunction('initData', {
                cash = [PlayerData.money.cash](http://PlayerData.money.cash),
                bank = [PlayerData.money.bank](http://PlayerData.money.bank),
                playerData = {
                    name = PlayerData.charinfo.firstname .. ' ' .. PlayerData.charinfo.lastname,
                    job = PlayerData.job
                }
            })
        end)

        -- Register event handlers
        bankingUI:Subscribe('deposit', function(data)
            TriggerServerEvent('qb-banking:server:deposit', data.amount)
        end)

        bankingUI:Subscribe('withdraw', function(data)
            TriggerServerEvent('qb-banking:server:withdraw', data.amount)
        end)

        bankingUI:Subscribe('close', function()
            bankingUI:Destroy()
            bankingUI = nil
        end)
    end
end

JavaScript (index.html):

// HELIX - Global function to send events to Lua
function hEvent(eventName, data) {
    // This is provided by HELIX WebUI system
}

// Called from Lua via CallFunction
window.initData = function(data) {
    document.getElementById('cash').textContent = '$' + [data.cash](http://data.cash)
    document.getElementById('bank').textContent = '$' + [data.bank](http://data.bank)
}

window.updateBalances = function(data) {
    document.getElementById('cash').textContent = '$' + [data.cash](http://data.cash)
    document.getElementById('bank').textContent = '$' + [data.bank](http://data.bank)
}

// Send event to Lua
document.getElementById('depositBtn').addEventListener('click', function() {
    const amount = parseInt(document.getElementById('amount').value)
    hEvent('deposit', { amount: amount })
})

Vehicle Management#

Spawning a Vehicle#

FiveM:

-- FiveM
local model = GetHashKey('adder')
RequestModel(model)
while not HasModelLoaded(model) do
    Wait(0)
end

local coords = GetEntityCoords(PlayerPedId())
local vehicle = CreateVehicle(model, coords.x, coords.y, coords.z, heading, true, false)
SetPedIntoVehicle(PlayerPedId(), vehicle, -1)

HELIX:

-- HELIX - Client side
local player = Client.GetLocalPlayer()
local character = player:GetControlledCharacter()
if not character then return end

local location = character:GetLocation()
local rotation = character:GetRotation()
local forward = rotation:GetForwardVector()
local spawnLocation = location + forward * 500  -- 500cm in front

-- Create vehicle using QBCore helper (or directly with HSimpleVehicle)
local vehicle = QBCore.Functions.CreateVehicle(source, 'vehicle_name', spawnLocation, rotation)

HELIX - Server Side (Better approach):

-- Server/functions.lua shows the implementation
function QBCore.Functions.CreateVehicle(source, vehicle_name, coords, rotation, plate, fuel)
    local vehicle_data = QBShared.Vehicles[vehicle_name]
    if not vehicle_data then return false end

    local ped = source:K2_GetPawn()
    if not ped then return false end

    -- Get spawn location if not provided
    local location = coords or ped:GetLocation()
    local rot = rotation or ped:GetRotation()

    -- Create the vehicle
    local vehicle = HSimpleVehicle(
        location, 
        rot, 
        vehicle_data.asset_name,
        vehicle_data.collision_type,
        vehicle_data.gravity_enabled
    )

    if not vehicle then return false end

    -- Set plate and fuel
    local plate_number = plate or QBCore.Functions.GeneratePlate(vehicle)
    vehicle:SetValue('plate', plate_number, true)

    local fuel_value = fuel or 100
    vehicle:SetValue('fuel', fuel_value, true)

    return vehicle
end

Getting Closest Vehicle#

FiveM:

-- FiveM
function GetClosestVehicle()
    local ped = PlayerPedId()
    local coords = GetEntityCoords(ped)
    local vehicles = GetGamePool('CVehicle')
    local closestDistance = -1
    local closestVehicle = -1

    for _, vehicle in pairs(vehicles) do
        local vehicleCoords = GetEntityCoords(vehicle)
        local distance = #(coords - vehicleCoords)
        if closestDistance == -1 or distance < closestDistance then
            closestVehicle = vehicle
            closestDistance = distance
        end
    end

    return closestVehicle, closestDistance
end

HELIX:

-- HELIX - Built into QBCore
local closestVehicle, closestDistance = QBCore.Functions.GetClosestVehicle()

-- Or get HSimpleVehicle
local closestHVehicle, distance = QBCore.Functions.GetClosestHVehicle()

Deleting Vehicles#

FiveM:

-- FiveM
DeleteEntity(vehicle)

HELIX:

-- HELIX
vehicle:Destroy()

Weapon Management#

Creating a Weapon#

FiveM:

-- FiveM
GiveWeaponToPed(ped, GetHashKey('WEAPON_PISTOL'), 100, false, true)

HELIX:

-- HELIX - More complex, uses Weapon class
local weapon = QBCore.Functions.CreateWeapon(source, 'weapon_gaston', coords, rotation, itemInfo)

-- Then give to player
if weapon then
    local ped = source:GetControlledCharacter()
    if ped then
        ped:PickUp(weapon)
    end
end

World Objects & Entities#

Getting All Entities of Type#

FiveM:

-- FiveM
local vehicles = GetGamePool('CVehicle')
local peds = GetGamePool('CPed')
local objects = GetGamePool('CObject')

HELIX:

-- HELIX - Uses class-specific GetAll()
local vehicles = HSimpleVehicle.GetAll()
local characters = HCharacter.GetAll()
local props = Prop.GetAll()
local weapons = Weapon.GetAll()

Distance Calculations#

FiveM:

-- FiveM
local coords1 = GetEntityCoords(entity1)
local coords2 = GetEntityCoords(entity2)
local distance = #(coords1 - coords2)

HELIX:

-- HELIX - Using Vector:Distance()
local location1 = entity1:GetLocation()
local location2 = entity2:GetLocation()
local distance = location1:Distance(location2)

Coordinate System#

Important: HELIX uses centimeters, not meters!

-- FiveM (meters)
local coords = vector3(100.0, 200.0, 30.0)

-- HELIX (centimeters)
local location = Vector(10000, 20000, 3000)  -- Equivalent to above in cm

-- Converting: 1 meter = 100 cm
-- So multiply FiveM coords by 100 for HELIX

Notifications & HUD#

Showing Notifications#

FiveM:

-- FiveM Client
QBCore.Functions.Notify('Text here', 'success', 5000)

-- FiveM Server
TriggerClientEvent('QBCore:Notify', source, 'Text here', 'error')

HELIX:

-- HELIX Client (Same!)
exports['qb-core']:Notify('Text here', 'success', 5000)

-- HELIX Server (Same!)
TriggerClientEvent(source, 'QBCore:Notify', 'Text here', 'error')

-- Or using export
exports['qb-core']:Player(source, 'Notify', 'Text here', 'success', 5000)

Notification Types#

Both systems support the same types

  • 'primary' - Blue/info
  • 'success' - Green
  • 'error' - Red
  • 'warning' - Orange/yellow

Draw Text (Interaction prompts)#

FiveM:

-- FiveM (varies by resource)
exports['qb-core']:DrawText('[E] - Interact', 'left')
exports['qb-core']:HideText()

HELIX:

-- HELIX (Same!)
exports['qb-core']:DrawText('[E] - Interact', 'left')
exports['qb-core']:HideText()

Complete Example: Porting a Simple Script#

Let's port a simple "ATM" script from FiveM to HELIX.

FiveM Version#

-- FiveM - client.lua
local QBCore = exports['qb-core']:GetCoreObject()

RegisterNetEvent('atm:client:openATM', function()
    local PlayerData = QBCore.Functions.GetPlayerData()

    SendNUIMessage({
        action = 'open',
        cash = [PlayerData.money.cash](http://PlayerData.money.cash),
        bank = [PlayerData.money.bank](http://PlayerData.money.bank)
    })
    SetNuiFocus(true, true)
end)

RegisterNUICallback('deposit', function(data, cb)
    TriggerServerEvent('atm:server:deposit', data.amount)
    cb('ok')
end)

RegisterNUICallback('withdraw', function(data, cb)
    TriggerServerEvent('atm:server:withdraw', data.amount)
    cb('ok')
end)

RegisterNUICallback('close', function(_, cb)
    SetNuiFocus(false, false)
    cb('ok')
end)

-- FiveM - server.lua
local QBCore = exports['qb-core']:GetCoreObject()

RegisterNetEvent('atm:server:deposit', function(amount)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)

    amount = math.floor(tonumber(amount) or 0)
    if amount <= 0 then return end

    if Player.Functions.RemoveMoney('cash', amount, 'atm-deposit') then
        Player.Functions.AddMoney('bank', amount, 'atm-deposit')
        TriggerClientEvent('QBCore:Notify', src, 'Deposited $' .. amount, 'success')
    end
end)

RegisterNetEvent('atm:server:withdraw', function(amount)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)

    amount = math.floor(tonumber(amount) or 0)
    if amount <= 0 then return end

    if Player.Functions.RemoveMoney('bank', amount, 'atm-withdraw') then
        Player.Functions.AddMoney('cash', amount, 'atm-withdraw')
        TriggerClientEvent('QBCore:Notify', src, 'Withdrew $' .. amount, 'success')
    end
end)

HELIX Version#

-- HELIX - Client/Index.lua
local QBCore = exports('qb-core', 'GetCoreObject')()
local atmUI = nil

RegisterClientEvent('atm:client:openATM', function()
    local PlayerData = QBCore.Functions.GetPlayerData()

    if not atmUI then
        -- Create WebUI instance
        atmUI = WebUI('ATM', 'atm-resource/Client/html/index.html', 3)

        -- Wait for browser load
        atmUI.Browser.OnLoadCompleted:Add(atmUI.Browser, function()
            -- Send initial data via function call
            atmUI:CallFunction('openATM', {
                cash = [PlayerData.money.cash](http://PlayerData.money.cash),
                bank = [PlayerData.money.bank](http://PlayerData.money.bank)
            })
        end)

        -- Register event handlers
        atmUI:Subscribe('deposit', function(data)
            TriggerServerEvent('atm:server:deposit', data.amount)
        end)

        atmUI:Subscribe('withdraw', function(data)
            TriggerServerEvent('atm:server:withdraw', data.amount)
        end)

        atmUI:Subscribe('close', function()
            atmUI:Destroy()
            atmUI = nil
        end)
    else
        -- UI already exists, just update data
        atmUI:CallFunction('openATM', {
            cash = [PlayerData.money.cash](http://PlayerData.money.cash),
            bank = [PlayerData.money.bank](http://PlayerData.money.bank)
        })
    end
end)

-- Update balances when money changes
RegisterClientEvent('atm:client:updateBalances', function(cash, bank)
    if atmUI then
        atmUI:CallFunction('updateBalances', { cash = cash, bank = bank })
    end
end)

-- HELIX - Server/Index.lua
local QBCore = exports('qb-core', 'GetCoreObject')()

RegisterServerEvent('atm:server:deposit', function(source, amount)
    local Player = QBCore.Functions.GetPlayer(source)
    if not Player then return end

    amount = math.floor(tonumber(amount) or 0)
    if amount <= 0 then return end

    if Player.Functions.RemoveMoney('cash', amount, 'atm-deposit') then
        Player.Functions.AddMoney('bank', amount, 'atm-deposit')
        Player.Functions.Notify('Deposited $' .. amount, 'success')

        -- Update UI
        TriggerClientEvent('atm:client:updateBalances', source, 
            [Player.PlayerData.money.cash](http://Player.PlayerData.money.cash), 
            [Player.PlayerData.money.bank](http://Player.PlayerData.money.bank)
        )
    else
        Player.Functions.Notify('Not enough cash', 'error')
    end
end)

RegisterServerEvent('atm:server:withdraw', function(source, amount)
    local Player = QBCore.Functions.GetPlayer(source)
    if not Player then return end

    amount = math.floor(tonumber(amount) or 0)
    if amount <= 0 then return end

    if Player.Functions.RemoveMoney('bank', amount, 'atm-withdraw') then
        Player.Functions.AddMoney('cash', amount, 'atm-withdraw')
        Player.Functions.Notify('Withdrew $' .. amount, 'success')

        -- Update UI
        TriggerClientEvent('atm:client:updateBalances', source,
            [Player.PlayerData.money.cash](http://Player.PlayerData.money.cash),
            [Player.PlayerData.money.bank](http://Player.PlayerData.money.bank)
        )
    else
        Player.Functions.Notify('Not enough bank balance', 'error')
    end
end)

JavaScript Changes#

// FiveM - script.js
window.addEventListener('message', function(event) {
    if ([event.data](http://event.data).action === 'open') {
        $('#atm-container').show()
        $('#cash-amount').text('$' + [event.data.cash](http://event.data.cash))
        $('#bank-amount').text('$' + [event.data.bank](http://event.data.bank))
    }
})

$('#deposit-btn').click(function() {
    $.post('[https://atm-resource/deposit](https://atm-resource/deposit)', JSON.stringify({
        amount: parseInt($('#amount').val())
    }))
})

// HELIX - script.js
window.openATM = function(data) {
    document.getElementById('atm-container').style.display = 'block'
    document.getElementById('cash-amount').textContent = '$' + [data.cash](http://data.cash)
    document.getElementById('bank-amount').textContent = '$' + [data.bank](http://data.bank)
}

window.updateBalances = function(data) {
    document.getElementById('cash-amount').textContent = '$' + [data.cash](http://data.cash)
    document.getElementById('bank-amount').textContent = '$' + [data.bank](http://data.bank)
}

document.getElementById('deposit-btn').addEventListener('click', function() {
    const amount = parseInt(document.getElementById('amount').value)
    hEvent('deposit', { amount: amount })
})

document.getElementById('withdraw-btn').addEventListener('click', function() {
    const amount = parseInt(document.getElementById('amount').value)
    hEvent('withdraw', { amount: amount })
})

document.getElementById('close-btn').addEventListener('click', function() {
    hEvent('close')
    document.getElementById('atm-container').style.display = 'none'
})

Common Pitfalls & Best Practices#

1. Always Check Character Exists#

❌ Wrong:

✅ Correct:

-- HELIX - Will crash if no character
local player = Client.GetLocalPlayer()
local coords = player:GetControlledCharacter():GetLocation()
-- HELIX
local player = Client.GetLocalPlayer()
local character = player:GetControlledCharacter()
if not character then return end
local coords = character:GetLocation()

2. Database Result Handling#

❌ Wrong:

-- HELIX - Direct access won't work
local result = [Database.Select](http://Database.Select)('SELECT * FROM players', {})
local data = result[1]  -- This is TArray, not Lua table!

✅ Correct:

-- HELIX - Convert to Lua table
local result = [Database.Select](http://Database.Select)('SELECT * FROM players', {})
if result[1] then
    local data = result[1].Columns:ToTable()
    -- Now data is usable
end

3. JSON Encoding/Decoding#

❌ Wrong:

-- HELIX - These don't exist
json.encode(data)
json.decode(str)

✅ Correct:

-- HELIX - Use JSON (capital J)
JSON.stringify(data)
JSON.parse(str)

4. WebUI Lifecycle Management#

❌ Wrong:

-- HELIX - Creating multiple instances
function OpenUI()
    local ui = WebUI('MyUI', 'path/to/ui.html', 3)
    ui:CallFunction('update', data)  -- Called before load!
end

✅ Correct:

-- HELIX - Proper lifecycle
local myUI = nil

function OpenUI()
    if not myUI then
        myUI = WebUI('MyUI', 'path/to/ui.html', 3)

        myUI.Browser.OnLoadCompleted:Add(myUI.Browser, function()
            -- Now safe to call
            myUI:CallFunction('update', data)
        end)
    end
end

function CloseUI()
    if myUI then
        myUI:Destroy()
        myUI = nil
    end
end

5. Server Event Source Parameter#

FiveM:

-- FiveM - source is global
RegisterServerEvent('myEvent')
AddEventHandler('myEvent', function(param1)
    local src = source  -- Global variable
end)

HELIX:

-- HELIX - source is parameter
RegisterServerEvent('myEvent', function(source, param1)
    -- source is passed as first parameter
end)

6. Coordinate System Scale#

❌ Wrong:

-- HELIX - Using FiveM meters directly
local offset = Vector(5, 0, 0)  -- Only 5cm!

✅ Correct:

-- HELIX - Remember: centimeters!
local offset = Vector(500, 0, 0)  -- 5 meters = 500cm

7. Entity Iteration#

FiveM:

-- FiveM
local vehicles = GetGamePool('CVehicle')
for _, veh in pairs(vehicles) do
    DeleteEntity(veh)
end

HELIX:

-- HELIX - Use class GetAll()
local vehicles = HSimpleVehicle.GetAll()
for _, vehicle in ipairs(vehicles) do
    vehicle:Destroy()
end

8. Player State Management#

Best Practice:

-- HELIX Server - Always validate player exists
local Player = QBCore.Functions.GetPlayer(source)
if not Player then return end

-- Now safe to use Player.Functions

9. Exports Syntax#

FiveM:

-- FiveM
local result = exports['resource-name']:ExportName(args)

HELIX:

-- HELIX - Different syntax
local result = exports('resource-name', 'ExportName', args)

10. Timer/Wait Functions#

Both systems:

-- Both FiveM and HELIX support
Timer.SetTimeout(function()
    -- Code here
end, 5000)  -- 5 seconds

Timer.SetInterval(function()
    -- Repeating code
end, 1000)  -- Every second

Package.json Structure#

Both FiveM and HELIX use similar package.json for resource organization:

{
    "shared": [
        "Shared/Index.lua",
        "Shared/config.lua",
        "Shared/items.lua"
    ],
    "client": [
        "Client/Index.lua",
        "Client/functions.lua"
    ],
    "server": [
        "Server/Index.lua",
        "Server/database.lua"
    ]
}

Summary: Key Differences#

Feature FiveM HELIX
Engine GTA V (RAGE) Unreal Engine 5
Player Ped PlayerPedId() Client.GetLocalPlayer():GetControlledCharacter()
Coordinates Meters (vector3) Centimeters (Vector)
Database MySQL (async) SQLite (sync)
JSON json.encode/decode JSON.stringify/parse
UI System NUI (SendNUIMessage) WebUI (CallFunction)
Entities GetGamePool() ClassName.GetAll()
Distance #(v1 - v2) v1:Distance(v2)
Delete Entity DeleteEntity() entity:Destroy()
Exports exports['name']:Func() exports('name', 'Func', args)

Final Tips#

  1. Start Simple: Port basic scripts first (like the ATM example)
  2. Test Frequently: Test after each major change
  3. Use Built-in Functions: QBCore provides many helper functions
  4. Check Character/Player: Always validate before accessing methods
  5. Read Existing Code: Study working HELIX resources for patterns
  6. Mind the Scale: Remember 1 meter = 100cm in HELIX

Good luck porting your FiveM scripts to HELIX!!