Module:Pets: Difference between revisions

From Melvor Idle
m (Include script errors category)
(Update for v1.1)
Line 2: Line 2:


local p = {}
local p = {}
local PetData = mw.loadData('Module:Pets/data')


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


-- Compute combat pet sources once for use later
local function getCombatPetSources()
local result = {}
local areas = Areas.getAreas(function(area) return area.pet ~= nil end)
for i, area in ipairs(areas) do
result[area.petID] = { id = area.id, name = area.name, type = area.type, weight = area.weight }
end
end
local CombatPetSources = getCombatPetSources()


function p.getPetByID(ID)
function p.getPetByID(ID)
local result = Shared.clone(PetData.Pets[ID + 1])
return GameData.getEntityByID('pets', ID)
if result ~= nil then
result.id = ID
end
return result
end
end


function p.getPet(name)
function p.getPet(name)
local result = nil
name = string.gsub(name, "%%27", "'")
name = string.gsub(name, "%%27", "'")
name = string.gsub(name, "'", "'")
name = string.gsub(name, "'", "'")
name = string.gsub(name, "'", "'")
name = string.gsub(name, "'", "'")
for i, pet in pairs(PetData.Pets) do
return GameData.getEntityByName('pets', name)
local PetName = string.gsub(pet.name, '#', '')
if name == PetName then
result = Shared.clone(pet)
--Make sure every pet has an id, and account for Lua being 1-index
result.id = i - 1
result.skill = p._getPetSkill(result)
break
end
end
return result
end
end


function p.getPets(checkFunc)
function p.getPets(checkFunc)
local result = {}
return GameData.getEntities('pets', checkFunc)
for i, pet in Shared.skpairs(PetData.Pets) do
end
if checkFunc(pet) then
 
local newPet = Shared.clone(pet)
function p._getPetSource(pet)
newPet.id = i - 1
local skillID = p._getPetSkill(pet)
newPet.skill = p._getPetSkill(newPet)
if skillID ~= nil then
table.insert(result, newPet)
local skillName = Constants.getSkillName(skillID)
end
return { id = skillID, name = skillName, type = 'skill' }
elseif CombatPetSources ~= nil then
return CombatPetSources[pet.id]
end
end
return result
end
end


function p._getPetSource(pet)
function p._getPetSourceText(pet)
local sourceOverrides = {
local sourceOverrides = {
-- Format: ['PetName'] = {'Source', UseIcon}
-- useIcon: true if Source has an associated icon, false otherwise
-- UseIcon = true if Source has an associated icon, false otherwise
['Ripper the Reindeer'] = { text = '[[Events#Christmas Event 2020|Christmas Event 2020]]', useIcon = false },
['Asura'] = {'Slayer', true},
['Festive Chio'] = { text = '[[Holiday Event 2021]]', useIcon = false },
['Ripper the Reindeer'] = {'[[Events#Christmas_Event_2020|Christmas Event 2020]]', false},
['Festive Cool Rock'] = { text = '[[Holiday Event 2021]]', useIcon = false },
['Festive Chio'] = {'[[Holiday Event 2021]]'},
['Jerry the Giraffe'] = { text = '[[Golbin Raid|Golbin Raid Shop]]', useIcon = false },
['Festive Cool Rock'] = {'[[Holiday Event 2021]]'},
['Preston the Platypus'] = { text = '[[Golbin Raid|Golbin Raid Shop]]', useIcon = false }
['Jerry the Giraffe'] = {'[[Golbin Raid|Golbin Raid Shop]]', false},
['Preston the Platypus'] = {'[[Golbin Raid|Golbin Raid Shop]]', false},
['Mark'] = {'Summoning', true},
['Bone'] = {'Impending Darkness Event', true}
}
}
local petSource = ''
local petSourceText = nil
local iconType = nil
local useIcon = true
local useIcon = true
local override = sourceOverrides[pet.name]
local override = sourceOverrides[pet.name]
if override ~= nil then
if override ~= nil then
petSource = override[1] ~= nil and override[1] or pet.acquiredBy
petSourceText = override.text
useIcon = override[2]
if override.useIcon ~= nil then
elseif pet.acquiredBy ~= nil then
useIcon = override.useIcon
petSource = pet.acquiredBy
end
end
end


