• 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!
  • New resources must be posted under Resources tab. A discussion thread will be created automatically, you can't open threads manually anymore.

CreatureEvent [TFS 1.3 / 1.4] Upgrade System

how is it gonna have a high cpu usage over few ifs thats ridiculous?
i tested today the equipment checking on each damage dealt and creating 2 extra combat damages virtually no performance loss
this is not 2008
I was literally doing things like that in 2008 :P
just came back to create something in free time.

And there is more then few ifs. It is reading every attribute on each eq slot, and many more. I dont know how powerfull TFS is in general. Maybe this is not too much, but I will try to correct it later this night or tommorow maybe.
 
Eh, all these loops, ugh, hate this system, even as a good base it's just cancer. Just cache the attributes and sum values instead of checking each item and each of the attributes every time. Shit will be 100x more efficient.
 
Eh, all these loops, ugh, hate this system, even as a good base it's just cancer. Just cache the attributes and sum values instead of checking each item and each of the attributes every time. Shit will be 100x more efficient.
how do you still think this system is functional without making huge changes into the way you cached conditions and many other things xD? im sure you never even tested it thoroughly, manual tests you can make with 2 players online in local server won't do it.
1) Attributes sometimes doesn't appear.
2) regen attributes takes action on equip instead on kill so you can spam this as much as you need using simple bot and gain infinite mana.
3) again attributes sometimes doesn't appear regardless of the event to trigger them login/equip you need to correctly declare subId because the way you made it is not correctly drawn and subId starts overlapping.
4) the weird equip in a specific order to be able to find all your attributes on the items

I'm not complaining [In fact i solved them all for myself] im giving you advice that will help you to fix your system after using it in 2 major tibia servers, the same fixes i did in 2021 is the same i did in 2025 and you still think its flawless, it is full of issues dude just big servers doesn't bother to inform you because how arrogant you are (no offense) im just stating a fact.

Edit : i didn't state the improvements required to make the system good with more than 300 players online. each player have equipped 8 items with your attributes system, you can do the math for yourself and understand how stressful this could become only to retrieve each condition/attribute data from the table because it is not linked in a considerable way :D, good luck if you want more information about other systems that you are selling and not for free you will have to share them for free because they are also fucked up
 
how do you still think this system is functional without making huge changes into the way you cached conditions and many other things xD? im sure you never even tested it thoroughly, manual tests you can make with 2 players online in local server won't do it.
1) Attributes sometimes doesn't appear.
2) regen attributes takes action on equip instead on kill so you can spam this as much as you need using simple bot and gain infinite mana.
3) again attributes sometimes doesn't appear regardless of the event to trigger them login/equip you need to correctly declare subId because the way you made it is not correctly drawn and subId starts overlapping.
4) the weird equip in a specific order to be able to find all your attributes on the items

I'm not complaining [In fact i solved them all for myself] im giving you advice that will help you to fix your system after using it in 2 major tibia servers, the same fixes i did in 2021 is the same i did in 2025 and you still think its flawless, it is full of issues dude just big servers doesn't bother to inform you because how arrogant you are (no offense) im just stating a fact.
What the hell are you on about? There are at least few posts in this thread where I specifically stated how many hacks and weird shit was done in this script and how much work it requires and if you are not a dev then it's more than unusable. I've recreated Path of Exile crafting system (1:1, no joke) using this script as a base because that's what it is good for, not copy-paste ready to use. The fact that it is done in pure Lua without proper equip/unequip and with fuck ton of looping over items and attributes says everything. I simply stopped caring about it and didn't bother improving it myself.
 
What the hell are you on about? There are at least few posts in this thread where I specifically stated how many hacks and weird shit was done in this script and how much work it requires and if you are not a dev then it's more than unusable. I've recreated Path of Exile crafting system (1:1, no joke) using this script as a base because that's what it is good for, not copy-paste ready to use. The fact that it is done in pure Lua without proper equip/unequip and with fuck ton of looping over items and attributes says everything. I simply stopped caring about it and didn't bother improving it myself.
for sure i didn't go through all comments i just got to read your last reply, maybe add in title [Requires improvements (Unresolved gamebreaking issues)] and don't forget to add that in github.

Because in my opinion being capable and not providing something flawless is lame, more lame is knowing what improvements its requires and not providing them neither informing people in a proper way to be aware of the bugged system they are adding, many huge servers owners aren't developers and developer is a role out of 10 roles in a successful server, idk it feels like its a way to advertise [Hire me to solve it] in the most proper way. sharing codes that contain bugs are making systems built on it.

Lets go through the facts :
1) You created upgrade system.
2) You created tooltips supporting you (BUGGED) upgrading system
3) now if i want to use your tooltips or in case somebody bought them he will have to use your upgrading system to save time if i want tooltips to support custom attributes in a proper way and use the system that tooltips system
4) After using the upgrade system only god knows the issues beside you and developers that can help themselves. [Other devs will surely refuse to solve issues you should solve for yourself and you know it]
5) your client will get back to you and receive a reply like "zero shame, im proud if it" that somebody i know already received regardless i warned him to not ask you for any help because you don't help unless there is a payment. lame.
6) You go through the cycle of hiring oen and paying oen to fix bugs to not have great loss after buying a system that is based on a system that is not working right [Because no developer accepts to get hold of somebody's shit, you hold your shit for yourself] so they will have to get back to you :D

PS: forcing your customers to always come back to you in my own opinion and i believe many others support this point of view is lame.

so its an endless loop of hiring oen imo that you create in a smart way regardless you mean it or not its just not good when other developers receive endless reports about problems in your systems in fact i get messages daily from people preferring to let me solve it instead receiving an arrogant reply "I dont' care", "Do it yourself"..etc.

I never said you are not smart, I'm sure you are but i ensure that you can be better in the ethical way.

All that bothers me is using codes that was paid for from a reputable one like you that ends with a maze of endless bugs, i didn't face that alone many did, bothways i don't care i did my part in eagerly willing that you keep this reputation and receive less requests to solve issues on your system because what i say ? its oen system? msg oen. which makes me feel bad that i won't help others because i don't want to carry the burden of solving your mess for my own friends.

MY whole point is : Either provide something intact with the objective of owners, or don't release pointless systems and make other systems based on it because new comers doesn't know that there is issues and looking to make deals with you because of your reputation and they end with non-functional server full of bugs after thinking they are doing the best anybody could do, but the facts say they will end with :
1) Server with game breaking issues.
2) Endless of bugs resolving hiring because they can't figure that out themselves.

