Difference between revisions of "Module:Infobox Monster"

From Idlescape Wiki
Jump to navigation Jump to search
m (bug)
m (Make zones and dps optional)
 
(2 intermediate revisions by the same user not shown)
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 },
+
  Melee = 1
    { Magic = 1 },
+
}, {
    { Range = 1 },
+
  Magic = 1
    { Piercing = 1 },
+
}, {
    { Blunt = 1 },
+
  Range = 1
    { Slashing = 1 },
+
}, {
    { Fire = 1 },
+
  Piercing = 1
    { Ice = 1 },
+
}, {
    { Nature = 1 },
+
  Blunt = 1
    { Chaos = 1 },
+
}, {
    { Posion = 1 },
+
  Slashing = 1
    { Lightning = 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]]",
+
  [1] = "[[File:Melee splash.png|20px|link=Combat#Affinities]]",
[2] = "[[File:Magic splash.png|20px|link=Combat#Affinities]]",
+
  [2] = "[[File:Magic splash.png|20px|link=Combat#Affinities]]",
[3] = "[[File:Range splash.png|20px|link=Combat#Affinities]]",
+
  [3] = "[[File:Range splash.png|20px|link=Combat#Affinities]]",
[4] = "[[File:Stab splash.png|20px|link=Combat#Affinities]]",
+
  [4] = "[[File:Stab splash.png|20px|link=Combat#Affinities]]",
[5] = "[[File:Crush splash.png|20px|link=Combat#Affinities]]",
+
  [5] = "[[File:Crush splash.png|20px|link=Combat#Affinities]]",
[6] = "[[File:Slash splash.png|20px|link=Combat#Affinities]]",
+
  [6] = "[[File:Slash splash.png|20px|link=Combat#Affinities]]",
[7] = "[[File:Fire_splash.png|20px|link=Combat#Affinities]]",
+
  [7] = "[[File:Fire_splash.png|20px|link=Combat#Affinities]]",
[8] = "[[File:Ice_splash.png|20px|link=Combat#Affinities]]",
+
  [8] = "[[File:Ice_splash.png|20px|link=Combat#Affinities]]",
[9] = "[[File:Nature_splash.png|20px|link=Combat#Affinities]]",
+
  [9] = "[[File:Nature_splash.png|20px|link=Combat#Affinities]]",
[10] = "[[File:Chaos_splash.png|20px|link=Combat#Affinities]]",
+
  [10] = "[[File:Chaos_splash.png|20px|link=Combat#Affinities]]",
[11] = "[[File:Poison_splash.png|20px|link=Combat#Affinities]]",
+
  [11] = "[[File:Poison_splash.png|20px|link=Combat#Affinities]]",
[12] = "[[File:Lightning_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
 
     s = s .. ',' -- ending comma
    local t = {} -- table to collect fields
+
  end
    local fieldstart = 1
+
  s = string.gsub(s, "[%[%]]", ""):gsub(",%s", ",")
    repeat
+
  local t = {} -- table to collect fields
        local nexti = string.find(s, ',', fieldstart)
+
  local fieldstart = 1
        table.insert(t, string.sub(s, fieldstart, nexti - 1))
+
  repeat
        fieldstart = nexti + 1
+
    local nexti = string.find(s, ',', fieldstart)
    until fieldstart > string.len(s)
+
    table.insert(t, string.sub(s, fieldstart, nexti - 1))
    return t
+
    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 a = {}
    local orgi_key_type
+
  local orgi_key_type
    local orgi_key_numbered
+
  local orgi_key_numbered
    for n in pairs(t) do
+
  for n in pairs(t) do
        if tonumber(n) == nil then
+
    if tonumber(n) == nil then
            table.insert(a, n)
+
      table.insert(a, n)
            orgi_key_type = "word"
+
      orgi_key_type = "word"
        elseif type(n) == "number" then
+
    elseif type(n) == "number" then
            table.insert(a, n)
+
      table.insert(a, n)
            orgi_key_type = "int"
+
      orgi_key_type = "int"
        elseif type(n) == "string" and type(tonumber(n) == "number") then
+
    elseif type(n) == "string" and type(tonumber(n) == "number") then
            orgi_key_type = "number"
+
      orgi_key_type = "number"
            table.insert(a, tonumber(n))
+
      table.insert(a, tonumber(n))
        end
 
 
     end
 
     end
    table.sort(a, f)
