• 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!

C++ Trade with NPC, nekiro downgrade 1.5 8.0

Bee King

Active Member
Joined
May 9, 2024
Messages
73
Reaction score
26
Here’s the translation:




I am using TFS 1.5 downgrade 8.0 from nekiro, and I want to add the NPC trade option, where the modal window opens and makes buying/selling with them easier...


I found something related here, but it’s a bit vague. If someone has already implemented this and knows the path I should follow, please help me! I've been trying for some time now.


I’m using only OTC, so I won’t have issues with debugging on the client side.
 
Last edited:
Solution
Many people asked me how to add NPC trade for TFS 1.5 7.72 and 8.0 as well. So, I decided to do it in a simple way. Here's the commit—be happy and enjoy it for the OTland community!

Just ignore the NPC XML and Lua part; I was testing and forgot to remove it before committing. So, yes, just follow the NPC modules and the rest of the CPP and H files, and your NPC trade will be working fine! :D

1741310399928.webp1741310408566.webp
Our friend Mateus Roberto will fix this soon... but let's be patient, after all, it's help without financial return hahaha. When he has some free time, he will definitely do it, don't worry, my friend... in the meantime, tell the players to select the "ignored cap" option in the trade modal.
It's complicated, there's always something to ruin what's good lol
 
View attachment 90786

I have a problem with the cap, otclient is not recognizing the correct capacity. I'm using TFS 1.5 downgrade nekiro 772!


OTClientv8/invetory.lua
LUA:
function onFreeCapacityChange(player, freeCapacity)
  if not freeCapacity then return end
  if freeCapacity > 99 then
    freeCapacity = math.floor(freeCapacity * 10) / 10
  end
  if freeCapacity > 999 then
    freeCapacity = math.floor(freeCapacity)
  end
  if freeCapacity > 99999 then
    freeCapacity = math.min(9999, math.floor(freeCapacity/1000)) .. "k"
  end
  capLabel:setText(tr('Cap') .. ':\n' .. freeCapacity * 100)
end

protocolgame.cpp
C++:
    msg.add<uint16_t>(player->getFreeCapacity() / 100);

Hey man, I just managed to solve this! Leave some reactions if it works for you.
My brother Mateus deserves the solution on this thread. :)

Server side: Go to protocolgame.cpp.

In the function
LUA:
void ProtocolGame::AddPlayerStats(NetworkMessage& msg)
change this:
Code:
msg.add<uint16_t>(player->getFreeCapacity() / 100);

to this:
Code:
msg.add<uint16_t>(player->getFreeCapacity());

Recompile source!

Client side: In otcv8 folder, go to modules/game_inventory/inventory.lua. In function
Code:
function onFreeCapacityChange(player, freeCapacity)
change this:
Code:
capLabel:setText(tr('Cap') .. ':\n' .. freeCapacity * 100)

to this:
Code:
capLabel:setText(tr('Cap') .. ':\n' .. freeCapacity)

Restart client and run new compiled ./tfs, and voila! Otclient and Npc Trade capacity should align.

If you should have a very different onFreeCapacityChange function than me in inventory.lua, this is my whole function after the fix, just in case someone needs it:

Code:
function onFreeCapacityChange(player, freeCapacity)
  if not freeCapacity then return end
  --if freeCapacity > 99 then
   -- freeCapacity = math.floor(freeCapacity * 10) / 10
  --end
  if freeCapacity > 999 then
    freeCapacity = math.floor(freeCapacity)
  end
  if freeCapacity > 99999 then
    freeCapacity = math.min(9999, math.floor(freeCapacity/1000)) .. "k"
  end
  capLabel:setText(tr('Cap') .. ':\n' .. freeCapacity)
end
 
Hey man, I just managed to solve this! Leave some reactions if it works for you.
My brother Mateus deserves the solution on this thread. :)

Server side: Go to protocolgame.cpp.

In the function
LUA:
void ProtocolGame::AddPlayerStats(NetworkMessage& msg)
change this:
Code:
msg.add<uint16_t>(player->getFreeCapacity() / 100);

to this:
Code:
msg.add<uint16_t>(player->getFreeCapacity());

Recompile source!

Client side: In otcv8 folder, go to modules/game_inventory/inventory.lua. In function
Code:
function onFreeCapacityChange(player, freeCapacity)
change this:
Code:
capLabel:setText(tr('Cap') .. ':\n' .. freeCapacity * 100)

to this:
Code:
capLabel:setText(tr('Cap') .. ':\n' .. freeCapacity)

Restart client and run new compiled ./tfs, and voila! Otclient and Npc Trade capacity should align.

If you should have a very different onFreeCapacityChange function than me in inventory.lua, this is my whole function after the fix, just in case someone needs it:

