Overview
This page showcases real scripts from the GTProxyscripts/ directory, demonstrating practical uses of the Lua API.
Rainbow Skin Effect
A complete command that cycles through rainbow colors on your character’s skin.- Full Script
- Key Features
local rainbow = {}
local is_active = false
local current_task_id = nil
local current_color_index = 0
local RAINBOW_COLORS = {
0xFF0000FF, -- Red
0xFF3300FF, -- Red-Orange
0xFF6600FF, -- Orange
0xFF9900FF,
0xFFCC00FF, -- Yellow-Orange
0xFFFF00FF, -- Yellow
0x99FF00FF,
0x00FF00FF, -- Green
0x00FF99FF,
0x00FFFFFF, -- Cyan
0x0099FFFF,
0x0000FFFF, -- Blue
0x000099FF,
0x6600FFFF, -- Indigo
0x9900FFFF -- Purple
}
local DEFAULT_INTERVAL = 200
local MIN_INTERVAL = 50
local MAX_INTERVAL = 5000
local function send_next_color()
local net_id = world:get_local_net_id()
if net_id < 0 then
if is_active then
stop_rainbow()
logger.info("Rainbow effect stopped (exited world)")
end
return
end
local color = RAINBOW_COLORS[current_color_index + 1]
local pkt = OnChangeSkinPacket.new()
pkt.net_id = net_id
pkt.skin = color
send.to_client(pkt)
current_color_index = (current_color_index + 1) % #RAINBOW_COLORS
end
local function toggle_on(ctx, interval)
current_task_id = scheduler.schedule_periodic(interval, function()
send_next_color()
return true
end)
is_active = true
current_color_index = 0
send_next_color()
ctx:reply("`2Rainbow effect started ``(speed: {}ms)", interval)
end
local function stop_rainbow()
if current_task_id ~= nil and scheduler.is_pending(current_task_id) then
scheduler.cancel(current_task_id)
current_task_id = nil
end
is_active = false
end
local function toggle_off(ctx)
stop_rainbow()
ctx:reply("`2Rainbow effect stopped")
end
local function parse_interval(args)
if #args == 0 then
return DEFAULT_INTERVAL, nil
end
local speed_str = args[1]
local speed_ms = tonumber(speed_str)
if speed_ms == nil then
return DEFAULT_INTERVAL, "Invalid speed value '" .. speed_str .. "'"
end
if speed_ms < MIN_INTERVAL then
return DEFAULT_INTERVAL, "Speed too slow (min " .. MIN_INTERVAL .. "ms)"
end
if speed_ms > MAX_INTERVAL then
return DEFAULT_INTERVAL, "Speed too fast (max " .. MAX_INTERVAL .. "ms)"
end
return speed_ms, nil
end
command.register("rainbow", "Toggle rainbow skin effect", function(ctx)
local net_id = world:get_local_net_id()
if net_id < 0 then
ctx:reply("`4Error: ``You are not in a world")
return false
end
if is_active then
toggle_off(ctx)
return true
end
local interval, error_msg = parse_interval(ctx.args)
if error_msg then
ctx:reply("`4Error: ``" .. error_msg)
return false
end
toggle_on(ctx, interval)
return true
end)
logger.info("Rainbow command loaded")
What it demonstrates:Concepts:
- Periodic task scheduling with
scheduler.schedule_periodic - Task cancellation with
scheduler.cancel - Command registration with argument parsing
- World state checking with
world:get_local_net_id - Packet sending with
send.to_client - Input validation and error handling
/rainbow - Toggle rainbow effect (default 200ms)
/rainbow 100 - Toggle with 100ms interval
/rainbow off - Stop rainbow effect
- State management (is_active, current_task_id)
- Color cycling with modulo arithmetic
- Graceful cleanup when exiting world
- User-configurable speed with validation
Command Examples
Simple command registration patterns.command.register("say", "Echo a message to the client", function(ctx)
if #ctx.args < 1 then
ctx:reply("Usage: /say <message>")
return false
end
local message = table.concat(ctx.args, " ")
local log = LogPacket.new()
log.msg = message
send.to_client(log)
return true
end)
command.register("echo", function(ctx)
if #ctx.args < 1 then
ctx:reply("Usage: /echo <message>")
return false
end
local message = table.concat(ctx.args, " ")
local log = LogPacket.new()
log.msg = message
send.to_client(log)
return true
end)
logger.info("Command test script loaded")
- Command with description (
say) - Command without description (
echo) - Argument validation
- Using
ctx:reply()for user feedback - Sending log packets to client
World and Player Tracking
Comprehensive player and world state monitoring.- Player Functions
- Event Handlers
local function test_local_player()
local local_player = world:get_local_player()
if local_player then
logger.info("[Test 1] Local player found!")
logger.info("[Test 1] Name: " .. local_player.name)
logger.info("[Test 1] Net ID: " .. local_player.net_id)
logger.info("[Test 1] User ID: " .. local_player.user_id)
logger.info("[Test 1] Country: " .. local_player.country_code)
logger.info("[Test 1] Position: (" .. local_player.position.x .. ", " .. local_player.position.y .. ")")
logger.info("[Test 1] Is Local: " .. tostring(local_player.is_local))
else
logger.warn("[Test 1] No local player found (not spawned yet?)")
end
end
local function test_local_net_id()
local local_net_id = world:get_local_net_id()
logger.info("[Test 2] Local Net ID: " .. local_net_id)
end
local function test_list_players()
local players = world:get_players()
logger.info("[Test 3] Players in world: " .. #players)
for net_id, player in pairs(players) do
logger.info("[Test 3] Player " .. player.net_id .. " - " .. player.name .. " (Pos: " .. player.position.x .. ", " .. player.position.y .. ")")
end
end
local function test_find_player()
local local_net_id = world:get_local_net_id()
if local_net_id >= 0 then
local player = world:get_player(local_net_id)
if player then
logger.info("[Test 4] Found player by net_id " .. local_net_id .. ": " .. player.name)
else
logger.warn("[Test 4] Could not find player by net_id " .. local_net_id)
end
else
logger.warn("[Test 4] Cannot test - no local net_id")
end
end
local function test_collision_data()
local local_player = world:get_local_player()
if local_player then
local col = local_player.collision
logger.info("[Test 5] Collision: x=" .. col.x .. ", y=" .. col.y .. ", z=" .. col.z .. ", w=" .. col.w)
logger.info("[Test 5] Invisible: " .. local_player.invisible .. ", Mod State: " .. local_player.mod_state)
end
end
test_local_player()
test_local_net_id()
test_list_players()
test_find_player()
test_collision_data()
event.on("OnSpawn", function(ctx)
if ctx:has_packet() then
local pkt = ctx:get_packet()
logger.info("[Event] Player spawned: " .. pkt.name)
test_local_player()
test_list_players()
end
end)
scheduler.schedule_periodic(5000, function()
logger.info("[Test 7] Periodic player check...")
test_local_player()
test_list_players()
return true
end)
logger.info("World test script loaded")
- Testing all player API functions
- Event-driven updates on spawn
- Periodic monitoring with scheduler
- Comprehensive player property access
- Collision and mod state checking
Scheduler Patterns
Different ways to use the scheduler API.local tasks = {}
local function schedule(key, delay, fn)
tasks[key] = scheduler.schedule(delay, fn)
return tasks[key]
end
local function schedule_periodic(key, interval, fn, initial_delay)
if initial_delay then
tasks[key] = scheduler.schedule_periodic(interval, fn, initial_delay)
else
tasks[key] = scheduler.schedule_periodic(interval, fn)
end
return tasks[key]
end
logger.info("Scheduler test script loaded")
-- One-shot timer
tasks.oneshot_500 = schedule("oneshot_500", 500, function()
logger.info("One-shot fired after 500ms")
end)
-- Limited periodic timer
do
local counter = 0
tasks.periodic_300 = schedule_periodic("periodic_300", 300, function()
counter = counter + 1
logger.info("Periodic tick: " .. counter .. "/5")
if counter >= 5 then
logger.info("Stopping periodic timer")
return false
end
return true
end)
end
-- Periodic with initial delay
tasks.periodic_delayed = schedule_periodic("periodic_delayed", 500, function()
logger.info("Periodic with initial delay fired")
return true
end, 2000)
logger.info("Pending task count: " .. scheduler.pending_count())
-- Task cancellation test
schedule("cancel_oneshot", 1500, function()
if scheduler.is_pending(tasks.oneshot_500) then
logger.info("One-shot still pending, cancelling...")
scheduler.cancel(tasks.oneshot_500)
logger.info("Pending count after cancellation: " .. scheduler.pending_count())
else
logger.info("One-shot already completed")
end
end)
-- Cancel all tasks
schedule("cancel_all", 4000, function()
logger.info("Cancelling all " .. scheduler.pending_count() .. " pending tasks")
scheduler.cancel_all()
logger.info("Pending count after cancel_all: " .. scheduler.pending_count())
end)
- Task management with named tasks
- One-shot timers
- Limited periodic timers
- Periodic timers with initial delay
- Task cancellation
- Pending task checking
- Batch cancellation with
cancel_all
Event Handling Patterns
Complex packet interception and handling.event.on("ServerBoundPacket", function(ctx)
local pkt = ctx:get_packet()
if not pkt then return end
if pkt:has_raw_data() then
logger.debug("[Raw] " .. #pkt.raw .. " bytes, modified=" .. tostring(pkt:is_modified()))
end
if pkt.text_parse then
local action = pkt.text_parse:get("action")
if action ~= "" then
logger.info("[Text] Action: " .. action)
end
elseif pkt.game_packet then
logger.info("[Game] Type: " .. tostring(pkt.game_packet.type) .. " NetID: " .. pkt.game_packet.net_id)
if pkt.game_packet.type == packet.PacketType.PACKET_STATE then
logger.info(string.format("[State] Pos: %.1f, %.1f", pkt.game_packet.pos_x, pkt.game_packet.pos_y))
end
if pkt.game_packet.flags & packet.PacketFlag.PACKET_FLAG_ON_JUMP ~= 0 then
logger.info("[Game] Player Jumped!")
end
elseif pkt.variant then
logger.info("[Variant] Function: " .. tostring(pkt.variant:get(0)))
end
end)
event.on("OnSendToServer", function(ctx)
local pkt = ctx:get_packet()
if not pkt then return end
logger.info("[OnSendToServer] Port: " .. tostring(pkt.port) .. " Token: " .. tostring(pkt.token))
logger.info("[OnSendToServer] Address: " .. pkt.address .. " User: " .. tostring(pkt.user))
end)
event.on("Input", function(ctx)
local pkt = ctx:get_packet()
if not pkt then return end
logger.info("[Chat] " .. pkt.text)
if pkt.text == "!hello" then
ctx:cancel()
local response = LogPacket.new()
response.msg = "Hello from Lua!"
send.to_client(response)
elseif pkt.text == "!rawtest" then
if pkt:has_raw_data() then
logger.info("[Raw] " .. #pkt.raw .. " bytes, modified=" .. tostring(pkt:is_modified()))
end
ctx:cancel()
elseif pkt.text == "!modifytest" then
pkt.text = "modified message"
pkt:mark_modified()
logger.info("[Modified] Packet marked as modified")
end
end)
logger.info("Event test script loaded")
- Multi-type packet handling
- Text parse packet inspection
- Game packet type checking
- Packet flag checking (jump detection)
- Variant packet handling
- Chat interception and auto-response
- Packet cancellation
- Packet modification
Best Practices Summary
Always validate input
Always validate input
Check arguments, player state, and data before using:
if #ctx.args < 1 then
ctx:reply("Usage: /command <arg>")
return false
end
local player = world:get_local_player()
if not player then
ctx:reply("`4Error: ``Not in a world")
return false
end
Clean up resources
Clean up resources
Cancel tasks and clean up state:
event.on("server:Disconnect", function(ctx)
if current_task_id then
scheduler.cancel(current_task_id)
current_task_id = nil
end
end)
Use local scope
Use local scope
Keep variables local to avoid conflicts:
local my_state = {}
local my_task_id = nil
-- Not:
my_state = {} -- Global!
Provide user feedback
Provide user feedback
Always inform users of success/failure:
ctx:reply("`2Success: ``Operation completed")
ctx:reply("`4Error: ``Invalid input")
See Also
Logger API
Logging reference
Events API
Event handling
Commands API
Command registration
Scheduler API
Task scheduling
World API
World and player data
Items API
Item database