+
  end
    local key
+
  table.sort(a, f)
    local value
+
  local key
    local i = 0             -- iterator variable
+
  local value
    local iter = function() -- iterator function
+
  local i = 0 -- iterator variable
        i = i + 1
+
  local iter = function() -- iterator function
        if a[i] == nil then
+
    i = i + 1
            return nil
+
    if a[i] == nil then
        elseif orgi_key_type == "word" or orgi_key_type == "int" then
+
      return nil
            key = a[i]
+
    elseif orgi_key_type == "word" or orgi_key_type == "int" then
            value = t[a[i]]
+
      key = a[i]
        elseif orgi_key_type == "number" then
+
      value = t[a[i]]
            key = tostring(a[i])
+
    elseif orgi_key_type == "number" then
            value = t[tostring(a[i])]
+
      key = tostring(a[i])
        end
+
      value = t[tostring(a[i])]
        return key, value
 
 
     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()
+
  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")
+
  s = s:gsub("(%a)([%w_']*)", tchelper):gsub(" Of ", " of "):gsub(" The ", " the "):gsub("Ii", "II")
return s
+
  return s
 
end
 
end
  
 
local function tablelength(T)
 
local function tablelength(T)
    local count = 0
+
  local count = 0
    for _ in pairs(T) do count = count + 1 end
+
  for _ in pairs(T) do
    return count
+
    count = count + 1
 +
  end
 +
  return count
 
end
 
end
  
 
local function h()
 
local function h()
local s = "header" .. headerCount
+
  local s = "header" .. headerCount
headerCount = headerCount + 1
+
  headerCount = headerCount + 1
labelCount = headerCount
+
  labelCount = headerCount
dataCount = headerCount
+
  dataCount = headerCount
return s
+
  return s
 
end
 
end
  
 
local function sbreak()
 
local function sbreak()
local s = "sbreak" .. headerCount
+
  local s = "sbreak" .. headerCount
headerCount = headerCount + 1
+
  headerCount = headerCount + 1
labelCount = headerCount
+
  labelCount = headerCount
dataCount = headerCount
+
  dataCount = headerCount
return s
+
  return s
 
end
 
end
  
 
local function l()
 
local function l()
local s = "label" .. labelCount
+
  local s = "label" .. labelCount
dataCount = labelCount
+
  dataCount = labelCount
labelCount = labelCount + 1
+
  labelCount = labelCount + 1
headerCount = labelCount
+
  headerCount = labelCount
return s
+
  return s
 
end
 
end
  
 
local function d()
 
local function d()
local s = "data" .. dataCount
+
  local s = "data" .. dataCount
dataCount = dataCount + 1
+
  dataCount = dataCount + 1
headerCount = dataCount
+
  headerCount = dataCount
labelCount = dataCount
+
  labelCount = dataCount
return s
+
  return s
 
end
 
end
  
 
local function sl()
 
local function sl()
local s = "s" .. l{}
+
  local s = "s" .. l {}
return s
+
  return s
 
end
 
end
  
 
local function sd()
 
local function sd()
local s = "s" .. d{}
+
  local s = "s" .. d {}
return s
+
  return s
 
end
 
end
  
 
local function rc()
 
local function rc()
local s = "rowclass" .. labelCount
+
  local s = "rowclass" .. labelCount
return s
+
  return s
 
end
 
end
  
Line 152: Line 168:
 
---@return table|nil
 
---@return table|nil
 
local function getMonster(id)
 
local function getMonster(id)
    local mosnter = monstersData[tostring(id)]
+
  local mosnter = monstersData[tostring(id)]
    if mosnter then
+
  if mosnter then
        return mosnter
+
    return mosnter
    end
+
  end
    return nil
+
  return nil
 
end
 
end
  
 
local function getMonsterStats(id)
 
local function getMonsterStats(id)
local mosnterStats = normalMonsters[tostring(id)]
+
  local mosnterStats = normalMonsters[tostring(id)]
if not mosnterStats then
+
  if not mosnterStats then
mosnterStats = dungeonMonsters[tostring(id)]
+
    mosnterStats = dungeonMonsters[tostring(id)]
if not mosnterStats then
+
    if not mosnterStats then
return nil
+
      return nil
end
+
    end
end
+
  end
return mosnterStats
+
  return mosnterStats
 
end
 
