Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ZTzTopia/GTProxy/llms.txt

Use this file to discover all available pages before exploring further.

Overview

This page showcases real scripts from the GTProxy scripts/ directory, demonstrating practical uses of the Lua API.

Rainbow Skin Effect

A complete command that cycles through rainbow colors on your character’s skin.
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")

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")
Key Features:
  • 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.
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
Key Features:
  • 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)
Key Features:
  • 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")
Key Features:
  • 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

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
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)
Keep variables local to avoid conflicts:
local my_state = {}
local my_task_id = nil

-- Not:
my_state = {}  -- Global!
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

Build docs developers (and LLMs) love