I see far too many people often asking about this and being confused.
Let me attempt to break down the whole process, step by step, while explaining what's going on, and perhaps that will be a good starting point for anyone struggling with opcodes. If anyone more experienced spots a mistake or something to point out, feel free to do so. This is meant for absolute newbies with this, so that everyone can understand. The examples are, I believe, from TFS 1.2, but they can be applied to any distro that supports extended opcodes (only the syntax/function names may vary).
──────────────────────────────────────────────────────────────────────────────
Server and client communicate with network packets. The first thing in the packet is expected to be an operation code, which will instruct the receiver on what to do next. The packet can then contain a bunch of other data inside of it, which will be read in a sequential order.
For each opcode on the receiver's side, there should be a piece of code which says:
"Ok, I received this exact opcode, this means I have to do ___".
1. We're gonna create a new network packet and fill it with some data on the server side.
Let's start with a header like this, and I will explain what it means:
Lua:
local packet = NetworkMessage()
packet:addByte(0x32) -- Extended Opcode (0x32 = 50 (in dec))
packet:addByte(YOUR_OPCODE_HERE) -- The Opcode of this Request
What is this "
extended opcode"?
Not sure exactly for which reasons, either to avoid running out of usable signal codes due to an unplanned influx of new features while structuring the network communications // OR simply because they wanted to differentiate between vanilla TFS/Tibia client communication and new custom communications that the users may create - the developers made it so that you can send this opcode to tell the receiver basically
"I wanna use a custom opcode, and that one will be the one you read after this one". So that's what
0x32 means, and that's why it's always
the first thing you add when creating an ExtendedOpcode packet.
So, what is this
YOUR_OPCODE_HERE code supposed to be?
Well, it's up to you to put some unused opcode here, and make your own code logic as to what happens when the receiver receives a packet starting with this opcode. You should be able to either put a hex or dec number, I think it will read both just fine.
For the sake of this example, I used "10" here. The code looks like this so far:
Lua:
local packet = NetworkMessage()
packet:addByte(0x32) -- Extended Opcode (0x32 = 50 (in dec))
packet:addByte(0x0A) -- The Opcode of this Request (0x0A = 10 (in dec))
Note: ExtendedOpcodes do not conflict with inherent TFS Opcodes, so don't worry about conflicting between those two.
2. Fill your packet with desired data.
After creating the header part of the packet, now we're ready to start filling in some data.
Depending on what type of data you want to add, you must use an appropriate method. Here are the common ones.
Lua:
packet:addByte(byte) -- Add a new byte into the packet.
packet:addString(string) -- Add a new string into the packet.
packet:addU8(int), packet:addU16(int), packet:addU32(int) -- Add a new unsigned integer into the packet. Depending on the size of your number, choose a fitting size.
Let's say the value you wanna use is some number stored in a storage value of the player, we can put:
Lua:
packet:addU8(player:getStorageValue(yourstoragekey))
3. Send the packet to the other side when it's ready.
Use the following to send the packet:
Lua:
packet:sendToPlayer(player)
And promptly delete the local object from the memory after it has already been sent out with:
───────────────────────────────────────────────
We constructed a packet and sent it from one side, now we need to go over to the receiver side (in this example, client) and make sure to handle
what happens when the client receives our packet over there.
───────────────────────────────────────────────
4.
Prepare the landing pad
For organizational reasons, and keeping this guide shorter, let's create a new folder in the otclient/modules/ folder and call it exactly:
game_extendedprotocol. We'll keep all extendedOpcode handling stuff in there.
Now create a new .lua file in there and call it:
extended_protocol_handler. Paste this inside for now:
Lua:
-- Initialization and termination of the module, your standard OTC module loading stuff.
function init()
connect(g_game, {
onGameStart = onGameStart,
onGameEnd = onGameEnd
}, true)
end
function terminate()
disconnect(g_game, {
onGameStart = onGameStart,
onGameEnd = onGameEnd
})
end
-- Custom onGameStart call which will contain the registration of all of our custom opcodes, and which functions they call when received.
-- Without registering an opcode, the client won't be even trying to receive and read that signal, so you will be shooting blanks from the server side.
function onGameStart()
ProtocolGame.registerExtendedOpcode(10, receiveStorageFromServer) -- When extended opcode "10" is received, function called "receiveStorageFromServer" is called and some arguments are passed to it, including our packet's data.
end
-- Unregistering what we registered in onGameStart when the player leaves the game.
function onGameEnd()
ProtocolGame.unregisterExtendedOpcode(10, true)
end
--- Callbacks ---
-- Below, let's write all the functions which will handle incoming packets. Let's write the "receiveStorageFromServer" function.
I left some comments inside of there too, so that you understand what's going on in there. Now create a new .otmod file in there and call it:
extendedprotocol.otmod and paste this into it:
Code:
Module
name: game_extendedprotocol
description: Contains extended protocol related stuff.
reloadable: false
sandboxed: true
scripts: [extended_protocol_handler]
@onLoad: init()
@onUnload: terminate()
autoload: true
autoload-priority: 1001
To finish off, in game_interface/interface.otmod, add:
to the list of loaded modules.
5. We need to add a handler for our "0x0A" extended opcode now, into the lua script we just created.
Open the script, read the comments if you want, if not, find the part where it says "-- Callbacks" at the bottom, and let's start scripting below that.
Create a new function:
Lua:
function receiveStorageFromServer(protocol, opcode, packet)
end
As you can see, I have reserved 3 arguments in it, which are the ones that will be getting passed down to it by the client when opcode 0x0A is detected.
The "packet" argument is the one that interests us here, since that's our packet with data that's waiting to be read.
People sometimes call this "msg" (as in: message, networkmessage, etc.); doesn't matter. You know what it is.
To read the packet, we must read data in the EXACT same order that it was stuffed into the packet.
Once a piece of data has been read from the packet, the packet moves on to the next piece of data and waits for it to be read as well, and repeats this until the end is reached.
Remember, our packet looked like:
1. 0x32 Signal
2. 0x0A Signal
3. U8 integer
Since the client has already sniffed and read the
0x32 and
0x0A signals due to it being registered (
0x32 reading is registered in the client sources, and we registered the reading of
0x0A manually in the onGameStart() function in this script), the only bit of data left to read is the
U8 integer, which is our storage value. So we can just save it into some variable, and then do whatever we want with it.
Let's edit the new function to read that data and do something with it, so it would look like this:
Lua:
function receiveStorageFromServer(protocol, opcode, packet)
local myStorageValue = packet:getU8()
print("Received a storage value from the server: " .. myStorageValue)
end
Restart your server, restart your client, and then try executing the server-side function we created in the first 3 steps.
If everything is okay, in your client's terminal, you should see the message.
──────────────────────────────────────────────────────────────────────────────