Difference between revisions of "Module:Monsters stats table"

From Idlescape Wiki
Jump to navigation Jump to search
(Tables are collapseable)
(Fix crit reduction value and spelling of affinities)
 
(7 intermediate revisions by the same user not shown)
Line 111: Line 111:
 
     local armorThreatFinal = (targetArmorRating + monster.defense * 10) * armorThreat
 
     local armorThreatFinal = (targetArmorRating + monster.defense * 10) * armorThreat
 
     local baseThreat = (potentialDamageThreatFinal + weaponThreatFinal) * attackSpeedThreatFinal + armorThreatFinal
 
     local baseThreat = (potentialDamageThreatFinal + weaponThreatFinal) * attackSpeedThreatFinal + armorThreatFinal
     return baseThreat
+
     return math.floor(baseThreat)
 
end
 
end
  
 
local function genDefensiveTable(monsters, collapsed)
 
local function genDefensiveTable(monsters, collapsed)
     local defensiveAffinties = {}
+
     local defensiveAffinities = {}
     local s = "{|class=\"wikitable sortable hover-highlight mw-collapsible " .. collapsed .. "\" style=\"text-align:center\"\n"
+
     local s = "{|class=\"wikitable sortable mw-collapsible " .. collapsed .. "\" style=\"text-align:center;width:100%\"\n"
     s = s .. "|+ class=\"nowrap\" |Defensive Affinities"
+
     s = s .. "|+ style=\"white-space:nowrap\" |Defensive Affinities\n"
 
     s = s .. "!rowspan=2 colspan=2|Monster\n"
 
     s = s .. "!rowspan=2 colspan=2|Monster\n"
 
     s = s .. "!rowspan=2|Base<br/>Threat\n"
 
     s = s .. "!rowspan=2|Base<br/>Threat\n"
Line 141: Line 141:
 
             for affinity, affinityValue in pairs(value) do
 
             for affinity, affinityValue in pairs(value) do
 
                 if monster.defensiveDamageAffinity[affinity] then
 
                 if monster.defensiveDamageAffinity[affinity] then
                     defensiveAffinties[index] = { affinity = monster.defensiveDamageAffinity[affinity] }
+
                     defensiveAffinities[index] = { affinity = monster.defensiveDamageAffinity[affinity] }
 
                 else
 
                 else
                     defensiveAffinties[index] = { affinity = affinityValue }
+
                     defensiveAffinities[index] = { affinity = affinityValue }
 
                 end
 
                 end
 
             end
 
             end
 
         end
 
         end
         s = s .. "|" .. img._img({ monster["name"], 42 }) .. "\n"
+
         s = s .. "|" .. img._img({ monster["name"], 60 }) .. "\n"
 
         s = s .. "|style=\"text-align:left\"|[[" .. monster["name"] .. "]]\n"
 
         s = s .. "|style=\"text-align:left\"|[[" .. monster["name"] .. "]]\n"
 
         s = s .. "| " .. calcThreat(monster) .. "\n"
 
         s = s .. "| " .. calcThreat(monster) .. "\n"
 
         s = s .. "|" .. tostring(monster.defensiveCritical.chance * 100) .. "%\n"
 
         s = s .. "|" .. tostring(monster.defensiveCritical.chance * 100) .. "%\n"
         s = s .. "|" .. tostring(monster.defensiveCritical.damageMultiplier * 100) .. "%\n"
+
         s = s .. "|" .. tostring(100 - (monster.defensiveCritical.damageMultiplier * 100)) .. "%\n"
         for _, value in pairsByKeys(defensiveAffinties) do
+
         for _, value in pairsByKeys(defensiveAffinities) do
             for affinity, affinityValue in pairs(value) do
+
             for _, affinityValue in pairs(value) do
 
                 if affinityValue > 1 then
 
                 if affinityValue > 1 then
 
                     s = s .. "|<span style=\"color:#4caf50\">" .. tonumber((affinityValue - 1) * 100) .. "%<span/>\n"
 
                     s = s .. "|<span style=\"color:#4caf50\">" .. tonumber((affinityValue - 1) * 100) .. "%<span/>\n"
