Module:Learnsets: Difference between revisions
Jump to navigation
Jump to search
No edit summary |
No edit summary |
||
| Line 1: | Line 1: | ||
local p = {} | local p = {} | ||
local | local function getArgs(frame) | ||
local args = {} | |||
local parent = frame:getParent() | |||
if parent then | |||
for k, v in pairs(parent.args) do | |||
if v ~= '' then | |||
args[k] = v | |||
end | |||
end | |||
end | |||
for k, v in pairs(frame.args) do | |||
if v ~= '' then | |||
args[k] = v | |||
end | |||
end | |||
return args | |||
end | |||
local | local moveDataCache = nil | ||
local | local function blankToNil(v) | ||
if v == nil or v == '' then | |||
return nil | |||
end | |||
return v | |||
end | |||
local | local function loadMoveData() | ||
if moveDataCache then | |||
return moveDataCache | |||
end | |||
local csvData = mw.ext.externalData.getExternalData({ | |||
url = 'https://wiki.pokemonrevolution.net/index.php?title=Special:GetData/RawMovesList', | |||
} | format = 'CSV with header', | ||
data = { | |||
Name = 'Name', | |||
Type = 'Type', | |||
Category = 'Category', | |||
Power = 'Power', | |||
Accuracy = 'Accuracy', | |||
PP = 'PP', | |||
Broken = 'Broken', | |||
Typeless = 'Typeless', | |||
Priority = 'Priority', | |||
TM = 'TM', | |||
['Move Tutor'] = 'Move Tutor', | |||
Egg = 'Egg', | |||
}, | |||
}) | |||
moveDataCache = {} | |||
if csvData then | |||
for _, row in ipairs(csvData) do | |||
moveDataCache[row.Name] = { | |||
type = blankToNil(row.Type) or 'Normal', | |||
category = blankToNil(row.Category) or 'Status', | |||
power = blankToNil(row.Power), | |||
accuracy = blankToNil(row.Accuracy), | |||
pp = blankToNil(row.PP), | |||
broken = row.Broken or 'N', | |||
typeless = row.Typeless or 'N', | |||
priority = blankToNil(row.Priority) or '0', | |||
tm = blankToNil(row.TM), | |||
move_tutor = blankToNil(row['Move Tutor']), | |||
egg = blankToNil(row.Egg) | |||
} | |||
end | |||
end | |||
return moveDataCache | |||
end | end | ||
local function | local function loadLearnset(pokemonName) | ||
local jsonPath = '$[' .. mw.text.jsonEncode(pokemonName) .. ']' | |||
local ok, result, errors = pcall(function() | |||
return mw.ext.externalData.getFileData({ | |||
source = 'data', | |||
['file name'] = 'Learnsets.json', | |||
format = 'JSON', | |||
['use jsonpath'] = true, | |||
data = { poke = jsonPath }, | |||
}) | |||
end) | |||
if not ok then | |||
return nil, 'Lua error: ' .. tostring(result) | |||
end | |||
if errors and #errors > 0 then | |||
return nil, 'ExternalData error: ' .. table.concat(errors, ' | ') | |||
end | |||
local raw = result and result.poke | |||
if type(raw) ~= 'table' then | |||
return nil, 'No learnset data found for ' .. pokemonName | |||
end | |||
return raw, nil | |||
end | end | ||
local function | local learnsetCache = {} | ||
local learnsetErrorCache = {} | |||
local function getCachedLearnset(pokemonName) | |||
if learnsetCache[pokemonName] == nil and learnsetErrorCache[pokemonName] == nil then | |||
learnsetCache[pokemonName], learnsetErrorCache[pokemonName] = loadLearnset(pokemonName) | |||
end | |||
return learnsetCache[pokemonName], learnsetErrorCache[pokemonName] | |||
end | end | ||
local function | local pokemonTypeCache = nil | ||
local function loadPokemonTypes() | |||
if pokemonTypeCache then return pokemonTypeCache end | |||
local csvData = mw.ext.externalData.getExternalData({ | |||
url = 'https://wiki.pokemonrevolution.net/index.php?title=Special:GetData/PokemonRawList', | |||
format = 'CSV with header', | |||
data = { | |||
Name = 'Name', | |||
Type1 = 'Type1', | |||
Type2 = 'Type2', | |||
}, | |||
}) | |||
pokemonTypeCache = {} | |||
if csvData then | |||
for _, row in ipairs(csvData) do | |||
pokemonTypeCache[row.Name] = { | |||
type1 = row.Type1 or '', | |||
type2 = row.Type2 or '', | |||
} | |||
end | |||
end | |||
return pokemonTypeCache | |||
end | end | ||
local function | local typeColorCache = {} | ||
local function typeColor(frame, pokeType) | |||
pokeType = (pokeType and pokeType ~= '') and pokeType or 'Normal' | |||
if not typeColorCache[pokeType] then | |||
typeColorCache[pokeType] = { | |||
light = frame:expandTemplate{ title = pokeType .. ' color light' }, | |||
base = frame:expandTemplate{ title = pokeType .. ' color' }, | |||
dark = frame:expandTemplate{ title = pokeType .. ' color dark' }, | |||
} | |||
end | |||
return typeColorCache[pokeType] | |||
end | end | ||
local function | local categoryColorCache = {} | ||
local function categoryColor(frame, category) | |||
category = (category and category ~= '') and category or 'Status' | |||
if not categoryColorCache[category] then | |||
categoryColorCache[category] = { | |||
base = frame:expandTemplate{ title = category .. ' color' }, | |||
dark = frame:expandTemplate{ title = category .. ' color dark' }, | |||
} | |||
end | |||
return categoryColorCache[category] | |||
end | end | ||
local function | local function isSTAB(moveType, primaryType, secondaryType) | ||
return moveType == primaryType or moveType == secondaryType | |||
end | end | ||
local function | local function formatItemNumber(label, num) | ||
num = tonumber(num) | |||
if not num then | |||
return '—' | |||
end | |||
return label .. string.format('%02d', num) | |||
end | end | ||
local | function p.renderLevelUp(frame) | ||
local args = getArgs(frame) | |||
local pokemonName = args[1] or args.pokemon | |||
local primaryType = args.type1 or "" | |||
local secondaryType = args.type2 or "" | |||
local pokemon, lsErr = getCachedLearnset(pokemonName) | |||
local moveData = loadMoveData() | |||
if not pokemon then | |||
return "<span style='color:red'>Data load error: " .. (lsErr or 'unknown') .. "</span>" | |||
end | |||
local moves = pokemon.level_up or {} | |||
local colors = typeColor(frame, primaryType) | |||
local html = mw.html.create('div') | |||
:css('width', '77%') | |||
:css('margin', 'auto') | |||
local table = html:tag('table') | |||
:addClass('sortable') | |||
:css('width', '100%') | |||
:css('text-align', 'center') | |||
:css('border-radius', '15px 15px 0 0') | |||
:css('border', '3px solid #' .. colors.dark) | |||
:css('border-bottom', 'none') | |||
:css('background-color', '#' .. colors.base) | |||
:css('padding', '7px') | |||
local header = table:tag('tr') | |||
:css('background-color', '#' .. colors.light) | |||
:css('color', '#' .. colors.dark) | |||
header:tag('th'):wikitext('Level'):css('width', '10%'):css('border-radius', '15px 1px 1px 1px') | |||
header:tag('th'):wikitext('Move'):css('width', '15%') | |||
header:tag('th'):wikitext('Type'):css('width', '17%') | |||
header:tag('th'):wikitext('Category'):css('width', '16%') | |||
header:tag('th'):wikitext('Base Power'):css('width', '17%') | |||
header:tag('th'):wikitext('Accuracy'):css('width', '15%') | |||
header:tag('th'):wikitext('PP'):css('width', '10%'):css('border-radius', '1px 15px 1px 1px') | |||
for _, move in ipairs(moves) do | |||
local moveName = move.move | |||
local level = move.level | |||
local moveInfo = moveData[moveName] or {} | |||
local row = table:tag('tr'):css('background-color', '#FFFFFF') | |||
row:tag('td'):wikitext(tostring(level)) | |||
local moveLink = '[[' .. moveName .. ']]' | |||
if isSTAB(moveInfo.type, primaryType, secondaryType) and moveInfo.category ~= "Status" then | |||
moveLink = "'''" .. moveLink .. "'''" | |||
end | |||
if moveInfo.broken == "Y" then | |||
moveLink = "''" .. moveLink .. "''" | |||
end | |||
row:tag('td'):wikitext(moveLink) | |||
local moveTypeColors = typeColor(frame, moveInfo.type) | |||
row:tag('td') | |||
:css('background-color', '#' .. moveTypeColors.base) | |||
:css('border', '2px solid #' .. moveTypeColors.dark) | |||
:wikitext('[[' .. (moveInfo.type or 'Normal') .. '_(type)|<span style="color:#FFFFFF;">' .. (moveInfo.type or 'Normal') .. '</span>]]') | |||
local moveCatColors = categoryColor(frame, moveInfo.category) | |||
row:tag('td') | |||
:css('background-color', '#' .. moveCatColors.base) | |||
:css('border', '2px solid #' .. moveCatColors.dark) | |||
:wikitext('[[' .. (moveInfo.category or 'Status') .. ' moves|<span style="color:#FFFFFF;">' .. (moveInfo.category or 'Status') .. '</span>]]') | |||
row:tag('td'):wikitext(moveInfo.power or '—') | |||
row:tag('td'):wikitext(moveInfo.accuracy or '—') | |||
row:tag('td'):wikitext(moveInfo.pp or '—') | |||
end | |||
html:tag('div') | |||
:css('text-align', 'left') | |||
:css('background-color', '#' .. colors.light) | |||
:css('padding', '10px') | |||
:css('border', '3px solid #' .. colors.dark) | |||
:css('border-top', 'none') | |||
:css('border-radius', '0 0 15px 15px') | |||
:wikitext([=[ | |||
* '''Emboldened''' moves denote this Pokémon avails from a STAB bonus for it. | |||
* ''Italicized'' moves are [[Broken_Moves_%26_Abilities|broken]]; see their individual pages for more information. | |||
* All moves subsumed on this list are relearnable via the [[Move Relearner]]s if lost. | |||
]=]) | |||
return tostring(html) .. '<br clear="all">' | |||
end | end | ||
local | function p.renderTMsHMs(frame) | ||
local args = getArgs(frame) | |||
local pokemonName = args[1] or args.pokemon | |||
local primaryType = args.type1 or "" | |||
local secondaryType = args.type2 or "" | |||
local pokemon = getCachedLearnset(pokemonName) | |||
local moveData = loadMoveData() | |||
if not pokemon then return '' end | |||
local tms = pokemon.tms or {} | |||
local hms = pokemon.hms or {} | |||
local colors = typeColor(frame, primaryType) | |||
local html = mw.html.create('div') | |||
:css('width', '77%') | |||
:css('margin', 'auto') | |||
local table = html:tag('table') | |||
:addClass('sortable') | |||
:css('width', '100%') | |||
:css('text-align', 'center') | |||
:css('border-radius', '25px 25px 0 0') | |||
:css('border', '3px solid #' .. colors.dark) | |||
:css('border-bottom', 'none') | |||
:css('background-color', '#' .. colors.base) | |||
:css('padding', '7px') | |||
local header = table:tag('tr') | |||
:css('background-color', '#' .. colors.light) | |||
:css('color', '#' .. colors.dark) | |||
header:tag('th'):attr('colspan', '2'):wikitext('TM'):css('width', '10%'):css('border-top-left-radius', '25px') | |||
header:tag('th'):wikitext('Move'):css('width', '15%') | |||
header:tag('th'):wikitext('Type'):css('width', '17%') | |||
header:tag('th'):wikitext('Category'):css('width', '16%') | |||
header:tag('th'):wikitext('Base Power'):css('width', '17%') | |||
header:tag('th'):wikitext('Accuracy'):css('width', '15%') | |||
header:tag('th'):wikitext('PP'):css('width', '10%'):css('border-top-right-radius', '25px') | |||
for _, moveName in ipairs(tms) do | |||
local moveInfo = moveData[moveName] or {} | |||
local row = table:tag('tr'):css('background-color', '#FFFFFF') | |||
row:tag('td'):attr('colspan', '2'):wikitext(formatItemNumber('TM', moveInfo.tm)) | |||
local moveLink = '[[' .. moveName .. ']]' | |||
if isSTAB(moveInfo.type, primaryType, secondaryType) and moveInfo.category ~= "Status" then | |||
moveLink = "'''" .. moveLink .. "'''" | |||
end | |||
if moveInfo.broken == "Y" then | |||
moveLink = "''" .. moveLink .. "''" | |||
end | |||
row:tag('td'):wikitext(moveLink) | |||
local moveTypeColors = typeColor(frame, moveInfo.type) | |||
row:tag('td') | |||
:css('background-color', '#' .. moveTypeColors.base) | |||
:css('border', '2px solid #' .. moveTypeColors.dark) | |||
:wikitext('[[' .. (moveInfo.type or 'Normal') .. '_(type)|<span style="color:#FFFFFF;">' .. (moveInfo.type or 'Normal') .. '</span>]]') | |||
local moveCatColors = categoryColor(frame, moveInfo.category) | |||
row:tag('td') | |||
:css('background-color', '#' .. moveCatColors.base) | |||
:css('border', '2px solid #' .. moveCatColors.dark) | |||
:wikitext('[[' .. (moveInfo.category or 'Status') .. ' moves|<span style="color:#FFFFFF;">' .. (moveInfo.category or 'Status') .. '</span>]]') | |||
row:tag('td'):wikitext(moveInfo.power or '—') | |||
row:tag('td'):wikitext(moveInfo.accuracy or '—') | |||
row:tag('td'):wikitext(moveInfo.pp or '—') | |||
end | |||
for _, moveName in ipairs(hms) do | |||
local moveInfo = moveData[moveName] or {} | |||
local row = table:tag('tr'):css('background-color', '#FFFFCC') | |||
row:tag('td'):attr('colspan', '2'):wikitext(formatItemNumber('HM', moveInfo.tm)):css('font-weight', 'bold') | |||
local moveLink = '[[' .. moveName .. ']]' | |||
if isSTAB(moveInfo.type, primaryType, secondaryType) and moveInfo.category ~= "Status" then | |||
moveLink = "'''" .. moveLink .. "'''" | |||
end | |||
if moveInfo.broken == "Y" then | |||
moveLink = "''" .. moveLink .. "''" | |||
end | |||
row:tag('td'):wikitext(moveLink) | |||
local moveTypeColors = typeColor(frame, moveInfo.type) | |||
row:tag('td') | |||
:css('background-color', '#' .. moveTypeColors.base) | |||
:css('border', '2px solid #' .. moveTypeColors.dark) | |||
:wikitext('[[' .. (moveInfo.type or 'Normal') .. '_(type)|<span style="color:#FFFFFF;">' .. (moveInfo.type or 'Normal') .. '</span>]]') | |||
local moveCatColors = categoryColor(frame, moveInfo.category) | |||
row:tag('td') | |||
:css('background-color', '#' .. moveCatColors.base) | |||
:css('border', '2px solid #' .. moveCatColors.dark) | |||
:wikitext('[[' .. (moveInfo.category or 'Status') .. ' moves|<span style="color:#FFFFFF;">' .. (moveInfo.category or 'Status') .. '</span>]]') | |||
row:tag('td'):wikitext(moveInfo.power or '—') | |||
row:tag('td'):wikitext(moveInfo.accuracy or '—') | |||
row:tag('td'):wikitext(moveInfo.pp or '—') | |||
end | |||
html:tag('div') | |||
:css('text-align', 'left') | |||
:css('background-color', '#' .. colors.light) | |||
:css('padding', '10px') | |||
:css('border', '3px solid #' .. colors.dark) | |||
:css('border-top', 'none') | |||
:css('border-radius', '0 0 25px 25px') | |||
:wikitext([=[ | |||
* '''Emboldened''' moves denote that this Pokémon avails from a STAB bonus for it. | |||
* ''Italicized'' moves are [[Broken_Moves_%26_Abilities|broken]]; see their individual pages for more information. | |||
* Refer to [[TMs and HMs]] for information on each move's obtainability. | |||
]=]) | |||
return tostring(html) .. '<br clear="all">' | |||
end | end | ||
function p. | function p.renderMoveTutors(frame) | ||
local args = getArgs(frame) | |||
local pokemonName = args[1] or args.pokemon | |||
local primaryType = args.type1 or "" | |||
local secondaryType = args.type2 or "" | |||
local pokemon = getCachedLearnset(pokemonName) | |||
local moveData = loadMoveData() | |||
if not pokemon then return '' end | |||
local moves = pokemon.move_tutors or {} | |||
if #moves == 0 then | |||
return "" | |||
end | |||
local colors = typeColor(frame, primaryType) | |||
local html = mw.html.create('div') | |||
:css('width', '77%') | |||
:css('margin', 'auto') | |||
local table = html:tag('table') | |||
:addClass('sortable') | |||
:css('width', '100%') | |||
:css('text-align', 'center') | |||
:css('border-radius', '25px 25px 0 0') | |||
:css('border', '3px solid #' .. colors.dark) | |||
:css('border-bottom', 'none') | |||
:css('background-color', '#' .. colors.base) | |||
:css('padding', '7px') | |||
local header = table:tag('tr') | |||
:css('background-color', '#' .. colors.light) | |||
:css('color', '#' .. colors.dark) | |||
header:tag('th'):wikitext('Move'):css('width', '20%'):css('border-top-left-radius', '25px') | |||
header:tag('th'):wikitext('Type'):css('width', '17%') | |||
header:tag('th'):wikitext('Category'):css('width', '16%') | |||
header:tag('th'):wikitext('Base Power'):css('width', '17%') | |||
header:tag('th'):wikitext('Accuracy'):css('width', '15%') | |||
header:tag('th'):wikitext('PP'):css('width', '15%'):css('border-top-right-radius', '25px') | |||
for _, moveName in ipairs(moves) do | |||
local moveInfo = moveData[moveName] or {} | |||
local row = table:tag('tr'):css('background-color', '#FFFFFF') | |||
local moveLink = '[[' .. moveName .. ']]' | |||
if isSTAB(moveInfo.type, primaryType, secondaryType) and moveInfo.category ~= "Status" then | |||
moveLink = "'''" .. moveLink .. "'''" | |||
end | |||
if moveInfo.broken == "Y" then | |||
moveLink = "''" .. moveLink .. "''" | |||
end | |||
row:tag('td'):wikitext(moveLink) | |||
local moveTypeColors = typeColor(frame, moveInfo.type) | |||
row:tag('td') | |||
:css('background-color', '#' .. moveTypeColors.base) | |||
:css('border', '2px solid #' .. moveTypeColors.dark) | |||
:wikitext('[[' .. (moveInfo.type or 'Normal') .. '_(type)|<span style="color:#FFFFFF;">' .. (moveInfo.type or 'Normal') .. '</span>]]') | |||
local moveCatColors = categoryColor(frame, moveInfo.category) | |||
row:tag('td') | |||
:css('background-color', '#' .. moveCatColors.base) | |||
:css('border', '2px solid #' .. moveCatColors.dark) | |||
:wikitext('[[' .. (moveInfo.category or 'Status') .. ' moves|<span style="color:#FFFFFF;">' .. (moveInfo.category or 'Status') .. '</span>]]') | |||
row:tag('td'):wikitext(moveInfo.power or '—') | |||
row:tag('td'):wikitext(moveInfo.accuracy or '—') | |||
row:tag('td'):wikitext(moveInfo.pp or '—') | |||
end | |||
html:tag('div') | |||
:css('text-align', 'left') | |||
:css('background-color', '#' .. colors.light) | |||
:css('padding', '10px') | |||
:css('border', '3px solid #' .. colors.dark) | |||
:css('border-top', 'none') | |||
:css('border-radius', '0 0 25px 25px') | |||
:wikitext([=[ | |||
* '''Emboldened''' moves denote that this Pokémon avails from a STAB bonus for it. | |||
* ''Italicized'' moves are [[Broken_Moves_%26_Abilities|broken]]; see their individual pages for more information. | |||
* Refer to the [[Move Tutors]] article for tutorship information for all moves hereon. | |||
]=]) | |||
return tostring(html) .. '<br clear="all">' | |||
end | end | ||
local | function p.renderEggMoves(frame) | ||
local args = getArgs(frame) | |||
local pokemonName = args[1] or args.pokemon | |||
local primaryType = args.type1 or "" | |||
local secondaryType = args.type2 or "" | |||
end | local pokemon = getCachedLearnset(pokemonName) | ||
local moveData = loadMoveData() | |||
if not pokemon then return '' end | |||
local | local moves = pokemon.egg_moves or {} | ||
if #moves == 0 then | |||
return "" | |||
end | |||
local colors = typeColor(frame, primaryType) | |||
local | local html = mw.html.create('div') | ||
:css('width', '77%') | |||
:css('margin', 'auto') | |||
local table = html:tag('table') | |||
:addClass('sortable') | |||
:css('width', '100%') | |||
:css('text-align', 'center') | |||
:css('border-radius', '25px 25px 0 0') | |||
:css('border', '3px solid #' .. colors.dark) | |||
:css('border-bottom', 'none') | |||
:css('background-color', '#' .. colors.base) | |||
:css('padding', '7px') | |||
local | local header = table:tag('tr') | ||
:css('background-color', '#' .. colors.light) | |||
:css('color', '#' .. colors.dark) | |||
header:tag('th'):wikitext('Move'):css('width', '20%'):css('border-top-left-radius', '25px') | |||
header:tag('th'):wikitext('Type'):css('width', '17%') | |||
header:tag('th'):wikitext('Category'):css('width', '16%') | |||
header:tag('th'):wikitext('Base Power'):css('width', '17%') | |||
header:tag('th'):wikitext('Accuracy'):css('width', '15%') | |||
header:tag('th'):wikitext('PP'):css('width', '15%'):css('border-top-right-radius', '25px') | |||
for _, moveName in ipairs(moves) do | |||
local moveInfo = moveData[moveName] or {} | |||
local row = table:tag('tr'):css('background-color', '#FFFFFF') | |||
local | local moveLink = '[[' .. moveName .. ']]' | ||
if isSTAB(moveInfo.type, primaryType, secondaryType) and moveInfo.category ~= "Status" then | |||
moveLink = "'''" .. moveLink .. "'''" | |||
end | |||
if moveInfo.broken == "Y" then | |||
moveLink = "''" .. moveLink .. "''" | |||
end | |||
row:tag('td'):wikitext(moveLink) | |||
local moveTypeColors = typeColor(frame, moveInfo.type) | |||
row:tag('td') | |||
:css('background-color', '#' .. moveTypeColors.base) | |||
:css('border', '2px solid #' .. moveTypeColors.dark) | |||
:wikitext('[[' .. (moveInfo.type or 'Normal') .. '_(type)|<span style="color:#FFFFFF;">' .. (moveInfo.type or 'Normal') .. '</span>]]') | |||
local moveCatColors = categoryColor(frame, moveInfo.category) | |||
row:tag('td') | |||
:css('background-color', '#' .. moveCatColors.base) | |||
:css('border', '2px solid #' .. moveCatColors.dark) | |||
:wikitext('[[' .. (moveInfo.category or 'Status') .. ' moves|<span style="color:#FFFFFF;">' .. (moveInfo.category or 'Status') .. '</span>]]') | |||
row:tag('td'):wikitext(moveInfo.power or '—') | |||
row:tag('td'):wikitext(moveInfo.accuracy or '—') | |||
row:tag('td'):wikitext(moveInfo.pp or '—') | |||
end | |||
html:tag('div') | |||
:css('text-align', 'left') | |||
:css('background-color', '#' .. colors.light) | |||
:css('padding', '10px') | |||
:css('border', '3px solid #' .. colors.dark) | |||
:css('border-top', 'none') | |||
:css('border-radius', '0 0 25px 25px') | |||
:wikitext([=[ | |||
* '''Emboldened''' moves denote that this Pokémon avails from a STAB bonus for it. | |||
* ''Italicized'' moves are [[Broken_Moves_%26_Abilities|broken]]; see their individual pages for more information. | |||
* For more information on egg-move-tutors, refer to: [[Egg Moves]]. | |||
]=]) | |||
return tostring(html) .. '<br clear="all">' | |||
end | |||
-- Resolves type1/type2 for a given species name, preferring an explicit | |||
-- override, falling back to the PokemonRawList lookup. | |||
local function resolveType(pokemonName, type1Override, type2Override) | |||
if type1Override ~= '' then | |||
return type1Override, type2Override | |||
end | |||
local typeData = loadPokemonTypes() | |||
local pt = typeData[pokemonName] | |||
if pt then | |||
return pt.type1 or '', pt.type2 or '' | |||
end | |||
return '', '' | |||
end | |||
local FORM_SUFFIXES = { 'Alolan', 'Galarian', 'Hisuian', 'Paldean' } | |||
function p.main(frame) | |||
local args = getArgs(frame) | |||
local pokemonName = args[1] or args.pokemon or '' | |||
if pokemonName == '' then | |||
return "<span style='color:red'>Usage: {{Learnsets|PokemonName}}</span>" | |||
end | |||
local primaryType, secondaryType = resolveType(pokemonName, args.type1 or '', args.type2 or '') | |||
local function callRender(fn, name, type1, type2) | |||
return fn(frame:newChild({ | |||
title = frame:getTitle(), | |||
args = { name, type1 = type1, type2 = type2 }, | |||
})) | |||
end | |||
local poke, lsErr = getCachedLearnset(pokemonName) | |||
if not poke then | |||
return "<span style='color:red'>Data load error: " .. (lsErr or 'unknown') .. "</span>" | |||
end | |||
local formName, formPoke, formType1, formType2 | |||
for _, suffix in ipairs(FORM_SUFFIXES) do | |||
local candidateName = pokemonName .. '-' .. suffix | |||
local candidatePoke = getCachedLearnset(candidateName) | |||
if candidatePoke then | |||
formName = suffix | |||
formPoke = candidatePoke | |||
formType1, formType2 = resolveType(candidateName, '', '') | |||
break | |||
end | |||
end | |||
local formFullName = formPoke and (pokemonName .. '-' .. formName) or nil | |||
local sectionDefs = { | |||
{ heading = 'Level-up', render = p.renderLevelUp, | |||
hasData = function(pk) return #(pk.level_up or {}) > 0 end }, | |||
{ heading = '[[TMs and HMs]]', render = p.renderTMsHMs, | |||
hasData = function(pk) return #(pk.tms or {}) > 0 or #(pk.hms or {}) > 0 end }, | |||
{ heading = '[[Move Tutors]]', render = p.renderMoveTutors, | |||
hasData = function(pk) return #(pk.move_tutors or {}) > 0 end }, | |||
{ heading = '[[Egg Moves]]', render = p.renderEggMoves, | |||
hasData = function(pk) return #(pk.egg_moves or {}) > 0 end }, | |||
} | |||
local sections = {} | |||
for _, def in ipairs(sectionDefs) do | |||
local normalHas = def.hasData(poke) | |||
local formHas = formPoke and def.hasData(formPoke) | |||
if normalHas or formHas then | |||
table.insert(sections, "===" .. def.heading .. "===") | |||
if formPoke then | |||
local normalBox = callRender(def.render, pokemonName, primaryType, secondaryType) | |||
local formBox = callRender(def.render, formFullName, formType1, formType2) | |||
table.insert(sections, | |||
"{{TabbedInfoBoxes|Width=85|Class=|TabHeaders={{Tabs:MultiformTab|Form=Normal}}{{Tabs:MultiformTab|Form=" .. formName .. "}}|InfoBoxes=\n" | |||
.. "{{Tabs:MultiformTableAdditional|Table=" .. normalBox .. "}}" | |||
.. "{{Tabs:MultiformTableAdditional|Form=" .. formName .. "|Table=" .. formBox .. "}}\n}}") | |||
else | |||
table.insert(sections, callRender(def.render, pokemonName, primaryType, secondaryType)) | |||
end | |||
end | |||
end | |||
if #sections == 0 then | |||
return '' | |||
end | |||
return "==Moves==\n" .. table.concat(sections, '\n') | |||
end | end | ||
return p | return p | ||
Revision as of 19:25, 9 July 2026
Documentation for this module may be created at Module:Learnsets/doc
local p = {}
local function getArgs(frame)
local args = {}
local parent = frame:getParent()
if parent then
for k, v in pairs(parent.args) do
if v ~= '' then
args[k] = v
end
end
end
for k, v in pairs(frame.args) do
if v ~= '' then
args[k] = v
end
end
return args
end
local moveDataCache = nil
local function blankToNil(v)
if v == nil or v == '' then
return nil
end
return v
end
local function loadMoveData()
if moveDataCache then
return moveDataCache
end
local csvData = mw.ext.externalData.getExternalData({
url = 'https://wiki.pokemonrevolution.net/index.php?title=Special:GetData/RawMovesList',
format = 'CSV with header',
data = {
Name = 'Name',
Type = 'Type',
Category = 'Category',
Power = 'Power',
Accuracy = 'Accuracy',
PP = 'PP',
Broken = 'Broken',
Typeless = 'Typeless',
Priority = 'Priority',
TM = 'TM',
['Move Tutor'] = 'Move Tutor',
Egg = 'Egg',
},
})
moveDataCache = {}
if csvData then
for _, row in ipairs(csvData) do
moveDataCache[row.Name] = {
type = blankToNil(row.Type) or 'Normal',
category = blankToNil(row.Category) or 'Status',
power = blankToNil(row.Power),
accuracy = blankToNil(row.Accuracy),
pp = blankToNil(row.PP),
broken = row.Broken or 'N',
typeless = row.Typeless or 'N',
priority = blankToNil(row.Priority) or '0',
tm = blankToNil(row.TM),
move_tutor = blankToNil(row['Move Tutor']),
egg = blankToNil(row.Egg)
}
end
end
return moveDataCache
end
local function loadLearnset(pokemonName)
local jsonPath = '$[' .. mw.text.jsonEncode(pokemonName) .. ']'
local ok, result, errors = pcall(function()
return mw.ext.externalData.getFileData({
source = 'data',
['file name'] = 'Learnsets.json',
format = 'JSON',
['use jsonpath'] = true,
data = { poke = jsonPath },
})
end)
if not ok then
return nil, 'Lua error: ' .. tostring(result)
end
if errors and #errors > 0 then
return nil, 'ExternalData error: ' .. table.concat(errors, ' | ')
end
local raw = result and result.poke
if type(raw) ~= 'table' then
return nil, 'No learnset data found for ' .. pokemonName
end
return raw, nil
end
local learnsetCache = {}
local learnsetErrorCache = {}
local function getCachedLearnset(pokemonName)
if learnsetCache[pokemonName] == nil and learnsetErrorCache[pokemonName] == nil then
learnsetCache[pokemonName], learnsetErrorCache[pokemonName] = loadLearnset(pokemonName)
end
return learnsetCache[pokemonName], learnsetErrorCache[pokemonName]
end
local pokemonTypeCache = nil
local function loadPokemonTypes()
if pokemonTypeCache then return pokemonTypeCache end
local csvData = mw.ext.externalData.getExternalData({
url = 'https://wiki.pokemonrevolution.net/index.php?title=Special:GetData/PokemonRawList',
format = 'CSV with header',
data = {
Name = 'Name',
Type1 = 'Type1',
Type2 = 'Type2',
},
})
pokemonTypeCache = {}
if csvData then
for _, row in ipairs(csvData) do
pokemonTypeCache[row.Name] = {
type1 = row.Type1 or '',
type2 = row.Type2 or '',
}
end
end
return pokemonTypeCache
end
local typeColorCache = {}
local function typeColor(frame, pokeType)
pokeType = (pokeType and pokeType ~= '') and pokeType or 'Normal'
if not typeColorCache[pokeType] then
typeColorCache[pokeType] = {
light = frame:expandTemplate{ title = pokeType .. ' color light' },
base = frame:expandTemplate{ title = pokeType .. ' color' },
dark = frame:expandTemplate{ title = pokeType .. ' color dark' },
}
end
return typeColorCache[pokeType]
end
local categoryColorCache = {}
local function categoryColor(frame, category)
category = (category and category ~= '') and category or 'Status'
if not categoryColorCache[category] then
categoryColorCache[category] = {
base = frame:expandTemplate{ title = category .. ' color' },
dark = frame:expandTemplate{ title = category .. ' color dark' },
}
end
return categoryColorCache[category]
end
local function isSTAB(moveType, primaryType, secondaryType)
return moveType == primaryType or moveType == secondaryType
end
local function formatItemNumber(label, num)
num = tonumber(num)
if not num then
return '—'
end
return label .. string.format('%02d', num)
end
function p.renderLevelUp(frame)
local args = getArgs(frame)
local pokemonName = args[1] or args.pokemon
local primaryType = args.type1 or ""
local secondaryType = args.type2 or ""
local pokemon, lsErr = getCachedLearnset(pokemonName)
local moveData = loadMoveData()
if not pokemon then
return "<span style='color:red'>Data load error: " .. (lsErr or 'unknown') .. "</span>"
end
local moves = pokemon.level_up or {}
local colors = typeColor(frame, primaryType)
local html = mw.html.create('div')
:css('width', '77%')
:css('margin', 'auto')
local table = html:tag('table')
:addClass('sortable')
:css('width', '100%')
:css('text-align', 'center')
:css('border-radius', '15px 15px 0 0')
:css('border', '3px solid #' .. colors.dark)
:css('border-bottom', 'none')
:css('background-color', '#' .. colors.base)
:css('padding', '7px')
local header = table:tag('tr')
:css('background-color', '#' .. colors.light)
:css('color', '#' .. colors.dark)
header:tag('th'):wikitext('Level'):css('width', '10%'):css('border-radius', '15px 1px 1px 1px')
header:tag('th'):wikitext('Move'):css('width', '15%')
header:tag('th'):wikitext('Type'):css('width', '17%')
header:tag('th'):wikitext('Category'):css('width', '16%')
header:tag('th'):wikitext('Base Power'):css('width', '17%')
header:tag('th'):wikitext('Accuracy'):css('width', '15%')
header:tag('th'):wikitext('PP'):css('width', '10%'):css('border-radius', '1px 15px 1px 1px')
for _, move in ipairs(moves) do
local moveName = move.move
local level = move.level
local moveInfo = moveData[moveName] or {}
local row = table:tag('tr'):css('background-color', '#FFFFFF')
row:tag('td'):wikitext(tostring(level))
local moveLink = '[[' .. moveName .. ']]'
if isSTAB(moveInfo.type, primaryType, secondaryType) and moveInfo.category ~= "Status" then
moveLink = "'''" .. moveLink .. "'''"
end
if moveInfo.broken == "Y" then
moveLink = "''" .. moveLink .. "''"
end
row:tag('td'):wikitext(moveLink)
local moveTypeColors = typeColor(frame, moveInfo.type)
row:tag('td')
:css('background-color', '#' .. moveTypeColors.base)
:css('border', '2px solid #' .. moveTypeColors.dark)
:wikitext('[[' .. (moveInfo.type or 'Normal') .. '_(type)|<span style="color:#FFFFFF;">' .. (moveInfo.type or 'Normal') .. '</span>]]')
local moveCatColors = categoryColor(frame, moveInfo.category)
row:tag('td')
:css('background-color', '#' .. moveCatColors.base)
:css('border', '2px solid #' .. moveCatColors.dark)
:wikitext('[[' .. (moveInfo.category or 'Status') .. ' moves|<span style="color:#FFFFFF;">' .. (moveInfo.category or 'Status') .. '</span>]]')
row:tag('td'):wikitext(moveInfo.power or '—')
row:tag('td'):wikitext(moveInfo.accuracy or '—')
row:tag('td'):wikitext(moveInfo.pp or '—')
end
html:tag('div')
:css('text-align', 'left')
:css('background-color', '#' .. colors.light)
:css('padding', '10px')
:css('border', '3px solid #' .. colors.dark)
:css('border-top', 'none')
:css('border-radius', '0 0 15px 15px')
:wikitext([=[
* '''Emboldened''' moves denote this Pokémon avails from a STAB bonus for it.
* ''Italicized'' moves are [[Broken_Moves_%26_Abilities|broken]]; see their individual pages for more information.
* All moves subsumed on this list are relearnable via the [[Move Relearner]]s if lost.
]=])
return tostring(html) .. '<br clear="all">'
end
function p.renderTMsHMs(frame)
local args = getArgs(frame)
local pokemonName = args[1] or args.pokemon
local primaryType = args.type1 or ""
local secondaryType = args.type2 or ""
local pokemon = getCachedLearnset(pokemonName)
local moveData = loadMoveData()
if not pokemon then return '' end
local tms = pokemon.tms or {}
local hms = pokemon.hms or {}
local colors = typeColor(frame, primaryType)
local html = mw.html.create('div')
:css('width', '77%')
:css('margin', 'auto')
local table = html:tag('table')
:addClass('sortable')
:css('width', '100%')
:css('text-align', 'center')
:css('border-radius', '25px 25px 0 0')
:css('border', '3px solid #' .. colors.dark)
:css('border-bottom', 'none')
:css('background-color', '#' .. colors.base)
:css('padding', '7px')
local header = table:tag('tr')
:css('background-color', '#' .. colors.light)
:css('color', '#' .. colors.dark)
header:tag('th'):attr('colspan', '2'):wikitext('TM'):css('width', '10%'):css('border-top-left-radius', '25px')
header:tag('th'):wikitext('Move'):css('width', '15%')
header:tag('th'):wikitext('Type'):css('width', '17%')
header:tag('th'):wikitext('Category'):css('width', '16%')
header:tag('th'):wikitext('Base Power'):css('width', '17%')
header:tag('th'):wikitext('Accuracy'):css('width', '15%')
header:tag('th'):wikitext('PP'):css('width', '10%'):css('border-top-right-radius', '25px')
for _, moveName in ipairs(tms) do
local moveInfo = moveData[moveName] or {}
local row = table:tag('tr'):css('background-color', '#FFFFFF')
row:tag('td'):attr('colspan', '2'):wikitext(formatItemNumber('TM', moveInfo.tm))
local moveLink = '[[' .. moveName .. ']]'
if isSTAB(moveInfo.type, primaryType, secondaryType) and moveInfo.category ~= "Status" then
moveLink = "'''" .. moveLink .. "'''"
end
if moveInfo.broken == "Y" then
moveLink = "''" .. moveLink .. "''"
end
row:tag('td'):wikitext(moveLink)
local moveTypeColors = typeColor(frame, moveInfo.type)
row:tag('td')
:css('background-color', '#' .. moveTypeColors.base)
:css('border', '2px solid #' .. moveTypeColors.dark)
:wikitext('[[' .. (moveInfo.type or 'Normal') .. '_(type)|<span style="color:#FFFFFF;">' .. (moveInfo.type or 'Normal') .. '</span>]]')
local moveCatColors = categoryColor(frame, moveInfo.category)
row:tag('td')
:css('background-color', '#' .. moveCatColors.base)
:css('border', '2px solid #' .. moveCatColors.dark)
:wikitext('[[' .. (moveInfo.category or 'Status') .. ' moves|<span style="color:#FFFFFF;">' .. (moveInfo.category or 'Status') .. '</span>]]')
row:tag('td'):wikitext(moveInfo.power or '—')
row:tag('td'):wikitext(moveInfo.accuracy or '—')
row:tag('td'):wikitext(moveInfo.pp or '—')
end
for _, moveName in ipairs(hms) do
local moveInfo = moveData[moveName] or {}
local row = table:tag('tr'):css('background-color', '#FFFFCC')
row:tag('td'):attr('colspan', '2'):wikitext(formatItemNumber('HM', moveInfo.tm)):css('font-weight', 'bold')
local moveLink = '[[' .. moveName .. ']]'
if isSTAB(moveInfo.type, primaryType, secondaryType) and moveInfo.category ~= "Status" then
moveLink = "'''" .. moveLink .. "'''"
end
if moveInfo.broken == "Y" then
moveLink = "''" .. moveLink .. "''"
end
row:tag('td'):wikitext(moveLink)
local moveTypeColors = typeColor(frame, moveInfo.type)
row:tag('td')
:css('background-color', '#' .. moveTypeColors.base)
:css('border', '2px solid #' .. moveTypeColors.dark)
:wikitext('[[' .. (moveInfo.type or 'Normal') .. '_(type)|<span style="color:#FFFFFF;">' .. (moveInfo.type or 'Normal') .. '</span>]]')
local moveCatColors = categoryColor(frame, moveInfo.category)
row:tag('td')
:css('background-color', '#' .. moveCatColors.base)
:css('border', '2px solid #' .. moveCatColors.dark)
:wikitext('[[' .. (moveInfo.category or 'Status') .. ' moves|<span style="color:#FFFFFF;">' .. (moveInfo.category or 'Status') .. '</span>]]')
row:tag('td'):wikitext(moveInfo.power or '—')
row:tag('td'):wikitext(moveInfo.accuracy or '—')
row:tag('td'):wikitext(moveInfo.pp or '—')
end
html:tag('div')
:css('text-align', 'left')
:css('background-color', '#' .. colors.light)
:css('padding', '10px')
:css('border', '3px solid #' .. colors.dark)
:css('border-top', 'none')
:css('border-radius', '0 0 25px 25px')
:wikitext([=[
* '''Emboldened''' moves denote that this Pokémon avails from a STAB bonus for it.
* ''Italicized'' moves are [[Broken_Moves_%26_Abilities|broken]]; see their individual pages for more information.
* Refer to [[TMs and HMs]] for information on each move's obtainability.
]=])
return tostring(html) .. '<br clear="all">'
end
function p.renderMoveTutors(frame)
local args = getArgs(frame)
local pokemonName = args[1] or args.pokemon
local primaryType = args.type1 or ""
local secondaryType = args.type2 or ""
local pokemon = getCachedLearnset(pokemonName)
local moveData = loadMoveData()
if not pokemon then return '' end
local moves = pokemon.move_tutors or {}
if #moves == 0 then
return ""
end
local colors = typeColor(frame, primaryType)
local html = mw.html.create('div')
:css('width', '77%')
:css('margin', 'auto')
local table = html:tag('table')
:addClass('sortable')
:css('width', '100%')
:css('text-align', 'center')
:css('border-radius', '25px 25px 0 0')
:css('border', '3px solid #' .. colors.dark)
:css('border-bottom', 'none')
:css('background-color', '#' .. colors.base)
:css('padding', '7px')
local header = table:tag('tr')
:css('background-color', '#' .. colors.light)
:css('color', '#' .. colors.dark)
header:tag('th'):wikitext('Move'):css('width', '20%'):css('border-top-left-radius', '25px')
header:tag('th'):wikitext('Type'):css('width', '17%')
header:tag('th'):wikitext('Category'):css('width', '16%')
header:tag('th'):wikitext('Base Power'):css('width', '17%')
header:tag('th'):wikitext('Accuracy'):css('width', '15%')
header:tag('th'):wikitext('PP'):css('width', '15%'):css('border-top-right-radius', '25px')
for _, moveName in ipairs(moves) do
local moveInfo = moveData[moveName] or {}
local row = table:tag('tr'):css('background-color', '#FFFFFF')
local moveLink = '[[' .. moveName .. ']]'
if isSTAB(moveInfo.type, primaryType, secondaryType) and moveInfo.category ~= "Status" then
moveLink = "'''" .. moveLink .. "'''"
end
if moveInfo.broken == "Y" then
moveLink = "''" .. moveLink .. "''"
end
row:tag('td'):wikitext(moveLink)
local moveTypeColors = typeColor(frame, moveInfo.type)
row:tag('td')
:css('background-color', '#' .. moveTypeColors.base)
:css('border', '2px solid #' .. moveTypeColors.dark)
:wikitext('[[' .. (moveInfo.type or 'Normal') .. '_(type)|<span style="color:#FFFFFF;">' .. (moveInfo.type or 'Normal') .. '</span>]]')
local moveCatColors = categoryColor(frame, moveInfo.category)
row:tag('td')
:css('background-color', '#' .. moveCatColors.base)
:css('border', '2px solid #' .. moveCatColors.dark)
:wikitext('[[' .. (moveInfo.category or 'Status') .. ' moves|<span style="color:#FFFFFF;">' .. (moveInfo.category or 'Status') .. '</span>]]')
row:tag('td'):wikitext(moveInfo.power or '—')
row:tag('td'):wikitext(moveInfo.accuracy or '—')
row:tag('td'):wikitext(moveInfo.pp or '—')
end
html:tag('div')
:css('text-align', 'left')
:css('background-color', '#' .. colors.light)
:css('padding', '10px')
:css('border', '3px solid #' .. colors.dark)
:css('border-top', 'none')
:css('border-radius', '0 0 25px 25px')
:wikitext([=[
* '''Emboldened''' moves denote that this Pokémon avails from a STAB bonus for it.
* ''Italicized'' moves are [[Broken_Moves_%26_Abilities|broken]]; see their individual pages for more information.
* Refer to the [[Move Tutors]] article for tutorship information for all moves hereon.
]=])
return tostring(html) .. '<br clear="all">'
end
function p.renderEggMoves(frame)
local args = getArgs(frame)
local pokemonName = args[1] or args.pokemon
local primaryType = args.type1 or ""
local secondaryType = args.type2 or ""
local pokemon = getCachedLearnset(pokemonName)
local moveData = loadMoveData()
if not pokemon then return '' end
local moves = pokemon.egg_moves or {}
if #moves == 0 then
return ""
end
local colors = typeColor(frame, primaryType)
local html = mw.html.create('div')
:css('width', '77%')
:css('margin', 'auto')
local table = html:tag('table')
:addClass('sortable')
:css('width', '100%')
:css('text-align', 'center')
:css('border-radius', '25px 25px 0 0')
:css('border', '3px solid #' .. colors.dark)
:css('border-bottom', 'none')
:css('background-color', '#' .. colors.base)
:css('padding', '7px')
local header = table:tag('tr')
:css('background-color', '#' .. colors.light)
:css('color', '#' .. colors.dark)
header:tag('th'):wikitext('Move'):css('width', '20%'):css('border-top-left-radius', '25px')
header:tag('th'):wikitext('Type'):css('width', '17%')
header:tag('th'):wikitext('Category'):css('width', '16%')
header:tag('th'):wikitext('Base Power'):css('width', '17%')
header:tag('th'):wikitext('Accuracy'):css('width', '15%')
header:tag('th'):wikitext('PP'):css('width', '15%'):css('border-top-right-radius', '25px')
for _, moveName in ipairs(moves) do
local moveInfo = moveData[moveName] or {}
local row = table:tag('tr'):css('background-color', '#FFFFFF')
local moveLink = '[[' .. moveName .. ']]'
if isSTAB(moveInfo.type, primaryType, secondaryType) and moveInfo.category ~= "Status" then
moveLink = "'''" .. moveLink .. "'''"
end
if moveInfo.broken == "Y" then
moveLink = "''" .. moveLink .. "''"
end
row:tag('td'):wikitext(moveLink)
local moveTypeColors = typeColor(frame, moveInfo.type)
row:tag('td')
:css('background-color', '#' .. moveTypeColors.base)
:css('border', '2px solid #' .. moveTypeColors.dark)
:wikitext('[[' .. (moveInfo.type or 'Normal') .. '_(type)|<span style="color:#FFFFFF;">' .. (moveInfo.type or 'Normal') .. '</span>]]')
local moveCatColors = categoryColor(frame, moveInfo.category)
row:tag('td')
:css('background-color', '#' .. moveCatColors.base)
:css('border', '2px solid #' .. moveCatColors.dark)
:wikitext('[[' .. (moveInfo.category or 'Status') .. ' moves|<span style="color:#FFFFFF;">' .. (moveInfo.category or 'Status') .. '</span>]]')
row:tag('td'):wikitext(moveInfo.power or '—')
row:tag('td'):wikitext(moveInfo.accuracy or '—')
row:tag('td'):wikitext(moveInfo.pp or '—')
end
html:tag('div')
:css('text-align', 'left')
:css('background-color', '#' .. colors.light)
:css('padding', '10px')
:css('border', '3px solid #' .. colors.dark)
:css('border-top', 'none')
:css('border-radius', '0 0 25px 25px')
:wikitext([=[
* '''Emboldened''' moves denote that this Pokémon avails from a STAB bonus for it.
* ''Italicized'' moves are [[Broken_Moves_%26_Abilities|broken]]; see their individual pages for more information.
* For more information on egg-move-tutors, refer to: [[Egg Moves]].
]=])
return tostring(html) .. '<br clear="all">'
end
-- Resolves type1/type2 for a given species name, preferring an explicit
-- override, falling back to the PokemonRawList lookup.
local function resolveType(pokemonName, type1Override, type2Override)
if type1Override ~= '' then
return type1Override, type2Override
end
local typeData = loadPokemonTypes()
local pt = typeData[pokemonName]
if pt then
return pt.type1 or '', pt.type2 or ''
end
return '', ''
end
local FORM_SUFFIXES = { 'Alolan', 'Galarian', 'Hisuian', 'Paldean' }
function p.main(frame)
local args = getArgs(frame)
local pokemonName = args[1] or args.pokemon or ''
if pokemonName == '' then
return "<span style='color:red'>Usage: {{Learnsets|PokemonName}}</span>"
end
local primaryType, secondaryType = resolveType(pokemonName, args.type1 or '', args.type2 or '')
local function callRender(fn, name, type1, type2)
return fn(frame:newChild({
title = frame:getTitle(),
args = { name, type1 = type1, type2 = type2 },
}))
end
local poke, lsErr = getCachedLearnset(pokemonName)
if not poke then
return "<span style='color:red'>Data load error: " .. (lsErr or 'unknown') .. "</span>"
end
local formName, formPoke, formType1, formType2
for _, suffix in ipairs(FORM_SUFFIXES) do
local candidateName = pokemonName .. '-' .. suffix
local candidatePoke = getCachedLearnset(candidateName)
if candidatePoke then
formName = suffix
formPoke = candidatePoke
formType1, formType2 = resolveType(candidateName, '', '')
break
end
end
local formFullName = formPoke and (pokemonName .. '-' .. formName) or nil
local sectionDefs = {
{ heading = 'Level-up', render = p.renderLevelUp,
hasData = function(pk) return #(pk.level_up or {}) > 0 end },
{ heading = '[[TMs and HMs]]', render = p.renderTMsHMs,
hasData = function(pk) return #(pk.tms or {}) > 0 or #(pk.hms or {}) > 0 end },
{ heading = '[[Move Tutors]]', render = p.renderMoveTutors,
hasData = function(pk) return #(pk.move_tutors or {}) > 0 end },
{ heading = '[[Egg Moves]]', render = p.renderEggMoves,
hasData = function(pk) return #(pk.egg_moves or {}) > 0 end },
}
local sections = {}
for _, def in ipairs(sectionDefs) do
local normalHas = def.hasData(poke)
local formHas = formPoke and def.hasData(formPoke)
if normalHas or formHas then
table.insert(sections, "===" .. def.heading .. "===")
if formPoke then
local normalBox = callRender(def.render, pokemonName, primaryType, secondaryType)
local formBox = callRender(def.render, formFullName, formType1, formType2)
table.insert(sections,
"{{TabbedInfoBoxes|Width=85|Class=|TabHeaders={{Tabs:MultiformTab|Form=Normal}}{{Tabs:MultiformTab|Form=" .. formName .. "}}|InfoBoxes=\n"
.. "{{Tabs:MultiformTableAdditional|Table=" .. normalBox .. "}}"
.. "{{Tabs:MultiformTableAdditional|Form=" .. formName .. "|Table=" .. formBox .. "}}\n}}")
else
table.insert(sections, callRender(def.render, pokemonName, primaryType, secondaryType))
end
end
end
if #sections == 0 then
return ''
end
return "==Moves==\n" .. table.concat(sections, '\n')
end
return p