• 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+ Some scripts for fix extra loot, boss chest reward, potion

damian00912

New Member
Joined
Sep 11, 2009
Messages
82
Reaction score
2
Hallo, i have problem, sometime, not every time, but sometime show me this error in logs. Someone know why?

Lua:
Lua Script Error: [Main Interface]
(Unknown scriptfile)
...ata/scripts/creaturescripts/others/boss_reward_chest.lua:21: attempt to call global 'serializeAttributes' (a nil value)
stack traceback:
    [C]: in function 'serializeAttributes'
    ...ata/scripts/creaturescripts/others/boss_reward_chest.lua:21: in function 'insertItems'
    ...ata/scripts/creaturescripts/others/boss_reward_chest.lua:73: in function <...ata/scripts/creaturescripts/others/boss_reward_chest.lua:42>


script:

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 item ~= nil then
            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.escapeString(serializeAttributes(item)))
            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
    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

local function resetAndSetTargetList(creature)
    if not creature then
        return
    end

    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

local bossDeath = CreatureEvent("BossDeath")
function bossDeath.onDeath(creature, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified)
    -- player
    if not creature or creature:isPlayer() then
        return true
    end

        -- Deny summons
    if creature:getMaster() then
        return true
    end
   
    -- boss
    local monsterType = creature:getType()
    if monsterType and monsterType:isRewardBoss() then -- Make sure it is a boss
        local bossId = creature:getId()
        local timestamp = os.time()

        resetAndSetTargetList(creature)

        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 get 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 and con.score ~= 0 then
                local lootMessage = ("The following items dropped by %s are available in your reward chest: %s"):format(creature:getName(), reward:getContentDescription())

                if stamina > 840 then
                    reward:getContentDescription(lootMessage)
                end
                con.player:sendTextMessage(MESSAGE_LOOT, lootMessage)
            elseif con.score ~= 0 then
                insertRewardItems(con.guid, timestamp, playerLoot)
            end
        end

        globalBosses[bossId] = nil
    end
    return true
end
bossDeath:register()

local bossThink = CreatureEvent("BossThink")
function bossThink.onThink(creature, interval)
    resetAndSetTargetList(creature)
end
bossThink:register()

local bossParticipation = CreatureEvent("BossParticipation")
function bossParticipation.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
bossParticipation:register()

local loginBossPlayer = CreatureEvent("LoginBossPlayer")
function loginBossPlayer.onLogin(player)
    player:registerEvent("BossDeath")
    return true
end
loginBossPlayer:register()

One more problem is with extra loot, also ont every drop only sometime show me this in log.

Lua:
Lua Script Error: [Scripts Interface]
/home/abcd/data/scripts/creaturescripts/others/extraloot.lua:callback
...data/scripts/creaturescripts/others/extraloot.lua:49: attempt to call method 'getType' (a nil value)
stack traceback:
    [C]: in function 'getType'
    ...data/scripts/creaturescripts/others/extraloot.lua:49: in function <...data/scripts/creaturescripts/others/extraloot.lua:39>

here is code:

Lua:
math.randomseed(os.time())
local extra_loot = {
    {items = {
        {id = 40407, chance = 6000}
    }}
}

local extra_loot_d = CreatureEvent("extra_loot_d")

function Container:addExtraLoot(c, t)
    if t.hasName then
        local cn = c:getName():lower()
        local cm = t.hasName:lower()
        if not cn:match(cm) then
            return true
        end
    end
  
    for i = 1, #t.items do
        local count = 1
        if t.items[i].count then
            if t.items[i].countMax then
                count = math.random(t.items[i].count, t.items[i].countMax)
            else
                count = t.items[i].count
            end
        else
            if t.items[i].countMax then
                count = math.random(1, t.items[i].countMax)
            end
        end
      
        if math.random(0, 100000) <= t.items[i].chance then
            self:addItem(t.items[i].id, count)
        end
    end
end

function extra_loot_d.onDeath(creature, corpse, killer, mostDamage, unjustified, mostDamage_unjustified)
    if not creature:isMonster() then
    return true
    end

        if creature:getMaster() then
        return true
        end
          
            if corpse then
            local itemType = corpse:getType()
            if itemType:isCorpse() and itemType:isContainer() then
            for i = 1, #extra_loot do
            corpse:addExtraLoot(creature, extra_loot[i])
            end
      
        end
    return true
end
end

extra_loot_d:register()

And last problem is with potions, also only one time on maybe 1k use show this error

Code:
Lua Script Error: [Scripts Interface]
/home/otxnowy/data/scripts/actions/other/potions.lua:callback
luaDoTargetCombatMana(). Creature not found
stack traceback:
    [C]: in function 'doTargetCombatMana'
    /home/otxnowy/data/scripts/actions/other/potions.lua:255: in function </home/otxnowy/data/scripts/actions/other/potions.lua:224>

Lua Script Error: [Scripts Interface]
/home/otxnowy/data/scripts/actions/other/potions.lua:callback
/home/otxnowy/data/scripts/actions/other/potions.lua:263: attempt to call method 'say' (a nil value)
stack traceback:
    [C]: in function 'say'
    /home/otxnowy/data/scripts/actions/other/potions.lua:263: in function </home/otxnowy/data/scripts/actions/other/potions.lua:224>