-- Determine icon type (if any)
if petSourceText == nil then
local iconType = nil
local petSource = p._getPetSource(pet)
if Constants.getSkillID(petSource) ~= nil then
if petSource ~= nil then
iconType = 'skill'
iconType = petSource.type
else
petSourceText = petSource.name
local combatArea = CombatAreas.getArea(petSource)
else
if combatArea ~= nil then
useIcon = false
iconType = combatArea.type
petSourceText = ''
end
end
end
end


if useIcon then
if useIcon then
petSource = Icons.Icon({petSource, type=iconType})
return Icons.Icon({petSourceText, type=iconType})
else
return petSourceText
end
end
return petSource
end
end


function p._getPetEffect(pet)
function p._getPetEffect(pet)
local effectOverrides = {
local modKeys = {'modifiers', 'enemyModifiers'}
['Ripper the Reindeer'] = 'None',
local effects = {}
}
for i, key in ipairs(modKeys) do
if effectOverrides[pet.name] ~= nil then
if pet[key] ~= nil and not Shared.tableIsEmpty(pet[key]) then
return effectOverrides[pet.name]
for effectName, effectValue in pairs(pet[key]) do
else
local preText = (key == 'enemyModifiers' and 'All enemies have: ' or '')
local modKeys = {'modifiers', 'enemyModifiers'}
table.insert(effects, preText .. Constants._getModifierText(effectName, effectValue, false))
local effects = {}
for i, key in ipairs(modKeys) do
if pet[key] ~= nil and Shared.tableCount(pet[key]) > 0 then
for effectName, effectValue in pairs(pet[key]) do
local preText = (key == 'enemyModifiers' and 'All enemies have: ' or '')
table.insert(effects, preText .. Constants._getModifierText(effectName, effectValue, false))
end
end
end
end
end
if Shared.tableCount(effects) > 0 then
end
return table.concat(effects, '<br/>')
if Shared.tableIsEmpty(effects) then
elseif pet.skill ~= nil and pet.skill >= 0 then
return 'None'
-- Attempt to remove skill prefix from description
else
local newDesc, subIdx = string.gsub(pet.description, '^' .. Constants.getSkillName(pet.skill) .. '<br>', '')
return table.concat(effects, '<br/>')
return newDesc
else
return pet.description
end
end
end
end
end


function p._getPetChance(pet)
function p._getPetChance(pet)
if pet.obtained ~= nil and pet.obtained.dungeonCompletion ~= nil then
local source = p._getPetSource(pet)
local odds = pet.obtained.dungeonCompletion[1][2]
if source ~= nil and source.weight ~= nil then
if pet.name == 'Pablo' then
-- Pet is from a dungeon or combat/slayer area
-- Special handling for Into the Mist/Pablo
-- Flaky logic for determining if pet is guaranteed or not
return 'Guaranteed after ' .. odds .. ' clears'
if source.weight <= 10 then
return 'Guaranteed after ' .. Shared.formatnum(source.weight) .. ' clears'
else
else
-- Dungeon pet
return '1 in ' .. Shared.formatnum(source.weight) .. ' (' .. Shared.round(100 / source.weight, 2, 2) .. '%)'
return '1 in ' .. odds .. ' (' .. Shared.round(100 / odds, 2, 2) .. '%)'
end
end
elseif pet.name == 'Bone' then
-- Special handling for Impending Darkness/Bone
return 'Guaranteed after 1 clear'
elseif pet.name == 'Peri' or pet.name == 'Otto' then
-- Slayer area pets
local odds = 7500
return '1 in ' .. odds .. ' (' .. Shared.round(100 / odds, 2, 2) .. '%)'
else
else
-- Skill pet or other
-- Skill pet or other
Line 144: Line 118:
function p._getPetSkill(pet)
function p._getPetSkill(pet)
local skillOverrides = {
local skillOverrides = {
['Ty'] = -1,
['melvorD:Ty'] = nil,
['Mark'] = 21
['melvorF:Mark'] = 'melvorD:Summoning'
}
}


local skillID = pet.skill
if skillOverrides[pet.id] ~= nil then
if skillOverrides[pet.name] ~= nil then
return skillOverrides[pet.id]
skillID = skillOverrides[pet.name]
end
if skillID < 0 then
return nil
else
else
return skillID
return pet.skillID
end
end
end
end


function p._getPetTable(pets)
function p._getPetTable(pets)
if pets == nil or Shared.tableCount(pets) == 0 then return nil end
if type(pets) ~= 'table' or Shared.tableIsEmpty(pets) then
return nil
end


