Skip to content

Ready-to-Use Script Examples

Here are 5 beginner-friendly Lua scripts you can directly copy, modify, and use in your mods.


1. Heal & Armor on Key Press (F4)

Restores the player's health and armor to 100% whenever F4 is pressed.

-- File: main.lua
local HEAL_KEY = 0x73 -- F4 Key

Thread.Create(function()
    while true do
        if Input.IsKeyDown(HEAL_KEY) then
            local playerPed = GetPlayerChar(GetPlayerId())

            SetCharHealth(playerPed, 200)   -- Full health
            AddArmourToChar(playerPed, 100) -- Full armor

            Log.Print("Player healed!")
        end
        Thread.Wait(0)
    end
end)

2. Never Wanted (Disable Police)

Automatically resets the player's police wanted stars to 0.

-- File: main.lua
Thread.Create(function()
    while true do
        local playerId = GetPlayerId()
        local playerPed = GetPlayerChar(playerId)

        -- Clear wanted level continuously
        ClearCharLastDamageEntity(playerPed)
        AlterWantedLevel(playerId, 0)
        ApplyWantedLevelChangeNow(playerId)
        Thread.Wait(0)
    end
end)

3. Teleport 10 Meters Up (Unstuck / Jump)

Press Z (0x5A) to teleport your character 10 meters upward (useful if stuck in walls or geometry).

-- File: main.lua
local TELEPORT_KEY = 0x5A -- Z Key

Thread.Create(function()
    while true do
        if Input.IsKeyDown(TELEPORT_KEY) then
            local playerPed = GetPlayerChar(GetPlayerId())

            -- Get current position: out parameters come back as plain Lua values
            local x, y, z = GetCharCoordinates(playerPed)

            -- Teleport 10m higher
            SetCharCoordinates(playerPed, x, y, z + 10.0)
            Log.Print("Teleported up!")
        end
        Thread.Wait(0)
    end
end)

4. Simple Car Speedometer & Nitro Boost (Shift)

Draws an on-screen HUD speedometer in km/h and boosts car speed with Shift:

-- File: main.lua
local NITRO_KEY = 0x10 -- Shift Key

Thread.Create(function()
    while true do
        local playerPed = GetPlayerChar(GetPlayerId())

        -- Check if inside a vehicle
        if IsCharInAnyCar(playerPed) then
            local veh = GetCarCharIsUsing(playerPed)
            local speedMps = GetCarSpeed(veh)
            local speedKmh = speedMps * 3.6

            -- Draw HUD box and speed text
            Draw.Rect(20, 700, 200, 50, 0xAA000000)
            Draw.Text(30, 715, string.format("SPEED: %3.0f KM/H", speedKmh), 0xFFFFFFFF)

            -- Press Shift for Nitro
            if Input.IsKeyDown(NITRO_KEY) then
                SetCarForwardSpeed(veh, speedMps * 1.3)
                Draw.Text(30, 735, ">> NITRO <<", 0xFFFF0000)
            end
        end
        Thread.Wait(0)
    end
end)

5. Invincibility Toggle (Godmode on F3)

Toggles God Mode on and off when pressing F3:

-- File: main.lua
local TOGGLE_KEY = 0x72 -- F3 Key
local godmode = false
local wasPressed = false

Thread.Create(function()
    while true do
        local isPressed = Input.IsKeyDown(TOGGLE_KEY)

        -- Trigger only on key down (not hold)
        if isPressed and not wasPressed then
            godmode = not godmode
            local playerPed = GetPlayerChar(GetPlayerId())

            SetCharInvincible(playerPed, godmode)
            SetCharProofs(playerPed, godmode, godmode, godmode, godmode, godmode)

            if godmode then
                Console.MsgBox("God Mode: ENABLED")
            else
                Console.MsgBox("God Mode: DISABLED")
            end
        end
        wasPressed = isPressed
        Thread.Wait(0)
    end
end)

6. Waiting Between Iterations

Every example above runs on every frame, which is what Thread.Wait(0) means. When a mod does not need that, wait longer. The thread sleeps, and costs nothing while it does.

-- File: main.lua
-- Regenerate 5 health per second, up to 200
Thread.Create(function()
    while true do
        local ped = GetPlayerChar(GetPlayerId())
        local hp = GetCharHealth(ped)

        if hp > 0 and hp < 200 then
            SetCharHealth(ped, math.min(200, hp + 5))
        end

        Thread.Wait(1000) -- one second, without freezing the game
    end
end)

Why not count frames yourself?

You could keep an elapsed = elapsed + dt counter and act when it crosses a second, but every mod then reimplements the same fragile bookkeeping. Thread.Wait is the pattern ScriptHookDotNet mods are written against, so ported code keeps its shape.


7. An On-Screen Menu (Input.SetMenuOpen)

Opens a small menu on F5, navigated with the arrow keys. Telling the loader the menu is open is what stops the character from moving and shooting while you navigate.

-- File: main.lua
local MENU_KEY = 0x74 -- F5
local items = { "Full health", "Full armour", "Repair vehicle" }

local open, index = false, 1
local wasDown = {}

-- Edge detection: Input.IsKeyDown is level-triggered, so without this every
-- frame the key is held counts as a new press.
local function pressed(vk)
    local down = Input.IsKeyDown(vk)
    local hit = down and not wasDown[vk]
    wasDown[vk] = down
    return hit
