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

RewardChest dont save items in database

Azerty

Active Member
Joined
Apr 15, 2022
Messages
301
Solutions
4
Reaction score
31
I'm implementing rewardchest on my server, but when the player logs out he is losing items in the rewardchest

Lua:
local function pushSeparated(buffer, sep, ...)
    local argv = {...}
    local argc = #argv
    for k, v in ipairs(argv) do
        table.insert(buffer, v)
        if k < argc and sep then
            table.insert(buffer, sep)
        end
    end
end

local function insertItems(buffer, info, parent, items)
    local start = info.running
    for _, item in ipairs(items) do
        if _ ~= 1 or parent > 100 then
            table.insert(buffer, ",")
        end

        info.running = info.running + 1
        table.insert(buffer, "(")       
        pushSeparated(buffer, ",", info.playerGuid, parent, info.running, item:getId(), item:getSubType(), db.escapeBlob(item:serializeAttributes()))
        table.insert(buffer, ")")

        if item:isContainer() then
            local size = item:getSize()
            if size > 0 then             
                local subItems = {}
                for i = 1, size do
                    table.insert(subItems, item:getItem(i - 1))
                end

                insertItems(buffer, info, info.running, subItems)
            end
        end
    end
    return info.running - start
end

local function insertRewardItems(playerGuid, timestamp, itemList)
    db.asyncStoreQuery('SELECT `pid`, `sid` FROM `player_rewards` WHERE player_id = ' .. playerGuid .. ' ORDER BY `sid` ASC;',
        function(query)
            local lastReward = 0
            local lastStoreId   
            if query then             
                repeat
                    local sid = result.getDataInt(query, 'sid')
                    local pid = result.getDataInt(query, 'pid')

                    if pid < 100 then
                        lastReward = pid
                    end
                    lastStoreId = sid
                until not result.next(query)
            end

            local buffer = {'INSERT INTO `player_rewards` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES'}

            --reward bag
            local info = {
                playerGuid = playerGuid,
                running = lastStoreId or 100
            }

            local bag = Game.createItem(ITEM_REWARD_CONTAINER)
            bag:setAttribute(ITEM_ATTRIBUTE_DATE, timestamp)

            if itemList then
                for _, item in ipairs(itemList) do
                    bag:addItem(item[1], item[2])
                end
            end

            local total = insertItems(buffer, info, lastReward + 1, {bag})
            table.insert(buffer, ";")

            if total ~= 0 then
                db.query(table.concat(buffer))
            end
        end
    )
end

local function getPlayerStats(bossId, playerGuid, autocreate)
    local ret = globalBosses[bossId][playerGuid]
    if not ret and autocreate then
        ret = {
            bossId = bossId,
            damageIn = 0, -- damage taken from the boss
            healing = 0, -- healing (other players) done
        }
        globalBosses[bossId][playerGuid] = ret
        return ret
    end
    return ret
end

function onDeath(creature, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified)
    local monsterType = creature:getType()
    if monsterType:isRewardBoss() then -- Make sure it is a boss
        local bossId = creature:getId()
        local timestamp = os.time()

        local totalDamageOut, totalDamageIn, totalHealing = 0.1, 0.1, 0.1 -- avoid dividing by zero

        local scores = {}
        local info = globalBosses[bossId]
        local damageMap = creature:getDamageMap()

        for guid, stats in pairs(info) do
            local player = Player(stats.playerId)
            local part = damageMap[stats.playerId]
            local damageOut, damageIn, healing = (stats.damageOut or 0) + (part and part.total or 0), stats.damageIn or 0, stats.healing or 0

            totalDamageOut = totalDamageOut + damageOut
            totalDamageIn = totalDamageIn + damageIn
            totalHealing = totalHealing + healing

            table.insert(scores, {
                player = player,
                guid = guid,
                damageOut = damageOut,
                damageIn = damageIn,
                healing = healing,
            })           
        end

        local participants = 0
        for _, con in ipairs(scores) do
            local score = (con.damageOut / totalDamageOut) + (con.damageIn / totalDamageIn) + (con.healing / totalHealing)
            con.score = score / 3 -- normalize to 0-1
            if score ~= 0 then
                participants = participants + 1
            end
        end
        table.sort(scores, function(a, b) return a.score > b.score end)

        local expectedScore = 1 / participants

        for _, con in ipairs(scores) do
            local reward, stamina -- ignoring stamina for now because I heard you receive rewards even when it's depleted   
            if con.player then   
                reward = con.player:getReward(timestamp, true)
                stamina = con.player:getStamina()
            else
                stamina = con.stamina or 0
            end

            local playerLoot
            if --[[stamina > 840 and]] con.score ~= 0 then
                local lootFactor = 1
                lootFactor = lootFactor / participants ^ (1 / 3) -- tone down the loot a notch if there are many participants
                lootFactor = lootFactor * (1 + lootFactor) ^ (con.score / expectedScore) -- increase the loot multiplicatively by how many times the player surpassed the expected score
                playerLoot = monsterType:getBossReward(lootFactor, _ == 1)

                if con.player then
                    for _, p in ipairs(playerLoot) do
                        reward:addItem(p[1], p[2])
                    end
                end
            end

            if con.player then
                local lootMessage = {"The following items are available in your reward chest: "}

                if --[[stamina > 840]]true then
                    reward:getContentDescription(lootMessage)
                else
                    table.insert(lootMessage, 'nothing (due to low stamina)')
                end
                table.insert(lootMessage, ".")
                con.player:sendTextMessage(MESSAGE_EVENT_ADVANCE, table.concat(lootMessage))
            else
                insertRewardItems(con.guid, timestamp, playerLoot)
            end
        end

        globalBosses[bossId] = nil
    end
    return true
