Difference between revisions of "Module:Monsters stats table"

From Idlescape Wiki
Jump to navigation Jump to search
m (chanced text-align to make it prettier)
(Fix crit reduction value and spelling of affinities)
 
(11 intermediate revisions by the same user not shown)
Line 2: Line 2:
 
local findId = require("Module:FindId")
 
local findId = require("Module:FindId")
 
local img = require("Module:Img")
 
local img = require("Module:Img")
 +
local dataModuleNames = {
 +
    normal = "Module:Monsters stats normal/data",
 +
    dungeon = "Module:Monsters stats dungeon/data",
 +
}
  
local monsters_stats = mw.loadData("Module:Monsters stats/data")
 
 
local defaultAffinities = {
 
local defaultAffinities = {
     {Melee = 1},
+
     { Melee = 1 },
     {Magic = 1},
+
     { Magic = 1 },
     {Range = 1},
+
     { Range = 1 },
     {Piercing = 1},
+
     { Piercing = 1 },
     {Blunt = 1},
+
     { Blunt = 1 },
     {Slashing = 1},
+
     { Slashing = 1 },
     {Fire = 1},
+
     { Fire = 1 },
     {Ice = 1},
+
     { Ice = 1 },
     {Nature = 1},
+
     { Nature = 1 },
     {Chaos = 1},
+
     { Chaos = 1 },
     {Posion = 1},
+
     { Posion = 1 },
     {Lightning = 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)
 
-- Convert from CSV string to table (converts a single line of a CSV file)
Line 92: 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)
+
local function genDefensiveTable(monsters, collapsed)
     local defensiveAffinties = {}
+
     local defensiveAffinities = {}
     local s = "{|class=\"wikitable sortable hover-highlight\" style=\"text-align:center\"\n"
+
     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 colspan=2|Monster\n"
 
     s = s .. "!rowspan=2|Base<br/>Threat\n"
 
     s = s .. "!rowspan=2|Base<br/>Threat\n"
Line 121: 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:center\"|[[" .. 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 _, 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
 
             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
 
                 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"
 
                 elseif affinityValue == 1 then
 
                 elseif affinityValue == 1 then
                     s =s .. "|" .. tonumber((affinityValue - 1) * 100) .. "%\n"
+
                     s = s .. "|" .. tonumber((affinityValue - 1) * 100) .. "%\n"
 
                 else
 
                 else
 
                     s = s .. "|<span style=\"color:#f44336\">" .. tonumber((affinityValue - 1) * 100) .. "%<span/>\n"
 
                     s = s .. "|<span style=\"color:#f44336\">" .. tonumber((affinityValue - 1) * 100) .. "%<span/>\n"
Line 149: Line 222:
 
end
 
end
  
local function genOffensiveTable(monsters)
+
local function genCombinedTable(monsters, collapsed)
     local offensiveAffinties = {}
+
     local offensiveAffinities = {}
     local s = "{|class=\"wikitable sortable hover-highlight\"\n"
+
    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 colspan=2|Monster\n"
 
     s = s .. "!rowspan=2|Crit<br/>Chance\n"
 
     s = s .. "!rowspan=2|Crit<br/>Chance\n"
 
     s = s .. "!rowspan=2|Crit<br/>Multiplier\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 .. "!colspan=12 style=\"text-align:center\"|Affinities\n"
 
     s = s .. "|-\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:Melee splash.png|20px|link=Combat#Affinities]]\n"
 
     s = s .. "![[File:Magic splash.png|20px|link=Combat#Affinities]]\n"
 
     s = s .. "![[File:Magic splash.png|20px|link=Combat#Affinities]]\n"
Line 174: Line 266:
 
             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
 +
                    offensiveAffinities[index] = { affinity = affinityValue }
 +
                end
 +
                if monster.defensiveDamageAffinity[affinity] then
 +
                    defensiveAffinities[index] = { affinity = monster.defensiveDamageAffinity[affinity] }
 
                 else
 
                 else
                     offensiveAffinties[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:center\"|[[" .. 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 affinity, affinityValue in pairs(value) 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"
 
                 elseif affinityValue == 1 then
 
                 elseif affinityValue == 1 then
                     s =s .. "|" .. tonumber((affinityValue - 1) * 100) .. "%\n"
+
                     s = s .. "|" .. tonumber((affinityValue - 1) * 100) .. "%\n"
 
                 else
 
                 else
 
                     s = s .. "|<span style=\"color:#f44336\">" .. tonumber((affinityValue - 1) * 100) .. "%<span/>\n"
 
                     s = s .. "|<span style=\"color:#f44336\">" .. tonumber((affinityValue - 1) * 100) .. "%<span/>\n"
Line 209: Line 320:
 
     local monsters = {}
 
     local monsters = {}
 
     local style = _args[2]
 
     local style = _args[2]
 +
    local collapsed
 
     local s = ""
 
     local s = ""
 +
    if _args["collapsed"] then
 +
        collapsed = "mw-collapsed"
 +
    else
 +
        collapsed = ""
 +
    end
  
 
     for _, value in ipairs(monstersName) do
 
     for _, value in ipairs(monstersName) do
         table.insert(monsters, monsters_stats[tostring(findId._findId({ value, "monster" }))])
+
         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
 
     end
 
     if style == "defensive" then
 
     if style == "defensive" then
         return genDefensiveTable(monsters)
+
         return genDefensiveTable(monsters, collapsed)
 
     elseif style == "offensive" then
 
     elseif style == "offensive" then
         return genOffensiveTable(monsters)
+
         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