Module:CombatAreas/AreaTables: Difference between revisions

From Melvor Idle
(_getDungeonRewards: Safely handle cases where boss monsters have no loot table)
(Add support for stronghold rewards in _getDungeonRewards)
 
(22 intermediate revisions by 4 users not shown)
Line 2: Line 2:


local p = {}
local p = {}
local AreaData = mw.loadData('Module:CombatAreas/data')


local Constants = require('Module:Constants')
local Constants = require('Module:Constants')
local Common = require('Module:Common')
local Shared = require('Module:Shared')
local Shared = require('Module:Shared')
local GameData = require('Module:GameData')
local Icons = require('Module:Icons')
local Icons = require('Module:Icons')
local Items = require('Module:Items')
local Items = require('Module:Items')
local Monsters = require('Module:Monsters')
local Monsters = require('Module:Monsters')
local CombatAreas = require('Module:CombatAreas')
local Pets = require('Module:Pets')
local Pets = require('Module:Pets')
local Areas = require('Module:CombatAreas')
local Num = require('Module:Number')


function p.getLowHighLevels(idList)
function p.getLowHighLevels(idList)
  local lowLevel = 1000000
local lowLevel = 1000000
  local highLevel = 0
local highLevel = 0
  for i, monID in Shared.skpairs(idList) do
for i, monID in ipairs(idList) do
    local monster = Monsters.getMonsterByID(monID)
local monster = Monsters.getMonsterByID(monID)
    local cmbLevel = Monsters._getMonsterCombatLevel(monster)
local cmbLevel = Monsters._getMonsterCombatLevel(monster)
    if cmbLevel < lowLevel then lowLevel = cmbLevel end
if cmbLevel < lowLevel then lowLevel = cmbLevel end
    if cmbLevel > highLevel then highLevel = cmbLevel end
if cmbLevel > highLevel then highLevel = cmbLevel end
  end
end
  return lowLevel, highLevel
return lowLevel, highLevel
end
end


function p.getCombatAreaTable()
function p.getAreaCategory(area)
  local result = '{| class="wikitable sortable stickyHeader"'
for _, areaCat in ipairs(GameData.rawData.combatAreaCategories) do
  result = result..'\r\n|- class="headerRow-0"'
if areaCat.areas ~= nil and Shared.contains(areaCat.areas, area.id) then
  result = result..'\r\n!colspan="2"|Zone!!Difficulty!!Lowest Monster Level!!Highest Monster Level'
return areaCat
end
end
end


  local combatAreas = Shared.clone(AreaData.combatAreas)
function p.getAreaCategoriesFromAreas(areas)
  table.sort(combatAreas, function(a, b)  
local areaCatIDs = {}
              if a.difficulty[1] ~= b.difficulty[1] then
for i, area in ipairs(areas) do
                return a.difficulty[1] < b.difficulty[1]
local areaCat = p.getAreaCategory(area)
              elseif a.difficulty[2] == nil then
if areaCat ~= nil and areaCat.id ~= nil then
                return true
areaCatIDs[areaCat.id] = true
              elseif b.difficulty[2] == nil then
end
                return false
end
              elseif a.difficulty[2] ~= b.difficulty[2] then
return GameData.getEntities('combatAreaCategories',
                return a.difficulty[2] < b.difficulty[2]
function(obj)
              else
return obj.id ~= nil and areaCatIDs[obj.id] ~= nil
                return a.name < b.name
end
              end
)
            end)
end


  for i, area in Shared.skpairs(combatAreas) do
local function getAreasForTable(areaDataKey, categoryName, defaultCategoryID)
    result = result..'\r\n|-'
local areaCategories = p.getAreaCategoriesFromAreas(GameData.rawData[areaDataKey])
    result = result..'\r\n|'..Icons.Icon({area.name, type='combat', size='50', notext=true})
local areaCategory = nil
    result = result..'||'..Icons.Icon({area.name, type='combat', noicon=true})
    local diff1 = Constants.getDifficultyString(area.difficulty[1])
    local diff2 = Constants.getDifficultyString(area.difficulty[2])
    result = result..'||data-sort-value="'..area.difficulty[1]..'"|'..diff1
    if diff1 ~= diff2 and diff2 ~= nil then result = result..' - '..diff2 end
    local lowLvl, highLvl = p.getLowHighLevels(area.monsters)
    result = result..'||'..lowLvl..'||'..highLvl
  end


  result = result..'\r\n|}'
