• 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+ [TFS 1.3] Reward Chest - works properly unless boss is summoned by server.

Fablow

Intermediate OT User
Content Editor
Joined
May 7, 2008
Messages
1,565
Solutions
6
Reaction score
145
Location
Canada
GitHub
Fablow77
Hello,

The reward chest system in my server works perfect until the boss is summoned in a creature, global or raid event. It will not give the players the reward. However, if the monster is in spawn (say I pick dragons) the rewards show up as they should. Even if i summon the boss on my GM it will work.

Is there a way around this?

Creaturescripts:
Code:
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 _, p in ipairs(itemList) do
                    bag:addItem(p[1], p[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

Lib:
Code:
if not globalBosses then
    globalBosses = {}
end

function Monster.setReward(self, enable)
    if enable then
        if not self:getType():isRewardBoss() then
            error("Rewards can only be enabled to rewards bosses.")
            return false
        end
        globalBosses[self:getId()] = {}
        self:registerEvent("BossDeath")
        self:registerEvent("BossThink")
    else
        globalBosses[self:getId()] = nil
        self:unregisterEvent("BossDeath")
        self:unregisterEvent("BossThink")
    end
    return true
end

local function pushValues(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

function Item.getNameDescription(self)
    local subType = self:getSubType()
    local itemType = self:getType()

    local buffer = {}

    local name = self:getName() or ''
    if(#name ~= 0) then
        if(itemType:isStackable() and subType > 1) then
            pushValues(buffer, ' ', subType, self:getPluralName())
        else
            local article = self:getArticle() or ''
            pushValues(buffer, ' ', select(#article ~= 0 and 1 or 2, article, name))
        end
    else
        pushValues(buffer, ' ', 'an item of type', self:getId())
    end

    return table.concat(buffer)
end

function Container.getContentDescription(self, outputBuffer)
    local firstItem = true
    local buffer = outputBuffer or {}
    for i = 1, self:getSize() do
        local item = self:getItem(i - 1)

        if(firstItem) then
            firstItem = false
        else
            table.insert(buffer, ", ")
        end

        table.insert(buffer, item:getNameDescription())
    end

    if firstItem then
        table.insert(buffer, "nothing")
    end

    if not outputBuffer then
        return table.concat(buffer)
    end
end

function Player.getRewardChest(self, autocreate)
    return self:getDepotChest(99, autocreate)
end

function Player.inBossFight(self)
    if not next(globalBosses) then
        return false
    end
    local playerGuid = self:getGuid()

    for _, info in pairs(globalBosses) do
        local stats = info[playerGuid]
        if stats and stats.active then
            return stats
        end
    end
    return false
end

-- by https://otland.net/members/cbrm.25752/ with some modifications
function MonsterType.createLootItem(self, lootBlock, chance, lootTable)
    local lootTable, itemCount = lootTable or {}, 0
    local randvalue = math.random(0, 100000) / (getConfigInfo("rateLoot") * chance)
    if randvalue < lootBlock.chance then
        if (ItemType(lootBlock.itemId):isStackable()) then
            itemCount = randvalue % lootBlock.maxCount + 1
        else
            itemCount = 1
        end
    end

    while itemCount > 0 do
        local n = math.min(itemCount, 100)
        itemCount = itemCount - n
        table.insert(lootTable, {lootBlock.itemId, n})
    end

    return lootTable
end

function MonsterType.getBossReward(self, lootFactor, topScore)
    local result = {}
    if getConfigInfo("rateLoot") > 0 then
        local loot = self:getLoot() or {}
        for i = #loot, 0, -1 do
            local lootBlock = loot[i]
            if lootBlock then
                if lootBlock.unique and not topScore then
                    --continue
                else
                    self:createLootItem(lootBlock, lootFactor, result)
                end
            end
        end
    end
    return result
end
 
Back
Top