• 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] Rarity Rolls & Custom Attributes Library

The function
Lua:
function statChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin)
is called only when one player attack other player. For exemple, when one player attack a monster the critical or ice,fire,earth damage doesnt work. Im using TFS 1.5 DOWNGRADE NEKIRO 7.72



Lua:
function statChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin)
    
    -- Defaults
    local chance = 0 -- Crit Chance
    local spellmodifier = 0 -- Spell Damage
    local manaleech = 0
    local lifeleech = 0
    local stunDuration = 2000
    local sourceDamage = primaryDamage -- Used for multi-shot when secondary targets are immune
    local elementalroll = false -- Flag for if weapon has an element on it
    local doubleroll = false -- Flag for if the weapon has two elements on it
    --local dawnbreaker = false
    --local rainbowshield = false
    
    -- What elements should be checked when calculating resistances
    local resistances = {
        [COMBAT_PHYSICALDAMAGE] = {Native = 0, Custom = 0, String = "Physical"},
        [COMBAT_FIREDAMAGE] = {Native = 0, Custom = 0, String = "Fire"},
        [COMBAT_ICEDAMAGE] = {Native = 0, Custom = 0, String = "Ice"},
        [COMBAT_ENERGYDAMAGE] = {Native = 0, Custom = 0, String = "Energy"},
        [COMBAT_POISONDAMAGE] = {Native = 0, Custom = 0, String = "Poison"},
        [COMBAT_PHYSICALDAMAGE] = {Native = 0, Custom = 0, String = "Death"}
    }
    
    -- Check resistences if victim is a player before applying extra elemental damage below
    if creature:isPlayer() then
        for i = 1,#checkallslots do
            if creature:getSlotItem(checkallslots[i]) ~= nil then
                local resistanceitem = creature:getSlotItem(checkallslots[i])
                local resistanceitemdesc = resistanceitem:getDescription()
                getResistences(resistanceitemdesc, resistances, true)
                --[[
                -- if rainbow shield
                if resistanceitem.itemid == 8905 then
                    if checkallslots[i] == CONST_SLOT_LEFT or checkallslots[i] == CONST_SLOT_RIGHT then -- Only if equipped
                        rainbowshield = resistanceitem
                    end
                end
                --]]
            end
        end
        
         -- Reduce main damage on custom resistance rolls before applying extra elemental damage below
        if primaryType ~= 0 then
            if resistances[primaryType] then
                if resistances[primaryType].Custom ~= 0 then
                    local resistancePercent = (100 - resistances[primaryType].Custom)
                    primaryDamage = (resistancePercent / 100) * primaryDamage
                end
            end
        end
        if secondaryType ~= 0 then
            if resistances[secondaryType] then
                if resistances[secondaryType].Custom ~= 0 then
                    local resistancePercent = (100 - resistances[secondaryType].Custom)
                    secondaryDamage = (resistancePercent / 100) * secondaryDamage
                end
            end
        end
    end

    if attacker then -- Ignore HP changes from healing fountains, terrain elemental fields (neutral sources basically)
        if attacker:isPlayer() then
            if origin == ORIGIN_RANGED or origin == ORIGIN_MELEE then -- Skip if player is using spells
                -- Natural elemental damage (wand, fire sword, flaming arrow etc) swap Types around for better visuals and set flags
                if secondaryType ~= 0 or primaryType ~= COMBAT_PHYSICALDAMAGE then
                    -- Cache original physical damage
                    local originalDamage = primaryDamage
                    local originalType = primaryType
                    
                    -- Swap primary & secondary
                    primaryDamage = secondaryDamage
                    primaryType = secondaryType
                    secondaryDamage = originalDamage
                    secondaryType = originalType
                    
                    -- damageTypes have been swapped, flag it
                    elementalroll = true
                end
                
                -- Check weapons for crit/elemental damage
                for i = 1,#checkweaponslots do -- Roll for each slot
                    if attacker:getSlotItem(checkweaponslots[i]) ~= nil then
                        local slotitem = attacker:getSlotItem(checkweaponslots[i])
                        local slotitemdesc = slotitem:getDescription()
                        
                    
                    
