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")
endThis 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
endExtract 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
-
Setup:
- Replace
LEPr2BRNoZGDwith your actual addon token - Change
MYAWESOMEFARM1236to your donation box world (must be UPPERCASE) - Ensure the bot account has access to the world
- Replace
-
Player Setup:
- Players run
/setgrowid THEIRGROWIDin your GTPS - They can now deposit locks in real Growtopia
- Players run
-
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
-
Receiving:
- Player opens their mailbox in GTPS
- Receives mail with World Lock equivalent of their deposit
Performance Notes
- The
onReceiveRawCallbackhas 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 WLChange Conversion Rates
elseif item == "Diamond Lock" then
add_locks = quantity * 150 -- Custom rateAdd Deposit Bonuses
if add_locks >= 100 then
add_locks = math.floor(add_locks * 1.1) -- 10% bonus for 100+ WL deposits
endDatabase 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
endTroubleshooting
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
onReceiveRawCallbackif not needed - Avoid having the bot in worlds with many players
See Also
- GrowtopiaBot API Reference
- Player Structure - For
getPlayer()and mail functions - Callbacks - For
onPlayerCommandCallback()