Appearance
Build from zero (Lua only)
Live folder is %USERPROFILE%\Documents\SharpTib\scripts. Start with the script developer guide. Use this page when the SDK does not yet expose an operation and you must implement the packet safely in Lua.
ObjectPosition oneof: 1=worldmap, 2=inventory (slots 1–10), 3=container. Tagging inventory as case 1 crashes. MoveObject onto an equipped quiver is case 2.
C++ provides the thin kernel. New author features remain .lua files. Preferred order: high-level sdk.* → sdk.packet → raw game.dbg construction. Do not create a one-off native wrapper when the operation can stay in Lua.
The fallback rule
First check the high-level SDK (LocalPlayer, Inventory, Npc, Data, Path, Kite, Navigator) and sdk.packet. If the operation is still missing, keep the fallback in Lua:
- Find the official
GameclientMessage*name (catalog or IDA). - Allocate the protobuf object in Lua.
- Write only proven vtable/fields.
d.enqueue(p, queue, msg).
Do not invent a C++ wrapper, RVA, or field layout.
Three inputs — do not mix them
| Source | Use it for | Never use it for |
|---|---|---|
| Canary Lua | What the feature means: preconditions, item ids | Opcodes (0x96, 0x84) as official protobuf type ids |
Working Lua examples 04_say_hello.lua, 06_buy_gold_potions.lua | The craft pattern: pkt() → d.alloc → d.vtable / p.va → writes → d.enqueue | Copying field offsets blindly to a different packet |
| IDA dump + IDA MCP | RTTI class name, object size, which fields the client writes | Pasting RVAs into Lua |
Canary !sellhouse is a server talkaction. Official Tibia 15.32 does not send that script. The matching client packet in the catalog is CyclopediaHouseAction / type 173.
Loop you repeat for every feature
- One sentence: "Buy N of item X while the NPC trade is open."
- Optional Canary search: item id, gold math, who must be nearby. Keep names, drop opcodes.
- Catalog first:
lua
local p = game.dbg.packet("BuyObject") or game.dbg.packet("GameclientMessageBuyObject") or game.dbg.packet(122)
print(p and p.rtti, p and p.type, p and p.va)- If the row is missing or
vais nil: open the IDA dump. Do not invent an RVA. - Print-only probe: session, queue, creature id, packet row.
- Alloc + fill + enqueue. One packet. Read logs.
- Only then loop, timers, or NPC greet.
Skeleton
lua
local d = game.dbg
local function u64(x)
if x == nil then return nil end
if type(x) == "number" then return x end
if type(x) ~= "string" then return nil end
return tonumber(x:gsub("^0[xX]", ""), 16)
end
local function pkt(name)
local p = d.packet(name)
if not p or not p.va then
return nil, "packet not in catalog / RTTI miss: " .. tostring(name)
end
return p
end
local function send_one()
local p, err = pkt("TalkMessage") -- change the name
if not p then return nil, err end
local queue = d.queue()
if not queue then return nil, "no queue — log into a character" end
local msg = u64(d.alloc(64))
if not msg then return nil, "alloc failed" end
d.memset(msg, 0, 64)
d.write_u64(msg, p.va)
-- write proven fields here (from 04/06 or IDA)
return d.enqueue(p, queue, d.hex(msg))
endNested objects (not packets):
lua
d.write_u64(ident, d.vtable("ObjectIdentifier"))
d.write_u32(ident + 16, 1)
d.write_u32(ident + 24, item_id)Worked: talk (04_say_hello.lua)
- Packet:
TalkMessage/GameclientMessageTalk/ 150 - Local say = mode
1. Do not set channel id for default chat. d.enqueue(p, queue, msg)— neverd.call(p.enqueue_rva, ...)- You can say
hi/tradeto an NPC with modenpc_to(12). Still Lua. Nogame.sayrequired.
See also the say example.
Worked: buy potions (06_buy_gold_potions.lua)
- Packet:
BuyObject/GameclientMessageBuyObject/ 122 - Gold ids
3031/3035/3043(from observed appearances) - NPC trade window must already be open
See also the buy example.
IDA dump + IDA MCP
list_instances()— noteinstance_id.analysis_wait(instance_id)untilfunctions_addedstays 0.- Find
GameclientMessage+ the feature word. Confirm the RTTI short name. - Decompile the constructor or fill function. Record offsets as
+16 flags, not image addresses. - Find
TProtocolMessageQueue_enqueue*only if the catalog has no row. Put that RVA inEngine/RttiPackets.inc. Never in Lua.
IDA hex is patch-specific. After a client update, re-resolve by name. Keep Lua on d.packet / d.vtable / d.enqueue.
What C++ is allowed to stay
| Keep in C++ | Do in Lua |
|---|---|
game.dbg.* memory, catalog, enqueue, RTTI | Every game feature (talk, buy, sell, use, greet, house, …) |
game.on / game.off / require / logs | Timers, NPC dialogue order, inventory walks |
RttiPackets.inc enqueue column after a client update | Field writes for that packet |
game.say / game.use_on_self / game.walk may exist. Treat them as legacy. New scripts copy 04 / 06.