TutorialsGrowtopiaBot Tutorial

GrowtopiaBot Tutorial: Deposit System

This tutorial demonstrates how to create a fully functional deposit system that allows players to deposit locks from real Growtopia into your GTPS server. The bot monitors a donation box in real Growtopia and automatically sends in-game mail with the deposited items.

Overview

This example covers:

  • Setting up and starting a GrowtopiaBot
  • Handling world entry and verification
  • Parsing donation box messages
  • Converting locks to their equivalents (WL, DL, BGL)
  • Linking GrowIDs to GTPS accounts
  • Sending in-game mail rewards
  • Parsing raw packets for world data

Complete Example Script

-- growtopia-bot script
print("(Loaded) growtopia-bot script for GrowSoft")
 
function startsWith(str, start)
    return string.sub(str, 1, string.len(start)) == start
end
 
local TARGET_WORLD = "MYAWESOMEFARM1236" -- Make sure its upper-case
 
local bot = GrowtopiaBot.new("LEPr2BRNoZGD") -- Token from purchased addon
 
local savedGrowIDs = {}
 
bot:start()
 
-- With our GrowtopiaBot you dont need to do anything extra its pretty much plug & play, 
-- no need to change subservers or send anti-cheat responses. 
-- All that is already handled by the backend.
 
-- bot:start() -- Starts the bot
-- bot:sendStr() -- Send string packet
-- bot:sendRaw() -- Send raw packet
 
function growtopiaBotEnterWorld()
    bot:sendStr(3, "action|join_request\nname|" .. TARGET_WORLD .. "\ninvitedWorld|0\n")
end
 
bot:onReceiveTextCallback(function(str, data)
    print(str)
end)
 
bot:onReceiveVariantCallback(function(data)
    print(data[1])
    if data[1] == "OnConsoleMessage" then
        local message = data[2]
        
        if startsWith(message, "Where would you like to go?") then
            growtopiaBotEnterWorld()
        elseif string.find(message, "into the Donation Box") then
            local player = message:match("w(%w+) places")
            local quantity = tonumber(message:match("`5(%d+)``"))
            local item = message:match("`2([^`]+)``")
 
            local add_locks = 0
 
            if item == "World Lock" then
                add_locks = quantity
            elseif item == "Diamond Lock" then
                add_locks = quantity * 100
            elseif item == "Blue Gem Lock" then
                add_locks = quantity * 10000
            end
 
            if add_locks ~= 0 then
                print("Player " .. player .. " deposit " .. item .. " x" .. quantity .. " TOTAL ADD: " .. add_locks)
 
                if savedGrowIDs[player:lower()] then
                    local user_id = savedGrowIDs[player:lower()]
                    local gtps_player = getPlayer(user_id)
 
                    local new_mail = {
                        titleID = 242,
                        titleLabel = "Your Deposit",
                        titleDescription = "Thanks for depositing!",
                        longDescription = "Heres your deposit.",
                        expireTime = os.time() + 6000, -- expires in 100 min
                        rewards = {
                            { id = 242, count = add_locks }
                        }
                    }
                    gtps_player:addMail(new_mail)
                end
            end
        end
    elseif data[1] == "OnFailedToEnterWorld" then
        bot:reconnect()
    end
end)
 
bot:onReceiveTrackCallback(function(str, data)
    -- print(str)
end)
 
bot:onReceiveRawCallback(function(data) 
    -- Remove from code if you're not using it (It has small CPU impact) 
    -- BIG CPU impact if ure in a world with TONS of people
    local rd = BinaryReader(data)
    rd:Skip(4) -- Ignore this, always skip here
 
    local game_packet_type = rd:ReadUInt8()
    
    if game_packet_type == 4 then -- World data packet! Lets verify if the bot is in the right world
        -- game update packet structure should be skipped as the world data goes after it (extended packet)
        rd:Skip(55)
        
        rd:Skip(6) -- This has some world data info but we dont need it, we only need the name
 
        local world_name_length = rd:ReadUInt16()
        local world_name = rd:ReadString(world_name_length)
 
        print("Bot is currently in world: " .. world_name)
 
        if world_name ~= TARGET_WORLD then
            growtopiaBotEnterWorld()
        end
    elseif game_packet_type == 17 then -- Example capture send particle effect raw packet & log it
        -- This is game update packet structure (Its same for all packets)
        local obj_type = rd:ReadUInt8()
        local count_1 = rd:ReadUInt8()
        local count_2 = rd:ReadUInt8()
        local net_id = rd:ReadInt32()
        local item = rd:ReadInt32()
        local flags = rd:ReadInt32()
        local float_var = rd:ReadFloat()
        local int_data = rd:ReadInt32()
        local vec_x = rd:ReadFloat()
        local vec_y = rd:ReadFloat()
        local vec_2_x = rd:ReadFloat()
        local vec_2_y = rd:ReadFloat()
        local particle_rotation = rd:ReadFloat()
        local int_x = rd:ReadUInt32()
        local int_y = rd:ReadUInt32()
 
        -- If you send identical packet in GTPS you will get exactly the same particle u captured 
        -- (U can change positions!)
 
        -- print("Capture particle data: (This includes actually everything about particle that real-gt has, including ID, position and extra properties)")
        -- print("particle data #1: " .. vec_2_y)
        -- print("particle data #2: " .. net_id)
        -- print("particle data #3: " .. particle_rotation)
        -- print("particle data #4: " .. vec_2_x)
        -- print("particle data #5: " .. vec_x)
        -- print("particle data #6: " .. vec_y)
        -- print("particle data #7: " .. int_data)
    end
end)
 
