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

Feature New Npc Currency System for TFS 1.2

unknownRanger

Banned User
Joined
Aug 7, 2016
Messages
4
Reaction score
14
Ever wanted to use an alternate currency at an npc instead of lugging around all sorts of tokens and knickknacks?
Well now you can, I call this the new npc currency system because it uses both the regular cash in the game but an alternate currency of your choosing.

How does it work? instead of writing a script with a million storage values for each npc, the npc determines the currency based on its currency id.
Using Lailane here as an example.
Code:
<?xml version="1.0" encoding="UTF-8"?>
<npc name="Lailene" currency="123456" script="lailene.lua" walkinterval="2000" floorchange="0" speechbubble="2">
<health now="100" max="100"/>
<look type="279" head="114" body="94" legs="113" feet="114" addons="0"/>
</npc>

If any player has a storage value of 123456 set with some value greater than 0, then they can purchase things from Lailene's shop, if the npc does not have a currency id then no worries, the npc will simply use the normal currency.

Basically what you are doing is using the value stored in storage as the currency, the storage value is just to determine what type of currency and is pretty much upto you what to call it.

Now you can have 100's of npc's with 100's of different ways to pay for things.

game.cpp
Find this
Code:
bool Game::removeMoney(Cylinder* cylinder, uint64_t money, uint32_t flags /*= 0*/)
Replace the whole function with this.
Code:
bool Game::removeMoney(Cylinder* cylinder, uint64_t money, uint32_t flags /*= 0*/)
{
    if (cylinder == nullptr) {
        return false;
    }

    if (money == 0) {
        return true;
    }
    uint32_t currencyId = 0;
    Player* player;
    if (Creature* creature = cylinder->getCreature()) {
        if (Player* p = creature->getPlayer()) {
            currencyId = p->getNpcCurrencyId();
            player = p;
        }
    }
    if (!currencyId) {
        std::vector<Container*> containers;

        std::multimap<uint32_t, Item*> moneyMap;
        uint64_t moneyCount = 0;

        for (size_t i = cylinder->getFirstIndex(), j = cylinder->getLastIndex(); i < j; ++i) {
            Thing* thing = cylinder->getThing(i);
            if (!thing) {
                continue;
            }

            Item* item = thing->getItem();
            if (!item) {
                continue;
            }

            Container* container = item->getContainer();
            if (container) {
                containers.push_back(container);
            }
            else {
                const uint32_t worth = item->getWorth();
                if (worth != 0) {
                    moneyCount += worth;
                    moneyMap.emplace(worth, item);
                }
            }
        }

        size_t i = 0;
        while (i < containers.size()) {
            Container* container = containers[i++];
            for (Item* item : container->getItemList()) {
                Container* tmpContainer = item->getContainer();
                if (tmpContainer) {
                    containers.push_back(tmpContainer);
                }
                else {
                    const uint32_t worth = item->getWorth();
                    if (worth != 0) {
                        moneyCount += worth;
                        moneyMap.emplace(worth, item);
                    }
                }
            }
        }

        if (moneyCount < money) {
            return false;
        }

        for (const auto& moneyEntry : moneyMap) {
            Item* item = moneyEntry.second;
            if (moneyEntry.first < money) {
                internalRemoveItem(item);
                money -= moneyEntry.first;
            }
            else if (moneyEntry.first > money) {
                const uint32_t worth = moneyEntry.first / item->getItemCount();
                const uint32_t removeCount = (money / worth) + 1;
                addMoney(cylinder, (worth * removeCount) - money, flags);
                internalRemoveItem(item, removeCount);
                break;
            }
            else {
                internalRemoveItem(item);
                break;
            }
        }
    }
    else {
        int32_t value;
        player->getStorageValue(currencyId, value);
        if (value < money) {
            return false;
        }
        player->addStorageValue(currencyId, value - money);
    }
    return true;
}