local resultPart = {}
local resultPart = {}
table.insert(resultPart, '{| class="wikitable"\r\n!Pet!!Name!!Effect')
table.insert(resultPart, '{| class="wikitable"\r\n!Pet!!Name!!Effect')


for i, pet in pairs(pets) do
for i, pet in ipairs(pets) do
table.insert(resultPart, '|-')
table.insert(resultPart, '|-')
table.insert(resultPart, '|style="text-align: center;"|' .. Icons.Icon({pet.name, type='pet', size=60, notext=true}))
table.insert(resultPart, '|style="text-align: center;"|' .. Icons.Icon({pet.name, type='pet', size=60, notext=true}))
Line 184: Line 156:
else
else
local pets = p.getPets(function(pet) return p._getPetSkill(pet) == skillID end)
local pets = p.getPets(function(pet) return p._getPetSkill(pet) == skillID end)
if pets == nil then
if pets == nil or Shared.tableIsEmpty(pets) then
return ''
return ''
else
else
Line 208: Line 180:
result = result..'style="text-align: center;"|' .. Icons.Icon({name, type='pet', size='250', notext=true})
result = result..'style="text-align: center;"|' .. Icons.Icon({name, type='pet', size='250', notext=true})
result = result.."\r\n|-\r\n|'''Pet ID:''' "..pet.id
result = result.."\r\n|-\r\n|'''Pet ID:''' "..pet.id
result = result.."\r\n|-\r\n|'''Source:''' "..p._getPetSource(pet)
result = result.."\r\n|-\r\n|'''Source:''' "..p._getPetSourceText(pet)
if dropChance ~= nil then
if dropChance ~= nil then
result = result.."\r\n|-\r\n|'''Drop Chance:''' "..dropChance
result = result.."\r\n|-\r\n|'''Drop Chance:''' "..dropChance
Line 220: Line 192:
function p.getPetPageTable()
function p.getPetPageTable()
local result = ''
local result = ''
local petList = Shared.clone(PetData.Pets)


result = '{|class="wikitable sortable lighttable stickyHeader"'
result = '{|class="wikitable sortable lighttable stickyHeader"'
result = result..'\r\n|- class="headerRow-0"\r\n! Name !! Image !! Acquired From !! Effect'
result = result..'\r\n|- class="headerRow-0"\r\n! Name !! Image !! Acquired From !! Effect'


table.sort(petList, function(a, b)
for i, thisPet in ipairs(GameData.rawData.pets) do
return p.getPet(a.name).id < p.getPet(b.name).id
end)
 
for i, thisPet in pairs(petList) do
result = result..'\r\n|-\r\n|'..Icons.Icon({thisPet.name, type='pet', noicon=true})
result = result..'\r\n|-\r\n|'..Icons.Icon({thisPet.name, type='pet', noicon=true})
result = result..'||style="text-align: center;"|'..Icons.Icon({thisPet.name, size='60', type='pet', notext=true})
result = result..'||style="text-align: center;"|'..Icons.Icon({thisPet.name, size='60', type='pet', notext=true})
result = result..'||'..p._getPetSource(thisPet)
result = result..'||'..p._getPetSourceText(thisPet)
result = result..'||'..p._getPetEffect(thisPet)
result = result..'||'..p._getPetEffect(thisPet)
end
end
Line 242: Line 209:
function p.getDungeonBoxPetText(frame)
function p.getDungeonBoxPetText(frame)
local dungeonName = frame.args ~= nil and frame.args[1] or frame
local dungeonName = frame.args ~= nil and frame.args[1] or frame
local dung = CombatAreas.getArea(dungeonName)
local dung = Areas.getArea(dungeonName)
if dung == nil then
if dung == nil then
return 'ERROR: Invalid dungeon name '..dungeonName..'[[Category:Pages with script errors]]'
return 'ERROR: Invalid dungeon name '..dungeonName..'[[Category:Pages with script errors]]'
end
end


