Difference between revisions of "Module:Infobox Monster"
Jump to navigation
Jump to search
m (bug) |
m (Make zones and dps optional) |
||
Line 1: | Line 1: | ||
− | local p ={} | + | local p = {} |
local findId = require("Module:FindId") | local findId = require("Module:FindId") | ||
local infoboxModule = require('Module:Infobox') | local infoboxModule = require('Module:Infobox') | ||
Line 8: | Line 8: | ||
local labelCount = 1 | local labelCount = 1 | ||
local dataCount = 1 | local dataCount = 1 | ||
− | local defaultAffinities = { | + | 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 affinitiesIcon = { | local affinitiesIcon = { | ||
− | + | [1] = "[[File:Melee splash.png|20px|link=Combat#Affinities]]", | |
− | + | [2] = "[[File:Magic splash.png|20px|link=Combat#Affinities]]", | |
− | + | [3] = "[[File:Range splash.png|20px|link=Combat#Affinities]]", | |
− | + | [4] = "[[File:Stab splash.png|20px|link=Combat#Affinities]]", | |
− | + | [5] = "[[File:Crush splash.png|20px|link=Combat#Affinities]]", | |
− | + | [6] = "[[File:Slash splash.png|20px|link=Combat#Affinities]]", | |
− | + | [7] = "[[File:Fire_splash.png|20px|link=Combat#Affinities]]", | |
− | + | [8] = "[[File:Ice_splash.png|20px|link=Combat#Affinities]]", | |
− | + | [9] = "[[File:Nature_splash.png|20px|link=Combat#Affinities]]", | |
− | + | [10] = "[[File:Chaos_splash.png|20px|link=Combat#Affinities]]", | |
− | + | [11] = "[[File:Poison_splash.png|20px|link=Combat#Affinities]]", | |
− | + | [12] = "[[File:Lightning_splash.png|20px|link=Combat#Affinities]]" | |
} | } | ||
-- 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) | ||
local function fromCSV(s) | local function fromCSV(s) | ||
− | + | if string.sub(s, -1) ~= ',' then | |
− | + | s = s .. ',' -- ending comma | |
− | + | end | |
− | + | s = string.gsub(s, "[%[%]]", ""):gsub(",%s", ",") | |
− | + | 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 | end | ||
local function pairsByKeys(t, f) | 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 | ||
− | + | 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 | end | ||
− | return iter | + | return key, value |
+ | end | ||
+ | return iter | ||
end | end | ||
local function tchelper(first, rest) | local function tchelper(first, rest) | ||
− | + | return first:upper() .. rest:lower() | |
end | end | ||
local function capitalize(s) | local function capitalize(s) | ||
− | + | s = s:gsub("(%a)([%w_']*)", tchelper):gsub(" Of ", " of "):gsub(" The ", " the "):gsub("Ii", "II") | |
− | + | return s | |
end | end | ||
local function tablelength(T) | local function tablelength(T) | ||
− | + | local count = 0 | |
− | + | for _ in pairs(T) do | |
− | + | count = count + 1 | |
+ | end | ||
+ | return count | ||
end | end | ||
local function h() | local function h() | ||
− | + | local s = "header" .. headerCount | |
− | + | headerCount = headerCount + 1 | |
− | + | labelCount = headerCount | |
− | + | dataCount = headerCount | |
− | + | return s | |
end | end | ||
local function sbreak() | local function sbreak() | ||
− | + | local s = "sbreak" .. headerCount | |
− | + | headerCount = headerCount + 1 | |
− | + | labelCount = headerCount | |
− | + | dataCount = headerCount | |
− | + | return s | |
end | end | ||
local function l() | local function l() | ||
− | + | local s = "label" .. labelCount | |
− | + | dataCount = labelCount | |
− | + | labelCount = labelCount + 1 | |
− | + | headerCount = labelCount | |
− | + | return s | |
end | end | ||
local function d() | local function d() | ||
− | + | local s = "data" .. dataCount | |
− | + | dataCount = dataCount + 1 | |
− | + | headerCount = dataCount | |
− | + | labelCount = dataCount | |
− | + | return s | |
end | end | ||
local function sl() | local function sl() | ||
− | + | local s = "s" .. l {} | |
− | + | return s | |
end | end | ||
local function sd() | local function sd() | ||
− | + | local s = "s" .. d {} | |
− | + | return s | |
end | end | ||
local function rc() | local function rc() | ||
− | + | local s = "rowclass" .. labelCount | |
− | + | return s | |
end | end | ||
Line 155: | Line 168: | ||
---@return table|nil | ---@return table|nil | ||
local function getMonster(id) | local function getMonster(id) | ||
− | + | local mosnter = monstersData[tostring(id)] | |
− | + | if mosnter then | |
− | + | return mosnter | |
− | + | end | |
− | + | return nil | |
end | end | ||
local function getMonsterStats(id) | local function getMonsterStats(id) | ||
− | + | local mosnterStats = normalMonsters[tostring(id)] | |
− | + | if not mosnterStats then | |
− | + | mosnterStats = dungeonMonsters[tostring(id)] | |
− | + | if not mosnterStats then | |
− | + | return nil | |
− | + | end | |
− | + | end | |
− | + | return mosnterStats | |
end | end | ||
local function calcThreat(monster) | 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 | end | ||
local function fullUrl(url) | local function fullUrl(url) | ||
− | + | local newUrl = url | |
− | + | if url:sub(1, 5) == "https" then | |
− | + | return newUrl | |
− | + | end | |
− | + | if url:sub(1, 1) ~= "/" then | |
− | + | newUrl = "/" .. newUrl | |
− | + | end | |
− | + | newUrl = "https://www.play.idlescape.com" .. newUrl | |
− | + | return newUrl | |
end | end | ||
local function createImgTag(monster) | local function createImgTag(monster) | ||
− | + | local url = fullUrl(monster.image) | |
− | + | local attrs = { | |
− | + | src = fullUrl(monster.image), | |
− | + | alt = monster.name, | |
− | + | width = "150px" | |
− | + | } | |
− | + | local img = mw.html.create('img'):attr(attrs) | |
− | + | return tostring(img) | |
end | end | ||
local function createOffAffinity(args, monsterStats) | local function createOffAffinity(args, monsterStats) | ||
− | + | for index, value in pairsByKeys(defaultAffinities) do | |
− | + | for affinity, affinityValue in pairs(value) do | |
− | + | if monsterStats.offensiveDamageAffinity[affinity] then | |
− | + | affinityValue = monsterStats.offensiveDamageAffinity[affinity] | |
− | + | args[sl()] = affinitiesIcon[index] | |
− | + | if affinityValue > 1 then | |
− | + | args[sd()] = "<span style=\"color:#4caf50\">" .. (affinityValue - 1) * 100 .. "%<span/>" | |
− | + | elseif affinityValue < 1 then | |
− | + | args[sd()] = "<span style=\"color:#f44336\">" .. (affinityValue - 1) * 100 .. "%<span/>" | |
− | + | else | |
− | + | args[sd()] = (affinityValue - 1) * 100 .. "%" | |
− | + | end | |
− | + | else | |
− | + | args[sl()] = affinitiesIcon[index] | |
− | + | args[sd()] = (affinityValue - 1) * 100 .. "%" | |
− | + | end | |
− | + | if index % 3 == 0 then | |
− | + | args["bodyclass"] = "equal-space" | |
− | + | args[sbreak()] = "yes" | |
− | + | end | |
− | + | end | |
− | + | end | |
− | + | return args | |
end | end | ||
local function createDeffAffinity(args, monsterStats) | local function createDeffAffinity(args, monsterStats) | ||
− | + | for index, value in pairsByKeys(defaultAffinities) do | |
− | + | for affinity, affinityValue in pairs(value) do | |
− | + | if monsterStats.defensiveDamageAffinity[affinity] then | |
− | + | affinityValue = monsterStats.defensiveDamageAffinity[affinity] | |
− | + | args[sl()] = affinitiesIcon[index] | |
− | + | if affinityValue > 1 then | |
− | + | args[sd()] = "<span style=\"color:#4caf50\">" .. (affinityValue - 1) * 100 .. "%<span/>" | |
− | + | elseif affinityValue < 1 then | |
− | + | args[sd()] = "<span style=\"color:#f44336\">" .. (affinityValue - 1) * 100 .. "%<span/>" | |
− | + | else | |
− | + | args[sd()] = (affinityValue - 1) * 100 .. "%" | |
− | + | end | |
− | + | else | |
− | + | args[sl()] = affinitiesIcon[index] | |
− | + | args[sd()] = (affinityValue - 1) * 100 .. "%" | |
− | + | end | |
− | + | if index % 3 == 0 then | |
− | + | args["bodyclass"] = "equal-space" | |
− | + | args[sbreak()] = "yes" | |
− | + | end | |
− | + | end | |
− | + | end | |
− | + | return args | |
end | end | ||
local function getZonesLink(zones) | local function getZonesLink(zones) | ||
− | + | local s = "" | |
− | + | if zones then | |
− | + | for index, value in ipairs(zones) do | |
− | + | s = s .. "[[" .. capitalize(value) .. "]]" | |
− | + | if tablelength(zones) > index then | |
− | + | s = s .. ", " | |
− | + | end | |
− | + | end | |
− | + | end | |
− | + | return s | |
end | end | ||
local function createInfobox(monster, monsterStats, zones, dps) | local function createInfobox(monster, monsterStats, zones, dps) | ||
− | + | local args = {} | |
− | + | args.autoheaders = "y" | |
− | + | args.subbox = "no" | |
− | + | args.bodystyle = " " | |
− | + | args.title = monster.name | |
− | + | args.image = createImgTag(monster) | |
− | + | args[l()] = "Zones" | |
− | + | args[d()] = getZonesLink(zones) | |
− | + | args[h()] = "Offensive Stats" | |
− | + | args[sl()] = "Attack Speed" | |
− | + | args[sd()] = tostring(monsterStats.attackSpeed) | |
− | + | args[sl()] = "DPS" | |
− | + | args[sd()] = tostring(dps) | |
− | + | args[sl()] = "Crit Chance" | |
− | + | args[sd()] = tostring(monsterStats.offensiveCritical.chance * 100) .. "%" | |
− | + | args[sl()] = "Crit Multiplier" | |
− | + | args[sd()] = tostring(monsterStats.offensiveCritical.damageMultiplier * 100) .. "%" | |
− | + | args[h()] = "Offensive Affinities" | |
− | + | createOffAffinity(args, monsterStats) | |
− | + | args[h()] = "Defensive Stats" | |
− | + | args[sl()] = "Threat" | |
− | + | args[sd()] = calcThreat(monsterStats) | |
− | + | args[sl()] = "Crit Avoidance" | |
− | + | args[sd()] = tostring(monsterStats.defensiveCritical.chance * 100) .. "%" | |
− | + | args[sl()] = "Crit Reduction" | |
− | + | args[sd()] = tostring(monsterStats.defensiveCritical.damageMultiplier * 100) .. "%" | |
− | + | args[h()] = "Defensive Affinities" | |
− | + | createDeffAffinity(args, monsterStats) | |
− | + | for key, data in pairs(args) do | |
− | + | if string.find(key, "data") then | |
− | + | args[key] = tostring(data) | |
− | + | end | |
− | + | end | |
− | + | return infoboxModule.infobox(args) | |
end | end | ||
− | function | + | function p.infoboxMonster(frame) |
− | + | local args = frame:getParent().args | |
− | + | return p._infoboxMonster(args) | |
end | end | ||
function p._infoboxMonster(_args) | function p._infoboxMonster(_args) | ||
− | + | local dps | |
− | + | local zones | |
− | local name | + | if _args["zones"] then |
− | + | zones = fromCSV(_args["zones"]) | |
− | + | end | |
− | + | if _args["DPS"] then | |
− | + | dps = _args["DPS"] | |
− | + | else | |
− | + | dps = "-" | |
− | + | end | |
− | + | local name | |
− | + | local id | |
− | + | local monster | |
− | + | local monsterStats | |
− | + | local infobox | |
− | + | if _args[1] then | |
− | + | name = _args[1] | |
− | + | else | |
− | + | name = mw.title.getCurrentTitle().text | |
− | + | end | |
− | + | id = findId._findId({name, "monster"}) | |
− | + | if not id then | |
− | + | return "<div style=\"color:red\"> No monster named '" .. name .. | |
− | + | "'</div>. The Module:Monsters Ids/data maybe outdated." | |
− | + | end | |
− | + | monster = getMonster(id) | |
+ | if not monster then | ||
+ | return "<div style=\"color:red\"> No monster named '" .. name .. "'</div>. The Module:Monsters/data maybe outdated." | ||
+ | end | ||
+ | monsterStats = getMonsterStats(id) | ||
+ | if not monsterStats then | ||
+ | return "<div style=\"color:red\"> No monster named '" .. name .. | ||
+ | "'</div>. Module:Monsters stats normal/data and Module:Monsters stats dungeon/data maybe outdated." | ||
+ | end | ||
+ | infobox = createInfobox(monster, monsterStats, zones, dps) | ||
+ | return infobox | ||
end | end | ||
return p | return p |
Latest revision as of 02:26, 14 December 2024
local p = {} local findId = require("Module:FindId") local infoboxModule = require('Module:Infobox') local monstersData = mw.loadData("Module:Monsters/data") local normalMonsters = mw.loadData("Module:Monsters stats normal/data") local dungeonMonsters = mw.loadData("Module:Monsters stats dungeon/data") local headerCount = 1 local labelCount = 1 local dataCount = 1 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 affinitiesIcon = { [1] = "[[File:Melee splash.png|20px|link=Combat#Affinities]]", [2] = "[[File:Magic splash.png|20px|link=Combat#Affinities]]", [3] = "[[File:Range splash.png|20px|link=Combat#Affinities]]", [4] = "[[File:Stab splash.png|20px|link=Combat#Affinities]]", [5] = "[[File:Crush splash.png|20px|link=Combat#Affinities]]", [6] = "[[File:Slash splash.png|20px|link=Combat#Affinities]]", [7] = "[[File:Fire_splash.png|20px|link=Combat#Affinities]]", [8] = "[[File:Ice_splash.png|20px|link=Combat#Affinities]]", [9] = "[[File:Nature_splash.png|20px|link=Combat#Affinities]]", [10] = "[[File:Chaos_splash.png|20px|link=Combat#Affinities]]", [11] = "[[File:Poison_splash.png|20px|link=Combat#Affinities]]", [12] = "[[File:Lightning_splash.png|20px|link=Combat#Affinities]]" } -- Convert from CSV string to table (converts a single line of a CSV file) local function fromCSV(s) if string.sub(s, -1) ~= ',' then s = s .. ',' -- ending comma end s = string.gsub(s, "[%[%]]", ""):gsub(",%s", ",") 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 tchelper(first, rest) return first:upper() .. rest:lower() end local function capitalize(s) s = s:gsub("(%a)([%w_']*)", tchelper):gsub(" Of ", " of "):gsub(" The ", " the "):gsub("Ii", "II") return s end local function tablelength(T) local count = 0 for _ in pairs(T) do count = count + 1 end return count end local function h() local s = "header" .. headerCount headerCount = headerCount + 1 labelCount = headerCount dataCount = headerCount return s end local function sbreak() local s = "sbreak" .. headerCount headerCount = headerCount + 1 labelCount = headerCount dataCount = headerCount return s end local function l() local s = "label" .. labelCount dataCount = labelCount labelCount = labelCount + 1 headerCount = labelCount return s end local function d() local s = "data" .. dataCount dataCount = dataCount + 1 headerCount = dataCount labelCount = dataCount return s end local function sl() local s = "s" .. l {} return s end local function sd() local s = "s" .. d {} return s end local function rc() local s = "rowclass" .. labelCount return s end ---fetches an monster object from monsters/data module ---@param id number ---@return table|nil local function getMonster(id) local mosnter = monstersData[tostring(id)] if mosnter then return mosnter end return nil end local function getMonsterStats(id) local mosnterStats = normalMonsters[tostring(id)] if not mosnterStats then mosnterStats = dungeonMonsters[tostring(id)] if not mosnterStats then return nil end end return mosnterStats 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 fullUrl(url) local newUrl = url if url:sub(1, 5) == "https" then return newUrl end if url:sub(1, 1) ~= "/" then newUrl = "/" .. newUrl end newUrl = "https://www.play.idlescape.com" .. newUrl return newUrl end local function createImgTag(monster) local url = fullUrl(monster.image) local attrs = { src = fullUrl(monster.image), alt = monster.name, width = "150px" } local img = mw.html.create('img'):attr(attrs) return tostring(img) end local function createOffAffinity(args, monsterStats) for index, value in pairsByKeys(defaultAffinities) do for affinity, affinityValue in pairs(value) do if monsterStats.offensiveDamageAffinity[affinity] then affinityValue = monsterStats.offensiveDamageAffinity[affinity] args[sl()] = affinitiesIcon[index] if affinityValue > 1 then args[sd()] = "<span style=\"color:#4caf50\">" .. (affinityValue - 1) * 100 .. "%<span/>" elseif affinityValue < 1 then args[sd()] = "<span style=\"color:#f44336\">" .. (affinityValue - 1) * 100 .. "%<span/>" else args[sd()] = (affinityValue - 1) * 100 .. "%" end else args[sl()] = affinitiesIcon[index] args[sd()] = (affinityValue - 1) * 100 .. "%" end if index % 3 == 0 then args["bodyclass"] = "equal-space" args[sbreak()] = "yes" end end end return args end local function createDeffAffinity(args, monsterStats) for index, value in pairsByKeys(defaultAffinities) do for affinity, affinityValue in pairs(value) do if monsterStats.defensiveDamageAffinity[affinity] then affinityValue = monsterStats.defensiveDamageAffinity[affinity] args[sl()] = affinitiesIcon[index] if affinityValue > 1 then args[sd()] = "<span style=\"color:#4caf50\">" .. (affinityValue - 1) * 100 .. "%<span/>" elseif affinityValue < 1 then args[sd()] = "<span style=\"color:#f44336\">" .. (affinityValue - 1) * 100 .. "%<span/>" else args[sd()] = (affinityValue - 1) * 100 .. "%" end else args[sl()] = affinitiesIcon[index] args[sd()] = (affinityValue - 1) * 100 .. "%" end if index % 3 == 0 then args["bodyclass"] = "equal-space" args[sbreak()] = "yes" end end end return args end local function getZonesLink(zones) local s = "" if zones then for index, value in ipairs(zones) do s = s .. "[[" .. capitalize(value) .. "]]" if tablelength(zones) > index then s = s .. ", " end end end return s end local function createInfobox(monster, monsterStats, zones, dps) local args = {} args.autoheaders = "y" args.subbox = "no" args.bodystyle = " " args.title = monster.name args.image = createImgTag(monster) args[l()] = "Zones" args[d()] = getZonesLink(zones) args[h()] = "Offensive Stats" args[sl()] = "Attack Speed" args[sd()] = tostring(monsterStats.attackSpeed) args[sl()] = "DPS" args[sd()] = tostring(dps) args[sl()] = "Crit Chance" args[sd()] = tostring(monsterStats.offensiveCritical.chance * 100) .. "%" args[sl()] = "Crit Multiplier" args[sd()] = tostring(monsterStats.offensiveCritical.damageMultiplier * 100) .. "%" args[h()] = "Offensive Affinities" createOffAffinity(args, monsterStats) args[h()] = "Defensive Stats" args[sl()] = "Threat" args[sd()] = calcThreat(monsterStats) args[sl()] = "Crit Avoidance" args[sd()] = tostring(monsterStats.defensiveCritical.chance * 100) .. "%" args[sl()] = "Crit Reduction" args[sd()] = tostring(monsterStats.defensiveCritical.damageMultiplier * 100) .. "%" args[h()] = "Defensive Affinities" createDeffAffinity(args, monsterStats) for key, data in pairs(args) do if string.find(key, "data") then args[key] = tostring(data) end end return infoboxModule.infobox(args) end function p.infoboxMonster(frame) local args = frame:getParent().args return p._infoboxMonster(args) end function p._infoboxMonster(_args) local dps local zones if _args["zones"] then zones = fromCSV(_args["zones"]) end if _args["DPS"] then dps = _args["DPS"] else dps = "-" end local name local id local monster local monsterStats local infobox if _args[1] then name = _args[1] else name = mw.title.getCurrentTitle().text end id = findId._findId({name, "monster"}) if not id then return "<div style=\"color:red\"> No monster named '" .. name .. "'</div>. The Module:Monsters Ids/data maybe outdated." end monster = getMonster(id) if not monster then return "<div style=\"color:red\"> No monster named '" .. name .. "'</div>. The Module:Monsters/data maybe outdated." end monsterStats = getMonsterStats(id) if not monsterStats then return "<div style=\"color:red\"> No monster named '" .. name .. "'</div>. Module:Monsters stats normal/data and Module:Monsters stats dungeon/data maybe outdated." end infobox = createInfobox(monster, monsterStats, zones, dps) return infobox end return p