Line 170: Line 170:
  
 
local function genOffensiveTable(monsters, collapsed)
 
local function genOffensiveTable(monsters, collapsed)
     local offensiveAffinties = {}
+
     local offensiveAffinities = {}
     local s = "{|class=\"wikitable sortable hover-highlight mw-collapsible " .. collapsed .. "\" style=\"text-align:center\"\n"
+
     local s = "{|class=\"wikitable sortable mw-collapsible " .. collapsed .. "\" style=\"text-align:center;width:100%\"\n"
     s = s .. "|+ class=\"nowrap\" |Offensive Affinities"
+
     s = s .. "|+ style=\"white-space:nowrap\" |Offensive Affinities\n"
 
     s = s .. "!rowspan=2 colspan=2|Monster\n"
 
     s = s .. "!rowspan=2 colspan=2|Monster\n"
 
     s = s .. "!rowspan=2|Crit<br/>Chance\n"
 
     s = s .. "!rowspan=2|Crit<br/>Chance\n"
Line 195: Line 195:
 
             for affinity, affinityValue in pairs(value) do
 
             for affinity, affinityValue in pairs(value) do
 
                 if monster.offensiveDamageAffinity[affinity] then
 
                 if monster.offensiveDamageAffinity[affinity] then
                     offensiveAffinties[index] = { affinity = monster.offensiveDamageAffinity[affinity] }
+
                     offensiveAffinities[index] = { affinity = monster.offensiveDamageAffinity[affinity] }
 
                 else
 
                 else
                     offensiveAffinties[index] = { affinity = affinityValue }
+
                     offensiveAffinities[index] = { affinity = affinityValue }
 
                 end
 
                 end
 
             end
 
             end
 
         end
 
         end
         s = s .. "|" .. img._img({ monster["name"], 42 }) .. "\n"
+
         s = s .. "|" .. img._img({ monster["name"], 60 }) .. "\n"
 
         s = s .. "|style=\"text-align:left\"|[[" .. monster["name"] .. "]]\n"
 
         s = s .. "|style=\"text-align:left\"|[[" .. monster["name"] .. "]]\n"
 
         s = s .. "|" .. tostring(monster.offensiveCritical.chance * 100) .. "%\n"
 
         s = s .. "|" .. tostring(monster.offensiveCritical.chance * 100) .. "%\n"
 
         s = s .. "|" .. tostring(monster.offensiveCritical.damageMultiplier * 100) .. "%\n"
 
         s = s .. "|" .. tostring(monster.offensiveCritical.damageMultiplier * 100) .. "%\n"
         for _, value in pairsByKeys(offensiveAffinties) do
+
         for _, value in pairsByKeys(offensiveAffinities) do
 +
            for _, affinityValue in pairs(value) do
 +
                if affinityValue > 1 then
 +
                    s = s .. "|<span style=\"color:#4caf50\">" .. tonumber((affinityValue - 1) * 100) .. "%<span/>\n"
 +
                elseif affinityValue == 1 then
 +
                    s = s .. "|" .. tonumber((affinityValue - 1) * 100) .. "%\n"
 +
                else
 +
                    s = s .. "|<span style=\"color:#f44336\">" .. tonumber((affinityValue - 1) * 100) .. "%<span/>\n"
 +
                end
 +
            end
 +
        end
 +
        s = s .. "|-\n"
 +
    end
 +
    s = s .. "|}"
 +
    return s
 +
end
 +
 
 +
