• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

Trying to receive a storage value from Tfs > Otclient

Nostalgian

Member
Joined
Mar 17, 2018
Messages
66
Reaction score
15
Ok so I'm trying to create a new skill menu for OTclient for some custom skills.

All of my skills work great, written in lua. The skill levels are using storage values. Now comes the time when I am trying to connect and display this information on Otclient.

Holy crap I am confused. I have created a new basic menu, and I think I know how to link it to the value, but my problem is that I can't figure out how to get the storage value from tfs to otclient. I know that I need to use extended opcodes, but with all the research I have done, there seems to be some key thing missing that causes it not to work. Maybe I'm overlooking it, I have no idea.

Here is what I have, why is it not working?

A new creaturescript:
customSkill.lua
Code:
function onExtendedOpcode(cid, opcode, buffer)
    local player = Player(cid)
    local skillLevel = player:getStorageValue(player, 20020)
    player:sendExtendedOpcode(cid, 52, skillLevel)
    return true
end

Register it creaturescripts:
Code:
<event type="extendedopcode" name="skillLevelOpcode" script="customSkill.lua" />

Then i created a new module in otclient by copy/pasting the existing skills.lua and added this in the init function:
Code:
ProtocolGame.registerExtendedOpcode(52, skillLevel)

Then this in the terminate function:
Code:
ProtocolGame.unregisterExtendedOpcode(52)

Then added this function at the bottom of the same file:
Code:
function skillLevel(protocol, opcode, buffer)
   print(buffer)
end


It does absolutely nothing. No errors, does not display the storage value as intended in the OTC terminal....nothing. its like this files dont exist.

What am I doing wrong?
 
Solution
Ok so here is where I am now:

I created a new lib file:
opcodeFunctions.lua
Code:
function Player.sendCustomStorageUpdate(self)
    local packet = NetworkMessage()
    packet:addByte(0x32) -- Extended Opcode (0x32 = 50 (in dec))
    packet:addByte(0x37) -- The Opcode of this Request (0x37 = 55 (in dec))
    packet:addByte(self:getStorageValue(20020))
    packet:sendToPlayer(self)
    packet:delete()
end

Then created a creaturescript and registered it:
opcodeTest.lua
Code:
function onLogin(player)
    player:sendCustomStorageUpdate()
    return true
end

It appears to be sending the opcode, but my client is not able to read it for some reason?
I am getting this error:
Code:
ERROR: ProtocolGame parse message exception (1 bytes unread...
Did you ever figure this out? I'm struggling with the same concept.

Itd be great to see a full working example
 
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:
Lua:
packet:delete()

───────────────────────────────────────────────
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:
Code:
- game_extendedprotocol
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.

──────────────────────────────────────────────────────────────────────────────
 
Wow. Thank you for taking the time to explain that. It is very clear and concise, and should be stickied on the otclient forum.

I had a good idea of the theory behind how it works, but I wasn't sure how to structure it exactly.

Thank you!
 
Ok, so i am getting an error: "attempt to call method 'addU8' (a nil value)"

Here is how I set it up.. I'm sure I'm misunderstanding something! :)


In Tfs > Data > Creaturescripts > scripts:
Code:
function onExtendedOpcode(cid, opcode, buffer)
    local player = Player(cid)
    local packet = NetworkMessage()
    packet:addByte(0x32) -- Extended Opcode (0x32 = 50 (in dec))
    packet:addByte(0x37) -- The Opcode of this Request (0x34 = 55 (in dec))
    
    packet:addU8(player:getStorageValue(player, 20020))
    
    packet:sendToPlayer(player)
    
    packet:delete()

    return true
end
(I changed the opcode to 55, because it said 10 was in use when I started OTC)


No errors on the OTC side yet, because my server ain't sending it due to this error :)

I don't understand why its returning nil.
 
Ok, so i am getting an error: "attempt to call method 'addU8' (a nil value)"

Here is how I set it up.. I'm sure I'm misunderstanding something! :)


