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.