Module:PokemonSpawnData: Difference between revisions

From Pokemon Revolution Online Wiki
Jump to navigation Jump to search
Cae (talk | contribs)
No edit summary
Cae (talk | contribs)
No edit summary
 
(12 intermediate revisions by the same user not shown)
Line 1: Line 1:
local p = {}
local p = {}
local _pokemonName = ''  -- set once in p.main, read by render functions


local function getArgs(frame)
local function getArgs(frame)
Line 20: Line 22:
end
end


-- Load an External Data source and return true if at least one row was found.
-- Load an External Data source.
-- Land and surf use separate external variable names (land_area vs surf_area)
local function loadFileData(frame, params)
-- so they never share a slot; each can be checked independently via #external_value.
local function loadFileData(frame, params, checkField)
     local callParams = { [1] = 'source=' .. (params.source or 'data') }
     local callParams = { [1] = 'source=' .. (params.source or 'data') }
     for k, v in pairs(params) do
     for k, v in pairs(params) do
Line 29: Line 29:
     end
     end
     frame:callParserFunction('#get_file_data', callParams)
     frame:callParserFunction('#get_file_data', callParams)
     local val = frame:callParserFunction('#external_value', { checkField })
end
     return val ~= nil and val ~= ''
 
-- Read a colour from a type-colour template e.g. Template:Grass_color_dark.
-- Returns the hex string WITHOUT the leading '#'.
local function typeColor(frame, colorType, variant)
    if not colorType or colorType == '' then return nil end
    -- Trichrome uses {{lc:Color}} so templates are lowercase: {{grass_color_dark}}
     local lower = mw.ustring.lower(colorType)
    local title = lower .. '_color' .. (variant ~= '' and ('_' .. variant) or '')
    local ok, val = pcall(function()
        return frame:expandTemplate{ title = title, args = {} }
    end)
     if ok and val and val ~= '' then return val end
    return nil
end
end


Line 37: Line 49:
-- ---------------------------------------------------------------------------
-- ---------------------------------------------------------------------------