function doTargetCombatCondition(attacker, creature, condition, effect)
    -- Implementação da função
end

-- Verificar dano elemental
if slotitemdesc:find("%[Enhanced Fire Damage") then
    primaryDamage, primaryType, secondaryDamage, secondaryType, elementalroll = elementalDmg(slotitemdesc, COMBAT_FIREDAMAGE, true, creature, resistances, primaryDamage, primaryType, secondaryDamage, secondaryType, elementalroll)

    -- 33% de chance de aplicar queimadura
    if math.random(1, 3) == 3 then
        local condition = createConditionObject(CONDITION_FIRE)
        local burnDamage = 20

        -- Ajustar dano de queimadura com base nas resistências
        if creature:isPlayer() then
            if resistances[COMBAT_FIREDAMAGE].Custom ~= 0 or resistances[COMBAT_FIREDAMAGE].Native ~= 0 then
                local resistancePercent = (100 - (resistances[COMBAT_FIREDAMAGE].Custom + resistances[COMBAT_FIREDAMAGE].Native))
                burnDamage = (resistancePercent / 100) * burnDamage
            end
        elseif creature:isMonster() then
            local resistance = creature:getType():getElementList()
            local immunity = creature:getType():getCombatImmunities()

            -- Ajustar dano de queimadura com base nas resistências do monstro
            if resistance[COMBAT_FIREDAMAGE] then
                local resistancePercent = (100 - resistance[COMBAT_FIREDAMAGE])
                burnDamage = (resistancePercent / 100) * burnDamage
            end

            -- Se o monstro for imune ao dano de fogo, definir dano de queimadura como zero
            if bit.band(immunity, COMBAT_FIREDAMAGE) == COMBAT_FIREDAMAGE then
                burnDamage = 0
            end
        end

        -- Se o dano de queimadura não for zero, criar condição e aplicar ao alvo
        if burnDamage ~= 0 then
            addDamageCondition(condition, 10, 2000, burnDamage)
            setConditionParam(condition, CONDITION_PARAM_DELAYED, true)
            doTargetCombatCondition(attacker, creature, condition, CONST_ME_FIRE)
        end
    end