local function genCombinedTable(monsters, collapsed)
 +
    local offensiveAffinities = {}
 +
    local defensiveAffinities = {}
 +
    local s = "{|class=\"wikitable sortable mw-collapsible " .. collapsed .. "\" style=\"text-align:center;width:100%\"\n"
 +
    s = s .. "|+ style=\"white-space:nowrap\" |Affinities\n"
 +
    s = s .. "!rowspan=2 colspan=2|Monster\n"
 +
    s = s .. "!rowspan=2|Crit<br/>Chance\n"
 +
    s = s .. "!rowspan=2|Crit<br/>Multiplier\n"
 +
    s = s .. "!colspan=12 style=\"text-align:center\"|Affinities\n"
 +
    s = s .. "!rowspan=2|Base<br/>Threat\n"
 +
    s = s .. "!rowspan=2|Crit<br/>Avoidance\n"
 +
    s = s .. "!rowspan=2|Crit<br/>Reduction\n"
 +
    s = s .. "!colspan=12 style=\"text-align:center\"|Affinities\n"
 +
    s = s .. "|-\n"
 +
    s = s .. "![[File:Melee splash.png|20px|link=Combat#Affinities]]\n"
 +
    s = s .. "![[File:Magic splash.png|20px|link=Combat#Affinities]]\n"
 +
    s = s .. "![[File:Range splash.png|20px|link=Combat#Affinities]]\n"
 +
    s = s .. "![[File:Stab splash.png|20px|link=Combat#Affinities]]\n"
 +
    s = s .. "![[File:Crush splash.png|20px|link=Combat#Affinities]]\n"
 +
    s = s .. "![[File:Slash splash.png|20px|link=Combat#Affinities]]\n"
 +
    s = s .. "![[File:Fire_splash.png|20px|link=Combat#Affinities]]\n"
 +
    s = s .. "![[File:Ice_splash.png|20px|link=Combat#Affinities]]\n"
 +
    s = s .. "![[File:Nature_splash.png|20px|link=Combat#Affinities]]\n"
 +
    s = s .. "![[File:Chaos_splash.png|20px|link=Combat#Affinities]]\n"
 +
    s = s .. "![[File:Poison_splash.png|20px|link=Combat#Affinities]]\n"
 +
    s = s .. "![[File:Lightning_splash.png|20px|link=Combat#Affinities]]\n"
 +
    -- sep
 +
    s = s .. "![[File:Melee splash.png|20px|link=Combat#Affinities]]\n"
 +
    s = s .. "![[File:Magic splash.png|20px|link=Combat#Affinities]]\n"
 +
    s = s .. "![[File:Range splash.png|20px|link=Combat#Affinities]]\n"
 +
    s = s .. "![[File:Stab splash.png|20px|link=Combat#Affinities]]\n"
 +
    s = s .. "![[File:Crush splash.png|20px|link=Combat#Affinities]]\n"
 +
    s = s .. "![[File:Slash splash.png|20px|link=Combat#Affinities]]\n"
 +
    s = s .. "![[File:Fire_splash.png|20px|link=Combat#Affinities]]\n"
 +
    s = s .. "![[File:Ice_splash.png|20px|link=Combat#Affinities]]\n"
 +
    s = s .. "![[File:Nature_splash.png|20px|link=Combat#Affinities]]\n"
 +
    s = s .. "![[File:Chaos_splash.png|20px|link=Combat#Affinities]]\n"
 +
    s = s .. "![[File:Poison_splash.png|20px|link=Combat#Affinities]]\n"
 +
    s = s .. "![[File:Lightning_splash.png|20px|link=Combat#Affinities]]\n"
 +
    s = s .. "|-\n"
 +
    for _, monster in ipairs(monsters) do
 +
        for index, value in pairsByKeys(defaultAffinities) do
 
             for affinity, affinityValue in pairs(value) do
 
             for affinity, affinityValue in pairs(value) do
 +
                if monster.offensiveDamageAffinity[affinity] then
 +
                    offensiveAffinities[index] = { affinity = monster.offensiveDamageAffinity[affinity] }
 +
                else
 +
                    offensiveAffinities[index] = { affinity = affinityValue }
 +
                end
 +
                if monster.defensiveDamageAffinity[affinity] then
 +
                    defensiveAffinities[index] = { affinity = monster.defensiveDamageAffinity[affinity] }
 +
                else
 +
                    defensiveAffinities[index] = { affinity = affinityValue }
 +
                end
 +
            end
 +
        end
 +
        s = s .. "|" .. img._img({ monster["name"], 60 }) .. "\n"
 +
        s = s .. "|style=\"text-align:left\"|[[" .. monster["name"] .. "]]\n"
 +
        s = s .. "|" .. tostring(monster.offensiveCritical.chance * 100) .. "%\n"
 +
        s = s .. "|" .. tostring(monster.offensiveCritical.damageMultiplier * 100) .. "%\n"
 +
        for _, value in pairsByKeys(offensiveAffinities) do
 +
            for _, affinityValue in pairs(value) do
 +
                if affinityValue > 1 then
 +
                    s = s .. "|<span style=\"color:#4caf50\">" .. tonumber((affinityValue - 1) * 100) .. "%<span/>\n"
 +
                elseif affinityValue == 1 then
 +
                    s = s .. "|" .. tonumber((affinityValue - 1) * 100) .. "%\n"
 +
                else
 +
                    s = s .. "|<span style=\"color:#f44336\">" .. tonumber((affinityValue - 1) * 100) .. "%<span/>\n"
 +
                end
 +
            end
 +
        end
 +
        s = s .. "| " .. calcThreat(monster) .. "\n"
 +
        s = s .. "|" .. tostring(monster.defensiveCritical.chance * 100) .. "%\n"
 +
        s = s .. "|" .. tostring(100 - (monster.defensiveCritical.damageMultiplier * 100)) .. "%\n"
 +
        for _, value in pairsByKeys(defensiveAffinities) do
 +
            for _, affinityValue in pairs(value) do
 
                 if affinityValue > 1 then
 
                 if affinityValue > 1 then
 
                     s = s .. "|<span style=\"color:#4caf50\">" .. tonumber((affinityValue - 1) * 100) .. "%<span/>\n"
 
                     s = s .. "|<span style=\"color:#4caf50\">" .. tonumber((affinityValue - 1) * 100) .. "%<span/>\n"
