Stand Lua API Documentation

Table of Contents

§ Introduction

Stand treats Lua Scripts like ASI mods, such that they are a sandboxed environment that will be removed from memory once finished.

This means that for idle background scripts that only register event handlers or menu commands and have no tasks to perform on a per-tick basis, a minimal loop is needed after all events and commands are registered:

while true do
    util.yield()
end

§ Types

Vector3

A table with x, y & z fields of type number.

Colour

A table with r, g, b & a fields of type number with values between 0.0 and 1.0.

§ Global Variables

SCRIPT_NAME

A string containing the name of your script without .lua at the end. Do not change this variable.

§ Native Invoker

Stand allows you to call most natives via its Lua API by providing a native invoker. However, native_invoker is not recommended for direct use and therefore not documented. Instead, you should use lib/natives.lua, distributed and maintained as a part of the example scripts.

§ Menu Functions

int menu.my_root()

Returns the command id of the list that your script gets when it is started.

int menu.player_root(int player_id)

Returns the command id of the list that the given player owns.

int menu.list(int list_id, string menu_name, table<any, string> command_names = {}, string help_text = "")

list_id should be the command id of the parent list, which you may get from the return value of menu.my_root, menu.player_root, or menu.list.

int menu.action(int list_id, string menu_name, table<any, string> command_names, string help_text, function on_click, ?function on_command = nil, ?string syntax = nil)

list_id should be the command id of the parent list, which you may get from the return value of menu.my_root, menu.player_root, or menu.list.

Your on_click function will be called with the click type as parameter which can be any of:

And could match any or neither of these bitflags:

Your on_command function will be called with the provided arguments as a string. If on_command is not provided, commands will be redirected to on_click.

int menu.toggle(int list_id, string menu_name, table<any, string> command_names, string help_text, function on_change)

list_id should be the command id of the parent list, which you may get from the return value of menu.my_root, menu.player_root, or menu.list.

Your on_change function will be called with a boolean parameter to indicate whether the toggle is on or off now.

int menu.divider(int list_id, string menu_name)

list_id should be the command id of the parent list, which you may get from the return value of menu.my_root, menu.player_root, or menu.list.

void menu.delete(int command_id)

void menu.show_command_box(string prefill)

void menu.show_command_box_click_based(int click_type, string prefill)

void menu.trigger_commands(string input)

§ Players Functions

int players.on_join(function callback)

Registers a function to be called when a player joins the session. Your callback will be called with the player id as argument.

int players.on_leave(function callback)

Registers a function to be called when a player leaves the session. Your callback will be called with the player id as argument.

bool players.exists(int player_id)

Checks if a player with the given id is in session.

int players.user()

Alternative to the PLAYER.PLAYER_ID native.

table<int, int> players.list(bool include_user = true, bool include_friends = true, bool include_strangers = true)

Returns an index-based table with all matching player ids.

int players.get_host()

int players.get_script_host()

string players.get_host_token(int player_id)

§ Chat Functions

int chat.on_message(function callback)

Registers a function to be called when a chat message is sent by any player in session:

chat.on_message(function(sender_player_id, sender_player_name, message, is_team_chat)
    -- Do stuff...
end)

void chat.send_message(string message, bool in_team_chat, bool add_to_local_history, bool networked)

As you might be aware, messages have a limit of 140 UTF-16 characters. However, that is only true for the local history, as you can use up to 255 UTF-8 characters over the network.

What you do with information is up to you, but if you just want to send messages and not have any truncation, make sure not to exceed either limit.

Note that Stand expects UTF-8-encoded strings from Lua and converts to UTF-16 as needed.

int chat.get_state()

Possible return values:

bool chat.is_open()

void chat.open()

void chat.close()

string chat.get_draft()

Returns the message that the user is currently drafting or an empty string if not applicable.

void chat.ensure_open_with_empty_draft(bool team_chat)

void chat.add_to_draft(string appendix)

void chat.remove_from_draft(int characters)

§ DirectX Functions

int directx.create_texture(string path)

An absolute path is recommended, e.g. by using filesystem.scripts_dir().

void directx.draw_texture(int id, int index, int level, int time, number sizeX, number sizeY, number centerX, number centerY, number posX, number posY, number rotation, number screenHeightScaleFactor, Colour colour)

void directx.draw_texture(int id, int index, int level, int time, number sizeX, number sizeY, number centerX, number centerY, number posX, number posY, number rotation, number screenHeightScaleFactor, number r, number g, number b, number a)

void directx.draw_text(number x, number y, string text, int alignment, number scale, Colour colour, bool force_in_bounds = false)

alignment can be any of:

void directx.draw_rect(number x, number y, number width, number height, Colour colour)

void directx.draw_line(number x1, number y1, number x2, number y2, Colour colour)

void directx.draw_line(number x1, number y1, number x2, number y2, Colour colour1, Colour colour2)

§ Util Functions

void util.yield(?int wake_in_ms = nil)

Pauses the execution of the calling thread until the next tick or in wake_in_ms milliseconds.