if categoryName == nil or categoryName == '' then
-- Default area category
areaCategory = GameData.getEntityByID(areaCategories, defaultCategoryID)
else
areaCategory = GameData.getEntityByName(areaCategories, categoryName)
end
if areaCategory == nil then
local catNames = {}
for _, areaCat in ipairs(areaCategories) do
if areaCat.name ~= nil then
table.insert(catNames, areaCat.name)
end
end
return nil, Shared.printError('Invalid area category, choose any of: ' .. table.concat(catNames, ', '))
end


  return result
local areas = GameData.getEntities(areaDataKey,
function(obj)
return obj.id ~= nil and Shared.contains(areaCategory.areas, obj.id)
end
)
areas = GameData.sortByOrderTable(areas, areaCategory.areas)
return areas, nil
end
end


function p.getSlayerAreaTable()
function p.getCombatAreaTable(frame)
  local result = '{| class="wikitable sortable stickyHeader"'
local args = frame.args ~= nil and frame.args or frame
  result = result..'\r\n|- class="headerRow-0"'
local categoryName = args[1]
  result = result..'\r\n!colspan="2"|Zone!!Difficulty!!Highest Monster Level!!Requirements!!Area Effect'
 
  local slayerAreas = Shared.clone(AreaData.slayerAreas)
  table.sort(slayerAreas, function(a, b)
              if a.difficulty[1] ~= b.difficulty[1] then
                return a.difficulty[1] < b.difficulty[1]
              elseif a.difficulty[2] == nil then
                return true
              elseif b.difficulty[2] == nil then
                return false
              elseif a.difficulty[2] ~= b.difficulty[2] then
                return a.difficulty[2] < b.difficulty[2]
              else
                return a.name < b.name
              end
            end)


  for i, area in Shared.skpairs(slayerAreas) do
local areas, errText = getAreasForTable('combatAreas', categoryName, 'melvorD:CombatAreas')
    result = result..'\r\n|-'
if errText ~= nil then
    result = result..'\r\n|'..Icons.Icon({area.name, type='slayer', size='50', notext=true})
return errText
    result = result..'||'..Icons.Icon({area.name, type='slayer', noicon=true})
elseif areas == nil then
    local diff1 = Constants.getDifficultyString(area.difficulty[1])
return ''
    local diff2 = Constants.getDifficultyString(area.difficulty[2])
end
    result = result..'||data-sort-value="'..area.difficulty[1]..'"|'..diff1
    if diff1 ~= diff2 and diff2 ~= nil then result = result..' - '..diff2 end
    local lowLvl, highLvl = p.getLowHighLevels(area.monsters)
    result = result..'||'..highLvl


    result = result..'||'..CombatAreas._getAreaRequirements(area)
local result = '{| class="wikitable sortable stickyHeader"'
    result = result..'||'
result = result..'\r\n|- class="headerRow-0"'
    if area.areaEffectDescription ~= nil then
result = result..'\r\n!colspan="2"|Zone!!Difficulty!!Lowest Monster Level!!Highest Monster Level!!Requirements'
      result = result..area.areaEffectDescription
    end
  end


  result = result..'\r\n|}'
for i, area in ipairs(areas) do
result = result..'\r\n|-'
result = result..'\r\n|class="table-img" data-sort-value="' .. area.name .. '"| '..Icons.Icon({area.name, type='combatArea', size='50', notext=true})
result = result..'||' .. Icons.getExpansionIcon(area.id) .. Icons.Icon({area.name, type='combatArea', noicon=true})
result = result..'||data-sort-value="'..area.difficulty[1]..'"|'..Areas._getAreaStat(area, 'difficulty')
local lowLvl, highLvl = p.getLowHighLevels(area.monsterIDs)
result = result .. '||data-sort-value="' .. lowLvl .. '"| ' .. Num.formatnum(lowLvl)
result = result .. '||data-sort-value="' .. highLvl .. '"| ' .. Num.formatnum(highLvl)
local reqText = Areas._getAreaRequirements(area)
if reqText == nil or reqText == '' then
result = result .. '||class="table-na"| None'
else
result = result .. '||' .. reqText
end
end
result = result..'\r\n|}'


  return result
return result
end
end


function p._getDungeonRewards(area, asList)
function p.getSlayerAreaTable(frame)
  if asList == nil then
local args = frame.args ~= nil and frame.args or frame
    asList = true
local categoryName = args[1]
  elseif type(asList) == 'string' then
    asList = asList.upper ~= 'FALSE'
  end


  local bossMonster = Monsters.getMonsterByID(area.monsters[Shared.tableCount(area.monsters)])
local areas, errText = getAreasForTable('slayerAreas', categoryName, 'melvorF:SlayerAreas')
  local gpMin = bossMonster.dropCoins[1]
if errText ~= nil then
  local gpMax = bossMonster.dropCoins[2] - 1
return errText
  local chestID, chestQty, theChest = nil, nil, nil