local function renderLandTable(frame)
local function renderLandTable(frame, color)
    loadFileData(frame, {
        source        = 'spawns',
        ['file name'] = 'land_spawns.csv',
        format        = 'CSV with header',
        filters      = 'Pokemon=' .. _pokemonName,
        data          = 'land_area=Map,land_member=Member,land_number=DexID,land_name=Pokemon,'
                    .. 'land_morning=Morning,land_day=Day,land_night=Night,'
                    .. 'land_item=Item,land_rarity=Tier,land_minlvl=MinLvl,land_maxlvl=MaxLvl',
    })
 
     local rows = frame:callParserFunction('#display_external_table', {
     local rows = frame:callParserFunction('#display_external_table', {
         [1]  = 'template=PokemonLandDataRow',
         [1]  = 'template=PokemonLandDataRow',
         data = 'area=land_area,member=member,number=number,name=name,'
         data = 'area=land_area,member=land_member,number=land_number,name=land_name,'
             .. 'morning=morning,day=day,night=night,'
             .. 'morning=land_morning,day=land_day,night=land_night,'
             .. 'item=item,rarity=rarity,minlvl=minlvl,maxlvl=maxlvl',
             .. 'item=land_item,rarity=land_rarity,minlvl=land_minlvl,maxlvl=land_maxlvl',
     })
     })


     -- #var values set by {{Trichrome}} are only readable via frame:preprocess(),
     if not rows or mw.text.trim(rows) == '' then return nil end
     -- not from plain module-output strings. We preprocess header/footer here so
 
     -- {{#var:border}} etc. are resolved in the same frame context as Trichrome.
    local border    = '#' .. (typeColor(frame, color, 'dark')  or '4a7c3f')
     local header = frame:preprocess([=[
     local background = '#' .. (typeColor(frame, color, '')      or 'c4e3a8')
<table align="left" style="width: 85%; max-width: 90%; text-align: center; margin: auto; border-radius: 15px; border: 3px solid {{#var:border}}; background-color: {{#var:background}}; padding: 7px;">
     local cell      = '#' .. (typeColor(frame, color, 'light') or '8db96e')
<tr style="background-color: {{#var:cell}}; color: {{#var:border}};"><th scope="col" style="border-top-left-radius: 25px; width: 15%; border: 1px solid {{#var:border}};">Location</th><th scope="col" style="width: 7%; border: 1px solid {{#var:border}};">Levels</th><th colspan="3" scope="col" style="width: 17%; border: 1px solid {{#var:border}};">[[Pokétime|<span style="color: {{#var:border}};">Times</span>]]</th><th style="width: 20%; border: 1px solid {{#var:border}};">Held Item</th><th style="border-top-right-radius: 25px; width: 10%; border: 1px solid {{#var:border}};">[[List of Pokémon by Rarity Tier|<span style="color: {{#var:border}};">Rarity Tier</span>]]</th></tr>]=])
 
     local header =
        '<table align="left" style="width: 85%; max-width: 90%; text-align: center; margin: auto;'
        .. ' border-radius: 15px; border: 3px solid ' .. border .. ';'
        .. ' background-color: ' .. background .. '; padding: 7px;">\n'
        .. '<tr style="background-color: ' .. cell .. '; color: ' .. border .. ';">
        .. '<th scope="col" style="border-top-left-radius: 25px; width: 15%; border: 1px solid ' .. border .. ';">Location</th>'
        .. '<th scope="col" style="width: 7%; border: 1px solid ' .. border .. ';">Levels</th>'
        .. '<th colspan="3" scope="col" style="width: 17%; border: 1px solid ' .. border .. ';">[[Pokétime|<span style="color: ' .. border .. ';">Times</span>]]</th>'
        .. '<th style="width: 20%; border: 1px solid ' .. border .. ';">Held Item</th>'
        .. '<th style="border-top-right-radius: 25px; width: 10%; border: 1px solid ' .. border .. ';">[[List of Pokémon by Rarity Tier|<span style="color: ' .. border .. ';">Rarity Tier</span>]]</th></tr>'


     local footer = frame:preprocess([=[
     local footer =
<tr><td colspan="8" style="text-align: left; border-radius: 1px 1px 25px 25px; border: 1px solid {{#var:border}}; background-color: {{#var:cell}};"><p style="margin-top: 8px; margin-left: 10px;"><ul>
        '<tr><td colspan="8" style="text-align: left; border-radius: 1px 1px 25px 25px;'
<li><span style="color:#FF00BF; font-weight: bold;">Pink-colored</span> areas denote that this spawn is [[membership]]-exclusive</li>
        .. ' border: 1px solid ' .. border .. '; background-color: ' .. cell .. ';">
<li>'''Emboldened''' levels indicate they are isolatable with the [[Repel trick]]</li></ul></p></td></tr>
        .. '<p style="margin-top: 8px; margin-left: 10px;"><ul>\n'
</table><br clear="all">]=])
        .. '<li><span style="color:#FF00BF; font-weight: bold;">Pink-colored</span> areas denote that this spawn is [[membership]]-exclusive</li>\n'
        .. "<li>'''Emboldened''' levels indicate they are isolatable with the [[Repel trick]]</li>"
        .. '</ul></p></td></tr>\n</table><br clear="all">'


     return header .. rows .. footer
     return header .. rows .. footer
Line 65: Line 99:
-- ---------------------------------------------------------------------------
-- ---------------------------------------------------------------------------


local WATER_DARK  = '{{Water_color_dark}}'
local function renderSurfTable(frame, color)
local WATER_BG    = '#{{Water_color}}'
    loadFileData(frame, {
local WATER_LIGHT = '#{{Water_color_light}}'
        source        = 'spawns',
        ['file name'] = 'surf_spawns.json',
        format        = 'JSON',
        filters      = 'Pokemon=' .. _pokemonName .. ',FishingOnly=0',
        data          = 'surf_area=Map,surf_member=MemberOnly,surf_number=MonsterID,surf_name=Pokemon,'
                    .. 'surf_time=Daytime,surf_item=Item,surf_rarity=Tier,'
                    .. 'surf_minlvl=MinLvl,surf_maxlvl=MaxLvl,surf_rod=RequiredRod,surf_fishable=Fishing',
    })


local function renderSurfTable(frame)
     local rows = frame:callParserFunction('#display_external_table', {
     local rows = frame:callParserFunction('#display_external_table', {
         [1]  = 'template=PokemonSurfingDataRow',
         [1]  = 'template=PokemonSurfingDataRow',
         data = 'area=surf_area,number=number,name=name,time=time,'
         data = 'area=surf_area,number=surf_number,name=surf_name,time=surf_time,'
             .. 'minlvl=minlvl,maxlvl=maxlvl,item=item,rarity=rarity,'
             .. 'minlvl=surf_minlvl,maxlvl=surf_maxlvl,item=surf_item,rarity=surf_rarity,'
             .. 'rod=rod,fishable=fishable,member=member',
             .. 'rod=surf_rod,fishable=surf_fishable,member=surf_member',
     })
     })


     local header = [=[
    if not rows or mw.text.trim(rows) == '' then return nil end
<table align="left" style="width:88%;max-width:100%;text-align:center;margin:auto;border-radius:15px;border:4px solid #{{Water_color_dark}};background-color:#{{Water_color}};padding:7px;">
 
<tr><td colspan="8"><div style="margin:auto;width:53px;height:53px;border:3px solid #{{Water_color_dark}};border-radius:40px;background-color:#{{Water_color_light}};padding:5px;">[[File:PikachuSurf.png]]</div></td></tr>
    -- Surf table always uses fixed water/blue colours regardless of Pokémon type.
<tr style="background-color:#{{Water_color_light}};">
    local dark  = '445E9C'
<th scope="col" style="border-radius:15px 1px 1px 1px;width:15%;border:1px solid #{{Water_color_dark}};color:#{{Water_color_dark}};">Location</th>
    local bg    = '6890F0'
<th scope="col" style="width:7%;border:1px solid #{{Water_color_dark}};color:#{{Water_color_dark}};">Levels</th>
    local light = '9DB7F5'
<th colspan="3" scope="col" style="width:6%;border:1px solid #{{Water_color_dark}};">[[Pokétime|<span style="color:#{{Water_color_dark}};">Times</span>]]</th>
 
<th style="width:10%;border:1px solid #{{Water_color_dark}};color:#{{Water_color_dark}};">Held Item</th>
     local header =
<th style="width:7%;border:1px solid #{{Water_color_dark}};color:#{{Water_color_dark}};">Rod</th>
        '<table align="left" style="width:88%;max-width:100%;text-align:center;margin:auto;border-radius:15px;border:4px solid #' .. dark .. ';background-color:#' .. bg .. ';padding:7px;">\n'
<th style="border-radius:1px 15px 1px 1px;width:10%;border:1px solid #{{Water_color_dark}};">[[List of Pokémon by Rarity Tier|<span style="color:#{{Water_color_dark}};">Rarity Tier</span>]]</th>
        .. '<tr><td colspan="8"><div style="margin:auto;width:53px;height:53px;border:3px solid #' .. dark .. ';border-radius:40px;background-color:#' .. light .. ';padding:5px;">[[File:PikachuSurf.png]]</div></td></tr>\n'
</tr>]=]
        .. '<tr style="background-color:#' .. light .. ';">
        .. '<th scope="col" style="border-radius:15px 1px 1px 1px;width:15%;border:1px solid #' .. dark .. ';color:#' .. dark .. ';">Location</th>'
        .. '<th scope="col" style="width:7%;border:1px solid #' .. dark .. ';color:#' .. dark .. ';">Levels</th>'
        .. '<th colspan="3" scope="col" style="width:6%;border:1px solid #' .. dark .. ';">[[Pokétime|<span style="color:#' .. dark .. ';">Times</span>]]</th>'
        .. '<th style="width:10%;border:1px solid #' .. dark .. ';color:#' .. dark .. ';">Held Item</th>'
        .. '<th style="width:7%;border:1px solid #' .. dark .. ';color:#' .. dark .. ';">Rod</th>'
        .. '<th style="border-radius:1px 15px 1px 1px;width:10%;border:1px solid #' .. dark .. ';">[[List of Pokémon by Rarity Tier|<span style="color:#' .. dark .. ';">Rarity Tier</span>]]</th></tr>'


     local footer = [=[
     local footer =
<tr><td colspan="9" style="text-align:left;border-radius:1px 1px 25px 25px;border:1px solid #{{Water_color_dark}};background-color:#{{Water_color_light}};"><ul style="margin-top:8px;margin-left:12px;padding:5px;">
        '<tr><td colspan="9" style="text-align:left;border-radius:1px 1px 25px 25px;border:1px solid #' .. dark .. ';background-color:#' .. light .. ';">
<li>All rows with a fishing rod indicate that its corresponding Pokémon can also be [[Fishing|fished]] with the rod tier shown</li>
        .. '<ul style="margin-top:8px;margin-left:12px;padding:5px;">\n'
<li><span style="color:#FF00BF;font-weight:bold;">Pink-colored</span> areas denote that this area or inhabiting spawn is strictly [[membership]]-exclusive</li>
        .. '<li>All rows with a fishing rod indicate that its corresponding Pokémon can also be [[Fishing|fished]] with the rod tier shown</li>\n'
<li>'''Emboldened''' levels indicate they are isolatable with the [[Repel trick]]</li>
        .. '<li><span style="color:#FF00BF;font-weight:bold;">Pink-colored</span> areas denote that this area or inhabiting spawn is strictly [[membership]]-exclusive</li>\n'
</ul></td></tr>
        .. "<li>'''Emboldened''' levels indicate they are isolatable with the [[Repel trick]]</li>"
</table><br clear="all">]=]
        .. '</ul></td></tr>\n</table><br clear="all">'


     return header .. rows .. footer
     return header .. rows .. footer
Line 108: Line 154:
     local pokemonName = args[1] or args.Name
     local pokemonName = args[1] or args.Name
                     or frame:callParserFunction('PAGENAME', {}) or ''
                     or frame:callParserFunction('PAGENAME', {}) or ''
    _pokemonName = pokemonName  -- shared with render functions
    -- Auto-detect primary type from PokemonRawList.csv (same source as
    -- PokemonTypeEffectiveness), unless an explicit Color override was given.
    local color = args.Color or args.color or ''
    if color == '' then
        frame:callParserFunction('#get_file_data', {
            [1]              = 'source=data',
            ['file name']    = 'PokemonRawList.csv',
            format          = 'CSV with header',
            filters          = 'Name=' .. pokemonName,
            data            = 'spawn_type1=Type1',
        })
        color = frame:callParserFunction('#external_value', { 'spawn_type1' }) or ''
    end


     local out    = {}
     local out    = {}
    local hasLand = false
    local hasSurf = false


     -- ── Land spawns ──────────────────────────────────────────────────────────
     local landResult = renderLandTable(frame, color)
    hasLand = loadFileData(frame, {
     local surfResult = renderSurfTable(frame, color)
        source        = 'spawns',
        ['file name'] = 'land_spawns.csv',
        format        = 'CSV with header',
        filters      = 'Pokemon=' .. pokemonName,
        data          = 'land_area=Map,member=Member,number=DexID,name=Pokemon,'
                    .. 'morning=Morning,day=Day,night=Night,'
                    .. 'item=Item,rarity=Tier,minlvl=MinLvl,maxlvl=MaxLvl',
    }, 'land_area')
     if hasLand then
        table.insert(out, renderLandTable(frame))
    end


     -- ── Surf / Fishing spawns ─────────────────────────────────────────────────
     if landResult then
    hasSurf = loadFileData(frame, {
         table.insert(out, '===Land===')
         source        = 'spawns',
         table.insert(out, landResult)
        ['file name'] = 'surf_spawns.json',
        format        = 'JSON',
        filters      = 'Pokemon=' .. pokemonName .. ',FishingOnly=0',
        data          = 'surf_area=Map,member=MemberOnly,number=MonsterID,name=Pokemon,'
                    .. 'time=Daytime,item=Item,rarity=Tier,'
                    .. 'minlvl=MinLvl,maxlvl=MaxLvl,rod=RequiredRod,fishable=Fishing',
    }, 'surf_area')
    if hasSurf then
         table.insert(out, renderSurfTable(frame))
     end
     end


    -- ── No spawns ─────────────────────────────────────────────────────────────
     if surfResult then
     if not hasLand and not hasSurf then
         table.insert(out, '===Surfing===')
         table.insert(out,
        table.insert(out, surfResult)
            "<div style='padding:8px;font-style:italic;'>"
        .. pokemonName
        .. " has no wild spawn locations.</div>"
        )
     end
     end



Latest revision as of 14:50, 10 March 2026

Documentation for this module may be created at Module:PokemonSpawnData/doc

local p = {}

local _pokemonName = ''   -- set once in p.main, read by render functions

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

-- Read a parser variable set by {{Trichrome}} (border, background, cell, etc.)
local function getVar(frame, name)
    return frame:callParserFunction('#var', { name }) or ''
end

-- Load an External Data source.
local function loadFileData(frame, params)
    local callParams = { [1] = 'source=' .. (params.source or 'data') }
    for k, v in pairs(params) do
        if k ~= 'source' then callParams[k] = v end
    end
    frame:callParserFunction('#get_file_data', callParams)
end

-- Read a colour from a type-colour template e.g. Template:Grass_color_dark.
-- Returns the hex string WITHOUT the leading '#'.
local function typeColor(frame, colorType, variant)
    if not colorType or colorType == '' then return nil end
    -- Trichrome uses {{lc:Color}} so templates are lowercase: {{grass_color_dark}}
    local lower = mw.ustring.lower(colorType)
    local title = lower .. '_color' .. (variant ~= '' and ('_' .. variant) or '')
    local ok, val = pcall(function()
        return frame:expandTemplate{ title = title, args = {} }
    end)
    if ok and val and val ~= '' then return val end
    return nil
end

-- ---------------------------------------------------------------------------
-- Land spawn table
-- ---------------------------------------------------------------------------

local function renderLandTable(frame, color)
    loadFileData(frame, {
        source        = 'spawns',
        ['file name'] = 'land_spawns.csv',
        format        = 'CSV with header',
        filters       = 'Pokemon=' .. _pokemonName,
        data          = 'land_area=Map,land_member=Member,land_number=DexID,land_name=Pokemon,'
                     .. 'land_morning=Morning,land_day=Day,land_night=Night,'
                     .. 'land_item=Item,land_rarity=Tier,land_minlvl=MinLvl,land_maxlvl=MaxLvl',
    })

    local rows = frame:callParserFunction('#display_external_table', {
        [1]  = 'template=PokemonLandDataRow',
        data = 'area=land_area,member=land_member,number=land_number,name=land_name,'
            .. 'morning=land_morning,day=land_day,night=land_night,'
            .. 'item=land_item,rarity=land_rarity,minlvl=land_minlvl,maxlvl=land_maxlvl',
    })

    if not rows or mw.text.trim(rows) == '' then return nil end

    local border     = '#' .. (typeColor(frame, color, 'dark')  or '4a7c3f')
    local background = '#' .. (typeColor(frame, color, '')      or 'c4e3a8')
    local cell       = '#' .. (typeColor(frame, color, 'light') or '8db96e')

    local header =
        '<table align="left" style="width: 85%; max-width: 90%; text-align: center; margin: auto;'
        .. ' border-radius: 15px; border: 3px solid ' .. border .. ';'
        .. ' background-color: ' .. background .. '; padding: 7px;">\n'
        .. '<tr style="background-color: ' .. cell .. '; color: ' .. border .. ';">'  
        .. '<th scope="col" style="border-top-left-radius: 25px; width: 15%; border: 1px solid ' .. border .. ';">Location</th>'
        .. '<th scope="col" style="width: 7%; border: 1px solid ' .. border .. ';">Levels</th>'
        .. '<th colspan="3" scope="col" style="width: 17%; border: 1px solid ' .. border .. ';">[[Pokétime|<span style="color: ' .. border .. ';">Times</span>]]</th>'
        .. '<th style="width: 20%; border: 1px solid ' .. border .. ';">Held Item</th>'
        .. '<th style="border-top-right-radius: 25px; width: 10%; border: 1px solid ' .. border .. ';">[[List of Pokémon by Rarity Tier|<span style="color: ' .. border .. ';">Rarity Tier</span>]]</th></tr>'

    local footer =
        '<tr><td colspan="8" style="text-align: left; border-radius: 1px 1px 25px 25px;'
        .. ' border: 1px solid ' .. border .. '; background-color: ' .. cell .. ';">'  
        .. '<p style="margin-top: 8px; margin-left: 10px;"><ul>\n'
        .. '<li><span style="color:#FF00BF; font-weight: bold;">Pink-colored</span> areas denote that this spawn is [[membership]]-exclusive</li>\n'
        .. "<li>'''Emboldened''' levels indicate they are isolatable with the [[Repel trick]]</li>"
        .. '</ul></p></td></tr>\n</table><br clear="all">'

    return header .. rows .. footer
end

-- ---------------------------------------------------------------------------
-- Surf/Fishing spawn table
-- ---------------------------------------------------------------------------

local function renderSurfTable(frame, color)
    loadFileData(frame, {
        source        = 'spawns',
        ['file name'] = 'surf_spawns.json',
        format        = 'JSON',
        filters       = 'Pokemon=' .. _pokemonName .. ',FishingOnly=0',
        data          = 'surf_area=Map,surf_member=MemberOnly,surf_number=MonsterID,surf_name=Pokemon,'
                     .. 'surf_time=Daytime,surf_item=Item,surf_rarity=Tier,'
                     .. 'surf_minlvl=MinLvl,surf_maxlvl=MaxLvl,surf_rod=RequiredRod,surf_fishable=Fishing',
    })

    local rows = frame:callParserFunction('#display_external_table', {
        [1]  = 'template=PokemonSurfingDataRow',
        data = 'area=surf_area,number=surf_number,name=surf_name,time=surf_time,'
            .. 'minlvl=surf_minlvl,maxlvl=surf_maxlvl,item=surf_item,rarity=surf_rarity,'
            .. 'rod=surf_rod,fishable=surf_fishable,member=surf_member',
    })

    if not rows or mw.text.trim(rows) == '' then return nil end

    -- Surf table always uses fixed water/blue colours regardless of Pokémon type.
    local dark  = '445E9C'
    local bg    = '6890F0'
    local light = '9DB7F5'

    local header =
        '<table align="left" style="width:88%;max-width:100%;text-align:center;margin:auto;border-radius:15px;border:4px solid #' .. dark .. ';background-color:#' .. bg .. ';padding:7px;">\n'
        .. '<tr><td colspan="8"><div style="margin:auto;width:53px;height:53px;border:3px solid #' .. dark .. ';border-radius:40px;background-color:#' .. light .. ';padding:5px;">[[File:PikachuSurf.png]]</div></td></tr>\n'
        .. '<tr style="background-color:#' .. light .. ';">'  
        .. '<th scope="col" style="border-radius:15px 1px 1px 1px;width:15%;border:1px solid #' .. dark .. ';color:#' .. dark .. ';">Location</th>'
        .. '<th scope="col" style="width:7%;border:1px solid #' .. dark .. ';color:#' .. dark .. ';">Levels</th>'
        .. '<th colspan="3" scope="col" style="width:6%;border:1px solid #' .. dark .. ';">[[Pokétime|<span style="color:#' .. dark .. ';">Times</span>]]</th>'
        .. '<th style="width:10%;border:1px solid #' .. dark .. ';color:#' .. dark .. ';">Held Item</th>'
        .. '<th style="width:7%;border:1px solid #' .. dark .. ';color:#' .. dark .. ';">Rod</th>'
        .. '<th style="border-radius:1px 15px 1px 1px;width:10%;border:1px solid #' .. dark .. ';">[[List of Pokémon by Rarity Tier|<span style="color:#' .. dark .. ';">Rarity Tier</span>]]</th></tr>'

    local footer =
        '<tr><td colspan="9" style="text-align:left;border-radius:1px 1px 25px 25px;border:1px solid #' .. dark .. ';background-color:#' .. light .. ';">'  
        .. '<ul style="margin-top:8px;margin-left:12px;padding:5px;">\n'
        .. '<li>All rows with a fishing rod indicate that its corresponding Pokémon can also be [[Fishing|fished]] with the rod tier shown</li>\n'
        .. '<li><span style="color:#FF00BF;font-weight:bold;">Pink-colored</span> areas denote that this area or inhabiting spawn is strictly [[membership]]-exclusive</li>\n'
        .. "<li>'''Emboldened''' levels indicate they are isolatable with the [[Repel trick]]</li>"
        .. '</ul></td></tr>\n</table><br clear="all">'

    return header .. rows .. footer
end

-- ---------------------------------------------------------------------------
-- main()
-- ---------------------------------------------------------------------------

function p.main(frame)
    local args        = getArgs(frame)
    local pokemonName = args[1] or args.Name
                     or frame:callParserFunction('PAGENAME', {}) or ''
    _pokemonName = pokemonName  -- shared with render functions

    -- Auto-detect primary type from PokemonRawList.csv (same source as
    -- PokemonTypeEffectiveness), unless an explicit Color override was given.
    local color = args.Color or args.color or ''
    if color == '' then
        frame:callParserFunction('#get_file_data', {
            [1]              = 'source=data',
            ['file name']    = 'PokemonRawList.csv',
            format           = 'CSV with header',
            filters          = 'Name=' .. pokemonName,
            data             = 'spawn_type1=Type1',
        })
        color = frame:callParserFunction('#external_value', { 'spawn_type1' }) or ''
    end

    local out     = {}

    local landResult = renderLandTable(frame, color)
    local surfResult = renderSurfTable(frame, color)

    if landResult then
        table.insert(out, '===Land===')
        table.insert(out, landResult)
    end

    if surfResult then
        table.insert(out, '===Surfing===')
        table.insert(out, surfResult)
    end

    return table.concat(out, '\n')
end

return p