Code:
function onFreeCapacityChange(player, freeCapacity)
  if not freeCapacity then return end
  --if freeCapacity > 99 then
   -- freeCapacity = math.floor(freeCapacity * 10) / 10
  --end
  if freeCapacity > 999 then
    freeCapacity = math.floor(freeCapacity)
  end
  if freeCapacity > 99999 then
    freeCapacity = math.min(9999, math.floor(freeCapacity/1000)) .. "k"
  end
  capLabel:setText(tr('Cap') .. ':\n' .. freeCapacity)
end
He is correct, however, for example, the character has 1918.4 of REAL cap, but he only marks 191.84, the comma is skipping 1 space.
 
He is correct, however, for example, the character has 1918.4 of REAL cap, but he only marks 191.84, the comma is skipping 1 space.

Hey man, what happens for me, is that I get extra decimals as you say - but its still correct.
Before addition I had, in your case, 191 oz cap. But after addition in source and OTC, i got 191.84 - which I am completely fine with.

I use TFS 1.5 (7.72), don't know what you are using though and if it makes any difference.. Try my whole function if you havent already in inventory.lua!

Screenshot of my case below, and it seems to be real CAP, cause when i reach 0 i cant pick up anything else.

1741645848050.webp
 
Hey man, what happens for me, is that I get extra decimals as you say - but its still correct.
Before addition I had, in your case, 191 oz cap. But after addition in source and OTC, i got 191.84 - which I am completely fine with.

I use TFS 1.5 (7.72), don't know what you are using though and if it makes any difference.. Try my whole function if you havent already in inventory.lua!

Screenshot of my case below, and it seems to be real CAP, cause when i reach 0 i cant pick up anything else.

View attachment 90891
If you look at your database for example, if you have a 1000+ cap and a 1000- cap, there will be a difference when you talk to the NPC.

I am facing the same problem, and also I use downgrade nekiro 7.72
 
If you look at your database for example, if you have a 1000+ cap and a 1000- cap, there will be a difference when you talk to the NPC.

I am facing the same problem, and also I use downgrade nekiro 7.72

Ok I'll check! Feels weird though.

Cause if you think of what I have essentially done --> removed a factor of 1/100 in protocolgame.cpp and also removed a factor of *100 in otclient.

Mathematically, there should be no difference. 1/100 * 100 = 100/100 = 1.
 
Ok I'll check! Feels weird though.

Cause if you think of what I have essentially done --> removed a factor of 1/100 in protocolgame.cpp and also removed a factor of *100 in otclient.

Mathematically, there should be no difference. 1/100 * 100 = 100/100 = 1.
That's what I thought too, I even thought about changing the way capfree is used within NPC TRADE but I haven't done any testing yet, if you have an update, I'll be waiting
 
That's what I thought too, I even thought about changing the way capfree is used within NPC TRADE but I haven't done any testing yet, if you have an update, I'll be waiting

Hey Guys, Forkz was right - approach above written by me for the NPC Trade Capacity display only works until a certain number of FreeCap, then it bugs out.

I have found the solution tho, sharing it now. Please test and feedback should it not be correct, it checks out all my tests.

Server side: Go to protocolgame.cpp, change back to this if you changed it already:

LUA:
msg.add<uint16_t>(player->getFreeCapacity() / 100);

Recompile source!

Client side: In otcv8 folder, go to modules/game_inventory/inventory.lua and use this initial function:


Code:
function onFreeCapacityChange(player, freeCapacity)
  if not freeCapacity then return end
  --if freeCapacity > 99 then
   -- freeCapacity = math.floor(freeCapacity * 10) / 10
  --end
  if freeCapacity > 999 then
    freeCapacity = math.floor(freeCapacity)
  end
  if freeCapacity > 99999 then
    freeCapacity = math.min(9999, math.floor(freeCapacity/1000)) .. "k"
  end
  capLabel:setText(tr('Cap') .. ':\n' .. freeCapacity * 100)
end

Now for the actual fix, people that have not done what I suggested above and came to this comment directly, just go to modules/game_npctrade/npctrade.lua and change this:

Code:
function onFreeCapacityChange(localPlayer, freeCapacity, oldFreeCapacity)
  playerFreeCapacity = freeCapacity

  if npcWindow:isVisible() then
    refreshPlayerGoods()
  end
end

for this:


Code:
function onFreeCapacityChange(localPlayer, freeCapacity, oldFreeCapacity)
  playerFreeCapacity = freeCapacity * 100

  if npcWindow:isVisible() then
    refreshPlayerGoods()
  end
end

Restart client and you're good to go!

//Zeke
 
Hey Guys, Forkz was right - approach above written by me for the NPC Trade Capacity display only works until a certain number of FreeCap, then it bugs out.

I have found the solution tho, sharing it now. Please test and feedback should it not be correct, it checks out all my tests.