elseif areas == nil then
  if bossMonster.lootTable ~= nil and Shared.tableCount(bossMonster.lootTable) > 0 then
return ''
  chestID = bossMonster.lootTable[1][1]
end
  chestQty = bossMonster.lootTable[1][3]
  theChest = Items.getItemByID(chestID)
  end
  local chr = asList and '* ' or ''
  local rewardList = {}


  if gpMin > 0 and gpMax > 0 then
local result = '{| class="wikitable sortable stickyHeader"'
    table.insert(rewardList, chr..Icons.GP(gpMin, gpMax))
result = result..'\r\n|- class="headerRow-0"'
  end
result = result..'\r\n!colspan="2"|Zone!!Difficulty!!Highest Monster Level!!Requirements!!Area Effect'
  if theChest ~= nil then
    table.insert(rewardList, chr..Icons.Icon({theChest.name, type='item', qty=chestQty}))
  end
  if area.name == 'Volcanic Cave' then
    table.insert(rewardList, chr..Icons.Icon({'Fire Cape', type='item', qty=1}))
  elseif area.name == 'Infernal Stronghold' then
    table.insert(rewardList, chr..Icons.Icon({'Infernal Cape', type='item', qty=1}))
  end


  if asList then
for i, area in ipairs(areas) do
    return table.concat(rewardList, '\r\n')
result = result..'\r\n|-'
  else
result = result..'\r\n|class="table-img" data-sort-value="' .. area.name .. '"| '..Icons.Icon({area.name, type='slayer', size='50', notext=true})
    return table.concat(rewardList, '<br/>')
result = result..'||' .. Icons.getExpansionIcon(area.id) .. Icons.Icon({area.name, type='slayer', noicon=true})
  end
result = result..'||data-sort-value="'..area.difficulty[1]..'"|'..Areas._getAreaStat(area, 'difficulty')
end
local lowLvl, highLvl = p.getLowHighLevels(area.monsterIDs)
result = result..'||data-sort-value="' .. highLvl .. '"| ' .. Num.formatnum(highLvl)
local reqText = Areas._getAreaRequirements(area)
if reqText == nil or reqText == '' then
result = result .. '||class="table-na"| None'
else
result = result .. '||' .. reqText
end
local effectText = Areas._getAreaStat(area, 'areaEffectDesc')
local classText = ((effectText == nil or effectText == 'None') and 'class="table-na"|') or ''
result = result..'||' .. classText .. ' ' .. effectText
end


function p.getDungeonRewards(frame)
result = result..'\r\n|}'
  local areaName = frame.args ~= nil and frame.args[1] or frame
  local asList = frame.args ~= nil and frame.args[2] or true
  local area = CombatAreas.getArea(areaName)
  if area == nil then
    return "ERROR: Could not find an area named "..areaName..'[[Category:Pages with script errors]]'
  end


  if area.type == 'dungeon' then
return result
    return p._getDungeonRewards(area, asList)
  else
    return "ERROR: "..areaName.." is not a dungeon[[Category:Pages with script errors]]"
  end
end
end


function p.getDungeonTable(frame)
function p._getDungeonRewards(area, asList)
  local dungeons = CombatAreas.getAreas(function(area) return area.type == 'dungeon' end)
if asList == nil then
asList = true
elseif type(asList) == 'string' then
asList = string.upper(asList) ~= 'FALSE'
end


  local result = '{| class="wikitable sortable stickyHeader"'
local lineSep = (asList and '\n' or '<br>')
  result = result..'\r\n|-class="headerRow-0"'
local formatLine = nil
  result = result..'\r\n!colspan="2"|Dungeon!!Difficulty!!Monsters!!Boss Level!!Reward(s)!!Boss Pet'
if asList then
 
formatLine = function(text) return '* ' .. text end
  table.sort(dungeons, function(a, b)
end
                      if a.difficulty[1] ~= b.difficulty[1] then
                        return a.difficulty[1] < b.difficulty[1]
-- Currency rewards
                      else
local rewardCurrency = {}
                        return a.id < b.id
if area.eventID == nil then
                      end end)
local bossMonster = Monsters.getMonsterByID(area.monsterIDs[Shared.tableCount(area.monsterIDs)])
  for i, dung in Shared.skpairs(dungeons) do
rewardCurrency = bossMonster.currencyDrops
    result = result..'\r\n|-'
end
    result = result..'\r\n|data-sort-value="'..dung.name..'"|'..Icons.Icon({dung.name, type='dungeon', size='50', notext=true})
    result = result..'||'..Icons.Icon({dung.name, type='dungeon', noicon=true})
-- Item rewards
    result = result..'||data-sort-value="'..dung.difficulty[1]..'"|'..CombatAreas._getAreaStat(dung, 'difficulty')
