Module:Monsters: Difference between revisions

Implement getSlayerTierMonsterTable; Improve performance of getMonster, getSpecialAttack, getPassive
(added functions to get average GP for all monster drops and then made a table of that)
(Implement getSlayerTierMonsterTable; Improve performance of getMonster, getSpecialAttack, getPassive)
Line 23: Line 23:
       --Make sure every monster has an ID, and account for the 1-based indexing of Lua
       --Make sure every monster has an ID, and account for the 1-based indexing of Lua
       result.id = i - 1
       result.id = i - 1
      break
     end
     end
   end
   end
Line 42: Line 43:
       --Make sure every attack has an ID, and account for the 1-based indexing of Lua
       --Make sure every attack has an ID, and account for the 1-based indexing of Lua
       result.id = i - 1
       result.id = i - 1
      break
     end
     end
   end
   end
Line 59: Line 61:
       --Make sure every passive has an ID, and account for the 1-based indexing of Lua
       --Make sure every passive has an ID, and account for the 1-based indexing of Lua
       result.id = i - 1
       result.id = i - 1
      break
     end
     end
   end
   end
Line 77: Line 80:


   if StatName == 'HP' then
   if StatName == 'HP' then
     return p.getMonsterHP(MonsterName)
     return p._getMonsterHP(monster)
   elseif StatName == 'maxHit' then
   elseif StatName == 'maxHit' then
     return p.getMonsterMaxHit(MonsterName)
     return p._getMonsterMaxHit(monster)
   elseif StatName == 'accuracyRating' then
   elseif StatName == 'accuracyRating' then
     return p.getMonsterAR(MonsterName)
     return p._getMonsterAR(monster)
   elseif StatName == 'meleeEvasionRating' then
   elseif StatName == 'meleeEvasionRating' then
     return p.getMonsterER({MonsterName, 'Melee'})
     return p._getMonsterER({monster, 'Melee'})
   elseif StatName == 'rangedEvasionRating' then
   elseif StatName == 'rangedEvasionRating' then
     return p.getMonsterER({MonsterName, 'Ranged'})
     return p._getMonsterER({monster, 'Ranged'})
   elseif StatName == 'magicEvasionRating' then
   elseif StatName == 'magicEvasionRating' then
     return p.getMonsterER({MonsterName, 'Magic'})
     return p._getMonsterER({monster, 'Magic'})
   end
   end


Line 93: Line 96:
end
end


function p.getMonsterStyleIcon(frame)
function p._getMonsterStyleIcon(frame)
   local args = frame.args ~= nil and frame.args or frame
   local args = frame.args ~= nil and frame.args or frame
   local MonsterName = args[1]
   local monster = args[1]
   local notext = args.notext
   local notext = args.notext
   local nolink = args.nolink
   local nolink = args.nolink
  local monster = p.getMonster(MonsterName)
  if monster == nil then
    return "ERROR: No monster with that name found"
  end


   local iconText = ''
   local iconText = ''
Line 114: Line 112:


   return iconText
   return iconText
end
function p.getMonsterStyleIcon(frame)
  local args = frame.args ~= nil and frame.args or frame
  local MonsterName = args[1]
  local monster = p.getMonster(MonsterName)
  if monster == nil then
    return "ERROR: No monster with that name found"
  end
  args[1] = monster
  return p._getMonsterStyleIcon(args)
end
function p._getMonsterHP(monster)
  return monster.hitpoints * 10
end
end


Line 120: Line 135:
   local monster = p.getMonster(MonsterName)
   local monster = p.getMonster(MonsterName)
   if monster ~= nil then
   if monster ~= nil then
     return monster.hitpoints * 10
     return p._getMonsterHP(monster)
   else
   else
     return "ERROR: No monster with that name found"
     return "ERROR: No monster with that name found"
   end
   end
end
function p._getMonsterAttackSpeed(monster)
  return monster.attackSpeed / 1000
end
end


Line 130: Line 149:
   local monster = p.getMonster(MonsterName)
   local monster = p.getMonster(MonsterName)
   if monster ~= nil then
   if monster ~= nil then
     return monster.attackSpeed / 1000
     return p._getMonsterAttackSpeed(monster)
   else
   else
     return "ERROR: No monster with that name found"
     return "ERROR: No monster with that name found"
Line 161: Line 180:
end
end