In Tfs > Data > Creaturescripts > scripts:
Code:
function onExtendedOpcode(cid, opcode, buffer)
    local player = Player(cid)
    local packet = NetworkMessage()
    packet:addByte(0x32) -- Extended Opcode (0x32 = 50 (in dec))
    packet:addByte(0x37) -- The Opcode of this Request (0x34 = 55 (in dec))
   
    packet:addU8(player:getStorageValue(player, 20020))
   
    packet:sendToPlayer(player)
   
    packet:delete()

    return true
end
(I changed the opcode to 55, because it said 10 was in use when I started OTC)


No errors on the OTC side yet, because my server ain't sending it due to this error :)

I don't understand why its returning nil.

It's because your server does not have an "addU8" method, so it's trying to call a method which doesn't exist.
However, "addByte" and "addU8" is the same thing, if you think about it, no?
Byte = 8 Bits

So you can just use addByte in that case. Keep in mind 8bit can only store values from 0 to 255. If you are retrieving some bigger number from that storage, use a larger msg type.
 
Ok, so how do I grab that value in otc then?

If I get rid of this:
Code:
packet:addU8(player:getStorageValue(player, 20020))

How do I declare that I want byte 0x37 to represent player storage value 20020?
 
Ok, so how do I grab that value in otc then?

If I get rid of this:
Code:
packet:addU8(player:getStorageValue(player, 20020))

How do I declare that I want byte 0x37 to represent player storage value 20020?

Well, first, byte 0x37 does not represent that storage value, it represents the number 55 (0x37 = 55 in dec), which is your chosen extended opcode that's in the packet. You still have to add the storage value as a new byte, and that's what that addU8 line does - but addU8 doesn't work for you, so just use addByte because addByte and addU8 are interchangeable, it's the same thing pretty much. So change your:

packet:addU8(player:getStorageValue(player, 20020))
to
packet:addByte(player:getStorageValue(player, 20020))
 
Well, first, byte 0x37 does not represent that storage value, it represents the number 55 (0x37 = 55 in dec), which is your chosen extended opcode that's in the packet. You still have to add the storage value as a new byte, and that's what that addU8 line does - but addU8 doesn't work for you, so just use addByte because addByte and addU8 are interchangeable, it's the same thing pretty much. So change your:

packet:addU8(player:getStorageValue(player, 20020))
to
packet:addByte(player:getStorageValue(player, 20020))
I think he got confused with >"Keep in mind 8bit can only store values from 0 to 255. If you are retrieving some bigger number from that storage, use a larger msg type."
The confusing part being that his storagevalue is 20020 and exceeds the value 255.
Just speculating though.
 
I think he got confused with >"Keep in mind 8bit can only store values from 0 to 255. If you are retrieving some bigger number from that storage, use a larger msg type."
The confusing part being that his storagevalue is 20020 and exceeds the value 255.
Just speculating though.

Ahh, it is entirely possible. 😶

I was referring to the size of the number you retrieve as a return from the storage via player:getStorageValue(20020).
Storage values are by default (-1) unless you modify them, and since this method addByte/addU8 uses an unsigned integer (this can also depend on your sources), it might not work nicely with a negative value.

Also it won't work for anything over 255.

Also, I noticed you wrote player:getStorageValue(player, 20020) instead of player:getStorageValue(20020). That's probably a typo or an oversight, just thought I'd point it out.
 
Ahhh. I was making that way overcomplicated! Haha

Thank you for taking the time to explain it. I'll try and plug it in when I get home from work today!

(And yes, I got confused about that, but once again.... I just way overcomplicated it haha)
 
Ok, still a bit stuck, but making progress :)

Now my server is sending the message over to the client, but the client is still not reading it properly.

I am getting this error:
Code:
ERROR: ProtocolGame parse message exception (1 bytes unread, last opcode is 50, prev opcode is -1): InputMessage eof reached

Maybe I am wrong, but I suspect it has to do with this part of the client script:
Code:
function receiveStorageFromServer(protocol, opcode, packet)
    local myStorageValue = packet:getU8()
    print("Received a storage value from the server: " .. myStorageValue)
end