end
  
 
local function calcThreat(monster)
 
local function calcThreat(monster)
    local damageThreat = 2
+
  local damageThreat = 2
    local weaponThreat = 3
+
  local weaponThreat = 3
    local armorThreat = 5
+
  local armorThreat = 5
    local attackSpeedThreat = 3
+
  local attackSpeedThreat = 3
    local attackSpeedThreatLevel = 2.4 / monster.attackSpeed
+
  local attackSpeedThreatLevel = 2.4 / monster.attackSpeed
    local attackSpeedThreatFinal = attackSpeedThreatLevel * attackSpeedThreat
+
  local attackSpeedThreatFinal = attackSpeedThreatLevel * attackSpeedThreat
    local potentialDamageThreatFinal =
+
  local potentialDamageThreatFinal = (monster.attack + monster.strength + monster.magic + monster.range) * damageThreat
        (monster.attack +
+
  local weaponThreatFinal = (monster.weapon.dexterity + monster.weapon.intellect + monster.weapon.strength) *
            monster.strength +
+
                              weaponThreat
            monster.magic +
+
  local targetArmorRating = monster.armor.protection + monster.armor.resistance + monster.armor.agility * 1.5
            monster.range) *
+
  local armorThreatFinal = (targetArmorRating + monster.defense * 10) * armorThreat
        damageThreat
+
  local baseThreat = (potentialDamageThreatFinal + weaponThreatFinal) * attackSpeedThreatFinal + armorThreatFinal
    local weaponThreatFinal =