Script potions:

Lua:
local berserk = Condition(CONDITION_ATTRIBUTES)
berserk:setParameter(CONDITION_PARAM_TICKS, 10 * 60 * 1000)
berserk:setParameter(CONDITION_PARAM_SKILL_MELEE, 5)
berserk:setParameter(CONDITION_PARAM_SKILL_SHIELD, -10)
berserk:setParameter(CONDITION_PARAM_BUFF_SPELL, true)

local mastermind = Condition(CONDITION_ATTRIBUTES)
mastermind:setParameter(CONDITION_PARAM_TICKS, 10 * 60 * 1000)
mastermind:setParameter(CONDITION_PARAM_STAT_MAGICPOINTS, 3)
mastermind:setParameter(CONDITION_PARAM_BUFF_SPELL, true)

local bullseye = Condition(CONDITION_ATTRIBUTES)
bullseye:setParameter(CONDITION_PARAM_TICKS, 10 * 60 * 1000)
bullseye:setParameter(CONDITION_PARAM_SKILL_DISTANCE, 5)
bullseye:setParameter(CONDITION_PARAM_SKILL_SHIELD, -10)
bullseye:setParameter(CONDITION_PARAM_BUFF_SPELL, true)

local antidote = Combat()
antidote:setParameter(COMBAT_PARAM_TYPE, COMBAT_HEALING)
antidote:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE)
antidote:setParameter(COMBAT_PARAM_DISPEL, CONDITION_POISON)
antidote:setParameter(COMBAT_PARAM_AGGRESSIVE, false)
antidote:setParameter(COMBAT_PARAM_TARGETCASTERORTOPMOST, true)

local exhaust = Condition(CONDITION_EXHAUST_HEAL)
exhaust:setParameter(CONDITION_PARAM_TICKS, (configManager.getNumber(configKeys.EX_ACTIONS_DELAY_INTERVAL) - 1000))
-- 1000 - 100 due to exact condition timing. -100 doesn't hurt us, and players don't have reminding ~50ms exhaustion.

local function magicshield(player)
local condition = Condition(CONDITION_MANASHIELD)
condition:setParameter(CONDITION_PARAM_TICKS, 60000)
condition:setParameter(CONDITION_PARAM_MANASHIELD, math.min(player:getMaxMana(), 300 + 7.6 * player:getLevel() + 7 * player:getMagicLevel()))
player:addCondition(condition)
end

local potions = {
    [6558] = {
        transform = {
            id = {7588, 7589}
        },
        effect = CONST_ME_DRAWBLOOD
    },
    [7439] = {
        vocations = {
            VOCATION.CLIENT_ID.KNIGHT
        },
        condition = berserk,
        effect = CONST_ME_MAGIC_RED,
        description = "Only knights may drink this potion.",
        text = "You feel stronger."
    },
    [7440] = {
        vocations = {
            VOCATION.CLIENT_ID.SORCERER,
            VOCATION.CLIENT_ID.DRUID
        },
        condition = mastermind,
        effect = CONST_ME_MAGIC_BLUE,
        description = "Only sorcerers and druids may drink this potion.",
        text = "You feel smarter."
    },
    [7443] = {
        vocations = {
            VOCATION.CLIENT_ID.PALADIN
        },
        condition = bullseye,
        effect = CONST_ME_MAGIC_GREEN,
        description = "Only paladins may drink this potion.",
        text = "You feel more accurate."
    },
    [40398] = {
        vocations = {
            VOCATION.CLIENT_ID.SORCERER,
            VOCATION.CLIENT_ID.DRUID
        },
        level = 14,
        func = magicshield,
        effect = CONST_ME_ENERGYAREA,
        description = "Only sorcerers and druids of level 14 or above may drink this potion.",
    },
    [7588] = {
        health = {
            250,
            350
        },
        vocations = {
            VOCATION.CLIENT_ID.PALADIN,
            VOCATION.CLIENT_ID.KNIGHT
        },
        level = 50,
        flask = 7634,
        description = "Only knights and paladins of level 50 or above may drink this fluid."
    },
    [7589] = {
        mana = {
            115,
            185
        },
        level = 50,
        flask = 7634,
        description = "Only players of level 50 or above may drink this fluid."
    },
    [7590] = {
        mana = {
            150,
            250
        },
        vocations = {
            VOCATION.CLIENT_ID.SORCERER,
            VOCATION.CLIENT_ID.DRUID,
            VOCATION.CLIENT_ID.PALADIN
        },
        level = 80,
        flask = 7635,
        description = "Only sorcerers, druids and paladins of level 80 or above may drink this fluid."
    },
    [7591] = {
        health = {
            425,
            575
        },
        vocations = {
            VOCATION.CLIENT_ID.KNIGHT
        },
        level = 80,
        flask = 7635,
        description = "Only knights of level 80 or above may drink this fluid."
    },
    [7618] = {
        health = {
            125,
            175
        },
        flask = 7636
    },
    [7620] = {
        mana = {
            75,
            125
        },
        flask = 7636
    },
    [8472] = {
        health = {
            250,
            350
        },
        mana = {
            100,
            200
        },
        vocations = {
            VOCATION.CLIENT_ID.PALADIN
        },
        level = 80,
        flask = 7635,
        description = "Only paladins of level 80 or above may drink this fluid."
    },
    [8473] = {
        health = {650, 850},
        vocations = {
            VOCATION.CLIENT_ID.KNIGHT
        },
        level = 130,
        flask = 7635,
        description = "Only knights of level 130 or above may drink this fluid."
    },
    [8474] = {
        combat = antidote,
        flask = 7636
    },
    [8704] = {
        health = {
            60,
            90
        },
        flask = 7636
    },
    [26029] = {
        mana = {
            425,
            575
        },
        vocations = {
            VOCATION.CLIENT_ID.SORCERER,
            VOCATION.CLIENT_ID.DRUID
        },
        level = 130,
        flask = 7635,
        description = "Only druids and sorcerers of level 130 or above may drink this fluid."
    },
    [26030] = {
        health = {
            420,
            580
        },
        mana = {
            250,
            350
        },
        vocations = {
            VOCATION.CLIENT_ID.PALADIN
        },
        level = 130,
        flask = 7635,
        description = "Only paladins of level 130 or above may drink this fluid."
    },
    [26031] = {
        health = {
            875,
            1125
        },
        vocations = {
            VOCATION.CLIENT_ID.KNIGHT
        },
        level = 200,
        flask = 7635,
        description = "Only knights of level 200 or above may drink this fluid."
    }
}

