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

TFS 1.X+ Trade window consider another item as money

lukatxd

Active Member
Joined
Dec 9, 2010
Messages
85
Solutions
1
Reaction score
39
TFS 1.3 downgraded by Nekiro (8.6)
I've downloaded a script to make users pay for items using another item as currency. However they can only buy those items when they also have actual money on them.
The actual trade works fine, it does remove my custom sword and adds a boots of haste to my backpack.

1597177308674.png1597177325538.png

I've added this to npc.lua
Lua:
function Player.getTokenCurrency(self)
    return self:getItemCount(12662)
end

So where do I need to change to consider the token currency in the window? I've already found uses of player:getTotalMoney() but it is used when confirming the buy action, not when loading the trade window.

EDIT:
Apparently there's this in the sources (didnt find by myself, found in another forum)
It is not considering the player balance, so it is consistent with what I'm seeing. Even though inside the buy script it checks for player balance.

C++:
void ProtocolGame::sendSaleItemList(const std::list<ShopInfo>& shop)
{
    NetworkMessage msg;
    msg.addByte(0x7B);
    msg.add<uint32_t>(player->getMoney());

    std::map<uint16_t, uint32_t> saleMap;
 
Last edited:
Solution
you could use this as base for changes:
I found threads similar to that, but I've read that there were limits to values in the houses of millions. Plus I believe some crazy gold hoarder could eventually buy token items through gold coins.

I've messed in the sources and (god forgive me) copy/pasted the openShopWindow code to open a token shop and it KINDA worked:
1597188763397.png
However if I pick up money from the floor or throw some gold coins from my backpack the shop window will reload my total money instead of keeping on reading my tokens.
I will keep reading through the sources to make this work flawlessly. It takes me a lot of effort to do it because I'm not used to C++...
you could use this as base for changes:
I found threads similar to that, but I've read that there were limits to values in the houses of millions. Plus I believe some crazy gold hoarder could eventually buy token items through gold coins.

I've messed in the sources and (god forgive me) copy/pasted the openShopWindow code to open a token shop and it KINDA worked:
1597188763397.png
However if I pick up money from the floor or throw some gold coins from my backpack the shop window will reload my total money instead of keeping on reading my tokens.
I will keep reading through the sources to make this work flawlessly. It takes me a lot of effort to do it because I'm not used to C++ or pointers.

EDIT: And it also only counts tokens that I own in the backpack.. will try to figure how to make it look through depot.
Post automatically merged:

When I move the custom items I've added to my server, the method sendSaleItemList is not called, but when I move crystal coins, or even a boots of haste, that method is called. I need it to be called independent of the item I move because it is the one that updates the "money" in the shop window.

1597194142759.png

1597194379015.png

@Evil Puncker please help a fellow monkey, huehue
Post automatically merged:

Okay so I managed to do it XD
I'll share it, even though I know the community doesn't approve too much of this "donate" market.

PLEASE BEWARE, if you already have made changes to these source files by yourself, do not copy/paste it mindlessly.
This is using the 1.3 TFS downgraded by Nekiro to run Tibia 8.60.

first:
find the method called sendSaleItemList and change it to this:
C++:
void ProtocolGame::sendSaleItemList(const std::list<ShopInfo>& shop)
{
    NetworkMessage msg;
    msg.addByte(0x7B);
    if (player->isDonateShop) {
        //msg.add<uint32_t>(player->getItemTypeCount(ITEM_DONATE_TOKEN)+ player->getDepotChest(1, false)->getItemTypeCount(ITEM_DONATE_TOKEN));
        msg.add<uint32_t>(player->getItemTypeCount(ITEM_DONATE_TOKEN));
    }
    else {
        msg.add<uint32_t>(player->getMoney() + player->getBankBalance());
    }

    std::map<uint16_t, uint32_t> saleMap;

    if (shop.size() <= 5) {
        // For very small shops it's not worth it to create the complete map
        for (const ShopInfo& shopInfo : shop) {
            if (shopInfo.sellPrice == 0) {
                continue;
            }

            int8_t subtype = -1;

            const ItemType& itemType = Item::items[shopInfo.itemId];
            if (itemType.hasSubType() && !itemType.stackable) {
                subtype = (shopInfo.subType == 0 ? -1 : shopInfo.subType);
            }

            uint32_t count = player->getItemTypeCount(shopInfo.itemId, subtype);
            if (count > 0) {
                saleMap[shopInfo.itemId] = count;
            }
        }
    } else {
        // Large shop, it's better to get a cached map of all item counts and use it
        // We need a temporary map since the finished map should only contain items
        // available in the shop
        std::map<uint32_t, uint32_t> tempSaleMap;
        player->getAllItemTypeCount(tempSaleMap);

        // We must still check manually for the special items that require subtype matches
        // (That is, fluids such as potions etc., actually these items are very few since
        // health potions now use their own ID)
        for (const ShopInfo& shopInfo : shop) {
            if (shopInfo.sellPrice == 0) {
                continue;
            }

            int8_t subtype = -1;

            const ItemType& itemType = Item::items[shopInfo.itemId];
            if (itemType.hasSubType() && !itemType.stackable) {
                subtype = (shopInfo.subType == 0 ? -1 : shopInfo.subType);
            }

            if (subtype != -1) {
                uint32_t count;
                if (!itemType.isFluidContainer() && !itemType.isSplash()) {
                    count = player->getItemTypeCount(shopInfo.itemId, subtype); // This shop item requires extra checks
                } else {
                    count = subtype;
                }

                if (count > 0) {
                    saleMap[shopInfo.itemId] = count;
                }
            } else {
                std::map<uint32_t, uint32_t>::const_iterator findIt = tempSaleMap.find(shopInfo.itemId);
                if (findIt != tempSaleMap.end() && findIt->second > 0) {
                    saleMap[shopInfo.itemId] = findIt->second;
                }
            }
        }
    }

    uint8_t itemsToSend = std::min<size_t>(saleMap.size(), std::numeric_limits<uint8_t>::max());
    msg.addByte(itemsToSend);

    uint8_t i = 0;
    for (std::map<uint16_t, uint32_t>::const_iterator it = saleMap.begin(); i < itemsToSend; ++it, ++i) {
        msg.addItemId(it->first);
        msg.addByte(std::min<uint32_t>(it->second, std::numeric_limits<uint8_t>::max()));
    }

    writeToOutputBuffer(msg);
}

then, search for the enum ITEM_CRYSTAL_COIN and add the enum ITEM_DONATE_TOKEN below it:
C++:
//in my server, 12661 is a custom token item
// change it according to your needs
ITEM_DONATE_TOKEN = 12661,

after that, search for the method updateSaleShopList and change it to this:
C++:
bool Player::updateSaleShopList(const Item* item)
{
    uint16_t itemId = item->getID();
    if (itemId != ITEM_GOLD_COIN && itemId != ITEM_PLATINUM_COIN && itemId != ITEM_CRYSTAL_COIN) {
        auto it = std::find_if(shopItemList.begin(), shopItemList.end(), [itemId](const ShopInfo& shopInfo) { return shopInfo.itemId == itemId && shopInfo.sellPrice != 0; });
        if (it == shopItemList.end()) {
            const Container* container = item->getContainer();

            if (item->getID() == ITEM_DONATE_TOKEN) {
                goto reloadItemsAndGetOut;
            }
            if (!container) {
                return false;
            }

            const auto& items = container->getItemList();
            return std::any_of(items.begin(), items.end(), [this](const Item* containerItem) {
                return updateSaleShopList(containerItem);
            });
        }
    }

    reloadItemsAndGetOut:
    if (client) {
        client->sendSaleItemList(shopItemList);
    }
    return true;
}

then, change openShopWindow :
C++:
void Player::openShopWindow(const std::list<ShopInfo>& shop)
{
    isDonateShop = false;
    doShopRoutines(shop);
}

void Player::openDonateShopWindow(const std::list<ShopInfo>& shop)
{
    isDonateShop = true;
    doShopRoutines(shop);
}

void Player::doShopRoutines(const std::list<ShopInfo>& shop) {
    shopItemList = shop;
    sendShop();
    sendSaleItemList();
}


Then go to player.h and change the field shopItemList to this:
C++:
        std::list<ShopInfo> shopItemList;
        bool isDonateShop = false;
and change openShopWindow to this:
C++:
        void openShopWindow(const std::list<ShopInfo>& shop);
        void openDonateShopWindow(const std::list<ShopInfo>& shop);
        void doShopRoutines(const std::list<ShopInfo>& shop);

After that, go to npc.cpp and search for "openShopWindow" and change the whole line for this:
C++:
    lua_register(luaState, "openShopWindow", NpcScriptInterface::luaOpenShopWindow);
    lua_register(luaState, "openDonateShopWindow", NpcScriptInterface::luaOpenDonateShopWindow);

Now go to your data/npc folder and create the donate.lua :
Lua:
local keywordHandler = KeywordHandler:new()
local npcHandler = NpcHandler:new(keywordHandler)
NpcSystem.parseParameters(npcHandler)
local talkState = {}
function onCreatureAppear(cid) npcHandler:onCreatureAppear(cid) end
function onCreatureDisappear(cid) npcHandler:onCreatureDisappear(cid) end
function onCreatureSay(cid, type, msg) npcHandler:onCreatureSay(cid, type, msg) end
function onThink() npcHandler:onThink() end

function creatureSayCallback(cid, type, msg)
if(not npcHandler:isFocused(cid)) then
return false
end
local talkUser = NPCHANDLER_CONVbehavior == CONVERSATION_DEFAULT and 0 or cid
local shopWindow = {}
local t = {
          [2195] = {price = 1, price2 = 10}, -- [ITEMID TO SELL] = {Buy cost (0 = not buyable), sell cost (0 = not sellable)}
          [2493] = {price = 1, price2 = 0},
          [2361] = {price = 3, price2 = 0},
          [8851] = {price = 20, price2 = 0},
          [8925] = {price = 30, price2 = 0},
          [2640] = {price = 50, price2 = 0},
          [2494] = {price = 100, price2 = 0},
          [9932] = {price = 50, price2 = 0},
          [2472] = {price = 70, price2 = 0},
          [8931] = {price = 100, price2 = 0}
          }
local TradeItem = 12662
local onBuy = function(cid, item, subType, amount, ignoreCap, inBackpacks)
    if getPlayerItemCount(cid, TradeItem) < t[item].price*amount then
        selfSay("You don't have enough tokens.", cid)
    else
        doPlayerAddItem(cid, item, amount)
        doPlayerRemoveItem(cid, TradeItem, t[item].price*amount)
        doPlayerSendTextMessage(cid, 20, "You have bought " .. amount .. "x " .. getItemName(item) .. " for " .. t[item].price*amount .. " tokens.")
    end
    return true
end
local onSell = function(cid, item, subType, amount)
    doPlayerRemoveItem(cid, item, amount)
    doPlayerAddItem(cid, TradeItem, t[item].price2*amount)
    doPlayerSendTextMessage(cid, 20, "You have sold " .. amount .. "x " .. getItemName(item) .. " for " .. t[item].price*amount .. " tokens.")
    --selfSay("Here your are!", cid)
    return true
end
if (msgcontains(msg, 'trade') or msgcontains(msg, 'TRADE'))then
    for var, ret in pairs(t) do
        table.insert(shopWindow, {id = var, subType = 0, buy = ret.price, sell = ret.price2, name = getItemName(var)})
    end
    openDonateShopWindow(cid, shopWindow, onBuy, onSell) end
    return true
end
npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback)
npcHandler:addModule(FocusModule:new())

