• 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++ Get premium_points from database thgrough protocolgame network packet

Gover

Member
Joined
Sep 3, 2009
Messages
72
Reaction score
19
Hello
I'm struggling with a C++ code in TFS1.5

I'm trying to get a information about the current premium_points value from player account in MySQL database.

I have created a protocolgame.cpp entry, I know the SQL query (SELECT premium_points FROM accounts WHERE id='ACCOUNT_ID';) but I cannot get it to work.
It looks like there is no way to get the player account id in protocolgame.cpp. Should I use some function in game.cpp? or maybe player.cpp?
I tried to get it done through game.cpp with:

C++:
        case 0xF2: // Premium points request
            addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerRequestPremiumPoints, player->getID());

But I think it is not the best way to do it. Maybe I should somehow get this value from iologindata and provide it to the protocolgame.cpp somehow?

C++:
void ProtocolGame::sendPremiumPoints(uint32_t points)
{
    NetworkMessage msg;
    msg.addByte(0xF3);  // Premium points response packet
    msg.add<uint32_t>(points);
    writeToOutputBuffer(msg);
}

I would appreciate any idea/suggestion in this case.
Thanks in advance all!
 
Solution
I saw that any usage of player->getAccount(); (of course added Player* player = getPlayerByID(playerId);) are not supported inside the protocolgame.cpp
It's not allowed, when you 'read' packets. You can use any variables (player/account/database connection), when you 'send' packets, but you must use dispatcher thread task anyway ex. g_dispatcher.addTask.
It's because packets from client parsing ex.:
C++:
case 0xF2: // Premium points request
    addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerRequestPremiumPoints, player->getID());
is executed in 'network thread' of OTS and player object can be safely modified only in 'dispatcher thread'. Also sending packets to player can be safely executed only in...
I have created a protocolgame.cpp entry, I know the SQL query (SELECT premium_points FROM accounts WHERE id='ACCOUNT_ID';) but I cannot get it to work.

What u mean you cannot get it work?
Be clear :D
LUA:
function Player.getPremiumPoints(self)
    local query = db.storeQuery("SELECT `premium_points` FROM `accounts` WHERE `id` = " .. self:getAccountId())
    if not query then
        return false
    end

    local value = result.getNumber(query, "premium_points")
    result.free(query)
    return value
end
 
Hi @Marko999x ,
Thanks for answer.
Im trying to send the network message with a value of current premium_points status from the server to the client (client should request this value from the server on demand).
I'm trying to build a Premium Store inside tibia client, and this value is required for me to make this store usefull.
Im not using otclient, im using tibianic-dll - thats why I have to do it in C++.

Using this query require getting the accountID.
C++:
    uint32_t accountId = player->getAccount();
    DBResult_ptr result = Database::getInstance().storeQuery(
        fmt::format("SELECT `premium_points` FROM `accounts` WHERE `id` = {:d}", accountId)
    );

I saw that any usage of player->getAccount(); (of course added Player* player = getPlayerByID(playerId);) are not supported inside the protocolgame.cpp (or I do not know how to get this accountId in this protocolgame.cpp). It looks like this network message inside protocolgame.cpp should store a variable for which a value was calculated in different file (like game.cpp or maybe player.cpp? or maybe iologindata.cpp?) - i would like to get any suggestion from you guys what is the best way :)


I have done it in game.cpp but I think it is not a proper way of doing that:

protocolgame.cpp:
LUA:
case 0xF2: // Premium points request
addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerRequestPremiumPoints, player->getID());


void ProtocolGame::sendPremiumPoints(uint32_t points)
{
    NetworkMessage msg;
    msg.addByte(0xF3);  // Premium points response packet
    msg.add<uint32_t>(points);
    writeToOutputBuffer(msg);
}

game.cpp
C++:
void Game::playerRequestPremiumPoints(uint32_t playerId)
{
    Player* player = getPlayerByID(playerId);
    if (!player) {
        return;
    }

    // Get account ID and query premium points
    uint32_t accountId = player->getAccount();
    DBResult_ptr result = Database::getInstance().storeQuery(
        fmt::format("SELECT `premium_points` FROM `accounts` WHERE `id` = {:d}", accountId)
    );

    // Send response packet
    if (player->client) {
        if (result) {
            player->client->sendPremiumPoints(result->getNumber<uint32_t>("premium_points"));
        } else {
            player->client->sendPremiumPoints(0);
        }
    }
}



EDIT:
I see now that in void ProtocolGame::login:
it is getting information if account is banned.
I see that it maybe is possible to get it using "g_game.getPlayerByAccount(player->getAccount()"