end
                        if slotitemdesc:find "%[Enhanced Ice Damage" then
                            primaryDamage, primaryType, secondaryDamage, secondaryType, elementalroll = elementalDmg(slotitemdesc, COMBAT_ICEDAMAGE, false, creature, resistances, primaryDamage, primaryType, secondaryDamage, secondaryType, elementalroll)
                            if math.random(1,5) == 5 then -- 20% to paralyze/slow
                                local condition = createConditionObject(CONDITION_PARALYZE)
                                setConditionParam(condition, CONDITION_PARAM_TICKS, 3000)
                                setConditionParam(condition, CONDITION_PARAM_SPEED, -300)
                                doTargetCombatCondition(attacker, creature, condition, CONST_ME_MAGIC_ICEAREA)
                            end
                        end
                        if slotitemdesc:find "%[Enhanced Energy Damage" then
                            primaryDamage, primaryType, secondaryDamage, secondaryType, elementalroll = elementalDmg(slotitemdesc, COMBAT_ENERGYDAMAGE, true, creature, resistances, primaryDamage, primaryType, secondaryDamage, secondaryType, elementalroll)
                        end
                        
                        -- Check crit chance of slots and add together
                        if slotitemdesc:find "%[Crit Chance" then
                            local crit = string.match(slotitemdesc, "Crit Chance: [+-](%d+)%%")
                            chance = chance + crit
                        end
                        
                        -- Mana leech gather %
                        if slotitemdesc:find "%[Mana Leech" then
                            manaleech = string.match(slotitemdesc, '%[Mana Leech: %+(%d+)%%%]') or 0
                        end
                        
                        -- Life leech gather %
                        if slotitemdesc:find "%[Life Leech" then
                            lifeleech = string.match(slotitemdesc, '%[Life Leech: %+(%d+)%%%]') or 0
                        end
                        
                        -- Stun Chance
                        if slotitemdesc:find "%[Stun Chance" then
                            local stunchance = tonumber(string.match(slotitemdesc, '%[Stun Chance: %+(%d+)%%%]')) or 0
                            local rollchance = math.random(1,100)
                            if stunchance >= rollchance and attacker ~= creature then
                                stunTarget(creature.uid, stunDuration)
                            end
                        end
                            
                        -- Multi-shot
                        if slotitemdesc:find "%[Multi Shot" then
                            if attacker:getSlotItem(CONST_SLOT_AMMO) ~= nil then -- Does player have ammo
                                local multishot = tonumber(string.match(slotitemdesc, "%[Multi Shot: %+(%d+)%]"))
                                local ammoSlot = attacker:getSlotItem(CONST_SLOT_AMMO).itemid
                                local validammo = false
                                for k,v in pairs(animation) do
                                    if k == ammoSlot then
                                        validammo = true -- Ammo they're using exists in animation table
                                        break
                                    end
                                end
                                if validammo then -- (!) You may need to add more CONST_ANI_XXXX animations to the animation table
                                    local targetpos = creature:getPosition()
                                    local targets = getSpectators(targetpos, 2, 2) -- get possible multi-shot targets
                                    local victims = {}
                                    if targets ~= nil then
                                        -- do the shuffle
                                        shuffle(targets)
                                        for i = 1,#targets do
                                            local target = Creature(targets[i])
                                            if target:isMonster() then -- only target monsters
                                                if isSightClear(attacker:getPosition(), target:getPosition()) then -- that are in sight
                                                    if target:getPosition() ~= targetpos then -- ignore the original target
                                                        local victimcount = #victims or 0
                                                        if victimcount < multishot then
                                                            table.insert(victims, target) -- collate valid targets into victims table
                                                        else
                                                            break -- exit the for loop, have enough targets
                                                        end
                                                    end
                                                end
                                            end
                                        end
                                    end
                                    function doTargetCombatHealth(attacker, target, damageType, adjustedDamage, originalDamage)
                                    -- Implementação da função aqui
                                                 end
                                -- this a rats nest due to swapping primaryType/secondaryType above for visuals
                                            if victims ~= nil then
                                        for i = 1, #victims do
                                            -- Defaults
                                            local damage = 0
                                            local elementalDamage = 0
                                            local backupDamage = 0
                                            local mainType = primaryType
                                            local altType = secondaryType
                                            local resistance = victims[i]:getType():getElementList()
                                            local immunity = victims[i]:getType():getCombatImmunities()
                                            local distanceEffect = animation[ammoSlot]

                                            -- Animation
                                            attacker:getPosition():sendDistanceEffect(victims[i]:getPosition(), animation[ammoSlot])

                                            -- Damage calculation
                                            if elementalroll then -- Check if types are swapped
                                                mainType = secondaryType
                                                altType = primaryType
                                                damage = filterResistance(secondaryDamage, secondaryType, immunity, resistance)
                                                elementalDamage = filterResistance(primaryDamage, primaryType, immunity, resistance)
                                            else -- Types haven't been swapped
                                                damage = filterResistance(primaryDamage, primaryType, immunity, resistance)
                                                elementalDamage = filterResistance(secondaryDamage, secondaryType, immunity, resistance)
                                            end

                                            -- Apply secondary damage if any
                                            if elementalDamage ~= 0 then
                                                doTargetCombatHealth(attacker, victims[i], altType, math.ceil((80 / 100) * elementalDamage), elementalDamage)
                                            end

                                            -- Apply main damage if any
                                            if damage ~= 0 then
                                                doTargetCombatHealth(attacker, victims[i], mainType, math.ceil((80 / 100) * damage), damage)
                                            else -- No damage; fallback to original physical damage
                                                backupDamage = filterResistance(sourceDamage, COMBAT_PHYSICALDAMAGE, immunity, resistance)
                                                if backupDamage ~= 0 then -- Try rolling back to base physical damage
                                                    doTargetCombatHealth(attacker, victims[i], COMBAT_PHYSICALDAMAGE, math.ceil((80 / 100) * sourceDamage), sourceDamage)
                                                else -- Monster is immune to physical damage too
                                                    victims[i]:getPosition():sendMagicEffect(CONST_ME_BLOCKHIT)
                                                end
                                            end
                                        end
                                    end
                                end
                            end
                        end
                    end
                end
                
                -- Roll crit chance and apply if critical strike
                if chance > 0 then
                    if math.random(100) <= chance then
                        local pos = creature:getPosition()
                        pos:sendMagicEffect(criteffect)
                         -- if elemental roll flag, crit the swapped secondaryDamage instead of primaryDamage
                        if elementalroll then
                            secondaryDamage = secondaryDamage * critmodifier
                        else
                            primaryDamage = primaryDamage * critmodifier
                        end
                    end
                end
                
                -- Apply mana leech
                if manaleech ~= 0 then
                    local manatoadd = math.floor((manaleech / 100) * (primaryDamage + secondaryDamage))
                    local attackerpos = attacker:getPosition()
                    addEvent(manaLeechConcat, 5, attacker.uid, manatoadd)
                end
                
