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

CreatureEvent [TFS 1.3] updated Slot System

zbizu

Legendary OT User
Joined
Nov 22, 2010
Messages
3,323
Solutions
26
Reaction score
2,694
Location
Poland
this but updated (no backwards compability)

example set generated by player:generateSetTest():
57iNbi5.png


example use:
Lua:
local it = player:addItem(2125, 1)
it:setMaxSockets(3)
it:addStat("HP", "+2%")
it:addStat("Sword", "+2")
it:removeStat(2) -- removes sword buff

changelog:
  • compatibility with tfs master branch (10th Dec 2019)
  • removed "." from slots display
  • added basic formatting config
  • added crit, hp leech and mp leech
  • moved stats to custom attributes
  • conditions are now created on the fly
  • stats now only get updated on login and on equip
  • flat values support
  • negative values support
  • removed buff status symbol from condition bar

creaturescripts.xml
XML:
    <event type="login" name="ItemSystemLogin" script="itemsystem.lua" />

creaturescripts/scripts/itemsystem.lua
Lua:
-- config (things player see, safe to edit anytime)
    STAT_SYSTEM_CONFIG = {
        slotsOutputFormat = "[%s %s]",        -- this item has %s stat modified by %s value
        emptySlotSymbol = "[ ]",            -- available slot symbol
    }
    
