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

TalkAction [TFS 1.3+] Player statistics in text dialog

VitoxMaster

Member
Joined
Feb 6, 2023
Messages
38
Reaction score
16
Hi guys!
I've been here for quite some time and I decided to share my last small talkaction implemented for my server. Since my server is based on 8.6 (Nekiro downgrade TFS 1.5) and I use official tibia client I have no possibility to simply implement new window on user interface. That's how an idea was born. Maybe someone will find it useful :)
  • What does it do?
This simple talkaction "!stats" display text dialog window with player's basic statistics related to character parameters, combat or resistances resulting from the worn equipment.
  • How does it look like?
1691349439599.png
  • How do you calculate resistance?
They are not added together, they are multiplied together. If you have 5% and 15% resistance on items you get:
1 - (1-0.15) * (1-0.05) = 0.1925. —>19%. [Reddit source: reddit]
  • Do you take server stages into consideration for skill advance calculation?
Yes, it will depend on your server's config.
  • How about custom attributes?
You will find in code that I have custom attributes proposed by @Sarah Wesker in this system. I hope it's not a problem that I used it there for crit/life/mana leech :D
There is no problem in adding new functions and custom attributes. Treat my code as a template with field for improvement.
  • How to implement this?
That's the neat part - it should be pretty easy. Just remember, in order to make it work it is required to have changes related to item abilities (If it doesn't sound familiar to you, please refer to this).
  1. Code:
    /data/talkactions/talkactions.xml
    Lua:
    <talkaction words="!stats" script="stats.lua" />
  2. Code:
    data/talkactions/scripts/stats.lua
    Lua:
    local slotName = { CONST_SLOT_AMMO,        CONST_SLOT_ARMOR,
            CONST_SLOT_BACKPACK,
            CONST_SLOT_FEET,
            CONST_SLOT_HEAD,
            CONST_SLOT_LEFT,
            CONST_SLOT_LEGS,
            CONST_SLOT_NECKLACE,
            CONST_SLOT_RIGHT,
            CONST_SLOT_RING
    }
    
    local map = {
        [COMBAT_PHYSICALDAMAGE] = 1,
        [COMBAT_ENERGYDAMAGE] = 2,
        [COMBAT_EARTHDAMAGE] = 3,
        [COMBAT_LIFEDRAIN] = 6,
        [COMBAT_MANADRAIN] = 7,
        [COMBAT_FIREDAMAGE] = 4,
        [COMBAT_DROWNDAMAGE] = 9,
        [COMBAT_ICEDAMAGE] = 10,
        [COMBAT_HOLYDAMAGE] = 11,
        [COMBAT_DEATHDAMAGE] = 12,
    }
    
    function combatToAbilityType(combat)
        return map[combat]
    end
    
    function onSay(player, words, param)
        local text = getPlayerName(player.uid) .. "'s statistics: \n"
        text = text .. "Level:  " .. player:getLevel() .."\n"
        text = text .. "Speed:  " .. math.floor(player:getSpeed()/2) .."\n"
        text = text .. "Max HP:  " .. player:getMaxHealth() .."\n"
        text = text .. "Max Mana:  " .. player:getMaxMana() .."\n"
        text = text .. "Max Capacity:  " .. math.floor(player:getCapacity()/100) .."\n"
        text = text .. "Stamina:  " .. calculateStamina(player) .."\n"
        text = text .. "Residence:  " .. player:getTown():getName() .."\n"
        text = text .. "--------------------------\n"
        text = text .. "Total Armor:  " .. sumAttributeValues(player.uid, ITEM_ATTRIBUTE_ARMOR) .."\n"
        text = text .. "Total Defense:  " .. sumAttributeValues(player.uid, ITEM_ATTRIBUTE_DEFENSE, ITEM_ATTRIBUTE_EXTRADEFENSE) .."\n"
        text = text .. "Attack Range:  " .. calculateAttackRange(player.uid) .."\n"
        text = text .. "Speed Boost:  " .. calculateSpeedBoost(player) .."\n"
        text = text .. "Physical Resistance:  " .. calculateTotalResistance(player.uid, COMBAT_PHYSICALDAMAGE) .."\n"
        text = text .. "Energy Resistance:  " .. calculateTotalResistance(player.uid, 2) .."\n"
        text = text .. "Earth Resistance:  " .. calculateTotalResistance(player.uid, COMBAT_EARTHDAMAGE) .."\n"
        text = text .. "Fire Resistance:  " .. calculateTotalResistance(player.uid, COMBAT_FIREDAMAGE) .."\n"
        text = text .. "Lifedrain Resistance:  " .. calculateTotalResistance(player.uid, COMBAT_LIFEDRAIN) .."\n"
        text = text .. "Manadrain Resistance:  " .. calculateTotalResistance(player.uid, COMBAT_MANADRAIN) .."\n"
        text = text .. "Drown Resistance:  " .. calculateTotalResistance(player.uid, COMBAT_DROWNDAMAGE) .."\n"
        text = text .. "Ice Resistance:  " .. calculateTotalResistance(player.uid, COMBAT_ICEDAMAGE) .."\n"
        text = text .. "Holy Resistance:  " .. calculateTotalResistance(player.uid, COMBAT_HOLYDAMAGE) .."\n"
        text = text .. "Death Resistance:  " .. calculateTotalResistance(player.uid, COMBAT_DEATHDAMAGE) .."\n"
        text = text .. "--------------------------\n"
        text = text .. "Critical Hit Chance:  +" .. getHighestCustomAttributeValues(player.uid, "SarahWesker_critical hit chance" ) .."\n"
        text = text .. "Critical Extra Damage:  +" .. sumCustomAttributeValues(player.uid, "SarahWesker_critical extra damage" ) .."\n"
        text = text .. "Life Leech Chance:  +" .. getHighestCustomAttributeValues(player.uid, "SarahWesker_hp leech chance" ) .."\n"
        text = text .. "Life Leech Amount:  +" .. sumCustomAttributeValues(player.uid, "SarahWesker_hp leech amount" ) .."\n"
        text = text .. "Mana Leech Chance:  +" .. getHighestCustomAttributeValues(player.uid, "SarahWesker_mana leech chance" ) .."\n"
        text = text .. "Mana Leech Amount:  +" .. sumCustomAttributeValues(player.uid, "SarahWesker_mana leech amount" ) .."\n"
        text = text .. "--------------------------\n"
        text = text .. "Magic Level [MLVL]:  " .. player:getMagicLevel() .."\n"
        text = text .. "Fist Fighting [FF]:  " .. player:getSkillLevel(SKILL_FIST) .."\n"
        text = text .. "Club Fighting [CF]:  " .. player:getSkillLevel(SKILL_CLUB) .."\n"
        text = text .. "Sword Fighting [SF]:  " .. player:getSkillLevel(SKILL_SWORD) .."\n"
        text = text .. "Axe Fighting [AF]:  " .. player:getSkillLevel(SKILL_AXE) .."\n"
        text = text .. "Distance Fighting [DF]:   " .. player:getSkillLevel(SKILL_DISTANCE) .."\n"
        text = text .. "Shielding [S]:  " .. player:getSkillLevel(SKILL_SHIELD) .."\n"
        text = text .. "--------------------------\n"
        text = text .. "Mana to reach next [MLVL]:   " .. calculateManaForNextMagicLevel(player) .."\n"
        text = text .. "Hits to reach next [FF]:   " .. calculateTriesForNext(player, SKILL_FIST) .."\n"
        text = text .. "Hits to reach next [CF]:   " .. calculateTriesForNext(player, SKILL_CLUB) .."\n"
        text = text .. "Hits to reach next [SF]:   " .. calculateTriesForNext(player, SKILL_SWORD) .."\n"
        text = text .. "Hits to reach next [AF]:   " .. calculateTriesForNext(player, SKILL_AXE) .."\n"
        text = text .. "Hits to reach next [DF]:   " .. calculateTriesForNext(player, SKILL_DISTANCE) .."\n"
        text = text .. "Hits to reach next [S]:   " .. calculateTriesForNext(player, SKILL_FIST) .."\n"
        player:showTextDialog(11755, text)
        return false
    end
    
    function calculateManaForNextMagicLevel(player)
        local requiredMP = player:getVocation():getRequiredManaSpent(player:getMagicLevel() + 1)
        local spentMP = getPlayerSpentMana(player.uid)
        return math.floor((requiredMP - spentMP)/configManager.getNumber(configKeys.RATE_MAGIC)+0.5)
    end
    function calculateTriesForNext(player, skillId)
        local requiredTries = player:getVocation():getRequiredSkillTries(skillId, player:getSkillLevel(skillId)+1)
        local doneTries = player:getSkillTries(skillId)
        return math.floor((requiredTries - doneTries)/configManager.getNumber(configKeys.RATE_SKILL)+0.5)
    end
    
    function calculateTotalResistance(cid, combatDamageType)
        local totalValue = 1
        for i = 1, #slotName do
            local item = getPlayerSlotItem(cid, i)
            if item ~= nil and item.itemid > 0 then
                local itemResistance = Item(item.uid):getType():getAbilities().absorbPercent[combatToAbilityType(combatDamageType)]
                if(itemResistance ~= nil) then
                    totalValue = totalValue * (1.0-itemResistance/100)
                end
            end
        end
        return  string.format("%.2f", (1.0-totalValue) * 100) .. '%'
    end
    
    function sumCustomAttributeValues(cid, customAttributeId)
        local totalValue = 0
        for i = 1, #slotName do
            local item = getPlayerSlotItem(cid, i)
            if item ~= nil and item.itemid > 0 then
                local itemAttributeVal = Item(item.uid):getCustomAttribute(customAttributeId)
                if(itemAttributeVal ~= nil and itemAttributeVal > 0) then
                    totalValue = totalValue + itemAttributeVal
                end
            end
        end
        return totalValue .."%"
    end
    
    function getHighestCustomAttributeValues(cid, customAttributeId)
        local totalValue = 0
        for i = 1, #slotName do
            local item = getPlayerSlotItem(cid, i)
            if item ~= nil and item.itemid > 0 then
                local itemAttributeVal = Item(item.uid):getCustomAttribute(customAttributeId)
                if(itemAttributeVal ~= nil and itemAttributeVal > 0) then
                    totalValue = math.max(totalValue, itemAttributeVal)
                end
            end
        end
        return totalValue .."%"
    end
    
    
    function sumAttributeValues(cid, attributeId, additionalAttributeId)
        local totalValue = 0
        for i = 1, #slotName do
            local item = getPlayerSlotItem(cid, i)
            if item ~= nil and item.itemid > 0 then
                local itemAttributeVal = getItemAttribute(item.uid, attributeId)
                if(itemAttributeVal ~= nil and itemAttributeVal > 0) then
                    totalValue = totalValue + itemAttributeVal
                end
                if(additionalAttributeId~=nil) then
                    local itemAdditionalAttributeVal = getItemAttribute(item.uid, additionalAttributeId)
                    if(itemAdditionalAttributeVal ~= nil and itemAdditionalAttributeVal > 0) then
                        totalValue = totalValue + itemAdditionalAttributeVal
                    end
                end
            end
        end
        return totalValue
    end
    
    
    function calculateStamina(player)
        local staminaMinutes = player:getStamina()
        return math.floor(staminaMinutes/60) .. 'h '.. math.fmod(staminaMinutes, 60) .. 'min'
    end
    
    function calculateAttackRange(cid)
        local defaultShootRange = 'melee'
        local weapon = getPlayerSlotItem(cid, CONST_SLOT_LEFT)
        if( weapon ~= nil and weapon.itemid > 0) then
            local attrShootRange = getItemAttribute(weapon.uid, ITEM_ATTRIBUTE_SHOOTRANGE)
            if(attrShootRange ~= nil and attrShootRange > 1) then
                return attrShootRange
            end
        end
        return defaultShootRange
    end
    
    function calculateSpeedBoost(player)
        local currentSpeed = math.floor(player:getSpeed()/2)
        local baseSpeed = math.floor(player:getBaseSpeed()/2)
        if(currentSpeed>baseSpeed) then
            return " +"..(math.abs(currentSpeed-baseSpeed))
        elseif (currentSpeed == baseSpeed) then
            return 0
        else
            return " -"..(math.abs(currentSpeed-baseSpeed))
        end
    end