local result = ''
if dung.pet ~= nil then
local pet = p.getPetByID(dung.petID)
local pet = p.getPetByID(dung.pet.petID)
if pet ~= nil then
if pet ~= nil then
result = "\r\n|-\r\n|'''[[Pets#Boss Pets|Pet]]:'''<br/>"
local result = "\r\n|-\r\n|'''[[Pets#Boss Pets|Pet]]:'''<br/>"
result = result..Icons.Icon({pet.name, type='pet'})
result = result..Icons.Icon({pet.name, type='pet'})
result = result.."\r\n|-\r\n|'''Pet Drop Chance:'''<br/>"..p._getPetChance(pet)
result = result.."\r\n|-\r\n|'''Pet Drop Chance:'''<br/>"..p._getPetChance(pet)
return result
end
end
end
return result
end
end


-- TODO Move to Module:Navboxes
function p.getPetNavbox()
function p.getPetNavbox()
--•
local resultPart = {}
local result = '{| class="wikitable" style="margin:auto; text-align:center; clear:both; width: 100%"'
table.insert(resultPart, '{| class="wikitable" style="margin:auto; text-align:center; clear:both; width: 100%"')
result = result..'\r\n|-\r\n!colspan="2"|[[Pets]]'
table.insert(resultPart, '\r\n|-\r\n!colspan="2"|[[Pets]]')


local petList = {
["skill"] = {},
["boss"] = {},
["other"] = {}
}
local skillPetList = {}
local skillPetList = {}
local bossPetList = {}
local bossPetList = {}
local otherPetList = {}
local otherPetList = {}
for i, petData in Shared.skpairs(PetData.Pets) do
for i, petData in ipairs(GameData.rawData.pets) do
if p._getPetSkill(petData) ~= nil then
local source = p._getPetSource(petData)
table.insert(skillPetList, Icons.Icon({petData.name, type='pet'}))
local listCat = 'other'
elseif petData.obtained ~= nil and petData.obtained.dungeonCompletion ~= nil then
if type(source) == 'table' and source.type ~= nil then
table.insert(bossPetList, Icons.Icon({petData.name, type='pet'}))
if source.type == 'skill' then
else
listCat = 'skill'
table.insert(otherPetList, Icons.Icon({petData.name, type='pet'}))
elseif source.type == 'dungeon' or source.type == 'slayerArea' then
listCat = 'boss'
else
listCat = 'other'
end
end
table.insert(petList[listCat], petData.name)
end
 
local getIconList =
function(pets)
local result = {}
for i, pet in ipairs(pets) do
table.insert(result, Icons.Icon({pet, type='pet'}))
end
end
return table.concat(result, ' • ')
end
for cat, catData in pairs(petList) do
table.sort(catData, function(a, b) return a < b end)
table.insert(resultPart, '\r\n|-\r\n!' .. Shared.titleCase(cat) .. ' Pets\r\n|' .. getIconList(catData))
end
end
table.sort(skillPetList, function(a, b) return a < b end)
table.insert(resultPart, '\r\n|}')
table.sort(bossPetList, function(a, b) return a < b end)
return table.concat(resultPart)
table.sort(otherPetList, function(a, b) return a < b end)
result = result..'\r\n|-\r\n!Skill Pets\r\n|'..table.concat(skillPetList, ' • ')
result = result..'\r\n|-\r\n!Boss Pets\r\n|'..table.concat(bossPetList, ' • ')
result = result..'\r\n|-\r\n!Other Pets\r\n|'..table.concat(otherPetList, ' • ')
result = result..'\r\n|}'
return result
end
end


return p
return p

Revision as of 15:51, 22 October 2022

Data for this page is stored in Module:GameData/data


--This module contains all sorts of functions for getting data on pets

local p = {}

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

-- Compute combat pet sources once for use later
local function getCombatPetSources()
	local result = {}
	local areas = Areas.getAreas(function(area) return area.pet ~= nil end)
	for i, area in ipairs(areas) do
		result[area.petID] = { id = area.id, name = area.name, type = area.type, weight = area.weight }
	end
end
local CombatPetSources = getCombatPetSources()

function p.getPetByID(ID)
	return GameData.getEntityByID('pets', ID)
end

function p.getPet(name)
	name = string.gsub(name, "%%27", "&apos;")
	name = string.gsub(name, "'", "&apos;")
	name = string.gsub(name, "&#39;", "&apos;")
	return GameData.getEntityByName('pets', name)
end

function p.getPets(checkFunc)
	return GameData.getEntities('pets', checkFunc)
end

function p._getPetSource(pet)
	local skillID = p._getPetSkill(pet)
	if skillID ~= nil then
		local skillName = Constants.getSkillName(skillID)
		return { id = skillID, name = skillName, type = 'skill' }
	elseif CombatPetSources ~= nil then
		return CombatPetSources[pet.id]
	end
