Scripting with Phasor - Server-Side Lua
Table of Contents
- Loading table of contents...
Phasor is a server-side extension for Halo PC/Custom Edition. It exposes a Lua Scripting API, anti-cheat tools, event hooks, command handling, player management, logging, and numerous under-the-hood features. This guide focuses on its Lua API, walks through the core scripting model and practical examples so you can build your Lua scripts.
Important: Phasor's Lua API is based on Lua 5.2.
This guide assumes you have read:
Script Skeleton & Version Check
See full blank script for a complete example.
Every Phasor Lua script must define both GetRequiredVersion() and OnScriptLoad().
GetRequiredVersion() must return 200. Without these definitions, Phasor will not load the script.
function GetRequiredVersion() -- Required or script will not load
return 200
end
function OnScriptLoad(processId, game, persistent) -- Required or script will not load
-- processId : number (the process ID of the server)
-- game : number (the game being played: "PC", "CE")
-- persistent : boolean (true if script is persistent)
end
function OnScriptUnload() -- Optional, but recommended to avoid Lua errors on unload
-- Cleanup code (optional)
end
Where to Put Your Scripts
Phasor looks for Lua scripts in its scripts folder. Typical path: ./cg/scripts/.
Script management commands
sv_script_load[script][persistent]sv_script_unload[script]sv_script_reload[script]sv_script_list
Handling Game Version (PC vs CE)
Because Phasor cannot scan signatures, you must use the game parameter to select the correct hardcoded addresses.
The Common Lua References provides many offsets - here’s a practical
example:
local gametype_base
local function get_score_limit()
return readbyte(gametype_base + 0x58)
end
function OnScriptLoad(processid, game, persistent)
if game == "PC" then
gametype_base = 0x671340
else -- CE
gametype_base = 0x5F5498
end
end
Phasor Functions Reference
General Functions
| Function | Description |
|---|---|
hprintf(message) |
Prints text to the server terminal. |
resolveplayer(player) |
Converts a memory ID (0-based) to an RCON/player ID (1-based). |
rresolveplayer(player) |
Converts an RCON ID (1-based) back to a memory ID (0-based). |
getrandomnumber(min, max) |
Returns a pseudo-random integer between min and max inclusive. |
svcmd(cmd) |
Executes a server command string (e.g., sv_cmd "sv_kick 5"). Player indices must be RCON IDs (use resolveplayer). |
getprofilepath() |
Returns the server’s data path (where banlist.txt is stored). |
Memory Reading
| Function | Description |
|---|---|
readbit(data, data_offset, bit_offset) |
Reads a single bit (0 or 1) from data + data_offset at bit_offset (0-7). |
readbyte(address) |
Reads an unsigned 8-bit byte (0-255). |
readchar(address) |
Reads a signed 8-bit byte (-128 to 127). |
readword(address) |
Reads an unsigned 16-bit integer (0-65535). |
readshort(address) |
Reads a signed 16-bit integer (-32768 to 32767). |
readdword(address) |
Reads an unsigned 32-bit integer (0-4294967295). |
readint(address) |
Reads a signed 32-bit integer. |
readfloat(address) |
Reads a 32-bit floating point number. |
readdouble(address) |
Reads a 64-bit double-precision float. |
readstring(address, [length]) |
Reads a null-terminated ASCII string (optional max length). |
readwidestring(address, [length]) |
Reads a null-terminated UTF-16LE string (converted to ASCII). |
Memory Writing
| Function | Description |
|---|---|
writebit(data_addr, data_offset, bit_offset, bit) |
Writes a bit to memory. |
writebyte(address, value) |
Writes an unsigned 8-bit byte. |
writechar(address, value) |
Writes a signed 8-bit byte. |
writeword(address, value) |
Writes an unsigned 16-bit integer. |
writeshort(address, value) |
Writes a signed 16-bit integer. |
writedword(address, value) |
Writes an unsigned 32-bit integer. |
writeint(address, value) |
Writes a signed 32-bit integer. |
writefloat(address, value) |
Writes a 32-bit floating point number. |
writedouble(address, value) |
Writes a 64-bit double. |
writestring(address, str, [length]) |
Writes a null-terminated ASCII string. |
writewidestring(address, str, [length]) |
Writes a null-terminated UTF-16LE string. |
Player & Team Functions
| Function | Description |
|---|---|
getplayer(player) |
Returns the memory address of a player table entry (0-based). |
getname(player) |
Returns the player’s name (ASCII). |
gethash(player) |
Returns the player’s CD-key hash (unique identifier). |
getteam(player) |
Returns the player’s team (0 = Red, 1 = Blue). |
getteamsize(team) |
Returns the number of players on the specified team. |
changeteam(player) |
Switches the player’s team. |
kill(player) |
Kills the specified player. |
applycamo(player, duration) |
Makes the player invisible (camo) for duration seconds. Use 0 for infinite (until death). |
setspeed(player, speed) |
Changes the player’s movement speed. Default is 1.0. |
isadmin(player) |
Returns true if the player is an admin, false otherwise. |
Object Functions
| Function | Description |
|---|---|
getobject(m_objId) |
Converts an object ID into its memory address for reading/writing. |
getplayerobjectid(player) |
Returns the object ID of the player’s biped (0xFFFFFFFF if dead/invalid). |
getobjectcoords(m_objectId) |
Returns the (x, y, z) coordinates of an object. If a player is in a vehicle, returns the vehicle’s coordinates. |
gettagid(tagType, tag) |
Returns the map ID of a tag given its type (e.g., "weap") and full name (e.g., "weapons\\pistol\\pistol"). |
createobject(tagType, tag, parentId, respawnTime, bRecycle, x, y, z) |
Creates a new object. Returns its object ID. Parameters: tagType (e.g., "weap"), tag (full tag name), parentId (object ID of parent, or -1), respawnTime (seconds; 0 = never, -1 = gametype default), bRecycle (true = respawn, false = destroy), and x,y,z coordinates. For item collections (weapons, nades, powerups), bRecycle is ignored and respawnTime is the destruction delay. |
destroyobject(m_objectId) |
Deletes an object. Ensure the object ID is valid to avoid crashes. |
movobjcoords(m_objectId, x, y, z) |
Teleports an object to absolute coordinates. |
movobjname(m_objectId, name) |
Teleports an object to a named location (e.g., "redbase"). |
assignweapon(player, m_objectId) |
Gives the specified weapon object to a player (if they have fewer than 4 weapons). |
updateammo(m_weaponId) |
Forces ammo changes on a weapon to sync correctly. |
entervehicle(player, m_vehicleId, seat) |
Forces a player into a vehicle (seat: 0 = driver, 1 = passenger, 2 = gunner). Ensure the player is not already in a vehicle. |
exitvehicle(player) |
Forces a player to exit their current vehicle. |
Chat & Console Output
| Function | Description |
|---|---|
say(msg) |
Broadcasts a message to all players. |
privatesay(player, msg) |
Sends a private message to a specific player. |
respond(msg) |
Sends a message to the server terminal. |
hprint(msg) |
Sends a message to the server terminal. |
print(msg) |
Sends a message to the server terminal. |
Timer Functions
| Function | Description |
|---|---|
registertimer(delay, callback, ...) |
Creates a timer that calls callback after delay milliseconds. The callback can return 1 to repeat or 0 to stop. Extra arguments are passed to the callback as userdata. Returns a timer ID. |
removetimer(id) |
Stops and removes a timer created by registertimer. |
Callback signature: function callback(id, count, userdata1, userdata2, ...), count is the number of times the
timer has fired.
Example:
-- registertimer: call function after delay (ms), can repeat
function my_callback(id, count, msg)
hprintf("Timer " .. id .. " fired " .. count .. " times. Msg: " .. msg)
if count >= 3 then
return 0 -- stop after 3 times
end
return 1 -- repeat
end
local timer_id = registertimer(1000, "my_callback", "Hello Timer")
-- removetimer: stop timer early (e.g., on script unload)
removetimer(timer_id)
Utility Functions Library
The following functions are user-defined Lua helpers that build on the Phasor API. They simplify common tasks such as
player state queries, object manipulation, memory I/O, and gametype handling.
Note: Many functions rely on global variables like gametype_base, map_pointer, ctf_globals, etc. You must set
these in OnScriptLoad based on the game version (PC or CE) using the offsets from the Common Lua References.
Player State & Info
get_player_biped
Parameters:
id - Player index (0-based memory ID)
Returns:
object_struct (table), object_id (number) - The player’s biped object and its ID, or nil if dead/invalid.
local function get_player_biped(id)
local obj_id = getplayerobjectid(id)
if not obj_id or obj_id == 0 then return nil end
return getobject(obj_id), obj_id
end
Example:
local obj, obj_id = get_player_biped(0)
if obj then
print("Player 0 biped address: " .. tostring(obj))
end
is_alive
Parameters:
id - Player index (0-based)
Returns:
boolean - true if the player has a valid biped object.
local function is_alive(id)
if id == nil then return false end
return getplayerobjectid(id) ~= nil
end
Example:
if is_alive(3) then
kill(3)
end
get_player_pos
Parameters:
id - Player index (0-based)
Returns:
x, y, z (numbers) - World coordinates compensating for crouch, or nil if the player is invalid.
local function get_player_pos(id)
local obj, obj_id = get_player_biped(id)
if not obj then return nil end
local x = readfloat(obj, 0x5C)
local y = readfloat(obj, 0x60)
local z = readfloat(obj, 0x64)
local crouch = readfloat(obj + 0x50C)
-- 0.65 is standing eye height, 0.35 is crouching adjustment
local z_offset = (crouch == 0) and 0.65 or 0.35 * crouch
return x, y, z + z_offset
end
Example:
local x, y, z = get_player_pos(2)
if x then
print(string.format("Player 2 position: %.2f, %.2f, %.2f", x, y, z))
end
is_player_camouflaged
Parameters:
id - Player index (0-based)
Returns:
boolean - true if the player is invisible (camo active).
local function is_player_camouflaged(id)
local obj = get_player_biped(id)
if not obj then return nil end
return readfloat(obj + 0x37C) == 1.0
end
get_player_stats
Parameters:
id - Player index (0-based)
Returns:
table or nil - Contains keys: kills, deaths, assists, streaks, flag_captures, flag_returns,
flag_grabs.
local function get_player_stats(id)
local p = getplayer(id)
if not p then return nil end
return {
kills = readword(p + 0x9C),
deaths = readword(p + 0xAE),
assists = readword(p + 0xA4),
streaks = readword(p + 0x98),
flag_captures = readword(p + 0xC8),
flag_returns = readword(p + 0xC6),
flag_grabs = readword(p + 0xC4)
}
end
Example:
local stats = get_player_stats(1)
if stats then
say(string.format("Player has %d kills, %d deaths", stats.kills, stats.deaths))
end
get_player_vehicle
Parameters:
id - Player index (0-based)
Returns:
number - Vehicle object ID, or nil if not in a vehicle.
local function get_player_vehicle(id)
local obj, obj_id = get_player_biped(id)
if not obj then return nil end
local vehicle_id = readdword(obj + 0x11C)
if vehicle_id == 0xFFFFFFFF then return nil end
return vehicle_id
end
is_in_vehicle
Parameters:
id - Player index (0-based)
Returns:
boolean - true if the player is inside any vehicle.
local function is_in_vehicle(id)
return get_player_vehicle(id) ~= nil
end
get_player_health
Parameters:
id - Player index (0-based)
Returns:
number or nil - Health value between 0.0 and 1.0.
local function get_player_health(id)
local obj = get_player_biped(id)
if not obj then return nil end
return readfloat(obj + 0xE0)
end
Example:
local health = get_player_health(3)
if health and health < 0.3 then
set_player_health(3, 1.0) -- full heal
privatesay(3, "You have been healed!")
end
get_player_shields
Parameters:
id - Player index (0-based)
Returns:
number or nil - Shields value (0.0 to ~3.0 with overshield).
local function get_player_shields(id)
local obj = get_player_biped(id)
if not obj then return nil end
return readfloat(obj + 0xE4)
end
set_player_health
Parameters:
id - Player index (0-based)
value - Desired health (0.0 to 1.0)
local function set_player_health(id, value)
local obj = get_player_biped(id)
if obj then
writefloat(obj + 0xE0, value)
end
end
set_player_shields
Parameters:
id - Player index (0-based)
value - Desired shields (0.0 to 3.0)
local function set_player_shields(id, value)
local obj = get_player_biped(id)
if obj then
writefloat(obj + 0xE4, value)
end
end
get_player_ping
Parameters:
id - Player index (0-based)
Returns:
number or nil - Ping in milliseconds.
local function get_player_ping(id)
local p = getplayer(id)
if not p then return nil end
return readword(p + 0xDC)
end
Example:
local ping = get_player_ping(0)
hprintf(string.format("%s ping: %d ms", getname(0), ping))
get_player_weapon
Parameters:
id - Player index (0-based)
slot (optional) - Weapon slot 1-4. If omitted, returns the current weapon (or vehicle weapon if in vehicle).
Returns:
number - Weapon object ID, or 0xFFFFFFFF if none.
local function get_player_weapon(id, slot)
local obj, obj_id = get_player_biped(id)
if not obj then return 0xFFFFFFFF end
-- If the player is in a vehicle, return the vehicle's weapon.
local vehicle_id = get_player_vehicle(id)
if vehicle_id then
local veh_obj = getobject(vehicle_id)
if veh_obj then
return readdword(veh_obj + 0x2F8)
end
end
if not slot then
return readdword(obj + 0x118) -- current weapon
end
if slot >= 1 and slot <= 4 then
return readdword(obj + 0x2F8 + (slot - 1) * 4)
end
return 0xFFFFFFFF
end
get_players_by_expression
Parameters:
expression - String (e.g., "*", "me", "red", "blue", "random", "nearest", "farthest", colour name,
wildcard name, or numeric ID 1-16)
self_id (optional) - Player index used for "me", "random" exclusion, and as reference for "nearest"/
"farthest".
Returns:
table or nil - List of player indices (0-based) matching the expression.
local function get_players_by_expression(expression, self_id)
if not expression then return nil end
-- All players
if expression == "*" then
local t = {}
for i = 0, 15 do
if getplayer(i) then t[#t + 1] = i end
end
return #t > 0 and t or nil
-- Self
elseif expression == "me" then
if self_id and getplayer(self_id) then return { self_id } end
return nil
-- Red team
elseif expression == "red" then
local t = {}
for i = 0, 15 do
if getplayer(i) and getteam(i) == 0 then t[#t + 1] = i end
end
return #t > 0 and t or nil
-- Blue team
elseif expression == "blue" then
local t = {}
for i = 0, 15 do
if getplayer(i) and getteam(i) == 1 then t[#t + 1] = i end
end
return #t > 0 and t or nil
-- Random player
elseif expression == "random" or expression == "rand" then
local t = {}
for i = 0, 15 do
if getplayer(i) and i ~= self_id then t[#t + 1] = i end
end
if #t > 0 then return { t[random(#t)] } end
return nil
-- Nearest player to self
elseif expression == "nearest" or expression == "closest" then
if not self_id or not get_player_pos(self_id) then return nil end
local sx, sy, sz = get_player_pos(self_id)
local min_dist, closest = math_huge, nil
for i = 0, 15 do
if i ~= self_id and getplayer(i) then
local x, y, z = get_player_pos(i)
if x then
local d = (x - sx) ^ 2 + (y - sy) ^ 2 + (z - sz) ^ 2
if d < min_dist then
min_dist = d
closest = i
end
end
end
end
return closest and { closest } or nil
-- Farthest player from self
elseif expression == "farthest" then
if not self_id or not get_player_pos(self_id) then return nil end
local sx, sy, sz = get_player_pos(self_id)
local max_dist, farthest = -1, nil
for i = 0, 15 do
if i ~= self_id and getplayer(i) then
local x, y, z = get_player_pos(i)
if x then
local d = (x - sx) ^ 2 + (y - sy) ^ 2 + (z - sz) ^ 2
if d > max_dist then
max_dist, farthest = d, i
end
end
end
end
return farthest and { farthest } or nil
else
-- Numeric ID (1-16)
local num = tonumber(expression)
if num and num >= 1 and num <= 16 then
local id = num - 1
if getplayer(id) then return { id } end
return nil
end
-- Player colour name (e.g. "white", "yellow", "green", "orange", "purple", etc.)
local colour_index = player_colours[expression]
if colour_index then
local t = {}
for i = 0, 15 do
local p = getplayer(i)
if p and readword(p + 0x60) == colour_index then
t[#t + 1] = i
end
end
return #t > 0 and t or nil
end
-- Wildcard name matching (case-insensitive)
local t = {}
for i = 0, 15 do
if getplayer(i) then
local name = getname(i)
if wildcard_match(name, expression) then
t[#t + 1] = i
end
end
end
return #t > 0 and t or nil
end
end
Example:
local players = get_players_by_expression("red", nil)
if players then
for _, id in ipairs(players) do
say(getname(id) .. " is on red team!")
end
end
get_player_color
Parameters:
id - Player index (0-based)
Returns:
number or nil - Colour index (0-17) as seen by others.
local function get_player_color(id)
local p = getplayer(id)
if p then return readword(p + 0x60) end
return nil
end
set_player_color
Parameters:
id - Player index (0-based)
colour - Colour index (0-17) or colour name string (e.g., "white", "yellow"). A respawn is usually required for
the change to fully apply.
local function set_player_color(id, colour)
local p = getplayer(id)
if not p then return end
if type(colour) == "string" then
colour = player_colours[colour:lower()]
end
if not colour or type(colour) ~= "number" then return end
writebyte(p + 0x60, colour)
end
get_player_object
Parameters:
id - Player index (0-based)
Returns:
object_struct, object_id - Same as get_player_biped.
local function get_player_object(id)
return get_player_biped(id)
end
get_player_vehicle_object
Parameters:
id - Player index (0-based)
Returns:
vehicle_struct, vehicle_id - The vehicle object and its ID, or nil if not in a vehicle.
local function get_player_vehicle_object(id)
local obj, obj_id = get_player_object(id)
if not obj then return nil end
local veh_id = readdword(obj + 0x11C)
if veh_id == 0xFFFFFFFF then return nil end
local veh_obj = getobject(veh_id)
if not veh_obj then return nil end
return veh_obj, veh_id
end
get_player_weapon_object
Parameters:
id - Player index (0-based)
slot (optional) - Same as get_player_weapon.
Returns:
weapon_struct, weapon_id - The weapon object and its ID, or nil.
local function get_player_weapon_object(id, slot)
local weapon_id = get_player_weapon(id, slot)
if weapon_id == 0xFFFFFFFF then return nil end
local weapon_obj = getobject(weapon_id)
if not weapon_obj then return nil end
return weapon_obj, weapon_id
end
get_player_speed
Parameters:
id - Player index (0-based)
Returns:
number or nil - Current movement speed multiplier.
local function get_player_speed(id)
local p = getplayer(id)
if not p then return nil end
return readfloat(p + 0x6C)
end
set_player_speed
Parameters:
id - Player index (0-based)
speed - Desired speed multiplier (capped to 999999 to avoid crashes).
local function set_player_speed(id, speed)
local p = getplayer(id)
if not p then return end
speed = tonumber(speed) or 1
if speed > 999999 then speed = 999999 end
writefloat(p + 0x6C, speed)
end
Object Utilities
get_object_coords
Parameters:
object_id - Object ID (e.g., from getplayerobjectid)
Returns:
x, y, z - Coordinates of the object (if attached to a parent vehicle, returns parent’s coordinates).
local function get_object_coords(object_id)
local obj = getobject(object_id)
if not obj then return nil end
local parent_id = readdword(obj + 0x11C)
if parent_id ~= 0xFFFFFFFF then
local parent_obj = getobject(parent_id)
if parent_obj then obj = parent_obj end
end
return readfloat(obj + 0x5C), readfloat(obj + 0x60), readfloat(obj + 0x64)
end
get_object_tag
Parameters:
object_id - Object ID
Returns:
tag_name, tag_class - The tag name (e.g., "vehicles\\ghost\\ghost_mp") and class (e.g., "vehi"), or nil.
local tag_lookup_cache
local function get_object_tag(object_id)
if not object_id then return nil end
local obj = getobject(object_id)
if not obj then return nil end
local object_map_id = readdword(obj)
if not tag_lookup_cache then
local map_base = readdword(map_pointer)
if not map_base then return nil end
local map_tag_count = to_decimal(read_string_reverse(map_base + 0xC, 3))
if not map_tag_count then return nil end
local tag_table_base = map_base + 0x28
local tag_table_size = 0x20
tag_lookup_cache = {}
for i = 0, map_tag_count - 1 do
local base = tag_table_base + (tag_table_size * i)
local tag_id = to_decimal(read_string_reverse(base + 0xC, 3))
local tag_class = read_string(base, 4, true)
local tag_name_addr = to_decimal(read_string_reverse(base + 0x10, 3))
local tag_name = read_tag_name(tag_name_addr)
tag_lookup_cache[tag_id] = { name = tag_name, class = tag_class }
end
end
local entry = tag_lookup_cache[object_map_id]
if entry then return entry.name, entry.class end
return nil
end
get_tag_id
Parameters:
tag_class - String, e.g., "weap"
tag_name - String, e.g., "weapons\\pistol\\pistol"
Returns:
number or nil - Tag map ID, or nil if not found.
local function get_tag_id(tag_class, tag_name)
if not tag_class or not tag_name then return nil end
-- ensure cache is built
if not tag_lookup_cache then
local map_base = readdword(map_pointer)
if not map_base then return nil end
local map_tag_count = to_decimal(read_string_reverse(map_base + 0xC, 3))
if not map_tag_count then return nil end
local tag_table_base = map_base + 0x28
local tag_table_size = 0x20
tag_lookup_cache = {}
for i = 0, map_tag_count - 1 do
local base = tag_table_base + (tag_table_size * i)
local tag_id = to_decimal(read_string_reverse(base + 0xC, 3))
local cls = read_string(base, 4, true)
local tname_addr = to_decimal(read_string_reverse(base + 0x10, 3))
local tname = read_tag_name(tname_addr)
tag_lookup_cache[tag_id] = { name = tname, class = cls }
end
end
for id, info in pairs(tag_lookup_cache) do
if info.class == tag_class and (info.name == tag_name or info.name:gsub("\\", "/") == tag_name:gsub("\\", "/")) then
return id
end
end
return nil
end
get_object_type
Parameters:
object_id - Object ID
Returns:
number or nil - Type identifier (0=biped, 1=vehicle, 2=weapon, 3=equipment, etc.).
local function get_object_type(object_id)
local obj = getobject(object_id)
if not obj then return nil end
return readword(obj + 0xB4)
end
upright_vehicle
Parameters:
vehicle_id - Object ID of the vehicle
Effect: Zeroes angular velocity and re-enables physics to set the vehicle upright.
local function upright_vehicle(vehicle_id)
local obj = getobject(vehicle_id)
if not obj then return end
writefloat(obj + 0x8A, 2.3 * (10 ^ -41))
writefloat(obj + 0x8C, 2.3 * (10 ^ -41))
writefloat(obj + 0x90, 2.3 * (10 ^ -41))
writefloat(obj + 0x94, 2.3 * (10 ^ -41))
write_bit(obj + 0x10, 0, 0) -- noCollisions off
write_bit(obj + 0x10, 5, 0) -- ignorePhysics off
end
upright_player_vehicle
Parameters:
player_id - Player index (0-based)
Effect: Uprights the vehicle the player is currently riding (if any).
local function upright_player_vehicle(player_id)
local veh_id = get_player_vehicle(player_id)
if veh_id then
upright_vehicle(veh_id)
end
end
Memory Utilities
The following helpers extend Phasor’s memory functions with safer writes and convenient string/bit operations.
read_string
Parameters:
address - Starting memory address
length (optional) - Maximum bytes to read (default 256)
reverse (optional) - Reverse byte order (big-endian)
Returns:
string - Decoded ASCII string (null-terminated).
local function read_string(address, length, reverse)
local t, i = {}, 0
local max = length or 256
while i < max do
local b = readbyte(address + i)
if b == 0 then break end
t[#t + 1] = char(b)
i = i + 1
end
if reverse then
local rev = {}
for j = #t, 1, -1 do
rev[#rev + 1] = t[j]
end
return concat(rev)
end
return concat(t)
end
read_tag_name
Parameters:
address - Memory location of a null-terminated string (no length cap)
Returns:
string - The tag name.
local function read_tag_name(address)
if not address or address == 0 then return "" end
return read_string(address)
end
read_string_reverse
Parameters:
address - Base address
offset - Offset from base
length - Number of bytes to read
Returns:
string - Hexadecimal representation in reversed (big-endian) order.
local function read_string_reverse(address, offset, length)
local hex = {}
for i = 0, length - 1 do
local b = readbyte(address + offset + i)
hex[#hex + 1] = format("%02X", b)
end
local result = ""
for i = #hex, 1, -1 do
result = result .. hex[i]
end
return result
end
read_widestring
Parameters:
address - Starting address of a UTF-16LE string
length (optional) - Maximum bytes (default 256)
Returns:
string - ASCII approximation (non-ASCII characters ignored).
local function read_widestring(address, length)
length = length or 256
local chars = {}
local pos = 0
while pos < length do
local b1 = readbyte(address + pos)
if b1 == 0 then break end
local b2 = readbyte(address + pos + 1)
if b2 == 0 then
chars[#chars + 1] = char(b1)
else
chars[#chars + 1] = char(b1)
end
pos = pos + 2
end
return concat(chars)
end
write_string
Parameters:
address - Starting address
str - String to write
offset (optional) - Offset from address
Effect: Writes null-terminated ASCII string.
local function write_string(address, str, offset)
offset = offset or 0
local addr = address + offset
for i = 1, #str do
writebyte(addr + i - 1, str:byte(i))
end
writebyte(addr + #str, 0)
end
write_widestring
Parameters:
address - Starting address
str - ASCII string to write as UTF-16LE
offset (optional) - Offset from address
Effect: Writes null-terminated wide string.
local function write_widestring(address, str, offset)
offset = offset or 0
local addr = address + offset
for i = 1, #str do
local byte = str:byte(i)
writebyte(addr + (i - 1) * 2, byte)
writebyte(addr + (i - 1) * 2 + 1, 0)
end
writeword(addr + #str * 2, 0)
end
read_bit
Parameters:
address - Memory address
bit_index - 0-based bit position (0 = LSB)
Returns:
number - 0 or 1.
local function read_bit(address, bit_index)
local byte_addr = address + floor(bit_index / 8)
local bit_pos = bit_index % 8
local val = readbyte(byte_addr)
return bit32.band(bit32.rshift(val, bit_pos), 1)
end
write_bit
Parameters:
address - Memory address
bit_index - 0-based bit position
value - 0 or 1
local function write_bit(address, bit_index, value)
local byte_addr = address + floor(bit_index / 8)
local bit_pos = bit_index % 8
local old = readbyte(byte_addr)
if value == 1 then
writebyte(byte_addr, bit32.bor(old, bit32.lshift(1, bit_pos)))
else
writebyte(byte_addr, bit32.band(old, bit32.bnot(bit32.lshift(1, bit_pos))))
end
end
safe_write_byte / safe_write_char / safe_write_short / safe_write_word / safe_write_int / safe_write_dword / safe_write_float
These functions clamp values to the valid range of the target type before writing, preventing out-of-range crashes.
Example signature:
local function safe_write_byte(address, offset, value)
if value then
address = address + offset
else
value = offset
end
value = math_min(math_max(value, 0), 0xFF)
writebyte(address, value)
end
(Similar safe wrappers exist for char, short, word, int, dword, float.)
Game Time & State
get_gametype_id
Parameters: none
Returns:
number - Gametype ID: 0=none, 1=CTF, 2=Slayer, 3=Oddball, 4=King, 5=Race.
local function get_gametype_id()
return readbyte(gametype_base + 0x30)
end
get_scorelimit
Parameters: none
Returns:
number - Score limit of the current gametype.
local function get_scorelimit()
return readbyte(gametype_base + 0x58)
end
set_scorelimit
Parameters:
score - New score limit (0-255)
local function set_scorelimit(score)
writebyte(gametype_base + 0x58, score)
end
is_ffa
Parameters: none
Returns:
boolean - true if Free-For-All (team play disabled).
local function is_ffa()
return readbyte(gametype_base + 0x34) == 0
end
get_team_name
Parameters:
id - Player index (0-based)
Returns:
string - "Red" or "Blue".
local function get_team_name(id)
local team_id = getteam(id)
return team_id == 0 and "Red" or "Blue"
end
get_ctf_team_scores
Parameters: none
Returns:
table - { red_score, blue_score }.
local function get_ctf_team_scores()
local scores = {}
for team = 0, 1 do
local current_score = readdword(ctf_globals + team * 4 + 0x10)
scores[team] = current_score
end
return scores
end
get_gametype_name
Parameters: none
Returns:
string - Name of the current gametype (e.g., "CTF", "Slayer", etc.).
local function get_gametype_name()
local type = get_gametype_id()
return type == 1 and "CTF" or type == 2 and "Slayer"
or type == 3 and "Oddball" or type == 4 and "KOTH"
or type == 5 and "Race"
end
get_score
Parameters:
player - Player index (0-based)
Returns:
number - Player’s score according to current gametype rules.
local function get_score(player)
local score = 0
local timed = false
local p = getplayer(player)
local game_type = get_gametype_id()
local team_play = is_ffa()
if game_type == 1 then
score = readword(p + 0xC8)
elseif game_type == 2 then
local kills = readword(p + 0x9C)
local suicides = 0
if not team_play then
suicides = readword(p + 0xB0)
else
suicides = readword(p + 0xAC)
end
score = kills - suicides
elseif game_type == 3 then
local oddball_type = readbyte(gametype_base + 0x8C)
if oddball_type == 0 or oddball_type == 1 then
timed = true
score = readdword(0x639E5C + player)
else
score = readword(p + 0xC8)
end
elseif game_type == 4 then
timed = true
score = readword(p + 0xC4)
elseif game_type == 5 then
score = readword(p + 0xC6)
end
if timed then score = floor(score / 30) end
return score
end
get_game_time_remaining
Parameters: none
Returns:
number - Seconds remaining until game end, or 0 if time is up.
local function get_game_time_remaining()
local time_passed = readdword(readdword(gametime_base) + 0xC) / 30
local time_limit = readdword(gametype_base + 0x78) / 30
local remaining = time_limit - time_passed
return remaining > 0 and remaining or 0
end
Example:
local remaining = get_game_time_remaining()
local elapsed = get_game_time_elapsed()
hprintf(string.format("Time remaining: %ds, elapsed: %ds", remaining, elapsed))
get_game_time_elapsed
Parameters: none
Returns:
number - Seconds elapsed since game start.
local function get_game_time_elapsed()
return readdword(readdword(gametime_base) + 0xC) / 30
end
Example:
local elapsed = get_game_time_elapsed()
local remaining = get_game_time_remaining()
hprintf(string.format("Time elapsed: %ds, remaining: %ds", elapsed, remaining))
get_player_score
Parameters:
player_id - Player index (0-based)
Returns:
number or nil - Gametype-dependent player score.
local function get_player_score(player_id)
local p = getplayer(player_id)
if not p then return nil end
local gt = get_gametype_id()
if gt == 1 then -- ctf
return readshort(p + 0xC8)
elseif gt == 2 then -- slayer
return readint(slayer_globals + 0x40 + player_id * 4)
elseif gt == 3 then -- oddball
local oddball_game = readbyte(gametype_base + 0x8C)
if oddball_game == 0 or oddball_game == 1 then
return readint(oddball_globals + 0x84 + player_id * 4) / 30
else
return readint(oddball_globals + player_id * 4 + 0x104)
end
elseif gt == 4 then -- king
return readshort(p + 0xC4)
elseif gt == 5 then -- race
return readshort(p + 0xC6)
end
return 0
end
set_player_score
Parameters:
player_id - Player index (0-based)
score - New score value
local function set_player_score(player_id, score)
local p = getplayer(player_id)
if not p then return end
local gt = get_gametype_id()
if gt == 1 then
writeshort(p + 0xC8, score)
elseif gt == 2 then
writeint(slayer_globals + 0x40 + player_id * 4, score)
elseif gt == 3 then
local oddball_game = readbyte(gametype_base + 0x8C)
if oddball_game == 0 or oddball_game == 1 then
writeint(oddball_globals + 0x84 + player_id * 4, score * 30)
else
writeint(oddball_globals + player_id * 4 + 0x104, score)
end
elseif gt == 4 then
writeshort(p + 0xC4, score)
elseif gt == 5 then
writeshort(p + 0xC6, score)
end
end
get_team_score
Parameters:
team - 0 (red) or 1 (blue)
Returns:
number - Team score for the current gametype.
local function get_team_score(team)
local gt = get_gametype_id()
if gt == 1 then -- ctf
return readint(ctf_globals + team * 4 + 0x10)
elseif gt == 2 then -- slayer
return readint(slayer_globals + team * 4)
elseif gt == 3 then -- oddball
return readint(oddball_globals + team * 4 + 0x4) / 30
elseif gt == 4 then -- king
return readint(koth_globals + team * 4) / 30
elseif gt == 5 then -- race
return readint(race_globals + team * 4 + 0x88) / 30
end
return 0
end
set_team_score
Parameters:
team - 0 (red) or 1 (blue)
score - New team score
local function set_team_score(team, score)
local gt = get_gametype_id()
if gt == 1 then
writeint(ctf_globals + team * 4 + 0x10, score)
elseif gt == 2 then
writeint(slayer_globals + team * 4, score)
elseif gt == 3 then
writeint(oddball_globals + team * 4 + 0x4, score * 30)
elseif gt == 4 then
writeint(koth_globals + team * 4, score * 30)
elseif gt == 5 then
writeint(race_globals + team * 4 + 0x88, score * 30)
end
end
Miscellaneous
get_body_part_position
Parameters:
biped_object - Object struct from get_player_object
body_part_offset - Offset from the biped’s unknown float block (e.g., 0x8C4 for right hand)
Returns:
x, y, z - World position of the specified body part, or nil.
local function get_body_part_position(biped_object, body_part_offset)
if not biped_object then return nil end
local x = readfloat(biped_object, body_part_offset + 0x28)
local y = readfloat(biped_object, body_part_offset + 0x2C)
local z = readfloat(biped_object, body_part_offset + 0x30)
return x, y, z
end
Example:
local biped_obj, _ = get_player_object(0)
if biped_obj then
local hand_x, hand_y, hand_z = get_body_part_position(biped_obj, 0x8C4) -- right hand
hprintf(string.format("Right hand position: %.2f, %.2f, %.2f", hand_x, hand_y, hand_z))
end
Event-Driven Script Examples
These snippets show how to use the Phasor event hooks together with the utility functions above.
Server-side speed hack protection
function GetRequiredVersion() return 200 end
function OnPlayerSpawn(player, m_objectId)
set_player_speed(player, 1.0) -- reset speed on spawn
end
function OnClientUpdate(player, m_objectId)
local dyn = getplayerobjectid(player)
if dyn == 0 then return nil end
local obj = getobject(dyn)
if not obj then return nil end
local speed = readfloat(obj + 0x6C)
if speed > 1.05 then
set_player_speed(player, 1.0)
end
end
Private message on join
function OnPlayerJoin(player, team)
privatesay(player, "Welcome to the server!")
end
Disable a certain weapon pickup
function OnObjectInteraction(player, m_ObjectId, tagType, tagName)
if tagType == "weap" and tagName == "weapons\\rocket_launcher\\rocket_launcher" then
return 0 -- block pickup
end
return 1
end
Per-player command cooldowns
local cooldowns = {}
function OnServerCommand(player, command, environment)
if command == "some_command" then
local now = os.clock()
if cooldowns[player] and now - cooldowns[player] < 3 then
privatesay(player, "Please wait 3 seconds before using !spawn again.")
return 1
end
cooldowns[player] = now
-- handle command here...
return 1
end
return 0
end
Check if player is holding flag or oddball
local function has_objective(player_index)
local dyn = getplayerobjectid(player_index)
if dyn == 0 then return false end
local obj = getobject(dyn)
if not obj then return false end
local weapon_id = readdword(obj + 0x118)
if weapon_id == 0xFFFFFFFF then return false end
local weapon_obj = getobject(weapon_id)
if not weapon_obj then return false end
local tag_data = readdword(readdword(base_tag_table) + readword(weapon_obj) * 0x20 + 0x14)
if not tag_data then return false end
local is_objective = (readbyte(tag_data + 0x308) >> 3) & 1
if is_objective ~= 1 then return false end
local obj_type = readbyte(tag_data + 2)
return (obj_type == 0 and "Flag") or (obj_type == 4 and "Oddball") or nil
end
function OnClientUpdate(id)
if is_alive(id) then
local flag_or_oddball = has_objective(id)
if flag_or_oddball then
say(getname(id) .. " has the " .. flag_or_oddball)
end
end
end
Override player’s respawn time
local function set_respawn_time(player_index, seconds)
seconds = seconds or 3
local static = getplayer(player_index)
if static then
writedword(static + 0x2C, seconds * 33) -- 33 ticks/second
end
end
Example:
function OnPlayerKill(killer, victim, mode)
set_respawn_time(victim, 0) -- instant respawn
end
Complete Examples:
See my Phasor Script Archive.