tibia

Documentation for this module may be created at Module:CreatureResistanceTable/doc

-- Module:CreatureResistanceTable

local p = {}

local ELEMENT_ICONS = {
    Physical = 'Bestiary Physical Icon Big',
    Death    = 'Cursed Icon Big',
    Holy     = 'Dazzled Icon Big',
    Ice      = 'Freezing Icon Big',
    Fire     = 'Burning Icon Big',
    Energy   = 'Electrified Icon Big',
    Earth    = 'Poisoned Icon Big',
    Drown    = 'Drowning Icon Big',
}

-- Strip the % sign and return the numeric value.
-- If the value contains '?' return the raw label and nil for numeric.
local function getPct(raw)
    if not raw then return 100, '100%' end
    local label = raw
    local stripped = raw:match('^([%-%.%d]+)%%')
    if stripped then
        return tonumber(stripped), label
    end
    return 100, label
end

-- Cap the modifier at ±300 for bar rendering purposes
local function clampMod(val)
    if math.abs(val) > 300 then
        return (val / math.abs(val)) * 300
    end
    return val
end

local function barColor(mod)
    if mod <= 0 then
        return '#ff3333'  -- immune or heals: red
    elseif mod < 100 then
        return '#d0bd56'  -- resistant: yellow
    elseif mod == 100 then
        return '#ffffff'  -- neutral: white
    else
        return '#27c429'  -- weak: green
    end
end

local function renderElement(element, elemMod, label, range, minVal, maxVal)
    local icon = ELEMENT_ICONS[element] or ELEMENT_ICONS['Physical']

    local barWidth = range > 0 and (100 * math.abs(elemMod) / range) or 0

    local borderLeft  = (elemMod < 0 or minVal == 0) and '1px' or '0'
    local borderRight = (elemMod > 0 or maxVal == 0) and '1px' or '0'
    local borderStyle = string.format('border-width:1px %s 1px %s;', borderRight, borderLeft)

    local barPosition
    if elemMod < 0 then
        local leftPct = range > 0 and (100 * (elemMod + math.abs(minVal)) / range) or 0
        barPosition = string.format('left:%.4f%%;', leftPct)
    else
        local leftPct = range > 0 and (100 * math.abs(minVal) / range) or 0
        barPosition = string.format('left:%.4f%%;', leftPct)
    end

    local barStyle = string.format(
        '%s width:%.4f%%; background-color:%s; %s',
        borderStyle, barWidth, barColor(elemMod), barPosition
    )

    local labelPos
    local outsideClass = ''
    if elemMod == 0 then
        local leftCalc = range > 0 and (100 * (math.abs(minVal) - math.abs(elemMod)) / range) or 0
        labelPos = string.format('left:calc(5px + 100%% * %.4f)', leftCalc / 100)
    elseif elemMod < 0 then
        local leftCalc = range > 0 and (100 * (math.abs(minVal) - math.abs(elemMod)) / range) or 0
        labelPos = string.format('left:calc(5px + 100%% * %.4f)', leftCalc / 100)
    else
        if range > 0 and (elemMod / range) > 0.15 then
            local rightCalc = 100 - (100 * (elemMod + math.abs(minVal)) / range)
            labelPos = string.format('right:calc(5px + %.4f%%)', rightCalc)
        else
            local leftCalc = range > 0 and (100 * (math.abs(elemMod) + math.abs(minVal)) / range) or 0
            labelPos = string.format('left:calc(%.4f%% + 5px)', leftCalc)
            outsideClass = ' outside-pct'
        end
    end

    local labelStyle = string.format(
        'position:absolute; %s; display:inline-block;',
        labelPos
    )

    return string.format(
        '<div class="creature-resistance-el" title="%s" data-value="%.4f">' ..
        '<div class="creature-resistance-label">%s [[File:%s.gif|16px|link=]]</div>' ..
        '<div class="creature-resistance-bar1">' ..
        '<div class="creature-resistance-bar2" style="%s"></div>' ..
        '<div class="creature-resistance-pct%s" style="%s">%s</div>' ..
        '</div></div>',
        element,
        elemMod / 100,
        element, icon,
        barStyle,
        outsideClass, labelStyle, label
    )
end

function p.render(frame)
    local args = frame.args

    -- Only show Drown if explicitly provided
    local elementOrder = { 'Physical', 'Death', 'Holy', 'Ice', 'Fire', 'Energy', 'Earth' }
    if args.drown and args.drown ~= '' then
        table.insert(elementOrder, 'Drown')
    end

    -- Parse elements
    local mods   = {}
    local labels = {}
    for _, el in ipairs(elementOrder) do
        local key = el:lower()
        local raw = args[key] or '100%'
        local val, lbl = getPct(raw)
        mods[el]   = clampMod(val)
        labels[el] = lbl
    end

    -- Compute range for bar scaling
    local values = { 100, 0 }  -- always anchor range to include 100 and 0
    for _, el in ipairs(elementOrder) do
        table.insert(values, mods[el])
    end

    local maxVal = values[1]
    local minVal = values[1]
    for _, v in ipairs(values) do
        if v > maxVal then maxVal = v end
        if v < minVal then minVal = v end
    end

    local range
    if minVal < 0 then
        range = math.abs(minVal) + maxVal
    else
        range = maxVal
        if range == 0 then range = 100 end
    end

    local out = {}

    out[#out+1] = '<div id="creature-resistance-d">'

    -- Center line divider (only when range spans both positive and negative values)
    if maxVal > 0 and minVal < 0 then
        local leftPct = 100 * math.abs(minVal) / range
        out[#out+1] = string.format(
            '<div style="position:absolute;border-right:1px solid;height:calc(100%% - 2px);left:99px;width:calc(1px + (100%% - 100px) * %.4f);z-index:10;"></div>',
            leftPct / 100
        )
    end

    for _, el in ipairs(elementOrder) do
        out[#out+1] = renderElement(el, mods[el], labels[el], range, minVal, maxVal)
    end

    out[#out+1] = '</div>'

    return table.concat(out, '\n')
end

return p