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.

§ Global Variables

SCRIPT_NAME

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

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

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 menu.get_x()

int menu.get_y()

string menu.get_active_list_cursor_text()

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)

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

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

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.

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.is_session_transition_active()

void util.set_weather(int weather_id)

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

Possible weather IDs:

void util.set_time(int hour, int minute = 0, int second = 0)

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.

table<int, int> util.get_all_vehicles()

table<int, int> util.get_all_peds()

table<int, int> util.get_all_objects()

table<int, int> util.get_all_pickups()

void util.delete_entity(int entity)

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

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