-- Apply life leech
if lifeleech ~= 0 then
    -- Calcula a quantidade de vida a ser roubada com base na porcentagem de life leech
    local lifetoadd = math.floor((lifeleech / 100) * (primaryDamage + secondaryDamage))
    
    -- Obtém a posição do atacante (se necessário)
    local attackerpos = attacker:getPosition()

    print("Life leech applied. Attacker ID: " .. attacker.uid .. ", Lifetoadd: " .. lifetoadd)

    -- Agende o evento para adicionar a vida roubada ao jogador
    addEvent(lifeLeechConcat, 5, attacker.uid, lifetoadd)
else
    print("Life leech not applied. Lifeleech is zero.")
end


            elseif origin == ORIGIN_SPELL then   
                -- Reduce spell damage based on custom resistances
                if resistances[primaryType] then
                    if resistances[primaryType].Custom ~= 0 then
                        local resistancePercent = (100 - resistances[primaryType].Custom)
                        primaryDamage = (resistancePercent / 100) * primaryDamage
                    end
                end
                
                -- Check slots that can roll the following attributes
                for i = 1,#checkweaponslots do
                    if attacker:getSlotItem(checkweaponslots[i]) ~= nil then
                        local slotitem = attacker:getSlotItem(checkweaponslots[i])
                        local slotitemdesc = slotitem:getDescription()
                        
                        -- Check crit chance of slots and add together
                        if slotitemdesc:find "%[Spell Damage" then
                            local spellDamage = string.match(slotitemdesc, "Spell Damage: [+-](%d+)%%")
                            spellmodifier = spellmodifier + spellDamage
                        end
                        
                        -- Mana leech gather %
                        if slotitemdesc:find "%[Mana Leech" then
                            manaleech = string.match(slotitemdesc, '%[Mana Leech: %+(%d+)%%%]') or 0
                        end
                        
                        -- Life leech gather %
                        if slotitemdesc:find "%[Life Leech" then
                            lifeleech = string.match(slotitemdesc, '%[Life Leech: %+(%d+)%%%]') or 0
                        end
                        
                        -- Stun Chance
                        if slotitemdesc:find "%[Stun Chance" then
                            local stunchance = tonumber(string.match(slotitemdesc, '%[Stun Chance: %+(%d+)%%%]')) or 0
                            local rollchance = math.random(1,100)
                            if stunchance >= rollchance and attacker ~= creature then
                                stunTarget(creature.uid, stunDuration)
                            end
                        end
                    end
                end
                
                -- if using a +%spelldamage wand, adjust damage
                if spellmodifier > 0 then
                    local extraDamage = (spellmodifier / 100) * primaryDamage
                    primaryDamage = primaryDamage + extraDamage
                end
                
                -- Apply mana leech
                if manaleech ~= 0 and primaryType ~= COMBAT_HEALING and secondaryType ~= COMBAT_HEALING then
                    local manatoadd = math.floor((manaleech / 100) * (primaryDamage + secondaryDamage))
                    manaLeechConcat(attacker.uid, manatoadd)
                end
                
                -- Apply life leech
                if lifeleech ~= 0 and primaryType ~= COMBAT_HEALING and secondaryType ~= COMBAT_HEALING then
                    local lifetoadd = math.floor((lifeleech / 100) * (primaryDamage + secondaryDamage))
                    lifeLeechConcat(attacker.uid, lifetoadd)
                end
            end
        end
    end
    
    return primaryDamage, primaryType, secondaryDamage, secondaryType, origin
