Skip to content

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:

  1. Find the official GameclientMessage* name (catalog or IDA).
  2. Allocate the protobuf object in Lua.
  3. Write only proven vtable/fields.
  4. d.enqueue(p, queue, msg).

Do not invent a C++ wrapper, RVA, or field layout.

Three inputs — do not mix them

SourceUse it forNever use it for
Canary LuaWhat the feature means: preconditions, item idsOpcodes (0x96, 0x84) as official protobuf type ids
Working Lua examples 04_say_hello.lua, 06_buy_gold_potions.luaThe craft pattern: pkt()d.allocd.vtable / p.va → writes → d.enqueueCopying field offsets blindly to a different packet
IDA dump + IDA MCPRTTI class name, object size, which fields the client writesPasting 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

  1. One sentence: "Buy N of item X while the NPC trade is open."
  2. Optional Canary search: item id, gold math, who must be nearby. Keep names, drop opcodes.
  3. 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)
  1. If the row is missing or va is nil: open the IDA dump. Do not invent an RVA.
  2. Print-only probe: session, queue, creature id, packet row.
  3. Alloc + fill + enqueue. One packet. Read logs.
  4. 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))
end

Nested 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) — never d.call(p.enqueue_rva, ...)
  • You can say hi / trade to an NPC with mode npc_to (12). Still Lua. No game.say required.

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

  1. list_instances() — note instance_id.
  2. analysis_wait(instance_id) until functions_added stays 0.
  3. Find GameclientMessage + the feature word. Confirm the RTTI short name.
  4. Decompile the constructor or fill function. Record offsets as +16 flags, not image addresses.
  5. Find TProtocolMessageQueue_enqueue* only if the catalog has no row. Put that RVA in Engine/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, RTTIEvery game feature (talk, buy, sell, use, greet, house, …)
game.on / game.off / require / logsTimers, NPC dialogue order, inventory walks
RttiPackets.inc enqueue column after a client updateField writes for that packet

game.say / game.use_on_self / game.walk may exist. Treat them as legacy. New scripts copy 04 / 06.

Arcane SDK catalog generated from scripts/sdk