SAPP-Specific Methods
Map Vote / Map Cycle Systems
Do not use mapvote_add
in init.txt; add entries directly to mapvotes.txt.
Format: map:variant:name:min:max
Property | Description |
---|---|
map | Name of the map |
variant | Name of custom game mode. Use quotes for spaces, e.g., "My Custom Mode" . |
name | User-defined message shown next to map vote option. |
min | Minimum players |
max | Maximum players |
Add max_idle 1
to init.txt to avoid 60-second hang on server boot.
map map_name mode
-- Example: map ratrace MyCustomCTF
SAPP Built-in rand() Function
local t = {'a', 'b', 'c'}
local i = rand(1, #t + 1)
print(t[i])
-- Ensures i ranges from 1 to #t
write_vector3d() for Vehicles
- Add small Z velocity (e.g., -0.025)
- Unset no-collision & ignore-physics bits
write_bit(object + 0x10, 0, 0)
write_bit(object + 0x10, 5, 0)
spawn_object() with Custom Rotation
Rotation values must be in radians.
Custom Spawn Systems
local r = pR -- rotation in radians
local z_off = 0.3 -- prevent falling through the map
local x, y, z = px, py, pz + z_off
write_vector3d(dyn + 0x5C, x, y, z)
write_vector3d(dyn + 0x74, math.cos(r), math.sin(r), 0)
Assigning More Than 2 Weapons
Delay assignment by ≥250ms to prevent extra weapons from dropping. See example script.
$pn Server Variable
-- On player leave, prints total players minus 1
function OnLeave()
local n = tonumber(get_var(0, "$pn")) - 1
print('Total Players: ' .. n)
end
Get Player Inventory
-- Returns table of weapons with ammo, clip, and stats
-- Usage: local inv = get_inventory(dyn_player)
local function get_inventory(dyn_player)
local inventory = {}
for i = 0, 3 do
local weapon = read_dword(dyn_player + 0x2F8 + i * 4)
local object = get_object_memory(weapon)
if object ~= 0 then
inventory[i + 1] = {
id = read_dword(object),
ammo = read_word(object + 0x2B6),
clip = read_word(object + 0x2B8),
ammo2 = read_word(object + 0x2C6),
clip2 = read_word(object + 0x2C8),
age = read_float(object + 0x240),
frags = read_byte(dyn_player + 0x31E),
plasmas = read_byte(dyn_player + 0x31F)
}
end
end
return inventory
end
Retrieve Weapon Slot
-- Returns currently selected weapon slot
-- Usage: local slot = get_weapon_slot(dyn_player)
local function get_weapon_slot(dyn_player)
return read_byte(dyn_player + 0x2F2)
end
Get Tag Reference Address
-- Usage: local tag_addr = get_tag('weap', 'sniper')
local function get_tag(class, name)
local tag = lookup_tag(class, name)
return tag and read_dword(tag + 0xC) or nil
end
Get Current Weapon Tag Identifier
-- Usage: local weapon_id = get_current_weapon(dyn)
local function get_current_weapon(dyn)
local weapon_id = read_dword(dyn + 0x118)
local weapon_obj = get_object_memory(weapon_id)
if weapon_obj == nil or weapon_obj == 0 then return nil end
return read_dword(weapon_obj)
end
Check if Player is in Vehicle
-- Returns true if player is inside a vehicle
-- Usage: if is_in_vehicle(dyn) then ...
local function is_in_vehicle(dyn)
return read_dword(dyn + 0x11C) ~= 0xFFFFFFFF
end
Clear Player's RCON Console
-- Usage: clear_rcon_console(1)
local function clear_rcon_console(player_index)
for _ = 1, 25 do rprint(player_index, " ") end
end
Broadcast Message Excluding One Player
-- Usage: send_message_exclude("Hello!", 2)
local function send_message_exclude(message, exclude_player)
for i = 1, 16 do
if player_present(i) and i ~= exclude_player then
say(i, message)
end
end
end
Check Player Invisibility State
-- Returns true if player is invisible
-- Usage: if is_player_invisible(1) then ...
local function is_player_invisible(player_index)
local dyn = get_dynamic_player(player_index)
return dyn ~= 0 and read_float(dyn + 0x37C) == 1
end
Get Player World Coordinates
-- Usage: local x, y, z = get_player_position(dyn_player)
local function get_player_position(dyn_player)
local crouch = read_float(dyn_player + 0x50C)
local vehicle_id = read_dword(dyn_player + 0x11C)
local vehicle_obj = get_object_memory(vehicle_id)
local x, y, z
if vehicle_id == 0xFFFFFFFF then
x, y, z = read_vector3d(dyn_player + 0x5C)
elseif vehicle_obj ~= 0 then
x, y, z = read_vector3d(vehicle_obj + 0x5C)
end
local z_offset = (crouch == 0) and 0.65 or 0.35 * crouch
return x, y, z + z_offset
end
Get Objective (oddball or flag)
-- Usage: if has_objective(dyn_player, "oddball") then ...
local function has_objective(dyn_player, objective_type)
local base_tag_table = 0x40440000
local tag_entry_size = 0x20
local tag_data_offset = 0x14
local bit_check_offset = 0x308
local bit_index = 3
objective_type = objective_type or "any"
local weapon_id = read_dword(dyn_player + 0x118)
local weapon_obj = get_object_memory(weapon_id)
if weapon_obj == nil or weapon_obj == 0 then return false end
local tag_address = read_word(weapon_obj)
local tag_data_base = read_dword(base_tag_table)
local tag_data = read_dword(tag_data_base + tag_address * tag_entry_size + tag_data_offset)
if read_bit(tag_data + bit_check_offset, bit_index) ~= 1 then return false end
local obj_byte = read_byte(tag_data + 2)
local is_oddball = (obj_byte == 4)
local is_flag = (obj_byte == 0)
if objective_type == "oddball" then return is_oddball
elseif objective_type == "flag" then return is_flag
else return is_oddball or is_flag end
end
Get Flag Object Meta & Tag Name
--[[
get_flag_data() → flag_meta_id, flag_tag_name
Retrieves the meta ID and tag name of the flag (objective) in the map.
Returns:
flag_meta_id (number) → Memory reference ID of the flag tag.
flag_tag_name (string) → Name of the flag tag.
Notes:
- Iterates through all tags in the base tag table.
- Checks for weapon class ("weap") with the specific bit set for objectives.
- Only returns the first tag where the objective type byte equals 0 (flag).
- Returns nil, nil if no valid flag is found.
Example Usage:
local meta_id, tag_name = get_flag_data()
if meta_id then
print("Flag Meta ID:", meta_id)
print("Flag Name:", tag_name)
else
print("No flag found in this map.")
end
]]
local flag_meta_id, flag_tag_name
local function get_flag_data()
local tag_array = read_dword(base_tag_table)
local tag_count = read_dword(base_tag_table + 0xC)
for i = 0, tag_count - 1 do
local tag = tag_array + tag_entry_size * i
local tag_class = read_dword(tag)
if tag_class == 0x77656170 then -- "weap"
local tag_data = read_dword(tag + tag_data_offset)
if read_bit(tag_data + bit_check_offset, bit_index) == 1 then
if read_byte(tag_data + 2) == 0 then
flag_meta_id = read_dword(tag + 0xC)
flag_tag_name = read_string(read_dword(tag + 0x10))
return flag_meta_id, flag_tag_name
end
end
end
end
return nil, nil
end
Check if Player is In Range
-- Usage: if in_range(x1, y1, z1, x2, y2, z2, 5) then ...
local function in_range(x1, y1, z1, x2, y2, z2, radius)
local dx = x1 - x2
local dy = y1 - y2
local dz = z1 - z2
return (dx*dx + dy*dy + dz*dz) <= radius
end
Send Global/Private Message
-- Usage: send(nil, "Hello world") or send(1, "Hi Player 1")
local format = string.format
local function send(player_id, ...)
if not player_id then
execute_command('msg_prefix ""')
say_all(format(...))
execute_command('msg_prefix "' .. MSG_PREFIX .. '"')
return
end
rprint(player_id, format(...))
end
Pure Lua Functions
String Split
-- Usage: local parts = string_split("a,b,c", ",")
local function string_split(input, delimiter)
local result = {}
for substring in input:gmatch("([^" .. delimiter .. "]+)") do
result[#result + 1] = substring
end
return result
end
Table Length (Key Count)
-- Usage: local count = table_length({a=1, b=2})
local function table_length(tbl)
local count = 0
for _ in pairs(tbl) do count = count + 1 end
return count
end
Shuffle Table Elements (Fisher-Yates)
-- Usage: shuffle_table(my_table)
local function shuffle_table(tbl)
for i = #tbl, 2, -1 do
local j = math.random(i)
tbl[i], tbl[j] = tbl[j], tbl[i]
end
end
Deep Copy Table
-- Usage: local copy = deep_copy(orig_table)
local function deep_copy(orig)
if type(orig) ~= "table" then return orig end
local copy = {}
for k, v in pairs(orig) do
copy[deep_copy(k)] = deep_copy(v)
end
return setmetatable(copy, deep_copy(getmetatable(orig)))
end
Non-blocking Timer Using Coroutines
-- Usage:
-- co = timer(2, function() print("2 seconds passed") end)
-- coroutine.resume(co) repeatedly until completion
local function timer(delay, func)
local co = coroutine.create(function()
local start = os.clock()
while os.clock() - start < delay do coroutine.yield() end
func()
end)
return co
end