end

function onThink(creature, interval)
    local bossId = creature:getId()
    local info = globalBosses[bossId]
    -- Reset all players' status
    for _, player in pairs(info) do
        player.active = false
    end
    
    -- Set all players in boss' target list as active in the fight
    local targets = creature:getTargetList()
    for _, target in ipairs(targets) do
        if target:isPlayer() then
            local stats = getPlayerStats(bossId, target:getGuid(), true)
            stats.playerId = target:getId() -- Update player id
            stats.active = true           
        end
    end
end

function onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin)
    if not next(globalBosses) then
        return primaryDamage, primaryType, secondaryDamage, secondaryType
    end

    if not creature or not attacker then
        return primaryDamage, primaryType, secondaryDamage, secondaryType
    end

    local stats = creature:inBossFight()
    if not stats then
        return primaryDamage, primaryType, secondaryDamage, secondaryType
    end

    local creatureId, attackerId = creature:getId(), attacker:getId()
    stats.playerId = creatureId -- Update player id

    -- Account for healing of others active in the boss fight
    if primaryType == COMBAT_HEALING and attacker:isPlayer() and attackerId ~= creatureId then
        local healerStats = getPlayerStats(stats.bossId, attacker:getGuid(), true)
        healerStats.active = true
        healerStats.playerId = attackerId -- Update player id
        healerStats.healing = healerStats.healing + primaryDamage
    elseif stats.bossId == attackerId then
        -- Account for damage taken from the boss
        stats.damageIn = stats.damageIn + primaryDamage
    end
    return primaryDamage, primaryType, secondaryDamage, secondaryType
end
 

Attachments

just need to convert this part to TFS 1.5