local itemLists = {}
    result = result..'||'..Shared.tableCount(dung.monsters)
if type(area.rewardItemIDs) == 'table' then
table.insert(itemLists, area.rewardItemIDs)
elseif area.type == 'stronghold' then
-- This won't show which tier each reward comes from
-- but there doesn't seem to be an easy way to do that
local enhancementList = {}
for tier, tierData in pairs(area.tiers) do
if type(tierData.rewards) == 'table' and type(tierData.rewards.items) == 'table' then
for i, reward in ipairs(tierData.rewards.items) do
table.insert(enhancementList, reward.id)
end
end
end
if not Shared.tableIsEmpty(enhancementList) then
table.insert(itemLists, enhancementList)
end
end
if area.eventID ~= nil then
local event = GameData.getEntityByID('combatEvents', area.eventID)
if event ~= nil and type(event.itemRewardIDs) == 'table' then
table.insert(itemLists, event.itemRewardIDs)
end
end


    local boss = Monsters.getMonsterByID(dung.monsters[Shared.tableCount(dung.monsters)])
local rewardItem = {}
    result = result..'||'..Monsters._getMonsterCombatLevel(boss)
for i, itemList in ipairs(itemLists) do
   
for j, rewardID in ipairs(itemList) do
    result = result..'||'..p._getDungeonRewards(dung, false)
table.insert(rewardItem, {
    if dung.petID ~= nil then
["id"] = rewardID,
      local pet = Pets.getPetByID(dung.petID)
["quantity"] = 1
      result = result..'||data-sort-value="'..pet.name..'"|'..Icons.Icon({pet.name, type='pet'})
})
    else
end
      result = result..'|| '
end
    end
  end


  result = result..'\r\n|}'
return Common.getCostString({ ["items"] = rewardItem, ["currencies"] = rewardCurrency}, '', formatLine, lineSep)
  return result
end
end


function p._getDungeonDRTable(dung, mode, doStuns)
function p.getDungeonRewards(frame)
  local AutoEatVals = {T1 = 0.2, T2 = 0.3, T3 = 0.4, T3W = 0.45}
local areaName = frame.args ~= nil and frame.args[1] or frame
  --NOTE: Due to Agility Obstacles, this value is a tad arbitrary
local asList = frame.args ~= nil and frame.args[2] or true
  local MaxViableHP = 1120
local area = Areas.getArea(areaName)
  --This is the highest DR to list as possible. Value might be slightly off right now, currently just setting up value for testing
if area == nil then
  local MaxViableDR = 81
return Shared.printError('Could not find an area named "' .. areaName .. '"')
  --This is the highest DR row shown. This should be higher than MaxViableDR
end
  local MaxVisibleDR = 85


  if doStuns == nil then
if area.type == 'dungeon' or area.type == 'abyssDepth' or area.type == 'stronghold' then
    doStuns = true
return p._getDungeonRewards(area, asList)
  elseif type(doStuns) == 'string' then
else
    doStuns = string.upper(doStuns) == 'TRUE'
return Shared.printError('"' .. areaName .. '" is not a dungeon')
  end
end
end


  --First, figure out what our max hit for each style is
function p.getDungeonTable(frame)
  local MaxHits = { Melee = 0, Ranged = 0, Magic = 0 }
local args = frame.args ~= nil and frame.args or frame
  for i, monsterID in Shared.skpairs(dung.monsters) do
local categoryName = args[1]
    local monster = Monsters.getMonsterByID(monsterID)
    local styleName = Constants.getCombatStyleName(monster.attackType)
    local maxHit = Monsters._getMonsterMaxHit(monster, doStuns)
    if maxHit > MaxHits[styleName] then
      MaxHits[styleName] = maxHit
    end
  end


  --Then, figure out the DR row to start with
local areas, errText = getAreasForTable('dungeons', categoryName, 'melvorD:Dungeons')
  --This is the DR that is one lower than the lowest possible DR for the best style
if errText ~= nil then
  local StyleArray = {"Melee", "Ranged", "Magic"}
return errText
  local EatThreshold = math.floor(MaxViableHP * AutoEatVals.T3W)
elseif areas == nil then
  local minDR = 100
return ''
  for i, playerStyle in Shared.skpairs(StyleArray) do
end
    local maxStyleDR = 0
    for enemyStyle, styleHit in Shared.skpairs(MaxHits) do
      if styleHit > 0 and styleHit > EatThreshold then
        local styleDR = math.ceil((1 - (EatThreshold / styleHit)) * 100)
        styleDR = math.ceil(styleDR / Constants.getTriangleDRModifier(playerStyle, enemyStyle, mode))
        maxStyleDR = math.max(maxStyleDR, styleDR)
      end
    end
    minDR = math.min(minDR, maxStyleDR)
  end


  minDR = minDR - 1