Server side: Go to protocolgame.cpp, change back to this if you changed it already:

LUA:
msg.add<uint16_t>(player->getFreeCapacity() / 100);

Recompile source!

Client side: In otcv8 folder, go to modules/game_inventory/inventory.lua and use this initial function:


Code:
function onFreeCapacityChange(player, freeCapacity)
  if not freeCapacity then return end
  --if freeCapacity > 99 then
   -- freeCapacity = math.floor(freeCapacity * 10) / 10
  --end
  if freeCapacity > 999 then
    freeCapacity = math.floor(freeCapacity)
  end
  if freeCapacity > 99999 then
    freeCapacity = math.min(9999, math.floor(freeCapacity/1000)) .. "k"
  end
  capLabel:setText(tr('Cap') .. ':\n' .. freeCapacity * 100)
end

Now for the actual fix, people that have not done what I suggested above and came to this comment directly, just go to modules/game_npctrade/npctrade.lua and change this:

Code:
function onFreeCapacityChange(localPlayer, freeCapacity, oldFreeCapacity)
  playerFreeCapacity = freeCapacity

  if npcWindow:isVisible() then
    refreshPlayerGoods()
  end
end

for this:


Code:
function onFreeCapacityChange(localPlayer, freeCapacity, oldFreeCapacity)
  playerFreeCapacity = freeCapacity * 100

  if npcWindow:isVisible() then
    refreshPlayerGoods()
  end
end

Restart client and you're good to go!

//Zeke
Thanks for your contribution, it worked!
 
Hey Guys, Forkz was right - approach above written by me for the NPC Trade Capacity display only works until a certain number of FreeCap, then it bugs out.

I have found the solution tho, sharing it now. Please test and feedback should it not be correct, it checks out all my tests.

Server side: Go to protocolgame.cpp, change back to this if you changed it already:

LUA:
msg.add<uint16_t>(player->getFreeCapacity() / 100);

Recompile source!

Client side: In otcv8 folder, go to modules/game_inventory/inventory.lua and use this initial function:


Code:
function onFreeCapacityChange(player, freeCapacity)
  if not freeCapacity then return end
  --if freeCapacity > 99 then
   -- freeCapacity = math.floor(freeCapacity * 10) / 10
  --end
  if freeCapacity > 999 then
    freeCapacity = math.floor(freeCapacity)
  end
  if freeCapacity > 99999 then
    freeCapacity = math.min(9999, math.floor(freeCapacity/1000)) .. "k"
  end
  capLabel:setText(tr('Cap') .. ':\n' .. freeCapacity * 100)
end

Now for the actual fix, people that have not done what I suggested above and came to this comment directly, just go to modules/game_npctrade/npctrade.lua and change this:

Code:
function onFreeCapacityChange(localPlayer, freeCapacity, oldFreeCapacity)
  playerFreeCapacity = freeCapacity

  if npcWindow:isVisible() then
    refreshPlayerGoods()
  end
end

for this:


Code:
function onFreeCapacityChange(localPlayer, freeCapacity, oldFreeCapacity)
  playerFreeCapacity = freeCapacity * 100

  if npcWindow:isVisible() then
    refreshPlayerGoods()
  end
end

Restart client and you're good to go!

//Zeke
This helped me immensly too.
Absolute machine, great work and thanks for sharing!
 
Many people asked me how to add NPC trade for TFS 1.5 7.72 and 8.0 as well. So, I decided to do it in a simple way. Here's the commit—be happy and enjoy it for the OTland community!

Just ignore the NPC XML and Lua part; I was testing and forgot to remove it before committing. So, yes, just follow the NPC modules and the rest of the CPP and H files, and your NPC trade will be working fine! :D

View attachment 90782View attachment 90784
This is great!
Although, for anyone using this in Nekiro Downgrade 7.72 in conjunction with OTClient mehah or OTClient v8 you might want to send the fluid type data through protocol without converting to client side subtype. For example,


C++:
void ProtocolGame::AddShopItem(NetworkMessage& msg, const ShopInfo& item)
{
    const ItemType& it = Item::items[item.itemId];
    msg.add<uint16_t>(it.clientId);

    if (it.isSplash() || it.isFluidContainer()) {
        msg.addByte(serverFluidToClient(item.subType)); // <- This is already handled client side
    } else {
        msg.addByte(0x00);
    }

    msg.addString(item.realName);
    msg.add<uint32_t>(it.weight);
    msg.add<uint32_t>(item.buyPrice);
    msg.add<uint32_t>(item.sellPrice);
}

Therefor the approate way to send this would be
C++:
if (it.isSplash() || it.isFluidContainer()) {
        msg.addByte(item.subType); // <--
    } else {
        msg.addByte(0x00);
    }