Next find this
Code:
void Game::addMoney(Cylinder* cylinder, uint64_t money, uint32_t flags /*= 0*/)
Replace the whole function with this
Code:
void Game::addMoney(Cylinder* cylinder, uint64_t money, uint32_t flags /*= 0*/)
{
    if (money == 0) {
        return;
    }

    if (Creature* creature = cylinder->getCreature()) {
        if (Player* player = creature->getPlayer()) {
            if(uint32_t currencyId = player->getNpcCurrencyId()){
                int32_t value;
                player->getStorageValue(currencyId, value);
                player->addStorageValue(currencyId, value + money);
                return;
            }
        }
    }

    uint32_t crystalCoins = money / 10000;
    money -= crystalCoins * 10000;
    while (crystalCoins > 0) {
        const uint16_t count = std::min<uint32_t>(100, crystalCoins);

        Item* remaindItem = Item::CreateItem(ITEM_CRYSTAL_COIN, count);

        ReturnValue ret = internalAddItem(cylinder, remaindItem, INDEX_WHEREEVER, flags);
        if (ret != RETURNVALUE_NOERROR) {
            internalAddItem(cylinder->getTile(), remaindItem, INDEX_WHEREEVER, FLAG_NOLIMIT);
        }

        crystalCoins -= count;
    }

    uint16_t platinumCoins = money / 100;
    if (platinumCoins != 0) {
        Item* remaindItem = Item::CreateItem(ITEM_PLATINUM_COIN, platinumCoins);

        ReturnValue ret = internalAddItem(cylinder, remaindItem, INDEX_WHEREEVER, flags);
        if (ret != RETURNVALUE_NOERROR) {
            internalAddItem(cylinder->getTile(), remaindItem, INDEX_WHEREEVER, FLAG_NOLIMIT);
        }

        money -= platinumCoins * 100;
    }

    if (money != 0) {
        Item* remaindItem = Item::CreateItem(ITEM_GOLD_COIN, money);

        ReturnValue ret = internalAddItem(cylinder, remaindItem, INDEX_WHEREEVER, flags);
        if (ret != RETURNVALUE_NOERROR) {
            internalAddItem(cylinder->getTile(), remaindItem, INDEX_WHEREEVER, FLAG_NOLIMIT);
        }
    }

}
-----------------------------------------------------
npc.cpp
look for this
Code:
    pugi::xml_attribute attr;
    if ((attr = npcNode.attribute("speed"))) {
        baseSpeed = pugi::cast<uint32_t>(attr.value());
    } else {
        baseSpeed = 100;
    }

Right underneath that you are going to place this.
Code:
    if ((attr = npcNode.attribute("currency"))) {
        currency = pugi::cast<uint32_t>(attr.value());
    }
--------------------------------------------
npc.h
look for this
Code:
        bool isPushable() const final {
            return walkTicks > 0;
        }
Place this right underneath
Code:
        uint32_t getCurrencyId() const {
            return currency;
        }

Look for this
Code:
uint32_t walkTicks;
Place this right underneath
Code:
uint32_t currency;
---------------------------
player.cpp

find this
Code:
void Player::openShopWindow(Npc* npc, const std::list<ShopInfo>& shop)
and replace that function with this
Code:
void Player::openShopWindow(Npc* npc, const std::list<ShopInfo>& shop)
{
    shopItemList = shop;
    sendShop(npc);
    sendSaleItemList(npc);
}

next find this
Code:
bool Player::updateSaleShopList(const Item* item)

and replace that function with this
Code:
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 (!container) {
                return false;
            }

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

    if (client) {
        client->sendSaleItemList(shopOwner, shopItemList);
    }
    return true;
}

Next you are going to look for
Code:
uint64_t Player::getMoney() const
Now right underneath that function you are going to place these.
Code:
uint64_t Player::getMoney(Npc* npc) const
{
    uint64_t cash;
    setNpcCurrencyId(npc);
    uint32_t currencyId = getNpcCurrencyId();
    if (currencyId) {
        int32_t value;
        getStorageValue(currencyId, value);
        cash = (uint64_t)value;
    }
    else {
        cash = getMoney();
    }
    return cash;
}


