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 function rarityOrder(rarity) return rarityTier._getTier(rarity).order end

function p.inquirerarities(pet) local pet_ = pet:gsub(' Pet$','') if not data[pet_] then return end local dt = data[pet_] local rarities = dt.rarities if not 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.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) pet = type(pet) == 'string' and pet:lower:gsub(' pet', '') or pet local data = pet and data[aliases[mw.text.trim(pet)] or string.ucfirst(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) pet = type(pet) == 'string' and pet:lower:gsub(' pet', '') or pet local data = pet and data[aliases[mw.text.trim(pet)] or string.ucfirst(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) pet = type(pet) == 'string' and pet:lower:gsub(' pet', '') or pet local data = pet and data[aliases[mw.text.trim(pet)] or string.ucfirst(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) pet = pet:lower:gsub(' pet', '') pet = aliases[mw.text.trim(pet)] or string.ucfirst(pet) level = tonumber(level) or 1 local data = data[pet] if not data then return string.wrapHtml("Unrecognized Pet: "..pet, 'span', { class = 'error' }) end 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, level) local pet = pet:lower:gsub(' pet', '') pet = aliases[mw.text.trim(pet)] or string.ucfirst(pet) local data = data[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) 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(('Base Stats (Level %d) %s '):format(baselv, 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, baselv, raritykey, baselv),				p.processAbilities(data.abilities, baselv, raritykey, baselv),				p.processHeldItems(heldItem, baselv, 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, '=', tostring(rarityTier._link(raritykey)), ' stats:', tostring(wikitable) } end return table.concat{' ', table.concat(tabs), ' '} end

--	Helper Functions of p._petStatsTable: --	├── >>>p.constructStats<<< --	├── p.processAbilities --	│	└── p.processVars --	│		├── p.xmlColorConvert, p.processSTAT, p.processRARITY, p.replaceCoins, p.formatFakeAbility --	│		└── p.processVariables --	└── p.processHeldItems --		└── (Same as p.processAbilities)

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 of p._petStatsTable: --	├── p.constructStats --	├── >>>p.processAbilities<<< --	│	└── p.processVars --	│		├── p.xmlColorConvert, p.processSTAT, p.processRARITY, p.replaceCoins, p.formatFakeAbility --	│		└── p.processVariables --	└── p.processHeldItems --		└── (Same as p.processAbilities)

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 of p._petStatsTable: --	├── p.constructStats --	├── p.processAbilities --	│	└── >>>p.processVars<<< --	│		├── p.xmlColorConvert, p.processSTAT, p.processRARITY, p.replaceCoins, p.formatFakeAbility --	│		└── p.processVariables --	└── p.processHeldItems --		└── (Same as p.processAbilities)

function p.processVars(desc, petlevel, vars, baselv) desc = p.xmlColorConvert(desc) desc = p.processSTAT(desc) desc = p.processRARITY(desc) desc = p.replaceCoins(desc) desc = p.formatFakeAbility(desc) if vars then local MAX = 99 for j = 0, MAX, 1 do			if desc:find(('{%d}'):format(j)) then local subst = p.processVariables(vars[j+1], petlevel, baselv) desc = subst and desc:gsub(('{%d}'):format(j), subst) or desc else break end end end -- fullstop handler if desc:match(".$") then desc = desc:sub(1,#desc-1) end -- linebreaks handler desc = desc:gsub("", "/") return desc end

--	Helper Functions of p._petStatsTable: --	├── p.constructStats --	├── p.processAbilities --	│	└── p.processVars --	│		├── >>>p.xmlColorConvert<<<, p.processSTAT, p.processRARITY, p.replaceCoins, p.formatFakeAbility --	│		└── p.processVariables --	└── p.processHeldItems --		└── (Same as p.processAbilities)

function p.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

--	Helper Functions of p._petStatsTable: --	├── p.constructStats --	├── p.processAbilities --	│	└── p.processVars --	│		├── p.xmlColorConvert, >>>p.processSTAT<<<, p.processRARITY, p.replaceCoins, p.formatFakeAbility --	│		└── p.processVariables --	└── p.processHeldItems --		└── (Same as p.processAbilities)

function p.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

--	Helper Functions of p._petStatsTable: --	├── p.constructStats --	├── p.processAbilities --	│	└── p.processVars --	│		├── p.xmlColorConvert, p.processSTAT, >>>p.processRARITY<<<, p.replaceCoins, p.formatFakeAbility --	│		└── p.processVariables --	└── p.processHeldItems --		└── (Same as p.processAbilities)

function p.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

--	Helper Functions of p._petStatsTable: --	├── p.constructStats --	├── p.processAbilities --	│	└── p.processVars --	│		├── p.xmlColorConvert, p.processSTAT, p.processRARITY, >>>p.replaceCoins<<<, p.formatFakeAbility --	│		└── p.processVariables --	└── p.processHeldItems --		└── (Same as p.processAbilities)

function p.replaceCoins(str) return str:gsub('COINS', tostring(makeCoins)) end

--	Helper Functions of p._petStatsTable: --	├── p.constructStats --	├── p.processAbilities --	│	└── p.processVars --	│		├── p.xmlColorConvert, p.processSTAT, p.processRARITY, p.replaceCoins, >>>p.formatFakeAbility<<< --	│		└── p.processVariables --	└── p.processHeldItems --		└── (Same as p.processAbilities)

function p.formatFakeAbility(s) checkType('formatFakeAbility', 1, s, 'string') return s:gsub('{ABILITY}(.+){/ABILITY}', function(s)		return makeAbility(s, false, 'abilityName')	end) end

--	Helper Functions of p._petStatsTable: --	├── p.constructStats --	├── p.processAbilities --	│	└── p.processVars --	│		├── p.xmlColorConvert, p.processSTAT, p.processRARITY, p.replaceCoins, p.formatFakeAbility --	│		└── >>>p.processVariables<<< --	└── p.processHeldItems --		└── (Same as p.processAbilities)

function p.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 if variable.is_roman then if variable.per_lvl then return makeColor(variable.color, variable.per_lvl) else return makeColor(variable.color, variable.base) end end -- check for errors again if not variable.per_lvl and variable.per_lvl ~= 0 then return error(string.format('Variable argument missing (per_lvl) (Pet: %s)', pet), 2) end --perform calculations local num if tonumber(petlevel) then num = (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 --add suffix num = tostring(num)..(variable.suffix or '') --colorize return makeColor(variable.color, num) end

--	Helper Functions of p._petStatsTable: --	├── p.constructStats --	├── p.processAbilities --	│	└── p.processVars --	│		├── p.xmlColorConvert, p.processSTAT, p.processRARITY, p.replaceCoins, p.formatFakeAbility --	│		└── p.processVariables --	└── >>>p.processHeldItems<<< --		└── (Same as p.processAbilities)

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 return table.merge(table.map(table.keys(data), function(pet) 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 rarities = (data.rarities):upper:gsub('[^%a]', '') i = #rarities -- highest rarity local rarity = rarityTier._getTier(rarities:sub(i,i)).name 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(rarity) >= rarityOrder(reqRarity) then local num = (value[2] or 0) + 100 * value[1] table.push(statStr,("&7%s: &a%s%s"):format( statModule._getstatdata(key).name, (num < 0 and '' or '+'), num ))			end end statStr = table.concat(statStr,'/')..'//' local ab_names = data.abilities.name local ab_tooltips = data.abilities.tooltip or data.abilities.tooltips local abilityStr = (ab_tooltips and (#ab_names == #ab_tooltips)) and (table.concat(table.map(ab_names, function(name, i)				if not ab_tooltips[i] then error(pet..' Pet: desc and tooltip length mismatch') end				return ('&6%s/&r%s'):format( name, ab_tooltips[i]:gsub('^/+(.*)','%1'):gsub('(.-)/+$','%1') )			end),'//')..'//') or '' local hi = data.heldItem local hiTooltip = hi and (hi.tooltip or hi.tooltips) local helditemStr = (hi and hiTooltip and (rarity == rarityTier._getTier(hi.req).name)) and ('&6Held Item: %s/&r%s//'):format( hi.name, hiTooltip:gsub('^/+(.*)','%1'):gsub('(.-)/+$','%1') ) or '' return { pet, data.petType, maxlv, statStr, abilityStr, helditemStr, r=rarity } end), { id = '$o Pet' }) 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) 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 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