function p.getMonsterAR(frame)
function p._getMonsterAR(monster)
  local MonsterName = frame.args ~= nil and frame.args[1] or frame
  local monster = p.getMonster(MonsterName)
 
  if monster == nil then
    return "ERROR: No monster with that name found"
  end
 
   local effAttLvl = 0
   local effAttLvl = 0
   local attBonus = 0
   local attBonus = 0
Line 187: Line 199:
end
end


function p.getMonsterER(frame)
function p.getMonsterAR(frame)
   local args = frame.args ~= nil and frame.args or frame
   local MonsterName = frame.args ~= nil and frame.args[1] or frame
  local MonsterName = args[1]
  local style = args[2]
   local monster = p.getMonster(MonsterName)
   local monster = p.getMonster(MonsterName)


Line 196: Line 206:
     return "ERROR: No monster with that name found"
     return "ERROR: No monster with that name found"
   end
   end
    
 
   return p._getMonsterAR(monster)
end
 
function p._getMonsterER(frame)
  local args = frame.args ~= nil and frame.args or frame
  local monster = args[1]
  local style = args[2]
 
   local effDefLvl = 0
   local effDefLvl = 0
   local defBonus = 0
   local defBonus = 0
Line 212: Line 230:
   end
   end
   return effDefLvl * defBonus
   return effDefLvl * defBonus
end
function p.getMonsterER(frame)
  local args = frame.args ~= nil and frame.args or frame
  local MonsterName = args[1]
  local style = args[2]
  local monster = p.getMonster(MonsterName)
  if monster == nil then
    return "ERROR: No monster with that name found"
  end
  return p._getMonsterER({monster, style})
end
end


Line 220: Line 251:


   for i, area in Shared.skpairs(areaList) do
   for i, area in Shared.skpairs(areaList) do
    mw.log(area.type)
     if area.type == 'dungeon' then
     if area.type == 'dungeon' then
       dunCount = dunCount + 1
       dunCount = dunCount + 1
Line 262: Line 292:
end
end


function p.getMonsterMaxHit(frame)
function p._getMonsterMaxHit(monster)
  local MonsterName = frame.args ~= nil and frame.args[1] or frame
  local monster = p.getMonster(MonsterName)
 
  if monster == nil then
    return "ERROR: No monster with that name found"
  end
 
   local normalChance = 100
   local normalChance = 100
   local specialMaxHit = 0
   local specialMaxHit = 0
   local normalMaxHit = p.getMonsterBaseMaxHit(frame)
   local normalMaxHit = p._getMonsterBaseMaxHit(monster)
   if monster.hasSpecialAttack then
   if monster.hasSpecialAttack then
     for i, specID in pairs(monster.specialAttackID) do
     for i, specID in pairs(monster.specialAttackID) do
Line 301: Line 324:
end
end


function p.getMonsterBaseMaxHit(frame)
function p.getMonsterMaxHit(frame)
   local MonsterName = frame.args ~= nil and frame.args[1] or frame
   local MonsterName = frame.args ~= nil and frame.args[1] or frame
   local monster = p.getMonster(MonsterName)
   local monster = p.getMonster(MonsterName)
Line 308: Line 331:
     return "ERROR: No monster with that name found"
     return "ERROR: No monster with that name found"
   end
   end
    
 
   return p._getMonsterMaxHit(monster)
end
 
function p._getMonsterBaseMaxHit(monster)
   local effStrLvl = 0
   local effStrLvl = 0
   local strBonus = 0
   local strBonus = 0
Line 331: Line 358:
   --Should only get here for Melee/Ranged, which use functionally the same damage formula
   --Should only get here for Melee/Ranged, which use functionally the same damage formula
   return math.floor(10 * (1.3 + (effStrLvl/10) + (strBonus / 80) + ((effStrLvl * strBonus) / 640)))
   return math.floor(10 * (1.3 + (effStrLvl/10) + (strBonus / 80) + ((effStrLvl * strBonus) / 640)))
end
function p.getMonsterBaseMaxHit(frame)
  local MonsterName = frame.args ~= nil and frame.args[1] or frame
  local monster = p.getMonster(MonsterName)
  if monster == nil then
    return "ERROR: No monster with that name found"
  end
  return p._getMonsterBaseMaxHit(monster)
end
end


Line 413: Line 451:


   local result = '[[Category:Monsters]]'
   local result = '[[Category:Monsters]]'
 
 
   if monster.attackType == Constants.attackType.Melee then
   if monster.attackType == Constants.attackType.Melee then
     result = result..'[[Category:Melee Monsters]]'
     result = result..'[[Category:Melee Monsters]]'