Roles = {
    ROLE_NONE = 0
}
 
local setGrowIDCommandData = {
    command = "setgrowid",
    roleRequired = Roles.ROLE_NONE,
    description = "This command allows you to deposit 1:1 locks from real-gt!"
}
 
registerLuaCommand(setGrowIDCommandData) -- This is just for some places such as role descriptions and help
 
onPlayerCommandCallback(function(world, player, fullCommand)
    local command, message = fullCommand:match("^(%S+)%s*(.*)")
    if command ~= setGrowIDCommandData.command then
        return false
    end
 
    local growid = message:lower()
 
    if growid == "" then
        player:onTalkBubble(player:getNetID(), "Please provide a GrowID.", 1)
        return true
    end
 
    for id, userId in pairs(savedGrowIDs) do
        if userId == player:getUserID() then
            player:onTalkBubble(player:getNetID(), "You already have a deposit GrowID set.", 1)
            return true
        end
    end
 
    if savedGrowIDs[growid] then
        player:onTalkBubble(player:getNetID(), "This GrowID is already in use.", 1)
        return true
    end
 
    savedGrowIDs[growid] = player:getUserID()
    player:onTalkBubble(
        player:getNetID(),
        "Your deposit GrowID has been set to " .. growid,
        1
    )
 
    return true
end)

Code Breakdown

1. Bot Initialization

local TARGET_WORLD = "MYAWESOMEFARM1236" -- Make sure its upper-case
local bot = GrowtopiaBot.new("LEPr2BRNoZGD") -- Token from purchased addon
local savedGrowIDs = {}
 
bot:start()
  • Define the target world where the donation box is located
  • Create a bot instance with your addon token
  • Initialize a table to store GrowID → UserID mappings
  • Start the bot connection

2. World Entry Function

function growtopiaBotEnterWorld()
    bot:sendStr(3, "action|join_request\nname|" .. TARGET_WORLD .. "\ninvitedWorld|0\n")
end

This function sends a join request packet to enter the specified world.

3. Handling Console Messages

bot:onReceiveVariantCallback(function(data)
    if data[1] == "OnConsoleMessage" then
        local message = data[2]
        
        if startsWith(message, "Where would you like to go?") then
            growtopiaBotEnterWorld()
        elseif string.find(message, "into the Donation Box") then
            -- Parse donation message
        end
    end
end)
  • Detect when the bot spawns and automatically enter the target world
  • Monitor donation box messages

4. Parsing Donation Messages

local player = message:match("w(%w+) places")
local quantity = tonumber(message:match("`5(%d+)``"))
local item = message:match("`2([^`]+)``")
 
local add_locks = 0
 
if item == "World Lock" then
    add_locks = quantity
elseif item == "Diamond Lock" then
    add_locks = quantity * 100
elseif item == "Blue Gem Lock" then
    add_locks = quantity * 10000
end

Extract information from donation box messages and convert different lock types to World Lock equivalents:

  • 1 World Lock = 1 WL
  • 1 Diamond Lock = 100 WL
  • 1 Blue Gem Lock = 10,000 WL

5. Sending In-Game Mail

if savedGrowIDs[player:lower()] then
    local user_id = savedGrowIDs[player:lower()]
    local gtps_player = getPlayer(user_id)
 
    local new_mail = {
        titleID = 242,
        titleLabel = "Your Deposit",
        titleDescription = "Thanks for depositing!",
        longDescription = "Heres your deposit.",
        expireTime = os.time() + 6000, -- expires in 100 min
        rewards = {
            { id = 242, count = add_locks }
        }
    }
    gtps_player:addMail(new_mail)
