Anonymous

Module:Monsters: Difference between revisions

From Melvor Idle
m
getChestDrops: Added punctuation
(Reorganizing to hopefully fix issue with boss monsters getting left out)
m (getChestDrops: Added punctuation)
(45 intermediate revisions by 5 users not shown)
Line 1: Line 1:
local p = {}
local p = {}


local Constants = mw.loadData('Module:Constants/data')
local MonsterData = mw.loadData('Module:Monsters/data')
local MonsterData = mw.loadData('Module:Monsters/data')


local Constants = require('Module:Constants')
local Areas = require('Module:CombatAreas')
local Areas = require('Module:CombatAreas')
local Magic = require('Module:Magic')
local Magic = require('Module:Magic')
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 29: Line 30:


function p.getMonsterByID(ID)
function p.getMonsterByID(ID)
   return MonsterData.Monsters[ID + 1]
   local result = Shared.clone(MonsterData.Monsters[ID + 1])
  result.id = ID
  return result
end
end


Line 38: Line 41:
     if(attack.name == name) then
     if(attack.name == name) then
       result = Shared.clone(attack)
       result = Shared.clone(attack)
       --Make sure every monster 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 47: Line 51:
function p.getSpecialAttackByID(ID)
function p.getSpecialAttackByID(ID)
   return MonsterData.SpecialAttacks[ID + 1]
   return MonsterData.SpecialAttacks[ID + 1]
end
function p.getPassive(name)
  local result = nil
  for i, passive in pairs(MonsterData.Passives) do
    if passive.name == name then
      result = Shared.clone(passive)
      --Make sure every passive has an ID, and account for the 1-based indexing of Lua
      result.id = i - 1
      break
    end
  end
  return result
end
function p.getPassiveByID(ID)
  return MonsterData.Passives[ID + 1]
end
end


Line 54: Line 76:
   local monster = p.getMonster(MonsterName)
   local monster = p.getMonster(MonsterName)
   if monster == nil then
   if monster == nil then
     return "ERROR: No monster with that name found"
     return "ERROR: No monster with that name found[[Category:Pages with script errors]]"
   end
   end


   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 74: 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 = ''
   if  monster.attackType == Constants.attackType.Melee then
   if  Constants.getCombatStyleName(monster.attackType) == 'Melee' then
     iconText = Icons.Icon({'Melee', notext=notext, nolink=nolink})
     iconText = Icons.Icon({'Melee', notext=notext, nolink=nolink})
   elseif monster.attackType == Constants.attackType.Ranged then
   elseif Constants.getCombatStyleName(monster.attackType) == 'Ranged' then
     iconText = Icons.Icon({'Ranged', type='skill', notext=notext, nolink=nolink})
     iconText = Icons.Icon({'Ranged', type='skill', notext=notext, nolink=nolink})
   else
   elseif Constants.getCombatStyleName(monster.attackType) == 'Magic' then
     iconText = Icons.Icon({'Magic', type='skill', notext=notext, nolink=nolink})
     iconText = Icons.Icon({'Magic', type='skill', notext=notext, nolink=nolink})
   end
   end


   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[[Category:Pages with script errors]]"
  end
  args[1] = monster
  return p._getMonsterStyleIcon(args)
end
function p._getMonsterHP(monster)
  return monster.hitpoints * 10
end
end


Line 101: 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[[Category:Pages with script errors]]"
   end
   end
end
function p._getMonsterAttackSpeed(monster)
  return monster.attackSpeed / 1000
end
end


Line 111: 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[[Category:Pages with script errors]]"
   end
   end
end
end


