Skip to Content
TutorialsGrowtopia Bot 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

Last updated on