so yeah having a proper hiring to a developer that doesn't make mistakes is much better, im wasting my time writing all of this for another reason : You can do better or just release fully intact codes that you keep them for yourself, or just don't bother to overwhelm others with bugs that wasn't supposed to be there.

by each time i reported to my friends a bug resolution i did for myself you committed it in your private repos, so why not do them once and for all and save their time.

edit: this will help you understand what am i "on about" : Programming ethics - Wikipedia (https://en.wikipedia.org/wiki/Programming_ethics)
 
Last edited:
Maybe this system isn't perfect but in my opinion still the best out of others available here on otland. It's clean, in one file, easy to understand by non developers users. I will try to change attributes loop for attributes caching. We can solve all of the issues from this system together on forum since it is for free here :) If there is another script like tooltips which was sold with promise that the other (in my opinion bigger) system will work with that - then it's not good. If this system was created to sell different one (tooltips) then it's just a business (give something for free to sell the additional features - standard business model). But i believe the paid system was the tooltips one, not the upgrade system. I think you could still use Tooltips with items set statically in items.xml with name's like legendary, rare etc - if it is bugged even with static items then the creator should fix the issues to fill the promises. The US is for free here asaik and if something is for free, then we should not complain. Oen put a work into this and released it publicly, we should appreciate it.

Anyway Oen did great job with this base, logic and prepere a lot stuff by his own, now we should polish it.
 
Last edited:
Editing this fields will cause a lot of issues I think. There is a lot of stuff which have to be considered (for example if creature stepIn, what with immunities etc).

I found that each field should have his own conditions, different then the others.
For example, poison should not have initDamage - it should hit with damage each 3 seconds for 100 cycles (300 seconds). Damage should be decremented from 5 to 1. Fire field should have init damage (depends from field - there are for example three campfires and each of them should work differently). For fire fields there should be less cycles and damage interval should not be decremented.

There is a lot of differencies between each of the field and they also should include the native absorb items bonus like might right (which is not in US_ENCHANTMENTS). So this should include that as well. I don't event know if it is possible to get attribute of the might right in different way than by description (now I found this, which can be usefull: TFS 1.X+ - Get item absorbPercent of element (https://otland.net/threads/get-item-absorbpercent-of-element.258936/#post-2504818)) or do it manually knowing that this protection percentages are always the same (it can just check the itemId and if it equals might ring itemid then add additional 20% of protection damage).

It would be a lot easier to just include additional bonuses from attributed items when any of damage is applied to the player (and just check the damage type and decrease it when additional abilities are present).

Script actually decrement the poison condition ticks damage - I think It is hard coded in C++, but it work in different way as before

Poison Damage Original: (set in movements.xml)
(60 Cycles)
1x 5
4x 4
7x 3
10x 2
38x 1
Total Damage: 100
180 sec

Custom script Poison Damage: (after removing movements.xml and use in revscript)
(78 Cycles)
5x 5
10x 4
12x 3
18x 2
33x 1
Total Damage: 170
234 sec

EDITED: (way less lines)

LUA:
local FieldAbsorb = MoveEvent()
function FieldAbsorb.onStepIn(player, item, position, fromPosition)

    --Required locals
    local primaryDamageTotal = 0
    local tickInterval = 0
    local duration = 0
    local conditionType = 0
    local initDamage = 0
    local startValue = 0
 
    if player:isPlayer() then
        --Set values for each field type
        if item:getId() == 1490 or item:getId() == 1496 or item:getId() == 1503 then
            tickInterval = 3000
            duration = 180000
            conditionType = CONDITION_POISON
            initDamage = initDamage
            startValue = startValue + 5
        elseif item:getId() == 1487 or item:getId() == 1492 or item:getId() == 1500 or item:getId() == 1423 then --Big Fire field id's (and Big Campfire ID), could be in table for example (initDamage = 20)
            tickInterval = 8000
            duration = 56000 --7 cycles for 8 second each
            conditionType = CONDITION_FIRE
            initDamage = initDamage + 20
            startValue = startValue + 10
        elseif item:getId() == 1488 or item:getId() == 1493 or item:getId() == 1501 or item:getId() == 1424 or item:getId() == 1425 then --Small Fire field id's (and Small Campfire ID's), could be in table for example (initDamage = 10)
            tickInterval = 8000
            duration = 56000 --7 cycles for 8 second each
            conditionType = CONDITION_FIRE
            initDamage = initDamage + 10
            startValue = startValue + 10
        elseif item:getId() == 1491 or item:getId() == 1495 or item:getId() == 1504 then --Energy field id's, could be in table for example (initDamage = 30)
            tickInterval = 10000
            duration = 15000 --1 cycle for 10 second each
            conditionType = CONDITION_ENERGY
            initDamage = initDamage + 30
            startValue = startValue + 25
        end
    
        --Find custom item absorb attributes
        for slot = CONST_SLOT_HEAD, CONST_SLOT_AMMO do
            local item = player:getSlotItem(slot)
            if item then
                if item:getType():usesSlot(slot) then
                    local values = item:getBonusAttributes()
                    if values then
                        for key, value in pairs(values) do
                            local attr = US_ENCHANTMENTS[value[1]]
                            if attr then
                                if attr.combatType and attr.combatType ~= US_TYPES.CONDITION then
                                    if conditionType == CONDITION_POISON then
                                        if attr.combatDamage == COMBAT_EARTHDAMAGE then
                                            if attr.combatType == US_TYPES.DEFENSIVE then
                                                primaryDamageTotal = primaryDamageTotal + value[2] --GetBonusFieldAbsorption
                                            end
                                        end
                                    elseif conditionType == CONDITION_FIRE then
                                        if attr.combatDamage == COMBAT_FIREDAMAGE then
                                            if attr.combatType == US_TYPES.DEFENSIVE then
                                                primaryDamageTotal = primaryDamageTotal + value[2] --GetBonusFieldAbsorption
                                            end
                                        end
                                    elseif conditionType == CONDITION_ENERGY then
                                        if attr.combatDamage == COMBAT_ENERGYDAMAGE then
                                            if attr.combatType == US_TYPES.DEFENSIVE then
                                                primaryDamageTotal = primaryDamageTotal + value[2] --GetBonusFieldAbsorption
                                            end
                                        end
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end
    
        --Set initDamage and startValue
        if primaryDamageTotal > 0 then
            --Decrease damage if attributes was present
            initDamage = math.ceil(initDamage - (initDamage * primaryDamageTotal / 100))
            startValue = math.ceil(startValue - (startValue * primaryDamageTotal / 100))
        end
    
        --Deal proper initial damage (different effects)
        if conditionType == CONDITION_POISON then
            player:getPosition():sendMagicEffect(CONST_ME_GREEN_RINGS)
        elseif conditionType == CONDITION_FIRE then
            doTargetCombat(0, player, COMBAT_FIREDAMAGE, -initDamage, -initDamage, CONST_ME_HITBYFIRE, true, false, false)
        elseif conditionType == CONDITION_ENERGY then
            doTargetCombat(0, player, COMBAT_ENERGYDAMAGE, -initDamage, -initDamage, CONST_ME_HITBYENERGY, true, false, false)
        end
    
        --Set condition to player
        local condition = Condition(conditionType)
            condition:setParameter(CONDITION_PARAM_DELAYED, true)
            condition:setParameter(CONDITION_PARAM_MINVALUE, 70) -- Minimum damage
            condition:setParameter(CONDITION_PARAM_MAXVALUE, 100) -- Maximum damage
            condition:setParameter(CONDITION_PARAM_STARTVALUE, startValue) -- Starting damage
            condition:setParameter(CONDITION_PARAM_TICKINTERVAL, tickInterval)
            condition:setParameter(CONDITION_PARAM_FORCEUPDATE, true)
            condition:setParameter(CONDITION_PARAM_DURATION, duration)
        
            player:addCondition(condition)
    
        return true
    end
