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

§ 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.

§ 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.

Functions that take this as a parameter also support 4 floats instead of it, which is more performant.

§ Global Variables

SCRIPT_NAME

A string containing the name of your script (this excludes .lua).

SCRIPT_FILENAME

A string containing the name of your script file.

SCRIPT_RELPATH

A string containing the path to your script file from the Lua Scripts folder.

SCRIPT_MANUAL_START

A bool indicating if your script was started in direct response to a user action.

§ 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 = "", ?function on_click = nil, ?function on_back = 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.

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, bool default_on = false)

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.slider(int list_id, string menu_name, table<any, string> command_names, string help_text, int min_value, int max_value, int default_value, int step_size, 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 two ints: value and prev_value.

int menu.click_slider(int list_id, string menu_name, table<any, string> command_names, string help_text, int min_value, int max_value, int default_value, int step_size, 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 the int value.

int menu.text_input(int list_id, string menu_name, table<any, string> command_names, string help_text, function on_change, string default_value = "")

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 the string and click type.

int menu.colour(int list_id, string menu_name, table<any, string> command_names, string help_text, Colour default, bool transparency, 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 Colour as parameter.

int menu.colour(int list_id, string menu_name, table<any, string> command_names, string help_text, number default_r, number default_g, number default_b, number default_a, bool transparency, 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 Colour as parameter.

int menu.rainbow(int colour_command_id)

Creates a rainbow slider for the given colour command. This should be called right after creating the colour command.

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.

int menu.hyperlink(int list_id, string menu_name, string link, 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.

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)

bool menu.is_open()

int, int menu.get_position()

Returns the menu grid origin x & y.

int, int, int, int menu.get_main_view_position_and_size()

Return x, y, width, & height for the current main view (active list, warning, etc.).

string menu.get_active_list_cursor_text(bool even_when_disabled = false, bool even_when_inappropriate = false)

string menu.get_language()

Returns the 2-letter, lowercase language code of the user's language for Stand.

int menu.on_tick_in_viewport(int command_id, function callback)

int menu.on_focus(int command_id, function callback)

int menu.on_blur(int command_id, function callback)

void menu.remove_handler(int command_id, function handler_id)

int menu.set_menu_name(int command_id, string menu_name)

int menu.set_command_names(int command_id, table<any, string> command_names)

int menu.set_help_text(int command_id, string help_text)

int menu.get_activation_key_hash()

Returns a 32-bit integer derived from the user's activation key. 0 if no activation key.

int menu.get_edition()

Returns a value between 0 and 3 depending on the user's edition.

§ 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()

int players.get_rockstar_id(int player_id)

This value might be spoofed by hard RID spoofing.

int players.get_rockstar_id_2(int player_id)

This value might be spoofed by soft RID spoofing.

int players.get_rank(int player_id)

int players.get_rp(int player_id)

int players.get_money(int player_id)

int players.get_wallet(int player_id)

int players.get_bank(int player_id)

number players.get_kd(int player_id)

int players.get_kills(int player_id)

int players.get_deaths(int player_id)

bool players.is_otr(int player_id)

string players.get_host_token(int player_id)

bool players.is_marked_as_modder(int player_id)

int players.get_boss(int player_id)

Returns -1 if not applicable.

int players.get_org_colour(int player_id)

Returns -1 if not applicable.

§ Entities Functions

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

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

int entities.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 = entities.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 entities.get_user_vehicle_as_handle()

Returns the user'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

int entities.get_user_vehicle_as_pointer()

int entities.handle_to_pointer(int handle)

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

bool entities.has_handle(int addr)

Returns if the entity with the given address has been given a script handle, which will be the case for any nearby entities, mission entities, and when mods have requested it.

int entities.pointer_to_handle(int addr)

Returns a script handle for th entity with the given address. This will force one to be allocated. Note that script handles are a limited resource.

table<int, int> entities.get_all_vehicles_as_handles()

This will force a script handle to be allocated for all vehicles. Note that script handles are a limited resource.

table<int, int> entities.get_all_vehicles_as_pointers()

table<int, int> entities.get_all_peds_as_handles()

This will force a script handle to be allocated for all peds. Note that script handles are a limited resource.

table<int, int> entities.get_all_peds_as_pointers()

table<int, int> entities.get_all_objects_as_handles()

This will force a script handle to be allocated for all objects. Note that script handles are a limited resource.

table<int, int> entities.get_all_objects_as_pointers()

table<int, int> entities.get_all_pickups_as_handles()

This will force a script handle to be allocated for all pickups. Note that script handles are a limited resource.

table<int, int> entities.get_all_pickups_as_pointers()

number entities.get_boost_charge_from_pointer(int addr)

Only applicable to vehicles. Returns a value between 0.0 and 1.25.

void entities.delete(int handle)

§ 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

Any X and Y value must be between 0.0 to 1.0.

The draw functions are in the HUD coordinate space, which is superimposed 1920x1080. You can also append _client to any draw function, e.g. draw_line_client to draw in client coordinate space, which is based on the game window size.

int directx.create_texture(string path)

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

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

void directx.draw_texture(int id, number sizeX, number sizeY, number centerX, number centerY, number posX, number posY, number rotation, 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)

void directx.draw_triangle(number x1, number y1, number x2, number y2, number x3, number y3, Colour colour)

number, number directx.get_client_size()

number, number directx.get_text_size(string text, number scale = 1.0)

Returns width and height.

§ 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.on_stop(function func)

Called in the final tick of your script. Yielding or creating threads in that context is undefined behaviour.

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.

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

Possible bitflags:

Note that the chat flags are mutually exclusive.

void util.log(string message)

Alias for

util.toast(message, TOAST_LOGGER)

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.remove_handler(int handler_id)

int util.stop_thread()

int util.stop_script()

bool util.is_session_transition_active()

bool, number util.get_ground_z(number x, number y, number z_hint = 1000.0)

The most precise way to get the ground Z coordinate which respects water.

The ground Z will be below the z_hint.

If the bool return value is true, the number is the ground Z. If not, you should try again next tick. You may want to count the calls you made and abort after a certain amount of calls with the bool being false.

bool util.spoof_script(string|int script, function func)

If the provided script is not running, your function is not called and this returns false.

§ 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 \.

§ Async HTTP Functions

void async_http.init(string host, string path, ?function success_func = nil, ?function fail_func = nil)

This will make a GET request unless you use async_http.set_post before calling async_http.dispatch.

On success, your success_func will be called with the response body as a string, if your script is still alive when the request finishes; the lifetimes are independent.

void async_http.dispatch()

Finish building the async http request and carry it out in separate OS thread.

void async_http.set_post(string content_type, string payload)

Changes the request method, adds Content-Type and Content-Length headers, and sets the payload.

Examples of content_type:

void async_http.add_header(string key, string value)

§ 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)