end

function onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin)
    if primaryType ~= 128 then -- Ignore health potions
        -- Call statChange for both player and monster attacks
        primaryDamage, primaryType, secondaryDamage, secondaryType, origin = statChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin)
    end

    return primaryDamage, primaryType, secondaryDamage, secondaryType, origin
end

function onManaChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin)
    if primaryType ~= 64 then -- Ignore mana potions
        -- Call statChange for both player and monster attacks
        primaryDamage, primaryType, secondaryDamage, secondaryType, origin = statChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin)
        
        -- Apply magic shield bonus, even if neutral source
        if creature:isPlayer() then
            local manashield = 0
            for i = 1,#checkweaponslots do
                if creature:getSlotItem(checkweaponslots[i]) ~= nil then
                    local slotitem = creature:getSlotItem(checkweaponslots[i])
                    local slotitemdesc = slotitem:getDescription()
                    if slotitemdesc:find "%[Mana Shield" then
                        manashield = manashield + tonumber(string.match(slotitemdesc, '%[Mana Shield: %+(%d+)%%%]'))
                    end
                end
            end
            if manashield ~= 0 then
                local shieldPercent = (100 - manashield)
                primaryDamage = (shieldPercent / 100) * primaryDamage
                secondaryDamage = (shieldPercent / 100) * secondaryDamage
            end
        end
        
    end
    
    return primaryDamage, primaryType, secondaryDamage, secondaryType, origin
end
 
< 10.94
The first post is for versions earlier than 10.94, before crit and mana/life leech was added to the game.
Someone should try it on an 8.6 server ;)

10.94+
If you are using the latest TFS release, refer to the post here.

What is this?
A newer version of this:
A variant of this, this and this mod.
It gives items a chance to roll rare, epic or legendary when it drops from a monster:

iSCVyA6.png