+
  return math.floor(baseThreat)
        (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
+
  local newUrl = url
if url:sub(1,5) == "https" then
+
  if url:sub(1, 5) == "https" then
return newUrl
+
    return newUrl
end
+
  end
if url:sub(1,1) ~= "/" then
+
  if url:sub(1, 1) ~= "/" then
newUrl = "/" .. newUrl
+
    newUrl = "/" .. newUrl
end
+
  end
newUrl = "https://www.play.idlescape.com" .. newUrl
+
  newUrl = "https://www.play.idlescape.com" .. newUrl
return newUrl
+
  return newUrl
 
end
 
end
  
 
local function createImgTag(monster)
 
local function createImgTag(monster)
    local url = fullUrl(monster.image)
+
  local url = fullUrl(monster.image)
    local attrs = {
+
  local attrs = {
        src = fullUrl(monster.image),
+
    src = fullUrl(monster.image),
        alt = monster.name,
+
    alt = monster.name,
        width = "150px"
+
    width = "150px"
    }
+
  }
    local img = mw.html.create('img'):attr(attrs)
+
  local img = mw.html.create('img'):attr(attrs)
    return tostring(img)
+
  return tostring(img)
 
end
 
end
  
 
local function createOffAffinity(args, monsterStats)
 
local function createOffAffinity(args, monsterStats)
for index, value in pairsByKeys(defaultAffinities) do
+
  for index, value in pairsByKeys(defaultAffinities) do
for affinity, affinityValue in pairs(value) do
+
    for affinity, affinityValue in pairs(value) do
if monsterStats.offensiveDamageAffinity[affinity] then
+
      if monsterStats.offensiveDamageAffinity[affinity] then
affinityValue = monsterStats.offensiveDamageAffinity[affinity]
+
        affinityValue = monsterStats.offensiveDamageAffinity[affinity]
args[sl()] = affinitiesIcon[index]
+
        args[sl()] = affinitiesIcon[index]
if affinityValue > 1 then
+
        if affinityValue > 1 then
                    args[sd()] = "<span style=\"color:#4caf50\">" .. (affinityValue - 1) * 100 .. "%<span/>"
+
          args[sd()] = "<span style=\"color:#4caf50\">" .. (affinityValue - 1) * 100 .. "%<span/>"
                elseif affinityValue < 1 then
+
        elseif affinityValue < 1 then
                    args[sd()] = "<span style=\"color:#f44336\">" .. (affinityValue - 1) * 100 .. "%<span/>"
+
          args[sd()] = "<span style=\"color:#f44336\">" .. (affinityValue - 1) * 100 .. "%<span/>"
else
+
        else
args[sd()] = (affinityValue - 1) * 100 .. "%"
+
          args[sd()] = (affinityValue - 1) * 100 .. "%"
                end
+
        end
else
+
      else
args[sl()] = affinitiesIcon[index]
+
        args[sl()] = affinitiesIcon[index]
args[sd()] = (affinityValue - 1) * 100 .. "%"
+
        args[sd()] = (affinityValue - 1) * 100 .. "%"
end
+
      end
if index % 3 == 0 then
+
      if index % 3 == 0 then
args["bodyclass"] = "equal-space"
+
        args["bodyclass"] = "equal-space"
args[sbreak()] ="yes"
+
        args[sbreak()] = "yes"
end
+
      end
end
+
    end
end
+
  end
return args
+
  return args
 
end
 
end
  
 
local function createDeffAffinity(args, monsterStats)
 
local function createDeffAffinity(args, monsterStats)
for index, value in pairsByKeys(defaultAffinities) do
+
  for index, value in pairsByKeys(defaultAffinities) do
for affinity, affinityValue in pairs(value) do
+
    for affinity, affinityValue in pairs(value) do
if monsterStats.defensiveDamageAffinity[affinity] then
+
      if monsterStats.defensiveDamageAffinity[affinity] then
affinityValue = monsterStats.defensiveDamageAffinity[affinity]
+
        affinityValue = monsterStats.defensiveDamageAffinity[affinity]
args[sl()] = affinitiesIcon[index]
+
        args[sl()] = affinitiesIcon[index]
if affinityValue > 1 then
+
        if affinityValue > 1 then
                    args[sd()] = "<span style=\"color:#4caf50\">" .. (affinityValue - 1) * 100 .. "%<span/>"
+
          args[sd()] = "<span style=\"color:#4caf50\">" .. (affinityValue - 1) * 100 .. "%<span/>"
                elseif affinityValue < 1 then
+
        elseif affinityValue < 1 then
                    args[sd()] = "<span style=\"color:#f44336\">" .. (affinityValue - 1) * 100 .. "%<span/>"
+
          args[sd()] = "<span style=\"color:#f44336\">" .. (affinityValue - 1) * 100 .. "%<span/>"
else
+
        else
args[sd()] = (affinityValue - 1) * 100 .. "%"
+
          args[sd()] = (affinityValue - 1) * 100 .. "%"
                end
+
        end
else
+
      else
args[sl()] = affinitiesIcon[index]
+
        args[sl()] = affinitiesIcon[index]
args[sd()] = (affinityValue - 1) * 100 .. "%"
+
        args[sd()] = (affinityValue - 1) * 100 .. "%"
end
+
      end
if index % 3 == 0 then
+
      if index % 3 == 0 then
args["bodyclass"] = "equal-space"
+
        args["bodyclass"] = "equal-space"
args[sbreak()] ="yes"
+
        args[sbreak()] = "yes"
end
+
      end
end
+
    end
end
+
  end
return args
+
  return args
 
end
 
end
  
 
local function getZonesLink(zones)
 
local function getZonesLink(zones)
local s = ""
+
  local s = ""
if zones then
+
  if zones then
for index, value in ipairs(zones) do
+
    for index, value in ipairs(zones) do
s = s .. "[[" .. capitalize(value) .. "]]"
+
      s = s .. "[[" .. capitalize(value) .. "]]"
if tablelength(zones) > index then
+
      if tablelength(zones) > index then
s = s .. ", "
+
        s = s .. ", "
end
+
      end
end
+
    end
end
+
  end
return s
+
  return s
 
end
 
end
  
 
local function createInfobox(monster, monsterStats, zones, dps)
 
local function createInfobox(monster, monsterStats, zones, dps)
    local args = {}
+
  local args = {}
  
    args.autoheaders = "y"
+
  args.autoheaders = "y"
args.subbox = "no"
+
  args.subbox = "no"
args.bodystyle = " "
+
  args.bodystyle = " "
args.title = monster.name
+
  args.title = monster.name
    args.image = createImgTag(monster)
+
  args.image = createImgTag(monster)
args[l()] = "Zones"
+
  args[l()] = "Zones"
args[d()] = getZonesLink(zones)
+
  args[d()] = getZonesLink(zones)
    args[h()] = "Offensive Stats"
+
  args[h()] = "Offensive Stats"
args[sl()] = "Attack Speed"
+
  args[sl()] = "Attack Speed"
args[sd()] = tostring(monsterStats.attackSpeed)
+
  args[sd()] = tostring(monsterStats.attackSpeed)
args[sl()] = "DPS"
+
  args[sl()] = "DPS"
args[sd()] = tostring(dps)
+
  args[sd()] = tostring(dps)
args[sl()] = "Crit Chance"
+
  args[sl()] = "Crit Chance"
args[sd()] = tostring(monsterStats.offensiveCritical.chance * 100) .. "%"
+
  args[sd()] = tostring(monsterStats.offensiveCritical.chance * 100) .. "%"
args[sl()] = "Crit Multiplier"
+
  args[sl()] = "Crit Multiplier"
args[sd()] = tostring(monsterStats.offensiveCritical.damageMultiplier * 100) .. "%"
+
  args[sd()] = tostring(monsterStats.offensiveCritical.damageMultiplier * 100) .. "%"
    args[h()] = "Offensive Affinities"
+
  args[h()] = "Offensive Affinities"
createOffAffinity(args, monsterStats)
+
  createOffAffinity(args, monsterStats)
args[h()] = "Defensive Stats"
+
  args[h()] = "Defensive Stats"
args[sl()] = "Threat"
+
  args[sl()] = "Threat"
args[sd()] = calcThreat(monsterStats)
+
  args[sd()] = calcThreat(monsterStats)
args[sl()] = "Crit Avoidance"
+
  args[sl()] = "Crit Avoidance"
args[sd()] = tostring(monsterStats.defensiveCritical.chance * 100) .. "%"
+
  args[sd()] = tostring(monsterStats.defensiveCritical.chance * 100) .. "%"
args[sl()] = "Crit Reduction"
+
  args[sl()] = "Crit Reduction"
args[sd()] = tostring(monsterStats.defensiveCritical.damageMultiplier * 100) .. "%"
+
  args[sd()] = tostring(monsterStats.defensiveCritical.damageMultiplier * 100) .. "%"
args[h()] = "Defensive Affinities"
+
  args[h()] = "Defensive Affinities"
createDeffAffinity(args, monsterStats)
+
  createDeffAffinity(args, monsterStats)
  
for key, data in pairs(args) do
+
  for key, data in pairs(args) do
if string.find(key, "data") then
+
    if string.find(key, "data") then
args[key] = tostring(data)
+
      args[key] = tostring(data)
end
+
    end
end
+
  end
  
return infoboxModule.infobox(args)
+
  return infoboxModule.infobox(args)
 
end
 
end
  
function p.infoboxMonster(frame)
+
function p.infoboxMonster(frame)
    local args = frame:getParent().args
+
  local args = frame:getParent().args
return p._infoboxMonster(args)
+
  return p._infoboxMonster(args)
 
end
 
end
  
 
function p._infoboxMonster(_args)
 
function p._infoboxMonster(_args)
local zones = fromCSV(_args["zones"])
+
  local dps
local dps = _args["DPS"]
+
  local zones
     local name
+
  if _args["zones"] then
local id
+
    zones = fromCSV(_args["zones"])
local monster
+
  end
local monsterStats
+
  if _args["DPS"] then
local infobox
+
    dps = _args["DPS"]
    if _args[1] then
+
  else
name = _args[1]
+
     dps = "-"
else
+
  end
name = mw.title.getCurrentTitle().text
+
  local name
end
+
  local id
id = findId._findId({name, "monster"})
+
  local monster
    if not id then
+
  local monsterStats
        return "<div style=\"color:red\"> No monster named '" .. name .. "'</div>. The Module:Monsters Ids/data maybe outdated."
+
  local infobox
    end
+
  if _args[1] then
monster = getMonster(id)
+
    name = _args[1]
if not monster then
+
  else
        return "<div style=\"color:red\"> No monster named '" .. name .. "'</div>. The Module:Monsters/data maybe outdated."
+
    name = mw.title.getCurrentTitle().text
    end
+
  end
monsterStats = getMonsterStats(id)
+
  id = findId._findId({name, "monster"})
if not monsterStats then
+
  if not id then
        return "<div style=\"color:red\"> No monster named '" .. name .. "'</div>. Module:Monsters stats normal/data and Module:Monsters stats dungeon/data maybe outdated."
+
    return "<div style=\"color:red\"> No monster named '" .. name ..
    end
+
            "'</div>. The Module:Monsters Ids/data maybe outdated."
    infobox = createInfobox(monster, monsterStats, zones, dps)
+
  end
return infobox
+
  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