since I am now using:
Code:
packet:addByte(player:getStorageValue(20020))
instead of packet:addU8


But I can't figure out how to grab that exactly. I tried
Code:
local myStorageValue = packet:getByte()
but I get the same error.

I am not sure where to find what methods are available to me such as this. :p

Thank you for your patience. This crap is hard to learn. :)
 
Ahh yea I didn't think of that.

In the related script that is using this storage value on the server, it checks for nil and fixes it in the beginning.... but that only happens once a player uses that skill the first time.

So I need to change that value away from nil somehow. Can I do that inside the opcode function before I send the packet?


Edit: I just remembered, the character that I am logging into trying to make this opcode work has in fact used said skill, and upon checking it, it is not nil. So, it must be something else?
 
Ahh yea I didn't think of that.

In the related script that is using this storage value on the server, it checks for nil and fixes it in the beginning.... but that only happens once a player uses that skill the first time.

So I need to change that value away from nil somehow. Can I do that inside the opcode function before I send the packet?


Edit: I just remembered, the character that I am logging into trying to make this opcode work has in fact used said skill, and upon checking it, it is not nil. So, it must be something else?
Storages are never nil, -1 by default. You can do something like:
Lua:
packet:addByte(math.max(0, player:getStorageValue(20020)))
Just make sure your storage value doesn't exceed 255, otherwise use addU16.
 
Ok, its been a bit, but I have been trying to make this work here and there ever since. No luck.

I'm going to recap here on what I have at the moment, and the error I am getting:

My extendedopcode.lua file looks like this:
Code:
function onExtendedOpcode(player, opcode, buffer)
    local packet = NetworkMessage()
    packet:addByte(0x32) -- Extended Opcode (0x32 = 50 (in dec))
    packet:addByte(0x37) -- The Opcode of this Request (0x37 = 55 (in dec))
  
    packet:addByte(player:getStorageValue(20020))
  
    packet:sendToPlayer(player)
  
    packet:delete()

end

I have also tried with:
Code:
packet:addByte(math.max(0, player:getStorageValue(20020)))

On the client side I have extended_protocol_handler.lua:
Code:
-- 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(55, receiveStorageFromServer) -- When extended opcode "55" 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(55, true)
end

--- Callbacks ---
-- Below, let's write all the functions which will handle incoming packets. Let's write the "receiveStorageFromServer" function.

function receiveStorageFromServer(protocol, opcode, buffer)
    print(buffer)
end

I have also tried this function:
Code:
function receiveStorageFromServer(protocol, opcode, packet)
    local myStorageValue = packet:getByte()
    print("Received a storage value from the server: " .. myStorageValue)
end

But the result is still the same. Once I login with my character, I get this error in the OTC terminal:
Code:
ERROR: ProtocolGame parse message exception (1 bytes unread, last opcode is 50, prev opcode is -1): InputMessage eof reached

I have legitimately read every thread I could find on here about opcodes and tried to incorporate everything I could think of. I have no idea what to do next :)

Thank you

Edit: I forgot to add that the storage value I'm sending is not -1. I wrote a script to manually change it, and it is currently 5. (just to be sure I'm not trying to send a -1 value)

What am I missing?!
 
Ok, its been a bit, but I have been trying to make this work here and there ever since. No luck.

I'm going to recap here on what I have at the moment, and the error I am getting:

My extendedopcode.lua file looks like this:
Code:
function onExtendedOpcode(player, opcode, buffer)
    local packet = NetworkMessage()
    packet:addByte(0x32) -- Extended Opcode (0x32 = 50 (in dec))
    packet:addByte(0x37) -- The Opcode of this Request (0x37 = 55 (in dec))
 
    packet:addByte(player:getStorageValue(20020))
 
    packet:sendToPlayer(player)
 
    packet:delete()

end

I have also tried with:
Code:
packet:addByte(math.max(0, player:getStorageValue(20020)))

On the client side I have extended_protocol_handler.lua:
Code:
-- 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(55, receiveStorageFromServer) -- When extended opcode "55" 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(55, true)
end

