Module:Pet

-- local p = {} -- Basics local loader = require('Module:Loader') local getArgs = require('Module:Arguments').getArgs

local string, table, yesno, color, currency, ability, libraryutil, uitext, rarityTier, statModule = loader.require('String', 'Table', 'Yesno', 'Color', 'Currency', 'Ability', 'LibraryUtil', 'UIText', 'RarityTier', 'Statname') local data, aliases, skindata, levelingdata, uitextData, colorAliases, rarityAliases, statAliases = loader.loadData('Pet/Data', 'Pet/Aliases', 'Pet/Skins', 'Pet/LevelingData', 'UIText/Data', 'Color/Aliases', 'RarityTier/Aliases', 'Statname/Aliases')

local makeColor = color._colorTemplates local makeCoins = currency._coins local makeAbility = ability._ability local checkType = libraryutil.checkType local getFormatting = uitext.getFormatting

local lang = mw.getContentLanguage local curtitle = mw.title.getCurrentTitle local pagename = curtitle.prefixedText local fullpagename = curtitle.fullText local reverseConv = table.invert(uitextData.conversions)

local infosymbol = 'ⓘ' local arrowsymbol = '➜'

local function rarityOrder(rarity) return rarityTier._getTier(rarity).order end

local function getPet(pet) local pet_ = pet:lower:gsub(' pet$','') pet_ = aliases[pet_] or string.ucfirst(pet_) -- returns the data and the pet name return data[pet_], pet_ end

function p.inquirerarities(pet) if type(pet) ~= 'string' then return '' end local dt, pet = getPet(pet) local rarities = dt and dt.rarities if not (dt and rarities) then return '' end rarities = rarities:upper:gsub('[^%a]', '') local ret = {} for i = 1,#rarities,1 do		local r = rarities:sub(i,i) local p = pet:gsub(' ','_') table.push(ret, ('%s;%d'):format(string.upper(p), rarityOrder(r))) end return table.concat(ret, ' ') end

function p.inquireslots(pet) if type(pet) ~= 'string' then return '' end local dt, pet = getPet(pet) local rarities = dt and dt.rarities if not (dt and rarities) then return '' end rarities = rarities:upper:gsub('[^%a]', '') local ret = {} for i = 1,#rarities,1 do		local r = rarities:sub(i,i) table.push(ret, (''):format(pet, rarityTier._getTier(r).short:upper)) end return table.concat(ret, '') end

function p.getSkinsList local petSkinsTable = {} for _, petName in ipairs(table.keys(data)) do		local skins = data[petName].skins; if skins then for _,v in ipairs(skins) do table.push(petSkinsTable, v[1]) end end end return petSkinsTable end

function p.getPetList(prepend, append) return table.map(table.keys(data), function(petName)		return (prepend or )..petName..(append or )	end) end

function p._getBaseMax(pet) local data, pet = getPet(pet) local baselv, maxlv if pet and data.levels then baselv, maxlv = data.levels:match('(%d+)[^%d](%d+)') end return tonumber(baselv) or 0, tonumber(maxlv) or 100 end

function p._rarityExists(pet, rarity) local data, pet = getPet(pet) local petRarities = (data.rarities):upper:gsub('[^%a]', '') for i = 1, #petRarities do		local r = petRarities:sub(i,i) if rarityOrder(r) == rarityOrder(rarity) then return true end end return false end

function p._discloseRarities(pet) local data, pet = getPet(pet) local petRarities = (data.rarities):upper:gsub('[^%a]', '') local ret = string.split(petRarities, '') return table.concat(table.map(ret, function(v) return (''):format(v) end), ' / ') 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 (curtitle.namespace == 0 and (fullpagename:find('Pet') and fullpagename or 'ERROR') or 'ERROR') local level = args[2] or args.level local rarity = args[3] or args.rarity return mw.getCurrentFrame:preprocess(p._requestStat(pet, level, rarity)) end