end

function p._getPetSourceText(pet)
	local sourceOverrides = {
		-- useIcon: true if Source has an associated icon, false otherwise
		['Ripper the Reindeer'] = { text = '[[Events#Christmas Event 2020|Christmas Event 2020]]', useIcon = false },
		['Festive Chio'] = { text = '[[Holiday Event 2021]]', useIcon = false },
		['Festive Cool Rock'] = { text = '[[Holiday Event 2021]]', useIcon = false },
		['Jerry the Giraffe'] = { text = '[[Golbin Raid|Golbin Raid Shop]]', useIcon = false },
		['Preston the Platypus'] = { text = '[[Golbin Raid|Golbin Raid Shop]]', useIcon = false }
	}
	local petSourceText = nil
	local iconType = nil
	local useIcon = true
	local override = sourceOverrides[pet.name]
	if override ~= nil then
		petSourceText = override.text
		if override.useIcon ~= nil then
			useIcon = override.useIcon
		end
	end

	if petSourceText == nil then
		local petSource = p._getPetSource(pet)
		if petSource ~= nil then
			iconType = petSource.type
			petSourceText = petSource.name
		else
			useIcon = false
			petSourceText = ''
		end
	end

	if useIcon then
		return Icons.Icon({petSourceText, type=iconType})
	else
		return petSourceText
	end
end

function p._getPetEffect(pet)
	local modKeys = {'modifiers', 'enemyModifiers'}
	local effects = {}
	for i, key in ipairs(modKeys) do
		if pet[key] ~= nil and not Shared.tableIsEmpty(pet[key]) then
			for effectName, effectValue in pairs(pet[key]) do
				local preText = (key == 'enemyModifiers' and 'All enemies have: ' or '')
				table.insert(effects, preText .. Constants._getModifierText(effectName, effectValue, false))
			end
		end
	end
	if Shared.tableIsEmpty(effects) then
		return 'None'
	else
		return table.concat(effects, '<br/>')
	end
end

function p._getPetChance(pet)
	local source = p._getPetSource(pet)
	if source ~= nil and source.weight ~= nil then
		-- Pet is from a dungeon or combat/slayer area
		-- Flaky logic for determining if pet is guaranteed or not
		if source.weight <= 10 then
			return 'Guaranteed after ' .. Shared.formatnum(source.weight) .. ' clears'
		else
			return '1 in ' .. Shared.formatnum(source.weight) .. ' (' .. Shared.round(100 / source.weight, 2, 2) .. '%)'
		end
	else
		-- Skill pet or other
		return 'See: [[Pets#Acquiring Pets|Acquiring Pets]]'
	end
end

function p._getPetSkill(pet)
	local skillOverrides = {
		['melvorD:Ty'] = nil,
		['melvorF:Mark'] = 'melvorD:Summoning'
	}

	if skillOverrides[pet.id] ~= nil then
		return skillOverrides[pet.id]
	else
		return pet.skillID
	end
end

function p._getPetTable(pets)
	if type(pets) ~= 'table' or Shared.tableIsEmpty(pets) then
		return nil
	end

	local resultPart = {}
	table.insert(resultPart, '{| class="wikitable"\r\n!Pet!!Name!!Effect')

	for i, pet in ipairs(pets) do
		table.insert(resultPart, '|-')
		table.insert(resultPart, '|style="text-align: center;"|' .. Icons.Icon({pet.name, type='pet', size=60, notext=true}))
		table.insert(resultPart, '|' .. Icons.Icon({pet.name, type='pet', noicon=true}))
		table.insert(resultPart, '| ' .. p._getPetEffect(pet))
	end
	table.insert(resultPart, '|}')

	return table.concat(resultPart, '\r\n')
end

function p.getPetTableBySkill(frame)
	local skillName = frame.args ~= nil and frame.args[1] or frame
	local skillID = Constants.getSkillID(skillName)

	if skillID == nil then
		return ''
	else
		local pets = p.getPets(function(pet) return p._getPetSkill(pet) == skillID end)
		if pets == nil or Shared.tableIsEmpty(pets) then
			return ''
		else
			return p._getPetTable(pets)
		end
	end
end