If someone will be interested in further version I will probably try to implement "recording" of player's best hit and display such data among others. You can give yours ideas which additional data would be nice to see there. I'm open to suggestions!
Your tibian friend, Vitox
Post automatically merged:

If someone wants to extend with "best hit" and "highest hit received".
Create file /data/scripts/player_monster_hits_monitor.lua and fill with:
Lua:
local creatureevent = CreatureEvent("PlayerMonsterHitMonitor")
function creatureevent.onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType,
    origin)
    if not creature or not attacker or creature == attacker then
        return primaryDamage, primaryType, secondaryDamage, secondaryType
    end               
    local totalDamage = primaryDamage + secondaryDamage
    if attacker:isPlayer() then
        player = Player(attacker.uid)
        local playerBestHit = player:getStorageValue(PlayerStorageKeys.playerBestHit)
        if (totalDamage > playerBestHit) then
            player:setStorageValue(PlayerStorageKeys.playerBestHit, totalDamage)
        end
    elseif (creature:isPlayer()) then
        player = Player(creature.uid)
        local totalDamage = primaryDamage + secondaryDamage
        local monsterBestHit = player:getStorageValue(PlayerStorageKeys.monsterBestHit)
        if (totalDamage > monsterBestHit) then
            player:setStorageValue(PlayerStorageKeys.monsterBestHit, totalDamage)
        end
    end
    return primaryDamage, primaryType, secondaryDamage, secondaryType
