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