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

OnAdvance give permanent bonus is possible?

E

Evil Puncker

Guest
TFS 1.3 latest:

Is it possible to give a random permanent bonus to a character using the OnAdvance creature event? Is yes, how to? (with a table where I can set the bonuses and chances to be chosen from each level)
 
Solution
Put this in /data/scripts and name it whatever you'd like:
Lua:
playerBonusConfig = {
    --[[
        type: Unique identifier for the bonus, will automatically be configured to a constant as well (e.g. PLAYERBONUS_ARMOR)
        readableString: String used when showing the player they received the bonus
        roll: Obvious, but math.random(min, max)
        loadOnLogin: Load the bonus when player logs in, since it's not saved to database
        callback: Callback for when you require special functionality (aka when basic storages aren't enough)
    ]]
    bonuses = {
        {
            type = "armor",
            readableString = "Armor",
            loadOnLogin = false,
            roll = {min = 5, max = 10},
            -- We...
Your question is pretty vague, it's impossible to say without knowing what you mean by bonuses.
oh yeah, sorry :p by bonuses I mean HP, Mana, Critical chance/damage, Armor, HP Regen etc

for example: every 5 level a character will earn 3 Armor, and another one will get 5 armor, and someone else will get 1% critical chance and 2% critical damage etc, every 5 level it will be random
 
Last edited by a moderator:
Hi,
I did the code rushy, can be better.
I tryed to comment the code, to you understand what im doing.
Try this:
data/creaturescripts/scripts/bonuslevel.lua:
Lua:
local config = {
    --avaliable bonus
    --["bonusName"] = {min = Min Bonus Value, max = Max Bonus Value}
    bonus = {
        ["hp"] = {min = 100, max = 200},
        ["mp"] = {min = 100, max = 200},
        ["critchance"] = {min = 10, max = 20},
        ["critdamage"] = {min = 10, max = 50},
    },
    lastRecivedBonusLevel = 2000, --storage to hold the last level that player recived the bonus
    bonusInEachLevel = 3, --the player will recive bonus in each 3 levels
}


function onAdvance(player, skill, oldLevel, newLevel)
    if skill ~= SKILL_LEVEL then --check if skill is LEVEL type
        return true
    end
    local lastLevel = player:getStorageValue(config.lastRecivedBonusLevel)--Get the last level that player recived the bonus
    if not lastLevel or lastLevel < 0 then --if nil or lower than 0(default is -1)
        lastLevel = 0 --set to 0, to calculate how many times player will recive the bonus
    end
    local timesToGiveBonus = math.floor((newLevel - lastLevel)/config.bonusInEachLevel)--calculate how many times player will recive the bonus
    if timesToGiveBonus >= 1 then--if more than 1 time
        for times = 1, timesToGiveBonus do--for each time
            local randomIndex = math.random(1, table.size(config.bonus)) --get a random index of table
            local indexCount = 1 --instantiate a counter to bonus table index starting by 1(default)
            for bonusName, bonusInfo in pairs(config.bonus) do --for each key, value in table
                --NOTE: pairs() will be unordained a iteration, but always will be the same unordained order
                if indexCount == randomIndex then--if the randomIndex is equal to the indexCount
                    giveBonus(player, bonusName, bonusInfo)--call a giveBonus function passing the bonusName and bonusInfo
                    break--break the loop and go for next bonus
                end
                indexCount = indexCount + 1 --incress the indexCount by 1
            end
        end
        player:setStorageValue(config.lastRecivedBonusLevel, newLevel)--Set the last level that player recived the bonus, to newLevel
    end
    return true
end

--this can be better
function giveBonus(player, bonusName, bonusInfo)
    local bonusAmount = math.random(bonusInfo.min, bonusInfo.max)--get a random value from min and max value of bonusInfo
    if bonusName == "hp" then--if bonus is Hp
        player:setMaxHealth(player:getMaxHealth() + bonusAmount)--set maxHP to current player maxHP + bonusAmount
    elseif bonusName == "mp" then--if bonus if Mp
        player:setMaxMana(player:getMaxMana() + bonusAmount)--set maxMP to current player maxMP + bonusAmount
    elseif bonusName == "critchance" then--if bonus is critchance
        --add your crit chance
    elseif bonusName == "critdamage" then--if bonus is critdamage
        --add your crit damage
    else--if bonus type don't exist, report error by PRINT on console
        print("[ERROR BONUS LEVEL] The player: "..player:getName().." recived a bonus that don't exist. BonusName = "..bonusName..".")
    end
    return true
end
data/creaturescripts/creaturescripts.xml:
XML:
<event type="advance" name="bonusLevel" script="bonuslevel.lua" />
Register the creaturescript event in player, in data/creaturescripts/scripts/login.lua above return true put:
Lua:
player:registerEvent("bonusLevel")

If you have a lib to store your custom functions, put this function inside:
Lua:
function table.size(t)
    local size = 0
    for k, v in pairs(t) do
        size = size + 1
    end
    return size
end
If don't and don't know how to add, let me know.

Remember to configure the script to work as you want and if you add a new bonus in config.bonus table, add to giveBonus function too.
I did some tests, but im without time now. If you found some problem, i can help later, just send me a PM.

You asked for player gain Armor, i don't know how to add Armor to player... If you discover, please teach me.
I hope it works.

Have a Happy New Year!
 
Put this in /data/scripts and name it whatever you'd like:
Lua:
playerBonusConfig = {
    --[[
        type: Unique identifier for the bonus, will automatically be configured to a constant as well (e.g. PLAYERBONUS_ARMOR)
        readableString: String used when showing the player they received the bonus
        roll: Obvious, but math.random(min, max)
        loadOnLogin: Load the bonus when player logs in, since it's not saved to database
        callback: Callback for when you require special functionality (aka when basic storages aren't enough)
    ]]
    bonuses = {
        {
            type = "armor",
            readableString = "Armor",
            loadOnLogin = false,
            roll = {min = 5, max = 10},
            -- We don't need a callback for armor since it's handled by storage value in onHealth/onManaChange events
        },
        {
            type = "health",
            readableString = "Maximum Health",
            roll = {min = 5, max = 10},
            loadOnLogin = false,
            callback = function(player, currentBonus, modifier)
                player:setMaxHealth(player:getMaxHealth() + modifier)
                player:addHealth(modifier)
            end
        },
        {
            type = "mana",
            readableString = "Maximum Mana",
            roll = {min = 5, max = 10},
            loadOnLogin = false,
            callback = function(player, currentBonus, modifier)
                player:setMaxMana(player:getMaxMana() + modifier)
                player:addMana(modifier)
            end
        },
        {
            type = "criticalChance",
            readableString = "Critical Hit Chance",
            roll = {min = 5, max = 10},
            loadOnLogin = true,
            callback = function(player, currentBonus, modifier)
                player:addSpecialSkill(SPECIALSKILL_CRITICALHITCHANCE, modifier)
            end
        },
        {
            type = "criticalAmount",
            readableString = "Critical Hit Amount",
            roll = {min = 5, max = 10},
            loadOnLogin = true,
            callback = function(player, currentBonus, modifier)
                player:addSpecialSkill(SPECIALSKILL_CRITICALHITAMOUNT, modifier)
            end
        },
        {
            type = "healthRegeneration",
            readableString = "Health Regeneration",
            roll = {min = 5, max = 10},
            loadOnLogin = true,
            condition = Condition(CONDITION_REGENERATION, CONDITIONID_DEFAULT),
            callback = function(player, currentBonus, modifier)
                local subId = 501
                local condition = player:getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT, subId)
                if condition then
                    condition:setParameter(CONDITION_PARAM_HEALTHGAIN, currentBonus + modifier)
                else
                    condition = playerBonusConfig.bonuses[6].condition
                    condition:setTicks(-1)
                    condition:setParameter(CONDITION_PARAM_SUBID, subId)
                    condition:setParameter(CONDITION_PARAM_HEALTHTICKS, 1000)
                    condition:setParameter(CONDITION_PARAM_HEALTHGAIN, currentBonus + modifier)
                    player:addCondition(condition)
                end
            end
        },
        {
            type = "manaRegeneration",
            readableString = "Mana Regeneration",
            roll = {min = 5, max = 10},
            loadOnLogin = true,
            condition = Condition(CONDITION_REGENERATION, CONDITIONID_DEFAULT),
            callback = function(player, currentBonus, modifier)
                local subId = 502
                local condition = player:getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT, subId)
                if condition then
                    condition:setParameter(CONDITION_PARAM_MANAGAIN, currentBonus + modifier)
                else
                    condition = playerBonusConfig.bonuses[7].condition
                    condition:setTicks(-1)
                    condition:setParameter(CONDITION_PARAM_SUBID, subId)
                    condition:setParameter(CONDITION_PARAM_MANATICKS, 1000)
                    condition:setParameter(CONDITION_PARAM_MANAGAIN, currentBonus + modifier)
                    player:addCondition(condition)
                end
            end
        }
    },

    baseStorage = 12345, -- Base storage key to start from, the rest are assigned based on baseStorage and adds + 1 for each bonus in the config
    perLevel = 5, -- Random bonus applied at each level interval defined here
    msg = {
        type = MESSAGE_STATUS_CONSOLE_ORANGE,
        --[[
            %s : readableString
            %d (1): Bonus modifier (current + >>modifier<<)
            %d (2): Total bonus (value of current + modifier)
        ]]
        string = "Your %s has been increased by %d, your total is now %d."
    }
}

for i, bonus in ipairs(playerBonusConfig.bonuses) do
    -- Add constants for bonus types, e.g. PLAYERBONUS_ARMOR -> "armor"
    _G["PLAYERBONUS_" .. bonus.type:upper()] = bonus.type

    -- Automatically assign storage keys because fuck doing them all manually
    playerBonusConfig.bonuses[i].storage = playerBonusConfig.baseStorage + i
end


-- Lib functions

function getBonusByType(bonusType)
    for _, bonus in ipairs(playerBonusConfig.bonuses) do
        if bonus.type == bonusType then
            return bonus
        end
    end
    return nil
end

function Player.addRandomBonus(self)
    local bonus = playerBonusConfig.bonuses[math.random(#playerBonusConfig.bonuses)]
    return self:addBonus(bonus.type, math.random(bonus.roll.min, bonus.roll.max))
end

function Player.addBonus(self, bonusType, modifier)
    local bonus = bonusType
    if type(bonus) == "string" then
        bonus = getBonusByType(bonus)
    end

    if not bonus then
        print("[Player.addBonus] Invalid bonus type given (".. tostring(bonusType) ..")")
        return false
    end

    -- Add modifier to the cumulative bonus
    local currentBonus = self:getBonus(bonus)
    self:setStorageValue(bonus.storage, currentBonus + modifier)
    if bonus.callback then
        bonus.callback(self, currentBonus, modifier)
    end

    -- bonus, modifier, total
    return bonus, modifier, currentBonus + modifier
end

function Player.getBonus(self, bonusType)
    local bonus = bonusType
    if type(bonus) == "string" then
        bonus = getBonusByType(bonus)
    end

    if not bonus then
        print("[Player.getBonus] Invalid bonus type given (".. tostring(bonusType) ..")")
        return 0
    end

    local value = self:getStorageValue(bonus.storage)
    return value == -1 and 0 or value
end




-- Below are all events, no touchy touchy

local function armorFormula(player, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType)
    -- Make sure we're being hit and the damage is negative (avoid the formula on healing)
    if not attacker or primaryType == COMBAT_HEALING then
        return primaryDamage, secondaryDamage
    end

    local armor = player:getBonus(PLAYERBONUS_ARMOR)

    if armor == 0 then
        return primaryDamage, secondaryDamage
    end

    -- Apply default armor formula from Creature::blockHit
    if armor > 3 then
        primaryDamage = primaryDamage - (math.random(armor / 2, armor - (armor % 2 + 1)))
        secondaryDamage = secondaryDamage - (math.random(armor / 2, armor - (armor % 2 + 1)))
    else
        primaryDamage = primaryDamage - 1
        secondaryDamage = secondaryDamage - 1
    end

    return math.max(0, primaryDamage), math.max(0, secondaryDamage)
end

-- HealthChange Armor mitigation event

local eventHpChange = CreatureEvent("bonuses_armorHpEvent")
eventHpChange:type("healthChange")

function eventHpChange.onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin)
    primaryDamage, secondaryDamage = armorFormula(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType)
    return primaryDamage, primaryType, secondaryDamage, secondaryType
end

eventHpChange:register()

-- ManaChange Armor mitigation event

local eventMpChange = CreatureEvent("bonuses_armorMpEvent")
eventMpChange:type("manaChange")

function eventMpChange.onManaChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin)
    primaryDamage, secondaryDamage = armorFormula(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType)
    return primaryDamage, primaryType, secondaryDamage, secondaryType
end

eventMpChange:register()

-- Advance event to give random bonuses to the player

local eventAdvance = CreatureEvent("bonuses_onAdvance")
eventAdvance:type("advance")

function eventAdvance.onAdvance(player, skill, oldLevel, newLevel)
    if skill ~= SKILL_LEVEL or newLevel < oldLevel then
        return true
    end

    -- Apply a new random bonus for each level interval reached
    local levelGain = newLevel - oldLevel
    while true do
        levelGain = levelGain - playerBonusConfig.perLevel
        if levelGain < 0 then
            break
        end
        local bonus, modifier, totalBonus = player:addRandomBonus()
        if bonus and modifier and totalBonus then
            player:sendTextMessage(playerBonusConfig.msg.type, string.format(playerBonusConfig.msg.string, bonus.readableString, modifier, totalBonus))
        end
    end

    return true
end

eventAdvance:register()


-- Login event to register above events

local eventLogin = CreatureEvent("bonuses_eventRegister")
eventLogin:type("login")

function eventLogin.onLogin(player)
    local events = {"bonuses_armorHpEvent", "bonuses_armorMpEvent", "bonuses_onAdvance"}

    for _, eventName in ipairs(events) do
        player:registerEvent(eventName)
    end

    for _, bonus in ipairs(playerBonusConfig.bonuses) do
        if bonus.loadOnLogin then
            -- Loads the storage value of the bonus and applies it to the player
            bonus.callback(player, 0, player:getBonus(bonus.type))
        end
    end

    return true
end

eventLogin:register()

New functions:
  • player:addBonus(bonusType or bonus table, modifier) ; Adds modifier to the player's cumulative bonus value
  • player:addRandomBonus() ; Adds a randomly selected bonus to the player and applies it immediately
  • player:getBonus(bonusType or bonus table) ; Returns the player's current bonus value for that type, default 0
Constants:
  • PLAYERBONUS_ARMOR
  • PLAYERBONUS_HEALTH
  • PLAYERBONUS_MANA
  • PLAYERBONUS_CRITICALCHANCE
  • PLAYERBONUS_CRITICALAMOUNT
  • PLAYERBONUS_HEALTHREGENERATION
  • PLAYERBONUS_MANAREGENERATION
Constants are automatically generated for each bonus based on what you put as the bonus type (auto-capitalizes it too, example my "armor" bonus type is generated to PLAYERBONUS_ARMOR), in case you add more. These are also meant to be used in the above functions where bonusType is expected (or you can just use the bonus type string if you prefer, ex player:getBonus("armor"))

I tested all of these, although not thoroughly. They seem to be working off the bat just fine. All you need to edit is the config table and everything else is done for you, unless you require a new creature event for the bonus you're adding.
 
Solution
@Delusion 1 issue with the max hp/mp it's not flexible if he decided to remove the bonus for any reason he might end up removing more hp/mp than he added at a lower level.
nvm thought it added percentages.
I know script is not designed for removing bounses later but would be a nice option to add.
 
Last edited:
@Delusion 1 issue with the max hp/mp it's not flexible if he decided to remove the bonus for any reason he might end up removing more hp/mp than he added at a lower level.
nvm thought it added percentagtes.
I know script is not designed for removing bounses later but would be a nice option to add.
You can, addBonus adds the modifier to the cumulative value, meaning you can pass negative values to subtract from that bonus and it updates.
 
No, my item abilities are just that, items, not players.
There are correlations between the two but that has nothing to do with my system, it's just a part of special skills (leech/crit).

I wish I could give random attack speed % for players, or even bonus to healing spells but its awesome already :D thanks
 
I wish I could give random attack speed % for players, or even bonus to healing spells but its awesome already :D thanks
Bonus attack speed:
Bonus healing to spells:
  • Create new healthChange & manaChange events like I did
  • Add them to the events table in the login event I created
  • Inside the healthChange & manaChange events, check the primaryType if it's == to COMBAT_HEALING
  • If it is COMBAT_HEALING, add primaryType = primaryType + (primaryType * player:getBonus(PLAYERBONUS_HEALING))
  • Same thing with secondaryDamage / secondaryType
 
Bonus attack speed:
Bonus healing to spells:
  • Create new healthChange & manaChange events like I did
  • Add them to the events table in the login event I created
  • Inside the healthChange & manaChange events, check the primaryType if it's == to COMBAT_HEALING
  • If it is COMBAT_HEALING, add primaryType = primaryType + (primaryType * player:getBonus(PLAYERBONUS_HEALING))
  • Same thing with secondaryDamage / secondaryType
First of all thanks for this script.
but i tried to add it and made loadOnLogin true but when i try to login i get this error and logout without entering the game.
I use your script plus i added attack speed bonus (i added the functions).

Lua:
playerBonusConfig = {
    --[[
        type: Unique identifier for the bonus, will automatically be configured to a constant as well (e.g. PLAYERBONUS_ARMOR)
        readableString: String used when showing the player they received the bonus
        roll: Obvious, but math.random(min, max)
        loadOnLogin: Load the bonus when player logs in, since it's not saved to database
        callback: Callback for when you require special functionality (aka when basic storages aren't enough)
    ]]
    bonuses = {
        {
            type = "armor",
            readableString = "Armor",
            loadOnLogin = true,
            roll = {min = 5, max = 10},
            -- We don't need a callback for armor since it's handled by storage value in onHealth/onManaChange events
        },
        {
            type = "attackspeed",
            readableString = "Attack Speed",
            roll = {min = 5, max = 10},
            loadOnLogin = true,
            callback = function(player, currentBonus, modifier)
                player:setAttackSpeed(player:getAttackSpeed() + modifier)
            end
        },
        {
            type = "health",
            readableString = "Maximum Health",
            roll = {min = 5, max = 10},
            loadOnLogin = true,
            callback = function(player, currentBonus, modifier)
                player:setMaxHealth(player:getMaxHealth() + modifier)
                player:addHealth(modifier)
            end
        },
        {
            type = "mana",
            readableString = "Maximum Mana",
            roll = {min = 5, max = 10},
            loadOnLogin = true,
            callback = function(player, currentBonus, modifier)
                player:setMaxMana(player:getMaxMana() + modifier)
                player:addMana(modifier)
            end
        },
        {
            type = "criticalChance",
            readableString = "Critical Hit Chance",
            roll = {min = 5, max = 10},
            loadOnLogin = true,
            callback = function(player, currentBonus, modifier)
                player:addSpecialSkill(SPECIALSKILL_CRITICALHITCHANCE, modifier)
            end
        },
        {
            type = "criticalAmount",
            readableString = "Critical Hit Amount",
            roll = {min = 5, max = 10},
            loadOnLogin = true,
            callback = function(player, currentBonus, modifier)
                player:addSpecialSkill(SPECIALSKILL_CRITICALHITAMOUNT, modifier)
            end
        },
        {
            type = "healthRegeneration",
            readableString = "Health Regeneration",
            roll = {min = 5, max = 10},
            loadOnLogin = true,
            condition = Condition(CONDITION_REGENERATION, CONDITIONID_DEFAULT),
            callback = function(player, currentBonus, modifier)
                local subId = 501
                local condition = player:getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT, subId)
                if condition then
                    condition:setParameter(CONDITION_PARAM_HEALTHGAIN, currentBonus + modifier)
                else
                    condition = playerBonusConfig.bonuses[6].condition
                    condition:setTicks(-1)
                    condition:setParameter(CONDITION_PARAM_SUBID, subId)
                    condition:setParameter(CONDITION_PARAM_HEALTHTICKS, 1000)
                    condition:setParameter(CONDITION_PARAM_HEALTHGAIN, currentBonus + modifier)
                    player:addCondition(condition)
                end
            end
        },
        {
            type = "manaRegeneration",
            readableString = "Mana Regeneration",
            roll = {min = 5, max = 10},
            loadOnLogin = true,
            condition = Condition(CONDITION_REGENERATION, CONDITIONID_DEFAULT),
            callback = function(player, currentBonus, modifier)
                local subId = 502
                local condition = player:getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT, subId)
                if condition then
                    condition:setParameter(CONDITION_PARAM_MANAGAIN, currentBonus + modifier)
                else
                    condition = playerBonusConfig.bonuses[7].condition
                    condition:setTicks(-1)
                    condition:setParameter(CONDITION_PARAM_SUBID, subId)
                    condition:setParameter(CONDITION_PARAM_MANATICKS, 1000)
                    condition:setParameter(CONDITION_PARAM_MANAGAIN, currentBonus + modifier)
                    player:addCondition(condition)
                end
            end
        }
    },

    baseStorage = 12345, -- Base storage key to start from, the rest are assigned based on baseStorage and adds + 1 for each bonus in the config
    perLevel = 5, -- Random bonus applied at each level interval defined here
    msg = {
        type = MESSAGE_STATUS_CONSOLE_ORANGE,
        --[[
            %s : readableString
            %d (1): Bonus modifier (current + >>modifier<<)
            %d (2): Total bonus (value of current + modifier)
        ]]
        string = "Your %s has been increased by %d, your total is now %d."
    }
}

for i, bonus in ipairs(playerBonusConfig.bonuses) do
    -- Add constants for bonus types, e.g. PLAYERBONUS_ARMOR -> "armor"
    _G["PLAYERBONUS_" .. bonus.type:upper()] = bonus.type

    -- Automatically assign storage keys because fuck doing them all manually
    playerBonusConfig.bonuses[i].storage = playerBonusConfig.baseStorage + i
end


-- Lib functions

function getBonusByType(bonusType)
    for _, bonus in ipairs(playerBonusConfig.bonuses) do
        if bonus.type == bonusType then
            return bonus
        end
    end
    return nil
end

function Player.addRandomBonus(self)
    local bonus = playerBonusConfig.bonuses[math.random(#playerBonusConfig.bonuses)]
    return self:addBonus(bonus.type, math.random(bonus.roll.min, bonus.roll.max))
end

function Player.addBonus(self, bonusType, modifier)
    local bonus = bonusType
    if type(bonus) == "string" then
        bonus = getBonusByType(bonus)
    end

    if not bonus then
        print("[Player.addBonus] Invalid bonus type given (".. tostring(bonusType) ..")")
        return false
    end

    -- Add modifier to the cumulative bonus
    local currentBonus = self:getBonus(bonus)
    self:setStorageValue(bonus.storage, currentBonus + modifier)
    if bonus.callback then
        bonus.callback(self, currentBonus, modifier)
    end

    -- bonus, modifier, total
    return bonus, modifier, currentBonus + modifier
end

function Player.getBonus(self, bonusType)
    local bonus = bonusType
    if type(bonus) == "string" then
        bonus = getBonusByType(bonus)
    end

    if not bonus then
        print("[Player.getBonus] Invalid bonus type given (".. tostring(bonusType) ..")")
        return 0
    end

    local value = self:getStorageValue(bonus.storage)
    return value == -1 and 0 or value
end




-- Below are all events, no touchy touchy

local function armorFormula(player, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType)
    -- Make sure we're being hit and the damage is negative (avoid the formula on healing)
    if not attacker or primaryType == COMBAT_HEALING then
        return primaryDamage, secondaryDamage
    end

    local armor = player:getBonus(PLAYERBONUS_ARMOR)

    if armor == 0 then
        return primaryDamage, secondaryDamage
    end

    -- Apply default armor formula from Creature::blockHit
    if armor > 3 then
        primaryDamage = primaryDamage - (math.random(armor / 2, armor - (armor % 2 + 1)))
        secondaryDamage = secondaryDamage - (math.random(armor / 2, armor - (armor % 2 + 1)))
    else
        primaryDamage = primaryDamage - 1
        secondaryDamage = secondaryDamage - 1
    end

    return math.max(0, primaryDamage), math.max(0, secondaryDamage)
end

-- HealthChange Armor mitigation event

local eventHpChange = CreatureEvent("bonuses_armorHpEvent")
eventHpChange:type("healthChange")

function eventHpChange.onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin)
    primaryDamage, secondaryDamage = armorFormula(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType)
    return primaryDamage, primaryType, secondaryDamage, secondaryType
end

eventHpChange:register()

-- ManaChange Armor mitigation event

local eventMpChange = CreatureEvent("bonuses_armorMpEvent")
eventMpChange:type("manaChange")

function eventMpChange.onManaChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin)
    primaryDamage, secondaryDamage = armorFormula(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType)
    return primaryDamage, primaryType, secondaryDamage, secondaryType
end

eventMpChange:register()

-- Advance event to give random bonuses to the player

local eventAdvance = CreatureEvent("bonuses_onAdvance")
eventAdvance:type("advance")

function eventAdvance.onAdvance(player, skill, oldLevel, newLevel)
    if skill ~= SKILL_LEVEL or newLevel < oldLevel then
        return true
    end

    -- Apply a new random bonus for each level interval reached
    local levelGain = newLevel - oldLevel
    while true do
        levelGain = levelGain - playerBonusConfig.perLevel
        if levelGain < 0 then
            break
        end
        local bonus, modifier, totalBonus = player:addRandomBonus()
        if bonus and modifier and totalBonus then
            player:sendTextMessage(playerBonusConfig.msg.type, string.format(playerBonusConfig.msg.string, bonus.readableString, modifier, totalBonus))
        end
    end

    return true
end

eventAdvance:register()


-- Login event to register above events

local eventLogin = CreatureEvent("bonuses_eventRegister")
eventLogin:type("login")

function eventLogin.onLogin(player)
    local events = {"bonuses_armorHpEvent", "bonuses_armorMpEvent", "bonuses_onAdvance"}

    for _, eventName in ipairs(events) do
        player:registerEvent(eventName)
    end

    for _, bonus in ipairs(playerBonusConfig.bonuses) do
        if bonus.loadOnLogin then
            -- Loads the storage value of the bonus and applies it to the player
            bonus.callback(player, 0, player:getBonus(bonus.type))
        end
    end

    return true
end

eventLogin:register()

The error:

Code:
GM mRefaat has logged in.

Lua Script Error: [Scripts Interface]
G:\Work\Tibia\NEW Project 2020 TFS 1.3 10.98\OT\data\scripts\creaturescripts\custom\bonus_on_level.lua:callback
...T\data\scripts\creaturescripts\custom\bonus_on_level.lua:280: attempt to call field 'callback' (a nil value)
stack traceback:
        [C]: in function 'callback'
        ...T\data\scripts\creaturescripts\custom\bonus_on_level.lua:280: in function <...T\data\scripts\creaturescripts\custom\bonus_on_level.lua:270>
GM mRefaat has logged out.
 
Last edited:
First of all thanks for this script.
but i tried to add it and made loadOnLogin true but when i try to login i get this error and logout without entering the game.
I use your script plus i added attack speed bonus (i added the functions).

Lua:
playerBonusConfig = {
    --[[
        type: Unique identifier for the bonus, will automatically be configured to a constant as well (e.g. PLAYERBONUS_ARMOR)
        readableString: String used when showing the player they received the bonus
        roll: Obvious, but math.random(min, max)
        loadOnLogin: Load the bonus when player logs in, since it's not saved to database
        callback: Callback for when you require special functionality (aka when basic storages aren't enough)
    ]]
    bonuses = {
        {
            type = "armor",
            readableString = "Armor",
            loadOnLogin = true,
            roll = {min = 5, max = 10},
            -- We don't need a callback for armor since it's handled by storage value in onHealth/onManaChange events
        },
        {
            type = "attackspeed",
            readableString = "Attack Speed",
            roll = {min = 5, max = 10},
            loadOnLogin = true,
            callback = function(player, currentBonus, modifier)
                player:setAttackSpeed(player:getAttackSpeed() + modifier)
            end
        },
        {
            type = "health",
            readableString = "Maximum Health",
            roll = {min = 5, max = 10},
            loadOnLogin = true,
            callback = function(player, currentBonus, modifier)
                player:setMaxHealth(player:getMaxHealth() + modifier)
                player:addHealth(modifier)
            end
        },
        {
            type = "mana",
            readableString = "Maximum Mana",
            roll = {min = 5, max = 10},
            loadOnLogin = true,
            callback = function(player, currentBonus, modifier)
                player:setMaxMana(player:getMaxMana() + modifier)
                player:addMana(modifier)
            end
        },
        {
            type = "criticalChance",
            readableString = "Critical Hit Chance",
            roll = {min = 5, max = 10},
            loadOnLogin = true,
            callback = function(player, currentBonus, modifier)
                player:addSpecialSkill(SPECIALSKILL_CRITICALHITCHANCE, modifier)
            end
        },
        {
            type = "criticalAmount",
            readableString = "Critical Hit Amount",
            roll = {min = 5, max = 10},
            loadOnLogin = true,
            callback = function(player, currentBonus, modifier)
                player:addSpecialSkill(SPECIALSKILL_CRITICALHITAMOUNT, modifier)
            end
        },
        {
            type = "healthRegeneration",
            readableString = "Health Regeneration",
            roll = {min = 5, max = 10},
            loadOnLogin = true,
            condition = Condition(CONDITION_REGENERATION, CONDITIONID_DEFAULT),
            callback = function(player, currentBonus, modifier)
                local subId = 501
                local condition = player:getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT, subId)
                if condition then
                    condition:setParameter(CONDITION_PARAM_HEALTHGAIN, currentBonus + modifier)
                else
                    condition = playerBonusConfig.bonuses[6].condition
                    condition:setTicks(-1)
                    condition:setParameter(CONDITION_PARAM_SUBID, subId)
                    condition:setParameter(CONDITION_PARAM_HEALTHTICKS, 1000)
                    condition:setParameter(CONDITION_PARAM_HEALTHGAIN, currentBonus + modifier)
                    player:addCondition(condition)
                end
            end
        },
        {
            type = "manaRegeneration",
            readableString = "Mana Regeneration",
            roll = {min = 5, max = 10},
            loadOnLogin = true,
            condition = Condition(CONDITION_REGENERATION, CONDITIONID_DEFAULT),
            callback = function(player, currentBonus, modifier)
                local subId = 502
                local condition = player:getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT, subId)
                if condition then
                    condition:setParameter(CONDITION_PARAM_MANAGAIN, currentBonus + modifier)
                else
                    condition = playerBonusConfig.bonuses[7].condition
                    condition:setTicks(-1)
                    condition:setParameter(CONDITION_PARAM_SUBID, subId)
                    condition:setParameter(CONDITION_PARAM_MANATICKS, 1000)
                    condition:setParameter(CONDITION_PARAM_MANAGAIN, currentBonus + modifier)
                    player:addCondition(condition)
                end
            end
        }
    },

    baseStorage = 12345, -- Base storage key to start from, the rest are assigned based on baseStorage and adds + 1 for each bonus in the config
    perLevel = 5, -- Random bonus applied at each level interval defined here
    msg = {
        type = MESSAGE_STATUS_CONSOLE_ORANGE,
        --[[
            %s : readableString
            %d (1): Bonus modifier (current + >>modifier<<)
            %d (2): Total bonus (value of current + modifier)
        ]]
        string = "Your %s has been increased by %d, your total is now %d."
    }
}

for i, bonus in ipairs(playerBonusConfig.bonuses) do
    -- Add constants for bonus types, e.g. PLAYERBONUS_ARMOR -> "armor"
    _G["PLAYERBONUS_" .. bonus.type:upper()] = bonus.type

    -- Automatically assign storage keys because fuck doing them all manually
    playerBonusConfig.bonuses[i].storage = playerBonusConfig.baseStorage + i
end


-- Lib functions

function getBonusByType(bonusType)
    for _, bonus in ipairs(playerBonusConfig.bonuses) do
        if bonus.type == bonusType then
            return bonus
        end
    end
    return nil
end

function Player.addRandomBonus(self)
    local bonus = playerBonusConfig.bonuses[math.random(#playerBonusConfig.bonuses)]
    return self:addBonus(bonus.type, math.random(bonus.roll.min, bonus.roll.max))
end

function Player.addBonus(self, bonusType, modifier)
    local bonus = bonusType
    if type(bonus) == "string" then
        bonus = getBonusByType(bonus)
    end

    if not bonus then
        print("[Player.addBonus] Invalid bonus type given (".. tostring(bonusType) ..")")
        return false
    end

    -- Add modifier to the cumulative bonus
    local currentBonus = self:getBonus(bonus)
    self:setStorageValue(bonus.storage, currentBonus + modifier)
    if bonus.callback then
        bonus.callback(self, currentBonus, modifier)
    end

    -- bonus, modifier, total
    return bonus, modifier, currentBonus + modifier
end

function Player.getBonus(self, bonusType)
    local bonus = bonusType
    if type(bonus) == "string" then
        bonus = getBonusByType(bonus)
    end

    if not bonus then
        print("[Player.getBonus] Invalid bonus type given (".. tostring(bonusType) ..")")
        return 0
    end

    local value = self:getStorageValue(bonus.storage)
    return value == -1 and 0 or value
end




-- Below are all events, no touchy touchy

local function armorFormula(player, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType)
    -- Make sure we're being hit and the damage is negative (avoid the formula on healing)
    if not attacker or primaryType == COMBAT_HEALING then
        return primaryDamage, secondaryDamage
    end

    local armor = player:getBonus(PLAYERBONUS_ARMOR)

    if armor == 0 then
        return primaryDamage, secondaryDamage
    end

    -- Apply default armor formula from Creature::blockHit
    if armor > 3 then
        primaryDamage = primaryDamage - (math.random(armor / 2, armor - (armor % 2 + 1)))
        secondaryDamage = secondaryDamage - (math.random(armor / 2, armor - (armor % 2 + 1)))
    else
        primaryDamage = primaryDamage - 1
        secondaryDamage = secondaryDamage - 1
    end

    return math.max(0, primaryDamage), math.max(0, secondaryDamage)
end

-- HealthChange Armor mitigation event

local eventHpChange = CreatureEvent("bonuses_armorHpEvent")
eventHpChange:type("healthChange")

function eventHpChange.onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin)
    primaryDamage, secondaryDamage = armorFormula(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType)
    return primaryDamage, primaryType, secondaryDamage, secondaryType
end

eventHpChange:register()

-- ManaChange Armor mitigation event

local eventMpChange = CreatureEvent("bonuses_armorMpEvent")
eventMpChange:type("manaChange")

function eventMpChange.onManaChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin)
    primaryDamage, secondaryDamage = armorFormula(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType)
    return primaryDamage, primaryType, secondaryDamage, secondaryType
end

eventMpChange:register()

-- Advance event to give random bonuses to the player

local eventAdvance = CreatureEvent("bonuses_onAdvance")
eventAdvance:type("advance")

function eventAdvance.onAdvance(player, skill, oldLevel, newLevel)
    if skill ~= SKILL_LEVEL or newLevel < oldLevel then
        return true
    end

    -- Apply a new random bonus for each level interval reached
    local levelGain = newLevel - oldLevel
    while true do
        levelGain = levelGain - playerBonusConfig.perLevel
        if levelGain < 0 then
            break
        end
        local bonus, modifier, totalBonus = player:addRandomBonus()
        if bonus and modifier and totalBonus then
            player:sendTextMessage(playerBonusConfig.msg.type, string.format(playerBonusConfig.msg.string, bonus.readableString, modifier, totalBonus))
        end
    end

    return true
end

eventAdvance:register()


-- Login event to register above events

local eventLogin = CreatureEvent("bonuses_eventRegister")
eventLogin:type("login")

function eventLogin.onLogin(player)
    local events = {"bonuses_armorHpEvent", "bonuses_armorMpEvent", "bonuses_onAdvance"}

    for _, eventName in ipairs(events) do
        player:registerEvent(eventName)
    end

    for _, bonus in ipairs(playerBonusConfig.bonuses) do
        if bonus.loadOnLogin then
            -- Loads the storage value of the bonus and applies it to the player
            bonus.callback(player, 0, player:getBonus(bonus.type))
        end
    end

    return true
end

eventLogin:register()

The error:

Code:
GM mRefaat has logged in.

Lua Script Error: [Scripts Interface]
G:\Work\Tibia\NEW Project 2020 TFS 1.3 10.98\OT\data\scripts\creaturescripts\custom\bonus_on_level.lua:callback
...T\data\scripts\creaturescripts\custom\bonus_on_level.lua:280: attempt to call field 'callback' (a nil value)
stack traceback:
        [C]: in function 'callback'
        ...T\data\scripts\creaturescripts\custom\bonus_on_level.lua:280: in function <...T\data\scripts\creaturescripts\custom\bonus_on_level.lua:270>
GM mRefaat has logged out.
Because it shouldn't be loaded on login, there are no values set to the player required on login for it to function through other things because armor is manually defined and handled.
 
Back
Top