Line 249: Line 339:
 
     elseif style == "offensive" then
 
     elseif style == "offensive" then
 
         return genOffensiveTable(monsters, collapsed)
 
         return genOffensiveTable(monsters, collapsed)
 +
    elseif style == "combined" then
 +
        return genCombinedTable(monsters, collapsed)
 
     else
 
     else
 
         return "wrong style"
 
         return "wrong style"

Latest revision as of 02:54, 5 December 2024


local p = {}
local findId = require("Module:FindId")
local img = require("Module:Img")
local dataModuleNames = {
    normal = "Module:Monsters stats normal/data",
    dungeon = "Module:Monsters stats dungeon/data",
}

local defaultAffinities = {
    { Melee = 1 },
    { Magic = 1 },
    { Range = 1 },
    { Piercing = 1 },
    { Blunt = 1 },
    { Slashing = 1 },
    { Fire = 1 },
    { Ice = 1 },
    { Nature = 1 },
    { Chaos = 1 },
    { Posion = 1 },
    { Lightning = 1 }
}

local loadedDataModules = {}

--
-- Loads data modules
--
-- @param dataType {string}
-- @return {data table}
--
function p.loadData(dataType)
    local moduleName = dataModuleNames[dataType]
    if loadedDataModules[moduleName] == nil then
        loadedDataModules[moduleName] = mw.loadData(moduleName)
    end
    return loadedDataModules[moduleName]
end

-- Convert from CSV string to table (converts a single line of a CSV file)
local function fromCSV(s)
    s = s .. ',' -- ending comma
    local t = {} -- table to collect fields
    local fieldstart = 1
    repeat
        local nexti = string.find(s, ',', fieldstart)
        table.insert(t, string.sub(s, fieldstart, nexti - 1))
        fieldstart = nexti + 1
    until fieldstart > string.len(s)
    return t
end