end

Thread.Create(function()
    while true do
        if pressed(MENU_KEY) then
            open = not open
            Input.SetMenuOpen(open) -- suppress arrows/Enter for the GAME, not for us
        end

        if not open then return end

        if pressed(0x26) then index = math.max(1, index - 1) end          -- Up
        if pressed(0x28) then index = math.min(#items, index + 1) end     -- Down

        if pressed(0x0D) then -- Enter
            local ped = GetPlayerChar(GetPlayerId())
            if index == 1 then SetCharHealth(ped, 200)
            elseif index == 2 then AddArmourToChar(ped, 100)
            elseif index == 3 and IsCharInAnyCar(ped) then FixCar(GetCarCharIsUsing(ped)) end
        end

        local bg        = Color.RGBA(0, 0, 0, 75)
        local white     = Color.RGB(255, 255, 255)
        local highlight = Color.RGB(255, 200, 60)

        Draw.Rect(40, 200, 260, 30 + #items * 24, bg)
        Draw.Text(52, 208, "MY MENU", highlight)

        for i, label in ipairs(items) do
            local prefix = (i == index) and "> " or "  "
            Draw.Text(52, 232 + (i - 1) * 24, prefix .. label,
                      (i == index) and highlight or white)
        end
        Thread.Wait(0)
    end
end)
function onUnload()
    Input.SetMenuOpen(false) -- never leave the game's inputs suppressed
end

Always release the menu flag

If the mod stops with Input.SetMenuOpen(true) still set, the player is left unable to move. Clearing it in onUnload() is not optional.


8. Gamepad Support (Gamepad.IsButtonDown)

Same idea as the keyboard, with raw XInput bitmasks on pad 0.

-- File: main.lua
local PAD_A     = 0x1000
local PAD_B     = 0x2000
local PAD_LEFT  = 0x0004
local PAD_RIGHT = 0x0008

Thread.Create(function()
    while true do
        if Gamepad.IsButtonDown(PAD_A) then
            SetCharHealth(GetPlayerChar(GetPlayerId()), 200)
        end
        Thread.Wait(0)
    end
end)
Button Mask Button Mask
D-Pad Up 0x0001 A 0x1000
D-Pad Down 0x0002 B 0x2000
D-Pad Left 0x0004 X 0x4000
D-Pad Right 0x0008 Y 0x8000
Start 0x0010 Left shoulder 0x0100
Back 0x0020 Right shoulder 0x0200
Left stick 0x0040 Right stick 0x0080

The game must have focus

Like Input.IsKeyDown, gamepad reads return false while the GTA IV window is not in the foreground.


9. Saving Settings Next to Your Mod (MOD_DIR)

MOD_DIR is the absolute path of your mod's folder. Build every file path from it. The process working directory belongs to the game, not to your mod.

-- File: main.lua
local SETTINGS = MOD_DIR and (MOD_DIR .. "\\settings.txt")

local godmode = false

local function load()
    if not SETTINGS then return end -- running from the editor, no folder
    local f = io.open(SETTINGS, "r")
    if not f then return end
    godmode = (f:read("l") == "1")
    f:close()
end

local function save()
    if not SETTINGS then return end
    local f = io.open(SETTINGS, "w")
    if not f then return end
    f:write(godmode and "1" or "0")
    f:close()
end

load()
Log.Print("god mode restored: " .. tostring(godmode))

Thread.Create(function()
    while true do
        if godmode then
            SetCharInvincible(GetPlayerChar(GetPlayerId()), true)
        end
        Thread.Wait(0)
    end
end)
function onUnload()
    save()
end

MOD_DIR is nil from the editor

A buffer executed straight from the F6 editor has no folder of its own, so MOD_DIR is nil. Guard for it, as above, or the mod will error the first time you test a snippet.


10. Calling a Native the Typed Global Cannot Reach

Around 237 natives have no documented signature, and a handful more are registered with the wrong one. Native.Call pushes arguments as they are, without consulting the signature. It is the escape hatch for when the PascalCase global gets it wrong.

-- File: main.lua
-- The name passed to Native.* is the canonical SNAKE_CASE one, always.

-- One output: GET_GAME_TIMER writes its result through a pointer rather than
-- returning it, so it needs CallOut, not Call.
local _, buf = Native.CallOut("GET_GAME_TIMER", 1)
if buf then
    Log.Print("game timer: " .. string.unpack("<i4", buf))
end

-- Several outputs at once: 3 contiguous buffers amount to a Vector3.
local ped = GetPlayerChar(GetPlayerId())
local _, bx, by, bz = Native.CallOut("GET_PED_BONE_POSITION", 3, ped, 0, 0.0, 0.0, 0.0)
if bx then
    Log.Print(string.format("bone at %.2f %.2f %.2f",
        string.unpack("<f", bx), string.unpack("<f", by), string.unpack("<f", bz)))
end

Call is not CallOut

Native.Call hands back the native's return buffer. A native that has no return value and writes through an output pointer, and GTA IV is full of them, gives you nothing useful that way. Check the native in the database first. An out parameter means you need Native.CallOut.

Browse every native, typed or not, in the Native Database.