function p.getMonsterCombatLevel(frame)
function p._getMonsterCombatLevel(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 base = 0.25 * (monster.defenceLevel + monster.hitpoints)
   local base = 0.25 * (monster.defenceLevel + monster.hitpoints)
   local melee = 0.325 * (monster.attackLevel + monster.strengthLevel)
   local melee = 0.325 * (monster.attackLevel + monster.strengthLevel)
Line 138: Line 169:
end
end


function p.getMonsterAR(frame)
function p.getMonsterCombatLevel(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)


   if monster == nil then
   if monster == nil then
     return "ERROR: No monster with that name found"
     return "ERROR: No monster with that name found[[Category:Pages with script errors]]"
   end
   end
    
 
   return p._getMonsterCombatLevel(monster)
end
 
function p._getMonsterAR(monster)
   local effAttLvl = 0
   local effAttLvl = 0
   local attBonus = 0
   local attBonus = 0
   if monster.attackType == Constants.attackType.Melee then
   if Constants.getCombatStyleName(monster.attackType) == 'Melee' then
     effAttLvl = monster.attackLevel + 9
     effAttLvl = monster.attackLevel + 9
     attBonus = monster.attackBonus + 64
     attBonus = monster.attackBonus + 64
   elseif monster.attackType == Constants.attackType.Ranged then
   elseif Constants.getCombatStyleName(monster.attackType) == 'Ranged' then
     effAttLvl = monster.rangedLevel + 9
     effAttLvl = monster.rangedLevel + 9
     attBonus = monster.attackBonusRanged + 64
     attBonus = monster.attackBonusRanged + 64
   elseif monster.attackType == Constants.attackType.Magic then
   elseif Constants.getCombatStyleName(monster.attackType) == 'Magic' then
     effAttLvl = monster.magicLevel + 9
     effAttLvl = monster.magicLevel + 9
     attBonus = monster.attackBonusMagic + 64
     attBonus = monster.attackBonusMagic + 64
   else
   else
     return "ERROR: This monster has an invalid attack type somehow"
     return "ERROR: This monster has an invalid attack type somehow[[Category:Pages with script errors]]"
   end
   end


Line 164: 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)


   if monster == nil then
   if monster == nil then
     return "ERROR: No monster with that name found"
     return "ERROR: No monster with that name found[[Category:Pages with script errors]]"
   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 186: Line 227:
     defBonus = monster.defenceBonusMagic + 64
     defBonus = monster.defenceBonusMagic + 64
   else
   else
     return "ERROR: Must choose Melee, Ranged, or Magic"
     return "ERROR: Must choose Melee, Ranged, or Magic[[Category:Pages with script errors]]"
   end
   end
   return effDefLvl * defBonus
   return effDefLvl * defBonus
end
end


function p.getMonsterAreas(frame)
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[[Category:Pages with script errors]]"
  end
 
  return p._getMonsterER({monster, style})
end
 
function p._isDungeonOnlyMonster(monster)
  local areaList = Areas.getMonsterAreas(monster.id)
  local dunCount = 0
  local nonDunCount = 0
 
  for i, area in Shared.skpairs(areaList) do
    if area.type == 'dungeon' then
      dunCount = dunCount + 1
    else
      nonDunCount = nonDunCount + 1
    end
  end
  return dunCount > 0 and nonDunCount == 0
end
 
function p.isDungeonOnlyMonster(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)


   if monster == nil then
   if monster == nil then
     return "ERROR: No monster with name "..monsterName.." found"
     return "ERROR: No monster with name "..monsterName.." found[[Category:Pages with script errors]]"
   end
   end


  return p._isDungeonOnlyMonster(monster)
end
function p._getMonsterAreas(monster, excludeDungeons)
   local result = ''
   local result = ''
  local hideDungeons = excludeDungeons ~= nil and excludeDungeons or false
   local areaList = Areas.getMonsterAreas(monster.id)
   local areaList = Areas.getMonsterAreas(monster.id)
   for i, area in pairs(areaList) do
   for i, area in pairs(areaList) do
     if i > 1 then result = result..'<br/>' end
     if area.type ~= 'dungeon' or not hideDungeons then
    result = result..Icons.Icon({area.name, type = area.type})
      if i > 1 then result = result..'<br/>' end
      result = result..Icons.Icon({area.name, type = area.type})
    end
   end
   end
   return result
   return result
end
end


function p.getMonsterMaxHit(frame)
function p.getMonsterAreas(frame)
   local MonsterName = frame.args ~= nil and frame.args[1] or frame
   local MonsterName = frame.args ~= nil and frame.args[1] or frame
  local hideDungeons = frame.args ~= nil and frame.args[2] or nil
   local monster = p.getMonster(MonsterName)
   local monster = p.getMonster(MonsterName)


   if monster == nil then
   if monster == nil then
     return "ERROR: No monster with that name found"
     return "ERROR: No monster with name "..monsterName.." found[[Category:Pages with script errors]]"
   end
   end


  return p._getMonsterAreas(monster, hideDungeons)