If you're gonna create a "neverending" loop, don't forget to yield:

while true do
    -- Code that runs every tick...
    util.yield()
end

void util.create_thread(function thread_func, ...)

Creates the kind of thread that your script gets when it is created, or one of your callbacks is invoked, which is just another coroutine that gets resumed every tick and is expected to yield or return.

void util.create_tick_handler(function func)

Registers the parameter-function to be called every tick until it returns false.

int util.toast(string message, int bitflags = TOAST_DEFAULT)

Possible bitflags:

If the TOAST_ABOVE_MAP bitflag is set, the return value will be the notification id returned by HUD.END_TEXT_COMMAND_THEFEED_POST_TICKER, else it will be 0.

Note that the chat flags are mutually exclusive.

void util.log(string message)

Alias for

util.toast(message, TOAST_LOGGER)

int util.create_ped(int type, int hash, Vector3 pos, number heading)

Enhanced version of the PED.CREATE_PED native which performs despawn bypass magic.

int util.create_vehicle(int hash, Vector3 pos, number heading)

Enhanced version of the VEHICLE.CREATE_VEHICLE native which performs despawn bypass magic and calls DECORATOR.DECOR_SET_INT(veh, "MPBitset", 0) in Online to prevent "You don't have access to this personal vehicle."

Note that the vehicle model needs to be loaded first:

local hash = util.joaat("oppressor")
if STREAMING.IS_MODEL_A_VEHICLE(hash) then
    STREAMING.REQUEST_MODEL(hash)
    while not STREAMING.HAS_MODEL_LOADED(hash) do
        util.yield()
    end
    local veh = util.create_vehicle(hash, ENTITY.GET_ENTITY_COORDS(PLAYER.PLAYER_PED_ID(), true), CAM.GET_GAMEPLAY_CAM_ROT(0).z)
    STREAMING.SET_MODEL_AS_NO_LONGER_NEEDED(hash)
    -- Do stuff with veh ...
end

int util.get_vehicle()

Returns the player's current vehicle, last driven vehicle, or 0.

Shorthand for

local veh = PED.GET_VEHICLE_PED_IS_IN(PLAYER.PLAYER_PED_ID(), false)
if not ENTITY.IS_ENTITY_A_VEHICLE(veh) then
    veh = PED.GET_VEHICLE_PED_IS_IN(PLAYER.PLAYER_PED_ID(), true)
end
if not ENTITY.IS_ENTITY_A_VEHICLE(veh) then
    veh = 0
end

void util.show_corner_help(string message)

Shorthand for

util.BEGIN_TEXT_COMMAND_IS_THIS_HELP_MESSAGE_BEING_DISPLAYED(message)
if not HUD.END_TEXT_COMMAND_IS_THIS_HELP_MESSAGE_BEING_DISPLAYED(0) then
    util.BEGIN_TEXT_COMMAND_DISPLAY_HELP(message)
    HUD.END_TEXT_COMMAND_DISPLAY_HELP(0, false, true, -1)
end

void util.replace_corner_help(string message, string replacement_message)

Shorthand for

util.BEGIN_TEXT_COMMAND_IS_THIS_HELP_MESSAGE_BEING_DISPLAYED(message)
if HUD.END_TEXT_COMMAND_IS_THIS_HELP_MESSAGE_BEING_DISPLAYED(0) then
    util.BEGIN_TEXT_COMMAND_DISPLAY_HELP(replacement_message)
    HUD.END_TEXT_COMMAND_DISPLAY_HELP(0, false, true, -1)
end

void util.set_local_player_wanted_level(int wanted_level, bool no_drop = false)

Replacement for

if no_drop then
    PLAYER.SET_PLAYER_WANTED_LEVEL_NO_DROP(PLAYER.PLAYER_ID(), wanted_level, false)
else
    PLAYER.SET_PLAYER_WANTED_LEVEL(PLAYER.PLAYER_ID(), wanted_level, false)
end
PLAYER.SET_PLAYER_WANTED_LEVEL_NOW(PLAYER.PLAYER_ID(), false)

using pointers to avoid potentially tripping anti-cheat.

void util.draw_debug_text(string text)

Draws the given text at the top left of the screen using the menu colour for the current frame.

int util.joaat(string text)

JOAAT stands for Jenkins One At A Time which is the name of the hashing algorithm used pretty much everywhere in GTA.

void util.BEGIN_TEXT_COMMAND_DISPLAY_TEXT(string message)

Replacement for

HUD.BEGIN_TEXT_COMMAND_DISPLAY_TEXT("STRING")
HUD.ADD_TEXT_COMPONENT_SUBSTRING_PLAYER_NAME(message)

which increases your message's character limit.

void util._BEGIN_TEXT_COMMAND_LINE_COUNT(string message)

Replacement for

HUD._BEGIN_TEXT_COMMAND_LINE_COUNT("STRING")
HUD.ADD_TEXT_COMPONENT_SUBSTRING_PLAYER_NAME(message)

which increases your message's character limit.