local result = '{| class="wikitable sortable stickyHeader"'
result = result..'\r\n|-class="headerRow-0"'
result = result..'\r\n!colspan="2"|Dungeon!!Difficulty!!Monsters!!Boss Level!!Requirements!!Rewards!!Boss Pet'


  --Finally, build the table using those starting points
for i, dung in ipairs(areas) do
  local StyleHeader = "Melee!!Ranged!!Magic"
local monsterCount = Shared.tableCount(dung.monsterIDs)
  StyleHeader = StyleHeader..'!!'..StyleHeader..'!!'..StyleHeader..'!!'..StyleHeader
local boss = Monsters.getMonsterByID(dung.monsterIDs[monsterCount])


  local result = '{| class="wikitable stickyHeader"'
result = result..'\r\n|-'
  result = result..'\r\n|-class="headerRow-0"'
result = result..'\r\n|class="table-img" data-sort-value="'..dung.name..'"|'..Icons.Icon({dung.name, type='dungeon', size='50', notext=true})
  result = result..'\r\n!Pre-Triangle DR!!colspan=3|AE T3 + Wasteful!!colspan=3|Auto Eat Tier 3!!colspan=3|Auto Eat Tier 2!!colspan=3|Auto Eat Tier 1'
result = result..'||' .. Icons.getExpansionIcon(dung.id) .. Icons.Icon({dung.name, type='dungeon', noicon=true})
  result = result..'\r\n|-class="headerRow-0"'
result = result..'||data-sort-value="'..dung.difficulty[1]..'"|'..Areas._getAreaStat(dung, 'difficulty')
  result = result..'\r\n!DR %!!'..StyleHeader
result = result..'||'..monsterCount
result = result..'||'..Num.formatnum(Monsters._getMonsterCombatLevel(boss))
local reqText = Areas._getAreaRequirements(dung)
if reqText == nil or reqText == '' then
result = result .. '||class="table-na"| None'
else
result = result .. '||' .. reqText
end
result = result..'||'..p._getDungeonRewards(dung, false)
local petAdded = false
if dung.pet ~= nil then
local pet = GameData.getEntityByID('pets', dung.pet.petID)
if pet ~= nil then
result = result..'||data-sort-value="'..pet.name..'"|'..Icons.Icon({pet.name, type='pet'})
local petDrop = Pets._getPetChance(pet)
if petDrop ~= nil then
result = result..'<br/>'..petDrop
end
petAdded = true
end
end
if not petAdded then
result = result..'||class="table-na"| None'
end
end


  local getHpForStyle = function(playerStyle, autoEat, playerDR)
result = result..'\r\n|}'
                         
return result
                        end
end


  for dr = minDR, MaxVisibleDR, 1 do
local function getAreaOrderTable(areaCategoryID)
   
local areaCategory = GameData.getEntityByID('combatAreaCategories', areaCategoryID)
  end
if areaCategory == nil then
 
return {}
  result = result..'\r\n|}'
else
 
return areaCategory.areas
  return result
end
end
end
 
function p.buildCombatNavText(frame)
function p.getDungeonDRTable(frame)
--This isn't actually called on any pages but instead exists to save me time on updating Template:CombatNav for the new expansion
  local dungName = frame.args ~= nil and frame.args[1] or frame[1]
local areas = GameData.sortByOrderTable(GameData.rawData.slayerAreas, getAreaOrderTable('melvorD:SlayerAreas'))
  local mode = frame.args ~= nil and frame.args[2] or frame[2]
local outArray = {}
  local doStuns = frame.args ~= nil and frame.args[3] or frame[3]
for i, area in ipairs(areas) do
  local dung = CombatAreas.getArea(dungName)
local id = area.id
 
if Shared.startsWith(id, 'melvorTotH') then
  if dung == nil then
table.insert(outArray, '|-')
    return 'ERROR: Invalid dungeon name'
table.insert(outArray, '| {{TotH}} {{ZoneIcon|'..area.name..'}}')
  end
local monsterArray = {}
 
for j, monsterID in ipairs(area.monsterIDs) do
  return p._getDungeonDRTable(dung, mode, doStuns)