I was having an issue where the trade window displayed the wrong fluid type. So if you are having a similar issue then you might want to test and consider applying this.

EDIT:

C++:
void Game::playerPurchaseItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint8_t amount,
                              bool ignoreCap/* = false*/, bool inBackpacks/* = false*/)
{
    if (amount == 0 || amount > 100) {
        return;
    }

    Player* player = getPlayerByID(playerId);
    if (!player) {
        return;
    }

    int32_t onBuy, onSell;

    Npc* merchant = player->getShopOwner(onBuy, onSell);
    if (!merchant) {
        return;
    }

    const ItemType& it = Item::items.getItemIdByClientId(spriteId);
    if (it.id == 0) {
        return;
    }

    uint8_t subType;
    if (it.isSplash() || it.isFluidContainer()) {
        subType = clientFluidToServer(count);
    } else {
        subType = count;
    }

    if (!player->hasShopItemForSale(it.id, subType)) {
        return;
    }

    merchant->onPlayerTrade(player, onBuy, it.id, subType, amount, ignoreCap, inBackpacks);
}

This is how the server handles the data received from client side through protocol & again, it is trying to convert the subtype but the client already handles that.


C++:
if (it.isSplash() || it.isFluidContainer()) {
        // subType = clientFluidToServer(count);
        subType = count;
} else {
        subType = count;
}
Without this change I found it impossible to be able to buy items from the npc that were fluid type.
I believe it is better to make these changes server side since we don't necessarily upgrade our engine for old protocol projects, but it's a lot likely to update the client which is why I don't recommend changing the way the client handles the data.
 
Last edited:
This is great!
Although, for anyone using this in Nekiro Downgrade 7.72 in conjunction with OTClient mehah or OTClient v8 you might want to send the fluid type data through protocol without converting to client side subtype. For example,


C++:
void ProtocolGame::AddShopItem(NetworkMessage& msg, const ShopInfo& item)
{
    const ItemType& it = Item::items[item.itemId];
    msg.add<uint16_t>(it.clientId);

    if (it.isSplash() || it.isFluidContainer()) {
        msg.addByte(serverFluidToClient(item.subType)); // <- This is already handled client side
    } else {
        msg.addByte(0x00);
    }

    msg.addString(item.realName);
    msg.add<uint32_t>(it.weight);
    msg.add<uint32_t>(item.buyPrice);
    msg.add<uint32_t>(item.sellPrice);
}

Therefor the approate way to send this would be
C++:
if (it.isSplash() || it.isFluidContainer()) {
        msg.addByte(item.subType); // <--
    } else {
        msg.addByte(0x00);
    }

I was having an issue where the trade window displayed the wrong fluid type. So if you are having a similar issue then you might want to test and consider applying this.

EDIT:

C++:
void Game::playerPurchaseItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint8_t amount,
                              bool ignoreCap/* = false*/, bool inBackpacks/* = false*/)
{
    if (amount == 0 || amount > 100) {
        return;
    }

    Player* player = getPlayerByID(playerId);
    if (!player) {
        return;
    }

    int32_t onBuy, onSell;

    Npc* merchant = player->getShopOwner(onBuy, onSell);
    if (!merchant) {
        return;
    }

    const ItemType& it = Item::items.getItemIdByClientId(spriteId);
    if (it.id == 0) {
        return;
    }

    uint8_t subType;
    if (it.isSplash() || it.isFluidContainer()) {
        subType = clientFluidToServer(count);
    } else {
        subType = count;
    }

    if (!player->hasShopItemForSale(it.id, subType)) {
        return;
    }

    merchant->onPlayerTrade(player, onBuy, it.id, subType, amount, ignoreCap, inBackpacks);
}

This is how the server handles the data received from client side through protocol & again, it is trying to convert the subtype but the client already handles that.


C++:
if (it.isSplash() || it.isFluidContainer()) {
        // subType = clientFluidToServer(count);
        subType = count;
} else {
        subType = count;
}
Without this change I found it impossible to be able to buy items from the npc that were fluid type.
I believe it is better to make these changes server side since we don't necessarily upgrade our engine for old protocol projects, but it's a lot likely to update the client which is why I don't recommend changing the way the client handles the data.

Hence the reason i threw this fluid source thing out of the window first thing I did on my server. :) it's so incredibly bad.

All my fluids are custom made vials with other item ID's and not the 2006, 1,2,3,4,... and so on.
So I do not have to deal with this. Added all my custom fluids to the NPC's and voila!

//Zeke
 
Hence the reason i threw this fluid source thing out of the window first thing I did on my server. :) it's so incredibly bad.

All my fluids are custom made vials with other item ID's and not the 2006, 1,2,3,4,... and so on.
So I do not have to deal with this. Added all my custom fluids to the NPC's and voila!

//Zeke
x2, I did this as well.
 
Back
Top