C++:
  //load reward chest items
  itemMap.clear();

  query.str(std::string());
  query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_rewards` WHERE `player_id` = " << player->getGUID() << " ORDER BY `sid` DESC";
    if ((result = db.storeQuery(query.str()))) {
    loadItems(itemMap, result);

    //first loop handles the reward containers to retrieve its date attribute
    //for (ItemMap::iterator it = itemMap.begin(), end = itemMap.end(); it != end; ++it) {
    for (auto& it : itemMap) {
      const std::pair<Item*, int32_t>& pair = it.second;
      Item* item = pair.first;

      int32_t pid = pair.second;
      if (pid >= 0 && pid < 100) {
        Reward* reward = player->getReward(item->getIntAttr(ITEM_ATTRIBUTE_DATE), true);
        if (reward) {
          it.second = std::pair<Item*, int32_t>(reward->getItem(), pid); //update the map with the special reward container
        }
      } else {
        break;
      }
    }

    //second loop (this time a reverse one) to insert the items in the correct order
    //for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) {
    for (const auto& it : boost::adaptors::reverse(itemMap)) {
      const std::pair<Item*, int32_t>& pair = it.second;
      Item* item = pair.first;

      int32_t pid = pair.second;
      if (pid >= 0 && pid < 100) {
        break;
      }

      ItemMap::const_iterator it2 = itemMap.find(pid);
      if (it2 == itemMap.end()) {
        continue;
      }

      Container* container = it2->second.first->getContainer();
      if (container) {
        container->internalAddThing(item);
      }
    }
  }

C++:
  //save reward items
  query.str(std::string());
  query << "DELETE FROM `player_rewards` WHERE `player_id` = " << player->getGUID();

  if (!db.executeQuery(query.str())) {
    return false;
  }

  std::vector<uint32_t> rewardList;
  player->getRewardList(rewardList);

  if (!rewardList.empty()) {
    DBInsert rewardQuery("INSERT INTO `player_rewards` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES ");
    itemList.clear();

    int running = 0;
    for (const auto& rewardId : rewardList) {
      Reward* reward = player->getReward(rewardId, false);
      // rewards that are empty or older than 7 days aren't stored
      if (!reward->empty() && (time(nullptr) - rewardId <= 60 * 60 * 24 * 7)) {
        itemList.emplace_back(++running, reward);
      }
    }

    if (!saveItems(player, itemList, rewardQuery, propWriteStream)) {
      return false;
    }
  }
 
it's not the solution, I'm trying to develop for TFS 1.5 but I need this help, I'm not very good at c++
 
I'm implementing rewardchest on my server, but when the player logs out he is losing items in the rewardchest

Lua:
local function pushSeparated(buffer, sep, ...)
    local argv = {...}
    local argc = #argv
    for k, v in ipairs(argv) do
        table.insert(buffer, v)
        if k < argc and sep then
            table.insert(buffer, sep)
        end
    end
end

local function insertItems(buffer, info, parent, items)
    local start = info.running
    for _, item in ipairs(items) do
        if _ ~= 1 or parent > 100 then
            table.insert(buffer, ",")
        end

        info.running = info.running + 1
        table.insert(buffer, "(")      
        pushSeparated(buffer, ",", info.playerGuid, parent, info.running, item:getId(), item:getSubType(), db.escapeBlob(item:serializeAttributes()))
        table.insert(buffer, ")")

        if item:isContainer() then
            local size = item:getSize()
            if size > 0 then            
                local subItems = {}
                for i = 1, size do
                    table.insert(subItems, item:getItem(i - 1))
                end

                insertItems(buffer, info, info.running, subItems)
            end
        end
    end
    return info.running - start
end

local function insertRewardItems(playerGuid, timestamp, itemList)
    db.asyncStoreQuery('SELECT `pid`, `sid` FROM `player_rewards` WHERE player_id = ' .. playerGuid .. ' ORDER BY `sid` ASC;',
        function(query)
            local lastReward = 0
            local lastStoreId  
            if query then            
                repeat
                    local sid = result.getDataInt(query, 'sid')
                    local pid = result.getDataInt(query, 'pid')

                    if pid < 100 then
                        lastReward = pid
                    end
                    lastStoreId = sid
                until not result.next(query)
            end

            local buffer = {'INSERT INTO `player_rewards` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES'}

            --reward bag
            local info = {
                playerGuid = playerGuid,
                running = lastStoreId or 100
            }

            local bag = Game.createItem(ITEM_REWARD_CONTAINER)
            bag:setAttribute(ITEM_ATTRIBUTE_DATE, timestamp)

            if itemList then
                for _, item in ipairs(itemList) do
                    bag:addItem(item[1], item[2])
                end
            end

            local total = insertItems(buffer, info, lastReward + 1, {bag})
            table.insert(buffer, ";")

            if total ~= 0 then
                db.query(table.concat(buffer))
            end
        end
    )
end

local function getPlayerStats(bossId, playerGuid, autocreate)
    local ret = globalBosses[bossId][playerGuid]
    if not ret and autocreate then
        ret = {
            bossId = bossId,
            damageIn = 0, -- damage taken from the boss
            healing = 0, -- healing (other players) done
        }
        globalBosses[bossId][playerGuid] = ret
        return ret
    end
    return ret
end

function onDeath(creature, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified)
    local monsterType = creature:getType()
    if monsterType:isRewardBoss() then -- Make sure it is a boss
        local bossId = creature:getId()
        local timestamp = os.time()

        local totalDamageOut, totalDamageIn, totalHealing = 0.1, 0.1, 0.1 -- avoid dividing by zero

        local scores = {}
        local info = globalBosses[bossId]
        local damageMap = creature:getDamageMap()

        for guid, stats in pairs(info) do
            local player = Player(stats.playerId)
            local part = damageMap[stats.playerId]
            local damageOut, damageIn, healing = (stats.damageOut or 0) + (part and part.total or 0), stats.damageIn or 0, stats.healing or 0

            totalDamageOut = totalDamageOut + damageOut
            totalDamageIn = totalDamageIn + damageIn
            totalHealing = totalHealing + healing

            table.insert(scores, {
                player = player,
                guid = guid,
                damageOut = damageOut,
                damageIn = damageIn,
                healing = healing,
            })          
        end

        local participants = 0
        for _, con in ipairs(scores) do
            local score = (con.damageOut / totalDamageOut) + (con.damageIn / totalDamageIn) + (con.healing / totalHealing)
            con.score = score / 3 -- normalize to 0-1
            if score ~= 0 then
                participants = participants + 1
            end
        end
        table.sort(scores, function(a, b) return a.score > b.score end)

        local expectedScore = 1 / participants

        for _, con in ipairs(scores) do
            local reward, stamina -- ignoring stamina for now because I heard you receive rewards even when it's depleted  
            if con.player then  
                reward = con.player:getReward(timestamp, true)
                stamina = con.player:getStamina()
            else
                stamina = con.stamina or 0
            end

            local playerLoot
            if --[[stamina > 840 and]] con.score ~= 0 then
                local lootFactor = 1
                lootFactor = lootFactor / participants ^ (1 / 3) -- tone down the loot a notch if there are many participants
                lootFactor = lootFactor * (1 + lootFactor) ^ (con.score / expectedScore) -- increase the loot multiplicatively by how many times the player surpassed the expected score
                playerLoot = monsterType:getBossReward(lootFactor, _ == 1)

                if con.player then
                    for _, p in ipairs(playerLoot) do
                        reward:addItem(p[1], p[2])
                    end
                end
            end

            if con.player then
                local lootMessage = {"The following items are available in your reward chest: "}

                if --[[stamina > 840]]true then
                    reward:getContentDescription(lootMessage)
                else
                    table.insert(lootMessage, 'nothing (due to low stamina)')
                end
                table.insert(lootMessage, ".")
                con.player:sendTextMessage(MESSAGE_EVENT_ADVANCE, table.concat(lootMessage))
            else
                insertRewardItems(con.guid, timestamp, playerLoot)
            end
        end

        globalBosses[bossId] = nil
    end
    return true
end

function onThink(creature, interval)
    local bossId = creature:getId()
    local info = globalBosses[bossId]
    -- Reset all players' status
    for _, player in pairs(info) do
        player.active = false
    end
   
    -- Set all players in boss' target list as active in the fight
    local targets = creature:getTargetList()
    for _, target in ipairs(targets) do
        if target:isPlayer() then
            local stats = getPlayerStats(bossId, target:getGuid(), true)
            stats.playerId = target:getId() -- Update player id
            stats.active = true          
        end
    end
end

function onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin)
    if not next(globalBosses) then
        return primaryDamage, primaryType, secondaryDamage, secondaryType
    end

    if not creature or not attacker then
        return primaryDamage, primaryType, secondaryDamage, secondaryType
    end

    local stats = creature:inBossFight()
    if not stats then
        return primaryDamage, primaryType, secondaryDamage, secondaryType
    end

    local creatureId, attackerId = creature:getId(), attacker:getId()
    stats.playerId = creatureId -- Update player id

    -- Account for healing of others active in the boss fight
    if primaryType == COMBAT_HEALING and attacker:isPlayer() and attackerId ~= creatureId then
        local healerStats = getPlayerStats(stats.bossId, attacker:getGuid(), true)
        healerStats.active = true
        healerStats.playerId = attackerId -- Update player id
        healerStats.healing = healerStats.healing + primaryDamage
    elseif stats.bossId == attackerId then
        -- Account for damage taken from the boss
        stats.damageIn = stats.damageIn + primaryDamage
    end
    return primaryDamage, primaryType, secondaryDamage, secondaryType
end
To save the items in the reward box when the player logs out, you need to add a database insert/update query that saves the items in the reward box to the database. You can add this query to the onLogout function or another appropriate function that is called when the player logs out.

Here's an example of how you can modify the insertRewardItems function to save the items in the reward box to the database:

Lua:
local function insertRewardItems(playerGuid, timestamp, itemList)
db.asyncStoreQuery('SELECT `pid`, `sid` FROM `player_rewards` WHERE player_id = ' .. playerGuid .. ' ORDER BY `sid` ASC;',
function(query)
local lastReward = 0
local lastStoreId 
if query then 
repeat
local sid = result.getDataInt(query, 'sid')
local pid = result.getDataInt(query, 'pid')

if pid < 100 then
lastReward = pid
end
lastStoreId = sid
until not result.next(query)
end

local buffer = {'INSERT INTO `player_rewards` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES'}

--reward bag
local info = {
playerGuid = playerGuid,
running = lastStoreId or 100
}

local bag = Game.createItem(ITEM_REWARD_CONTAINER)
bag:setAttribute(ITEM_ATTRIBUTE_DATE, timestamp)

if itemList then
for _, item in ipairs(itemList) do
bag:addItem(item[1], item[2])
end
end

local total = insertItems(buffer, info, lastReward + 1, {bag})
table.insert(buffer, ";")

if total ~= 0 then
db.query(table.concat(buffer))
end
end
)
end

In this example, we create a new item bag of type ITEM_REWARD_CONTAINER, set the ITEM_ATTRIBUTE_DATE attribute to the current timestamp, and add the items in itemList to the bag. We then call the insertItems function to generate the SQL query to insert the items in the bag into the database. Finally, we execute the SQL query with db.query.

To save the items in the reward box when the player logs out, you can call this insertRewardItems function from the onLogout function, like this:


Lua:
function onLogout(player)
local itemList = {}
local rewardContainer = player:getRewardContainer()
if rewardContainer then
for i = 0, rewardContainer:getSize() - 1 do
local item = rewardContainer:getItem(i)
table.insert(itemList, {item:getId(), item:getCount()})
end
end
insertRewardItems(player:getGuid(), os.time(), itemList)
end

In this example, we first retrieve the items in the player's reward container with player:getRewardContainer(). We then loop through the items in the container and add them to itemList. Finally, we call insertRewardItems with the player's GUID, the current timestamp, and itemList. This will save the items in the player's reward container to the database when the player logs out.
 
To save the items in the reward box when the player logs out, you need to add a database insert/update query that saves the items in the reward box to the database. You can add this query to the onLogout function or another appropriate function that is called when the player logs out.

Here's an example of how you can modify the insertRewardItems function to save the items in the reward box to the database:

Lua:
local function insertRewardItems(playerGuid, timestamp, itemList)
db.asyncStoreQuery('SELECT `pid`, `sid` FROM `player_rewards` WHERE player_id = ' .. playerGuid .. ' ORDER BY `sid` ASC;',
function(query)
local lastReward = 0
local lastStoreId
if query then
repeat
local sid = result.getDataInt(query, 'sid')
local pid = result.getDataInt(query, 'pid')

if pid < 100 then
lastReward = pid
end
lastStoreId = sid
until not result.next(query)
end

local buffer = {'INSERT INTO `player_rewards` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES'}

--reward bag
local info = {
playerGuid = playerGuid,
running = lastStoreId or 100
}

local bag = Game.createItem(ITEM_REWARD_CONTAINER)
bag:setAttribute(ITEM_ATTRIBUTE_DATE, timestamp)

if itemList then
for _, item in ipairs(itemList) do
bag:addItem(item[1], item[2])
end
end

local total = insertItems(buffer, info, lastReward + 1, {bag})
table.insert(buffer, ";")

if total ~= 0 then
db.query(table.concat(buffer))
end
end
)
end

In this example, we create a new item bag of type ITEM_REWARD_CONTAINER, set the ITEM_ATTRIBUTE_DATE attribute to the current timestamp, and add the items in itemList to the bag. We then call the insertItems function to generate the SQL query to insert the items in the bag into the database. Finally, we execute the SQL query with db.query.

To save the items in the reward box when the player logs out, you can call this insertRewardItems function from the onLogout function, like this:


Lua:
function onLogout(player)
local itemList = {}
local rewardContainer = player:getRewardContainer()
if rewardContainer then
for i = 0, rewardContainer:getSize() - 1 do
local item = rewardContainer:getItem(i)
table.insert(itemList, {item:getId(), item:getCount()})
end
end
insertRewardItems(player:getGuid(), os.time(), itemList)
end

In this example, we first retrieve the items in the player's reward container with player:getRewardContainer(). We then loop through the items in the container and add them to itemList. Finally, we call insertRewardItems with the player's GUID, the current timestamp, and itemList. This will save the items in the player's reward container to the database when the player logs out.
Dont work
 
If you already made the change to solve the issue of saving the items in the Dp?, perhaps it can be implemented similarly to the chest reward. However, I cannot guarantee for sure. It would be interesting to do a test to see if the items are being saved correctly or not.
If you still haven't made the correction to resolve the cloning bug or not saving in Dp, I suggest you test and modify your source. Then test the reward system to verify that items are being saved correctly.
 
If you already made the change to solve the issue of saving the items in the Dp?, perhaps it can be implemented similarly to the chest reward. However, I cannot guarantee for sure. It would be interesting to do a test to see if the items are being saved correctly or not.
If you still haven't made the correction to resolve the cloning bug or not saving in Dp, I suggest you test and modify your source. Then test the reward system to verify that items are being saved correctly.
did not work
 
Back
Top