end

creatureevent:register()

local ec = EventCallback
function ec.onTargetCombat(creature, target)
    if creature and target then
        creature:registerEvent("PlayerMonsterHitMonitor")
        target:registerEvent("PlayerMonsterHitMonitor")
    end
    return RETURNVALUE_NOERROR
end
ec:register(7)


Add to /data/lib/core/storages.lua following properties (change values if you want to):
Lua:
PlayerStorageKeys = {
[...]
    playerBestHit = 499000,
    monsterBestHit = 499001,
[...]
}

Now going back to stats.lua. Add function:
Lua:
function getPlayerStorageData(player, storageId)
    local value = player:getStorageValue(storageId)
    if(value == nil or value <0) then
        return 0
    else
        return value
    end
end
Having this function (better value display) we can finally add 2 lines with new statistics (place it where you want to, in block where text is prepared):
Lua:
    text = text .. "Best Hit Dealt:  " .. getPlayerStorageData(player, PlayerStorageKeys.playerBestHit) .. "\n"
    text = text .. "Hightest Hit Received:  " .. getPlayerStorageData(player, PlayerStorageKeys.monsterBestHit) .. "\n"
 
Last edited:
Correction: Basic functionality with abilities (for resistances) taken from itemType doesn't need special scripts (item ability v2 is not required in that case). If you have implemented that mechanism, please take abilities from Item not from the ItemType as I did.
 
Back
Top