local monster = Monsters.getMonsterByID(monsterID)
table.insert(monsterArray, '{{MonsterIcon|'..monster.name..'}}')
end
table.insert(outArray, '| class="center" style="vertical-align:middle;" | '..table.concat(monsterArray, ' {{*}} '))
end
end
areas = GameData.sortByOrderTable(GameData.rawData.dungeons, getAreaOrderTable('melvorD:Dungeons'))
for i, area in ipairs(areas) do
local id = area.id
if Shared.startsWith(id, 'melvorTotH') then
table.insert(outArray, '|-')
table.insert(outArray, '| {{TotH}} {{ZoneIcon|'..area.name..'}}')
local monsterArray = {}
local mCheck = {}
for j, monsterID in ipairs(area.monsterIDs) do
local monster = Monsters.getMonsterByID(monsterID)
if mCheck[monster.name] == nil then
table.insert(monsterArray, '{{MonsterIcon|'..monster.name..'}}')
mCheck[monster.name] = 1
end
end
table.insert(outArray, '| class="center" style="vertical-align:middle;" | '..table.concat(monsterArray, ' {{*}} '))
end
end
return table.concat(outArray, '\r\n')
end
end


return p
return p

Latest revision as of 22:38, 7 August 2024

Documentation for this module may be created at Module:CombatAreas/AreaTables/doc

--Splitting this out so I can make Module:Monsters calls (Monsters calls CombatAreas, so this prevents a loop)

local p = {}

local Constants = require('Module:Constants')
local Common = require('Module:Common')
local Shared = require('Module:Shared')
local GameData = require('Module:GameData')
local Icons = require('Module:Icons')
local Items = require('Module:Items')
local Monsters = require('Module:Monsters')
local Pets = require('Module:Pets')
local Areas = require('Module:CombatAreas')
local Num = require('Module:Number')


function p.getLowHighLevels(idList)
	local lowLevel = 1000000
	local highLevel = 0
	for i, monID in ipairs(idList) do
		local monster = Monsters.getMonsterByID(monID)
		local cmbLevel = Monsters._getMonsterCombatLevel(monster)
		if cmbLevel < lowLevel then lowLevel = cmbLevel end
		if cmbLevel > highLevel then highLevel = cmbLevel end
	end
	return lowLevel, highLevel
end

function p.getAreaCategory(area)
	for _, areaCat in ipairs(GameData.rawData.combatAreaCategories) do
		if areaCat.areas ~= nil and Shared.contains(areaCat.areas, area.id) then
			return areaCat
		end
	end
end

function p.getAreaCategoriesFromAreas(areas)
	local areaCatIDs = {}
	for i, area in ipairs(areas) do
		local areaCat = p.getAreaCategory(area)
		if areaCat ~= nil and areaCat.id ~= nil then
			areaCatIDs[areaCat.id] = true
		end
	end
	return GameData.getEntities('combatAreaCategories',
		function(obj)
			return obj.id ~= nil and areaCatIDs[obj.id] ~= nil
		end
	)
end

local function getAreasForTable(areaDataKey, categoryName, defaultCategoryID)
	local areaCategories = p.getAreaCategoriesFromAreas(GameData.rawData[areaDataKey])
	local areaCategory = nil

	if categoryName == nil or categoryName == '' then
		-- Default area category
		areaCategory = GameData.getEntityByID(areaCategories, defaultCategoryID)
	else
		areaCategory = GameData.getEntityByName(areaCategories, categoryName)
	end
	if areaCategory == nil then
		local catNames = {}
		for _, areaCat in ipairs(areaCategories) do
			if areaCat.name ~= nil then
				table.insert(catNames, areaCat.name)
			end
		end
		return nil, Shared.printError('Invalid area category, choose any of: ' .. table.concat(catNames, ', '))
	end

	local areas = GameData.getEntities(areaDataKey,
		function(obj)
			return obj.id ~= nil and Shared.contains(areaCategory.areas, obj.id)
		end
	)
	areas = GameData.sortByOrderTable(areas, areaCategory.areas)
	return areas, nil
end

function p.getCombatAreaTable(frame)
	local args = frame.args ~= nil and frame.args or frame
	local categoryName = args[1]

	local areas, errText = getAreasForTable('combatAreas', categoryName, 'melvorD:CombatAreas')
	if errText ~= nil then
		return errText
	elseif areas == nil then
		return ''
	end

	local result = '{| class="wikitable sortable stickyHeader"'
	result = result..'\r\n|- class="headerRow-0"'
	result = result..'\r\n!colspan="2"|Zone!!Difficulty!!Lowest Monster Level!!Highest Monster Level!!Requirements'

	for i, area in ipairs(areas) do
		result = result..'\r\n|-'
		result = result..'\r\n|class="table-img" data-sort-value="' .. area.name .. '"| '..Icons.Icon({area.name, type='combatArea', size='50', notext=true})
		result = result..'||' .. Icons.getExpansionIcon(area.id) .. Icons.Icon({area.name, type='combatArea', noicon=true})
		result = result..'||data-sort-value="'..area.difficulty[1]..'"|'..Areas._getAreaStat(area, 'difficulty')
		local lowLvl, highLvl = p.getLowHighLevels(area.monsterIDs)
		result = result .. '||data-sort-value="' .. lowLvl .. '"| ' .. Num.formatnum(lowLvl)
		result = result .. '||data-sort-value="' .. highLvl .. '"| ' .. Num.formatnum(highLvl)
		local reqText = Areas._getAreaRequirements(area)
		if reqText == nil or reqText == '' then
			result = result .. '||class="table-na"| None'
		else
			result = result .. '||' .. reqText
		end
	end
	result = result..'\r\n|}'

	return result