Thanks in advance for help !
 
Last edited:
I saw that any usage of player->getAccount(); (of course added Player* player = getPlayerByID(playerId);) are not supported inside the protocolgame.cpp
It's not allowed, when you 'read' packets. You can use any variables (player/account/database connection), when you 'send' packets, but you must use dispatcher thread task anyway ex. g_dispatcher.addTask.
It's because packets from client parsing ex.:
C++:
case 0xF2: // Premium points request
    addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerRequestPremiumPoints, player->getID());
is executed in 'network thread' of OTS and player object can be safely modified only in 'dispatcher thread'. Also sending packets to player can be safely executed only in 'dispatcher thread' (if 2 threads [network and dispatcher] try to send packet to same player in same time, server will crash/freez).

player and account are used in ProtocolGame::login function, but you can see note in code, that this function is executed in 'dispatcher thread':
It's executed in dispatcher thread, because it's called like that:
I have done it in game.cpp but I think it is not a proper way of doing that:
That way your code works like rest of TFS code. Reads packet in network thread, creates dispatcher task that generates data in game.cpp (ex. points amount) and sends packet to client.
If you don't like that code design and want to write all in protocolgame.cpp, it would be something like:
C++:
case 0xF2: // Premium points request
    g_dispatcher.addTask([=, playerId = player->getID()]() {
// use `g_game` to call Game functions

// get player = check, if he still exists in Game
        Player* player = g_game.getPlayerByID(playerId);
        if (!player) {
            return;
        }

        // Get account ID and query premium points
        uint32_t accountId = player->getAccount();
        DBResult_ptr result = Database::getInstance().storeQuery(
            fmt::format("SELECT `premium_points` FROM `accounts` WHERE `id` = {:d}", accountId)
        );

        // Send response packet
// get current 'client' (ProtocolGame),
// it can be changed (player reconnected, before dispatcher task executed),
// so we don't use `thisPtr` to pass current ProtocolGame (like ProtocolGame::login function does)
        if (player->client) {
            if (result) {
                player->client->sendPremiumPoints(result->getNumber<uint32_t>("premium_points"));
            } else {
                player->client->sendPremiumPoints(0);
            }
        }
    });