end
function p._getMonsterMaxHit(monster)
   local normalChance = 100
   local normalChance = 100
   local specialMaxHit = 0
   local specialMaxHit = 0
   local normalMaxHit = p.getMonsterBaseMaxHit(frame)
   local normalMaxHit = p._getMonsterBaseMaxHit(monster)
  local hasActiveBuffSpec = false
   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 232: Line 314:
       else
       else
         thisMax = normalMaxHit
         thisMax = normalMaxHit
      end
      if specAttack.stunDamageMultiplier ~= nil and specAttack.stunDamageMultiplier > 1 then
        thisMax = thisMax * specAttack.stunDamageMultiplier
      end
      if specAttack.sleepDamageMultiplier ~= nil and specAttack.sleepDamageMultiplier > 1 then
        thisMax = thisMax * specAttack.sleepDamageMultiplier
       end
       end
       if thisMax > specialMaxHit then specialMaxHit = thisMax end
       if thisMax > specialMaxHit then specialMaxHit = thisMax end
      if specAttack.activeBuffs then hasActiveBuffSpec = true end
     end
     end
   end
   end
   --Ensure that if the monster never does a normal attack, the normal max hit is irrelevant
   --Ensure that if the monster never does a normal attack, the normal max hit is irrelevant
   if normalChance == 0 then normalMaxHit = 0 end
   if normalChance == 0 and not hasActiveBuffSpec then normalMaxHit = 0 end
   return math.max(specialMaxHit, normalMaxHit)
   return math.max(specialMaxHit, normalMaxHit)
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)


   if monster == nil then
   if monster == nil then
     return "ERROR: No monster with that name found"
     return "ERROR: No monster with that name found[[Category:Pages with script errors]]"
   end
   end
    
 
   return p._getMonsterMaxHit(monster)
end
 
function p._getMonsterBaseMaxHit(monster)
   local effStrLvl = 0
   local effStrLvl = 0
   local strBonus = 0
   local strBonus = 0
   if monster.attackType == Constants.attackType.Melee then
   if Constants.getCombatStyleName(monster.attackType) == 'Melee' then
     effStrLvl = monster.strengthLevel + 9
     effStrLvl = monster.strengthLevel + 9
     strBonus = monster.strengthBonus
     strBonus = monster.strengthBonus
   elseif monster.attackType == Constants.attackType.Ranged then
   elseif Constants.getCombatStyleName(monster.attackType) == 'Ranged' then
     effStrLvl = monster.rangedLevel + 9
     effStrLvl = monster.rangedLevel + 9
     strBonus = monster.strengthBonusRanged
     strBonus = monster.strengthBonusRanged
   elseif monster.attackType == Constants.attackType.Magic then
   elseif Constants.getCombatStyleName(monster.attackType) == 'Magic' then
     local mSpell = nil
     local mSpell = nil
     if monster.selectedSpell ~= nil then mSpell = Magic.getSpellByID('Spells', monster.selectedSpell) end
     if monster.selectedSpell ~= nil then mSpell = Magic.getSpellByID('Spells', monster.selectedSpell) end
Line 266: Line 359:
     end
     end
   else
   else
     return "ERROR: This monster has an invalid attack type somehow"
    error('blah')
     return "ERROR: This monster has an invalid attack type somehow[[Category:Pages with script errors]]"
   end
   end


   --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[[Category:Pages with script errors]]"
  end
  return p._getMonsterBaseMaxHit(monster)
end
end


Line 278: Line 383:


   if monster == nil then
   if monster == nil then
     return "ERROR: No monster with that name found"
     return "ERROR: No monster with that name found[[Category:Pages with script errors]]"
   end
   end


Line 285: Line 390:
   local iconText = ''
   local iconText = ''
   local typeText = ''
   local typeText = ''
   if  monster.attackType == Constants.attackType.Melee then
   if  Constants.getCombatStyleName(monster.attackType) == 'Melee' then
     iconText = Icons.Icon({'Melee', notext=true})
     iconText = Icons.Icon({'Melee', notext=true})
     typeText = 'Melee'
     typeText = 'Melee'
   elseif monster.attackType == Constants.attackType.Ranged then
   elseif Constants.getCombatStyleName(monster.attackType) == 'Ranged' then
     iconText = Icons.Icon({'Ranged', type='skill', notext=true})
     iconText = Icons.Icon({'Ranged', type='skill', notext=true})
     typeText = 'Ranged'
     typeText = 'Ranged'
   else
   elseif Constants.getCombatStyleName(monster.attackType) == 'Magic' then
     iconText = Icons.Icon({'Magic', type='skill', notext=true})
     iconText = Icons.Icon({'Magic', type='skill', notext=true})
     typeText = 'Magic'
     typeText = 'Magic'
Line 315: Line 420:
   elseif normalAttackChance > 0 then
   elseif normalAttackChance > 0 then
     result = '* '..normalAttackChance..'% '..iconText..'1-'..p.getMonsterBaseMaxHit(frame)..' '..typeText..' Damage'..result
     result = '* '..normalAttackChance..'% '..iconText..'1-'..p.getMonsterBaseMaxHit(frame)..' '..typeText..' Damage'..result
  end
  return result