-- (Template:Pet level stat) Module Access Point function p._requestStat(pet, level, rarity) local data, pet = getPet(pet) if not data then return string.wrapHtml('Unrecognized Pet: '..pet, 'span', { class = 'error' }) end level = tonumber(level) or 1 local petRarities = (data.rarities):upper:gsub('[^%a]', '') rarity = rarity or petRarities:sub(1,1) local stats, heldItem = data.stats, data.heldItem local baselv, maxlv = p._getBaseMax(pet) if not stats then return string.wrapHtml(('Missing pet stats (Pet: %s)'):format(pet), 'span', { class = 'error' }) end if not p._rarityExists(pet, rarity) then return string.wrapHtml(('%s Pet does not have such rarity: %s Rarities available: %s')			:format(pet, rarity, p._discloseRarities(pet)), 'span', { class = 'error' }) end if level maxlv then return string.wrapHtml(('%s Pet cannot have such level: %s'):format(pet, level), 'span', { class = 'error' }) end local wikitable = mw.html.create('table'):addClass('wikitable') wikitable:tag('tr') :tag('th'):wikitext((' %s Pet'):format(pet, pet, pet)):css({			width = '70%',			['font-size'] = '3em',			['white-space'] = 'nowrap',			padding = '0 1em !important',		}):attr('rowspan', 2):done :tag('th'):wikitext(('Lv.%d'):format(level)):css('font-size', '1.5em'):done :done wikitable:tag('tr') :tag('th'):wikitext(('%s '):format(rarityTier._link(rarity))):done :done local raritykey = rarityAliases[rarity:lower] wikitable:tag('tr') :tag('td'):wikitext(table.concat{			p.constructStats(stats, level, raritykey, baselv),			p.processAbilities(data.abilities, level, raritykey, baselv),			p.processHeldItems(heldItem, level, raritykey),		}) :css('padding', '1em 2em 0 2em !important') :attr('colspan', 3) :done :done return tostring(wikitable) end

-- 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 (curtitle.namespace == 0 and (fullpagename:find('Pet') and fullpagename or 'ERROR') or 'ERROR') if pet == 'ERROR' then return string.error('Pet name expected, got none') end return mw.getCurrentFrame:preprocess(string.pcall(p._petStatsTable, pet)) end

-- (Template:PetStats) Module Access Point function p._petStatsTable(pet) local data, pet = getPet(pet) if not data then error('Unrecognized Pet: '..pet) end --create various variables local stats, heldItem = data.stats, data.heldItem local baselv, maxlv = p._getBaseMax(pet) local startlv = tonumber(baselv) + 1 if not stats then return error(string.format('Missing pet stats (Pet: %s)', pet), 2) end local tabs = {} local rarities = (data.rarities):upper:gsub('[^%a]', '') for i = 1, #rarities, 1 do		- -- Headers -		local ved_buttons = string.wrapHtml(			'V • E • D', 'span',			{ style = 'float: right; font-size: 11px;' }		) local wikitable = mw.html.create('table'):addClass('wikitable'):css({ ['width'] = '100%' }) wikitable:tag('tr') :tag('th'):wikitext(('Starting Stats (Level %d) %s '):format(startlv, infosymbol)):css({ ['width'] = '50%' }):done :tag('th'):wikitext(('Max Stats (Level %d) %s %s'):format(maxlv, infosymbol, ved_buttons)):css({ ['width'] = '50%' }):done :done local rarityletter = rarities:sub(i,i) local raritykey = rarityAliases[rarityletter:lower] wikitable:tag('tr') -			-- Base stats -			:tag('td'):wikitext(table.concat{				p.constructStats(stats, startlv, raritykey, baselv),				p.processAbilities(data.abilities, startlv, raritykey, baselv),				p.processHeldItems(heldItem, startlv, raritykey),			}):done -			-- Max level stats -			:tag('td'):wikitext(table.concat{				p.constructStats(stats, maxlv, raritykey, baselv),				p.processAbilities(data.abilities, maxlv, raritykey, baselv),				p.processHeldItems(heldItem, maxlv, raritykey),			}):done :done -		-- Bonus headers -		wikitable:tag('tr') :tag('th'):attr({ colspan = 2 }):wikitext(('Leveling Bonuses%s '):format(infosymbol)):done :done wikitable:tag('tr') -			-- Bonus stats -			:tag('td'):attr({ colspan = 2 }):wikitext(table.concat{				p.constructStats(stats, 'bonus', raritykey, baselv),				p.processAbilities(data.abilities, 'bonus', raritykey),			}):done tabs[#tabs+1] = table.concat{ '|-|',			rarityTier._getTier(rarityletter).name, ' = ',			string.wrapHtml(('%s %s Pet'):format(tostring(rarityTier._link(raritykey)), pet), 'div', { style = {				['font-size'] = '1.5em',				['text-align'] = 'center',				['margin'] = '.5em 0',			}}), 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 p.processAbilities --	│	└── 1.3 p.processVars --	│		└── 1.4 p._processVariables --	└── 1.5 p.processHeldItems --		└── (Same as p.processAbilities)