local function pairsByKeys(t, f)
    local a = {}
    local orgi_key_type
    local orgi_key_numbered
    for n in pairs(t) do
        if tonumber(n) == nil then
            table.insert(a, n)
            orgi_key_type = "word"
        elseif type(n) == "number" then
            table.insert(a, n)
            orgi_key_type = "int"
        elseif type(n) == "string" and type(tonumber(n) == "number") then
            orgi_key_type = "number"
            table.insert(a, tonumber(n))
        end
    end
    table.sort(a, f)
    local key
    local value
    local i = 0             -- iterator variable
    local iter = function() -- iterator function
        i = i + 1
        if a[i] == nil then
            return nil
        elseif orgi_key_type == "word" or orgi_key_type == "int" then
            key = a[i]
            value = t[a[i]]
        elseif orgi_key_type == "number" then
            key = tostring(a[i])
            value = t[tostring(a[i])]
        end
        return key, value
    end
    return iter
end

local function calcThreat(monster)
    local damageThreat = 2
    local weaponThreat = 3
    local armorThreat = 5
    local attackSpeedThreat = 3
    local attackSpeedThreatLevel = 2.4 / monster.attackSpeed
    local attackSpeedThreatFinal = attackSpeedThreatLevel * attackSpeedThreat
    local potentialDamageThreatFinal =
        (monster.attack +
            monster.strength +
            monster.magic +
            monster.range) *
        damageThreat
    local weaponThreatFinal =
        (monster.weapon.dexterity +
            monster.weapon.intellect +
            monster.weapon.strength) *
        weaponThreat
    local targetArmorRating =
        monster.armor.protection +
        monster.armor.resistance +
        monster.armor.agility * 1.5
    local armorThreatFinal = (targetArmorRating + monster.defense * 10) * armorThreat
    local baseThreat = (potentialDamageThreatFinal + weaponThreatFinal) * attackSpeedThreatFinal + armorThreatFinal
    return math.floor(baseThreat)
end

local function genDefensiveTable(monsters, collapsed)
    local defensiveAffinities = {}
    local s = "{|class=\"wikitable sortable mw-collapsible " .. collapsed .. "\" style=\"text-align:center;width:100%\"\n"
    s = s .. "|+ style=\"white-space:nowrap\" |Defensive Affinities\n"
    s = s .. "!rowspan=2 colspan=2|Monster\n"
    s = s .. "!rowspan=2|Base<br/>Threat\n"
    s = s .. "!rowspan=2|Crit<br/>Avoidance\n"
    s = s .. "!rowspan=2|Crit<br/>Reduction\n"
    s = s .. "!colspan=12 style=\"text-align:center\"|Affinities\n"
    s = s .. "|-\n"
    s = s .. "![[File:Melee splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Magic splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Range splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Stab splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Crush splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Slash splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Fire_splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Ice_splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Nature_splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Chaos_splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Poison_splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Lightning_splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "|-\n"
    for _, monster in ipairs(monsters) do
        for index, value in pairsByKeys(defaultAffinities) do
            for affinity, affinityValue in pairs(value) do
                if monster.defensiveDamageAffinity[affinity] then
                    defensiveAffinities[index] = { affinity = monster.defensiveDamageAffinity[affinity] }
                else
                    defensiveAffinities[index] = { affinity = affinityValue }
                end
            end
        end
        s = s .. "|" .. img._img({ monster["name"], 60 }) .. "\n"
        s = s .. "|style=\"text-align:left\"|[[" .. monster["name"] .. "]]\n"
        s = s .. "| " .. calcThreat(monster) .. "\n"
        s = s .. "|" .. tostring(monster.defensiveCritical.chance * 100) .. "%\n"
        s = s .. "|" .. tostring(100 - (monster.defensiveCritical.damageMultiplier * 100)) .. "%\n"
        for _, value in pairsByKeys(defensiveAffinities) do
            for _, affinityValue in pairs(value) do
                if affinityValue > 1 then
                    s = s .. "|<span style=\"color:#4caf50\">" .. tonumber((affinityValue - 1) * 100) .. "%<span/>\n"
                elseif affinityValue == 1 then
                    s = s .. "|" .. tonumber((affinityValue - 1) * 100) .. "%\n"
                else
                    s = s .. "|<span style=\"color:#f44336\">" .. tonumber((affinityValue - 1) * 100) .. "%<span/>\n"
                end
            end
        end
        s = s .. "|-\n"
    end
    s = s .. "|}"
    return s