local flaskPotion = Action()

function flaskPotion.onUse(player, item, fromPosition, target, toPosition, isHotkey)
    if type(target) == "userdata" and not target:isPlayer() then
        return false
    end

    -- Delay potion
    if not playerDelayPotion[player:getId()] then
        playerDelayPotion[player:getId()] = 0
    end
    if playerDelayPotion[player:getId()] > os.mtime() then
        player:sendTextMessage(MESSAGE_FAILURE, Game.getReturnMessage(RETURNVALUE_YOUAREEXHAUSTED))
        return true
    end

    local potion = potions[item:getId()]
    if potion.level and player:getLevel() < potion.level or potion.vocations and not table.contains(potion.vocations, player:getVocation():getClientId()) and not (player:getGroup():getId() >= 2) then
        player:say(potion.description, MESSAGE_POTION)
        return true
    end

    if player:getCondition(CONDITION_EXHAUST_HEAL) then
        player:sendTextMessage(MESSAGE_FAILURE, Game.getReturnMessage(RETURNVALUE_YOUAREEXHAUSTED))
        return true
    end

    if potion.health or potion.mana or potion.combat then
        if potion.health then
            doTargetCombatHealth(0, target, COMBAT_HEALING, potion.health[1], potion.health[2], CONST_ME_MAGIC_BLUE)
        end

        if potion.mana then
            doTargetCombatMana(0, target, potion.mana[1], potion.mana[2], CONST_ME_MAGIC_BLUE)
        end

        if potion.combat then
            potion.combat:execute(target, Variant(target:getId()))
        end

        player:addAchievementProgress('Potion Addict', 100000)
        target:say("Aaaah...", MESSAGE_POTION)
        player:addCondition(exhaust)
        player:setStorageValue(38412, player:getStorageValue(38412)+1)
    end

    -- Delay potion
    playerDelayPotion[player:getId()] = os.mtime() + 500
    
    if potion.func then
        potion.func(player)
        if potion.text then
            player:say(potion.text, MESSAGE_POTION)
        end
        player:getPosition():sendMagicEffect(potion.effect)
    end

    if potion.condition then
        player:addCondition(potion.condition)
        player:say(potion.text, MESSAGE_POTION)
        player:getPosition():sendMagicEffect(potion.effect)
    end

    if potion.transform then
        if item:getCount() >= 1 then
            item:remove(1)
            player:addItem(potion.transform.id[math.random(#potion.transform.id)], 1)
            item:getPosition():sendMagicEffect(potion.effect)
            return true
        end
    end

    if not configManager.getBoolean(configKeys.REMOVE_POTION_CHARGES) then
        return true
    end

    player:updateSupplyTracker(item)
    item:remove(1)
    return true
end

for index, value in pairs(potions) do
    flaskPotion:id(index)
end

flaskPotion:register()
Thanks for help!
 

Sarah Wesker

ค∂vαηcε รүηтαx ❤
Support Team
Joined
Mar 16, 2017
Messages
991
Solutions
98
Reaction score
1,021
Location
London
GitHub
MillhioreBT
I think asking for help with multiple problems in the same thread is not a pretty thing to see
in the next occasion it would be much better if you published each problem separately, it is easier for you to receive help if you publish a specific problem instead of several at the same time in a thread

If someone gives you an answer for the first, and then someone else gives you the answer for the second, they will not be able to give you the best answer to both.
 
Top