--	Helper Functions for p._petStatsTable: 1.1

function p.constructStats(stats, petlevel, rarity, baselv) checkType('constructStats', 1, stats, 'table') checkType('constructStats', 2, petlevel, {'string', 'number'}) rarity = rarityAliases[rarity] or rarity local list = {} for key, value in pairs(stats) do		local lvl = value.level or value.lvl or value.bonus or value[1] local base = value.base or value[2] or 0 reqRarity = value.req and rarityAliases[value.req:lower] or nil if not value.req or rarityOrder(rarity) >= rarityOrder(reqRarity) then local num if tonumber(petlevel) then num = base + (tonumber(petlevel) - baselv) * lvl elseif petlevel == 'bonus' then num = lvl end table.push(list, string.wrapHtml({ tostring(statModule._getStatName(key)), (num < 0 and ': ' or ': +'), num, (petlevel == 'bonus' and ' per level' or '') }, 'li')) end end return table.concat{ petlevel == 'bonus' and 'Bonus stats:' or 'Stats:', string.wrapHtml(list, 'ul') } end

--	Helper Functions for p._petStatsTable: 1.2

function p.processAbilities(ab, petlevel, rarity, baselv) checkType('processAbilities', 1, ab, 'table') checkType('processAbilities', 2, petlevel, {'string', 'number'}) checkType('processAbilities', 3, rarity, 'string') --if not ab.names[ab.amounts] then error(string.format('Missing ability name #%s', amount), 2) end --if not ab.descs[ab.amounts] then error(string.format('Missing ability description #%s', amount), 2) end local amounts = ab.amounts and ab.amounts:gsub('[^%d]', '') local raritynum = rarityOrder(rarity) + 1 local amount = amounts and amounts:sub(raritynum, raritynum) or table.length(ab.variables[rarity]) local list = {} for i = 1, tonumber(amount), 1 do		if tonumber(petlevel) then desc = p.processVars(ab.desc[i], petlevel, ab.variables[rarity][i], baselv) table.push(list,					string.wrapHtml({ makeAbility(ab.name[i], false, 'abilityName'), ' - ', desc }, 'li')			) elseif petlevel == 'bonus' then desc = p.processVars(ab.bonus_desc[i] or 'No leveling bonus', petlevel, ab.variables[rarity][i], baselv) table.push(list,				string.wrapHtml({ makeAbility(ab.name[i], false, 'abilityName'), ': ', desc }, 'li')			) end end return table.concat{ makeAbility('Abilities:'), string.wrapHtml{ list, '', { style = 'list-style-type: circle; line-height: inherit;' } }	} end

--	Helper Functions for p._petStatsTable: 1.3