function p.getPetSidebar(frame)
	local args = frame.args ~= nil and frame.args or frame
	local result = nil
	local name = (args.name ~= nil and args.name ~= '') and args.name or args[1]
	local pet = p.getPet(name)
	if pet == nil then
		return 'ERROR: Could not find pet with name ' .. (name or 'Unknown') .. '[[Category:Pages with script errros]]'
	end
	local effect = (args.effect ~= nil and args.effect ~= '') and args.effect or p._getPetEffect(pet)
	local completionReq = (pet.ignoreCompletion ~= nil and pet.ignoreCompletion) and 'No' or 'Yes'
	local dropChance = p._getPetChance(pet)

	result = '{| class="wikitable infobox" style="float:right; clear:right;"\r\n|-\r\n'
	result = result..'! '..name..'\r\n|-\r\n| '
	result = result..'style="text-align: center;"|' .. Icons.Icon({name, type='pet', size='250', notext=true})
	result = result.."\r\n|-\r\n|'''Pet ID:''' "..pet.id
	result = result.."\r\n|-\r\n|'''Source:''' "..p._getPetSourceText(pet)
	if dropChance ~= nil then
		result = result.."\r\n|-\r\n|'''Drop Chance:''' "..dropChance
	end
	result = result.."\r\n|-\r\n| style =\"width: 250px;\"|'''Effect:''' "..effect
	result = result .. "\r\n|-\r\n|'''Part of 100% Completion:''' " .. completionReq .. "\r\n|}"

	return result
end

function p.getPetPageTable()
	local result = ''

	result = '{|class="wikitable sortable lighttable stickyHeader"'
	result = result..'\r\n|- class="headerRow-0"\r\n! Name !! Image !! Acquired From !! Effect'

	for i, thisPet in ipairs(GameData.rawData.pets) do
		result = result..'\r\n|-\r\n|'..Icons.Icon({thisPet.name, type='pet', noicon=true})
		result = result..'||style="text-align: center;"|'..Icons.Icon({thisPet.name, size='60', type='pet', notext=true})
		result = result..'||'..p._getPetSourceText(thisPet)
		result = result..'||'..p._getPetEffect(thisPet)
	end
	result = result..'\r\n|}'

	return result
end

function p.getDungeonBoxPetText(frame)
	local dungeonName = frame.args ~= nil and frame.args[1] or frame
	local dung = Areas.getArea(dungeonName)
	if dung == nil then
		return 'ERROR: Invalid dungeon name '..dungeonName..'[[Category:Pages with script errors]]'
	end

	if dung.pet ~= nil then
		local pet = p.getPetByID(dung.pet.petID)
		if pet ~= nil then
			local result = "\r\n|-\r\n|'''[[Pets#Boss Pets|Pet]]:'''<br/>"
			result = result..Icons.Icon({pet.name, type='pet'})
			result = result.."\r\n|-\r\n|'''Pet Drop Chance:'''<br/>"..p._getPetChance(pet)
			return result
		end
	end
end

-- TODO Move to Module:Navboxes
function p.getPetNavbox()
	local resultPart = {}
	table.insert(resultPart, '{| class="wikitable" style="margin:auto; text-align:center; clear:both; width: 100%"')
	table.insert(resultPart, '\r\n|-\r\n!colspan="2"|[[Pets]]')

	local petList = {
		["skill"] = {},
		["boss"] = {},
		["other"] = {}
	}
	local skillPetList = {}
	local bossPetList = {}
	local otherPetList = {}
	for i, petData in ipairs(GameData.rawData.pets) do
		local source = p._getPetSource(petData)
		local listCat = 'other'
		if type(source) == 'table' and source.type ~= nil then
			if source.type == 'skill' then
				listCat = 'skill'
			elseif source.type == 'dungeon' or source.type == 'slayerArea' then
				listCat = 'boss'
			else
				listCat = 'other'
			end
		end
		table.insert(petList[listCat], petData.name)
	end

	local getIconList =
	function(pets)
		local result = {}
		for i, pet in ipairs(pets) do
			table.insert(result, Icons.Icon({pet, type='pet'}))
		end
		return table.concat(result, ' • ')
	end
	for cat, catData in pairs(petList) do
		table.sort(catData, function(a, b) return a < b end)
		table.insert(resultPart, '\r\n|-\r\n!' .. Shared.titleCase(cat) .. ' Pets\r\n|' .. getIconList(catData))
	end
	table.insert(resultPart, '\r\n|}')
	return table.concat(resultPart)
end

return p