Line 425: Line 463:
     result = result..'[[Category:Monsters with Special Attacks]]'
     result = result..'[[Category:Monsters with Special Attacks]]'
   end
   end
 
 
   if monster.isBoss then
   if monster.isBoss then
     result = result..'[[Category:Bosses]]'
     result = result..'[[Category:Bosses]]'
Line 486: Line 524:
       result = result..'||style="text-align:right" data-sort-value="'..maxQty..'"|'
       result = result..'||style="text-align:right" data-sort-value="'..maxQty..'"|'


       if maxQty > 1 then  
       if maxQty > 1 then
         result = result.. '1 - '  
         result = result.. '1 - '
       end
       end
       result = result..Shared.formatnum(row[3])
       result = result..Shared.formatnum(row[3])
Line 573: Line 611:
       result = result..'||style="text-align:right" data-sort-value="'..qty..'"|'
       result = result..'||style="text-align:right" data-sort-value="'..qty..'"|'


       if qty > 1 then  
       if qty > 1 then
         result = result.. '1 - '  
         result = result.. '1 - '
       end
       end
       result = result..Shared.formatnum(qty)
       result = result..Shared.formatnum(qty)
Line 634: Line 672:
   local monsterCounts = {}
   local monsterCounts = {}
   for i, monsterID in pairs(area.monsters) do
   for i, monsterID in pairs(area.monsters) do
     if monsterCounts[monsterID] == nil then  
     if monsterCounts[monsterID] == nil then
       monsterCounts[monsterID] = 1
       monsterCounts[monsterID] = 1
     else
     else
Line 816: Line 854:
       lootValue = lootValue + (dropChance * 0.01 * itemPrice * ((1 + maxQty) / 2))
       lootValue = lootValue + (dropChance * 0.01 * itemPrice * ((1 + maxQty) / 2))
     end
     end
   
 
     totalGP = totalGP + lootValue
     totalGP = totalGP + lootValue
   end
   end
Line 830: Line 868:
     return "ERROR: No monster with that name found"
     return "ERROR: No monster with that name found"
   end
   end
 
 
   return p._getMonsterAverageGP(monster)
   return p._getMonsterAverageGP(monster)
end
end
Line 848: Line 886:
   result = result..'\r\n|}'
   result = result..'\r\n|}'
   return result
   return result