end
  • Check if the depositing player has linked their GrowID
  • Get the player object from their UserID
  • Create and send in-game mail with World Locks (item ID 242)

6. World Verification with Raw Packets

bot:onReceiveRawCallback(function(data)
    local rd = BinaryReader(data)
    rd:Skip(4)
    
    local game_packet_type = rd:ReadUInt8()
    
    if game_packet_type == 4 then
        rd:Skip(55)
        rd:Skip(6)
        
        local world_name_length = rd:ReadUInt16()
        local world_name = rd:ReadString(world_name_length)
        
        if world_name ~= TARGET_WORLD then
            growtopiaBotEnterWorld()
        end
    end
end)

Parse raw world data packets to verify the bot is in the correct world. If not, automatically rejoin the target world.

7. GrowID Linking Command

local setGrowIDCommandData = {
    command = "setgrowid",
    roleRequired = Roles.ROLE_NONE,
    description = "This command allows you to deposit 1:1 locks from real-gt!"
}
 
registerLuaCommand(setGrowIDCommandData)
 
onPlayerCommandCallback(function(world, player, fullCommand)
    local command, message = fullCommand:match("^(%S+)%s*(.*)")
    if command ~= setGrowIDCommandData.command then
        return false
    end
 
    local growid = message:lower()
 
    -- Validation checks
    if growid == "" then
        player:onTalkBubble(player:getNetID(), "Please provide a GrowID.", 1)
        return true
    end
 
    -- Check if player already has a GrowID set
    for id, userId in pairs(savedGrowIDs) do
        if userId == player:getUserID() then
            player:onTalkBubble(player:getNetID(), "You already have a deposit GrowID set.", 1)
            return true
        end
    end
 
    -- Check if GrowID is already taken
    if savedGrowIDs[growid] then
        player:onTalkBubble(player:getNetID(), "This GrowID is already in use.", 1)
        return true
    end
 
    savedGrowIDs[growid] = player:getUserID()
    player:onTalkBubble(
        player:getNetID(),
        "Your deposit GrowID has been set to " .. growid,
        1
    )
 
    return true
end)

Players use /setgrowid <growid> to link their real Growtopia GrowID with their GTPS account.

Usage Instructions

  1. Setup:

    • Replace LEPr2BRNoZGD with your actual addon token
    • Change MYAWESOMEFARM1236 to your donation box world (must be UPPERCASE)
    • Ensure the bot account has access to the world
  2. Player Setup:

    • Players run /setgrowid THEIRGROWID in your GTPS
    • They can now deposit locks in real Growtopia
  3. Depositing:

    • Player goes to the donation box world in real Growtopia
    • Places locks in the donation box
    • Bot detects the deposit and sends in-game mail to their GTPS account
  4. Receiving:

    • Player opens their mailbox in GTPS
    • Receives mail with World Lock equivalent of their deposit

Performance Notes

  • The onReceiveRawCallback has CPU impact, especially in crowded worlds
  • Remove the particle effect parsing code if you don’t need it
  • Consider adding a database to persist GrowID mappings between server restarts

Advanced Customization

Add More Lock Types

elseif item == "Small Lock" then
    add_locks = quantity / 100 -- 100 SL = 1 WL

Change Conversion Rates

elseif item == "Diamond Lock" then
    add_locks = quantity * 150 -- Custom rate

Add Deposit Bonuses

if add_locks >= 100 then
    add_locks = math.floor(add_locks * 1.1) -- 10% bonus for 100+ WL deposits
end

Database Persistence

Consider storing savedGrowIDs in a file or database for persistence:

-- Save to file when updated
function saveGrowIDs()
    local json = encodeJSON(savedGrowIDs)
    writeFile("growids.json", json)
end
 
-- Load on startup
function loadGrowIDs()
    local json = readFile("growids.json")
    if json then
        savedGrowIDs = decodeJSON(json)
    end
end

Troubleshooting

Bot not entering world:

  • Verify the world name is in UPPERCASE
  • Check if the bot account has access to the world (If there is a level requirement)
  • Ensure the world exists

Deposits not being credited:

  • Verify players have linked their GrowID with /setgrowid
  • Check that the GrowID matches exactly (case-insensitive)

CPU usage too high:

  • Remove or comment out onReceiveRawCallback if not needed
  • Avoid having the bot in worlds with many players

See Also