end
function p.getMonsterPassives(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[[Category:Pages with script errors]]"
  end
  local result = ''
  if monster.hasPassive then
    result = result .. '===Passives==='
    for i, passiveID in pairs(monster.passiveID) do
      local passive = p.getPassiveByID(passiveID)
      local passiveChance = 0
      if passive.chance ~= nil then
        passiveChance = passive.chance
      end
      result = result .. '\r\n* ' .. Shared.round(passiveChance, 2, 0) .. '% ' .. passive.name .. '\r\n** ' .. passive.description
    end
   end
   end
   return result
   return result
Line 324: Line 454:


   if monster == nil then
   if monster == nil then
     return "ERROR: No monster with that name found"
     return "ERROR: No monster with that name found[[Category:Pages with script errors]]"
   end
   end


   local result = '[[Category:Monsters]]'
   local result = '[[Category:Monsters]]'
 
 
   if monster.attackType == Constants.attackType.Melee then
   if Constants.getCombatStyleName(monster.attackType) == 'Melee' then
     result = result..'[[Category:Melee Monsters]]'
     result = result..'[[Category:Melee Monsters]]'
   elseif monster.attackType == Constants.attackType.Ranged then
   elseif Constants.getCombatStyleName(monster.attackType) == 'Ranged' then
     result = result..'[[Category:Ranged Monsters]]'
     result = result..'[[Category:Ranged Monsters]]'
   elseif monster.attackType == Constants.attackType.Magic then
   elseif Constants.getCombatStyleName(monster.attackType) == 'Magic' then
     result = result..'[[Category:Magic Monsters]]'
     result = result..'[[Category:Magic Monsters]]'
   end
   end
Line 340: Line 470:
     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]]'
   end
   end
  return result