-- WARNING: changing values below after system launch may damage previously upgraded items
    -- attributes config (things player don't see)
    CUSTOM_ATTRIBUTE_STATS = "stats"
    CUSTOM_ATTRIBUTE_SOCKETS = "sockets"
    -- stat separators, common characters that are unlikely to appear in stat systems
    SLOT_SEPARATOR = "`"
    SLOT_VALUE_SEPARATOR = ";"
    
    -- stat names configuration
    STAT_HP                = "HP"
    STAT_MP                = "MP"
    -- skills
    STAT_MAGICLEVEL     = "MLvl"
    STAT_MELEE            = "Melee"
    STAT_FIST            = "Fist"
    STAT_CLUB            = "Club"
    STAT_SWORD            = "Sword"
    STAT_AXE            = "Axe"
    STAT_DISTANCE        = "Distance"
    STAT_SHIELDING        = "Shielding"
    STAT_FISHING        = "Fishing"
    -- other
    STAT_CRIT            = "Crit"
    STAT_CRITCHANCE        = "Crit%"
    STAT_LIFELEECH        = "LifeLeech"
    STAT_LIFELEECHCHANCE= "LifeLeech%"
    STAT_MANALEECH        = "ManaLeech"
    STAT_MANALEECHCHANCE= "ManaLeech%"
    
    STAT_SYSTEM_STATTABLE = {
        STAT_HP, STAT_MP,
        STAT_MAGICLEVEL,
        STAT_MELEE, STAT_FIST, STAT_CLUB, STAT_SWORD, STAT_AXE, STAT_DISTANCE, STAT_SHIELDING, STAT_FISHING,
        STAT_CRIT, STAT_CRITCHANCE,
        STAT_LIFELEECH, STAT_LIFELEECHCHANCE, -- hp leech
        STAT_MANALEECH, STAT_MANALEECHCHANCE, -- mana leech        
    }

    STAT_SYSTEM_CONDITIONTABLE = {
        -- regular stats
        [STAT_HP]          = { type = CONDITION_ATTRIBUTES,        flat = CONDITION_PARAM_STAT_MAXHITPOINTS,    percent = CONDITION_PARAM_STAT_MAXHITPOINTSPERCENT    },
        [STAT_MP]          = { type = CONDITION_ATTRIBUTES,        flat = CONDITION_PARAM_STAT_MAXMANAPOINTS,    percent = CONDITION_PARAM_STAT_MAXMANAPOINTSPERCENT    },
        [STAT_MAGICLEVEL]  = { type = CONDITION_ATTRIBUTES,        flat = CONDITION_PARAM_STAT_MAGICPOINTS,    percent = CONDITION_PARAM_STAT_MAGICPOINTSPERCENT    },
        [STAT_MELEE]       = { type = CONDITION_ATTRIBUTES,        flat = CONDITION_PARAM_SKILL_MELEE,            percent = CONDITION_PARAM_SKILL_MELEEPERCENT        },
        [STAT_FIST]        = { type = CONDITION_ATTRIBUTES,        flat = CONDITION_PARAM_SKILL_FIST,            percent = CONDITION_PARAM_SKILL_FISTPERCENT            },
        [STAT_CLUB]        = { type = CONDITION_ATTRIBUTES,        flat = CONDITION_PARAM_SKILL_CLUB,            percent = CONDITION_PARAM_SKILL_CLUBPERCENT            },
        [STAT_SWORD]       = { type = CONDITION_ATTRIBUTES,        flat = CONDITION_PARAM_SKILL_SWORD,            percent = CONDITION_PARAM_SKILL_SWORDPERCENT        },
        [STAT_AXE]         = { type = CONDITION_ATTRIBUTES,        flat = CONDITION_PARAM_SKILL_AXE,            percent = CONDITION_PARAM_SKILL_AXEPERCENT            },
        [STAT_DISTANCE]    = { type = CONDITION_ATTRIBUTES,        flat = CONDITION_PARAM_SKILL_DISTANCE,        percent = CONDITION_PARAM_SKILL_DISTANCEPERCENT        },
        [STAT_SHIELDING]   = { type = CONDITION_ATTRIBUTES,        flat = CONDITION_PARAM_SKILL_SHIELD,        percent = CONDITION_PARAM_SKILL_SHIELDPERCENT        },
        [STAT_FISHING]     = { type = CONDITION_ATTRIBUTES,        flat = CONDITION_PARAM_SKILL_FISHING,          percent = CONDITION_PARAM_SKILL_FISHINGPERCENT        },
        -- special cases (different starting point for % values)
        [STAT_LIFELEECH]        = { type = CONDITION_ATTRIBUTES,        flat = CONDITION_PARAM_SPECIALSKILL_LIFELEECHAMOUNT,    percent = CONDITION_PARAM_SPECIALSKILL_LIFELEECHAMOUNT,        specialPercent = true},
        [STAT_LIFELEECHCHANCE]    = { type = CONDITION_ATTRIBUTES,        flat = CONDITION_PARAM_SPECIALSKILL_LIFELEECHCHANCE,    percent = CONDITION_PARAM_SPECIALSKILL_LIFELEECHCHANCE,        specialPercent = true},
        [STAT_MANALEECH]        = { type = CONDITION_ATTRIBUTES,        flat = CONDITION_PARAM_SPECIALSKILL_MANALEECHAMOUNT,    percent = CONDITION_PARAM_SPECIALSKILL_MANALEECHAMOUNT,        specialPercent = true},
        [STAT_MANALEECHCHANCE]    = { type = CONDITION_ATTRIBUTES,        flat = CONDITION_PARAM_SPECIALSKILL_MANALEECHCHANCE,    percent = CONDITION_PARAM_SPECIALSKILL_MANALEECHCHANCE,        specialPercent = true},
        [STAT_CRIT]                = { type = CONDITION_ATTRIBUTES,        flat = CONDITION_PARAM_SPECIALSKILL_CRITICALHITAMOUNT,    percent = CONDITION_PARAM_SPECIALSKILL_CRITICALHITAMOUNT,    specialPercent = true},
        [STAT_CRITCHANCE]        = { type = CONDITION_ATTRIBUTES,        flat = CONDITION_PARAM_SPECIALSKILL_CRITICALHITCHANCE,    percent = CONDITION_PARAM_SPECIALSKILL_CRITICALHITCHANCE,    specialPercent = true},
    }
    
if not PLAYERSTATS then
    PLAYERSTATS = {}
end

-- tfs just doesn't have it lol
table.find = function(table, value)
    for i, v in pairs(table) do
        if v == value then
            return i
        end
    end
    return false
end

function Item:getMaxSockets()
    return self:getCustomAttribute(CUSTOM_ATTRIBUTE_SOCKETS) or 0
end

function Item:setMaxSockets(amount)
    if type(amount) == "number" then
        return self:setCustomAttribute(CUSTOM_ATTRIBUTE_SOCKETS, amount)
    end
    
    return false
end

function Item:getStats()
    local slots = self:getCustomAttribute(CUSTOM_ATTRIBUTE_STATS) or ""
    slots = slots:split(SLOT_SEPARATOR)

    return slots
end

function Item:getFreeSockets()
    return self:getMaxSockets() - #self:getStats()
end

function compileItemStats(stats)
    return table.concat(stats, SLOT_SEPARATOR)
end

function string:verifyStatIntegrity()
    local word = self:match("[+-x]?%d+%.?%d*%%?") or ""
    local length = self:len()
    
    return word:len() == length
end

function Item:addStat(key,value)
    if self:getFreeSockets() > 0 then
        local stats = self:getStats()
        local newStat = key .. SLOT_VALUE_SEPARATOR .. value
        if not value:verifyStatIntegrity() then
            error("Incorrect stat syntax")
        end
        table.insert(stats, newStat)
        
        local newStats = compileItemStats(stats)
        self:setCustomAttribute(CUSTOM_ATTRIBUTE_STATS, newStats)
        return true
    end
    
    return false
end

function Item:removeStat(index)
    local stats = self:getStats()
    
    if index > 0 and index <= #stats then
        table.remove(stats, index)
        local newStats = compileItemStats(stats)
        self:setCustomAttribute(CUSTOM_ATTRIBUTE_STATS, newStats)
        return true
    end
    
    return false
end

function Item:displayStats()
    if self:getCustomAttribute(CUSTOM_ATTRIBUTE_STATS) or self:getMaxSockets() > 0 then
        local stats = self:getStats()
        local out = ""
        
        for _, v in pairs(stats) do
            v = v:split(SLOT_VALUE_SEPARATOR)
            if v[2] then
                out = out .. string.format(STAT_SYSTEM_CONFIG.slotsOutputFormat, v[1], v[2])
            end
        end
        
        local freeSlots = self:getFreeSockets()
        if freeSlots > 0 then
            for i = 1, freeSlots do
                out = out .. STAT_SYSTEM_CONFIG.emptySlotSymbol
            end
        end
        
        return out
    end
    
    return ""
end

function string:getStatValues()
    local s = self:split(SLOT_VALUE_SEPARATOR)
    local out = {}
    
    if not s[2] then
        s[2] = ""
    end
    
    out = {{
        key = s[1],
        sign = s[2]:match("[+-x]?"),
        amount = s[2]:match("%d+%.?%d*"),
        flat = not s[2]:match("%%")
    }}
    
    return out
end

function ItemType:isWeapon()
    -- NOTE: stackable weapons such as throwing stars and spears are treated as ammunition
    -- ammunition is not supposed to be upgradeable
    -- stackable items with attributes can't be restacked
    return (self:getAttack() > 0 or self:getShootRange() > 1) and not self:isStackable()
end

-- doesn't actually apply buffs, just collects values based on player's data
function Player:assignStat(t, statValues)
    local k = statValues.key
    
    if not t[k] then
        t[k] = {}
    end

    if statValues.sign == "-" then
        statValues.amount = -statValues.amount
    end

    if statValues.flat then
        if t[k].flat then
            t[k].flat = t[k].flat + statValues.amount
        else
            t[k].flat = statValues.amount
        end
    else
        if t[k].percent then
            t[k].percent = t[k].percent + statValues.amount
        else
            t[k].percent = statValues.amount
        end
    end
end

function Player:getEquipmentStats()
    local newStats = {}
    
    for i = 1, 10 do
        local item = self:getSlotItem(i)
        
        if item then
            local stats = item:getStats()
            if #stats > 0 then
                local it = item:getType()
                local weapon = false
                if i == CONST_SLOT_RIGHT or i == CONST_SLOT_LEFT then
                    if it:isWeapon() then
                        weapon = true
                    end
                end

                for j = 1, #stats do
                    local statValues = stats[j]:getStatValues()
                    for k = 1, #statValues do                    
                        if isInArray(STAT_SYSTEM_STATTABLE, statValues[k].key) then
                            self:assignStat(newStats, statValues[k])
                        end
                    end
                end                        
            end
        end
    end
    
    return newStats
end

function calculateBuffValue(player, buffType, buffValue, isFlat)
    local buffData = STAT_SYSTEM_CONDITIONTABLE[buffType]
    if buffData then
        if not isFlat then
            if not buffData.specialPercent then
                buffValue = buffValue + 100
            end
        end

        return buffValue
    end
end

function getConditionStatCode(stat, isFlat)
    local statIndex = table.find(STAT_SYSTEM_STATTABLE, stat)
    
    if statIndex then
        return 1 .. (isFlat and 1 or 0) .. (statIndex < 10 and "0" .. statIndex or statIndex)
    end
end

function getStatBuffParam(buffData, isFlat)
    if isFlat then
        return buffData.flat
    end
    
    return buffData.percent    
end

function Player:updateStats()
    local cid = self:getId()
    local checklist = {} -- stats to remove in case they aren't in the updated set
    
    if PLAYERSTATS[cid] then
        for k, v in pairs(PLAYERSTATS[cid]) do
            checklist[k] = v
        end
    else
        PLAYERSTATS[cid] = {}
    end
    
    -- get updated stats
    local newStats = self:getEquipmentStats()
    local updatedStats = {} -- new stats summed
    
    for stat, value in pairs(newStats) do
    
        if STAT_SYSTEM_CONDITIONTABLE[stat] then
            local vals = {value.flat, value.percent}
            
            for i = 1, #vals do
                local isFlat = i == 1
                if vals[i] then
                    local conditionCode = getConditionStatCode(stat, isFlat)

                    if conditionCode then
                        local conditionType = STAT_SYSTEM_CONDITIONTABLE[stat].type
                        
                        if updatedStats[conditionCode] then
                            updatedStats[conditionCode][2] = vals[i] + updatedStats[conditionCode][2]
                        else
                            updatedStats[conditionCode] = {conditionType, vals[i]}
                        end
                    end
                end
            end
        end
    end
    
    -- apply updated stats
    for conditionCode, conditionValues in pairs(updatedStats) do
        local buffType
        local buffCode = tonumber(conditionCode:sub(-2, -1))
        
        buffType = STAT_SYSTEM_STATTABLE[buffCode]
        
        if buffType then
            local isFlat = tonumber(conditionCode:sub(-3, -3)) == 1
            local playerStat = PLAYERSTATS[cid][conditionCode]
            local conditionType = conditionValues[1]
            local ignore = false
            
            if playerStat then
                if playerStat[2] ~= conditionValues[2] then
                    doRemoveCondition(cid, conditionType, conditionCode)
                else
                    ignore = true -- avoid building already existing condition
                end
                
                checklist[conditionCode] = nil
            end

            -- build buff condition
            if not ignore then
                local buff = createConditionObject(conditionType)
                local buffValue = calculateBuffValue(self, buffType, conditionValues[2], isFlat)
                local buffData = STAT_SYSTEM_CONDITIONTABLE[buffType]
                
                setConditionParam(buff, getStatBuffParam(buffData, isFlat), buffValue)
                
                if buffData.ticks then
                    tickType, tickAmount = buffData.ticks(self)
                    setConditionParam(buff, tickType, tickAmount * 1000)
                end
                
                setConditionParam(buff, CONDITION_PARAM_TICKS, -1)
                setConditionParam(buff, CONDITION_PARAM_SUBID, conditionCode)
                self:addCondition(buff)
                PLAYERSTATS[cid][conditionCode] = {conditionType, conditionValues[2]}
            end
        else
            print("[statSystem] Warning: no buff found for STAT_SYSTEM_STATTABLE code " .. conditionCode:sub(0, -4) .. ", value " .. conditionCode:sub(-2, -1) .. " (condition code: " .. conditionCode .. ")")
        end
    end
    
    -- remove outdated stats
    for conditionCode, conditionValues in pairs(checklist) do    
        doRemoveCondition(cid, conditionValues[1], conditionCode)
        
        PLAYERSTATS[cid][conditionCode] = nil
    end
end

-- player events hooks
function onLogin(player)
    player:updateStats()
    return true
end

function Player:stat_onItemMoved(item, fromPosition, toPosition)
    if fromPosition.x == 65535 or toPosition.x == 65535 then
        if fromPosition.y <= 10 or toPosition.y <= 10 then
            self:updateStats()
        end
    end
end

-- example:
-- local it = player:addItem(2125, 1)
-- it:setMaxSockets(3)
-- it:addStat("HP", "+2%")
-- it:addStat("Sword", "+2")
-- it:removeStat(2) -- removes sword buff

function Player:generateSetTest()
    local set = {2125, 2461, 2379, 2467, 2512, 2124, 2649, 2050, 2643}
    
    for i = 1, #set do
        local it = Item(doPlayerAddItem(self:getId(), set[i], 1))
        it:setMaxSockets(3)
        for j = 1, 3 do
            local value = STAT_SYSTEM_STATTABLE[math.random(1, #STAT_SYSTEM_STATTABLE)]
            local percent = math.random(0,1) == 1 and "%" or ""
            it:addStat(value, "+" .. math.random(1,100) .. percent)
        end
    end
end

data/events/events.xml:
- set onLook, onLookInTrade and onItemMoved to 1

data/events/scripts/player.lua (in Player:onLook):
Lua:
-- right below this:
-- local description = "You see " .. thing:getDescription(distance)
    if thing:isItem() then
        local stats = thing:displayStats()
        if stats ~= "" then
            description = description .. "\n" .. stats
        end
    end

data/events/scripts/player.lua (in Player:onLookInTrade):
Lua:
-- right below this:
-- self:sendTextMessage(MESSAGE_INFO_DESCR, "You see " .. item:getDescription(distance))
local stats = item:displayStats()
        if stats ~= "" then
            description = description .. "\n" .. stats
        end

data/events/scripts/player.lua (in Player:onItemMoved):
Lua:
self:stat_onItemMoved(item, fromPosition, toPosition)



list of functions:
Code:
Player:getEquipmentStats() -- table with stat names as indexes like: ["stat"] = {flat = x, percent = y}
Player:updateStats() -- refresh player equipment buffs
Player:generateSetTest() -- testing function, generates random buffs and values (careful: strong buffs possible)

Item:getMaxSockets() -- number (total item slots)
Item:getStats() -- table of things like: {"stat", "+x%"}
Item:getFreeSockets() -- number (remaining free slots)

Item:displayStats() -- shows stats in readable format (like when looking on items)
Item:addStat(key,value) -- eg. "HP", "+2%"
Item:setMaxSockets(amount) -- change max item slots to exact number
Item:removeStat(index) -- removes x stat from the left

ItemType:isWeapon() -- has attack or is ranged, stackables return false
 
Last edited:
Hey i cant install this. SHow this error:
Code:
Lua Script Error: [CreatureScript Interface]
data/creaturescripts/scripts/itemsystem.lua:onLogin
data/creaturescripts/scripts/itemsystem.lua:94: attempt to call method 'getCustomAttribute' (a nil value)
stack traceback:
        [C]: in function 'getCustomAttribute'
        data/creaturescripts/scripts/itemsystem.lua:94: in function 'getStats'
        data/creaturescripts/scripts/itemsystem.lua:229: in function 'getEquipmentStats'
        data/creaturescripts/scripts/itemsystem.lua:296: in function 'updateStats'
        data/creaturescripts/scripts/itemsystem.lua:379: in function <data/creaturescripts/scripts/itemsystem.lua:378>
 
Hey i cant install this. SHow this error:
Code:
Lua Script Error: [CreatureScript Interface]
data/creaturescripts/scripts/itemsystem.lua:onLogin
data/creaturescripts/scripts/itemsystem.lua:94: attempt to call method 'getCustomAttribute' (a nil value)
stack traceback:
        [C]: in function 'getCustomAttribute'
        data/creaturescripts/scripts/itemsystem.lua:94: in function 'getStats'
        data/creaturescripts/scripts/itemsystem.lua:229: in function 'getEquipmentStats'
        data/creaturescripts/scripts/itemsystem.lua:296: in function 'updateStats'
        data/creaturescripts/scripts/itemsystem.lua:379: in function <data/creaturescripts/scripts/itemsystem.lua:378>

update your TFS, it's for master branch / 1.3
 
I use OTX 3.1

update to otx 4 or use opentibiabr if you want this script, but don't want tfs
 
update to otx 4 or use opentibiabr if you want this script, but don't want tfs
i will update, but this script work like the other? With rune i use in item and add slots?
 
i will update, but this script work like the other? With rune i use in item and add slots?

Yes, but you have to write the way of adding stats yourself. Here is an exampe using functions from the script I provided:
Code:
-- example:
-- local it = player:addItem(2125, 1)
-- it:setMaxSockets(3)
-- it:addStat("HP", "+2%")
-- it:addStat("Sword", "+2")
-- it:removeStat(2) -- removes sword buff
 
It would be great if you added
Attribute regeneration and speed
 
any way to make the slots temporary? could be great for a money sink on a ot
 
any way to make the slots temporary? could be great for a money sink on a ot
You can store expiry date as third information in each slot and rebuild stats updater to remove them when os time() higher than third slot value
 
edit: fixed, i didn't follow directions xd
Great system, thank you!
 
Last edited:
I didn't understand this part where I use this part

local it = player:addItem(2125, 1)
it:setMaxSockets(3)
it:addStat("HP", "+2%")
it:addStat("Sword", "+2")
it:removeStat(2) -- removes sword buff
 
where I put this part

local it = player:addItem(2125, 1)
it:setMaxSockets(3)
it:addStat("HP", "+2%")
it:addStat("Sword", "+2")
it:removeStat(2) -- removes sword buff

I changed a lottery ticket script to make easy tests, you use item 5957, and will have 50% chance to generate a sword (id 2376) with item attributes, or, 50% to make a test set with player:generateSetTest() function. Make the .lua file at data\scripts\actions
Lua:
local ItemTest = Action()
function ItemTest.onUse(player, item, fromPosition, target, toPosition, isHotkey)
    if math.random(2) == 1 then
        player:getPosition():sendMagicEffect(CONST_ME_SOUND_YELLOW)
        item:transform(2376)
        item:addStat("HP","+2%")
        item:addStat("Sword", "+2")
        item:setMaxSockets(3)
    else
        item:getPosition():sendMagicEffect(CONST_ME_POFF)
        item:remove(1)
        player:generateSetTest()
    end
    return true
end

ItemTest:id(5957)
ItemTest:register()
I also recommend you to make a talkaction to test this two functions
Lua:
Player:getEquipmentStats() -- table with stat names as indexes like: ["stat"] = {flat = x, percent = y}
Player:updateStats() -- refresh player equipment buffs

Hope this helps you
 
Back
Top