void util.BEGIN_TEXT_COMMAND_IS_THIS_HELP_MESSAGE_BEING_DISPLAYED(string message)

Replacement for

HUD.BEGIN_TEXT_COMMAND_IS_THIS_HELP_MESSAGE_BEING_DISPLAYED("STRING")
HUD.ADD_TEXT_COMPONENT_SUBSTRING_PLAYER_NAME(message)

which increases your message's character limit.

void util.BEGIN_TEXT_COMMAND_DISPLAY_HELP(string message)

Replacement for

HUD.BEGIN_TEXT_COMMAND_DISPLAY_HELP("STRING")
HUD.ADD_TEXT_COMPONENT_SUBSTRING_PLAYER_NAME(message)

which increases your message's character limit.

void util._BEGIN_TEXT_COMMAND_GET_WIDTH(string message)

Replacement for

HUD._BEGIN_TEXT_COMMAND_GET_WIDTH("STRING")
HUD.ADD_TEXT_COMPONENT_SUBSTRING_PLAYER_NAME(message)

which increases your message's character limit.

void util.BEGIN_TEXT_COMMAND_THEFEED_POST(string message)

Replacement for

HUD.BEGIN_TEXT_COMMAND_THEFEED_POST("STRING")
HUD.ADD_TEXT_COMPONENT_SUBSTRING_PLAYER_NAME(message)

which increases your message's character limit.

int util.get_rp_required_for_rank(int rank)

int util.get_session_players_bitflag()

void util.trigger_script_event(int session_player_bitflags, table<any, int> data)

session_player_bitflags has a bit set to 1 for every player that should receive the script event; you can use util.get_session_players_bitflag() if you intend for everyone to receive the script event or use 1 << player_id to target individual players.

int util.current_time_millis()

int util.current_unix_time_millis()

Returns how many miliseconds have passed since the UNIX epoch (00:00:00 UTC on 1 January 1970).

int util.get_entity_address(int entity)

Returns the address of the entity with the given script handle.

int util.remove_handler(int handler_id)

int util.stop_thread()

int util.stop_script()

bool util.is_session_started()

bool util.set_weather(int weather_id)

Changes the weather for yourself and everyone else in your session.

Possible weather IDs:

§ Filesystem Functions

string filesystem.appdata_dir()

Possible return value: C:\Users\John\AppData\Roaming\

string filesystem.stand_dir()

Possible return value: C:\Users\John\AppData\Roaming\Stand\

string filesystem.scripts_dir()

Possible return value: C:\Users\John\AppData\Roaming\Stand\Lua Scripts\

bool filesystem.exists(string path)

bool filesystem.is_regular_file(string path)

bool filesystem.is_dir(string path)

void filesystem.mkdir(string path)

void filesystem.mkdirs(string path)

table<int, string> filesystem.list_files(string path)

Returns an index-based table with all files in the given directory.

for i, path in ipairs(filesystem.list_files(filesystem.scripts_dir())) do
    util.log(path)
end

Note that directories in the resulting table don't end on a \.

§ Memory Functions

int memory.script_global(int global)

Returns the address of the given script global.

int memory.alloc(int size = 24)

The default size is 24 so it can fit a Vector3.

void memory.free(int addr)

Frees a memory section allocated by memory.alloc. This is automatically done for all memory your script has allocated but not freed once it finishes.

int memory.scan(string pattern)

Scans the game's memory for the given IDA-style pattern. This is an expensive call so ideally you'd only ever scan for a pattern once and then use the resulting address until your script finishes.

int memory.rip(int addr)

Follows an offset from the instruction pointer ("RIP") at the given address.

So, whereas in C++ you might do something like this:

memory::scan("4C 8D 05 ? ? ? ? 48 8D 15 ? ? ? ? 48 8B C8 E8 ? ? ? ? 48 8D 15 ? ? ? ? 48 8D 4C 24 20 E8").add(3).rip().as<const char*>();

You'd do this in Lua (with a check for null-pointer because we're smart):

local addr = memory.scan("4C 8D 05 ? ? ? ? 48 8D 15 ? ? ? ? 48 8B C8 E8 ? ? ? ? 48 8D 15 ? ? ? ? 48 8D 4C 24 20 E8")
if addr == 0 then
    util.toast("pattern scan failed")
else
    util.toast(memory.read_string(memory.rip(addr + 3)))
end

int memory.read_byte(int addr)

Reads an 8-bit integer at the given address.

int memory.read_int(int addr)

Reads a 32-bit integer at the given address.

int memory.read_long(int addr)

Reads a 64-bit integer at the given address.

number memory.read_float(int addr)

string memory.read_string(int addr)

Vector3 memory.read_vector3(int addr)

void memory.write_byte(int addr, int value)

Writes an 8-bit integer to the given address.

void memory.write_int(int addr, int value)

Writes a 32-bit integer to the given address.

void memory.write_long(int addr, int value)

Writes a 64-bit integer to the given address.

void memory.write_float(int addr, number value)

void memory.write_string(int addr, string value)

void memory.write_vector3(int addr, Vector3 value)