end
--Poison Field's Register
FieldAbsorb:id(1490) --Standard Poison Field
FieldAbsorb:id(1496) --Standard Poison Field
FieldAbsorb:id(1503) --Yellow Poison Field

--Big Fire Field's and Big Campfire register
FieldAbsorb:id(1487) --Big Fire Field
FieldAbsorb:id(1492) --Big Fire Field
FieldAbsorb:id(1500) --Yellow Big Fire Field
FieldAbsorb:id(1423) --CampFire Big (20 Init Damage)

--Small Fire Field's and Small Campfire's register
FieldAbsorb:id(1488) --Small Fire Field
FieldAbsorb:id(1493) --Small Fire Field
FieldAbsorb:id(1501) --Yellow Small Fire Field
FieldAbsorb:id(1424) --CampFire Small (10 Init Damage)
FieldAbsorb:id(1425) --CampFire Small (10 Init Damage)

--Energy Field's register:
FieldAbsorb:id(1491) --Energy Field
FieldAbsorb:id(1495) --Energy Field
FieldAbsorb:id(1504) --Yellow Energy Field
FieldAbsorb:register()

Today I will try to include the protection from might rings and other items with Absorbtion in items.xml (using the edited luascript.cpp from link above in this post).

UPDATE: Native absorb's are somehow included by default. Propably it is in C++ somewhere (bound with CONDITION's itself).

I see that this version is not working properly. For firefield it ignores the duration time: (below is a log from fire field initdamage20)
Code:
11:58 You lose 10 hitpoints.
11:59 You lose 10 hitpoints.
11:59 You lose 9 hitpoints.
11:59 You lose 9 hitpoints.
11:59 You lose 8 hitpoints.
11:59 You lose 8 hitpoints.
11:59 You lose 7 hitpoints.
11:59 You lose 7 hitpoints.
12:00 You lose 7 hitpoints.
12:00 You lose 7 hitpoints.
12:00 You lose 6 hitpoints.
12:00 You lose 6 hitpoints.
12:00 You lose 6 hitpoints.
12:00 You lose 6 hitpoints.
12:00 You lose 5 hitpoints.
12:00 You lose 5 hitpoints.
12:01 You lose 4 hitpoints.
12:01 You lose 4 hitpoints.
12:01 You lose 4 hitpoints.
12:01 You lose 4 hitpoints.
12:01 You lose 4 hitpoints.
12:01 You lose 4 hitpoints.
12:01 You lose 3 hitpoints.
12:02 You lose 3 hitpoints.
12:02 You lose 3 hitpoints.
12:02 You lose 3 hitpoints.
12:02 You lose 3 hitpoints.
12:02 You lose 3 hitpoints.
12:02 You lose 2 hitpoints.
12:02 You lose 2 hitpoints.
12:03 You lose 2 hitpoints.
12:03 You lose 2 hitpoints.
12:03 You lose 2 hitpoints.
12:03 You lose 2 hitpoints.
12:03 You lose 2 hitpoints.
12:03 You lose 2 hitpoints.
12:03 You lose 2 hitpoints.
12:03 You lose 2 hitpoints.
12:04 You lose 1 hitpoint.
12:04 You lose 1 hitpoint.
12:04 You lose 1 hitpoint.
12:04 You lose 1 hitpoint.
12:04 You lose 1 hitpoint.
12:04 You lose 1 hitpoint.
12:04 You lose 1 hitpoint.
12:05 You lose 1 hitpoint.
12:05 You lose 1 hitpoint.
12:05 You lose 1 hitpoint.
12:05 You lose 1 hitpoint.
12:05 You lose 1 hitpoint.
12:05 You lose 1 hitpoint.
12:05 You lose 1 hitpoint.
12:05 You lose 1 hitpoint.
12:06 You lose 1 hitpoint.
12:06 You lose 1 hitpoint.
12:06 You lose 1 hitpoint.
12:06 You lose 1 hitpoint.
12:06 You lose 1 hitpoint.
12:06 You lose 1 hitpoint.
 
Last edited:
Eh, all these loops, ugh, hate this system, even as a good base it's just cancer. Just cache the attributes and sum values instead of checking each item and each of the attributes every time. Shit will be 100x more efficient.
@oen432 maybe small tip how I can achieve this? use a table and store all the attributes there after onEquip/onLogin? or maybe use PlayerStorageValue in database and store it there?
 
Last edited:
@oen432 maybe small tip how I can achieve this? use a table and store all the attributes there after onEquip/onLogin? or maybe use PlayerStorageValue in database and store it there?
Have global variable for each enchant index. Then simply create empty table local PLAYER_ATTRIBUTES = {} and in onLogin PLAYER_ATTRIBUTES[player:getId()] = {} (also set to nil on logout). All you then need is functions to work with that. On login, iterate inventory and set attributes, on equip/unequip remove old, add new. And then no more iterations, just check by enchant id if player has one, get the value and do stuff.

LUA:
ENCHANT_MAX_HP = 1
ENCHANT_MAX_MP = 2
ENCHANT_MAGIC_LEVEL = 3
ENCHANT_MELEE_SKILLS = 4
ENCHANT_FISHING = 5
ENCHANT_DISTANCE_FIGHTING = 6
ENCHANT_SHIELDING = 7