function p.processVars(desc, petlevel, vars, baselv) local function xmlColorConvert(s) checkType('xmlColorConvert', 1, s, 'string') 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 makeColor(c, str) or '<'..tag..'>'..str..''			end		) end local function processSTAT(str) if str:find('STAT_[%u-]+') then str = str:gsub('STAT_([%u-]+)', function(s)				local dt = statModule._getstatdata(s)				return statModule._getStatName(s)			end) end if str:find('STATs_([%u-]+)') then str = str:gsub('STATs_([%u-]+)', function(s)				local dt = statModule._getstatdata(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(makeCoins)) end local function formatFakeAbility(s) checkType('formatFakeAbility', 1, s, 'string') return s:gsub('{ABILITY}(.+){/ABILITY}', function(s)			return makeAbility(s, false, 'abilityName')		end) end local function processVariables(variable, petlevel, baselv) -- check for errors if not variable then return end if not variable.color then error(string.format('Variable argument missing (color) (Pet: %s)', pet), 2) end local num = p._processVariables(variable, petlevel, baselv) --add suffix num = tostring(num) .. (variable.suffix or '') --colorize return makeColor(variable.color, num) end desc = formatFakeAbility(replaceCoins(processRARITY(processSTAT(xmlColorConvert(desc))))) if vars then for i in desc:gmatch('{(%d+)}') do			local j = tonumber(i) local subst = processVariables(vars[j+1], petlevel, baselv) desc = subst and desc:gsub(('{%d}'):format(j), subst) or desc end end -- fullstop handler if desc:match('%.$') then desc = desc:sub(1,#desc-1) end return desc end

--	Helper Functions for p._petStatsTable: 1.4

--	Also a helper function for p.getPetsTemplate

function p._processVariables(variable, petlevel, baselv) -- check for errors if not variable.per_lvl then return error('Variable argument missing (per_lvl)', 2) end --perform calculations local num if tonumber(petlevel) then num = (string._toArabic(variable.base) or 0) + (tonumber(petlevel) - baselv) * variable.per_lvl elseif petlevel == 'bonus' then num = variable.per_lvl end if variable.round_to_full then num = math.floor(num) end return variable.is_roman and string._toRoman(num) or num end

--	Helper Functions for p._petStatsTable: 1.5

function p.processHeldItems(heldItem, petlevel, rarity) checkType('processHeldItems', 1, heldItem, 'table', true) checkType('processHeldItems', 2, petlevel, { 'string', 'number' }) checkType('processHeldItems', 3, rarity, 'string') local list, desc if heldItem and (tonumber(petlevel)) and rarity == heldItem.req then desc = p.processVars(heldItem.desc, petlevel, nil, nil) list = string.wrapHtml(desc, 'li') else return end return table.concat{ makeAbility('Held Item: '..heldItem.name, false, 'abilityName'), string.wrapHtml{ list, '', { style = 'list-style-type: none; line-height: inherit;' } }	} end

--- -- Generates table for Module:Inventory slot/Templates --- function p.getPetsTemplate local allPackages = table.map(table.keys(data), function(pet)		local returnPackage = {}		local data = data[pet]		if not data then return end		if not data.rarities then return end		local baselv, maxlv = 0, 100		if data.levels then			baselv, maxlv = data.levels:match('(%d+)[^%d](%d+)')		end		local leveldisplay = tonumber(baselv) + 1 .. arrowsymbol .. maxlv		local rarities = (data.rarities):upper:gsub('[^%a]', '')		local i = #rarities -- highest rarity		for i = 1, #rarities, 1 do			local rarityletter = rarities:sub(i,i)			local raritykey = rarityAliases[rarityletter:lower]			if not data.stats then return end			local stats = data.stats			local statStr = {}			for key, value in pairs(stats) do				reqRarity = value.req and rarityAliases[value.req:lower] or nil				if not value.req or rarityOrder(raritykey) >= rarityOrder(reqRarity) then					local min, max = (value[2] or 0) + value[1], (value[2] or 0) + 100 * value[1] table.push(statStr,('&7%s: &a%s%s%s%s'):format( statModule._getstatdata(key).name, (min < 0 and '' or '+'), min, arrowsymbol, max ))				end end statStr = table.concat(statStr,'/')..'//' local ab_names = data.abilities.name local ab_tooltips = data.abilities.tooltip or data.abilities.tooltips local abilityStr = '' local amounts = data.abilities.amounts and data.abilities.amounts:gsub('[^%d]', '') local raritynum = rarityOrder(raritykey) + 1 local amount = amounts and amounts:sub(raritynum, raritynum) or table.length(data.abilities.variables[raritykey]) if ab_tooltips and (#ab_names == #ab_tooltips) then local list = {} for i = 1, tonumber(amount), 1 do					local name = ab_names[i] if not ab_tooltips[i] then error(pet..' Pet: desc and tooltip length mismatch') end local desc = ab_tooltips[i] local vars = data.abilities.variables[raritykey][i] if vars then for k in desc:gmatch('{(%d+)}') do							local j = tonumber(k) if not vars[j+1].per_lvl then error(('Variable argument missing (per_lvl) (Pet: %s; Rarity: %s)'):format(pet, raritykey)) end local min = p._processVariables(vars[j+1], tonumber(baselv) + 1, baselv) local max = p._processVariables(vars[j+1], maxlv, baselv) local subst if (not min or not max or min == max) then subst = min or max else subst = min .. '➜' .. max end desc = subst and desc:gsub(('{%d}'):format(j), subst) or desc end end table.push(list, ('&6%s/&r%s'):format( name, desc:gsub('^/+(.*)','%1'):gsub('(.-)/+$','%1') )) end abilityStr = table.concat(list,'//')..'//' end local hi = data.heldItem local hiTooltip = hi and (hi.tooltip or hi.tooltips) local helditemStr = (hi and hiTooltip and (raritykey == rarityAliases[hi.req:lower])) and ('&6Held Item: %s/&r%s//'):format( hi.name, hiTooltip:gsub('^/+(.*)','%1'):gsub('(.-)/+$','%1') ) or '' local id = ('%s Pet (%s)'):format(pet, rarityTier._getTier(raritykey).short:upper) table.push(returnPackage, { pet, data.petType, leveldisplay, statStr, abilityStr, helditemStr, r = raritykey, id = id }) if i == #rarities then table.push(returnPackage, { pet, data.petType, leveldisplay, statStr, abilityStr, helditemStr, r = raritykey, id = '{o} Pet' }) end end return returnPackage end)	local ret = {}	table.each(allPackages, function(pack) table.each(pack, function(t)			table.push(ret, t)		end) end)	return ret end

--- -- Generates table for Module:Inventory slot/Templates --- function p.getMysteryTemplate local ls = table.map(table.keys(data), function(petName)		local petData = data[petName]		local stats = {}		for stat, value in pairs(petData.stats) do			if not value.req then				value = tonumber(value[1])				table.push(stats, ('&r%s: &a%s'):format(string.ucfirst(statAliases[stat]), value < 0 and value or '+'..value))			end		end		return { petName, petData.petType, table.concat(stats, '/') }	end) return table.merge(ls, { id = 'Mystery {o} Pet' }) end

--- -- Generates table for Module:Inventory slot/Templates --- function p.getSkinsTemplate local petSkinsTable = {} for petName, skins in pairs(skindata) do		for _,v in ipairs(skins) do			local attrs = string.split(v[2], '%s+') local r = rarityTier._getTier(attrs[1]).name table.remove(attrs,1) local animated, daynight = false, false for _,attr in ipairs(attrs) do				if attr:lower == 'animated' then animated = true elseif attr:lower == 'daynight' then daynight = true end end local suppStr = string.format('%s%s',				animated and getFormatting(r,'This skin is animated!//') or ,				daynight and getFormatting(r,'This skin adapts to the day-night cycle!//') or ) table.push(petSkinsTable, { v[1], petName, r:upper, getFormatting(r), suppStr }) end end return petSkinsTable 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 inp = args[1] or args['p'] or args['pet'] or args['lvl'] or args['level'] or curtitle.namespace == 0 and fullpagename:find('Pet') and fullpagename or nil local tabledata = {} local levels = levelingdata.pet_levels local base = tonumber(inp) or p._getBaseMax(inp) for k,v in rarityTier._getTierIterable do		local offset = levelingdata.pet_rarity_offset[k] if offset then local t = table.slice(levels,1+offset,99+offset) local cur = #tabledata+1 tabledata[cur] = table.map(table.merge({0}, t), function(n)				return string.wrapHtml(string._formatNum(n), 'font', {style = 'color:#55FF55'})			end) tabledata[cur].title = rarityTier._link(k) mw.log(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') :tag('tr'):tag('th'):attr('colspan',('%d'):format(#tabledata+1)) :wikitext('  XP Needed to Upgrade This Pet from its Previous Level   ') :done:done local row = wikitable:tag('tr'):tag('th'):wikitext('Level'):done for _,v in ipairs(tabledata) do		row:tag('th'):wikitext(v.title) end for n = 1, 100, 1 do		row = wikitable:tag('tr'):tag('td'):css('font-weight','bold') :attr('data-sort-value',n):wikitext(('Lvl %s'):format(n+base)):done for _,v in ipairs(tabledata) do			row:tag('td'):wikitext(v[n]) end end return tostring(wikitable:allDone) 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, base, lvl_o, lvl_f = args.rarity, tonumber(args.base), tonumber(args.initial), tonumber(args.final) local icon = ' ' if not (rarity and base and lvl_o and lvl_f) then return string.wrapHtml(icon..'Input is invalid', 'font', {style = 'color: #FF5555;'}) end local rtable, rkey = rarityTier._getTier(rarity) local levels, offset = levelingdata.pet_levels, levelingdata.pet_rarity_offset[rkey] lvl_o, lvl_f = lvl_o - base, lvl_f - base if lvl_o < 1 or lvl_o > 100 or lvl_f < 1 or lvl_f > 100 or lvl_f < lvl_o then return string.wrapHtml(icon..'Input is invalid', 'font', {style = 'color: #FF5555;'}) end local t = table.merge({0}, table.slice(levels,1+offset,99+offset)) local tot = 0 for i = lvl_o+1, lvl_f, 1 do		tot = tot + t[i] end return (icon..'Total XP required: %d'):format(tot) end

return p