end

function p.getSlayerAreaTable(frame)
	local args = frame.args ~= nil and frame.args or frame
	local categoryName = args[1]

	local areas, errText = getAreasForTable('slayerAreas', categoryName, 'melvorF:SlayerAreas')
	if errText ~= nil then
		return errText
	elseif areas == nil then
		return ''
	end

	local result = '{| class="wikitable sortable stickyHeader"'
	result = result..'\r\n|- class="headerRow-0"'
	result = result..'\r\n!colspan="2"|Zone!!Difficulty!!Highest Monster Level!!Requirements!!Area Effect'

	for i, area in ipairs(areas) do
		result = result..'\r\n|-'
		result = result..'\r\n|class="table-img" data-sort-value="' .. area.name .. '"| '..Icons.Icon({area.name, type='slayer', size='50', notext=true})
		result = result..'||' .. Icons.getExpansionIcon(area.id) .. Icons.Icon({area.name, type='slayer', noicon=true})
		result = result..'||data-sort-value="'..area.difficulty[1]..'"|'..Areas._getAreaStat(area, 'difficulty')
		local lowLvl, highLvl = p.getLowHighLevels(area.monsterIDs)
		result = result..'||data-sort-value="' .. highLvl .. '"| ' .. Num.formatnum(highLvl)
		local reqText = Areas._getAreaRequirements(area)
		if reqText == nil or reqText == '' then
			result = result .. '||class="table-na"| None'
		else
			result = result .. '||' .. reqText
		end
		local effectText = Areas._getAreaStat(area, 'areaEffectDesc')
		local classText = ((effectText == nil or effectText == 'None') and 'class="table-na"|') or ''
		result = result..'||' .. classText .. ' ' .. effectText
	end

	result = result..'\r\n|}'

	return result
end

function p._getDungeonRewards(area, asList)
	if asList == nil then
		asList = true
	elseif type(asList) == 'string' then
		asList = string.upper(asList) ~= 'FALSE'
	end

	local lineSep = (asList and '\n' or '<br>')
	local formatLine = nil
	if asList then
		formatLine = function(text) return '* ' .. text end
	end
	
	-- Currency rewards
	local rewardCurrency = {}
	if area.eventID == nil then
		local bossMonster = Monsters.getMonsterByID(area.monsterIDs[Shared.tableCount(area.monsterIDs)])
		rewardCurrency = bossMonster.currencyDrops
	end
	
	-- Item rewards
	local itemLists = {}
	if type(area.rewardItemIDs) == 'table' then
		table.insert(itemLists, area.rewardItemIDs)
	elseif area.type == 'stronghold' then
		-- This won't show which tier each reward comes from
		-- but there doesn't seem to be an easy way to do that
		local enhancementList = {}
		for tier, tierData in pairs(area.tiers) do
			if type(tierData.rewards) == 'table' and type(tierData.rewards.items) == 'table' then
				for i, reward in ipairs(tierData.rewards.items) do
					table.insert(enhancementList, reward.id)
				end
			end
		end
		if not Shared.tableIsEmpty(enhancementList) then
			table.insert(itemLists, enhancementList)
		end
	end
	if area.eventID ~= nil then
		local event = GameData.getEntityByID('combatEvents', area.eventID)
		if event ~= nil and type(event.itemRewardIDs) == 'table' then
			table.insert(itemLists, event.itemRewardIDs)
		end
	end

	local rewardItem = {}
	for i, itemList in ipairs(itemLists) do
		for j, rewardID in ipairs(itemList) do
			table.insert(rewardItem, {
				["id"] = rewardID,
				["quantity"] = 1
			})
		end
	end

	return Common.getCostString({ ["items"] = rewardItem, ["currencies"] = rewardCurrency}, '', formatLine, lineSep)
end

function p.getDungeonRewards(frame)
	local areaName = frame.args ~= nil and frame.args[1] or frame
	local asList = frame.args ~= nil and frame.args[2] or true
	local area = Areas.getArea(areaName)
	if area == nil then
		return Shared.printError('Could not find an area named "' .. areaName .. '"')
	end

	if area.type == 'dungeon' or area.type == 'abyssDepth' or area.type == 'stronghold' then
		return p._getDungeonRewards(area, asList)
	else
		return Shared.printError('"' .. areaName .. '" is not a dungeon')
	end