end
function p.getOtherMonsterBoxText(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[[Category:Pages with script errors]]"
  end
  local result = ''
  --Going through and finding out which damage bonuses will apply to this monster
  local monsterTypes = {}
  if monster.isBoss then table.insert(monsterTypes, 'Boss') end
  local areaList = Areas.getMonsterAreas(monster.id)
  local counts = {combat = 0, slayer = 0, dungeon = 0}
  for i, area in Shared.skpairs(areaList) do
    counts[area.type] = counts[area.type] + 1
  end
  if counts.combat > 0 then table.insert(monsterTypes, 'Combat Area') end
  if counts.slayer > 0 then table.insert(monsterTypes, 'Slayer Area') end
  if counts.dungeon > 0 then table.insert(monsterTypes, 'Dungeon') end
  result = result.."\r\n|-\r\n|'''Monster Types:''' "..table.concat(monsterTypes, ", ")
  local SlayerTier = 'N/A'
  if not p._isDungeonOnlyMonster(monster) then
    SlayerTier = Constants.getSlayerTierNameByLevel(p._getMonsterCombatLevel(monster))
  end
  result = result.."\r\n|-\r\n|'''"..Icons.Icon({'Slayer', type='skill'}).." [[Slayer#Slayer Tier Monsters|Tier]]:''' "..SlayerTier


   return result
   return result
Line 353: Line 519:


   if monster == nil then
   if monster == nil then
     return "ERROR: No monster with that name found"
     return "ERROR: No monster with that name found[[Category:Pages with script errors]]"
   end
   end


Line 361: Line 527:
     local bones = Items.getItemByID(monster.bones)
     local bones = Items.getItemByID(monster.bones)
     --Show the bones only if either the monster shows up outside of dungeons _or_ the monster drops shards
     --Show the bones only if either the monster shows up outside of dungeons _or_ the monster drops shards
     if (monster.lootTable ~= nil and not monster.isBoss) or Shared.contains(bones.name, 'Shard') then
     if not p._isDungeonOnlyMonster(monster) or Shared.contains(bones.name, 'Shard') then
       result = result.."'''Always Drops:'''"
       result = result.."'''Always Drops:'''"
       result = result..'\r\n{|class="wikitable"'
       result = result..'\r\n{|class="wikitable"'
Line 370: Line 536:
   end
   end


   if monster.lootTable ~= nil and not monster.isBoss then
  --Likewise, seeing the loot table is tied to the monster appearing outside of dungeons
   if not p._isDungeonOnlyMonster(monster) then
     local lootChance = monster.lootChance ~= nil and monster.lootChance or 100
     local lootChance = monster.lootChance ~= nil and monster.lootChance or 100
     local lootValue = 0
     local lootValue = 0
Line 377: Line 544:
     local avgGp = 0
     local avgGp = 0


     if monster.dropCoins ~= nil then
     if monster.dropCoins ~= nil and monster.dropCoins[2] > 1 then
       avgGp = (monster.dropCoins[1] + monster.dropCoins[2]) / 2
       avgGp = (monster.dropCoins[1] + monster.dropCoins[2] - 1) / 2
       local gpTxt = Icons.GP(monster.dropCoins[1], monster.dropCoins[2])
       local gpTxt = Icons.GP(monster.dropCoins[1], monster.dropCoins[2] - 1)
       if lootChance == 100 then
       result = result.."\r\nIn addition to loot, the monster will also drop "..gpTxt..'.'
        result = result.."\r\nIn addition to loot, the monster will also drop "..gpTxt
      else
        result = result.."\r\nIf loot is received, the monster will also drop "..gpTxt
      end
     end
     end


Line 404: Line 567:
       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 441: Line 604:
       result = result..'style="text-align:right"|'..lootChance..'.00%'
       result = result..'style="text-align:right"|'..lootChance..'.00%'
     end
     end
    --Account for GP only dropping when other loot is dropped
    avgGp = avgGp * (lootChance * .01)
     result = result..'\r\n|}'
     result = result..'\r\n|}'
     result = result..'\r\nThe loot dropped by the average kill is worth '..Icons.GP(Shared.round(lootValue, 2, 0)).." if sold"
     result = result..'\r\nThe loot dropped by the average kill is worth '..Icons.GP(Shared.round(lootValue, 2, 0)).." if sold."
     result = result..'<br/>Including GP, the average kill is worth '..Icons.GP(Shared.round(avgGp + lootValue, 2, 0))
     if avgGp > 0 then
      result = result..'<br/>Including GP, the average kill is worth '..Icons.GP(Shared.round(avgGp + lootValue, 2, 0))..'.'
    end
   end
   end


Line 458: Line 621:


   if chest == nil then
   if chest == nil then
     return "ERROR: No item named "..ChestName..' found'
     return "ERROR: No item named "..ChestName..' found[[Category:Pages with script errors]]'
   end
   end


Line 464: Line 627:


   if chest.dropTable == nil then
   if chest.dropTable == nil then
     return "ERROR: "..ChestName.." does not have a drop table"
     return "ERROR: "..ChestName.." does not have a drop table[[Category:Pages with script errors]]"
   else
   else
     local lootChance = 100
     local lootChance = 100
Line 493: Line 656:
       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 513: Line 676:
     end
     end
     result = result..'\r\n|}'
     result = result..'\r\n|}'
     result = result..'\r\nThe average value of the contents of one chest is '..Icons.GP(Shared.round(lootValue, 2, 0))
     result = result..'\r\nThe average value of the contents of one chest is '..Icons.GP(Shared.round(lootValue, 2, 0))..'.'
   end
   end


Line 523: Line 686:
   local area = Areas.getArea(areaName)
   local area = Areas.getArea(areaName)
   if area == nil then
   if area == nil then
     return "ERROR: Could not find an area named "..areaName
     return "ERROR: Could not find an area named "..areaName..'[[Category:Pages with script errors]]'
   end
   end


Line 535: Line 698:
     local monster = p.getMonsterByID(monsterID)
     local monster = p.getMonsterByID(monsterID)
     tableTxt = tableTxt..'\r\n|-\r\n|'..Icons.Icon({monster.name, type='monster'})
     tableTxt = tableTxt..'\r\n|-\r\n|'..Icons.Icon({monster.name, type='monster'})
     tableTxt = tableTxt..'||'..p.getMonsterCombatLevel(monster.name)
     tableTxt = tableTxt..'||'..p._getMonsterCombatLevel(monster)
     tableTxt = tableTxt..'||'..Shared.formatnum(p.getMonsterHP(monster.name))
     tableTxt = tableTxt..'||'..Shared.formatnum(p.getMonsterHP(monster.name))
     tableTxt = tableTxt..'||'..Shared.formatnum(p.getMonsterMaxHit(monster.name))
     tableTxt = tableTxt..'||'..Shared.formatnum(p.getMonsterMaxHit(monster.name))
Line 548: Line 711:
   local area = Areas.getArea(areaName)
   local area = Areas.getArea(areaName)
   if area == nil then
   if area == nil then
     return "ERROR: Could not find a dungeon named "..areaName
     return "ERROR: Could not find a dungeon named "..areaName..'[[Category:Pages with script errors]]'
   end
   end


Line 554: Line 717:
   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 570: Line 733:
       local name = monster.name
       local name = monster.name
       if monsterID == 51 then name = 'Spider2' end
       if monsterID == 51 then name = 'Spider2' end
       tableTxt = tableTxt..'\r\n|-\r\n|'..Icons.Icon({name, type='monster'})
       if monsterID ~= 1 then
      tableTxt = tableTxt..'||'..p.getMonsterCombatLevel(name)
        tableTxt = tableTxt..'\r\n|-\r\n|'..Icons.Icon({name, type='monster'})
      tableTxt = tableTxt..'||'..Shared.formatnum(p.getMonsterHP(name))
        tableTxt = tableTxt..'||'..p._getMonsterCombatLevel(monster)
      tableTxt = tableTxt..'||'..Shared.formatnum(p.getMonsterMaxHit(name))
        tableTxt = tableTxt..'||'..Shared.formatnum(p.getMonsterHP(name))
      tableTxt = tableTxt..'||'..p.getMonsterStyleIcon({name, nolink='true'})
        tableTxt = tableTxt..'||'..Shared.formatnum(p.getMonsterMaxHit(name))
       tableTxt = tableTxt..'||'..monsterCounts[monsterID]
        tableTxt = tableTxt..'||'..p.getMonsterStyleIcon({name, nolink='true'})
        tableTxt = tableTxt..'||'..monsterCounts[monsterID]
       else
        --Special handling for Into the Mist
        tableTxt = tableTxt..'\r\n|-\r\n|'..Icons.Icon({'Into the Mist', 'Afflicted Monster', nolink=true, img='Question'})
        tableTxt = tableTxt..'||data-sort-value="0"|'..Icons.Icon({'Into the Mist', notext=true, nolink=true, img='Question'})
        tableTxt = tableTxt..'||data-sort-value="0"|'..Icons.Icon({'Into the Mist', notext=true, nolink=true, img='Question'})
        tableTxt = tableTxt..'||data-sort-value="0"|'..Icons.Icon({'Into the Mist', notext=true, nolink=true, img='Question'})
        tableTxt = tableTxt..'||data-sort-value="0"|'..Icons.Icon({'Into the Mist', notext=true, nolink=true, img='Question'})
        tableTxt = tableTxt..'||'..monsterCounts[monsterID]
      end
       table.insert(usedMonsters, monsterID)
       table.insert(usedMonsters, monsterID)
     end
     end
Line 597: Line 770:
   local lastID = -1
   local lastID = -1
   local count = 0
   local count = 0
   for i, monsterID in pairs(area.monsters) do
   for i, monsterID in Shared.skpairs(area.monsters) do
     local monster = p.getMonsterByID(monsterID)
     if monsterID ~= lastID then
    if monster.id ~= lastID then
      local monster = p.getMonsterByID(monsterID)
       if lastMonster ~= nil then
       if lastMonster ~= nil then
         local name = lastMonster.name
         if lastID == 1 then
        if lastMonster.id == 51 then name = 'Spider2' end
          --Special handling for Afflicted Monsters
        table.insert(monsterList, Icons.Icon({name, type='monster', qty=count}))
          table.insert(monsterList, Icons.Icon({'Affliction', 'Afflicted Monster', img='Question', qty=count}))
        else
          local name = lastMonster.name
          if lastMonster.id == 51 then name = 'Spider2' end
          table.insert(monsterList, Icons.Icon({name, type='monster', qty=count}))
        end
       end
       end
       lastMonster = monster
       lastMonster = monster
Line 612: Line 790:
     end
     end
     --Make sure the final monster in the dungeon gets counted
     --Make sure the final monster in the dungeon gets counted
     if i == Shared.tableCount(monsterList) then
     if i == Shared.tableCount(area.monsters) then
       local name = lastMonster.name
       local name = lastMonster.name
       table.insert(monsterList, Icons.Icon({lastMonster.name, type='monster', qty=count}))
       table.insert(monsterList, Icons.Icon({lastMonster.name, type='monster', qty=count}))
Line 624: Line 802:
   local area = Areas.getArea(areaName)
   local area = Areas.getArea(areaName)
   if area == nil then
   if area == nil then
     return "ERROR: Could not find an area named "..areaName
     return "ERROR: Could not find an area named "..areaName..'[[Category:Pages with script errors]]'
   end
   end


Line 637: Line 815:
   local bossMonster = p.getMonsterByID(area.monsters[Shared.tableCount(area.monsters)])
   local bossMonster = p.getMonsterByID(area.monsters[Shared.tableCount(area.monsters)])
   local gpMin = bossMonster.dropCoins[1]
   local gpMin = bossMonster.dropCoins[1]
   local gpMax = bossMonster.dropCoins[2]
   local gpMax = bossMonster.dropCoins[2] - 1
   local chestID = bossMonster.lootTable[1][1]
   local chestID = bossMonster.lootTable[1][1]
   local chestQty = bossMonster.lootTable[1][3]
   local chestQty = bossMonster.lootTable[1][3]
   local theChest = Items.getItemByID(chestID)
   local theChest = Items.getItemByID(chestID)
   local result = '* '..Icons.GP(gpMin, gpMax)
   local result = ''
   result = result..'\r\n* '..Icons.Icon({theChest.name, type='item', qty=chestQty})
  if gpMin > 0 and gpMax > 0 then
    result = result..'* '..Icons.GP(gpMin, gpMax)..'\r\n'
  end
   result = result..'* '..Icons.Icon({theChest.name, type='item', qty=chestQty})
   if area.name == 'Volcanic Cave' then
   if area.name == 'Volcanic Cave' then
     result = result..'\r\n* '..Icons.Icon({'Fire Cape', type='item', qty=1})
     result = result..'\r\n* '..Icons.Icon({'Fire Cape', type='item', qty=1})
Line 655: Line 836:
   local area = Areas.getArea(areaName)
   local area = Areas.getArea(areaName)
   if area == nil then
   if area == nil then
     return "ERROR: Could not find an area named "..areaName
     return "ERROR: Could not find an area named "..areaName..'[[Category:Pages with script errors]]'
   end
   end


Line 661: Line 842:
     return p._getDungeonRewards(area)
     return p._getDungeonRewards(area)
   else
   else
     return "ERROR: "..areaName.." is not a dungeon"
     return "ERROR: "..areaName.." is not a dungeon[[Category:Pages with script errors]]"
  end
end
 
function p.getFoxyTable(frame)
  local result = 'Monster,Min GP,Max GP,Average GP'
  for i, monster in Shared.skpairs(MonsterData.Monsters) do
    if not p._isDungeonOnlyMonster(monster) then
      if monster.dropCoins ~= nil and monster.dropCoins[2] > 1 then
      local avgGp = (monster.dropCoins[1] + monster.dropCoins[2] - 1) / 2
      result = result..'<br/>'..monster.name..','..monster.dropCoins[1]..','..(monster.dropCoins[2]-1)..','..avgGp
      end
    end
   end
   end
  return result
end
function p._getMonsterAverageGP(monster)
  local result = ''
  local totalGP = 0
  if monster.bones ~= nil then
    local bones = Items.getItemByID(monster.bones)
    --Show the bones only if either the monster shows up outside of dungeons _or_ the monster drops shards
    if not p._isDungeonOnlyMonster(monster) or Shared.contains(bones.name, 'Shard') then
      totalGP = totalGP + bones.sellsFor
    end
  end
  --Likewise, seeing the loot table is tied to the monster appearing outside of dungeons
  if not p._isDungeonOnlyMonster(monster) then
    local lootChance = monster.lootChance ~= nil and monster.lootChance or 100
    local lootValue = 0
    local avgGp = 0
    if monster.dropCoins ~= nil and monster.dropCoins[2] > 1 then
      avgGp = (monster.dropCoins[1] + monster.dropCoins[2] - 1) / 2
    end
    totalGP = totalGP + avgGp
    local multiDrop = Shared.tableCount(monster.lootTable) > 1
    local totalWt = 0
    for i, row in pairs(monster.lootTable) do
      totalWt = totalWt + row[2]
    end
    for i, row in Shared.skpairs(monster.lootTable) do
      local thisItem = Items.getItemByID(row[1])
      local maxQty = row[3]
      local itemPrice = thisItem.sellsFor ~= nil and thisItem.sellsFor or 0
      --Getting the drop chance
      local dropChance = (row[2] / totalWt * lootChance)
      --Adding to the average loot value based on price & dropchance
      lootValue = lootValue + (dropChance * 0.01 * itemPrice * ((1 + maxQty) / 2))
    end
    totalGP = totalGP + lootValue
  end
  return Shared.round(totalGP, 2, 2)
end
function p.getMonsterAverageGP(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[[Category:Pages with script errors]]"
  end
  return p._getMonsterAverageGP(monster)
end
function p.getMonsterEVTable(frame)
  local result = '{| class="wikitable sortable"'
  result = result..'\r\n!Monster!!Combat Level!!Average GP'
  for i, monsterTemp in Shared.skpairs(MonsterData.Monsters) do
    local monster = Shared.clone(monsterTemp)
    monster.id = i - 1
    if not p._isDungeonOnlyMonster(monster) then
      local monsterGP = p._getMonsterAverageGP(monster)
      local combatLevel = p._getMonsterCombatLevel(monster, 'Combat Level')
      result = result..'\r\n|-\r\n|[['..monster.name..']]||'..combatLevel..'||'..monsterGP
    end
  end
  result = result..'\r\n|}'
  return result
end
function p.getSlayerTierMonsterTable(frame)
  -- Input validation
  local tier = frame.args ~= nil and frame.args[1] or frame
  local slayerTier = nil
  if tier == nil then
    return "ERROR: No tier specified[[Category:Pages with script errors]]"
  end
  if tonumber(tier) ~= nil then
    slayerTier = Constants.getSlayerTierByID(tonumber(tier))
  else
    slayerTier = Constants.getSlayerTier(tier)
  end
  if slayerTier == nil then
    return "ERROR: Invalid slayer tier[[Category:Pages with script errors]]"
  end
  -- Obtain required tier details
  local minLevel, maxLevel = slayerTier.minLevel, slayerTier.maxLevel
  if maxLevel < 0 then
    maxLevel = nil
  end
  -- Build list of monster IDs
  -- Right now hiddenMonsterIDs is empty
  local hiddenMonsterIDs = {}
  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
    return p._getMonsterTable(monsterIDs, true)
  end
end
function p.getFullMonsterTable(frame)
  local monsterIDs = {}
  for i = 0, Shared.tableCount(MonsterData.Monsters) - 1, 1 do
    table.insert(monsterIDs, i)
  end
  return p._getMonsterTable(monsterIDs, false)
end
function p._getMonsterTable(monsterIDs, excludeDungeons)
  --Making a single function for getting a table of monsters given a list of IDs.
  local hideDungeons = excludeDungeons ~= nil and excludeDungeons or false
  local tableParts = {}
  table.insert(tableParts, '{| class="wikitable sortable stickyHeader"')
  -- First header row
  table.insert(tableParts, '\r\n|- class="headerRow-0"\r\n! colspan="5" | !! colspan="4" |Offensive Stats !! colspan="3" |Evasion Rating !! colspan="4" |')
  -- Second header row
  table.insert(tableParts, '\r\n|- class="headerRow-1"\r\n!Monster !!Name !!ID !!Combat Level ')
  table.insert(tableParts, '!!style="padding:0 1em 0 0"|' .. Icons.Icon({'Hitpoints', type='skill'}))
  table.insert(tableParts, '!!Attack Type !!Attack Speed (s) !!Max Hit !!Accuracy ')
  table.insert(tableParts, '!!style="padding:0 1em 0 0"|' .. Icons.Icon({'Defence', type='skill', notext=true}))
  table.insert(tableParts, '!!style="padding:0 1em 0 0"|' .. Icons.Icon({'Ranged', type='skill', notext=true}))
  table.insert(tableParts, '!!style="padding:0 1em 0 0"|' .. Icons.Icon({'Magic', type='skill', notext=true}))
  table.insert(tableParts, '!!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 = Icons.GP(gpRange[1])
    else
      gpTxt = Icons.GP(gpRange[1], 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
    table.insert(tableParts, '\r\n|-\r\n|style="text-align: left;" |' .. Icons.Icon({monster.name, type='monster', size=50, notext=true}))
    table.insert(tableParts, '\r\n|style="text-align:left" |[[' .. monster.name .. ']]')
    table.insert(tableParts, '\r\n|style="text-align:right" |' .. monsterID)
    table.insert(tableParts, '\r\n|style="text-align:right" data-sort-value="' .. cmbLevel .. '" |' .. Shared.formatnum(cmbLevel))
    table.insert(tableParts, '\r\n|style="text-align:right" data-sort-value="' .. monster.hitpoints .. '" |' .. Shared.formatnum(p._getMonsterHP(monster)))
    table.insert(tableParts, '\r\n|style="text-align:right;white-space:nowrap" |' .. p._getMonsterStyleIcon({monster, nolink='true'}))
    table.insert(tableParts, '\r\n|style="text-align:right" data-sort-value="' .. atkSpeed .. '" |' .. Shared.round(atkSpeed, 1, 1))
    table.insert(tableParts, '\r\n|style="text-align:right" data-sort-value="' .. maxHit .. '" |' .. Shared.formatnum(maxHit))
    table.insert(tableParts, '\r\n|style="text-align:right" data-sort-value="' .. accR .. '" |' .. Shared.formatnum(accR))
    table.insert(tableParts, '\r\n|style="text-align:right" data-sort-value="' .. evaR[1] .. '" |' .. Shared.formatnum(evaR[1]))
    table.insert(tableParts, '\r\n|style="text-align:right" data-sort-value="' .. evaR[2] .. '" |' .. Shared.formatnum(evaR[2]))
    table.insert(tableParts, '\r\n|style="text-align:right" data-sort-value="' .. evaR[3] .. '" |' .. Shared.formatnum(evaR[3]))
    table.insert(tableParts, '\r\n|style="text-align:right" data-sort-value="' .. lootChance .. '" |' .. lootChance .. '%')
    table.insert(tableParts, '\r\n|style="text-align:right" data-sort-value="' .. (gpRange[1] + gpRange[2]) / 2 .. '" |' .. gpTxt)
    table.insert(tableParts, '\r\n|style="text-align:center" |' .. boneTxt)
    table.insert(tableParts, '\r\n|style="text-align:right;white-space:nowrap" |' .. p._getMonsterAreas(monster, hideDungeons))
  end
  table.insert(tableParts, '\r\n|}')
  return table.concat(tableParts)
end
end


return p
return p