Module:Pokemon
Jump to navigation
Jump to search
Documentation for this module may be created at Module:Pokemon/doc
local p = {}
local DATA_SOURCE = 'data'
local DATA_FILE = 'PokemonRawList.csv'
local DATA_FORMAT = 'CSV with header'
local COLUMNS = {
number = 'Pokedex Number',
name = 'Name',
type1 = 'Type1',
type2 = 'Type2',
ability1 = 'Ability 1',
ability2 = 'Ability 2',
ha = 'Hidden Ability',
generation = 'Generation',
hp = 'HP',
atk = 'Attack',
def = 'Defense',
spatk = 'Special Attack',
spdef = 'Special Defense',
spd = 'Speed',
catch = 'Catch Rate',
evhp = 'EVHP',
evatk = 'EVATK',
evdef = 'EVDEF',
evspa = 'EVSPA',
evspd = 'EVSPD',
evsp = 'EVSP',
male = 'Male Ratio',
height = 'Height',
weight = 'Weight',
npc = 'NPC',
exp = 'Base Experience',
rarity = 'Tier',
}
local GEN_NUMERALS = { 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX' }
local STATS = {
{ key = 'hp', label = 'HP', template = 'HP' },
{ key = 'atk', label = 'Atk', template = 'Attack' },
{ key = 'def', label = 'Def', template = 'Defense' },
{ key = 'spatk', label = 'Sp.Atk', template = 'Special Attack' },
{ key = 'spdef', label = 'Sp.Def', template = 'Special Defense' },
{ key = 'spd', label = 'Spd', template = 'Speed' },
}
local EV_KEYS = { 'evhp', 'evatk', 'evdef', 'evspa', 'evspd', 'evsp' }
local DATA_SPEC
do
local parts = {}
for key, header in pairs(COLUMNS) do
parts[#parts + 1] = key .. '=' .. header
end
DATA_SPEC = table.concat(parts, ',')
end
local function nonEmpty(s)
if s == nil then return nil end
s = mw.text.trim(tostring(s))
if s == '' then return nil end
return s
end
local function templateArgs(frame)
local parent = frame:getParent()
return (parent and parent.args) or frame.args
end
local function padNumber(n)
n = tonumber(n) or 0
return string.format('%03d', n)
end
local function fetchRow(filterColumn, filterValue)
local ok, values, errors = pcall(function()
return mw.ext.externalData.getFileData{
source = DATA_SOURCE,
['file name'] = DATA_FILE,
format = DATA_FORMAT,
filters = filterColumn .. '=' .. filterValue,
data = DATA_SPEC,
}
end)
if not ok then
error('Module:Pokemon: External Data call failed (' .. tostring(values) ..
'). Is Legacy mode enabled (mw.ext.externalData.getFileData)?')
end
if not values or values.name == nil then
return nil
end
return values
end
local function byName(name)
return fetchRow('Name', name)
end
local function byNumber(number)
return fetchRow('Pokedex Number', number)
end
local function typeColors(frame, pokeType)
return {
light = frame:expandTemplate{ title = pokeType .. ' color light' },
base = frame:expandTemplate{ title = pokeType .. ' color' },
dark = frame:expandTemplate{ title = pokeType .. ' color dark' },
}
end
local function statColors(frame, statTemplate)
return {
base = frame:expandTemplate{ title = statTemplate .. ' color' },
dark = frame:expandTemplate{ title = statTemplate .. ' color dark' },
}
end
local function navTableHtml(frame, data, colors, pageName)
local number = tonumber(data.number)
local prevCell = ''
if number and number > 1 then
local prev = byNumber(number - 1)
if prev then
prevCell = ' [[File:' .. (number - 1) .. 'Icon.png|' .. prev.name .. '|link=' .. prev.name ..
']] #' .. padNumber(prev.number)
end
end
local nextCell = ''
if number then
local nxt = byNumber(number + 1)
if nxt then
nextCell = '#' .. padNumber(nxt.number) .. ' [[File:' .. (number + 1) ..
'Icon.png|' .. nxt.name .. '|link=' .. nxt.name .. ']]'
end
end
return '<table style="margin-left: auto; margin-right: auto; margin-bottom: 15px; height: 60px; ' ..
'border: 4px #' .. colors.dark .. ' solid; font-weight: bold; width: 65%; color: #' .. colors.dark ..
'; border-radius: 20px; padding: 3px; background: #' .. colors.base .. ';">' ..
'<tr>' ..
'<td style="text-align: left; width: 36%; background: #' .. colors.light ..
'; border-radius: 15px 1px 1px 15px; border: 1px solid #' .. colors.dark .. ';">' .. prevCell .. '</td>' ..
'<td style="text-align: center; width: 28%; background: #' .. colors.light ..
'; border: 1px solid #' .. colors.dark .. ';">#' .. padNumber(number) .. ' - ' ..
pageName .. '</td>' ..
'<td style="text-align: right; width: 36%; padding-right: 10px; background: #' .. colors.light ..
'; border: 1px solid #' .. colors.dark .. '; border-radius: 1px 15px 15px 1px;">' .. nextCell .. '</td>' ..
'</tr></table>'
end
function p.navTable(frame)
local args = templateArgs(frame)
local pageName = mw.title.getCurrentTitle().text
local name = nonEmpty(args.Name) or pageName
local data = byName(name)
if not data then
return '<strong class="error">PokeNavTable: no data found for "' .. name .. '"</strong>'
end
return navTableHtml(frame, data, typeColors(frame, data.type1), pageName)
end
local function infoCell(colors, labelHtml, valueHtml)
return '<td style="border: 3px solid #' .. colors.dark .. '; background-color: #' .. colors.light ..
'; border-radius: 15px; padding: 5px;">' ..
'<p style="margin-bottom: 7px; font-weight: bold; color: #' .. colors.dark .. ';">' .. labelHtml .. '</p>' ..
'<span style="border-radius: 20px; padding: 6px; border: 4px solid #' .. colors.dark ..
'; background-color: #FFFFFF;"> ' .. valueHtml .. ' </span></td>'
end
local function abilityCell(colors, label, ability, widthPercent)
if not ability then return '' end
local style = 'border-radius: 20px; padding: 6px; border: 4px solid #' .. colors.dark ..
'; background-color: #FFFFFF;'
if widthPercent then
style = 'width: ' .. widthPercent .. '%; ' .. style
end
return '<td style="' .. style .. '"><b>' .. label .. '</b><br>[[' .. ability .. '_(ability)|' .. ability ..
']]</td>'
end
local function abilitiesRow(colors, data)
local entries = {
{ 'Ability 1', nonEmpty(data.ability1) },
{ 'Ability 2', nonEmpty(data.ability2) },
{ 'Hidden Ability', nonEmpty(data.ha) },
}
local present = {}
for _, entry in ipairs(entries) do
if entry[2] then present[#present + 1] = entry end
end
local widthPercent = (#present == 2) and 50 or nil
local cells = {}
for _, entry in ipairs(present) do
cells[#cells + 1] = abilityCell(colors, entry[1], entry[2], widthPercent)
end
return table.concat(cells)
end
local function statValueCell(frame, stat, value)
local colors = statColors(frame, stat.template)
return '<td style="border: 4px solid #' .. colors.dark .. '; background-color: #' .. colors.base ..
'; border-radius: 15px; padding: 5px; font-size: 11px;">\'\'\'' .. stat.label .. '\'\'\'<br>' ..
value .. '</td>'
end
local function genderRatioHtml(male)
if male == 'Genderless' then
return '<span style="color:#000000; background-color: #FFFFFF; border-radius: 15px; ' ..
'border: 4px solid #000000; padding: 5px;"> Genderless </span>'
end
local m = tonumber(male) or 100
if m >= 100 then
return '<span style="color:#0000FF; border-radius: 15px; background-color: #FFFFFF; padding: 5px; ' ..
'border: 4px solid #0000FF;"> 100% </span>'
elseif m <= 0 then
return '<span style="color:#FF6060; background-color: #FFFFFF; border-radius: 15px; ' ..
'border: 4px solid #FF6060; padding: 5px;"> 100% </span>'
else
return '<span style="color:#0000FF; border-radius: 15px; background-color: #FFFFFF; padding: 5px; ' ..
'border: 4px solid #0000FF;"> ' .. m .. '% </span>' ..
'<span style="color:#FF6060; background-color: #FFFFFF; border-radius: 15px; ' ..
'border: 4px solid #FF6060; padding: 5px;"> ' .. (100 - m) .. '% </span>'
end
end
local function categories(data, gen, bstCategory, args)
local cats = {
'[[Category:Pokémon species]]',
'[[Category:Generation ' .. gen .. ' Pokémon]]',
'[[Category:' .. data.type1 .. '-type Pokémon]]',
}
if nonEmpty(data.type2) then
cats[#cats + 1] = '[[Category:' .. data.type2 .. '-type Pokémon]]'
end
cats[#cats + 1] = '[[Category:' .. data.rarity .. ' Pokémon]]'
if nonEmpty(data.npc) then
cats[#cats + 1] = '[[Category:NPC-obtainable Pokémon]]'
end
if not nonEmpty(args.EvolvesFrom) then
cats[#cats + 1] = '[[Category:Base-stage Pokémon]]'
end
cats[#cats + 1] = '[[Category:' .. bstCategory .. '-plus-BST Pokémon]]'
return table.concat(cats, ' ')
end
function p.infobox(frame)
local args = templateArgs(frame)
local pageName = mw.title.getCurrentTitle().text
local name = nonEmpty(args.Name) or pageName
local displayName = nonEmpty(args.Name) or pageName
local width = nonEmpty(args.Width) or '45'
local data = byName(name)
if not data then
return '<strong class="error">PokemonInfo: no data found for "' .. name .. '"</strong>'
end
local colors = typeColors(frame, data.type1)
local gen = GEN_NUMERALS[tonumber(data.generation)]
-- Types pill(s).
local typesHtml
if nonEmpty(data.type2) then
local type2Colors = typeColors(frame, data.type2)
typesHtml =
'<td style="width: 50%; background-color: #' .. colors.dark .. '; border-radius: 25px 1px 1px 25px;">' ..
'[[' .. data.type1 .. '_(type)|<span style="color: #FFFFFF;">' .. data.type1 .. '</span>]]</td>' ..
'<td style="width: 50%; background-color: #' .. type2Colors.dark .. '; border-radius: 1px 25px 25px 1px;">' ..
'[[' .. data.type2 .. '_(type)|<span style="color: #FFFFFF;">' .. data.type2 .. '</span>]]</td>'
else
typesHtml =
'<td style="width:100%; background-color: #' .. colors.dark .. '; border-radius: 25px;">' ..
'[[' .. data.type1 .. '_(type)|<span style="color: #FFFFFF;">' .. data.type1 .. '</span>]]</td>'
end
-- Base stats
local statCells, bst = {}, 0
for _, stat in ipairs(STATS) do
local value = tonumber(data[stat.key]) or 0
bst = bst + value
statCells[#statCells + 1] = statValueCell(frame, stat, value)
end
local bstCategory = math.floor(bst / 100 + 0.5) * 100
-- EV yield.
local evCells, evTotal = {}, 0
for i, stat in ipairs(STATS) do
local ev = tonumber(data[EV_KEYS[i]]) or 0
evTotal = evTotal + ev
evCells[#evCells + 1] = statValueCell(frame, stat, ev)
end
-- Catch rate.
local catchRaw = tonumber(data.catch) or 0
local catchPercent = string.format('%.2f', catchRaw / 255 / 3 * 100)
local buf = {}
buf[#buf + 1] = navTableHtml(frame, data, colors, pageName)
buf[#buf + 1] = '<table align="right" class="informational-box" cellpadding="4" cellspacing="4" ' ..
'style="width: ' .. width .. '%; border: 4px solid #' .. colors.dark .. '; background-color: #' ..
colors.base .. '; padding: 4px; border-radius: 15px;">'
-- Header: dex number + icon, name.
buf[#buf + 1] = '<tr><td style="border: 3px solid #' .. colors.dark .. '; background-color: #' ..
colors.light .. '; border-radius: 15px;"><span style="margin-bottom: 7px; font-weight: bold; color:#' ..
colors.dark .. ';">#' .. padNumber(data.number) .. '</span> [[File:' .. data.number ..
'Icon.png]]</td><td style="border: 3px solid #' .. colors.dark .. '; background-color: #' ..
colors.light .. '; border-radius: 15px;"><span style="margin-bottom: 7px; font-weight: bold; color:#' ..
colors.dark .. ';">' .. displayName .. '</span></td></tr>'
-- Artwork.
buf[#buf + 1] = '<tr><td colspan="3" style="border: 3px solid #' .. colors.dark .. '; background-color: #' ..
colors.light .. '; border-radius: 15px; padding: 5px;"><div style="border-radius: 15px; ' ..
'text-align: center; padding: 5px; background-color: #FFFFFF;">[[File:' .. data.number .. data.name ..
'.png|150px]]</div></td></tr>'
-- Types + Generation.
buf[#buf + 1] = '<tr><td style="border: 3px solid #' .. colors.dark .. '; background-color: #' ..
colors.light .. '; padding: 11px; border-radius: 15px;"><p style="margin-bottom: 7px; font-weight: bold; ' ..
'color: #' .. colors.dark .. ';">Types</p><table align="center" style="width: 75%; font-weight: bold; ' ..
'padding: 3px;"><tr>' .. typesHtml .. '</tr></table></td>' ..
'<td style="border: 3px solid #' .. colors.dark .. '; background-color: #' .. colors.light ..
'; border-radius: 15px;"><p style="margin-bottom: 7px; font-weight: bold; color: #' .. colors.dark ..
';">Generation</p><span style="border-radius: 20px; padding: 6px; border: 4px solid #' .. colors.dark ..
'; background-color: #FFFFFF;"> [[:Category:Generation ' .. gen .. ' Pokémon|<span style="color:#' ..
colors.dark .. ';">' .. gen .. '</span>]] </span></td></tr>'
-- Abilities.
buf[#buf + 1] = '<tr><td colspan="6" style="border: 3px solid #' .. colors.dark .. '; background-color: #' ..
colors.light .. '; padding: 11px; border-radius: 15px; width: 100%;"><table width="100%">' ..
'<tr><th colspan="6"><p style="margin-bottom: 7px; font-weight: bold; color:#' .. colors.dark ..
';">[[Abilities|<span style="color:#' .. colors.dark .. ';">Abilities</span>]]</p></th></tr>' ..
'<tr>' .. abilitiesRow(colors, data) .. '</tr></table></td></tr>'
-- Rarity + Catch rate.
buf[#buf + 1] = '<tr>' ..
infoCell(colors, '[[List of Pokémon by Rarity Tier|<span style="color:#' .. colors.dark ..
';">Rarity Tier</span>]]', '[[:Category:' .. data.rarity .. ' Pokémon|<span style="color:#' ..
colors.dark .. ';">' .. data.rarity .. '</span>]]') ..
infoCell(colors, '[[Catch Rates|<span style="color:#' .. colors.dark .. ';">Catch rate</span>]]',
data.catch .. '/255 (' .. catchPercent .. '%)') ..
'</tr>'
-- Base stats.
buf[#buf + 1] = '<tr><td colspan="6" style="border: 3px solid #' .. colors.dark .. '; background-color: #' ..
colors.light .. '; border-radius: 15px; padding: 5px; width: 100%"><table width="100%">' ..
'<tr><th colspan="6"><p style="margin-bottom: 7px; font-weight: bold; color: #' .. colors.dark ..
';">[[Base stats|<span style="margin-bottom: 7px; font-weight: bold; color: #' .. colors.dark ..
';">Base stats</span>]]</p></th></tr><tr>' .. table.concat(statCells) .. '</tr>' ..
'<tr><td colspan="6"><div style="margin: auto; background-color:#FFFFFF; border: 3px solid #' ..
colors.dark .. '; padding: 6px; border-radius: 25px; margin-bottom: 5px; width: 50%;">' ..
'Base-stat total: ' .. bst .. '</div></td></tr></table></td></tr>'
-- EV yield.
buf[#buf + 1] = '<tr><td colspan="6" style="border: 3px solid #' .. colors.dark .. '; background-color: #' ..
colors.light .. '; border-radius: 15px; padding: 5px; width: 100%"><table width="100%">' ..
'<tr><th colspan="6">[[EVs|<span style="margin-bottom: 7px; font-weight: bold; color: #' .. colors.dark ..
';">EV Yield</span>]]</th></tr><tr>' .. table.concat(evCells) .. '</tr>' ..
'<tr><td colspan="6"><div style="margin: auto; background-color:#FFFFFF; border: 3px solid #' ..
colors.dark .. '; padding: 6px; border-radius: 25px; margin-bottom: 5px; width: 50%;">' ..
'EV-yield total: ' .. evTotal .. '</div></td></tr></table></td></tr>'
-- Base experience + Gender ratio.
buf[#buf + 1] = '<tr>' ..
infoCell(colors, '[[Experience System#Base Experience|<span style="color:#' .. colors.dark ..
';">Base Experience</span>]]', data.exp) ..
'<td style="border: 3px solid #' .. colors.dark .. '; background-color: #' .. colors.light ..
'; border-radius: 15px; padding: 5px;"><p style="margin-bottom: 7px; font-weight: bold; color: #' ..
colors.dark .. ';">Gender Ratio</p> ' .. genderRatioHtml(data.male) .. ' </td>' ..
'</tr>'
-- Height + Weight.
buf[#buf + 1] = '<tr>' ..
infoCell(colors, 'Height', data.height .. 'm') ..
infoCell(colors, 'Weight', data.weight .. 'kg') ..
'</tr>'
buf[#buf + 1] = '</table>'
buf[#buf + 1] = categories(data, gen, bstCategory, args)
return table.concat(buf)
end
return p