void Player::setNpcCurrencyId(Npc* npc) const{
    currencyId = npc->getCurrencyId();
}

uint32_t Player::getNpcCurrencyId() const {
    return currencyId;
}
----------------------------------
player.h

look for this
Code:
        uint64_t getMoney() const;

place this right underneath
Code:
        uint64_t getMoney(Npc*) const;

        void setNpcCurrencyId(Npc*) const;

        uint32_t getNpcCurrencyId() const;
find this
Code:
        void sendShop(Npc* npc) const {
            if (client) {
                client->sendShop(npc, shopItemList);
            }
        }

and place this right underneath
Code:
        void sendSaleItemList(Npc* npc) const {
            if (client) {
                client->sendSaleItemList(npc, shopItemList);
            }
        }
find this
Code:
        uint32_t manaMax;
place this underneath
Code:
         mutable uint32_t currencyId;
-----------------------------------------
protocolgame.cpp
Now find this function
Code:
void ProtocolGame::sendSaleItemList(const std::list<ShopInfo>& shop)

and place this right underneath
Code:
void ProtocolGame::sendSaleItemList(Npc* npc, const std::list<ShopInfo>& shop)
{
    NetworkMessage msg;
    msg.addByte(0x7B);
    msg.add<uint64_t>(player->getMoney(npc));

    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);
}
--------------------------------------------------
protocolgame.h

Find this
Code:
void sendSaleItemList(const std::list<ShopInfo>& shop);

place this right underneath
Code:
void sendSaleItemList(Npc* npc, const std::list<ShopInfo>& shop);

You are going to ban me anyway which I think is highly unfair...
New Npc Currency System by Codex NG xD
 
Last edited:
luascript.cpp

find
Code:
int LuaScriptInterface::luaPlayerAddMoney(lua_State* L)
and replace that function with this
Code:
int LuaScriptInterface::luaPlayerAddMoney(lua_State* L)
{
    // player:addMoney(money[, currencyId])
    uint64_t money = getNumber<uint64_t>(L, 2);
    uint32_t currencyId = getNumber<uint32_t>(L, 3);
    Player* player = getUserdata<Player>(L, 1);
    if (player) {
        if (currencyId) {
            int32_t value;
            player->getStorageValue(currencyId, value);
            player->addStorageValue(currencyId, value + money);
        }
        else {
            g_game.addMoney(player, money);
        }
        pushBoolean(L, true);
    } else {
        lua_pushnil(L);
    }
    return 1;
}

Next find this function which should be right below it.
Code:
int LuaScriptInterface::luaPlayerRemoveMoney(lua_State* L)
replace that function with this
Code:
int LuaScriptInterface::luaPlayerRemoveMoney(lua_State* L)
{
    // player:removeMoney(money[, currencyId])
    Player* player = getUserdata<Player>(L, 1);
    if (player) {
        uint64_t money = getNumber<uint64_t>(L, 2);
        uint32_t currencyId = getNumber<uint32_t>(L, 3);
        if (currencyId) {
            int32_t value;
            player->getStorageValue(currencyId, value);
            if (value < money) {
                pushBoolean(L, false);
                return 1;
            }
            player->addStorageValue(currencyId, value - money);
            pushBoolean(L, true);
        }
        else {
            pushBoolean(L, g_game.removeMoney(player, money));
        }

    } else {
        lua_pushnil(L);
    }
    return 1;
}

Currently getMoney() works the same as it always has. Although I can write something for it :)
 
Last edited:
If I did a request, you did the wrong reply. I am asking him to let me know if any news.

I think your resquest definition is kind distorted hajajaja
The post you quoted was a request, you were asking if the request was solved, we are all wrong, cause we should be talking about the code that the OP posted not about another script related to npcs.
 
Back
Top