Do not try to copy canary code ( GitHub - opentibiabr/canary: Canary Server 14.x for OpenTibia community. (https://github.com/opentibiabr/canary) ) into TFS. Their 'parse packet' code is executed in 'dispatcher thread', so there are many examples of generating data in protocolgame.cpp, that would crash randomly on TFS (you can run it 100 times locally with no crash and then get crash, when more players login).
 
Solution
As always - really helpful.
Thanks gesior!

Maybe someone will find it usefull:

gesior provided pretty much a complete guide how to handle it on server side, to make it full i will provide the client changes (tibianicdll):

main.cpp:
at top
C++:
// Premium points global variable
uint32_t g_premiumPoints = 0;

lib section
C++:
void LIB::RequestPremiumPoints() {
    if(!Tibia::IsOnline()) {
        return;
    }

    NetworkMessage* msg = new NetworkMessage(NetworkMessage::XTEA);
    msg->AddByte(0xF2); // Premium points request packet
    SendXTEANetworkMessage(msg);
}

void LIB::HandlePremiumPoints(NetworkMessage* msg) {
    g_premiumPoints = msg->GetU32();
    // Notify store dialog about the update
    if(g_gui && g_gui->getDialog() && g_gui->getDialog()->getType() == GUI_DIALOG_STORE) {
        ((GUIStoreDialog*)g_gui->getDialog())->updatePremiumPoints(g_premiumPoints);
    }
}

uint32_t LIB::GetPremiumPoints() {
    return g_premiumPoints;
}

case
C++:
    /* Premium points response */
    case 0xF3: {
        g_premiumPoints = Tibia::NetworkGetU32();
        // Notify store dialog about the update
        if(g_gui && g_gui->getDialog() && g_gui->getDialog()->getType() == GUI_DIALOG_STORE) {
            ((GUIStoreDialog*)g_gui->getDialog())->updatePremiumPoints(g_premiumPoints);
        }
        break;
    }
    }

the store window itself (storedialog.cpp)
C++:
#include "storedialog.h"
#include "../gui.h"
#include "../../main/main.h"
#include "../../network/networkmessage.h"

void buyItem(Button* button, void* lParam) {
    GUIStoreDialog* dialog = static_cast<GUIStoreDialog*>(lParam);
    dialog->executeBuy();
}

GUIStoreDialog::GUIStoreDialog() : GUIWindow("Store", 0, 0, 400, 300) {
    // Create buttons using GUIWindow's button handling
    GUIWindow::pushButton("Cancel", &GUI::nullDialog, NULL, GUIWindow::getButtonNextX(), GUIWindow::getButtonNextY());
    GUIWindow::pushButton("Buy", buyItem, this, GUIWindow::getButtonNextX(), GUIWindow::getButtonNextY());

    // Create a simple store interface
    Label* welcomeLabel = new Label(20, 20);
    welcomeLabel->setText("Welcome to the Store!");
    GUIParent::addChild(welcomeLabel);

    // Add premium points display
    m_premiumPointsLabel = new Label(20, 40);
    m_premiumPointsLabel->setText("Premium Points: 0");
    GUIParent::addChild(m_premiumPointsLabel);

    // Add a list of store items
    m_itemList = new ListBox(20, 70, 200, 10);
    m_itemList->addItem("Premium Scroll - 250 coins");
    m_itemList->addItem("Health Potion - 50 coins");
    m_itemList->addItem("Mana Potion - 50 coins");
    m_itemList->addItem("Magic Sword - 1000 coins");
    m_itemList->addItem("Dragon Shield - 800 coins");
    GUIParent::addChild(m_itemList);

    // Request premium points when store opens
    g_dll.RequestPremiumPoints();
}

void GUIStoreDialog::executeBuy() {
    if(!Tibia::IsOnline()) {
        return;
    }

    int selection = m_itemList->selection();
    if(selection < 0) {
        return;
    }

    // Create the message based on selection
    std::string message;
    switch(selection) {
        case 0: message = "buy premium scroll"; break;
        case 1: message = "buy health potion"; break;
        case 2: message = "buy mana potion"; break;
        case 3: message = "buy magic sword"; break;
        case 4: message = "buy dragon shield"; break;
        default: return;
    }

    // Create network message
    NetworkMessage* msg = new NetworkMessage(NetworkMessage::XTEA);
 
    // Fill network message - using default chat (0x96 is speak, 0x01 is say)
    msg->AddByte(0x96);
    msg->AddByte(0x01);
    msg->AddString(message);
 
    // Send the network message
    g_dll.SendXTEANetworkMessage(msg);
}

void GUIStoreDialog::updatePremiumPoints(uint32_t points) {
    if(m_premiumPointsLabel) {
        char buffer[64];
        sprintf(buffer, "Premium Points: %u", points);
        m_premiumPointsLabel->setText(buffer);
    }
}

void GUIStoreDialog::tick(uint32_t ticks) {
    GUIWindow::tick(ticks);
}

h
C++:
#ifndef __STOREDIALOG_H__
#define __STOREDIALOG_H__

#include "../guiwindow.h"
#include "listbox.h"
#include "label.h"

class GUIStoreDialog : public GUIWindow {
private:
    ListBox* m_itemList;
    Label* m_premiumPointsLabel;

public:
    GUIStoreDialog();
    void executeBuy();
    void tick(uint32_t ticks);
    void updatePremiumPoints(uint32_t points);
 
    GUIDialogType getType() const override { return GUI_DIALOG_STORE; }
};

#endif


This works with executing the commands in default chat. simply edit "case 0: message = "buy premium scroll"; break;" with revscripts talkations commands which will handle all the checks, points removal, items adding etc. For now the premium_points value is refreshed only when store window is executed - you should add it also when buy button is clicked.

You can fit the button after help button - it looks decent:
C++:
  // Lets create quests button in inventory
  m_gameQuestsButton = m_inventoryButtons->pushButton("Quests", gameQuests, NULL, 0, -56, false, true);

  // Options button
  m_inventoryButtons->pushButton("Options", gameOptions, NULL, 0, -34, false, true);

  // Help button
  m_inventoryButtons->pushButton("Help", gameHelp, NULL, 0, -12, false, true);

  // Store button
  m_inventoryButtons->pushButton("Store", gameStore, NULL, 0, 10, false, true);

1748012292654.webp

Again gesior thanks for complex help.
Have a great day!

NOT TESTED WELL. DO NOT USE IN PRODUCTION - USE IT FOR FUN.

A simple store system in revscripts below (to test purposes - not properly tested, use on your own risk):
LUA:
local STORE_SYSTEM_VERSION = "1.0.0"
print(">> Loaded Store System v" .. STORE_SYSTEM_VERSION)


-- Premium Store System
-- Configuration for store items
local storeItems = {
    ["/shop1"] = {itemId = 2160, price = 1, name = "Crystal Coin"}, -- Example item
    ["/shop2"] = {itemId = 2400, price = 1, name = "Magic Sword"}, -- Example item
    ["/shop3"] = {itemId = 2195, price = 1, name = "Boots of Haste"}, -- Example item
    ["/shop4"] = {itemId = 2493, price = 1, name = "Demon Helmet"} -- Example item
}

-- Improved premium points getter function
function Player.getPremiumPoints(self)
    if not self then
        return 0
    end

    local accountId = self:getAccountId()
    if not accountId then
        return 0
    end

    local query = string.format("SELECT `premium_points` FROM `accounts` WHERE `id` = %d", accountId)
    local resultId = db.storeQuery(query)
   
    if not resultId then
        return 0
    end

    local points = result.getNumber(resultId, "premium_points")
    result.free(resultId)
    return points or 0
end

-- Function to update premium points
function Player.removePremiumPoints(self, points)
    if not self or points <= 0 then
        return false
    end

    local accountId = self:getAccountId()
    if not accountId then
        return false
    end

    local query = string.format("UPDATE `accounts` SET `premium_points` = `premium_points` - %d WHERE `id` = %d", points, accountId)
    return db.query(query)
end

-- Helper function to check if player can carry the item
function Player.canCarryItem(self, itemId)
    if not self or not itemId then
        return false
    end

    -- Check if player has a backpack and enough slots
    local backpack = self:getSlotItem(CONST_SLOT_BACKPACK)
    if not backpack or backpack:getEmptySlots(false) < 1 then
        return false
    end

    -- Check weight capacity using ItemType
    local itemType = ItemType(itemId)
    if not itemType then
        return false
    end

    return self:getFreeCapacity() >= itemType:getWeight()
end

-- Main store function handler
local function handleStoreCommand(player, words, param)
    if not player:isPlayer() then
        return false
    end

    -- Remove the '/' from the command to match our storeItems keys
    local command = words:lower()
    local storeItem = storeItems[command]
   
    if not storeItem then
        player:sendTextMessage(MESSAGE_STATUS_SMALL, "This store command does not exist.")
        return false
    end

    -- Check premium points
    local playerPoints = player:getPremiumPoints()
    if playerPoints < storeItem.price then
        player:sendTextMessage(MESSAGE_STATUS_SMALL, string.format("You need %d premium points to buy %s. You have only %d points.",
            storeItem.price, storeItem.name, playerPoints))
        return false
    end

    -- Check if player can carry the item
    if not player:canCarryItem(storeItem.itemId) then
        player:sendTextMessage(MESSAGE_STATUS_SMALL, "Your main backpack is full or you don't have enough capacity. You need at least 1 free slot.")
        return false
    end

    -- Create item and remove points
    local item = player:addItem(storeItem.itemId, 1)
    if not item then
        player:sendTextMessage(MESSAGE_STATUS_SMALL, "Failed to create the item.")
        return false
    end

    -- Remove premium points
    if not player:removePremiumPoints(storeItem.price) then
        -- If points removal fails, remove the item
        item:remove()
        player:sendTextMessage(MESSAGE_STATUS_SMALL, "Transaction failed. Please try again.")
        return false
    end

    player:sendTextMessage(MESSAGE_INFO_DESCR, string.format("You have successfully purchased %s for %d premium points.",
        storeItem.name, storeItem.price))
    return true
end

-- Register each store command individually
for command, _ in pairs(storeItems) do
    local commandName = command:sub(2) -- remove the '/' from the command
    local talkAction = TalkAction(command)
    talkAction:separator(" ")
    talkAction:onSay(function(player, words, param)
        return handleStoreCommand(player, command, param)
    end)
    talkAction:register()
end

the executeBuy function you can edit like this (the end of the function):
C++:
    // Send the network message
    g_dll.SendXTEANetworkMessage(msg);

    // Set timer for delayed premium points update
    m_updateTimer = 0;
    m_pendingUpdate = true;

And edit the ticks to add some delay (if we execute update instantly then lua script will not be able to process the request in that time, so the delay is required I think)

C++:
void GUIStoreDialog::tick(uint32_t ticks) {
    GUIWindow::tick(ticks);

    // Handle delayed premium points update
    if (m_pendingUpdate) {
        m_updateTimer += ticks;
        if (m_updateTimer >= 500) { // 500ms delay
            g_dll.RequestPremiumPoints();
            m_pendingUpdate = false;
        }
    }
}


Of course do not forget about updating the h file.
C++:
    uint32_t m_updateTimer;
    bool m_pendingUpdate;
 
Last edited:
Back
Top