end

local function genOffensiveTable(monsters, collapsed)
    local offensiveAffinities = {}
    local s = "{|class=\"wikitable sortable mw-collapsible " .. collapsed .. "\" style=\"text-align:center;width:100%\"\n"
    s = s .. "|+ style=\"white-space:nowrap\" |Offensive Affinities\n"
    s = s .. "!rowspan=2 colspan=2|Monster\n"
    s = s .. "!rowspan=2|Crit<br/>Chance\n"
    s = s .. "!rowspan=2|Crit<br/>Multiplier\n"
    s = s .. "!colspan=12 style=\"text-align:center\"|Affinities\n"
    s = s .. "|-\n"
    s = s .. "![[File:Melee splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Magic splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Range splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Stab splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Crush splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Slash splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Fire_splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Ice_splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Nature_splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Chaos_splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Poison_splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Lightning_splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "|-\n"
    for _, monster in ipairs(monsters) do
        for index, value in pairsByKeys(defaultAffinities) do
            for affinity, affinityValue in pairs(value) do
                if monster.offensiveDamageAffinity[affinity] then
                    offensiveAffinities[index] = { affinity = monster.offensiveDamageAffinity[affinity] }
                else
                    offensiveAffinities[index] = { affinity = affinityValue }
                end
            end
        end
        s = s .. "|" .. img._img({ monster["name"], 60 }) .. "\n"
        s = s .. "|style=\"text-align:left\"|[[" .. monster["name"] .. "]]\n"
        s = s .. "|" .. tostring(monster.offensiveCritical.chance * 100) .. "%\n"
        s = s .. "|" .. tostring(monster.offensiveCritical.damageMultiplier * 100) .. "%\n"
        for _, value in pairsByKeys(offensiveAffinities) do
            for _, affinityValue in pairs(value) do
                if affinityValue > 1 then
                    s = s .. "|<span style=\"color:#4caf50\">" .. tonumber((affinityValue - 1) * 100) .. "%<span/>\n"
                elseif affinityValue == 1 then
                    s = s .. "|" .. tonumber((affinityValue - 1) * 100) .. "%\n"
                else
                    s = s .. "|<span style=\"color:#f44336\">" .. tonumber((affinityValue - 1) * 100) .. "%<span/>\n"
                end
            end
        end
        s = s .. "|-\n"
    end
    s = s .. "|}"
    return s
end