end
function p.getSlayerTierMonsterTable(tier)
  -- Input validation
  local SlayerTiers = Constants.Slayer.Tiers
  if tier == nil then
    return "ERROR: No tier specified"
  elseif type(tier) ~= "number" then
    return "ERROR: Tier must be an integer"
  else
    tier = math.floor(tier)
    local tierCount = Shared.tableCount(SlayerTiers) - 1
    if tier < 0 or tier > tierCount then
      return "ERROR: Tier must be between 0 and " .. tierCount
    end
    tier = tier + 1
  end
  -- Obtain required tier details
  local minLevel, maxLevel = SlayerTiers[tier].minLevel, SlayerTiers[tier].maxLevel
  if maxLevel < 0 then
    maxLevel = nil
  end
  -- Build list of monster IDs
  -- hiddenMonsterIDs below should be changed to {} when 0.19 releases
  local hiddenMonsterIDs = {139}
  local monsterIDs = {}
  for i, monster in Shared.skpairs(MonsterData.Monsters) do
    if monster.canSlayer and not Shared.contains(hiddenMonsterIDs, i - 1) then
      local cmbLevel = p._getMonsterCombatLevel(monster)
      if cmbLevel >= minLevel and (maxLevel == nil or cmbLevel <= maxLevel) then
        table.insert(monsterIDs, i - 1)
      end
    end
  end
  if Shared.tableCount(monsterIDs) == 0 then
    -- Somehow no monsters are in the tier, return nothing
    return ''
  else
    local tableTxt = '{| class="wikitable sortable stickyHeader"'
    -- First header row
    tableTxt = tableTxt .. '\r\n|- class="headerRow-0"\r\n! colspan="5" | !! colspan="4" |Offensive Stats !! colspan="3" |Evasion Rating !! colspan="4" |'
    -- Second header row
    tableTxt = tableTxt .. '\r\n|- class="headerRow-1"\r\n!Monster !!Name !!ID !!Combat Level '
    tableTxt = tableTxt .. '!!style="padding:0 1em 0 0"|' .. Icons.Icon({'Hitpoints', type='skill'})
    tableTxt = tableTxt .. '!!Attack Type !!Attack Speed (s) !!Max Hit !!Accuracy '
    tableTxt = tableTxt .. '!!style="padding:0 1em 0 0"|' .. Icons.Icon({'Defence', type='skill', notext=true})
    tableTxt = tableTxt .. '!!style="padding:0 1em 0 0"|' .. Icons.Icon({'Ranged', type='skill', notext=true})
    tableTxt = tableTxt .. '!!style="padding:0 1em 0 0"|' .. Icons.Icon({'Magic', type='skill', notext=true})
    tableTxt = tableTxt .. '!!Drop Chance !!Coins !!Bones !!Locations'
    -- Generate row per monster
    for i, monsterID in Shared.skpairs(monsterIDs) do
      local monster = p.getMonsterByID(monsterID)
      local cmbLevel = p._getMonsterCombatLevel(monster)
      local atkSpeed = p._getMonsterAttackSpeed(monster)
      local maxHit = p._getMonsterMaxHit(monster)
      local accR = p._getMonsterAR(monster)
      local evaR = {p._getMonsterER({monster, "Melee"}), p._getMonsterER({monster, "Ranged"}), p._getMonsterER({monster, "Magic"})}
      local lootChance = monster.lootChance ~= nil and monster.lootChance or 100
      local gpRange = {0, 0}
      if monster.dropCoins ~= nil and monster.dropCoins[2] > 1 then
        gpRange = {monster.dropCoins[1], monster.dropCoins[2] - 1}
      end
      local gpTxt = nil
      if gpRange[1] == gpRange[2] then
        gpTxt = gpRange[1]
      else
        gpTxt = gpRange[1] .. '&nbsp;-&nbsp;' .. gpRange[2]
      end
      local boneTxt = 'None'
      if monster.bones ~= nil then
        local bones = Items.getItemByID(monster.bones)
        boneTxt = Icons.Icon({bones.name, type='item', notext=true})
      end
      tableTxt = tableTxt .. '\r\n|-\r\n|style="text-align: left;" |' .. Icons.Icon({monster.name, type='monster', size=50, notext=true})
      tableTxt = tableTxt .. '\r\n|style="text-align:left" |[[' .. monster.name .. ']]'
      tableTxt = tableTxt .. '\r\n|style="text-align:right" |' .. monsterID
      tableTxt = tableTxt .. '\r\n|style="text-align:right" data-sort-value="' .. cmbLevel .. '" |' .. Shared.formatnum(cmbLevel)
      tableTxt = tableTxt .. '\r\n|style="text-align:right" data-sort-value="' .. monster.hitpoints .. '" |' .. Shared.formatnum(p._getMonsterHP(monster))
      tableTxt = tableTxt .. '\r\n|style="text-align:right;white-space:nowrap" |' .. p._getMonsterStyleIcon({monster, nolink='true'})
      tableTxt = tableTxt .. '\r\n|style="text-align:right" data-sort-value="' .. atkSpeed .. '" |' .. Shared.round(atkSpeed, 1, 1)
      tableTxt = tableTxt .. '\r\n|style="text-align:right" data-sort-value="' .. maxHit .. '" |' .. Shared.formatnum(maxHit)
      tableTxt = tableTxt .. '\r\n|style="text-align:right" data-sort-value="' .. accR .. '" |' .. Shared.formatnum(accR)
      tableTxt = tableTxt .. '\r\n|style="text-align:right" data-sort-value="' .. evaR[1] .. '" |' .. Shared.formatnum(evaR[1])
      tableTxt = tableTxt .. '\r\n|style="text-align:right" data-sort-value="' .. evaR[2] .. '" |' .. Shared.formatnum(evaR[2])
      tableTxt = tableTxt .. '\r\n|style="text-align:right" data-sort-value="' .. evaR[3] .. '" |' .. Shared.formatnum(evaR[3])
      tableTxt = tableTxt .. '\r\n|style="text-align:right" data-sort-value="' .. lootChance .. '" |' .. lootChance .. '%'
      tableTxt = tableTxt .. '\r\n|style="text-align:right" data-sort-value="' .. (gpRange[1] + gpRange[2]) / 2 .. '" |' .. gpTxt
      tableTxt = tableTxt .. '\r\n|style="text-align:center" |' .. boneTxt
      tableTxt = tableTxt .. '\r\n|style="text-align:right;white-space:nowrap" |' .. p._getMonsterAreas(monster)
    end
    tableTxt = tableTxt .. "\r\n|}"
    return tableTxt
  end
end
end


return p
return p