US_ENCHANTMENTS = {
  [ENCHANT_MAX_HP] = {
    name = "Maximum Life",

function Player:addAttribute(attrId, value)
  local pid = self:getId()
  if not PLAYER_ATTRIBUTES[pid][attrId] then
    PLAYER_ATTRIBUTES[pid][attrId] = 0
  end
 
  PLAYER_ATTRIBUTES[pid][attrId] = PLAYER_ATTRIBUTES[pid][attrId] + value
end

function Player:removeAttribute(attrId, value)
  local pid = self:getId()
  if PLAYER_ATTRIBUTES[pid][attrId] ~= nil then
    PLAYER_ATTRIBUTES[pid][attrId] = PLAYER_ATTRIBUTES[pid][attrId] - value
    if PLAYER_ATTRIBUTES[pid][attrId] == 0 then
      PLAYER_ATTRIBUTES[pid][attrId] = nil
    end
  end
end

function Player:getAttributes()
  return PLAYER_ATTRIBUTES[self:getId()]
end

function Player:getAttribute(attrId)
  return PLAYER_ATTRIBUTES[self:getId()][attrId] or 0
end

function Player:hasAttribute(attrId)
  return PLAYER_ATTRIBUTES[self:getId()][attrId] ~= nil
end
 
Thanks Oen :) will work with that. That's more then I expected.
 
Last edited:
nvm i just lost 7h because of different script cause all the issues :/
Post automatically merged:

Have global variable for each enchant index. Then simply create empty table local PLAYER_ATTRIBUTES = {} and in onLogin PLAYER_ATTRIBUTES[player:getId()] = {} (also set to nil on logout). All you then need is functions to work with that. On login, iterate inventory and set attributes, on equip/unequip remove old, add new. And then no more iterations, just check by enchant id if player has one, get the value and do stuff.

LUA:
ENCHANT_MAX_HP = 1
ENCHANT_MAX_MP = 2
ENCHANT_MAGIC_LEVEL = 3
ENCHANT_MELEE_SKILLS = 4
ENCHANT_FISHING = 5
ENCHANT_DISTANCE_FIGHTING = 6
ENCHANT_SHIELDING = 7

US_ENCHANTMENTS = {
  [ENCHANT_MAX_HP] = {
    name = "Maximum Life",

function Player:addAttribute(attrId, value)
  local pid = self:getId()
  if not PLAYER_ATTRIBUTES[pid][attrId] then
    PLAYER_ATTRIBUTES[pid][attrId] = 0
  end
 
  PLAYER_ATTRIBUTES[pid][attrId] = PLAYER_ATTRIBUTES[pid][attrId] + value
end

function Player:removeAttribute(attrId, value)
  local pid = self:getId()
  if PLAYER_ATTRIBUTES[pid][attrId] ~= nil then
    PLAYER_ATTRIBUTES[pid][attrId] = PLAYER_ATTRIBUTES[pid][attrId] - value
    if PLAYER_ATTRIBUTES[pid][attrId] == 0 then
      PLAYER_ATTRIBUTES[pid][attrId] = nil
    end
  end
end

function Player:getAttributes()
  return PLAYER_ATTRIBUTES[self:getId()]
end

function Player:getAttribute(attrId)
  return PLAYER_ATTRIBUTES[self:getId()][attrId] or 0
end

function Player:hasAttribute(attrId)
  return PLAYER_ATTRIBUTES[self:getId()][attrId] ~= nil
end
It is working great. I would never come up with it on my own. Thanks a lot Oen :) it will really improve the performance.

Im just wondering is there a reason of usage the onItemMoved and onMoveItem instead of moveevent.onEquip and moveevent.onDeEquip??

I found that they should be available on revscripts

It can be soooooo clean right now
LUA:
-- Optimized Damage Calculation
function us_onDamaged(creature, attacker, primaryDamage, primaryType, origin)
    -- Handle healing bonuses
    if primaryType == COMBAT_HEALING then
        if attacker:isPlayer() then
            local healBonus = attacker:getAttribute(ATTR_INCREASED_HEALING)
            if healBonus > 0 then
                primaryDamage = math.floor(primaryDamage + (primaryDamage * healBonus / 100))
            end
        end
        if creature:isPlayer() then
            local healBonus = creature:getAttribute(ATTR_INCREASED_HEALING)
            if healBonus > 0 then
                primaryDamage = math.floor(primaryDamage + (primaryDamage * healBonus / 100))
            end
        end
        return primaryDamage, primaryType
    end

    -- Offensive attributes (attacker)
    if attacker:isPlayer() then
        -- Double damage chance
        local doubleDamageChance = attacker:getAttribute(ATTR_DOUBLE_DAMAGE)
        if doubleDamageChance > 0 and math.random(100) < doubleDamageChance then
            primaryDamage = primaryDamage * 2
            attacker:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Double damage!")
        end

        -- Damage type bonuses - now includes both specific and elemental damage
        local damageBonus = 0
     
        -- 1. Get specific damage type bonus (fire/earth/energy/physical)
        local damageAttr = getDamageBonusAttribute(primaryType)
        if damageAttr then
            damageBonus = damageBonus + attacker:getAttribute(damageAttr)
        end
     
        -- 2. Add elemental damage bonus (applies to all damage types)
        damageBonus = damageBonus + attacker:getAttribute(ATTR_ELEMENTAL_DAMAGE)
     
        -- Apply total damage bonus
        if damageBonus > 0 then
        print(damageBonus)
            primaryDamage = math.floor(primaryDamage + (primaryDamage * damageBonus / 100))
        end
     
        -- Life/Mana steal
        if math.random(100) < 95 then
            local lifeSteal = attacker:getAttribute(ATTR_LIFE_STEAL)
            if lifeSteal > 0 then
                attacker:addHealth(math.floor(primaryDamage * lifeSteal / 100))
            end
         
            local manaSteal = attacker:getAttribute(ATTR_MANA_STEAL)
            if manaSteal > 0 then
                attacker:addMana(math.floor(primaryDamage * manaSteal / 100))
            end
        end
    end

    -- Defensive attributes (creature)
    if creature:isPlayer() then
        -- Reduction type bonuses - now includes both specific and elemental reduction
        local damageReduction = 0
     
        -- 1. Get specific damage type reduction (fire/earth/energy/physical)
        local reductionAttr = getDamageReductionAttribute(primaryType)
        if reductionAttr then
            damageReduction = damageReduction + creature:getAttribute(reductionAttr)
        end
     
        -- 2. Add elemental reduction (applies to all damage types)
        damageReduction = damageReduction + creature:getAttribute(ATTR_ELEMENTAL_REDUCTION)
     
        -- Apply total damage reduction
        if damageReduction > 0 then
            primaryDamage = math.floor(primaryDamage - (primaryDamage * damageReduction / 100))
        end
    end

    return primaryDamage, primaryType
end

function getDamageBonusAttribute(damageType)
    if damageType == COMBAT_PHYSICALDAMAGE then
        return ATTR_PHYSICAL_DAMAGE
    elseif damageType == COMBAT_FIREDAMAGE then
        return ATTR_FIRE_DAMAGE
    elseif damageType == COMBAT_EARTHDAMAGE then
        return ATTR_EARTH_DAMAGE
    elseif damageType == COMBAT_ENERGYDAMAGE then
        return ATTR_ENERGY_DAMAGE
    end
    return nil
end

function getDamageReductionAttribute(damageType)
    if damageType == COMBAT_PHYSICALDAMAGE then
        return ATTR_PHYSICAL_REDUCTION
    elseif damageType == COMBAT_FIREDAMAGE then
        return ATTR_FIRE_REDUCTION
    elseif damageType == COMBAT_EARTHDAMAGE then
        return ATTR_EARTH_REDUCTION
    elseif damageType == COMBAT_ENERGYDAMAGE then
        return ATTR_ENERGY_REDUCTION
    end
    return nil
end

fluids.lua with new logic:

LUA:
            --Start of Mana healing bonus when attribute is present
            local healvalue = math.random(25, 100)
            local healBonus = player:getAttribute(ATTR_MANA_HEALING)
                if healBonus > 0 then
                    healvalue = math.floor(healvalue + (healvalue * healBonus / 100))
                end
            --End of Mana healing bonus when attribute is present
            target:addMana(healvalue)
 
Last edited:
Im just wondering is there a reason of usage the onItemMoved and onMoveItem instead of moveevent.onEquip and moveevent.onDeEquip??
It wasn't available back then. And I didn't want to introduce source changes into this system.
 
Using the onEquip and onDeEquip in revscripts was problematic (the itemId had to be registred etc), so I stole the code from @Infernum (onInventoryUpdate) and with help of AI created something like that:

LUA:
UpgradeSystemInventory = {
    conditionTracker = {}
}

function UpgradeSystemInventory.onInventoryUpdate(player, item, slot, equip)
    local pid = player:getId()

    -- Handle de-equip: remove conditions and attributes
    if not equip and UpgradeSystemInventory.conditionTracker[pid] and UpgradeSystemInventory.conditionTracker[pid][slot] then
        for conditionId, data in pairs(UpgradeSystemInventory.conditionTracker[pid][slot]) do
            player:removeCondition(data.condition:getType(), CONDITIONID_COMBAT, data.condition:getSubId())
            player:removeAttribute(data.bonusId, data.value)
            if US_CONDITIONS[data.bonusId] and US_CONDITIONS[data.bonusId][data.value] then
                US_CONDITIONS[data.bonusId][data.value][conditionId] = nil
            end
        end
        UpgradeSystemInventory.conditionTracker[pid][slot] = nil
    end

    -- Handle equip logic
    if equip and item then
        local itemType = item:getType()

        -- Add attributes and conditions
        local bonuses = item:getBonusAttributes()
        if bonuses then
            for index, bonus in ipairs(bonuses) do
                local bonusId, bonusValue = bonus[1], bonus[2]
                local attr = US_ENCHANTMENTS[bonusId]
                if attr then
                    player:addAttribute(bonusId, bonusValue)

                    if attr.combatType == US_TYPES.CONDITION then
                        UpgradeSystemInventory.conditionTracker[pid] = UpgradeSystemInventory.conditionTracker[pid] or {}
                        UpgradeSystemInventory.conditionTracker[pid][slot] = UpgradeSystemInventory.conditionTracker[pid][slot] or {}

                        US_CONDITIONS[bonusId] = US_CONDITIONS[bonusId] or {}
                        US_CONDITIONS[bonusId][bonusValue] = US_CONDITIONS[bonusId][bonusValue] or {}

                        local conditionId = string.format("%d:%d:%d:%d", pid, slot, item:getId(), bonusId)
                        if not US_CONDITIONS[bonusId][bonusValue][conditionId] then
                            local condition = Condition(attr.condition)
                            condition:setParameter(CONDITION_PARAM_SUBID, 1000 + player:getNextSubId(slot, index))
                            condition:setParameter(attr.param, attr.percentage and (100 + bonusValue) or bonusValue)
                            condition:setParameter(CONDITION_PARAM_TICKS, -1)

                            US_CONDITIONS[bonusId][bonusValue][conditionId] = condition
                            UpgradeSystemInventory.conditionTracker[pid][slot][conditionId] = {
                                condition = condition,
                                bonusId = bonusId,
                                value = bonusValue
                            }

                            player:addCondition(condition)
                        end
                    end
                end
            end
        end
    end

    return true
end

function us_onLogin(player)
    local pid = player:getId()
    PLAYER_ATTRIBUTES[pid] = {}

    -- Register player-related upgrade events
    player:registerEvent("UpgradeSystemKill")
    player:registerEvent("UpgradeSystemHealth")
    player:registerEvent("UpgradeSystemMana")
    player:registerEvent("UpgradeSystemPD")

    -- Apply bonuses for all equipped items using centralized logic
    for slot = CONST_SLOT_HEAD, CONST_SLOT_AMMO do
        local item = player:getSlotItem(slot)
        if item then
            UpgradeSystemInventory.onInventoryUpdate(player, item, slot, true)
        end
    end
end

It is working good in basic tests.
This one is only update the inventory status - it cannot prevent user from wearing for example unidentified items. So the function onMoveItem should also be implemented to prevent those actions.


Field Absorb with new logic - working with might rings and other non-attributed items with absorbtion. It also removes the charges like it is in default:

LUA:
local FieldAbsorb = MoveEvent()
function FieldAbsorb.onStepIn(player, item, position, fromPosition)
    -- Validate player
    if not player or not player:isPlayer() then
        return true
    end

    -- Field type configuration table (more maintainable than if/else chains)
    local fieldConfig = {
        -- Poison fields
        [1490] = {type = CONDITION_POISON, tick = 3000, cycles = 100, initDmg = 0, startVal = 5, effect = CONST_ME_GREEN_RINGS},
        [1496] = {type = CONDITION_POISON, tick = 3000, cycles = 100, initDmg = 0, startVal = 5, effect = CONST_ME_GREEN_RINGS},
        [1503] = {type = CONDITION_POISON, tick = 3000, cycles = 100, initDmg = 0, startVal = 5, effect = CONST_ME_GREEN_RINGS},
      
        -- Big fire fields
        [1487] = {type = CONDITION_FIRE, tick = 8000, cycles = 70, initDmg = 20, startVal = 10, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_HITBYFIRE},
        [1492] = {type = CONDITION_FIRE, tick = 8000, cycles = 70, initDmg = 20, startVal = 10, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_HITBYFIRE},
        [1500] = {type = CONDITION_FIRE, tick = 8000, cycles = 70, initDmg = 20, startVal = 10, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_HITBYFIRE},
        [1423] = {type = CONDITION_FIRE, tick = 8000, cycles = 70, initDmg = 20, startVal = 10, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_HITBYFIRE},
      
        -- Small fire fields
        [1488] = {type = CONDITION_FIRE, tick = 8000, cycles = 50, initDmg = 10, startVal = 10, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_HITBYFIRE},
        [1493] = {type = CONDITION_FIRE, tick = 8000, cycles = 50, initDmg = 10, startVal = 10, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_HITBYFIRE},
        [1501] = {type = CONDITION_FIRE, tick = 8000, cycles = 50, initDmg = 10, startVal = 10, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_HITBYFIRE},
        [1424] = {type = CONDITION_FIRE, tick = 8000, cycles = 50, initDmg = 10, startVal = 10, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_HITBYFIRE},
        [1425] = {type = CONDITION_FIRE, tick = 8000, cycles = 50, initDmg = 10, startVal = 10, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_HITBYFIRE},
      
        -- Energy fields
        [1491] = {type = CONDITION_ENERGY, tick = 10000, cycles = 25, initDmg = 30, startVal = 25, combat = COMBAT_ENERGYDAMAGE, effect = CONST_ME_HITBYENERGY},
        [1495] = {type = CONDITION_ENERGY, tick = 10000, cycles = 25, initDmg = 30, startVal = 25, combat = COMBAT_ENERGYDAMAGE, effect = CONST_ME_HITBYENERGY},
        [1504] = {type = CONDITION_ENERGY, tick = 10000, cycles = 25, initDmg = 30, startVal = 25, combat = COMBAT_ENERGYDAMAGE, effect = CONST_ME_HITBYENERGY}
    }

    -- Get field configuration
    local config = fieldConfig[item:getId()]
    if not config then
        return true
    end

    -- Calculate damage reduction based on player's resistances
    local damageReduction = 0
  
    -- NEW: Use player:getAttribute() instead of inventory scanning
    if config.type == CONDITION_POISON then
        damageReduction = player:getAttribute(ATTR_EARTH_REDUCTION) + player:getAttribute(ATTR_ELEMENTAL_REDUCTION)
    elseif config.type == CONDITION_FIRE then
        damageReduction = player:getAttribute(ATTR_FIRE_REDUCTION) + player:getAttribute(ATTR_ELEMENTAL_REDUCTION)
    elseif config.type == CONDITION_ENERGY then
        damageReduction = player:getAttribute(ATTR_ENERGY_REDUCTION) + player:getAttribute(ATTR_ELEMENTAL_REDUCTION)
    end

    -- Apply damage reduction
    local finalInitDmg = math.ceil(config.initDmg * (1 - damageReduction / 100))
    local finalStartVal = math.ceil(config.startVal * (1 - damageReduction / 100))

    -- Apply initial damage/effect
    if config.combat then
        doTargetCombat(0, player, config.combat, -finalInitDmg, -finalInitDmg, config.effect, true, false, false)
    else
        player:getPosition():sendMagicEffect(config.effect)
    end

    -- Create and apply condition
    local condition = Condition(config.type)
  
    -- Type-specific parameters
    if config.type == CONDITION_FIRE then
        local condition = Condition(CONDITION_FIRE)
        condition:setParameter(CONDITION_PARAM_DELAYED, true)
        condition:addDamage(6, 8000, -finalStartVal)
        player:addCondition(condition)
    elseif config.type == CONDITION_POISON then
        local condition = Condition(CONDITION_POISON)
        condition:setParameter(CONDITION_PARAM_DELAYED, true)
        condition:setParameter(CONDITION_PARAM_MINVALUE, 70) -- Minimum damage
        condition:setParameter(CONDITION_PARAM_MAXVALUE, 100) -- Maximum damage
        condition:setParameter(CONDITION_PARAM_STARTVALUE, -finalStartVal) -- Starting damage
        condition:setParameter(CONDITION_PARAM_TICKINTERVAL, 4000) -- 5 seconds interval
        condition:setParameter(CONDITION_PARAM_FORCEUPDATE, true)
        condition:setParameter(CONDITION_PARAM_DURATION, 240000) -- 240 seconds (4 minutes)
        player:addCondition(condition)
    elseif config.type == CONDITION_ENERGY then
        local condition = Condition(CONDITION_ENERGY)
        condition:setParameter(CONDITION_PARAM_DELAYED, true)
        condition:addDamage(0, 10000, -finalStartVal)
        player:addCondition(condition)
    end
    return true
end

-- Register all field types
FieldAbsorb:id(1490, 1496, 1503)  -- Poison fields
FieldAbsorb:id(1487, 1492, 1500, 1423)  -- Big fire fields
FieldAbsorb:id(1488, 1493, 1501, 1424, 1425)  -- Small fire fields
FieldAbsorb:id(1491, 1495, 1504)  -- Energy fields
FieldAbsorb:register()


There are differencies between poison condition in realtibia and this implemented here. I will try to fix this script to be exactly the same as posion on real.
 
Last edited:
Corrected Field Absorption with proper old tibia values (followed Oen advice - table is outside the function. There is not need to recreate it every time the function is called).

LUA:
-- Field configuration table
local FIELD_CONFIG = {
    -- Poison fields
    [1490] = {type = CONDITION_POISON, tick = 3000, initDmg = 0, startVal = 5, effect = CONST_ME_GREEN_RINGS},
    [1496] = {type = CONDITION_POISON, tick = 3000, initDmg = 0, startVal = 5, effect = CONST_ME_GREEN_RINGS},
    [1503] = {type = CONDITION_POISON, tick = 3000, initDmg = 0, startVal = 5, effect = CONST_ME_GREEN_RINGS},
    
    -- Big fire fields
    [1487] = {type = CONDITION_FIRE, tick = 8000, initDmg = 20, startVal = 10, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_HITBYFIRE},
    [1492] = {type = CONDITION_FIRE, tick = 8000, initDmg = 20, startVal = 10, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_HITBYFIRE},
    [1500] = {type = CONDITION_FIRE, tick = 8000, initDmg = 20, startVal = 10, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_HITBYFIRE},
    [1423] = {type = CONDITION_FIRE, tick = 8000, initDmg = 20, startVal = 10, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_HITBYFIRE},
    
    -- Small fire fields
    [1488] = {type = CONDITION_FIRE, tick = 8000, initDmg = 10, startVal = 10, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_HITBYFIRE},
    [1493] = {type = CONDITION_FIRE, tick = 8000, initDmg = 10, startVal = 10, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_HITBYFIRE},
    [1501] = {type = CONDITION_FIRE, tick = 8000, initDmg = 10, startVal = 10, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_HITBYFIRE},
    [1424] = {type = CONDITION_FIRE, tick = 8000, initDmg = 10, startVal = 10, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_HITBYFIRE},
    [1425] = {type = CONDITION_FIRE, tick = 8000, initDmg = 10, startVal = 10, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_HITBYFIRE},
    
    -- Energy fields
    [1491] = {type = CONDITION_ENERGY, tick = 10000, initDmg = 30, startVal = 25, combat = COMBAT_ENERGYDAMAGE, effect = CONST_ME_HITBYENERGY},
    [1495] = {type = CONDITION_ENERGY, tick = 10000, initDmg = 30, startVal = 25, combat = COMBAT_ENERGYDAMAGE, effect = CONST_ME_HITBYENERGY},
    [1504] = {type = CONDITION_ENERGY, tick = 10000, initDmg = 30, startVal = 25, combat = COMBAT_ENERGYDAMAGE, effect = CONST_ME_HITBYENERGY}
}

local FieldAbsorb = MoveEvent()
function FieldAbsorb.onStepIn(player, item, position, fromPosition)
    -- Validate player
    if not player or not player:isPlayer() then
        return true
    end

    -- Get field configuration
    local config = FIELD_CONFIG[item:getId()]
    if not config then
        return true
    end

    -- Calculate damage reduction based on player's resistances
    local damageReduction = 0
    
    -- NEW: Use player:getAttribute() instead of inventory scanning
    if config.type == CONDITION_POISON then
        damageReduction = player:getAttribute(ATTR_EARTH_REDUCTION) + player:getAttribute(ATTR_ELEMENTAL_REDUCTION)
    elseif config.type == CONDITION_FIRE then
        damageReduction = player:getAttribute(ATTR_FIRE_REDUCTION) + player:getAttribute(ATTR_ELEMENTAL_REDUCTION)
    elseif config.type == CONDITION_ENERGY then
        damageReduction = player:getAttribute(ATTR_ENERGY_REDUCTION) + player:getAttribute(ATTR_ELEMENTAL_REDUCTION)
    end

    -- Apply damage reduction
    local finalInitDmg = math.ceil(config.initDmg * (1 - damageReduction / 100))
    local finalStartVal = math.ceil(config.startVal * (1 - damageReduction / 100))

    -- Apply initial damage/effect
    if config.combat then
        doTargetCombat(0, player, config.combat, -finalInitDmg, -finalInitDmg, config.effect, true, false, false)
    else
        player:getPosition():sendMagicEffect(config.effect)
    end

    -- Create and apply condition
    local condition = Condition(config.type)
    
    -- Type-specific parameters
    if config.type == CONDITION_FIRE then
        local condition = Condition(CONDITION_FIRE)
        condition:setParameter(CONDITION_PARAM_DELAYED, true)
        condition:addDamage(6, 8000, -finalStartVal)
        player:addCondition(condition)
    elseif config.type == CONDITION_POISON then
        local condition = Condition(CONDITION_POISON)
        condition:setParameter(CONDITION_PARAM_DELAYED, true)
        
        -- Always add the first damage tick with the full damage
        condition:addDamage(0, 4000, -finalStartVal)
        
        -- Add subsequent ticks only if they would do at least 1 damage
        local damage = finalStartVal - 1
        if damage >= 1 then
            condition:addDamage(3, 4000, -damage)
        end
        
        damage = damage - 1
        if damage >= 1 then
            condition:addDamage(6, 4000, -damage)
        end
        
        damage = damage - 1
        if damage >= 1 then
            condition:addDamage(9, 4000, -damage)
        end
        
        damage = damage - 1
        if damage >= 1 then
            condition:addDamage(37, 4000, -damage)
        end
        
        player:addCondition(condition)
    elseif config.type == CONDITION_ENERGY then
        local condition = Condition(CONDITION_ENERGY)
        condition:setParameter(CONDITION_PARAM_DELAYED, true)
        condition:addDamage(0, 10000, -finalStartVal)
        player:addCondition(condition)
    end
    return true
end

-- Register all field types
FieldAbsorb:id(1490, 1496, 1503)  -- Poison fields
FieldAbsorb:id(1487, 1492, 1500, 1423)  -- Big fire fields
FieldAbsorb:id(1488, 1493, 1501, 1424, 1425)  -- Small fire fields
FieldAbsorb:id(1491, 1495, 1504)  -- Energy fields
FieldAbsorb:register()
 
Last edited:
Corrected Field Absorption with proper old tibia values.

LUA:
local FieldAbsorb = MoveEvent()
function FieldAbsorb.onStepIn(player, item, position, fromPosition)
    -- Validate player
    if not player or not player:isPlayer() then
        return true
    end

    -- Field type configuration table (more maintainable than if/else chains)
    local fieldConfig = {
        -- Poison fields
        [1490] = {type = CONDITION_POISON, tick = 3000, startVal = 5, effect = CONST_ME_GREEN_RINGS},
        [1496] = {type = CONDITION_POISON, tick = 3000, startVal = 5, effect = CONST_ME_GREEN_RINGS},
        [1503] = {type = CONDITION_POISON, tick = 3000, startVal = 5, effect = CONST_ME_GREEN_RINGS},
       
        -- Big fire fields
        [1487] = {type = CONDITION_FIRE, tick = 8000, initDmg = 20, startVal = 10, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_HITBYFIRE},
        [1492] = {type = CONDITION_FIRE, tick = 8000, initDmg = 20, startVal = 10, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_HITBYFIRE},
        [1500] = {type = CONDITION_FIRE, tick = 8000, initDmg = 20, startVal = 10, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_HITBYFIRE},
        [1423] = {type = CONDITION_FIRE, tick = 8000, initDmg = 20, startVal = 10, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_HITBYFIRE},
       
        -- Small fire fields
        [1488] = {type = CONDITION_FIRE, tick = 8000, initDmg = 10, startVal = 10, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_HITBYFIRE},
        [1493] = {type = CONDITION_FIRE, tick = 8000, initDmg = 10, startVal = 10, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_HITBYFIRE},
        [1501] = {type = CONDITION_FIRE, tick = 8000, initDmg = 10, startVal = 10, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_HITBYFIRE},
        [1424] = {type = CONDITION_FIRE, tick = 8000, initDmg = 10, startVal = 10, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_HITBYFIRE},
        [1425] = {type = CONDITION_FIRE, tick = 8000, initDmg = 10, startVal = 10, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_HITBYFIRE},
       
        -- Energy fields
        [1491] = {type = CONDITION_ENERGY, tick = 10000, initDmg = 30, startVal = 25, combat = COMBAT_ENERGYDAMAGE, effect = CONST_ME_HITBYENERGY},
        [1495] = {type = CONDITION_ENERGY, tick = 10000, initDmg = 30, startVal = 25, combat = COMBAT_ENERGYDAMAGE, effect = CONST_ME_HITBYENERGY},
        [1504] = {type = CONDITION_ENERGY, tick = 10000, initDmg = 30, startVal = 25, combat = COMBAT_ENERGYDAMAGE, effect = CONST_ME_HITBYENERGY}
    }

    -- Get field configuration
    local config = fieldConfig[item:getId()]
    if not config then
        return true
    end

    -- Calculate damage reduction based on player's resistances
    local damageReduction = 0
   
    -- NEW: Use player:getAttribute() instead of inventory scanning
    if config.type == CONDITION_POISON then
        damageReduction = player:getAttribute(ATTR_EARTH_REDUCTION) + player:getAttribute(ATTR_ELEMENTAL_REDUCTION)
    elseif config.type == CONDITION_FIRE then
        damageReduction = player:getAttribute(ATTR_FIRE_REDUCTION) + player:getAttribute(ATTR_ELEMENTAL_REDUCTION)
    elseif config.type == CONDITION_ENERGY then
        damageReduction = player:getAttribute(ATTR_ENERGY_REDUCTION) + player:getAttribute(ATTR_ELEMENTAL_REDUCTION)
    end

    -- Apply damage reduction
    local finalInitDmg = math.ceil(config.initDmg * (1 - damageReduction / 100))
    local finalStartVal = math.ceil(config.startVal * (1 - damageReduction / 100))

    -- Apply initial damage/effect
    if config.combat then
        doTargetCombat(0, player, config.combat, -finalInitDmg, -finalInitDmg, config.effect, true, false, false)
    else
        player:getPosition():sendMagicEffect(config.effect)
    end

    -- Create and apply condition
    local condition = Condition(config.type)
   
    -- Type-specific parameters
    if config.type == CONDITION_FIRE then
        local condition = Condition(CONDITION_FIRE)
        condition:setParameter(CONDITION_PARAM_DELAYED, true)
        condition:addDamage(6, 8000, -finalStartVal)
        player:addCondition(condition)
    elseif config.type == CONDITION_POISON then
        local condition = Condition(CONDITION_POISON)
        condition:setParameter(CONDITION_PARAM_DELAYED, true)

        -- Always add the first damage tick with the full damage
        condition:addDamage(0, 4000, -finalStartVal)

        -- Add subsequent ticks only if they would do at least 1 damage
        local damage = finalStartVal - 1
        if damage >= 1 then
            condition:addDamage(3, 4000, -damage)
        end

        damage = damage - 1
        if damage >= 1 then
            condition:addDamage(6, 4000, -damage)
        end

        damage = damage - 1
        if damage >= 1 then
            condition:addDamage(9, 4000, -damage)
        end

        damage = damage - 1
        if damage >= 1 then
            condition:addDamage(37, 4000, -damage)
        end

        player:addCondition(condition)
    elseif config.type == CONDITION_ENERGY then
        local condition = Condition(CONDITION_ENERGY)
        condition:setParameter(CONDITION_PARAM_DELAYED, true)
        condition:addDamage(0, 10000, -finalStartVal)
        player:addCondition(condition)
    end
    return true
end

-- Register all field types
FieldAbsorb:id(1490, 1496, 1503)  -- Poison fields
FieldAbsorb:id(1487, 1492, 1500, 1423)  -- Big fire fields
FieldAbsorb:id(1488, 1493, 1501, 1424, 1425)  -- Small fire fields
FieldAbsorb:id(1491, 1495, 1504)  -- Energy fields
FieldAbsorb:register()
Move that table outside the function.
 
Here is also my suggestion with getNextSubId issue. I did not found an issue with Oen's version - but as @El Bringy said - it maybe could starts overlapping when a lot of players would do equip/unequip operation.

LUA:
function Player.getNextSubId(self, itemSlot, attrSlot)
    local cid = self:getId()
    if not US_SUBID[cid] then
        US_SUBID[cid] = {}
    end

    if not US_SUBID[cid][itemSlot] then
        US_SUBID[cid][itemSlot] = {}
    end

    -- Calculate a unique subId based on slot and attribute position
    -- This ensures each combination of itemSlot and attrSlot gets a unique subId
    -- Format: 1XXYY where XX is itemSlot (01-99) and YY is attrSlot (01-99)
    -- This gives us unique subIds from 10101 to 19999
    
    -- Ensure slots are within valid range
    local normalizedItemSlot = (itemSlot % 99) + 1
    local normalizedAttrSlot = (attrSlot % 99) + 1
    
    -- Generate unique subId
    local subId = 10000 + (normalizedItemSlot * 100) + normalizedAttrSlot
    
    -- Store the subId for reference
    US_SUBID[cid][itemSlot][attrSlot] = subId

    return subId
end

And also clean US_SUBID when logout:
LUA:
US_SUBID[pid] = nil  -- Clean up subIds on logout

Initialize it on login:
LUA:
US_SUBID[pid] = {}

and also doing unequip operation clean the slot:

LUA:
                -- Clean up subIds for this slot
                if US_SUBID[pid] and US_SUBID[pid][slot] then
                    US_SUBID[pid][slot] = {}
                end

I'm not sure about this change - any suggestions are very welcome.
 
Back
Top