local function genCombinedTable(monsters, collapsed)
    local offensiveAffinities = {}
    local defensiveAffinities = {}
    local s = "{|class=\"wikitable sortable mw-collapsible " .. collapsed .. "\" style=\"text-align:center;width:100%\"\n"
    s = s .. "|+ style=\"white-space:nowrap\" |Affinities\n"
    s = s .. "!rowspan=2 colspan=2|Monster\n"
    s = s .. "!rowspan=2|Crit<br/>Chance\n"
    s = s .. "!rowspan=2|Crit<br/>Multiplier\n"
    s = s .. "!colspan=12 style=\"text-align:center\"|Affinities\n"
    s = s .. "!rowspan=2|Base<br/>Threat\n"
    s = s .. "!rowspan=2|Crit<br/>Avoidance\n"
    s = s .. "!rowspan=2|Crit<br/>Reduction\n"
    s = s .. "!colspan=12 style=\"text-align:center\"|Affinities\n"
    s = s .. "|-\n"
    s = s .. "![[File:Melee splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Magic splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Range splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Stab splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Crush splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Slash splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Fire_splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Ice_splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Nature_splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Chaos_splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Poison_splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Lightning_splash.png|20px|link=Combat#Affinities]]\n"
    -- sep
    s = s .. "![[File:Melee splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Magic splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Range splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Stab splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Crush splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Slash splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Fire_splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Ice_splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Nature_splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Chaos_splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Poison_splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "![[File:Lightning_splash.png|20px|link=Combat#Affinities]]\n"
    s = s .. "|-\n"
    for _, monster in ipairs(monsters) do
        for index, value in pairsByKeys(defaultAffinities) do
            for affinity, affinityValue in pairs(value) do
                if monster.offensiveDamageAffinity[affinity] then
                    offensiveAffinities[index] = { affinity = monster.offensiveDamageAffinity[affinity] }
                else
                    offensiveAffinities[index] = { affinity = affinityValue }
                end
                if monster.defensiveDamageAffinity[affinity] then
                    defensiveAffinities[index] = { affinity = monster.defensiveDamageAffinity[affinity] }
                else
                    defensiveAffinities[index] = { affinity = affinityValue }
                end
            end
        end
        s = s .. "|" .. img._img({ monster["name"], 60 }) .. "\n"
        s = s .. "|style=\"text-align:left\"|[[" .. monster["name"] .. "]]\n"
        s = s .. "|" .. tostring(monster.offensiveCritical.chance * 100) .. "%\n"
        s = s .. "|" .. tostring(monster.offensiveCritical.damageMultiplier * 100) .. "%\n"
        for _, value in pairsByKeys(offensiveAffinities) do
            for _, affinityValue in pairs(value) do
                if affinityValue > 1 then
                    s = s .. "|<span style=\"color:#4caf50\">" .. tonumber((affinityValue - 1) * 100) .. "%<span/>\n"
                elseif affinityValue == 1 then
                    s = s .. "|" .. tonumber((affinityValue - 1) * 100) .. "%\n"
                else
                    s = s .. "|<span style=\"color:#f44336\">" .. tonumber((affinityValue - 1) * 100) .. "%<span/>\n"
                end
            end
        end
        s = s .. "| " .. calcThreat(monster) .. "\n"
        s = s .. "|" .. tostring(monster.defensiveCritical.chance * 100) .. "%\n"
        s = s .. "|" .. tostring(100 - (monster.defensiveCritical.damageMultiplier * 100)) .. "%\n"
        for _, value in pairsByKeys(defensiveAffinities) do
            for _, affinityValue in pairs(value) do
                if affinityValue > 1 then
                    s = s .. "|<span style=\"color:#4caf50\">" .. tonumber((affinityValue - 1) * 100) .. "%<span/>\n"
                elseif affinityValue == 1 then
                    s = s .. "|" .. tonumber((affinityValue - 1) * 100) .. "%\n"
                else
                    s = s .. "|<span style=\"color:#f44336\">" .. tonumber((affinityValue - 1) * 100) .. "%<span/>\n"
                end
            end
        end
        s = s .. "|-\n"
    end
    s = s .. "|}"
    return s
end

function p.monsters_stats_table(frame)
    return p._monsters_stats_table(frame:getParent().args)
end

function p._monsters_stats_table(_args)
    local monstersName = fromCSV(_args[1])
    local monsters = {}
    local style = _args[2]
    local collapsed
    local s = ""
    if _args["collapsed"] then
        collapsed = "mw-collapsed"
    else
        collapsed = ""
    end

    for _, value in ipairs(monstersName) do
        table.insert(monsters, p.loadData("normal")[tostring(findId._findId({ value, "monster" }))])
        table.insert(monsters, p.loadData("dungeon")[tostring(findId._findId({ value, "monster" }))])
    end
    if monsters == nil then
        return "no monster match"
    end
    if style == "defensive" then
        return genDefensiveTable(monsters, collapsed)
    elseif style == "offensive" then
        return genOffensiveTable(monsters, collapsed)
    elseif style == "combined" then
        return genCombinedTable(monsters, collapsed)
    else
        return "wrong style"
    end
end

return p