Module:Pet

-- local loader = require('Module:Loader')

local string, table, yesno, libU, color, currency, ability, rarityTier, statModule, invslot, slotutils, arguments, petUtils = loader.lazy.require('String', 'Table', 'Yesno', 'LibU', 'Color', 'Currency', 'Ability', 'RarityTier', 'Statname', 'Inventory slot', 'Inventory slot/Utils', 'Arguments', 'Pet/Utils') local levelingdata, uitextData, colorAliases, rarityAliases = loader.lazy.loadData('Pet/LevelingData', 'UIText/Data', 'Color/Aliases', 'RarityTier/Aliases') local getArgs = arguments.getArgs

local p = {} local curtitle = mw.title.getCurrentTitle local fullpagename = curtitle.fullText local isPetPage = curtitle.namespace == 0 and fullpagename:find('Pet') local infosymbol = 'ⓘ'

-- Helper functions local function range(n) local t = {} for i = 1, tonumber(n) do t[#t + 1] = i end return t end local function rarityOrder(rarity) return rarityTier._getTier(rarity).order end

-- Template:Pet level stat -- -- Calculator that makes an HTML table showing information about a pet at a certain level and rarity

function p.requestStat(frame) local args = getArgs(frame) local pet = args[1] or args.p or args.pet or (isPetPage and fullpagename) or 'ERROR' local level = args[2] or args.level local rarity = args[3] or args.rarity local petitem = args.pet_item return mw.getCurrentFrame:preprocess(p._requestStat(pet, { level = level, rarity = rarity, petitem = petitem, })) end

-- Template:Pet slot -- -- Creates a slot showing a pet at a certain level and rarity

function p.petSlot(frame) local args = getArgs(frame) local pet = args[1] or args.p or args.pet or (isPetPage and fullpagename) or 'ERROR' local level = args[2] or args.level local rarity = args[3] or args.rarity local petitem = args.pet_item return p._petSlot(pet, {		level = level,		rarity = rarity,		petitem = petitem,	}) end

-- Module Access Point function p._specificPetHandler(mode, pet, options) options = options or {} local level, rarity, petitem = options.level, options.rarity, options.petitem local data, pet, baselv, maxlv = petUtils.getPet(pet, true) if not data then return string.wrapHtml('Unrecognized Pet: ' .. pet, 'span', { class = 'error' }) end level = tonumber(level) or 1 local petRarities = petUtils.discloseRarities(pet, true) rarity = rarity or petRarities[1] local raritykey = rarityAliases[rarity:lower] if not table.includes(petRarities, raritykey) then return string.wrapHtml(('%s Pet does not have such rarity: %s Rarities available: %s')			:format(pet, rarity, petUtils.discloseRarities(pet)), 'span', { class = 'error' }) end if level maxlv then return string.wrapHtml(('%s Pet cannot have such level: %s Valid levels: %s to %s'):format(pet, level, baselv + 1, maxlv), 'span', { class = 'error' }) end local stats, heldItem = data.variables[raritykey].stats or data.stats, data.heldItem if not stats then return string.wrapHtml(('Missing pet stats (Pet: %s; Rarity: %s)'):format(pet, raritykey), 'span',			{ class = 'error' }) end local function tableview local wikitable = mw.html.create('table'):addClass('wikitable article-petlevelstats') local row = wikitable:tag('tr') row:tag('th'):wikitext((' %s Pet'):format( pet, fullpagename:match(pet) and '' or (pet .. ' Pet'), pet )):addClass('article-petlevelstats-title'):attr('rowspan', 2) row:tag('th'):wikitext(('Lv.%d'):format(level)):addClass('article-petlevelstats-level') row = wikitable:tag('tr') row:tag('th'):wikitext(rarityTier._link(rarity)):addClass('article-petlevelstats-tier') row = wikitable:tag('tr') row:tag('td'):wikitext(table.concat {				string.wrapHtml(p._petSlot(pet, {					level = level,					rarity = rarity,					petitem = petitem,				}), 'div', { class = 'article-petlevelstats-slot' }),				p.constructStats(stats, level, raritykey, baselv, { petitem = petitem }),				p.processAbilities(pet, raritykey, level),				p.processHeldItems(pet, raritykey, level, { petitem = petitem })			}) :addClass('article-petlevelstats-main') :attr('colspan', 3) return tostring(wikitable) end local function slotview local slot = slotutils._useTemplate(			{ petUtils.petTemplateCreator(pet, level, rarity, 'THIS', { petitem = petitem }) }, 'Pet'		) local params = '' slot.THIS[1] = petUtils.petNameWithRarity(pet, raritykey) if (fullpagename:match(pet)) then slot.THIS.link = 'none' end return invslot.slot(slot.THIS) end mode = mode and tostring(mode):lower or 'table' if mode == 'table' then return tableview(pet, level, rarity) elseif mode == 'slot' then return slotview(pet, level, rarity) else return string.wrapHtml(('No such mode "%s"'):format(mode), 'span', { class = 'error' }) end end

p._requestStat = libU.bind(p._specificPetHandler, 'table') p._petSlot = libU.bind(p._specificPetHandler, 'slot')

-- Template:PetStats -- -- Makes an HTML table showing information about a pet.

function p.petStatsTable(frame) local args = getArgs(frame) local pet = args[1] or args['p'] or args['pet'] or (isPetPage and fullpagename) or 'ERROR' if pet == 'ERROR' then return string.error('Pet name expected, got none') end return mw.getCurrentFrame:preprocess(p._petStatsTable(pet)) end

-- (Template:PetStats) Module Access Point function p._petStatsTable(pet) local data, pet, baselv, maxlv = petUtils.getPet(pet, true) if not data then error('Unrecognized Pet: ' .. pet) end local startlv = tonumber(baselv) + 1 local onelevel = baselv == maxlv local tabs = {} for _, raritykey in ipairs(petUtils.discloseRarities(pet, true)) do		local stats, variables = data.variables[raritykey].stats or data.stats, data.variables if not stats then return error(string.format('Missing pet stats (Pet: %s)', pet), 2) end -		-- Headers -		local ved_buttons = string.wrapHtml(			'VED'			,			'span', { class = 'table-vedbuttons nomobile' }		) local wikitable = mw.html.create('table'):addClass('wikitable full-width') local row = wikitable:tag('tr') if not onelevel then row:tag('th'):addClass('half-width'):wikitext(string.makeTitle( ('Starting Stats (Level %d) %s'):format(startlv, infosymbol), 'Stats of a pet at starting level' ))			end row:tag('th'):addClass('half-width'):wikitext(string.makeTitle( ('Max Stats (Level %d) %s'):format(maxlv, infosymbol), 'Stats of a maxed-out pet' ) .. ved_buttons) local petname = variables[raritykey] and variables[raritykey].petname or pet local row = wikitable:tag('tr') -			-- Base stats -			if not onelevel then row:tag('td'):wikitext(table.concat {					p.constructStats(stats, startlv, raritykey, baselv),					p.processAbilities(pet, raritykey, startlv),					p.processHeldItems(pet, raritykey, startlv)				}) end -			-- Max level stats -			row:tag('td'):wikitext(table.concat {				p.constructStats(stats, maxlv, raritykey, baselv),				p.processAbilities(pet, raritykey, maxlv),				p.processHeldItems(pet, raritykey, maxlv)			}) -		-- Bonus headers -		if not onelevel then wikitable:tag('tr') :tag('th'):attr({ colspan = 2 }):wikitext(string.makeTitle( 'Leveling Bonuses' .. infosymbol, 'The bonuses gained with every pet level' ))			wikitable:tag('tr') -				-- Bonus stats -				:tag('td'):attr({ colspan = 2 }):wikitext(table.concat {					p.constructStats(stats, 'bonus', raritykey, baselv),					p.processAbilities(pet, raritykey, 'bonus')				}) end tabs[#tabs + 1] = table.concat { '|-|',			rarityTier._getTier(raritykey).category, ' = ',			string.wrapHtml((rarityTier._link(raritykey) .. ' ' .. petname .. ' Pet'), 'div',				{ class = 'article-petstats-title' }), tostring(wikitable), (raritykey == 'mythic' and  or ) }	end return table.concat { ' ', table.concat(tabs), ' ' } end

--	Helper Functions for p._petStatsTable: --	├── 1.1 p.constructStats --	│	└── 1.2 petUtils._getStatByTable --	├── 1.3 p.processAbilities --	│	└── 1.4 p.processVars --	│		└── 1.5 petUtils._getVariable --	└── 1.6 p.processHeldItems --		└── (Same as p.processAbilities)

--	Helper Functions for p._petStatsTable: 1.1

function p.constructStats(stats, petlevel, rarity, baselv, options) options = options or {} rarity = rarityAliases[rarity] or rarity local list = {} local statValuesActual, statValuesRounded = petUtils._getStatByTable(stats, petlevel, rarity, baselv, {		petitem = options.petitem,	}) for key, _ in pairs(statValuesActual) do		local sdt = statModule._getstatdata(key) local mainText = (petlevel == 'bonus') and statValuesActual[key] or			(math.abs(statValuesActual[key] - statValuesRounded[key]) < 1e-9) and statValuesRounded[key] or			string.makeTitle(				statValuesRounded[key],				('Rounded down to the nearest integer;&#10;The exact value is %s.'):format(statValuesActual[key])			) table.push(list, string.wrapHtml({ statModule._stat(sdt.name), (statValuesActual[key] < 0 and ': ' or ': +'), mainText, (sdt.percent and '%' or ''), (petlevel == 'bonus' and ' per level' or '') }, 'li')) end return table.concat { string.wrapHtml(petlevel == 'bonus' and 'Bonus stats:' or 'Stats:', 'span',			{ class = 'color-turquoise hsw-gamefont bold' }		), string.wrapHtml(list, 'ul') } end

--	Helper Functions for p._petStatsTable: 1.3

function p.processAbilities(pet, rarity, petlevel) local data, pet, baselv, maxlv = petUtils.getPet(pet, true) local ab, variables = data.abilities, data.variables local abilityList = variables[rarity].ability_indices or range(variables[rarity].ability_count) local list = {} for _, i in ipairs(abilityList) do		if tonumber(petlevel) then desc = p.processVars(pet, rarity, petlevel, ab.desc[i]) table.push(list,				string.wrapHtml({ ability._ability(ab.name[i], false, 'abilityName'), ' - ', desc }, 'li')			) elseif petlevel == 'bonus' then desc = p.processVars(pet, rarity, petlevel, ab.bonus_desc[i] or 'No leveling bonus') table.push(list,				string.wrapHtml({ ability._ability(ab.name[i], false, 'abilityName'), ': ', desc }, 'li')			) end end return table.concat { ability._ability('Abilities:'), string.wrapHtml(list, '', { class = 'article-petstats-abilities' }) } end

--	Helper Functions for p._petStatsTable: 1.4

function p.processVars(pet, raritykey, petlevel, desc) local data, pet, baselv, maxlv = petUtils.getPet(pet, true) local variable = data.variables[raritykey] local reverseConv = table.invert(uitextData.conversions) local function xmlColorConvert(s) return s:gsub(			'(.-)',			function(tag, str, endTag)				tag = string.trim(tag):lower				endTag = string.trim(endTag):lower				if tag and (not endTag or endTag == '')				then error(string.format('XML color tags syntax error: No closing tag found for tag ', tag), 4)				elseif tag:lower ~= endTag:lower				then error( string.format(							'XML color tags syntax error: Expected closing tag for \'\' is \'\', got ',							tag,							tag,							endTag						), 4)				end				local c = colorAliases[tag] or reverseConv[tag]				return c and color._colorTemplates(c, str) or '<' .. tag .. '>' .. str .. ''			end		) end local function processSTAT(str) if str:find('STAT_[%u-]+') then str = str:gsub('STAT_([%u-]+)', function(s)				return statModule._getStatName(s)			end) end if str:find('STATs_([%u-]+)') then str = str:gsub('STATs_([%u-]+)', function(s)				return statModule._getStatName(s, nil, true)			end) end return str end local function processRARITY(str) if str:find('RARITY_%u+') then str = str:gsub('RARITY_(%u+)', function(s)				local dt = rarityTier._getTier(s)				return rarityTier._link(s)			end) end return str end local function replaceCoins(str) return str:gsub('COINS', tostring(currency._coins)) end local function formatFakeAbility(s) return s:gsub('{ABILITY}(.+){/ABILITY}', function(s)			return ability._ability(s, false, 'abilityName')		end) end local function processVariables(index, petlevel) local num = petUtils._getVariable(pet, raritykey, index, petlevel, true) -- add suffix num = tostring(num) .. (variable[index].suffix or '') -- colorize return color._colorTemplates(variable[index].color, num) end desc = formatFakeAbility(replaceCoins(processRARITY(processSTAT(xmlColorConvert(desc))))) if variable then for i in desc:gmatch('{(%d+)}') do			local j = tonumber(i) local subst = processVariables(j, petlevel, baselv) desc = subst and desc:gsub(('{%d}'):format(j), subst) or desc end end return desc end

--	Helper Functions for p._petStatsTable: 1.6

function p.processHeldItems(pet, rarity, petlevel, options) local data, pet, baselv, maxlv = petUtils.getPet(pet, true) local heldItem = data.heldItem if not heldItem or not tonumber(petlevel) or rarity ~= heldItem.req then if options == nil then return nil end local petItemName = options.petitem if type(petItemName) == 'string' and petItemName ~= '' then local petItem = petUtils.petItemInfo(petItemName) local desc = p.processVars(pet, rarity, petlevel, petItem.description) local list = string.wrapHtml(desc, 'li') return table.concat { ability._ability('Held Item: ' .. petItem.name, false, 'abilityName'), string.wrapHtml { list, '', { class = 'article-petstats-helditem' } }			}		end return nil end local desc = p.processVars(pet, rarity, petlevel, heldItem.desc) local list = string.wrapHtml(desc, 'li') return table.concat { ability._ability('Held Item: ' .. heldItem.name, false, 'abilityName'), string.wrapHtml { list, '', { class = 'article-petstats-helditem' } }	} end

--- -- Template:Pet XP Table -- -- Outputs the pet xp per level table -- Accepts a pet base level or pet name (used to determine the base level) as input. -- Without such input, the page name will be used to determine the base level. --- function p.xpTable(frame) local args = getArgs(frame) local pet = args[1] or args['p'] or args['pet'] or args['lvl'] or args['level'] or (isPetPage and fullpagename) or nil local data, pet_ = pet and petUtils.getPet(pet) local tabledata = {} local rarities = (data and data.discloseXP) or (pet and petUtils.discloseRarities(pet, true)) or {} local higherLevels = (pet and petUtils.getBaseMax(pet) or 0) > 99 -- if base level is large, display xp for pet lv.101-200 if not pet then for k in rarityTier._getTierIterable do			table.push(rarities, k)		end end for _, k in ipairs(rarities) do		local t = levelingdata[k] if t then local cur = #tabledata + 1 tabledata[cur] = table.map(t, function(n)				return string.wrapHtml(string._formatNum(n), 'span', { class = 'color-green' })			end) tabledata[cur].title = rarityTier._link(k) end end local wikitable = mw.html.create('table'):attr('id', 'mw-customcollapsible-xp-table') :addClass('article-table'):addClass('sortable'):addClass('mw-collapsible'):addClass('mw-collapsed') wikitable:tag('tr'):tag('th'):attr('colspan', 25) :wikitext('   XP Needed to Upgrade This Pet from its Previous Level    ') local row = wikitable:tag('tr'):addClass('table-nocollapse') row:tag('th'):wikitext('Level') for _, v in ipairs(tabledata) do		row:tag('th'):wikitext(v.title) end if higherLevels then row:tag('th'):wikitext('Level') for _, v in ipairs(tabledata) do			row:tag('th'):wikitext(v.title) end end for n = 1, 100, 1 do		row = wikitable:tag('tr') row:tag('td'):addClass('bold'):attr('data-sort-value', n):wikitext(('Lvl %s'):format(n)) for _, v in ipairs(tabledata) do			row:tag('td'):wikitext(v[n] or '') end if higherLevels then local n2 = n + 100 row:tag('td'):addClass('bold'):attr('data-sort-value', n2):wikitext(('Lvl %s'):format(n2)) for _, v in ipairs(tabledata) do				row:tag('td'):wikitext(v[n2] or '') end end end return tostring(wikitable) end

--- -- Template:Calculator/Pet_Leveling_XP -- -- Outputs the pet xp per level table using the base level, the initial and final levels --- function p.calcXP(frame) local args = getArgs(frame) local rarity, lvl_o, lvl_f = args.rarity, tonumber(args.initial), tonumber(args.final) if not (rarity and lvl_o and lvl_f) then return string.wrapHtml('Input is invalid', 'span', { class = 'color-red' }) end local rtable, rkey = rarityTier._getTier(rarity) if lvl_o < 1 or lvl_o > 200 or lvl_f < 1 or lvl_f > 200 or lvl_f < lvl_o then return string.wrapHtml('Input is invalid', 'span', { class = 'color-red' }) end if (lvl_f > 100 and rkey ~= 'legendary') then return string.wrapHtml(('No %s pet can be upgraded beyond level 100'):format(rkey), 'span',			{ class = 'color-red' }) end local levels = levelingdata[rkey] local tot = 0 for i = lvl_o + 1, lvl_f, 1 do		tot = tot + levels[i] end return ('Total XP required: %d'):format(tot) end

--- -- Other exported functions --- function p.inquirerarities(pet) -- used in Module:Infobox/Item if type(pet) ~= 'string' then return '' end local dt, pet = petUtils.getPet(pet) local rarities = dt and dt.rarities if not (dt and rarities) then return '' end local ret = {} for _, r in ipairs(petUtils.discloseRarities(pet, true)) do		local pet_ = pet:gsub(' ', '_') table.push(ret, ('%s;%d'):format(string.upper(pet_), rarityOrder(r))) end return table.concat(ret, ' ') end

return p