--- Callbacks ---
-- Below, let's write all the functions which will handle incoming packets. Let's write the "receiveStorageFromServer" function.

function receiveStorageFromServer(protocol, opcode, buffer)
    print(buffer)
end

I have also tried this function:
Code:
function receiveStorageFromServer(protocol, opcode, packet)
    local myStorageValue = packet:getByte()
    print("Received a storage value from the server: " .. myStorageValue)
end

But the result is still the same. Once I login with my character, I get this error in the OTC terminal:
Code:
ERROR: ProtocolGame parse message exception (1 bytes unread, last opcode is 50, prev opcode is -1): InputMessage eof reached

I have legitimately read every thread I could find on here about opcodes and tried to incorporate everything I could think of. I have no idea what to do next :)

Thank you

Edit: I forgot to add that the storage value I'm sending is not -1. I wrote a script to manually change it, and it is currently 5. (just to be sure I'm not trying to send a -1 value)

What am I missing?!

Well for a start, your understanding of the event onExtendedOpcode seems to be wrong.

onExtendedOpcode is a server-side creature event that fires every time the server receives an extended opcode from the client.

What you should be doing is just sending your newly created packet from the server to the client. But instead, you placed the code that does that into the event which handles incoming traffic from the client.

Your script does the following:

Whenever client sends ANY opcode to the server - create a new packet that uses extendedopcode 0x37, add player's storage value to it, and send + delete the packet.

This is obviously wrong. onExtendedOpcode event should be used to deal with the opposite side of the traffic (client->server). Whenever a new packet sent via opcode 0x32 in the incoming traffic appears, onExtendedOpcode will fire.

The code for generating a packet is fine, but usually what you'd do with it is - place it somewhere from where you call it when you need it.

For example, inside a custom function.
Lua:
function Player.sendCustomStorageUpdate(self)
    local packet = NetworkMessage()
    packet:addByte(0x32) -- Extended Opcode (0x32 = 50 (in dec))
    packet:addByte(0x37) -- The Opcode of this Request (0x37 = 55 (in dec))
    packet:addByte(player:getStorageValue(20020))
    packet:sendToPlayer(player)
    packet:delete()
end

And then call it with player:sendCustomStorageUpdate() when you wanna do it. Like, during an action script or when a talkaction command is spoken by player, etc.

To get back to the onExtendedOpcode point - if you are expecting some response from the client, this is how it would need to look like:

Lua:
function onExtendedOpcode(player, opcode, msg)
    -- Some extended opcode was received.
    if opcode == 1 then -- Check if it was opcode "1".
        print("Received message from client: " .. msg) -- The 'msg' will be the buffer of the packet.
                                                       -- Usually, in vanilla TFS, we can expect this message to be a single string. 
                                                       -- Source edits are required if you wanna make extended opcode packets contain anything more than a single string.
    end
end

So don't mix them up. It's one thing when server is sending something to the client, it's another when client is sending something to the server.
 
Ahhhhhh. That makes perfect sense. You're right, my understanding was wrong.

I am intending on use this value to display a skill level in a skill window.
I see how to set it up and call it from like an action script or talk action, but what about using it to display a players skill level in a custom skill window?

In that case, the player wouldn't be doing anything to call it, right? When the player logs in, it should just automatically send the info.

Thank you for your help, I'm starting to get it!
 
Ahhhhhh. That makes perfect sense. You're right, my understanding was wrong.

I am intending on use this value to display a skill level in a skill window.
I see how to set it up and call it from like an action script or talk action, but what about using it to display a players skill level in a custom skill window?

In that case, the player wouldn't be doing anything to call it, right? When the player logs in, it should just automatically send the info.

Thank you for your help, I'm starting to get it!

Yes you're right. You have to imagine every scenario in which you'd want to update the player's client with a new value for this skill.
For skills, this will usually be:
  • As soon as player logs in.
  • Every time the skill value is increased (e.g. player advances by a level in this skill)
  • Every time the skill value is decreased (e.g. player dies and loses a skill level)
So you'll need probably 2 places to do this - in an onLogin event and onAdvance event.
 
Back
Top