With the following possible attributes:
(For config and if you want to understand the code, start at the stats table in lib/core/attributes.lua)
Lua:
[1] = { -- Attack
[2] = { -- Defense
[3] = { -- Extra Defense
[4] = { -- Armor
[5] = { -- Accuracy
[6] = { -- Range
[7] = { -- Equipment with < 50 charges
[8] = { -- Equipment with >= 50 charges
[9] = { -- Time
[10] = { -- Crit Chance
[11] = { -- Crit Amount (Currently Unused)
[12] = { -- Fire Damage
[13] = { -- Ice Damage
[14] = { -- Energy Damage
[15] = { -- Fire Resistance
[16] = { -- Ice Resistance
[17] = { -- Energy Resistance
[18] = { -- Earth Resistance
[19] = { -- Physical Resistance
[20] = { -- Death Resistance
[21] = { -- Spell Damage
[22] = { -- Multi Shot
[23] = { -- Stun Chance
[24] = { -- Mana Shield
[25] = { -- Sword Skill
[26] = { -- Skill Axe
[27] = { -- Skill Club
[28] = { -- Skill Melee
[29] = { -- Skill Distance
[30] = { -- Skill Shielding
[31] = { -- Magic Level
[32] = { -- Max Health i.e +100
[33] = { -- Max Mana i.e +100
[34] = { -- Max Health % i.e +10%
[35] = { -- Max Mana % i.e +10%
[36] = { -- Life Leech Chance (Currently Unused)
[37] = { -- Life Leech Amount
[38] = { -- Mana Leech Chance (Currently Unused)
[39] = { -- Mana Leech Amount


Cw1kJDN.png


Some of these are completely custom attributes, like elemental damage:

56C8xHU.gif


This requires the following updates/features:

1) Loot drop handled via Lua
2) Add the new health/mana gain coloured text messages
3) onInventoryUpdate()
4) onSpawn()


Installation:
📁 rollAttributes.zip
paste in /data

lib/core/core.lua
add below:
Lua:
-- Rolls & Custom Item Attributes
dofile('data/lib/core/attributes.lua')

talkactions.xml
add below:
XML:
<talkaction words="/roll" separator=" " script="roll.lua" />

creaturescripts.xml
add below:
XML:
<!-- Roll & Attribute Damage -->
<event type="healthchange" name="rollHealth" script="attributes.lua"/>
<event type="manachange" name="rollMana" script="attributes.lua"/>

creaturescripts/scripts/login.lua
add below:
Lua:
-- Activate Custom Item Attributes
for i = 1,10 do -- CONST_SLOT_FIRST,CONST_SLOT_LAST
    local item = player:getSlotItem(i)
    if item then
        itemAttributes(player, item, i, true)
    end
end
-- If player logged with more 'current health' than their db 'max health' due to an item attribute
local query = db.storeQuery("SELECT `health`,`mana` FROM players where `id`="..player:getGuid())
if query then
    local health = tonumber(result.getDataString(query, 'health'))
    local mana = tonumber(result.getDataString(query, 'mana'))
    local playerHealth = player:getHealth()
    local playerMana = player:getMana()
    if playerHealth < health then
        player:addHealth(health - playerHealth)
    end
    if playerMana < mana then
        player:addMana(mana - playerMana)
    end
    result.free(query)
end

add below:
Lua:
player:registerEvent("rollHealth")
player:registerEvent("rollMana")

events/scripts/player.lua
add to the bottom:
Lua:
function Player:onInventoryUpdate(item, slot, equip)
    itemAttributes(self, item, slot, equip)
end

events/scripts/monster.lua
add to the top of the file:
Lua:
-- Rarity Animations
local rare_popup = true
local rare_effect = true
local rare_effect_id = CONST_ME_STUN
add between:
Lua:
-- Apply rarity chance to corpse contents and apply animation
if rollRarity(corpse) > 0 then -- If a rare item was rolled, play animation
        if rare_popup then
            local spectators = Game.getSpectators(corpse:getPosition(), false, true, 7, 7, 5, 5)
            for i = 1, #spectators do
                spectators[i]:say(rare_text, TALKTYPE_MONSTER_SAY, false, spectators[i], corpse:getPosition())
            end
        end
    if rare_effect then
        corpse:getPosition():sendMagicEffect(rare_effect_id)
    end
end
add at the end:
Lua:
function Monster:onSpawn(position, startup, artificial)
    self:registerEvent("rollHealth")
    self:registerEvent("rollMana")
   return true
end


The core scripts are attached because they are to big to embed in post, so they're attached as rollAttributes.zip
Don't forget to paste the core scripts into your /data dir.

Extras
  • 'Stun Target' is disabled by default, add the condition from here and uncomment the lines in creaturescripts/scripts/attributes.lua
  • primaryDamage and primaryType are swapped with secondaryDamage and secondaryType on elemental damage (better, consistent visuals)
  • Use the included /roll <rarity> talkaction for testing i.e /roll legendary
  • Mana Leech and Life Leech apply 5ms after damage is done, this may feel different to vanilla Tibia.
  • Multi shot ignores creature armor/shielding - its quite primitive.
  • If you want to re-roll an item you should item:remove() it and re-create it (so it has stock base stats).
  • Custom resistances apply after natural resistances and are additive.
  • A unique weapon & shield using this systems on-hit mechanics can be found in extras.xml
    Overwrite their information in items.xml and uncomment their code in attributes.lua for cool points.

Link for 10.94+ Github is down
 
finally got it to work! Now it seems i got the same problem as people above also have.

Lua Script Error: [CreatureScript Interface]
data/creaturescripts/scripts/attributes.lua:eek:nHealthChange
data/creaturescripts/scripts/attributes.lua:418: attempt to call global 'doTargetCombatCondition' (a nil value)
stack traceback:
[C]: in function 'doTargetCombatCondition'
data/creaturescripts/scripts/attributes.lua:418: in function 'statChange'
data/creaturescripts/scripts/attributes.lua:685: in function <data/creaturescripts/scripts/attributes.lua:682>

tfs 1.5 772 downgrade
ive got the same problem, share how you fix that
 
Back
Top