Module:Monsters: Difference between revisions

Add column name for average healing
(Added an additional check to the activeBuffs check to fix an issue with specifically Aeris's Cyclone attack.)
(Add column name for average healing)
(125 intermediate revisions by 9 users not shown)
Line 1: Line 1:
local p = {}
local p = {}
local MonsterData = mw.loadData('Module:Monsters/data')


local Constants = require('Module:Constants')
local Constants = require('Module:Constants')
local Shared = require('Module:Shared')
local GameData = require('Module:GameData')
local Areas = require('Module:CombatAreas')
local Areas = require('Module:CombatAreas')
local Magic = require('Module:Magic')
local Magic = require('Module:Magic')
local Shared = require('Module:Shared')
local Icons = require('Module:Icons')
local Icons = require('Module:Icons')
local Items = require('Module:Items')
local Items = require('Module:Items')


function p.getMonster(name)
function p.getMonster(name)
  local result = nil
if name == 'Earth Golem (AoD)' then
  if name == 'Spider (lv. 51)' or name == 'Spider' then
-- Special case for ambiguous monster name
    return p.getMonsterByID(50)
return p.getMonsterByID('melvorAoD:EarthGolem')
  elseif name == 'Spider (lv. 52)' or name == 'Spider2' then
else
    return p.getMonsterByID(51)
    return GameData.getEntityByName('monsters', name)
  end
 
  for i, monster in pairs(MonsterData.Monsters) do
    if(monster.name == name) then
      result = Shared.clone(monster)
      --Make sure every monster has an ID, and account for the 1-based indexing of Lua
      result.id = i - 1
      break
     end
     end
  end
  return result
end
end


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


function p.getSpecialAttack(name)
function p.getMonsterName(monster)
  local result = nil
if monster.id == 'melvorAoD:EarthGolem' then
-- Special case for ambiguous monster name
return 'Earth Golem (AoD)'
else
return monster.name
end
end


  for i, attack in pairs(MonsterData.SpecialAttacks) do
function p.getPassive(name)
    if(attack.name == name) then
    return GameData.getEntityByName('combatPassives', name)
      result = Shared.clone(attack)
      --Make sure every attack has an ID, and account for the 1-based indexing of Lua
      result.id = i - 1
      break
    end
  end
  return result
end
end


function p.getSpecialAttackByID(ID)
function p.getPassiveByID(ID)
  return MonsterData.SpecialAttacks[ID + 1]
    return GameData.getEntityByID('combatPassives', ID)
end
end


function p.getPassive(name)
-- Given a list of monster IDs, calls statFunc with each monster and returns
  local result = nil
-- the lowest & highest values
function p.getLowHighStat(idList, statFunc)
local lowVal, highVal = nil, nil
for i, monID in ipairs(idList) do
local monster = p.getMonsterByID(monID)
local statVal = statFunc(monster)
if lowVal == nil or statVal < lowVal then lowVal = statVal end
if highVal == nil or statVal > highVal then highVal = statVal end
end
return lowVal, highVal
end


  for i, passive in pairs(MonsterData.Passives) do
function p._getMonsterStat(monster, statName)
    if passive.name == name then
if statName == 'Barrier' then
      result = Shared.clone(passive)
return p._getMonsterBarrier(monster)
      --Make sure every passive has an ID, and account for the 1-based indexing of Lua
elseif statName == 'HP' then
      result.id = i - 1
return p._getMonsterHP(monster)
      break
elseif statName == 'maxHit' then
    end
return p._getMonsterMaxHit(monster)
  end
elseif statName == 'accuracyRating' then
  return result
return p._getMonsterAR(monster)
end
elseif statName == 'meleeEvasionRating' then
return p._getMonsterER(monster, 'Melee')
elseif statName == 'rangedEvasionRating' then
return p._getMonsterER(monster, 'Ranged')
elseif statName == 'magicEvasionRating' then
return p._getMonsterER(monster, 'Magic')
elseif statName == 'damageReduction' then
return p.getEquipmentStat(monster, 'damageReduction')
elseif statName == 'drReduction' then
return p._getMonsterDrReduction(monster)
end


function p.getPassiveByID(ID)
return monster[statName]
  return MonsterData.Passives[ID + 1]
end
end


function p.getMonsterStat(frame)
function p.getMonsterStat(frame)
  local MonsterName = frame.args ~= nil and frame.args[1] or frame[1]
local MonsterName = frame.args ~= nil and frame.args[1] or frame[1]
  local StatName = frame.args ~= nil and frame.args[2] or frame[2]
local StatName = frame.args ~= nil and frame.args[2] or frame[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[[Category:Pages with script errors]]"
return Shared.printError('No monster with that name found')
  end
end
 
  if StatName == 'HP' then
    return p._getMonsterHP(monster)
  elseif StatName == 'maxHit' then
    return p._getMonsterMaxHit(monster)
  elseif StatName == 'accuracyRating' then
    return p._getMonsterAR(monster)
  elseif StatName == 'meleeEvasionRating' then
    return p._getMonsterER({monster, 'Melee'})
  elseif StatName == 'rangedEvasionRating' then
    return p._getMonsterER({monster, 'Ranged'})
  elseif StatName == 'magicEvasionRating' then
    return p._getMonsterER({monster, 'Magic'})
  end


  return monster[StatName]
return p._getMonsterStat(monster, StatName)
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 monster = args[1]
local monster = args[1]
  local notext = args.notext
local notext = args.notext
  local nolink = args.nolink
local nolink = args.nolink


  local iconText = ''
local iconText = ''
  if Constants.getCombatStyleName(monster.attackType) == 'Melee' then
if monster.attackType == 'melee' then
    iconText = Icons.Icon({'Melee', notext=notext, nolink=nolink})
iconText = Icons.Icon({'Melee', notext=notext, nolink=nolink})
  elseif Constants.getCombatStyleName(monster.attackType) == 'Ranged' then
elseif monster.attackType == 'ranged' then
    iconText = Icons.Icon({'Ranged', type='skill', notext=notext, nolink=nolink})
iconText = Icons.Icon({'Ranged', type='skill', notext=notext, nolink=nolink})
  elseif Constants.getCombatStyleName(monster.attackType) == 'Magic' then
elseif 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
elseif monster.attackType == 'random' then
iconText = Icons.Icon({p.getMonsterName(monster), notext=notext, nolink=nolink, img='Question'})
end


  return iconText
return iconText
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 MonsterName = args[1]
  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[[Category:Pages with script errors]]"
return Shared.printError('No monster with that name found')
  end
end


  args[1] = monster
args[1] = monster
  return p._getMonsterStyleIcon(args)
return p._getMonsterStyleIcon(args)
end
end


function p._getMonsterHP(monster)
function p._getMonsterHP(monster)
  return monster.hitpoints * 10
return 10 * p._getMonsterLevel(monster, 'Hitpoints')
end
 
function p._getMonsterBarrier(monster)
--Monster Barrier is a percentage of its max health
local barPercent = 0
if monster.barrierPercent ~= nil then
barPercent = monster.barrierPercent
end
return p._getMonsterHP(monster) * barPercent * 0.01
end
 
function p.getMonsterEffectiveHP(frame)
local MonsterName = frame.args ~= nil and frame.args[1] or frame
local monster = p.getMonster(MonsterName)
if monster ~= nil then
return math.floor((p._getMonsterHP(monster)/(1 - p._getMonsterStat(monster, 'damageReduction')/100)) + 0.5)
else
return Shared.printError('No monster with that name found')
end
end
 
function p.getMonsterEffectiveBarrier(frame)
local MonsterName = frame.args ~= nil and frame.args[1] or frame
local monster = p.getMonster(MonsterName)
if monster ~= nil then
return math.floor((p._getMonsterBarrier(monster)/(1 - p._getMonsterStat(monster, 'damageReduction')/100)) + 0.5)
else
return Shared.printError('No monster with that name found')
end
end
 
function p.getMonsterBarrier(frame)
local MonsterName = frame.args ~= nil and frame.args[1] or frame
local monster = p.getMonster(MonsterName)
if monster ~= nil then
return p._getMonsterBarrier(monster)
else
return Shared.printError('No monster with that name found')
end
end
end


function p.getMonsterHP(frame)
function p.getMonsterHP(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 p._getMonsterHP(monster)
return p._getMonsterHP(monster)
  else
else
    return "ERROR: No monster with that name found[[Category:Pages with script errors]]"
return Shared.printError('No monster with that name found')
  end
end
end
 
function p._getMonsterLevel(monster, skillName)
local result = 0
if monster.levels[skillName] ~= nil then
result = monster.levels[skillName]
end
return result
end
 
function p.getMonsterLevel(frame)
local MonsterName = frame.args ~= nil and frame.args[1] or frame[1]
local SkillName = frame.args ~= nil and frame.args[2] or frame[2]
local monster = p.getMonster(MonsterName)
 
if monster == nil then
return Shared.printError('No monster with that name found')
end
 
return p._getMonsterLevel(monster, SkillName)
end
 
function p.getEquipmentStat(monster, statName)
if monster.equipmentStats == nil then
return 0
else
    return monster.equipmentStats[statName] or 0
    end
end
 
function p.calculateStandardStat(effectiveLevel, bonus)
--Based on calculateStandardStat in Characters.js
return (effectiveLevel + 9) * (bonus + 64)
end
 
function p.calculateStandardMaxHit(baseLevel, strengthBonus)
--Based on calculateStandardMaxHit in Characters.js
local effectiveLevel = baseLevel + 9
return math.floor(10 * (1.3 + effectiveLevel / 10 + strengthBonus / 80 + effectiveLevel * strengthBonus / 640))
end
end


function p._getMonsterAttackSpeed(monster)
function p._getMonsterAttackSpeed(monster)
  return monster.attackSpeed / 1000
return p.getEquipmentStat(monster, 'attackSpeed') / 1000
end
end


function p.getMonsterAttackSpeed(frame)
function p.getMonsterAttackSpeed(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 p._getMonsterAttackSpeed(monster)
return p._getMonsterAttackSpeed(monster)
  else
else
    return "ERROR: No monster with that name found[[Category:Pages with script errors]]"
return Shared.printError('No monster with that name found')
  end
end
end
end


function p._getMonsterCombatLevel(monster)
function p._getMonsterCombatLevel(monster)
  local base = 0.25 * (monster.defenceLevel + monster.hitpoints)
local base = 0.25 * (p._getMonsterLevel(monster, 'Defence') + p._getMonsterLevel(monster, 'Hitpoints'))
  local melee = 0.325 * (monster.attackLevel + monster.strengthLevel)
local melee = 0.325 * (p._getMonsterLevel(monster, 'Attack') + p._getMonsterLevel(monster, 'Strength'))
  local range = 0.325 * (1.5 * monster.rangedLevel)
local range = 0.325 * (1.5 * p._getMonsterLevel(monster, 'Ranged'))
  local magic = 0.325 * (1.5 * monster.magicLevel)
local magic = 0.325 * (1.5 * p._getMonsterLevel(monster, 'Magic'))
  if melee > range and melee > magic then
return math.floor(base + math.max(melee, range, magic))
    return math.floor(base + melee)
  elseif range > magic then
    return math.floor(base + range)
  else
    return math.floor(base + magic)
  end
end
end


function p.getMonsterCombatLevel(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[[Category:Pages with script errors]]"
return Shared.printError('No monster with that name found')
  end
end


  return p._getMonsterCombatLevel(monster)
return p._getMonsterCombatLevel(monster)
end
end


function p._getMonsterAR(monster)
function p._getMonsterAR(monster)
  local effAttLvl = 0
local baseLevel = 0
  local attBonus = 0
local bonus = 0
  if Constants.getCombatStyleName(monster.attackType) == 'Melee' then
if monster.attackType == 'melee' then
    effAttLvl = monster.attackLevel + 9
baseLevel = p._getMonsterLevel(monster, 'Attack')
    attBonus = monster.attackBonus + 64
bonus = p.getEquipmentStat(monster, 'stabAttackBonus')
  elseif Constants.getCombatStyleName(monster.attackType) == 'Ranged' then
elseif monster.attackType == 'ranged' then
    effAttLvl = monster.rangedLevel + 9
baseLevel = p._getMonsterLevel(monster, 'Ranged')
    attBonus = monster.attackBonusRanged + 64
bonus = p.getEquipmentStat(monster, 'rangedAttackBonus')
  elseif Constants.getCombatStyleName(monster.attackType) == 'Magic' then
elseif monster.attackType == 'magic' then
    effAttLvl = monster.magicLevel + 9
baseLevel = p._getMonsterLevel(monster, 'Magic')
    attBonus = monster.attackBonusMagic + 64
bonus = p.getEquipmentStat(monster, 'magicAttackBonus')
  else
elseif monster.attackType == 'random' then
    return "ERROR: This monster has an invalid attack type somehow[[Category:Pages with script errors]]"
--Bane has the same AR with every attack type so being lazy and just showing the one.
  end
baseLevel = p._getMonsterLevel(monster, 'Attack')
bonus = p.getEquipmentStat(monster, 'stabAttackBonus')
else
return Shared.printError('This monster has an invalid attack type somehow')
end


  return effAttLvl * attBonus
return p.calculateStandardStat(baseLevel, bonus)
end
end


function p.getMonsterAR(frame)
function p.getMonsterAR(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[[Category:Pages with script errors]]"
return Shared.printError('No monster with that name found')
  end
end


  return p._getMonsterAR(monster)
return p._getMonsterAR(monster)
end
end


function p._getMonsterER(frame)
function p._getMonsterER(monster, style)
  local args = frame.args ~= nil and frame.args or frame
local baseLevel= 0
  local monster = args[1]
local bonus = 0
  local style = args[2]


  local effDefLvl = 0
if style == "Melee" then
  local defBonus = 0
baseLevel = p._getMonsterLevel(monster, 'Defence')
  if style == "Melee" then
bonus = p.getEquipmentStat(monster, 'meleeDefenceBonus')
    effDefLvl = monster.defenceLevel + 9
elseif style == "Ranged" then
    defBonus = monster.defenceBonus + 64
baseLevel = p._getMonsterLevel(monster, 'Defence')
  elseif style == "Ranged" then
bonus = p.getEquipmentStat(monster, 'rangedDefenceBonus')
    effDefLvl = monster.defenceLevel + 9
elseif style == "Magic" then
    defBonus = monster.defenceBonusRanged + 64
baseLevel = math.floor(p._getMonsterLevel(monster, 'Magic') * 0.7 + p._getMonsterLevel(monster, 'Defence') * 0.3)
  elseif style == "Magic" then
bonus = p.getEquipmentStat(monster, 'magicDefenceBonus')
    effDefLvl = math.floor(monster.magicLevel * 0.7) + math.floor(monster.defenceLevel * 0.3) + 9
else
    defBonus = monster.defenceBonusMagic + 64
return Shared.printError('Must choose Melee, Ranged, or Magic')
  else
end
    return "ERROR: Must choose Melee, Ranged, or Magic[[Category:Pages with script errors]]"
 
  end
return p.calculateStandardStat(baseLevel, bonus)
  return effDefLvl * defBonus
end
end


function p.getMonsterER(frame)
function p.getMonsterER(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 MonsterName = args[1]
  local style = args[2]
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[[Category:Pages with script errors]]"
return Shared.printError('No monster with that name found')
  end
end


  return p._getMonsterER({monster, style})
return p._getMonsterER(monster, style)
end
 
-- Determines if the monster is capable of dropping bones, and returns the bones
-- item if so, or nil otherwise
function p._getMonsterBones(monster)
if monster.bones ~= nil then
local boneItem = Items.getItemByID(monster.bones.itemID)
        local boneObj = { ["item"] = boneItem, ["quantity"] = monster.bones.quantity }
if boneItem.prayerPoints == nil then
-- Assume bones without prayer points are shards (from God dungeons),
-- and drop unconditionally
return boneObj
elseif not monster.isBoss and not p._isDungeonOnlyMonster(monster) then
-- Otherwise, bones drop when the monster isn't dungeon exclusive
return boneObj
end
end
end
end


function p._isDungeonOnlyMonster(monster)
function p._isDungeonOnlyMonster(monster)
  local areaList = Areas.getMonsterAreas(monster.id)
local areaList = Areas.getMonsterAreas(monster.id)
  local dunCount = 0
local inDungeon = false
  local nonDunCount = 0


  for i, area in Shared.skpairs(areaList) do
for i, area in ipairs(areaList) do
    if area.type == 'dungeon' then
if area.type == 'dungeon' then
      dunCount = dunCount + 1
inDungeon = true
    else
else
      nonDunCount = nonDunCount + 1
return false
    end
end
  end
end
  return dunCount > 0 and nonDunCount == 0
return inDungeon
end
end


function p.isDungeonOnlyMonster(frame)
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[[Category:Pages with script errors]]"
return Shared.printError('No monster with name ' .. monsterName .. ' found')
  end
end


  return p._isDungeonOnlyMonster(monster)
return p._isDungeonOnlyMonster(monster)
end
end


function p._getMonsterAreas(monster, excludeDungeons)
function p._getMonsterAreas(monster, excludeDungeons, includeEffects)
  local result = ''
if includeEffects == nil then includeEffects = false end
  local hideDungeons = excludeDungeons ~= nil and excludeDungeons or false
local resultPart = {}
  local areaList = Areas.getMonsterAreas(monster.id)
local hideDungeons = excludeDungeons ~= nil and excludeDungeons or false
  for i, area in pairs(areaList) do
local areaList = Areas.getMonsterAreas(monster.id)
    if area.type ~= 'dungeon' or not hideDungeons then
for i, area in ipairs(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})
local imgType = (area.type == 'slayerArea' and 'combatArea') or area.type
    end
local txt = Icons.Icon({area.name, type = imgType})
  end
if area.type == 'slayerArea' then
  return result
local areaDescrip = Areas._getAreaStat(area, 'areaEffectDesc')
if areaDescrip ~= 'None' then
txt = txt.." - ''"..areaDescrip.."''"
end
end
table.insert(resultPart, txt)
end
end
return table.concat(resultPart, '<br/>')
end
end


function p.getMonsterAreas(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 hideDungeons = frame.args ~= nil and frame.args[2] or nil
  local monster = p.getMonster(MonsterName)
local includeEffects = frame.args ~= nil and frame.args[3] or true
local monster = p.getMonster(monsterName)
 
if monster == nil then
return Shared.printError('No monster with name ' .. monsterName .. ' found')
end


  if monster == nil then
return p._getMonsterAreas(monster, hideDungeons, includeEffects)
    return "ERROR: No monster with name "..monsterName.." found[[Category:Pages with script errors]]"
end
  end


  return p._getMonsterAreas(monster, hideDungeons)
function p.getSpecAttackMaxHit(specAttack, normalMaxHit, monster)
local bestHit = 0
for i, dmg in pairs(specAttack.damage) do
local thisHit = 0
if dmg.damageType == 'Normal' then
--Account for special attacks that include a normal attack hit
thisHit = normalMaxHit
if dmg.amplitude ~= nil then
thisHit = thisHit * (dmg.amplitude / 100)
end
elseif dmg.maxRoll == 'Fixed' then
thisHit = dmg.maxPercent * 10
elseif dmg.maxRoll == 'MaxHit' then
if dmg.character == 'Target' then
--Confusion applied damage based on the player's max hit. Gonna just ignore that one
thisHit = 0
else
thisHit = dmg.maxPercent * normalMaxHit * 0.01
end
elseif Shared.contains(dmg.maxRoll, "Fixed100") then
--Handles attacks that are doubled when conditions are met like Trogark's double damage if the player is burning
thisHit = dmg.maxPercent * 20
elseif dmg.maxRoll == 'MaxHitScaledByHP2x' then
thisHit = normalMaxHit * 2
elseif dmg.maxRoll == 'PoisonMax35' then
thisHit = normalMaxHit * 1.35
elseif dmg.maxRoll == "MaxHitDR" then
local monsterDR = 0
if monster ~= nil then
monsterDR = p._getMonsterStat(monster, 'damageReduction')
end
thisHit = normalMaxHit * dmg.maxPercent * 0.01 * (1 + monsterDR * 0.01)
elseif Shared.contains({'Bleeding', 'Poisoned'}, dmg.maxRoll) then
-- TODO: This is limited in that there is no verification that bleed/poison
-- can be applied to the target, it is assumed that it can and so this applies
thisHit = thisHit + dmg.maxPercent * 10
end
if thisHit > bestHit then
bestHit = thisHit
end
end
return bestHit
end
 
function p.canSpecAttackApplyEffect(specAttack, effectType)
local effectKeys = { 'prehitEffects', 'onhitEffects' }
for i, effectKey in ipairs(effectKeys) do
if type(specAttack[effectKey]) == 'table' then
for j, effect in pairs(specAttack[effectKey]) do
if effect.type == effectType or p.canModifiersApplyEffect(effect.modifiers, effectType) then
return true
end
end
end
end
return false
end
 
function p.canModifiersApplyEffect(modifiers, effectType)
-- List of modifiers which can result in the application of status effects
local statusModsAll = {
["Stun"] = { 'increasedGlobalStunChance', 'increasedMeleeStunChance' },
["Sleep"] = { 'increasedGlobalSleepChance' },
["Poison"] = { 'increasedChanceToApplyPoison' },
["Slow"] = { 'increased15SlowStunChance2Turns', 'increased30Slow5TurnsChance' }
}
 
local statusMods = statusModsAll[effectType]
if statusMods ~= nil and type(modifiers) == 'table' then
for modName, modMagnitude in pairs(modifiers) do
if Shared.contains(statusMods, modName) then
return true
end
end
end
return false
end
end


function p._getMonsterMaxHit(monster, doStuns)
function p._getMonsterMaxHit(monster, doStuns)
  -- 2021-06-11 Adjusted for v0.20 stun/sleep changes, where damage multiplier now applies
-- 2021-06-11 Adjusted for v0.20 stun/sleep changes, where damage multiplier now applies
  -- to all enemy attacks if stun/sleep is present on at least one special attack
-- to all enemy attacks if stun/sleep is present on at least one special attack
  if doStuns == nil then
if doStuns == nil then
    doStuns = true
doStuns = true
  elseif type(doStuns) == 'string' then
elseif type(doStuns) == 'string' then
    doStuns = string.upper(doStuns) == 'TRUE'
doStuns = string.upper(doStuns) == 'TRUE'
  end
end
 
-- Damage adjustments are defined as follows:
-- multiplier - Damage from modifier 'increasedDamageTaken' & additional damage while
-- stunned, asleep, or poisoned. Defined by in-game function
-- getDamageModifiers(). Applies after other percentage of flat adjustments.
-- percent - Percentage adjustments to the max hit. Applies before flat & multiplier
-- adjustments.
-- flat - Flat adjustments to the max hit. Applies after percent adjustments, and
-- after multiplier adjustments.
local dmgAdjust = { ["percent"] = 100, ["flat"] = 0, ["multiplier"] = 100 }
-- Check passives & effects that apply pre or on hit for damage modifiers
local dmgMods = {
-- List of modifiers which affect damage dealt, and whether they are percentage or flat adjustments
["increasedDamageTaken"] = { type = 'multiplier', mult = 1 },
["increasedMaxHitPercent"] = { type = 'percent', mult = 1 },
["increasedMeleeMaxHit"] = { type = 'percent', mult = 1 },
["increasedRangedMaxHit"] = { type = 'percent', mult = 1 },
["increasedMagicMaxHit"] = { type = 'percent', mult = 1 },
["increasedMaxHitFlat"] = { type = 'flat', mult = 10 },
["increasedMeleeMaxHitFlat"] = { type = 'flat', mult = 10 },
["increasedRangedMaxHitFlat"] = { type = 'flat', mult = 10 },
["increasedMagicMaxHitFlat"] = { type = 'flat', mult = 10 },
-- Rage: +2% max hit per stack, maximum of 10 stacks
["increasedRage"] = { type = 'percent', mult = 1, magnitude = 2, maxStacks = 10 },
-- Dark Blade: +1% max hit per successful hit, maximum of 30 stacks
["increasedChanceDarkBlade"] = { type = 'percent', mult = 1, magnitude = 1, maxStacks = 30 },
-- Growing Madness/Moment in Time/Reign Over Time: +2% max hit per stack, maximum of 25 stacks
["growingMadnessPassive"] = { type = 'percent', mult = 1, magnitude = 2, maxStacks = 25 },
["momentInTimePassive"] = { type = 'percent', mult = 1, magnitude = 2, maxStacks = 25 },
["reignOverTimePassive"] = { type = 'percent', mult = 1, magnitude = 2, maxStacks = 25 }
}
local effectKeys = { 'prehitEffects', 'onhitEffects' }
local dmgStatuses = {
-- List of status effects which can affect damage dealt
["Stun"] = { type = 'multiplier', magnitude = 30 },
["Sleep"] = { type = 'multiplier', magnitude = 20 }
}
local canApplyStatus = {}
-- Initialize table
for statusName, def in pairs(dmgStatuses) do
canApplyStatus[statusName] = false
end
 
local adjustForMod = function(mod, modMagnitude, effect)
local magnitude = mod.magnitude or modMagnitude
local maxStacks = mod.maxStacks or (effect ~= nil and effect.maxStacks) or 1
dmgAdjust[mod.type] = dmgAdjust[mod.type] + magnitude * mod.mult * maxStacks
end
 
local adjustForCurse = function(curseID, effect)
local curse = Magic.getSpellByID(curseID, 'curse')
if type(curse) == 'table' and type(curse.targetModifiers) == 'table' then
for modName, modMagnitude in pairs(curse.targetModifiers) do
local mod = dmgMods[modName]
if mod ~= nil then
-- The modifier is one which affects damage dealt
adjustForMod(mod, modMagnitude, effect)
end
end
end
end


  local normalChance = 100
-- Check monster passives for modifiers which affect damage dealt, and alo if any modifiers
  local specialMaxHit = 0
-- present can apply stun or sleep
  local normalMaxHit = p._getMonsterBaseMaxHit(monster)
if monster ~= nil and type(monster.passives) ~= nil then
  local hasActiveBuffSpec = false
for i, passiveID in ipairs(monster.passives) do
  local damageMultiplier = 1
local passive = p.getPassiveByID(passiveID)
  if monster.hasSpecialAttack then
if passive ~= nil and type(passive.modifiers) == 'table' then
    local canStun = false
for modName, modMagnitude in pairs(passive.modifiers) do
    local canSleep = false
local mod = dmgMods[modName]
    for i, specID in pairs(monster.specialAttackID) do
if modName == 'applyRandomCurseOnSpawn' then
      local specAttack = p.getSpecialAttackByID(specID)
-- Special case in which the enemy can apply a random curse. Currently
      if monster.overrideSpecialChances ~= nil then
-- Anguish III is the curse with the highest +% damage taken, so use this.
        normalChance = normalChance - monster.overrideSpecialChances[i]
adjustForCurse('melvorF:AnguishIII')
      else
elseif mod ~= nil then
        normalChance = normalChance - specAttack.chance
-- The modifier is one which affects damage dealt
      end
adjustForMod(mod, modMagnitude)
      local thisMax = 0
end
      if specAttack.setDamage ~= nil then
end
        thisMax = specAttack.setDamage * 10
-- Check for application of relevant status effects
      else
if doStuns then
        thisMax = normalMaxHit
for statusName, statusDef in pairs(dmgStatuses) do
      end
if not canApplyStatus[statusName] and p.canModifiersApplyEffect(passive.modifiers, statusName) then
      if specAttack.canStun ~= nil and specAttack.canStun then canStun = true end
canApplyStatus[statusName] = true
      if specAttack.canSleep ~= nil and specAttack.canSleep then canSleep = true end
end
end
end
end
end
end


      if thisMax > specialMaxHit then specialMaxHit = thisMax end
local normalChance = 100
      if specAttack.activeBuffs and specAttack.activeBuffTurns ~= nil and specAttack.activeBuffTurns > 0 then  
local specialMaxHit = 0
        hasActiveBuffSpec = true  
local normalMaxHit = p._getMonsterBaseMaxHit(monster)
      end
local hasActiveBuffSpec = false
    end
if monster.specialAttacks ~= nil then
for i, specAttackID in pairs(monster.specialAttacks) do
            local specAttack = GameData.getEntityByID('attacks', specAttackID)
for i, effectKey in ipairs(effectKeys) do
if type(specAttack[effectKey]) == 'table' then
for j, effect in ipairs(specAttack[effectKey]) do
local countsOnPlayer = (effect.countsOn == nil or effect.countsOn == 'Attacker')
if countsOnPlayer then
-- Check for pre or on hit effects for modifiers which affect damage dealt
if type(effect.modifiers) == 'table' then
for modName, modMagnitude in pairs(effect.modifiers) do
local mod = dmgMods[modName]
if mod ~= nil then
-- The modifier is one which affects damage dealt
adjustForMod(mod, modMagnitude, effect)
end
end
end
-- Check for curses which may cause the player to incur additional damage
if effect.effectType == 'Curse' then
-- If isRandom is true then a random curse is selected. Currently
-- Anguish III is the curse with the highest +% damage taken, so
-- use this.
local curseID = (effect.isRandom and 'melvorF:AnguishIII') or effect.curse
if curseID ~= nil then
adjustForCurse(curseID, effect)
end
end
end
end
end
end
 
if monster.overrideSpecialChances ~= nil then
normalChance = normalChance - monster.overrideSpecialChances[i]
else
normalChance = normalChance - specAttack.defaultChance
end
-- Check for application of relevant status effects
if doStuns then
for statusName, statusDef in pairs(dmgStatuses) do
if not canApplyStatus[statusName] and p.canSpecAttackApplyEffect(specAttack, statusName) then
canApplyStatus[statusName] = true
end
end
end
 
local thisMax = p.getSpecAttackMaxHit(specAttack, normalMaxHit, monster)
if thisMax > specialMaxHit then specialMaxHit = thisMax end
if Shared.contains(string.upper(specAttack.description), 'NORMAL ATTACK INSTEAD') then  
hasActiveBuffSpec = true  
end
end


    if canStun and doStuns then damageMultiplier = damageMultiplier * 1.3 end
if doStuns then
    if canSleep and doStuns then damageMultiplier = damageMultiplier * 1.2 end
for statusName, statusDef in pairs(dmgStatuses) do
  end
if canApplyStatus[statusName] then
  --Ensure that if the monster never does a normal attack, the normal max hit is irrelevant
local adjType = statusDef.type
  if normalChance == 0 and not hasActiveBuffSpec then normalMaxHit = 0 end
dmgAdjust[adjType] = dmgAdjust[adjType] + statusDef.magnitude
  return math.floor(math.max(specialMaxHit, normalMaxHit) * damageMultiplier)
end
end
end
end
--Ensure that if the monster never does a normal attack, the normal max hit is irrelevant
if normalChance == 0 and not hasActiveBuffSpec then normalMaxHit = 0 end
local maxHit = math.floor(math.max(specialMaxHit, normalMaxHit) * dmgAdjust.percent / 100) + dmgAdjust.flat
return math.floor(maxHit * dmgAdjust.multiplier / 100)
end
end


function p.getMonsterMaxHit(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 doStuns = frame.args ~= nil and frame.args[2] or true
local doStuns = frame.args ~= nil and frame.args[2] or true
  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[[Category:Pages with script errors]]"
return Shared.printError('No monster with that name found')
  end
end


  return p._getMonsterMaxHit(monster, doStuns)
return p._getMonsterMaxHit(monster, doStuns)
end
end


function p._getMonsterBaseMaxHit(monster)
function p._getMonsterBaseMaxHit(monster)
  local effStrLvl = 0
--8/27/21 - Now references p.calculateStandardMaxHit for Melee & Ranged
  local strBonus = 0
local result = 0
  if Constants.getCombatStyleName(monster.attackType) == 'Melee' then
local baseLevel = 0
    effStrLvl = monster.strengthLevel + 9
local bonus = 0
    strBonus = monster.strengthBonus
if monster.attackType == 'melee' then
  elseif Constants.getCombatStyleName(monster.attackType) == 'Ranged' then
baseLevel = p._getMonsterLevel(monster, 'Strength')
    effStrLvl = monster.rangedLevel + 9
bonus = p.getEquipmentStat(monster, 'meleeStrengthBonus')
    strBonus = monster.strengthBonusRanged
result = p.calculateStandardMaxHit(baseLevel, bonus)
  elseif Constants.getCombatStyleName(monster.attackType) == 'Magic' then
elseif monster.attackType == 'ranged' then
    local mSpell = nil
baseLevel = p._getMonsterLevel(monster, 'Ranged')
    if monster.selectedSpell ~= nil then mSpell = Magic.getSpellByID('Spells', monster.selectedSpell) end
bonus = p.getEquipmentStat(monster, 'rangedStrengthBonus')
    if mSpell == nil then
result = p.calculateStandardMaxHit(baseLevel, bonus)
      return math.floor(10 * (monster.setMaxHit + (monster.setMaxHit * monster.damageBonusMagic / 100)))
elseif monster.attackType == 'magic' then
    else
        if monster.selectedSpell == nil then
      return math.floor(10 * (mSpell.maxHit + (mSpell.maxHit * monster.damageBonusMagic / 100)))
            result = 0
    end
        else
  else
            local mSpell = Magic.getSpellByID(monster.selectedSpell, 'standard')
    return "ERROR: This monster has an invalid attack type somehow[[Category:Pages with script errors]]"
            if mSpell == nil then
  end
                result = 0
            else
                baseLevel = p._getMonsterLevel(monster, 'Magic')
                bonus = p.getEquipmentStat(monster, 'magicDamageBonus')
                result = math.floor(10 * mSpell.maxHit * (1 + bonus / 100) * (1 + (baseLevel + 1) / 200))
            end
        end
elseif monster.attackType == 'random' then
local hitArray = {}
local iconText = Icons.Icon({'Melee', notext=true})
baseLevel = p._getMonsterLevel(monster, 'Strength')
bonus = p.getEquipmentStat(monster, 'meleeStrengthBonus')
table.insert(hitArray, p.calculateStandardMaxHit(baseLevel, bonus))
iconText = Icons.Icon({'Ranged', type='skill', notext=true})
baseLevel = p._getMonsterLevel(monster, 'Ranged')
bonus = p.getEquipmentStat(monster, 'rangedStrengthBonus')
table.insert(hitArray, p.calculateStandardMaxHit(baseLevel, bonus))
iconText = Icons.Icon({'Magic', type='skill', notext=true})
        local magicDmg = 0
        if monster.selectedSpell ~= nil then
            local mSpell = Magic.getSpellByID(monster.selectedSpell, 'standard')
            if mSpell ~= nil then
                baseLevel = p._getMonsterLevel(monster, 'Magic')
                bonus = p.getEquipmentStat(monster, 'magicDamageBonus')
                magicDmg = math.floor(10 * mSpell.maxHit * (1 + bonus / 100) * (1 + (baseLevel + 1) / 200))
            end
        end
table.insert(hitArray, magicDmg)
local max = 0
for i, val in pairs(hitArray) do
if val > max then max = val end
end
result = max
else
return Shared.printError('This monster has an invalid attack type somehow')
end


  --Should only get here for Melee/Ranged, which use functionally the same damage formula
return result
  return math.floor(10 * (1.3 + (effStrLvl/10) + (strBonus / 80) + ((effStrLvl * strBonus) / 640)))
end
end


function p.getMonsterBaseMaxHit(frame)
function p.getMonsterBaseMaxHit(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[[Category:Pages with script errors]]"
return Shared.printError('No monster with that name found')
  end
end


  return p._getMonsterBaseMaxHit(monster)
return p._getMonsterBaseMaxHit(monster)
end
end


function p.getMonsterAttacks(frame)
function p.getMonsterAttacks(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
return Shared.printError('No monster with that name found')
end
 
local result = ''
local iconText = p._getMonsterStyleIcon({monster, notext=true})
local typeText = ''
if monster.attackType == 'melee' then
typeText = 'Melee'
elseif monster.attackType == 'ranged' then
typeText = 'Ranged'
elseif monster.attackType == 'magic' then
typeText = 'Magic'
elseif monster.attackType == 'random' then
typeText = "Random"
end
 
local buffAttacks = {}
local hasActiveBuffSpec = false
local isNormalAttackRelevant = false


  if monster == nil then
local normalAttackChance = 100
    return "ERROR: No monster with that name found[[Category:Pages with script errors]]"
if monster.specialAttacks ~= nil then
  end
for i, specAttackID in pairs(monster.specialAttacks) do
            local specAttack = GameData.getEntityByID('attacks', specAttackID)
local attChance = 0
if monster.overrideSpecialChances ~= nil then
attChance = monster.overrideSpecialChances[i]
else
attChance = specAttack.defaultChance
end
normalAttackChance = normalAttackChance - attChance


  local result = ''
result = result..'\r\n* '..attChance..'% '..iconText..' '..specAttack.name..'\r\n** '..specAttack.description
--If this special attack applies a curse, let's actually list what that curse does
if specAttack.onhitEffects ~= nil then
for j, hitEffect in ipairs(specAttack.onhitEffects) do
if hitEffect.effectType == 'Curse' then
local curse = Magic.getSpellByID(hitEffect.curse, 'curse')
result = result..'\r\n*** '..Icons.Icon({curse.name, type='curse'})..': '..Magic._getSpellDescription(curse, true)
end
end
end


  local iconText = ''
if Shared.contains(string.upper(specAttack.description), 'NORMAL ATTACK INSTEAD') then
  local typeText = ''
table.insert(buffAttacks, specAttack.name)
  if Constants.getCombatStyleName(monster.attackType) == 'Melee' then
hasActiveBuffSpec = true
    iconText = Icons.Icon({'Melee', notext=true})
isNormalAttackRelevant = true
    typeText = 'Melee'
end
  elseif Constants.getCombatStyleName(monster.attackType) == 'Ranged' then
if not isNormalAttackRelevant and type(specAttack.damage) == 'table' then
    iconText = Icons.Icon({'Ranged', type='skill', notext=true})
-- Determine if the special attack uses normal damage in some form
    typeText = 'Ranged'
for j, dmgData in ipairs(specAttack.damage) do
  elseif Constants.getCombatStyleName(monster.attackType) == 'Magic' then
if dmgData.damageType == 'Normal' then
    iconText = Icons.Icon({'Magic', type='skill', notext=true})
isNormalAttackRelevant = true
    typeText = 'Magic'
break
  end
end
end
end
end
end
if isNormalAttackRelevant or normalAttackChance > 0 then
--Reformatting slightly - If there are any special attacks, specifically label the Normal Attack
local normalDmgText = ' 1 - '..Shared.formatnum(p._getMonsterBaseMaxHit(monster))..' '..typeText..' Damage'
if normalAttackChance > 0 and normalAttackChance < 100 then
normalDmgText = normalAttackChance .. '% ' ..iconText..' Normal Attack\r\n** '..normalDmgText
elseif hasActiveBuffSpec and normalAttackChance == 0 then
--If the monster normally has a 0% chance of doing a normal attack but some special attacks can't be repeated, include it
--(With a note about when it does it)
normalDmgText = iconText..' Normal Attack\r\n** '..normalDmgText .. ' (Instead of repeating '..table.concat(buffAttacks, ' or ')..' while the effect is already active)'
end
result = '* ' .. normalDmgText .. result
end


  local normalAttackChance = 100
return result
  if monster.hasSpecialAttack then
end
    for i, specID in pairs(monster.specialAttackID) do
      local specAttack = p.getSpecialAttackByID(specID)
      local attChance = 0
      if monster.overrideSpecialChances ~= nil then
        attChance = monster.overrideSpecialChances[i]
      else
        attChance = specAttack.chance
      end
      normalAttackChance = normalAttackChance - attChance


      result = result..'\r\n* '..attChance..'% '..iconText..' '..specAttack.name..'\r\n** '..specAttack.description
--Function for pulling how much the monster reduces the player DR
--Goes through the passvies to look for the decreasedPlayerDamageReduction modifier
function p._getMonsterDrReduction(monster)
local totalResult = 0
    if type(monster.passives) == 'table' and not Shared.tableIsEmpty(monster.passives) then
for i, passiveID in ipairs(monster.passives) do
local passive = p.getPassiveByID(passiveID)
if passive.modifiers ~= nil then
if passive.modifiers['decreasedPlayerDamageReduction'] ~= nil then
totalResult = totalResult + passive.modifiers['decreasedPlayerDamageReduction']
end
end
end
     end
     end
  end
      
  if normalAttackChance == 100 then
     return totalResult
     result = iconText..'1 - '..p.getMonsterBaseMaxHit(frame)..' '..typeText..' Damage'
  elseif normalAttackChance > 0 then
     result = '* '..normalAttackChance..'% '..iconText..'1-'..p.getMonsterBaseMaxHit(frame)..' '..typeText..' Damage'..result
  end
  return result
end
end


function p.getMonsterPassives(frame)
function p.getMonsterDrReduction(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[[Category:Pages with script errors]]"
return Shared.printError('No monster with that name found')
  end
end
return p._getMonsterDrReduction(monster)
end


  local result = ''
function p.getMonsterPassives(frame)
local MonsterName = frame.args ~= nil and frame.args[1] or frame
local monster = p.getMonster(MonsterName)


  if monster.hasPassive then
if monster == nil then
    result = result .. '===Passives==='
return Shared.printError('No monster with that name found')
    for i, passiveID in pairs(monster.passiveID) do
end
      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
local result = ''
    end
    if type(monster.passives) == 'table' and not Shared.tableIsEmpty(monster.passives) then
  end
result = result .. '===Passives==='
  return result
for i, passiveID in ipairs(monster.passives) do
local passive = p.getPassiveByID(passiveID)
result = result .. '\r\n* ' .. passive.name .. '\r\n** ' .. Constants.getDescription(passive.customDescription, passive.modifiers)
end
end
return result
end
end


function p.getMonsterCategories(frame)
function p.getMonsterCategories(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[[Category:Pages with script errors]]"
return Shared.printError('No monster with that name found')
  end
end


  local result = '[[Category:Monsters]]'
local result = '[[Category:Monsters]]'


  if Constants.getCombatStyleName(monster.attackType) == 'Melee' then
if monster.attackType == 'melee' then
    result = result..'[[Category:Melee Monsters]]'
result = result..'[[Category:Melee Monsters]]'
  elseif Constants.getCombatStyleName(monster.attackType) == 'Ranged' then
elseif monster.attackType == 'ranged' then
    result = result..'[[Category:Ranged Monsters]]'
result = result..'[[Category:Ranged Monsters]]'
  elseif Constants.getCombatStyleName(monster.attackType) == 'Magic' then
elseif monster.attackType == 'magic' then
    result = result..'[[Category:Magic Monsters]]'
result = result..'[[Category:Magic Monsters]]'
  end
end


  if monster.hasSpecialAttack then
if type(monster.passives) == 'table' and not Shared.tableIsEmpty(monster.passives) then
    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
return result
end
 
function p.getMonsterBoxBarrierText(frame)
local MonsterName = frame.args ~= nil and frame.args[1] or frame
local monster = p.getMonster(MonsterName)
 
if monster == nil then
return Shared.printError('No monster with that name found')
end
local barrier = p._getMonsterBarrier(monster)
if barrier == 0 then
return ''
end
local result = {}
table.insert(result, '|-\r\n| style="font-weight: bold;" | [[Barrier]]:')
table.insert(result, '\r\n| colspan=15 style="text-align: right" |')
table.insert(result, Icons.Icon({"Barrier", notext="true"}))
table.insert(result, ' '..barrier)
return table.concat(result, '')
end
end


function p.getOtherMonsterBoxText(frame)
function p.getOtherMonsterBoxText(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[[Category:Pages with script errors]]"
return Shared.printError('No monster with that name found')
  end
end


  local result = ''
local result = ''


  --Going through and finding out which damage bonuses will apply to this monster
--Going through and finding out which damage bonuses will apply to this monster
  local monsterTypes = {}
local monsterTypes = {}
  if monster.isBoss then table.insert(monsterTypes, 'Boss') end
if monster.isBoss then table.insert(monsterTypes, 'Boss') end


  local areaList = Areas.getMonsterAreas(monster.id)
local areaList = Areas.getMonsterAreas(monster.id)
  local counts = {combat = 0, slayer = 0, dungeon = 0}
local counts = {combatArea = 0, slayerArea = 0, dungeon = 0}
  for i, area in Shared.skpairs(areaList) do
for i, area in ipairs(areaList) do
    counts[area.type] = counts[area.type] + 1
counts[area.type] = counts[area.type] + 1
  end
end


  if counts.combat > 0 then table.insert(monsterTypes, 'Combat Area') end
if counts.combatArea > 0 then table.insert(monsterTypes, 'Combat Area') end
  if counts.slayer > 0 then table.insert(monsterTypes, 'Slayer Area') end
if counts.slayerArea > 0 then table.insert(monsterTypes, 'Slayer Area') end
  if counts.dungeon > 0 then table.insert(monsterTypes, 'Dungeon') end
if counts.dungeon > 0 then table.insert(monsterTypes, 'Dungeon') end


  result = result.."\r\n|-\r\n|'''Monster Types:''' "..table.concat(monsterTypes, ", ")
result = result.."\r\n|-\r\n|'''Monster Types:''' "..table.concat(monsterTypes, ", ")


  local SlayerTier = 'N/A'
local SlayerTier = 'N/A'
  if not p._isDungeonOnlyMonster(monster) then
if monster.canSlayer then
    SlayerTier = Constants.getSlayerTierNameByLevel(p._getMonsterCombatLevel(monster))
SlayerTier = Constants.getSlayerTierNameByLevel(p._getMonsterCombatLevel(monster))
  end
end


  result = result.."\r\n|-\r\n|'''"..Icons.Icon({'Slayer', type='skill'}).." [[Slayer#Slayer Tier Monsters|Tier]]:''' "..SlayerTier
result = result.."\r\n|-\r\n|'''"..Icons.Icon({'Slayer', type='skill'}).." [[Slayer#Slayer Tier Monsters|Tier]]:''' "
if monster.canSlayer then
result = result.."[[Slayer#"..SlayerTier.."|"..SlayerTier.."]]"
else
result = result..SlayerTier
end


  return result
return result
end
end


function p.getMonsterDrops(frame)
function p.getMonsterDrops(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[[Category:Pages with script errors]]"
return Shared.printError('No monster with that name found')
  end
end


  local result = ''
local result = ''


  if monster.bones ~= nil then
local bones = p._getMonsterBones(monster)
    local bones = Items.getItemByID(monster.bones)
local boneVal = 0
    --Show the bones only if either the monster shows up outside of dungeons _or_ the monster drops shards
local barrierDust = Items.getItemByID("melvorAoD:Barrier_Dust")
    if not p._isDungeonOnlyMonster(monster) or Shared.contains(bones.name, 'Shard') then
local dustVal = 0
      result = result.."'''Always Drops:'''"
--Show the bones only if either the monster shows up outside of dungeons _or_ the monster drops shards
      result = result..'\r\n{|class="wikitable"'
if bones ~= nil then
      result = result..'\r\n!Item !! Qty'
local boneQty = (bones.quantity ~= nil and bones.quantity or 1)
      result = result..'\r\n|-\r\n|'..Icons.Icon({bones.name, type='item'})
local barrier = p._getMonsterBarrier(monster)
      result = result..'||'..(monster.boneQty ~= nil and monster.boneQty or 1)..'\r\n'..'|}'
result = result.."'''Always Drops:'''"
    end
result = result..'\r\n{|class="wikitable" id="bonedrops"'
  end
result = result..'\r\n!Item !! Qty'
result = result..'\r\n|-\r\n|'..Icons.Icon({bones.item.name, type='item'})
result = result..'||'..boneQty
if barrier > 0 then
local dustQty = math.max(math.floor(barrier / 10 / 20), 1)
result = result..'\r\n|-\r\n|'..Icons.Icon({barrierDust.name, type='item'})
result = result..'||'..dustQty
dustVal = dustQty * barrierDust.sellsFor
end
result = result..'\r\n'..'|}'
boneVal = boneQty * bones.item.sellsFor
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
 
result = result.."'''Loot:'''"
local avgGp = 0
 
if monster.gpDrops ~= nil then
avgGp = (monster.gpDrops.min + monster.gpDrops.max) / 2
local gpTxt = Icons.GP(monster.gpDrops.min, monster.gpDrops.max)
result = result.."\r\nIn addition to loot, the monster will also drop "..gpTxt..'.'
end
 
local multiDrop = Shared.tableCount(monster.lootTable) > 1
local totalWt = 0
for i, row in ipairs(monster.lootTable) do
totalWt = totalWt + row.weight
end
result = result..'\r\n{|class="wikitable sortable" id="itemdrops"'
result = result..'\r\n!Item!!Qty'
result = result..'!!Price!!colspan="2"|Chance'
 
--Sort the loot table by weight in descending order
local lootTable = Shared.shallowClone(monster.lootTable)
table.sort(lootTable, function(a, b)
if a.weight == b.weight then
local aItem, bItem = Items.getItemByID(a.itemID), Items.getItemByID(b.itemID)
if aItem ~= nil and bItem ~= nil then
return aItem.name < bItem.name
else
return a.itemID < b.itemID
end
else
return a.weight > b.weight
end
end)
for i, row in ipairs(lootTable) do
local thisItem = Items.getItemByID(row.itemID)
if thisItem ~= nil then
result = result..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'})
else
result = result..'\r\n|-\r\n|Unknown Item[[Category:Pages with script errors]]'
end
result = result..'||style="text-align:right" data-sort-value="'..row.maxQuantity..'"|'
 
if row.maxQuantity > row.minQuantity then
result = result .. Shared.formatnum(row.minQuantity) .. ' - '
end
result = result .. Shared.formatnum(row.maxQuantity)
 
--Adding price columns
local itemPrice = 0
if thisItem == nil then
result = result..'||data-sort-value="0"|???'
else
itemPrice = thisItem.sellsFor ~= nil and thisItem.sellsFor or 0
if itemPrice == 0 or row.maxQuantity == row.minQuantity then
result = result..'||'..Icons.GP(itemPrice * row.minQuantity)
else
result = result..'||'..Icons.GP(itemPrice * row.minQuantity, itemPrice * row.maxQuantity)
end
end
 
--Getting the drop chance
local dropChance = (row.weight / totalWt * lootChance)
if dropChance < 100 then
--Show fraction as long as it isn't going to be 1/1
result = result..'||style="text-align:right" data-sort-value="'..row.weight..'"'
result = result..'|'..Shared.fraction(row.weight * lootChance, totalWt * 100)
result = result..'||'
else
result = result..'||colspan="2" data-sort-value="'..row.weight..'"'
end
-- If chance is less than 0.10% then show 2 significant figures, otherwise 2 decimal places
local fmt = (dropChance < 0.10 and '%.2g') or '%.2f'
result = result..'style="text-align:right"|'..string.format(fmt, dropChance)..'%'
 
--Adding to the average loot value based on price & dropchance
lootValue = lootValue + (dropChance * 0.01 * itemPrice * ((row.minQuantity + row.maxQuantity) / 2))
end
if multiDrop then
result = result..'\r\n|-class="sortbottom" \r\n!colspan="3"|Total:'
if lootChance < 100 then
result = result..'\r\n|style="text-align:right"|'..Shared.fraction(lootChance, 100)..'||'
else
result = result..'\r\n|colspan="2" '
end
result = result..'style="text-align:right"|'..Shared.round(lootChance, 2, 2)..'%'
end
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."
if avgGp > 0 then
result = result.."<br/>Including GP"
if boneVal > 0 then
result = result..' and bones'
end
if dustVal > 0 then
result = result..' and barrier dust'
end
result = result..', the average kill is worth '..Icons.GP(Shared.round(avgGp + lootValue + boneVal + dustVal, 2, 0))..'.'
end
end
 
--If no other drops, make sure to at least say so.
if result == '' then result = 'None' end
return result
end
 
function p._getMonsterLootValue(monster)
if monster == nil then
return Shared.printError('No monster with that name found')
end


  --Likewise, seeing the loot table is tied to the monster appearing outside of dungeons
local result = 0
  if not p._isDungeonOnlyMonster(monster) then
local boneVal = 0
    local lootChance = monster.lootChance ~= nil and monster.lootChance or 100
    local lootValue = 0


    result = result.."'''Loot:'''"
local bones = p._getMonsterBones(monster)
    local avgGp = 0
--Show the bones only if either the monster shows up outside of dungeons _or_ the monster drops shards
if bones ~= nil then
local boneQty = (bones.quantity ~= nil and bones.quantity) or 1
boneVal = bones.item.sellsFor * boneQty
result = result + boneVal
end


    if monster.dropCoins ~= nil and monster.dropCoins[2] > 1 then
--Likewise, seeing the loot table is tied to the monster appearing outside of dungeons
      avgGp = (monster.dropCoins[1] + monster.dropCoins[2] - 1) / 2
if not p._isDungeonOnlyMonster(monster) then
      local gpTxt = Icons.GP(monster.dropCoins[1], monster.dropCoins[2] - 1)
local lootChance = monster.lootChance ~= nil and monster.lootChance or 100
      result = result.."\r\nIn addition to loot, the monster will also drop "..gpTxt..'.'
local lootValue = 0
    end


    local multiDrop = Shared.tableCount(monster.lootTable) > 1
local avgGp = 0
    local totalWt = 0
    for i, row in pairs(monster.lootTable) do
      totalWt = totalWt + row[2]
    end
    result = result..'\r\n{|class="wikitable sortable"'
    result = result..'\r\n!Item!!Qty'
    result = result..'!!Price!!colspan="2"|Chance'


    --Sort the loot table by weight in descending order
if monster.gpDrops ~= nil then
    table.sort(monster.lootTable, function(a, b) return a[2] > b[2] end)
avgGp = (monster.gpDrops.min + monster.gpDrops.max) / 2
    for i, row in Shared.skpairs(monster.lootTable) do
end
      local thisItem = Items.getItemByID(row[1])
      local maxQty = row[3]
      result = result..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'})
      result = result..'||style="text-align:right" data-sort-value="'..maxQty..'"|'


      if maxQty > 1 then
local multiDrop = Shared.tableCount(monster.lootTable) > 1
        result = result.. '1 - '
local totalWt = 0
      end
for i, row in pairs(monster.lootTable) do
      result = result..Shared.formatnum(row[3])
totalWt = totalWt + row.weight
end


      --Adding price columns
for i, row in ipairs(monster.lootTable) do
      local itemPrice = thisItem.sellsFor ~= nil and thisItem.sellsFor or 0
local thisItem = Items.getItemByID(row.itemID)
      if itemPrice == 0 or maxQty == 1 then
        result = result..'||'..Icons.GP(itemPrice)
      else
        result = result..'||'..Icons.GP(itemPrice, itemPrice * maxQty)
      end


      --Getting the drop chance
--Adding price columns
      local dropChance = (row[2] / totalWt * lootChance)
local itemPrice = 0
      if dropChance ~= 100 then
if thisItem ~= nil then
        --Show fraction as long as it isn't going to be 1/1
itemPrice = thisItem.sellsFor ~= nil and thisItem.sellsFor or 0
        result = result..'||style="text-align:right" data-sort-value="'..row[2]..'"'
end
        result = result..'|'..Shared.fraction(row[2] * lootChance, totalWt * 100)
        result = result..'||'
      else
        result = result..'||colspan="2" data-sort-value="'..row[2]..'"'
      end
      result = result..'style="text-align:right"|'..Shared.round(dropChance, 2, 2)..'%'


      --Adding to the average loot value based on price & dropchance
--Getting the drop chance
      lootValue = lootValue + (dropChance * 0.01 * itemPrice * ((1 + maxQty) / 2))
local dropChance = (row.weight / totalWt * lootChance)
    end
--Adding to the average loot value based on price & dropchance
    if multiDrop then
lootValue = lootValue + (dropChance * 0.01 * itemPrice * ((row.minQuantity + row.maxQuantity) / 2))
      result = result..'\r\n|-class="sortbottom" \r\n!colspan="3"|Total:'
end
      if lootChance < 100 then
if avgGp > 0 then
        result = result..'\r\n|style="text-align:right"|'..Shared.fraction(lootChance, 100)..'||'
result = result + avgGp + lootValue
      else
end
        result = result..'\r\n|colspan="2" '
end
      end
      result = result..'style="text-align:right"|'..lootChance..'.00%'
    end
    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."
    if avgGp > 0 then
      result = result..'<br/>Including GP, the average kill is worth '..Icons.GP(Shared.round(avgGp + lootValue, 2, 0))..'.'
    end
  end


  --If no other drops, make sure to at least say so.
return result
  if result == '' then result = 'None' end
  return result
end
end
-- Find drop chance of specified item from specified monster.
-- Usage: |Monster Name|Item Name
function p.getItemDropChance(frame)
local MonsterName = frame.args ~= nil and frame.args[1] or frame[1]
local ItemName = frame.args ~= nil and frame.args[2] or frame[2]
local monster = p.getMonster(MonsterName)
local item = Items.getItem(ItemName)
if monster == nil then
return Shared.printError('No monster with that name found')
end
if item == nil then
return Shared.printError('No item with that name found')
end
if not p._isDungeonOnlyMonster(monster) then
local lootChance = monster.lootChance ~= nil and monster.lootChance or 100
local totalWt = 0
--for i, row in pairs(monster.lootTable) do
--totalWt = totalWt + row[2]
--end
local dropChance = 0
local dropWt = 0
for i, row in ipairs(monster.lootTable) do
totalWt = totalWt + row.weight
if item.id == row.itemID then
dropWt = row.weight
end
end
dropChance = (dropWt / totalWt * lootChance)
return Shared.round(dropChance, 2, 2)
end
end


function p.getChestDrops(frame)
function p.getChestDrops(frame)
  local ChestName = frame.args ~= nil and frame.args[1] or frame
local chestName = frame.args ~= nil and frame.args[1] or frame
  local chest = Items.getItem(ChestName)
local chest = Items.getItem(chestName)


  if chest == nil then
if chest == nil then
    return "ERROR: No item named "..ChestName..' found[[Category:Pages with script errors]]'
return Shared.printError('No item named ' .. chestName .. ' found')
  end
end
local result = ''


  local result = ''
if chest.dropTable == nil then
return Shared.printError(chestName .. ' does not have a drop table')
else


  if chest.dropTable == nil then
local function formatNumRange(minValue, maxValue)
    return "ERROR: "..ChestName.." does not have a drop table[[Category:Pages with script errors]]"
if maxValue ~= nil and maxValue > minValue then
  else
return Shared.formatnum(minValue) .. ' - ' .. Shared.formatnum(maxValue)
    local lootChance = 100
else
    local lootValue = 0
return Shared.formatnum(minValue)
end
end


    local multiDrop = Shared.tableCount(chest.dropTable) > 1
local lootValue, foodValue = 0, 0
    local totalWt = 0
local totalWt = 0
    for i, row in pairs(chest.dropTable) do
local isAllFood = true
      totalWt = totalWt + row[2]
for i, row in ipairs(chest.dropTable) do
    end
totalWt = totalWt + row.weight
    result = result..'\r\n{|class="wikitable sortable"'
if isAllFood then
    result = result..'\r\n!Item!!Qty'
-- If the container's contents are entirely food then we add additional
    result = result..'!!colspan="2"|Chance!!Price'
-- information to the output, so we determine this here
local item = Items.getItemByID(row.itemID)
if item ~= nil and item.healsFor == nil then
isAllFood = false
end
end
end
result = result..'\r\n{|class="wikitable sortable"'
result = result..'\r\n!Item!!Qty'
result = result..'!!colspan="2"|Chance!!Price' .. (isAllFood and '!!Healing!!Avg. Healing' or '')


    --Sort the loot table by weight in descending order
--Sort the loot table by weight in descending order
    for i, row in pairs(chest.dropTable) do
local chestDrops = Shared.shallowClone(chest.dropTable)
      if chest.dropQty ~= nil then
table.sort(chestDrops, function(a, b) return a.weight > b.weight end)
        table.insert(row, chest.dropQty[i])
for i, row in ipairs(chestDrops) do
      else
local thisItem = Items.getItemByID(row.itemID)
        table.insert(row, 1)
result = result..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'})
      end
result = result..'||style="text-align:right" data-sort-value="'..(row.minQuantity + row.maxQuantity)..'"| ' .. formatNumRange(row.minQuantity, row.maxQuantity)
    end
    table.sort(chest.dropTable, function(a, b) return a[2] > b[2] end)
    for i, row in Shared.skpairs(chest.dropTable) do
      local thisItem = Items.getItemByID(row[1])
      local qty = row[3]
      result = result..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'})
      result = result..'||style="text-align:right" data-sort-value="'..qty..'"|'


      if qty > 1 then
local dropChance = (row.weight / totalWt) * 100
        result = result.. '1 - '
result = result..'||style="text-align:right" data-sort-value="'..row.weight..'"'
      end
result = result..'|'..Shared.fraction(row.weight, totalWt)
      result = result..Shared.formatnum(qty)


      local dropChance = (row[2] / totalWt) * 100
result = result..'||style="text-align:right"|'..Shared.round(dropChance, 2, 2)..'%'
      result = result..'||style="text-align:right" data-sort-value="'..row[2]..'"'
      result = result..'|'..Shared.fraction(row[2], totalWt)


      result = result..'||style="text-align:right"|'..Shared.round(dropChance, 2, 2)..'%'
result = result..'||style="text-align:left" data-sort-value="'..thisItem.sellsFor..'"'
if thisItem.sellsFor == 0 or row.minQuantity == row.maxQuantity then
result = result..'|'..Icons.GP(thisItem.sellsFor * row.minQuantity)
else
result = result..'|'..Icons.GP(thisItem.sellsFor * row.minQuantity, thisItem.sellsFor * row.maxQuantity)
end
lootValue = lootValue + (dropChance * 0.01 * thisItem.sellsFor * ((row.minQuantity + row.maxQuantity)/ 2))


      result = result..'||style="text-align:left" data-sort-value="'..thisItem.sellsFor..'"'
if isAllFood then
      if qty > 1 then
local hp = thisItem.healsFor * 10
        result = result..'|'..Icons.GP(thisItem.sellsFor, thisItem.sellsFor * qty)
local minHeal, maxHeal = hp * row.minQuantity, hp * row.maxQuantity
      else
local avgHpPerLoot = (dropChance * 0.01 * (minHeal + maxHeal) / 2)
        result = result..'|'..Icons.GP(thisItem.sellsFor)
foodValue = foodValue + avgHpPerLoot
      end
result = result .. '||data-sort-value="' .. thisItem.healsFor .. '"'
      lootValue = lootValue + (dropChance * 0.01 * thisItem.sellsFor * ((1 + qty)/ 2))
result = result .. '|' .. Icons.Icon({'Hitpoints', type='skill', notext=true, nolink=true}) .. ' ' .. formatNumRange(minHeal, maxHeal)
    end
result = result .. '||data-sort-value="' .. avgHpPerLoot .. '"'
    result = result..'\r\n|}'
result = result .. '|' .. Icons.Icon({'Hitpoints', type='skill', notext=true, nolink=true}) .. ' ' .. Shared.round(avgHpPerLoot, 2, 0)
    result = result..'\r\nThe average value of the contents of one chest is '..Icons.GP(Shared.round(lootValue, 2, 0))..'.'
end
  end
end
result = result..'\r\n|}'
result = result..'\r\nThe average value of the contents of one chest is '..Icons.GP(Shared.round(lootValue, 2, 0))..'.'
if isAllFood then
result = result..'\r\n\r\nThe average healing of the contents of one chest is ' .. Icons.Icon({'Hitpoints', type='skill', notext=true, nolink=true}) .. ' ' .. Shared.round(foodValue, 2, 0) .. '.'
end
end


  return result
return result
end
end


function p.getAreaMonsterTable(frame)
function p.getAreaMonsterTable(frame)
  local areaName = frame.args ~= nil and frame.args[1] or frame
local areaName = frame.args ~= nil and frame.args[1] or frame
  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..'[[Category:Pages with script errors]]'
return Shared.printError('Could not find an area named ' .. areaName)
  end
end


  if area.type == 'dungeon' then
if area.type == 'dungeon' then
    return p.getDungeonMonsterTable(frame)
return p.getDungeonMonsterTable(frame)
  end
end
local monsters = {}
local hasBarrier = false
for i, monsterID in ipairs(area.monsterIDs) do
local monster = p.getMonsterByID(monsterID)
if not hasBarrier and p._getMonsterBarrier(monster) > 0 then
hasBarrier = true
end
table.insert(monsters, monster)
end


  local tableTxt = '{| class="wikitable sortable"'
local tableBits = {}
  tableTxt = tableTxt..'\r\n! Name !! Combat Level !! Hitpoints !! Max Hit !! [[Combat Triangle|Combat Style]]'
table.insert(tableBits, '{| class="wikitable sortable"')
  for i, monsterID in pairs(area.monsters) do
table.insert(tableBits, '\r\n! Name !! Combat Level ')
    local monster = p.getMonsterByID(monsterID)
if hasBarrier then
    tableTxt = tableTxt..'\r\n|-\r\n|'..Icons.Icon({monster.name, type='monster'})
table.insert(tableBits, '!! [[Barrier]] ')
    tableTxt = tableTxt..'||'..p._getMonsterCombatLevel(monster)
end
    tableTxt = tableTxt..'||'..Shared.formatnum(p.getMonsterHP(monster.name))
table.insert(tableBits, '!! Hitpoints !! colspan=2| Max Hit !! [[Combat Triangle|Combat Style]]')
    tableTxt = tableTxt..'||'..Shared.formatnum(p.getMonsterMaxHit(monster.name))
for i, monster in ipairs(monsters) do
    tableTxt = tableTxt..'||'..p.getMonsterStyleIcon({monster.name, nolink='true'})
local rowBits = {}
  end
table.insert(tableBits, '\r\n|-\r\n|'..Icons.Icon({p.getMonsterName(monster), type='monster'}))
  tableTxt = tableTxt..'\r\n|}'
table.insert(tableBits, '||'..p._getMonsterCombatLevel(monster))
  return tableTxt
if hasBarrier then
table.insert(tableBits, '||'..Shared.formatnum(p._getMonsterBarrier(monster)))
end
table.insert(tableBits, '||'..Shared.formatnum(p._getMonsterHP(monster)))
local drReduction = p._getMonsterDrReduction(monster)
local maxHit = p._getMonsterMaxHit(monster)
if drReduction > 0 then
table.insert(tableBits, '||style="text-align:right" data-sort-value="'..maxHit..'"| -'..drReduction..'% DR')
table.insert(tableBits, '||style="text-align:right"|'..Shared.formatnum(maxHit))
else
table.insert(tableBits, '||style="text-align:right" colspan="2" data-sort-value="'..maxHit..'"|'..Shared.formatnum(maxHit))
end
table.insert(tableBits, '||'..p._getMonsterStyleIcon({monster, nolink=true}))
end
table.insert(tableBits, '\r\n|}')
return table.concat(tableBits, '')
end
end


function p.getDungeonMonsterTable(frame)
function p.getDungeonMonsterTable(frame)
  local areaName = frame.args ~= nil and frame.args[1] or frame
local areaName = frame.args ~= nil and frame.args[1] or frame
  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..'[[Category:Pages with script errors]]'
return Shared.printError('Could not find a dungeon named ' .. areaName)
  end
end
 
--For Dungeons, go through and count how many of each monster are in the dungeon first
local monsterCounts = {}
local monsters = {}
local hasBarrier = false
for i, monsterID in ipairs(area.monsterIDs) do
if monsterCounts[monsterID] == nil then
monsterCounts[monsterID] = 1
else
monsterCounts[monsterID] = monsterCounts[monsterID] + 1
if monsterID ~= 'melvorF:RandomITM' and monsterID ~= 'melvorTotH:RandomSpiderLair' then
monsters[monsterID] = p.getMonsterByID(monsterID)
if not hasBarrier and p._getMonsterBarrier(monsters[monsterID]) > 0 then
hasBarrier = true
end
end
end
end
 
local usedMonsters = {}
 
-- Declare function for building table rows to avoid repeating code
local buildRow = function(entityID, monsterCount, specialType)
local monIcon, monLevel, monHP, monMaxHit, monStyle, monCount, monDrReduce, monBarrier
local monData = {}
if specialType ~= nil and Shared.contains({'Afflicted', 'Spider', 'SlayerArea'}, specialType) then
-- Special handling for Into the Mist
if specialType == 'Afflicted' then
local iconQ = Icons.Icon({'Into the Mist', notext=true, nolink=true, img='Question'})
monIcon = Icons.Icon({'Into the Mist', 'Afflicted Monster', nolink=true, img='Question'})
monLevel, monBarrier, monHP, monMaxHit, monDrReduce, monStyle, monCount = iconQ, iconQ, iconQ, iconQ, iconQ, iconQ, monsterCount
elseif specialType == 'Spider' then
local iconQ = Icons.Icon({'', notext=true, nolink=true, img='Question'})
local monIconPart = { 'Any of the following:' }
for i, monsterID in ipairs(GameData.rawData.spiderLairMonsters) do
local monster = p.getMonsterByID(monsterID)
if monster ~= nil then
table.insert(monIconPart, Icons.Icon({p.getMonsterName(monster), type='monster'}))
end
end
monIcon = table.concat(monIconPart, '<br/>')
monLevel, monBarrier, monHP, monMaxHit, monDrReduce, monStyle, monCount = iconQ, iconQ, iconQ, iconQ, iconQ, iconQ, monsterCount
elseif specialType == 'SlayerArea' then
-- entityID corresponds to a slayer area
local area = Areas.getAreaByID('slayer', entityID)
monIcon = Icons.Icon({area.name, type='combatArea'}) .. ' Monsters'
monLevel = {p.getLowHighStat(area.monsterIDs, function(monster) return p._getMonsterCombatLevel(monster) end)}
if hasBarrier then
monBarrier = {p.getLowHighStat(area.monsterIDs, function(monster) return p._getMonsterBarrier(monster) end)}
end
monHP = {p.getLowHighStat(area.monsterIDs, function(monster) return p._getMonsterHP(monster) end)}
local lowMaxHit, highMaxHit = p.getLowHighStat(area.monsterIDs, function(monster) return p._getMonsterMaxHit(monster) end)
local lowDrReduce, highDrReduce = p.getLowHighStat(area.monsterIDs, function(monster) return p._getMonsterDrReduction(monster) end)
monMaxHit = highMaxHit
monDrReduce = highDrReduce
monStyle = Icons.Icon({area.name, area.name, notext=true, nolink=true, img='Question'})
monCount = monsterCount
end
else
-- entityID corresponds to a monster
local monster = p.getMonsterByID(entityID)
monIcon = Icons.Icon({p.getMonsterName(monster), type='monster'})
monLevel = p._getMonsterCombatLevel(monster)
if hasBarrier then
monBarrier = p._getMonsterBarrier(monster)
end
monHP = p._getMonsterHP(monster)
monDrReduce = p._getMonsterDrReduction(monster)
monMaxHit = p._getMonsterMaxHit(monster)
monStyle = p._getMonsterStyleIcon({monster})
monCount = monsterCount
end
local getValSort = function(val)
if type(val) == 'table' then
if type(val[1]) == 'number' and type(val[2]) == 'number' then
return (val[1] + val[2]) / 2
else
return (type(val[1]) == 'number' and val[1]) or 0
end
else
return (type(val) == 'number' and val) or 0
end
end
local getValText = function(val)
if type(val) == 'table' and Shared.tableCount(val) == 2 then
if type(val[1]) == 'number' and type(val[2]) == 'number' then
return Shared.formatnum(val[1]) .. ' - ' .. Shared.formatnum(val[2])
else
return val[1] .. ' - ' .. val[2]
end
elseif type(val) == 'number' then
return Shared.formatnum(val)
else
return val
end
end
local resultPart = {}
table.insert(resultPart, '\r\n|-\r\n| ' .. monIcon)
table.insert(resultPart, '\r\n|style="text-align:right;" data-sort-value="' .. getValSort(monLevel) .. '"| ' .. getValText(monLevel))
if hasBarrier then
table.insert(resultPart, '\r\n|style="text-align:right;" data-sort-value="' .. getValSort(monBarrier) .. '"| ' .. getValText(monBarrier))
end
table.insert(resultPart, '\r\n|style="text-align:right;" data-sort-value="' .. getValSort(monHP) .. '"| ' .. getValText(monHP))
if type(monDrReduce) == 'number' and monDrReduce > 0 then
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="'..getValSort(monMaxHit)..'"| -'..monDrReduce..'% DR')
table.insert(resultPart, '\r\n|style="text-align:right"|'..getValText(monMaxHit))
else
table.insert(resultPart, '\r\n|style="text-align:right" colspan="2" data-sort-value="'..getValSort(monMaxHit)..'"|'..getValText(monMaxHit))
end
table.insert(resultPart, '\r\n| ' .. monStyle)
table.insert(resultPart, '\r\n|style="text-align:right;" data-sort-value="' .. getValSort(monCount) .. '"| ' .. getValText(monCount))
return table.concat(resultPart)
end


  --For Dungeons, go through and count how many of each monster are in the dungeon first
local returnPart = {}
  local monsterCounts = {}
table.insert(returnPart, '{| class="wikitable sortable"')
  for i, monsterID in pairs(area.monsters) do
table.insert(returnPart, '\r\n! Name !! Combat Level ')
    if monsterCounts[monsterID] == nil then
if hasBarrier then
      monsterCounts[monsterID] = 1
table.insert(returnPart, '!! [[Barrier]] ')
    else
end
      monsterCounts[monsterID] = monsterCounts[monsterID] + 1
table.insert(returnPart, '!! Hitpoints !! colspan="2" | Max Hit !! [[Combat Triangle|Combat Style]] !! Count')
    end
-- Special handing for Impending Darkness event
  end
-- TODO needs to be revised once there is a better understanding of how the event works
for i, monsterID in ipairs(area.monsterIDs) do
if not Shared.contains(usedMonsters, monsterID) then
if monsterID == 'melvorF:RandomITM' then
--Special handling for Into the Mist
table.insert(returnPart, buildRow(monsterID, monsterCounts[monsterID], 'Afflicted'))
elseif monsterID == 'melvorTotH:RandomSpiderLair' then
table.insert(returnPart, buildRow(monsterID, monsterCounts[monsterID], 'Spider'))
else
table.insert(returnPart, buildRow(monsterID, monsterCounts[monsterID], hasBarrier))
end
table.insert(usedMonsters, monsterID)
end
end
table.insert(returnPart, '\r\n|}')
return table.concat(returnPart)
end


  local usedMonsters = {}
function p.getDungeonTotalHp(frame)
local areaName = frame.args ~= nil and frame.args[1] or frame
local area = Areas.getArea(areaName)
if area == nil then
return Shared.printError('Could not find a dungeon named ' .. areaName)
end
local totalHP = 0


  local tableTxt = '{| class="wikitable sortable"'
for i, monsterID in ipairs(area.monsterIDs) do
  tableTxt = tableTxt..'\r\n! Name !! Combat Level !! Hitpoints !! Max Hit !! [[Combat Triangle|Combat Style]] !! Count'
        local monster = p.getMonsterByID(monsterID)
  for i, monsterID in pairs(area.monsters) do
         totalHP = totalHP + p._getMonsterHP(monster)
    if not Shared.contains(usedMonsters, monsterID) then
end
      local monster = p.getMonsterByID(monsterID)
return totalHP
      local name = monster.name
      if monsterID == 51 then name = 'Spider2' end
      if monsterID ~= 1 then
         tableTxt = tableTxt..'\r\n|-\r\n|'..Icons.Icon({name, type='monster'})
        tableTxt = tableTxt..'||'..p._getMonsterCombatLevel(monster)
        tableTxt = tableTxt..'||'..Shared.formatnum(p.getMonsterHP(name))
        tableTxt = tableTxt..'||'..Shared.formatnum(p.getMonsterMaxHit(name))
        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)
    end
  end
  tableTxt = tableTxt..'\r\n|}'
  return tableTxt
end
end


function p._getAreaMonsterList(area)
function p._getAreaMonsterList(area)
  local monsterList = {}
local monsterList = {}
  for i, monsterID in pairs(area.monsters) do
for i, monsterID in ipairs(area.monsterIDs) do
    local monster = p.getMonsterByID(monsterID)
local monster = p.getMonsterByID(monsterID)
    table.insert(monsterList, Icons.Icon({monster.name, type='monster'}))
table.insert(monsterList, Icons.Icon({p.getMonsterName(monster), type='monster'}))
  end
end
  return table.concat(monsterList, '<br/>')
return table.concat(monsterList, '<br/>')
end
end


function p._getDungeonMonsterList(area)
function p._getDungeonMonsterList(area)
  local monsterList = {}
local monsterList = {}
  local lastMonster = nil
local lastID = ''
  local lastID = -1
local count = 0
  local count = 0
  for i, monsterID in Shared.skpairs(area.monsters) do
local monsterCounts = {}
    if monsterID ~= lastID then
for i, monsterID in ipairs(area.monsterIDs) do
      local monster = p.getMonsterByID(monsterID)
if lastID == '' then
      if lastMonster ~= nil then
lastID = monsterID
        if lastID == 1 then
count = 1
          --Special handling for Afflicted Monsters
elseif lastID == monsterID then
          table.insert(monsterList, Icons.Icon({'Affliction', 'Afflicted Monster', img='Question', qty=count}))
count = count + 1
        else
else
          local name = lastMonster.name
table.insert(monsterCounts, { id = lastID, count = count })
          if lastMonster.id == 51 then name = 'Spider2' end
lastID = monsterID
          table.insert(monsterList, Icons.Icon({name, type='monster', qty=count}))
count = 1
        end
end
      end
end
      lastMonster = monster
table.insert(monsterCounts, { id = lastID, count = count })
      lastID = monster.id
 
      count = 1
for i, monster in ipairs(monsterCounts) do
    else
if monster.id == 'melvorF:RandomITM' then
      count = count + 1
--Special handling for Afflicted Monsters
    end
table.insert(monsterList, Icons.Icon({'Affliction', 'Afflicted Monster', img='Question', qty=monster.count}))
    --Make sure the final monster in the dungeon gets counted
elseif monster.id == 'melvorTotH:RandomSpiderLair' then
    if i == Shared.tableCount(area.monsters) then
local monIconPart = { Shared.formatnum(monster.count) .. ' Spiders:' }
      local name = lastMonster.name
for i, monsterID in ipairs(GameData.rawData.spiderLairMonsters) do
      table.insert(monsterList, Icons.Icon({lastMonster.name, type='monster', qty=count}))
local monster = p.getMonsterByID(monsterID)
    end
if monster ~= nil then
  end
table.insert(monIconPart, '&nbsp;&nbsp;&nbsp;' .. Icons.Icon({p.getMonsterName(monster), type='monster'}))
  return table.concat(monsterList, '<br/>')
end
end
table.insert(monsterList, table.concat(monIconPart, '<br/>'))
else
local monsterObj = p.getMonsterByID(monster.id)
table.insert(monsterList, Icons.Icon({p.getMonsterName(monsterObj), type='monster', qty=monster.count}))
end
end
 
return table.concat(monsterList, '<br/>')
end
end


function p.getAreaMonsterList(frame)
function p.getAreaMonsterList(frame)
  local areaName = frame.args ~= nil and frame.args[1] or frame
local areaName = frame.args ~= nil and frame.args[1] or frame
  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..'[[Category:Pages with script errors]]'
return Shared.printError('Could not find an area named ' .. areaName)
  end
end


  if area.type == 'dungeon' then
if area.type == 'dungeon' then
    return p._getDungeonMonsterList(area)
return p._getDungeonMonsterList(area)
  else
else
    return p._getAreaMonsterList(area)
return p._getAreaMonsterList(area)
  end
end
end
end


function p.getFoxyTable(frame)
function p.getFoxyTable(frame)
  local result = 'Monster,Min GP,Max GP,Average GP'
local result = 'Monster,Min GP,Max GP,Average GP'
  for i, monster in Shared.skpairs(MonsterData.Monsters) do
for i, monster in ipairs(GameData.rawData.monsters) do
    if not p._isDungeonOnlyMonster(monster) then
if not p._isDungeonOnlyMonster(monster) then
      if monster.dropCoins ~= nil and monster.dropCoins[2] > 1 then
if monster.gpDrops ~= nil and monster.gpDrops.max > 0 then
      local avgGp = (monster.dropCoins[1] + monster.dropCoins[2] - 1) / 2
local avgGp = (monster.gpDrops.min + monster.gpDrops.max) / 2
      result = result..'<br/>'..monster.name..','..monster.dropCoins[1]..','..(monster.dropCoins[2]-1)..','..avgGp
result = result .. '<br/>' .. p.getMonsterName(monster) .. ',' .. monster.gpDrops.min .. ',' .. monster.gpDrops.max .. ',' .. avgGp
      end
end
    end
end
  end
end
  return result
return result
end
end


function p._getMonsterAverageGP(monster)
function p._getMonsterAverageGP(monster)
  local result = ''
local result = ''
  local totalGP = 0
local totalGP = 0


  if monster.bones ~= nil then
local bones = p._getMonsterBones(monster)
    local bones = Items.getItemByID(monster.bones)
if bones ~= nil then
    --Show the bones only if either the monster shows up outside of dungeons _or_ the monster drops shards
totalGP = totalGP + bones.item.sellsFor * bones.quantity
    if not p._isDungeonOnlyMonster(monster) or Shared.contains(bones.name, 'Shard') then
end
      totalGP = totalGP + bones.sellsFor
    end
  end


  --Likewise, seeing the loot table is tied to the monster appearing outside of dungeons
--Likewise, seeing the loot table is tied to the monster appearing outside of dungeons
  if not p._isDungeonOnlyMonster(monster) then
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


    local avgGp = 0
local avgGp = 0


    if monster.dropCoins ~= nil and monster.dropCoins[2] > 1 then
if monster.gpDrops ~= nil then
      avgGp = (monster.dropCoins[1] + monster.dropCoins[2] - 1) / 2
avgGp = (monster.gpDrops.min + monster.gpDrops.max) / 2
    end
end


    totalGP = totalGP + avgGp
totalGP = totalGP + avgGp


    local multiDrop = Shared.tableCount(monster.lootTable) > 1
local totalWt = 0
    local totalWt = 0
for i, row in ipairs(monster.lootTable) do
    for i, row in pairs(monster.lootTable) do
totalWt = totalWt + row.weight
      totalWt = totalWt + row[2]
end
    end


    for i, row in Shared.skpairs(monster.lootTable) do
for i, row in ipairs(monster.lootTable) do
      local thisItem = Items.getItemByID(row[1])
local thisItem = Items.getItemByID(row.itemID)
      local maxQty = row[3]


      local itemPrice = thisItem.sellsFor ~= nil and thisItem.sellsFor or 0
local itemPrice = thisItem.sellsFor ~= nil and thisItem.sellsFor or 0


      --Getting the drop chance
--Getting the drop chance
      local dropChance = (row[2] / totalWt * lootChance)
local dropChance = (row.weight / totalWt * lootChance)


      --Adding to the average loot value based on price & dropchance
--Adding to the average loot value based on price & dropchance
      lootValue = lootValue + (dropChance * 0.01 * itemPrice * ((1 + maxQty) / 2))
lootValue = lootValue + (dropChance * 0.01 * itemPrice * ((row.minQuantity + row.maxQuantity) / 2))
    end
end


    totalGP = totalGP + lootValue
totalGP = totalGP + lootValue
  end
end


  return Shared.round(totalGP, 2, 2)
return Shared.round(totalGP, 2, 2)
end
end


function p.getMonsterAverageGP(frame)
function p.getMonsterAverageGP(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[[Category:Pages with script errors]]"
return Shared.printError('No monster with that name found')
  end
end


  return p._getMonsterAverageGP(monster)
return p._getMonsterAverageGP(monster)
end
end


function p.getMonsterEVTable(frame)
function p.getMonsterEVTable(frame)
  local result = '{| class="wikitable sortable"'
local result = '{| class="wikitable sortable"'
  result = result..'\r\n!Monster!!Combat Level!!Average GP'
result = result..'\r\n!Monster!!Combat Level!!Average GP'
  for i, monsterTemp in Shared.skpairs(MonsterData.Monsters) do
for i, monster in ipairs(GameData.rawData.monsters) do
    local monster = Shared.clone(monsterTemp)
if not p._isDungeonOnlyMonster(monster) then
    monster.id = i - 1
local monsterGP = p._getMonsterAverageGP(monster)
    if not p._isDungeonOnlyMonster(monster) then
local combatLevel = p._getMonsterCombatLevel(monster)
      local monsterGP = p._getMonsterAverageGP(monster)
result = result..'\r\n|-\r\n|'..Icons.Icon({p.getMonsterName(monster), type='monster', noicon=true})..'||'..combatLevel..'||'..monsterGP
      local combatLevel = p._getMonsterCombatLevel(monster, 'Combat Level')
end
      result = result..'\r\n|-\r\n|[['..monster.name..']]||'..combatLevel..'||'..monsterGP
end
    end
result = result..'\r\n|}'
  end
return result
  result = result..'\r\n|}'
  return result
end
end


function p.getSlayerTierMonsterTable(frame)
function p.getSlayerTierMonsterTable(frame)
  -- Input validation
-- Input validation
  local tier = frame.args ~= nil and frame.args[1] or frame
local tier = frame.args ~= nil and frame.args[1] or frame
  local slayerTier = nil
local slayerTier = nil


  if tier == nil then
if tier == nil then
    return "ERROR: No tier specified[[Category:Pages with script errors]]"
return Shared.printError('No tier specified')
  end
end


  if tonumber(tier) ~= nil then
if tonumber(tier) ~= nil then
    slayerTier = Constants.getSlayerTierByID(tonumber(tier))
slayerTier = Constants.getSlayerTierByID(tonumber(tier))
  else
else
    slayerTier = Constants.getSlayerTier(tier)
slayerTier = Constants.getSlayerTier(tier)
  end
end


  if slayerTier == nil then
if slayerTier == nil then
    return "ERROR: Invalid slayer tier[[Category:Pages with script errors]]"
return Shared.printError('Invalid slayer tier')
  end
end


  -- Obtain required tier details
-- Obtain required tier details
  local minLevel, maxLevel = slayerTier.minLevel, slayerTier.maxLevel
local minLevel, maxLevel = slayerTier.minLevel, slayerTier.maxLevel
  if maxLevel < 0 then
    maxLevel = nil
  end


  -- Build list of monster IDs
-- Build list of monster IDs
  -- Right now hiddenMonsterIDs is empty
-- Right now hiddenMonsterIDs is empty
  local hiddenMonsterIDs = {}
local hiddenMonsterIDs = {}
  local monsterIDs = {}
local monsterList = GameData.getEntities('monsters',
  for i, monster in Shared.skpairs(MonsterData.Monsters) do
        function(monster)
    if monster.canSlayer and not Shared.contains(hiddenMonsterIDs, i - 1) then
            if monster.canSlayer and not Shared.contains(hiddenMonsterIDs, monster.id) then
      local cmbLevel = p._getMonsterCombatLevel(monster)
                local cmbLevel = p._getMonsterCombatLevel(monster)
      if cmbLevel >= minLevel and (maxLevel == nil or cmbLevel <= maxLevel) then
                return cmbLevel >= minLevel and (maxLevel == nil or cmbLevel <= maxLevel)
        table.insert(monsterIDs, i - 1)
            end
      end
            return false
    end
        end)
  end


  if Shared.tableCount(monsterIDs) == 0 then
if Shared.tableIsEmpty(monsterList) then
    -- Somehow no monsters are in the tier, return nothing
-- Somehow no monsters are in the tier, return nothing
    return ''
return ''
  else
else
    return p._getMonsterTable(monsterIDs, true)
return p._getMonsterTable(monsterList, true)
  end
end
end
end


function p.getFullMonsterTable(frame)
function p.getFullMonsterTable(frame)
  local monsterIDs = {}
return p._getMonsterTable(GameData.rawData.monsters, false)
  for i = 0, Shared.tableCount(MonsterData.Monsters) - 1, 1 do
end
    table.insert(monsterIDs, i)
 
  end
function p._getMonsterTable(monsters, 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="4" | !! colspan="5" |Offensive Stats !! colspan="3" |Evasion Rating !! colspan="4" |')
-- Second header row
table.insert(tableParts, '\r\n|- class="headerRow-1"\r\n!Monster !!Name !!Combat Level ')
table.insert(tableParts, '!!style="padding:0 1em 0 0"|' .. Icons.Icon({'Hitpoints', type='skill'}))
table.insert(tableParts, '!!Attack Speed (s) !!colspan="3"|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, '!!DR!!' .. Icons.Icon({'Coins', notext=true, nolink=true}) .. ' Coins !!Bones !!Locations')
 
-- Generate row per monster
for i, monster in ipairs(monsters) do
-- Avoid processing monsters without equipment stats. These aren't actual
-- monsters, but instead are placeholders such as 'melvorF:RandomITM'
-- and 'melvorTotH:RandomSpiderLair' to denote a random selection from
-- a pool of monsters
if monster.equipmentStats ~= nil then
local cmbLevel = p._getMonsterCombatLevel(monster)
local atkSpeed = p._getMonsterAttackSpeed(monster)
local maxHit = p._getMonsterMaxHit(monster)
local dr = p._getMonsterStat(monster, 'damageReduction')
local drReduce = p._getMonsterDrReduction(monster)
local accR = p._getMonsterAR(monster)
local evaR = {p._getMonsterER(monster, "Melee"), p._getMonsterER(monster, "Ranged"), p._getMonsterER(monster, "Magic")}
local gpTxt = nil
if monster.gpDrops.min >= monster.gpDrops.max then
gpTxt = Shared.formatnum(monster.gpDrops.min)
else
gpTxt = Shared.formatnum(monster.gpDrops.min) .. ' - ' .. Shared.formatnum(monster.gpDrops.max)
end
local bones = p._getMonsterBones(monster)
local boneTxt = (bones ~= nil and Icons.Icon({bones.item.name, type='item', notext=true})) or 'None'
table.insert(tableParts, '\r\n|-\r\n|style="text-align: center;" |' .. Icons.Icon({p.getMonsterName(monster), type='monster', size=50, notext=true}))
table.insert(tableParts, '\r\n|style="text-align:left" |' .. Icons.Icon({p.getMonsterName(monster), type='monster', noicon=true}))
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="' .. p._getMonsterHP(monster) .. '" |' .. Shared.formatnum(p._getMonsterHP(monster)))
table.insert(tableParts, '\r\n|style="text-align:right" data-sort-value="' .. atkSpeed .. '" |' .. Shared.round(atkSpeed, 1, 1))
if drReduce > 0 then
table.insert(tableParts, '\r\n|style="text-align:right" data-sort-value="' .. maxHit .. '"| -' .. drReduce..'% DR')
table.insert(tableParts, '\r\n|style="text-align:right;border-right:hidden" |' .. p._getMonsterStyleIcon({monster, notext=true}))
table.insert(tableParts, '\r\n|style="text-align:right" |' .. Shared.formatnum(maxHit))
else
table.insert(tableParts, '\r\n|style="text-align:right;border-right:hidden" colspan="2" data-sort-value="' .. maxHit .. '"|' .. p._getMonsterStyleIcon({monster, notext=true}))
table.insert(tableParts, '\r\n|style="text-align:right"|' .. Shared.formatnum(maxHit))
end
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="' .. dr .. '" |' .. dr..'%')
table.insert(tableParts, '\r\n|style="text-align:right" data-sort-value="' .. (monster.gpDrops.min + monster.gpDrops.max) / 2 .. '" |' .. gpTxt)
table.insert(tableParts, '\r\n|style="text-align:center" |' .. boneTxt)
table.insert(tableParts, '\r\n|style="text-align:right;width:190px" |' .. p._getMonsterAreas(monster, hideDungeons))
end
end
 
table.insert(tableParts, '\r\n|}')
return table.concat(tableParts)
end
 
function p.getMattMonsterTable(frame)
--Making a single function for getting a table of monsters given a list of IDs.
local tableParts = {}
table.insert(tableParts, '{| class="wikitable sortable stickyHeader"')
-- 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, '!!' .. Icons.Icon({'Coins', notext=true, nolink=true}) .. ' Coins !!Avg. Kill Value!!Locations')


  return p._getMonsterTable(monsterIDs, false)
-- Generate row per monster
for i, monster in ipairs(GameData.rawData.monsters) do
if p.getMonsterName(monster) ~= nil then
local cmbLevel = p._getMonsterCombatLevel(monster)
local gpTxt = nil
if monster.gpDrops.min >= monster.gpDrops.max then
gpTxt = Shared.formatnum(monster.gpDrops.min)
else
gpTxt = Shared.formatnum(monster.gpDrops.min) .. ' - ' .. Shared.formatnum(monster.gpDrops.max)
end
local lootVal = p._getMonsterLootValue(monster)
local lootTxt = '0'
if lootVal ~= 0 then
lootTxt = Shared.formatnum(Shared.round(lootVal, 2, 2))
end
table.insert(tableParts, '\r\n|-\r\n|style="text-align: center;" |' .. Icons.Icon({p.getMonsterName(monster), type='monster', size=50, notext=true}))
table.insert(tableParts, '\r\n|style="text-align:left" |' .. Icons.Icon({p.getMonsterName(monster), type='monster', noicon=true}))
table.insert(tableParts, '\r\n|style="text-align:right" |' .. monster.id)
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="' .. p._getMonsterHP(monster) .. '" |' .. Shared.formatnum(p._getMonsterHP(monster)))
table.insert(tableParts, '\r\n|style="text-align:right" data-sort-value="' .. (monster.gpDrops.min + monster.gpDrops.max) / 2 .. '" |' .. gpTxt)
table.insert(tableParts, '\r\n|style="text-align:right" data-sort-value="' .. lootVal .. '" |' .. lootTxt)
table.insert(tableParts, '\r\n|style="text-align:right;width:190px" |' .. p._getMonsterAreas(monster, false))
end
end
 
table.insert(tableParts, '\r\n|}')
return table.concat(tableParts)
end
end


function p._getMonsterTable(monsterIDs, excludeDungeons)
function p.getMattMonsterTableV2(frame)
  --Making a single function for getting a table of monsters given a list of IDs.
--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 = {}
  local tableParts = {}
table.insert(tableParts, '{| class="wikitable sortable stickyHeader"')
  table.insert(tableParts, '{| class="wikitable sortable stickyHeader"')
-- Second header row
  -- First header row
table.insert(tableParts, '\r\n|- class="headerRow-1"\r\n!Monster !!Name !!Combat Level ')
  table.insert(tableParts, '\r\n|- class="headerRow-0"\r\n! colspan="5" | !! colspan="4" |Offensive Stats !! colspan="3" |Evasion Rating !! colspan="4" |')
table.insert(tableParts, '!!style="padding:0 1em 0 0"|' .. Icons.Icon({'Hitpoints', type='skill'}))
  -- Second header row
table.insert(tableParts, '!!style="padding:0 1em 0 0"|' .. Icons.Icon({'Defence', type='skill', notext=true}))
  table.insert(tableParts, '\r\n|- class="headerRow-1"\r\n!Monster !!Name !!ID !!Combat Level ')
table.insert(tableParts, '!!Attack Speed (s) !!colspan="2"|Max Hit !!Accuracy ')
  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
-- table.insert(tableParts, '!!style="padding:0 1em 0 0"|' .. Icons.Icon({'Ranged', type='skill', notext=true}))
  for i, monsterID in Shared.skpairs(monsterIDs) do
-- table.insert(tableParts, '!!style="padding:0 1em 0 0"|' .. Icons.Icon({'Magic', type='skill', notext=true}))
    local monster = p.getMonsterByID(monsterID)
table.insert(tableParts, '!!' .. Icons.Icon({'Coins', notext=true, nolink=true}) .. ' Coins !!Avg. Kill Value !!Bones')
    local cmbLevel = p._getMonsterCombatLevel(monster)
 
    local atkSpeed = p._getMonsterAttackSpeed(monster)
-- Generate row per monster
    local maxHit = p._getMonsterMaxHit(monster)
for i, monster in ipairs(GameData.rawData.monsters) do
    local accR = p._getMonsterAR(monster)
if p.getMonsterName(monster) ~= nil then
    local evaR = {p._getMonsterER({monster, "Melee"}), p._getMonsterER({monster, "Ranged"}), p._getMonsterER({monster, "Magic"})}
local cmbLevel = p._getMonsterCombatLevel(monster)
    local lootChance = monster.lootChance ~= nil and monster.lootChance or 100
    local gpRange = {0, 0}
local gpTxt = nil
    if monster.dropCoins ~= nil and monster.dropCoins[2] > 1 then
if monster.gpDrops.min >= monster.gpDrops.max then
      gpRange = {monster.dropCoins[1], monster.dropCoins[2] - 1}
gpTxt = Shared.formatnum(monster.gpDrops.min)
    end
else
    local gpTxt = nil
gpTxt = Shared.formatnum(monster.gpDrops.min) .. ' - ' .. Shared.formatnum(monster.gpDrops.max)
    if gpRange[1] >= gpRange[2] then
end
      gpTxt = Icons.GP(gpRange[1])
    else
local lootVal = p._getMonsterLootValue(monster)
      gpTxt = Icons.GP(gpRange[1], gpRange[2])
local lootTxt = '0'
    end
if lootVal ~= 0 then
    local boneTxt = 'None'
lootTxt = Shared.formatnum(Shared.round(lootVal, 2, 2))
    if monster.bones ~= nil then
end
      local bones = Items.getItemByID(monster.bones)
      boneTxt = Icons.Icon({bones.name, type='item', notext=true})
local atkSpeed = p._getMonsterAttackSpeed(monster)
    end
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 bones = p._getMonsterBones(monster)
local boneTxt = (bones ~= nil and Icons.Icon({bones.item.name, type='item', notext=true})) or 'None'
table.insert(tableParts, '\r\n|-\r\n|style="text-align: center;" |' .. Icons.Icon({p.getMonsterName(monster), type='monster', size=50, notext=true}))
table.insert(tableParts, '\r\n|style="text-align:left" |' .. Icons.Icon({p.getMonsterName(monster), type='monster', noicon=true}))
-- table.insert(tableParts, '\r\n|style="text-align:right" |' .. monster.id)
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="' .. p._getMonsterHP(monster) .. '" |' .. Shared.formatnum(p._getMonsterHP(monster)))
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="' .. atkSpeed .. '" |' .. Shared.round(atkSpeed, 1, 1))
table.insert(tableParts, '\r\n|style="text-align:center;border-right:hidden" |' .. p._getMonsterStyleIcon({monster, notext=true}))
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[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="' .. (monster.gpDrops.min + monster.gpDrops.max) / 2 .. '" |' .. gpTxt)
table.insert(tableParts, '\r\n|style="text-align:right" data-sort-value="' .. lootVal .. '" |' .. lootTxt)
table.insert(tableParts, '\r\n|style="text-align:center" |' .. boneTxt)
-- table.insert(tableParts, '\r\n|style="text-align:right;width:190px" |' .. p._getMonsterAreas(monster, hideDungeons))
end
end
 
table.insert(tableParts, '\r\n|}')
return table.concat(tableParts)
end
 
function p.getSpecialAttackTable(frame)
local spAttTable = {}
 
for i, monster in ipairs(GameData.rawData.monsters) do
if monster.specialAttacks ~= nil and not Shared.tableIsEmpty(monster.specialAttacks) then
local overrideChance = (monster.overrideSpecialChances ~= nil and Shared.tableCount(monster.overrideSpecialChances) > 0)
for j, spAttID in ipairs(monster.specialAttacks) do
                local spAtt = GameData.getEntityByID('attacks', spAttID)
local attChance = (overrideChance and monster.overrideSpecialChances[j] or spAtt.defaultChance)
if spAttTable[spAtt.id] == nil then
spAttTable[spAtt.id] = { ['defn'] = spAtt, ['icons'] = {} }
end
if spAttTable[spAtt.id]['icons'][attChance] == nil then
spAttTable[spAtt.id]['icons'][attChance] = {}
end
table.insert(spAttTable[spAtt.id]['icons'][attChance], Icons.Icon({ p.getMonsterName(monster), type = 'monster' }))
end
end
end
 
local resultPart = {}
table.insert(resultPart, '{|class="wikitable sortable stickyHeader"')
table.insert(resultPart, '\r\n|- class="headerRow-0"')
table.insert(resultPart, '\r\n!Name!!style="min-width:225px"|Monsters!!Chance!!Effect')
 
for i, spAttData in Shared.skpairs(spAttTable) do
local spAtt = spAttData.defn
local firstRow = true
local rowsSpanned = Shared.tableCount(spAttData.icons)
local rowSuffix = ''
if rowsSpanned > 1 then
rowSuffix = '|rowspan="' .. rowsSpanned .. '"'
end
for chance, iconList in Shared.skpairs(spAttData.icons) do
table.insert(resultPart, '\r\n|-')
if firstRow then
table.insert(resultPart, '\r\n' .. rowSuffix .. '| ' .. spAtt.name)
end
table.insert(resultPart, '\r\n|data-sort-value="' .. spAtt.name .. '"| ' .. table.concat(iconList, '<br/>'))
table.insert(resultPart, '\r\n|data-sort-value="' .. chance .. '"| ' .. Shared.round(chance, 2, 0) .. '%')
if firstRow then
table.insert(resultPart, '\r\n' .. rowSuffix .. '| ' .. spAtt.description)
firstRow = false
end
end
end
table.insert(resultPart, '\r\n|}')
 
return table.concat(resultPart)
end
 
--NOTE: This is not a function that should be called directly. It generates text to be pasted into Chest Loot TablesTemplate:MonsterLootTables
--It exists because I'm too lazy to manually type up all the new monsters - User:Falterfire
function p.getMonsterLootTableText()
local getAreaText = function(area)
local outArray = {}
table.insert(outArray, "==={{ZoneIcon|"..area.name.."|size=50}}===")
table.insert(outArray, "")
for i, monsterID in ipairs(area.monsterIDs) do
local monster = p.getMonsterByID(monsterID)
table.insert(outArray, "===={{MonsterIcon|"..p.getMonsterName(monster).."|size=40}}====")
table.insert(outArray, "{{MonsterDrops|"..p.getMonsterName(monster).."|size=40}}")
end
return table.concat(outArray, "\r\n")
end
local fullArray = {}
local areaArray = Areas.getAreas(function(a) return a.type == 'combatArea' end)
for i, area in ipairs(areaArray) do
table.insert(fullArray, getAreaText(area))
end
areaArray = Areas.getAreas(function(a) return a.type == 'slayerArea' end)
for i, area in ipairs(areaArray) do
table.insert(fullArray, getAreaText(area))
end
return table.concat(fullArray, "\r\n\r\n----\r\n")
end
 
--NOTE: This is not a function that should be called directly. It generates text to be pasted into Chest Loot Tables
--It exists because I'm too lazy to manually type up all the new chests - User:Falterfire
function p.getChestLootTables()
local items = Items.getItems(function(item) return item.dropTable ~= nil end)
local outArray = {}
for i, item in ipairs(items) do
table.insert(outArray, "==={{ItemIcon|"..item.name.."|size=30}}===")
table.insert(outArray, "{{ChestDrops|"..item.name.."}}")
end
return table.concat(outArray, "\r\n")
end


    table.insert(tableParts, '\r\n|-\r\n|style="text-align: center;" |' .. Icons.Icon({monster.name, type='monster', size=50, notext=true}))
--Returns the expansion icon for the item if it has one
    table.insert(tableParts, '\r\n|style="text-align:left" |[[' .. monster.name .. ']]')
function p.getExpansionIcon(frame)
    table.insert(tableParts, '\r\n|style="text-align:right" |' .. monsterID)
local monsterName = frame.args ~= nil and frame.args[1] or frame
    table.insert(tableParts, '\r\n|style="text-align:right" data-sort-value="' .. cmbLevel .. '" |' .. Shared.formatnum(cmbLevel))
local monster = p.getMonster(monsterName)
    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|}')
if monster == nil then
  return table.concat(tableParts)
return Shared.printError('No monster with that name found')
end
return Icons.getExpansionIcon(monster.id)
end
end


return p
return p
3

edits