end

function p.getDungeonTable(frame)
	local args = frame.args ~= nil and frame.args or frame
	local categoryName = args[1]

	local areas, errText = getAreasForTable('dungeons', categoryName, 'melvorD:Dungeons')
	if errText ~= nil then
		return errText
	elseif areas == nil then
		return ''
	end

	local result = '{| class="wikitable sortable stickyHeader"'
	result = result..'\r\n|-class="headerRow-0"'
	result = result..'\r\n!colspan="2"|Dungeon!!Difficulty!!Monsters!!Boss Level!!Requirements!!Rewards!!Boss Pet'

	for i, dung in ipairs(areas) do
		local monsterCount = Shared.tableCount(dung.monsterIDs)
		local boss = Monsters.getMonsterByID(dung.monsterIDs[monsterCount])

		result = result..'\r\n|-'
		result = result..'\r\n|class="table-img" data-sort-value="'..dung.name..'"|'..Icons.Icon({dung.name, type='dungeon', size='50', notext=true})
		result = result..'||' .. Icons.getExpansionIcon(dung.id) .. Icons.Icon({dung.name, type='dungeon', noicon=true})
		result = result..'||data-sort-value="'..dung.difficulty[1]..'"|'..Areas._getAreaStat(dung, 'difficulty')
		result = result..'||'..monsterCount
		result = result..'||'..Num.formatnum(Monsters._getMonsterCombatLevel(boss))
		local reqText = Areas._getAreaRequirements(dung)
		if reqText == nil or reqText == '' then
			result = result .. '||class="table-na"| None'
		else
			result = result .. '||' .. reqText
		end
		result = result..'||'..p._getDungeonRewards(dung, false)
		local petAdded = false
		if dung.pet ~= nil then
			local pet = GameData.getEntityByID('pets', dung.pet.petID)
			if pet ~= nil then
				result = result..'||data-sort-value="'..pet.name..'"|'..Icons.Icon({pet.name, type='pet'})
				local petDrop = Pets._getPetChance(pet)
				if petDrop ~= nil then
					result = result..'<br/>'..petDrop
				end
				petAdded = true
			end
		end
		if not petAdded then
			result = result..'||class="table-na"| None'
		end
	end

	result = result..'\r\n|}'
	return result
end

local function getAreaOrderTable(areaCategoryID)
	local areaCategory = GameData.getEntityByID('combatAreaCategories', areaCategoryID)
	if areaCategory == nil then
		return {}
	else
		return areaCategory.areas
	end
end
function p.buildCombatNavText(frame)
	--This isn't actually called on any pages but instead exists to save me time on updating Template:CombatNav for the new expansion
	local areas = GameData.sortByOrderTable(GameData.rawData.slayerAreas, getAreaOrderTable('melvorD:SlayerAreas'))
	local outArray = {}
	for i, area in ipairs(areas) do
		local id = area.id
		if Shared.startsWith(id, 'melvorTotH') then
			table.insert(outArray, '|-')
			table.insert(outArray, '| {{TotH}} {{ZoneIcon|'..area.name..'}}')
			local monsterArray = {}
			for j, monsterID in ipairs(area.monsterIDs) do
				local monster = Monsters.getMonsterByID(monsterID)
				table.insert(monsterArray, '{{MonsterIcon|'..monster.name..'}}')
			end
			table.insert(outArray, '| class="center" style="vertical-align:middle;" | '..table.concat(monsterArray, ' {{*}} '))
		end
	end
	
	areas = GameData.sortByOrderTable(GameData.rawData.dungeons, getAreaOrderTable('melvorD:Dungeons'))
	for i, area in ipairs(areas) do
		local id = area.id
		if Shared.startsWith(id, 'melvorTotH') then
			table.insert(outArray, '|-')
			table.insert(outArray, '| {{TotH}} {{ZoneIcon|'..area.name..'}}')
			local monsterArray = {}
			local mCheck = {}
			for j, monsterID in ipairs(area.monsterIDs) do
				local monster = Monsters.getMonsterByID(monsterID)
				if mCheck[monster.name] == nil then
					table.insert(monsterArray, '{{MonsterIcon|'..monster.name..'}}')
					mCheck[monster.name] = 1
				end
					end
			table.insert(outArray, '| class="center" style="vertical-align:middle;" | '..table.concat(monsterArray, ' {{*}} '))
		end
	end
	
	return table.concat(outArray, '\r\n')
end

return p