Now when creating a new npc, just make sure it uses the donate.lua script.
I'm sorry whoever created this script, I lost the original thread so I can't give credits right now.
Lua:
<?xml version="1.0" encoding="UTF-8"?>
<npc name="Donate Shop" script="donate.lua" walkinterval="1500" speed="100" walkradius="2" floorchange="0">
    <health max="100" now="100"/>
    <look type="130" head="0" body="0" legs="0" feet="0" addons="3" mount="0"/>
</npc>

Now you can just ask for a trade with the npc and the currency used will be tokens.

Damn, I wish I could mark my own answer as best answer :(
 
Last edited:
Solution
Could you specify which source files you edited?

Thanks in advance!
Post automatically merged:

1609091602690.png

I followed all your steps but I get these bugs when I compile. I am using tfs 1.2
 
Last edited:
Could you specify which source files you edited?

Thanks in advance!
Post automatically merged:

View attachment 53037

I followed all your steps but I get these bugs when I compile. I am using tfs 1.2
This is using the 1.3 TFS downgraded by Nekiro to run Tibia 8.60. I do not think there would be massive changes in versions, but maybe stuff has indeed changed... I'll EDIT my previous post to detail what source files have been edited.

-
EDIT: It seems I cannot edit my previous posts.
So first: to add the "if (player->isDonateShop) {" line, search in the entire document for "sendSaleItemList", it should be protocolgame.cpp

Second, "ITEM_DONATE_TOKEN" should be placed in the const.h

Third, "updateSaleShopList" and ""openShopWindow" are methods in player.cpp.

The rest of the changes already have the source file listed or are to create new files. Hope that helps.
 
Last edited:
Back
Top