Module:Area: Difference between revisions

From Path of Exile Wiki
Jump to navigation Jump to search
>OmegaK2
mNo edit summary
(Added is_legacy_map_area field. Fixed area_type_tags and tags fields, which were storing null values.)
 
(45 intermediate revisions by 5 users not shown)
Line 1: Line 1:
-- SMW powered area module
-------------------------------------------------------------------------------
--
--                            Module:Area
--
-- This module implements Template:Area and Template:Query area infoboxes
-------------------------------------------------------------------------------


-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
Line 7: Line 12:
-- spawn_weight* values
-- spawn_weight* values
-- spawnchacne values
-- spawnchacne values
--
-- i18n for properties


-- ----------------------------------------------------------------------------
require('Module:No globals')
-- Imports
local m_util = require('Module:Util')
-- ----------------------------------------------------------------------------
local m_cargo = require('Module:Cargo')


local m_util = require('Module:Util')
local m_game = mw.loadData('Module:Game')
local getArgs = require('Module:Arguments').getArgs
local m_game = require('Module:Game')
local f_infocard = require('Module:Infocard')._main


local cargo = mw.ext.cargo
-- Should we use the sandbox version of our submodules?
local use_sandbox = m_util.misc.maybe_sandbox('Area')


-- ----------------------------------------------------------------------------
local f_infocard = require('Module:Infocard')._main
-- Localization
-- ----------------------------------------------------------------------------


-- Strings
-- The cfg table contains all localisable strings and configuration, to make it
local i18n = {
-- easier to port this module to another wiki.
    images = {
local cfg = use_sandbox and mw.loadData('Module:Area/config/sandbox') or mw.loadData('Module:Area/config')
        waypoint_no = '[[File:No waypoint area icon.png|link=|No Waypoint]]',
        waypoint_yes = '[[File:Waypoint area icon.png|link=|No Waypoint]]',
        waypoint_town = '[[File:Town area icon.png|link=|Town Hub]]',
        loading_screen = 'File:%s loading screen.png',
        loading_screen_infobox = '[[File:%s loading screen.png|250px]]',
        screenshot = 'File:%s area screenshot.%s',
        screenshot_infobox = '[[File:%s area screenshot.%s|250px]]',
    },


    args = {
local i18n = cfg.i18n
        main_page = 'main_page',
        id = 'id',
        name = 'name',
        act = 'act',
        area_level = 'level',
        level_restriction_max = 'level_restriction_max',
        area_type_tags = 'area_type_tags',
        tags = 'tags',
        loading_screen = 'loading_screen',
        connection_ids = 'connection_ids',
        parent_area_id = 'parent_area_id',
        modifier_ids = 'modifier_ids',
        boss_monster_ids = 'boss_monster_ids',
        monster_ids = 'monster_ids',
        entry_text = 'entry_text',
        entry_npc = 'entry_npc',
        flavour_text = 'flavour_text',
        screenshot_ext = 'screenshot_ext',
        vaal_area_spawn_chance = 'vaal_area_spawn_chance',
        vaal_area_ids = 'vaal_area_ids',
        strongbox_spawn_chance = 'strongbox_spawn_chance',
        strongbox_max_count = 'strongbox_max',
        strongbox_rarity_weight = 'strongbox_rarity_weight',
        is_map_area = 'is_map_area',
        is_unique_map_area = 'is_unique_map_area',
        is_town_area = 'is_town_area',
        is_hideout_area = 'is_hideout_area',
        is_vaal_area = 'is_vaal_area',
        is_master_daily_area = 'is_master_daily_area',
        is_labyrinth_area = 'is_labyrinth_area',
        is_labyrinth_airlock_area = 'is_labyrinth_airlock_area',
        is_labyrinth_boss_area = 'is_labyrinth_boss_area',
        has_waypoint = 'has_waypoint',
    },
    errors = {
        invalid_tag = '%s is not a valid tag',
        main_page_is_invalid = 'main_page argument got "%s" which is not a valid wiki page',
        main_page_does_not_exist = 'main_page argument requires the specified page "%s" to exist in the main wiki namespace',
    },
    tooltips = {
        -- boolean tooltips. Use singular form here, categories makes these plural.
        is_map_area = 'Map area',
        is_unique_map_area = 'Unique Map area',
        is_town_area = 'Town area',
        is_hideout_area = 'Hideout area',
        is_vaal_area = 'Vaal area',
        is_master_daily_area = 'Master daily spawn area',
        is_labyrinth_area = 'Labyrinth area',
        is_labyrinth_airlock_area = 'Labyrinth airlock area',
        is_labyrinth_boss_area = 'Labyrinth boss area',
        area = 'area',
       
        --
        entry_message = '%s: %s',
    },
    headers = {
        id = 'Id',
        act = 'Act',
        area_level = 'Area level',
        level_restriction_max = m_util.html.abbr('Max level', 'Characters above this level can not enter this zone.'),
        area_type_tags = 'Area type tags',
        tags = 'Tags',
        parent_area = m_util.html.abbr('Parent Town', 'Portals will lead to the parent town'),
        connections = 'Connections',
        -- monsters
        -- boss monsters
        vaal_areas = 'Vaal Areas',
    },
}


-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
-- Constats
-- Utility & helper functions
-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------


local c = {}
local h = {}
-- TODO: test
c.max_area_query = 8


-- ----------------------------------------------------------------------------
--
-- Utility & helper functions
-- Functions for processing tpl_args
-- ----------------------------------------------------------------------------
--
h.proc = {}
h.proc.factory = {}


local factory = {}
function h.proc.factory.list(args)
function factory.arg_list(k, args)
    args = args or {}
     return function (tpl_args, frame)
     return function (tpl_args, value)
         if tpl_args[k] ~= nil then
         return m_util.cast.table(value, {
             tpl_args[k] = m_util.string.split(tpl_args[k], ', ')
            pattern = args.pattern,
         end
             callback = args.callback,
         })
     end
     end
end
end
local factory = {}


function factory.display_value(k, args)
function factory.display_value(k, args)
     return function(tpl_args, frame)
     return function(tpl_args)
         return tpl_args[k]
         return tpl_args[k]
     end
     end
Line 137: Line 62:
local util = {}
local util = {}
util.display = {}
util.display = {}
function util.display.multiple_areas(tpl_args, frame, area_ids)
function util.display.multiple_areas(tpl_args, area_ids)
     local out = {}
     local out = {}
     for _, area_id in ipairs(area_ids) do
     for _, area_id in ipairs(area_ids) do
         out[#out+1] = util.display.single_area(tpl_args, frame, area_id)
         out[#out+1] = util.display.single_area(tpl_args, area_id)
     end
     end
     return table.concat(out, '<br>')
     return table.concat(out, '<br>')
end
end


function util.display.single_area(tpl_args, frame, area_id)
function util.display.single_area(tpl_args, area_id)
     if tpl_args.areas[area_id] then
     if tpl_args.areas[area_id] then
         if tpl_args.areas[area_id]['areas.main_page'] ~= '' then
         if tpl_args.areas[area_id]['areas.main_page'] then
             return string.format('[[%s|%s]]', tpl_args.areas[area_id]['areas.main_page'], tpl_args.areas[area_id]['areas.name'])
             return string.format('[[%s|%s]]', tpl_args.areas[area_id]['areas.main_page'], tpl_args.areas[area_id]['areas.name'])
         else
         else
Line 160: Line 85:
-- Argument & display mapping
-- Argument & display mapping
-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
local display  = {}


local argument_map = {
local argument_map = {
     table = 'areas',
     table = 'areas',
   
    order = {
        'main_page',
        'id',
        'name',
        'act',
        'area_level',
        'level_restriction_max',
        'area_type_tags',
        'tags',
        'loading_screen',
        'connection_ids',
        'parent_area_id',
        'modifier_ids',
        -- populated via modifier ids
        'stat_text',
        'boss_monster_ids',
        'monster_ids',
        'entry_text',
        'entry_npc',
        'flavour_text',
        'screenshot_ext',
        'screenshot',
        'vaal_area_spawn_chance',
        'vaal_area_ids',
        'strongbox_spawn_chance',
        'strongbox_max_count',
        'strongbox_rarity_weight',
        -- those four are parsed via the argument above
        'strongbox_weight_normal',
        'strongbox_weight_magic',
        'strongbox_weight_rare',
        'strongbox_weight_unique',
        'is_map_area',
        'is_unique_map_area',
        'is_legacy_map_area',
        'is_town_area',
        'is_hideout_area',
        'is_vaal_area',
        'is_labyrinth_area',
        'is_labyrinth_airlock_area',
        'is_labyrinth_boss_area',
        'has_waypoint',
        'mainpage_categories',
       
        -- Non argument, but passed to the script
        'areas',
    },
   
     --
     --
     -- User supplied arguments
     -- User supplied arguments
Line 170: Line 145:
             field = 'main_page',  
             field = 'main_page',  
             type = 'Page',
             type = 'Page',
             func = function(tpl_args, frame)
             func = function(tpl_args, value)
                local page = tpl_args.main_page
                 if value ~= nil then
                 if page ~= nil then
                     local page = mw.title.new(value)
                     page = mw.title.new(tpl_args.main_page)
                     if page == nil then
                     if page == nil then
                         error(string.format(i18n.errors.main_page_is_invalid, tpl_args.main_page))
                         error(string.format(i18n.errors.main_page_is_invalid, value))
                     elseif not page.exists then
                     elseif not page.exists then
                         error(string.format(i18n.errors.main_page_does_not_exist, tpl_args.main_page))
                         error(string.format(i18n.errors.main_page_does_not_exist, value))
                     else
                     else
                         -- dont need the title object anymore
                         -- dont need the title object anymore
Line 183: Line 157:
                     end
                     end
                 end
                 end
                return value
             end
             end
         },
         },
Line 202: Line 177:
             field = 'act',
             field = 'act',
             type = 'Integer',
             type = 'Integer',
            func = m_util.cast.factory.number(i18n.args.act, {key_out='act'}),
         },
         },
         area_level = {
         area_level = {
             field = 'area_level',
             field = 'area_level',
             type = 'Integer',
             type = 'Integer',
            func = m_util.cast.factory.number(i18n.args.area_level, {key_out='area_level'}),
         },
         },
         level_restriction_max = {
         level_restriction_max = {
             field = 'level_restriction_max',
             field = 'level_restriction_max',
             type = 'Integer',
             type = 'Integer',
            func = m_util.cast.factory.number(i18n.args.level_restriction_max, {key_out='level_restriction_max'}),
             default = 100,
             default = 100,
         },
         },
Line 218: Line 190:
             field = 'area_type_tags',
             field = 'area_type_tags',
             type = 'List (,) of String',
             type = 'List (,) of String',
             func = m_util.cast.factory.assoc_table(i18n.args.area_type_tags, {
             func = h.proc.factory.list{
                tbl = m_game.constants.tags,
                callback = m_util.validate.factory.in_table_keys{
                errmsg = i18n.errors.invalid_tag,
                    tbl = m_game.constants.tags,
                 key_out = 'area_type_tags',
                    errmsg = i18n.errors.invalid_tag,
             }),
                    errlvl = 4,
                 },
            },
             default = {},
         },
         },
         tags = {
         tags = {
             field = 'tags',
             field = 'tags',
             type = 'List (,) of String',
             type = 'List (,) of String',
             func = m_util.cast.factory.assoc_table(i18n.args.tags, {
             func = h.proc.factory.list{
                tbl = m_game.constants.tags,
                callback = m_util.validate.factory.in_table_keys{
                errmsg = i18n.errors.invalid_tag,
                    tbl = m_game.constants.tags,
                 key_out = 'tags',
                    errmsg = i18n.errors.invalid_tag,
             }),
                    errlvl = 4,
                 },
            },
             default = {},
         },
         },
         loading_screen = {
         loading_screen = {
             field = 'loading_screen',
             field = 'loading_screen',
             type = 'Page',
             type = 'Page',
             func = function (tpl_args, frame)
             func = function (tpl_args, value)
                local loading_id = tpl_args[i18n.args.loading_screen]
                 if value ~= nil then
                 if loading_id ~= nil then
                    -- value contains loading id
                     tpl_args.loading_screen = string.format(i18n.images.loading_screen, loading_id)
                     tpl_args.loading_screen_infobox = string.format(i18n.images.loading_screen_infobox, value)
                     tpl_args.loading_screen_infobox = string.format(i18n.images.loading_screen_infobox, loading_id)  
                      
                    return string.format(i18n.images.loading_screen, value)
                 end
                 end
             end,
             end,
Line 247: Line 226:
             field = 'connection_ids',
             field = 'connection_ids',
             type = 'List (,) of String',
             type = 'List (,) of String',
            func = factory.arg_list(i18n.args.connection_ids, {key_out='connection_ids'}),
             default = {},
             default = {},
         },
         },
Line 257: Line 235:
             field = 'modifier_ids',
             field = 'modifier_ids',
             type = 'List (,) of String',
             type = 'List (,) of String',
             func = function(tpl_args, frame)
             func = function(tpl_args, value)
                factory.arg_list(i18n.args.modifier_ids, {key_out='modifier_ids'})(tpl_args, frame)
                 if value == nil then
               
                 if tpl_args.modifier_ids == nil then
                     return
                     return
                 end
                 end
                tpl_args.mods = m_cargo.array_query{
                    tables={'mods'},
                    fields={'mods.stat_text'},
                    id_field='mods.id',
                    id_array=value
                }
                  
                  
                 local query_ids = {}
                 return value
                for i, mod_id in ipairs(tpl_args.modifier_ids) do
            end,
                    query_ids[#query_ids+1] = string.format('mods.id="%s"', mod_id)
            default = {},
        },
        stat_text = {
            field = 'stat_text',
            type = 'Text',
            func = function (tpl_args, value)
                if tpl_args.mods == nil then
                    return
                 end
                 end
               
                local results = cargo.query(
                    'mods',
                    'mods.id, mods.stat_text',
                    {
                        where=table.concat(query_ids, ' OR '),
                    }
                )
               
                 local text = {}
                 local text = {}
                 for page, row in pairs(results) do
                 for page, row in pairs(tpl_args.mods) do
                     if row['mods.stat_text'] ~= '' then
                     if row['mods.stat_text'] ~= '' then
                         text[#text+1] = row['mods.stat_text']
                         text[#text+1] = row['mods.stat_text']
                     end
                     end
                 end
                 end
                  
                 return table.concat(text, '<br>')
                tpl_args.stat_text = table.concat(text, '<br>')
             end,
             end,
            default = {},
         },
         },
         monster_ids = {
         monster_ids = {
             field = 'monster_ids',
             field = 'monster_ids',
             type = 'List (,) of String',
             type = 'List (,) of String',
            func = factory.arg_list(i18n.args.monster_ids, {key_out='monster_ids'}),
             default = {},
             default = {},
         },
         },
Line 297: Line 274:
             field = 'boss_monster_ids',
             field = 'boss_monster_ids',
             type = 'List (,) of String',
             type = 'List (,) of String',
             func = factory.arg_list(i18n.args.boss_monster_ids, {key_out='boss_monster_ids'}),
             func = function(tpl_args, value)
                if value == nil then
                    return
                end
               
                -- Format the id so it follows cargo standards:
                local id = {}
                for i, v in ipairs(value) do
                    id[#id+1] = string.format('"%s"', v)
                end
               
                -- Query monster data:
                tpl_args._boss_monster_ids = m_cargo.query(
                    {'monsters', 'main_pages'},
                    {
                        'monsters._pageName',
                        'monsters.name',
                        'monsters.metadata_id',
                        'main_pages._pageName',
                    },
                    {
                        join='monsters.metadata_id=main_pages.id',
                        where=string.format(
                            'monsters.metadata_id IN (%s)',
                            table.concat(id, ', ')
                        ),
                    }
                )
               
                return value
            end,
             default = {},
             default = {},
         },
         },
         entry_text = {
         entry_text = {
             field = 'entry_text',
             field = 'entry_text',
             type = 'String',
             type = 'Text',
         },
         },
         entry_npc = {
         entry_npc = {
Line 310: Line 317:
         flavour_text = {
         flavour_text = {
             field = 'flavour_text',
             field = 'flavour_text',
             type = 'String',
             type = 'Text',
         },
         },
         screenshot_ext = {
         screenshot_ext = {
             func = nil,
             func = nil,
            type = 'String',
             default = 'jpg',
             default = 'jpg',
         },
         },
Line 319: Line 327:
             field = 'screenshot',
             field = 'screenshot',
             type = 'Page',
             type = 'Page',
             func = function(tpl_args, frame)
             func = function(tpl_args, value)
                 if tpl_args.name ~= nil then
                 if tpl_args.name ~= nil then
                     tpl_args.screenshot = string.format(i18n.images.screenshot, tpl_args.name, tpl_args.screenshot_ext)
                    -- value contains screenshot id
                     tpl_args.screenshot_infobox = string.format(i18n.images.screenshot_infobox, tpl_args.name, tpl_args.screenshot_ext)
                    local name = value or tpl_args.main_page or tpl_args.name
                     tpl_args.screenshot = string.format(i18n.images.screenshot, name, tpl_args.screenshot_ext)
                     tpl_args.screenshot_infobox = string.format(i18n.images.screenshot_infobox, name, tpl_args.screenshot_ext)
                 end
                 end
             end,
             end,
Line 332: Line 342:
             field = 'vaal_area_ids',
             field = 'vaal_area_ids',
             type = 'List (,) of String',
             type = 'List (,) of String',
            func = factory.arg_list(i18n.args.vaal_area_ids, {key_out='vaal_area_ids'}),
             default = {},
             default = {},
         },
         },
Line 338: Line 347:
             field = 'vaal_area_spawn_chance',
             field = 'vaal_area_spawn_chance',
             type = 'Integer',
             type = 'Integer',
            func = m_util.cast.factory.number(i18n.args.vaal_area_spawn_chance, {key_out='vaal_area_spawn_chance'}),
             default = 0,
             default = 0,
         },
         },
Line 344: Line 352:
             field = 'strongbox_spawn_chance',
             field = 'strongbox_spawn_chance',
             type = 'Integer',
             type = 'Integer',
            func = m_util.cast.factory.number(i18n.args.strongbox_spawn_chance, {key_out='strongbox_spawn_chance'}),
             default = 0,
             default = 0,
         },
         },
Line 350: Line 357:
             field = 'strongbox_max_count',
             field = 'strongbox_max_count',
             type = 'Integer',
             type = 'Integer',
            func = m_util.cast.factory.number(i18n.args.strongbox_max_count, {key_out='strongbox_max_count'}),
             default = 0,
             default = 0,
         },
         },
         -- fields are handled for this below
         -- fields are handled for this below
         strongbox_rarity_weight = {
         strongbox_rarity_weight = {
             func = function (tpl_args, frame)
             func = function (tpl_args, value)
                 local weights = m_util.string.split(tpl_args[i18n.args.strongbox_rarity_weight] or '', ', ')
                 local weights = m_util.string.split(tpl_args['strongbox_rarity_weight'] or '', ', ')
               
                tpl_args.strongbox_rarity_weight = {}
                  
                  
                 for index, data in ipairs(m_game.constants.item.rarity) do
                 for index, rarity in ipairs(m_game.constants.rarity_order) do
                     local value = tonumber(weights[index]) or 0
                     local value = tonumber(weights[index]) or 0
                     tpl_args.strongbox_rarity_weight[data.long_lower] = value
                     -- will be read later
                     tpl_args._properties[string.format('strongbox_weight_%s', data.long_lower)] = value  
                     tpl_args[string.format('strongbox_weight_%s', rarity)] = value
                 end
                 end
             end,
             end,
        },
        strongbox_weight_normal = {
            field = 'strongbox_weight_normal',
            type = 'Integer',
        },
        strongbox_weight_magic = {
            field = 'strongbox_weight_magic',
            type = 'Integer',
        },
        strongbox_weight_rare = {
            field = 'strongbox_weight_rare',
            type = 'Integer',
        },
        strongbox_weight_unique = {
            field = 'strongbox_weight_unique',
            type = 'Integer',
         },
         },
         --
         --
Line 373: Line 393:
             field = 'is_map_area',
             field = 'is_map_area',
             type = 'Boolean',
             type = 'Boolean',
            func = m_util.cast.factory.boolean(i18n.args.is_map_area, {key_out='is_map_area'}),
             default = false,
             default = false,
         },
         },
Line 379: Line 398:
             field = 'is_unique_map_area',
             field = 'is_unique_map_area',
             type = 'Boolean',
             type = 'Boolean',
             func = m_util.cast.factory.boolean(i18n.args.is_unique_map_area, {key_out='is_unique_map_area'}),
            default = false,
        },
        is_legacy_map_area = {
            field = 'is_legacy_map_area',
            type = 'Boolean',
             func = function(tpl_args, value)
                if tpl_args.is_map_area or tpl_args.is_unique_map_area then
                    for _, pattern in ipairs(cfg.legacy_map_area_id_patterns) do
                        if string.find(tpl_args.id, pattern) then
                            return true
                        end
                    end
                end
                return false
            end,
             default = false,
             default = false,
         },
         },
Line 385: Line 418:
             field = 'is_town_area',
             field = 'is_town_area',
             type = 'Boolean',
             type = 'Boolean',
            func = m_util.cast.factory.boolean(i18n.args.is_town_area, {key_out='is_town_area'}),
             default = false,
             default = false,
         },
         },
Line 391: Line 423:
             field = 'is_hideout_area',
             field = 'is_hideout_area',
             type = 'Boolean',
             type = 'Boolean',
            func = m_util.cast.factory.boolean(i18n.args.is_hideout_area, {key_out='is_hideout_area'}),
             default = false,
             default = false,
         },
         },
Line 397: Line 428:
             field = 'is_vaal_area',
             field = 'is_vaal_area',
             type = 'Boolean',
             type = 'Boolean',
            func = m_util.cast.factory.boolean(i18n.args.is_vaal_area, {key_out='is_vaal_area'}),
            default = false,
        },
        is_master_daily_area = {
            field = 'is_is_master_daily_area',
            type = 'Boolean',
            func = m_util.cast.factory.boolean(i18n.args.is_master_daily_area, {key_out='is_master_daily_area'}),
             default = false,
             default = false,
         },
         },
Line 409: Line 433:
             field = 'is_labyrinth_area',
             field = 'is_labyrinth_area',
             type = 'Boolean',
             type = 'Boolean',
            func = m_util.cast.factory.boolean(i18n.args.is_labyrinth_area, {key_out='is_labyrinth_area'}),
             default = false,
             default = false,
         },
         },
Line 415: Line 438:
             field = 'is_labyrinth_airlock_area',
             field = 'is_labyrinth_airlock_area',
             type = 'Boolean',
             type = 'Boolean',
            func = m_util.cast.factory.boolean(i18n.args.is_labyrinth_airlock_area, {key_out='is_labyrinth_airlock_area'}),
             default = false,
             default = false,
         },
         },
Line 421: Line 443:
             field = 'is_labyrinth_boss_area',
             field = 'is_labyrinth_boss_area',
             type = 'Boolean',
             type = 'Boolean',
            func = m_util.cast.factory.boolean(i18n.args.is_labyrinth_boss_area, {key_out='is_labyrinth_boss_area'}),
             default = false,
             default = false,
         },
         },
Line 427: Line 448:
             field = 'has_waypoint',
             field = 'has_waypoint',
             type = 'Boolean',
             type = 'Boolean',
            func = m_util.cast.factory.boolean(i18n.args.has_waypoint, {key_out='has_waypoint'}),
             default = false,
             default = false,
        },
        mainpage_categories = {
            field = 'mainpage_categories',
            type = 'List (,) of String',
            func = function (tpl_args, value)
                -- Category handling for main page only by adding the categories to a cargo field:
                -- Do notice the plural form.
                -- -- Areas, Act X areas/Map areas, Unique map areas
                local cats = {
                    'Category:Areas',
                }
                local cats_ini_len = #cats
                for _, key in ipairs(display.area_type) do
                    if tpl_args[key] == true then
                        cats[#cats+1] = string.format('Category:%ss', i18n.tooltips[key])
                    end
                end
                if #cats == cats_ini_len then
                    cats[#cats+1] = string.format('Category:Act %s areas', tpl_args.act)
                end
                return cats
            end
         },
         },
         --
         --
         -- Handled elsewhere in the old
         -- Handled elsewhere
         --
         --
         release_version = {
         release_version = {
             field = 'release_version',
             field = 'release_version',
             type = 'String'
             type = 'String',
            skip = true,
         },
         },
         removal_version = {
         removal_version = {
             field = 'removal_version',
             field = 'removal_version',
             type = 'String',
             type = 'String',
        },
             skip = true,
        mainpage_categories = {
            field = 'mainpage_categories',
             type = 'List (,) of String',
         },
         },
         infobox_html = {
         infobox_html = {
             field = 'infobox_html',
             field = 'infobox_html',
             type = 'String',
             type = 'Text',
        },
             skip = true,
        strongbox_weight_normal = {
            field = 'strongbox_weight_normal',
            type = 'Integer',
        },
        strongbox_weight_magic = {
            field = 'strongbox_weight_magic',
            type = 'Integer',
        },
        strongbox_weight_rare = {
            field = 'strongbox_weight_rare',
            type = 'Integer',
        },
        strongbox_weight_unique = {
            field = 'strongbox_weight_unique',
             type = 'Integer',
        },
        stat_text = {
            field = 'stat_text',
            type = 'String',
         },
         },
         --
         --
Line 473: Line 494:
         --
         --
         areas = {
         areas = {
             func = function(tpl_args, frame)
             func = function(tpl_args, value)
                 local query_ids = {}
                 local query_ids = {}
                 for _, arg in ipairs({'parent_area_id', 'connection_ids', 'vaal_area_ids'}) do  
                 for _, arg in ipairs({'parent_area_id', 'connection_ids', 'vaal_area_ids'}) do  
                     if type(tpl_args[arg]) == 'table' then
                     if type(tpl_args[arg]) == 'table' then
                         for _, tbl_id in ipairs(tpl_args[arg]) do
                         for _, tbl_id in ipairs(tpl_args[arg]) do
                             query_ids[tbl_id] = {}
                             query_ids[tbl_id] = true
                         end
                         end
                     elseif tpl_args[arg] then
                     elseif tpl_args[arg] then
                         query_ids[tpl_args[arg]] = {}
                         query_ids[tpl_args[arg]] = true
                     end
                     end
                 end
                 end
Line 487: Line 508:
                 local query_ids_trimmed = {}
                 local query_ids_trimmed = {}
                 for k, _ in pairs(query_ids) do
                 for k, _ in pairs(query_ids) do
                     if type(k) ~= 'number' then
                     query_ids_trimmed[#query_ids_trimmed+1] = k
                        query_ids_trimmed[#query_ids_trimmed+1] = string.format('areas.id="%s"', k)
                end
                     end
               
                local results=m_cargo.array_query{
                    tables={'areas'},
                    fields={'areas._pageName', 'areas.name', 'areas.main_page'},
                    id_field='areas.id',
                    id_array=query_ids_trimmed,
                    warning_on_missing=true,
                }
                local areas = {}
                for _, data in ipairs(results) do
                     areas[data['areas.id']] = data
                 end
                 end
                  
                  
                 if #query_ids_trimmed == 0 then
                 if tpl_args.is_vaal_area then
                     tpl_args.areas = {}
                     local spawn_areas = {}
                else
                     results = m_cargo.query(
                     local result = cargo.query(
                         {'areas'},
                         'areas',
                         {'areas._pageName', 'areas.id', 'areas.name', 'areas.main_page'},
                         'areas._pageName, areas.id, areas.name, areas.main_page',
                         {
                         {
                             where=table.concat(query_ids_trimmed, ' OR '),
                            -- TODO using a workaround since HOLDS is bricked
                            -- Don't show connected areas without a main_page to avoid showing disabled or areas which are not relevant
                             where=string.format([[
areas.vaal_area_ids__full LIKE "%%%s%%"
AND areas.main_page IS NOT NULL
AND areas.vaal_area_spawn_chance > 0
]], tpl_args.id),
                            -- area id for story areas basically orders them by appearance, should be good enough
                            orderBy='areas.id ASC',
                         }
                         }
                     )
                     )
                      
                      
                    tpl_args.areas = {}
                     for _, data in ipairs(results) do
                     for _, row in ipairs(result) do
                         table.insert(spawn_areas, data['areas.id'])
                         tpl_args.areas[row['areas.id']] = row
                        areas[data['areas.id']] = data
                     end
                     end
                    tpl_args.vaal_spawn_areas = spawn_areas
                 end
                 end
                 -- TODO: Error/Warning for missing areas?
                  
                return areas
             end,
             end,
         },
         },
Line 514: Line 554:
}
}


local argument_order = {
    'main_page',
    'id',
    'name',
    'act',
    'area_level',
    'level_restriction_max',
    'area_type_tags',
    'tags',
    'loading_screen',
    'connection_ids',
    'parent_area_id',
    'modifier_ids',
    'boss_monster_ids',
    'monster_ids',
    'entry_text',
    'entry_npc',
    'flavour_text',
    'screenshot_ext',
    'screenshot',
    'vaal_area_spawn_chance',
    'vaal_area_ids',
    'strongbox_spawn_chance',
    'strongbox_max_count',
    'strongbox_rarity_weight',
    'is_map_area',
    'is_unique_map_area',
    'is_town_area',
    'is_hideout_area',
    'is_vaal_area',
    'is_master_daily_area',
    'is_labyrinth_area',
    'is_labyrinth_airlock_area',
    'is_labyrinth_boss_area',
    'has_waypoint',
    -- parsed by m_util.args.version:
    -- 'release_version',
    -- 'removal_version',
   
    -- Non argument, but passed to the script
    'areas',
}
local display  = {}
display.area_type = {
display.area_type = {
     'is_map_area',
     'is_map_area',
Line 564: Line 560:
     'is_hideout_area',
     'is_hideout_area',
     'is_vaal_area',
     'is_vaal_area',
    'is_master_daily_area',
     'is_labyrinth_area',
     'is_labyrinth_area',
     'is_labyrinth_airlock_area',
     'is_labyrinth_airlock_area',
Line 577: Line 572:
         },
         },
         header = i18n.headers.id,
         header = i18n.headers.id,
         func = function(tpl_args, frame)
         func = function(tpl_args)
             return string.format('[[%s|%s]]', mw.title.getCurrentTitle().fullText, tpl_args.id)
             return string.format('[[%s|%s]]', mw.title.getCurrentTitle().fullText, tpl_args.id)
         end,
         end,
Line 605: Line 600:
         header = i18n.headers.level_restriction_max,
         header = i18n.headers.level_restriction_max,
         func = factory.display_value('level_restriction_max'),
         func = factory.display_value('level_restriction_max'),
    },
    {
        args = {
            boss_monster_ids = {
            },
        },
        header = i18n.headers.boss_monster_ids,
        func = function(tpl_args)
            local out = {}
            for _,v in ipairs(tpl_args._boss_monster_ids) do
                local page = v['main_pages._pageName'] or v['monsters._pageName']
                local name = v['monsters.name'] or v['monsters.metadata_id']
                out[#out+1] = string.format('[[%s|%s]]', page, name)
            end
            return table.concat(out, '<br>')
        end,
     },
     },
     {
     {
Line 612: Line 623:
         },
         },
         header = i18n.headers.area_type_tags,
         header = i18n.headers.area_type_tags,
         func = function(tpl_args, frame)
         func = function(tpl_args)
             return table.concat(tpl_args.area_type_tags, ', ')
             return table.concat(tpl_args.area_type_tags, ', ')
         end,
         end,
Line 622: Line 633:
         },
         },
         header = i18n.headers.tags,
         header = i18n.headers.tags,
         func = function(tpl_args, frame)
         func = function(tpl_args)
             return table.concat(tpl_args.tags, ', ')
             return table.concat(tpl_args.tags, ', ')
         end,
         end,
Line 632: Line 643:
         },
         },
         header = i18n.headers.entry_messsage,
         header = i18n.headers.entry_messsage,
         func = function(tpl_args, frame)
         func = function(tpl_args)
             return m_util.html.poe_color('quest',  -- Any other alternatives for spoken text?
             return m_util.html.poe_color('quest',  -- Any other alternatives for spoken text?
                 string.format(i18n.tooltips.entry_message, tpl_args.entry_npc, tpl_args.entry_text)  
                 string.format(i18n.tooltips.entry_message, tpl_args.entry_npc, tpl_args.entry_text)  
Line 643: Line 654:
         },
         },
         header = i18n.headers.parent_area,
         header = i18n.headers.parent_area,
         func = function(tpl_args, frame)
         func = function(tpl_args)
             return util.display.single_area(tpl_args, frame, tpl_args.parent_area)
             return util.display.single_area(tpl_args, tpl_args.parent_area)
         end,
         end,
     },
     },
Line 652: Line 663:
         },
         },
         header = i18n.headers.connections,
         header = i18n.headers.connections,
         func = function(tpl_args, frame)
         func = function(tpl_args)
             return util.display.multiple_areas(tpl_args, frame, tpl_args.connection_ids)
             return util.display.multiple_areas(tpl_args, tpl_args.connection_ids)
         end,
         end,
     },
     },
Line 661: Line 672:
         },
         },
         header = i18n.headers.vaal_areas,
         header = i18n.headers.vaal_areas,
         func = function(tpl_args, frame)
         func = function(tpl_args)
             return util.display.multiple_areas(tpl_args, frame, tpl_args.vaal_area_ids)
            return util.display.multiple_areas(tpl_args, tpl_args.vaal_area_ids)
        end,
    },
    -- virtual field created by querying other area data to find where a vaal area is used
    {
        args = {
            is_vaal_area = {},
            vaal_spawn_areas = {},
        },
        header = i18n.headers.vaal_spawn_areas,
        func = function(tpl_args)
             return util.display.multiple_areas(tpl_args, tpl_args.vaal_spawn_areas)
         end,
         end,
     },
     },
Line 672: Line 694:
             flavour_text = {},
             flavour_text = {},
         },
         },
         func = function(tpl_args, frame)
         func = function(tpl_args)
             return m_util.html.poe_color('flavour', tpl_args.flavour_text)
             return m_util.html.poe_color('flavour', tpl_args.flavour_text)
         end,
         end,
Line 692: Line 714:
             stat_text = {},
             stat_text = {},
         },
         },
         func = function(tpl_args, frame)
         func = function(tpl_args)
             return m_util.html.poe_color('mod', tpl_args.stat_text)
             return m_util.html.poe_color('mod', tpl_args.stat_text)
         end,
         end,
Line 704: Line 726:
local d = {}
local d = {}


function d.intro_text(tpl_args, frame)
function d.intro_text(tpl_args)
    --[[
    Display an introductory text about the area.
    ]]
     local out = {}
     local out = {}
     if mw.ustring.find(tpl_args['id'], '_') then
     if mw.ustring.find(tpl_args['id'], '_') then
         out[#out+1] = frame:expandTemplate{ title = 'Incorrect title', args = { title=tpl_args['id'] } }
         out[#out+1] = mw.getCurrentFrame():expandTemplate{
            title='Incorrect title',  
            args = {title=tpl_args['id']}  
        }
     end
     end
      
      
     if tpl_args['name'] then
     if tpl_args['name'] then
         out[#out+1] = string.format("'''%s''' is the internal id for the '''%s''' area. ", tpl_args['id'], tpl_args['name'])
         out[#out+1] = string.format(
            i18n.intro.text_with_name,
            tpl_args['id'],  
            tpl_args['main_page'] or tostring(mw.title.getCurrentTitle()),  
            tpl_args['name']
        )
     else  
     else  
         out[#out+1] = string.format("'''%s''' is the internal id of an unnamed area. ", tpl_args['id'])
         out[#out+1] = string.format(
            i18n.intro.text_without_name,
            tpl_args['id']
        )
     end  
     end  


Line 721: Line 757:
             for _, id in ipairs(tpl_args[arg]) do
             for _, id in ipairs(tpl_args[arg]) do
                 if tpl_args.areas[id] then  
                 if tpl_args.areas[id] then  
                     connected_areas[#connected_areas+1] = string.format('<li>[[%s|%s]] (%s)</li>', tostring(tpl_args.areas[id]['areas._pageName']), id, tostring(tpl_args.areas[id]['areas.name']))
                     connected_areas[#connected_areas+1] = string.format(
                        '<li>[[%s|%s]] (%s)</li>',  
                        tostring(tpl_args.areas[id]['areas._pageName']),  
                        id,  
                        tostring(tpl_args.areas[id]['areas.name'])
                    )
                 end
                 end
             end
             end
Line 727: Line 768:
     end
     end
     if #connected_areas > 0 then  
     if #connected_areas > 0 then  
         out[#out+1] = string.format('It is connected to the following areas:<ul>%s</ul>', table.concat(connected_areas))
         out[#out+1] = string.format(
            i18n.intro.connections .. '<ul>%s</ul>',  
            table.concat(connected_areas)
        )
     end
     end
      
      
Line 733: Line 777:
end
end


function d._check_args(tpl_args, frame, data)
function d._check_args(tpl_args, data)
     local continue = true
     local continue = true
     if data.args ~= nil then
     if data.args ~= nil then
Line 761: Line 805:
end
end


function d.area_box(tpl_args, frame)
function d.area_box(tpl_args)
    --[[
    Display the area info box.
    ]]
   
     local infocard_args = {}
     local infocard_args = {}
      
      
Line 792: Line 840:
     local tbl = mw.html.create('table')
     local tbl = mw.html.create('table')
     for _, data in ipairs(display.table_map) do
     for _, data in ipairs(display.table_map) do
         if d._check_args(tpl_args, frame, data) then
         if d._check_args(tpl_args, data) then
             tbl
             tbl
                 :tag('tr')
                 :tag('tr')
Line 799: Line 847:
                         :done()
                         :done()
                     :tag('td')
                     :tag('td')
                         :wikitext(data.func(tpl_args, frame) or '')
                         :wikitext(data.func(tpl_args) or '')
                         :done()
                         :done()
                     :done()
                     :done()
Line 809: Line 857:
     local i = 2
     local i = 2
     for _, data in ipairs(display.list_map) do
     for _, data in ipairs(display.list_map) do
         if d._check_args(tpl_args, frame, data) then
         if d._check_args(tpl_args, data) then
             infocard_args[i] = data.func(tpl_args, frame)
             infocard_args[i] = data.func(tpl_args)
             i = i + 1
             i = i + 1
         end
         end
Line 818: Line 866:
end
end


function d.subobject_box(tpl_args, frame)
function d.subobject_box(tpl_args)
    --[[
    Display the subobject box.
    ]]
     local container = mw.html.create('div')
     local container = mw.html.create('div')
     container
     container
Line 824: Line 875:
          
          
     -- spawn weight table  
     -- spawn weight table  
     tbl = container:tag('table')
     local tbl = container:tag('table')
     tbl
     tbl
         :attr('class', 'wikitable sortable')
         :attr('class', 'wikitable sortable')
Line 877: Line 928:


-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
-- Page functions
-- Main functions
-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------


local p = {}
local function _area(tpl_args)
 
     --[[
p.table_areas = m_util.cargo.declare_factory{data=argument_map}
    This function adds cargo tables and displays information about the
 
     area.
function p.area(frame)
     -- = p.area{id = '1_1_1', name = 'The Twilight Strand', act = 1, level = 1, tags = 'no_tempests, area_with_water', loading_screen = 'Act1', connection_ids = '1_1_town',parent_area_id = '1_1_town',  boss_monster_ids = 'Metadata/Monsters/ZombieBoss/ZombieBossHillockNormal', flavour_text = 'Hope was drowned here.', main_page = 'The Twilight Strand (Act 1)'}
     -- = p.area{id = '1_4_2', name = 'The Dried Lake', act = '4', level = '34', area_type_tags = 'shore', tags = 'act_boss_area', loading_screen = 'Act4', connection_ids = '1_4_town', parent_area_id = '1_4_town', boss_monster_ids = 'Metadata/Monsters/Voll/VollBoss, Metadata/Monsters/SkeletonSoldier/SkeletonSoldierRangedBoss', vaal_area_spawn_chance = '18', vaal_area_ids = '1_SideArea4_2, 1_SideArea4_4', strongbox_spawn_chance = '30', strongbox_max = '2', strongbox_rarity_weight = '50, 50, 50, 1', flavour_text = 'Bones of betrayal, ashes of purity.', main_page = 'The Dried Lake'}
      
      
     local tpl_args = getArgs(frame, {
     Examples
         parentFirst = true
    --------
     })
    = p.area{
     frame = m_util.misc.get_frame(frame)
        id = '1_1_1',
        name = 'The Twilight Strand',
        act = 1,
        level = 1,
        tags = 'no_tempests, area_with_water',
        loading_screen = 'Act1',
        connection_ids = '1_1_town',
        parent_area_id = '1_1_town', 
        boss_monster_ids = 'Metadata/Monsters/ZombieBoss/ZombieBossHillockNormal',
        flavour_text = 'Hope was drowned here.',  
         main_page = 'The Twilight Strand (Act 1)'
     }
     = p.area{
        id = '1_1_1',
        name = 'The Twilight Strand',
        act = 1,
        level = 1,
        tags = 'no_tempests, area_with_water',
        loading_screen = 'Act1',
        connection_ids = '1_1_town',
        parent_area_id = '1_1_town', 
        boss_monster_ids = 'Metadata/Monsters/ZombieBoss/ZombieBossHillockNormal',
        flavour_text = 'Hope was drowned here.', main_page = 'The Twilight Strand (Act 1)'
    }
    = p.area{
        id = '1_4_2',
        name = 'The Dried Lake',
        act = '4',
        level = '34',
        area_type_tags = 'shore',
        tags = 'act_boss_area',
        loading_screen = 'Act4',
        connection_ids = '1_4_town',
        parent_area_id = '1_4_town',
        boss_monster_ids = 'Metadata/Monsters/Voll/VollBoss, Metadata/Monsters/SkeletonSoldier/SkeletonSoldierRangedBoss',
        vaal_area_spawn_chance = '18',
        vaal_area_ids = '1_SideArea4_2, 1_SideArea4_4',
        strongbox_spawn_chance = '30',
        strongbox_max = '2', strongbox_rarity_weight = '50, 50, 50, 1',
        flavour_text = 'Bones of betrayal, ashes of purity.', main_page = 'The Dried Lake'
    }
    ]]
      
      
     --
     --
     -- Shared args
     -- Shared args
     --
     --
    tpl_args._properties = {
        _table = argument_map.table,
    }
   
     -- Handle release_version and removal_version
     -- Handle release_version and removal_version
     m_util.args.version(tpl_args, {frame=frame})
     m_util.args.version(tpl_args)
      
      
     -- Parse args
     local cargo_values = m_util.args.from_cargo_map{
    for _, k in ipairs(argument_order) do
         tpl_args=tpl_args,
        local data = argument_map.fields[k]
         table_map=argument_map,
        if data == nil then
         rtr=true,
            error('Missing data in argument_map: ' .. k)
     }
         end
        if data.func ~= nil then
            data.func(tpl_args, frame)
         end
       
        if data.default ~= nil and tpl_args[k] == nil then
            tpl_args[k] = data.default
        end
    end
   
    -- this should include all values as long they're set, regardless of whether they are parsed above
    for key, data in pairs(argument_map.fields) do
        local v = tpl_args[key]
         if data.field ~= nil and v ~= nil then
            if type(v) == 'table' then
                v = table.concat(v, ',')
            end
            tpl_args._properties[data.field] = v
        end
     end
      
      
     -- Parse spawn weights
     -- Parse spawn weights
     m_util.args.weight_list(tpl_args, {
     m_util.args.spawn_weight_list(tpl_args)
        frame=frame,
        output_argument='spawn_weights',
    })
   
    -- Category handling for main page only by adding the categories in a property:
    -- Do notice the plural form.
    -- -- Areas, Act X areas/Map areas, Unique map areas
    local cats = {
        'Category:Areas',
    }
    local cats_ini_len = #cats
    for _, key in ipairs(display.area_type) do
        if tpl_args[key] == true then
            cats[#cats+1] = string.format('Category:%ss', i18n.tooltips[key])
        end
    end
    if #cats == cats_ini_len then
        cats[#cats+1] = string.format('Category:Act %s areas', tpl_args.act)
    end
 
    tpl_args._properties['mainpage_categories'] = table.concat(cats, ',')
   
    -- Category handling for the local data page:
    local page_cats = {
        'Area data',
    }
      
      
     -- Display only on main pages:
     -- Display only on main pages:
     local out = {}
     local out = {}
     out[#out+1] = d.area_box(tpl_args, frame)
     out[#out+1] = d.area_box(tpl_args)
      
      
     -- Property to store what's output to main pages:
     -- Property to store what's output to main pages:
     tpl_args._properties['infobox_html'] = out[1]
     cargo_values['infobox_html'] = out[1]
      
      
     -- Set all semantic properties:
     -- Set all semantic properties:
     mw.logObject('Cargo:' .. m_util.cargo.store(frame, tpl_args._properties))
     mw.logObject('Cargo:' .. m_cargo.store(cargo_values))
      
 
     -- mw.logObject(tpl_args)
     -- Display only on data page:
     -- Display only on data page:
     out[#out+1] = d.subobject_box(tpl_args, frame)
     out[#out+1] = d.subobject_box(tpl_args)
     out[#out+1] = d.intro_text(tpl_args, frame)
     out[#out+1] = d.intro_text(tpl_args)
 
    -- Attach to table
    mw.getCurrentFrame():expandTemplate{title = i18n.templates.attach_template}
 
    -- Category handling for the local data page:
    out[#out+1] = m_util.misc.add_category({i18n.categories.area_data})
      
      
     -- Output of function
     -- Output of function
     return table.concat(out) .. m_util.misc.add_category(page_cats)
     return table.concat(out)
end
end


function p.query_area_info(frame)
local function _query_area_info(tpl_args)
     -- p.query_area_info{conditions = '[[Is area id::test]]', cats='yes'}
     --[[
     local tpl_args = getArgs(frame, {
    Queries and displays the area infobox.
         parentFirst = true
   
    })
    ]]
     frame = m_util.misc.get_frame(frame)
    -- = p.query_area_info{conditions = '[[Is area id::test]]', cats='yes'}
      
    if tpl_args.where == nil then
         return
     end
      
      
     tpl_args.cats = m_util.cast.boolean(tpl_args.cats)
     tpl_args.cats = m_util.cast.boolean(tpl_args.cats)
    tpl_args.sort = tpl_args.sort or 'Is area id'
      
      
     local results = m_util.smw.query({
     local results = m_cargo.query(
         tpl_args.conditions,
         {'areas'},
         '?Has infobox HTML#',
         {'areas.infobox_html', 'areas.mainpage_categories'},
        '?Has main page categories#',
         {
         sort=tpl_args.sort,
            where=tpl_args.where,
     }, frame)
            orderBy=tpl_args.order_by,
        }
     )
      
      
     local out = {}
     local out = {}
Line 998: Line 1,048:
      
      
     for _, row in ipairs(results) do
     for _, row in ipairs(results) do
         out[#out+1] = row['Has infobox HTML']
         out[#out+1] = row['areas.infobox_html']
         if row['Has main page categories'] ~= '' then
         if row['areas.mainpage_categories'] ~= '' then
             for _, cat in ipairs(m_util.string.split(row['Has main page categories'], '<MANY>')) do
             for _, cat in ipairs(m_util.string.split(row['areas.mainpage_categories'], ',')) do
                 cats[#cats+1] = string.gsub(cat, '[Cc]ategory:', '')
                 cats[#cats+1] = string.gsub(cat, '[Cc]ategory:', '')
             end
             end
Line 1,011: Line 1,061:
     end
     end
end
end
-- ----------------------------------------------------------------------------
-- Exported functions
-- ----------------------------------------------------------------------------
local p = {}
p.table_areas = m_cargo.declare_factory{data=argument_map}
--
-- Template:Area
--
p.area = m_util.misc.invoker_factory(_area, {
    wrappers = cfg.wrappers.area,
})
--
-- Template:Query area infoboxes
--
p.query_area_info = m_util.misc.invoker_factory(_query_area_info, {
    wrappers = cfg.wrappers.query_area_info,
})


return p
return p

Latest revision as of 16:44, 15 July 2022

Module documentation[view] [edit] [history] [purge]


This module is used on 4,000+ pages.

To avoid major disruption and server load, do not make unnecessary edits to this module. Test changes to this module first using its /sandbox and /testcases subpages . All of the changes can then be applied to this module in a single edit.

Consider discussing changes on the talk page or on Discord before implementing them.

The item module provides functionality for various area-related templates.

Overview

This module is responsible for creating area boxes, other area-related tasks. In the process a lot of the input data is verified and also added as semantic property to pages; as such, any templates deriving from this module should not be used on user pages other then for temporary testing purposes.

This template is also backed by an export script in PyPoE (pypoe_exporter wiki area ...) which can be used to export item data from the game files which then can be used on the wiki. Use the export when possible.

Implemented templates

Core templates

-------------------------------------------------------------------------------
-- 
--                             Module:Area
-- 
-- This module implements Template:Area and Template:Query area infoboxes
-------------------------------------------------------------------------------

-- ----------------------------------------------------------------------------
-- TODO
-- ----------------------------------------------------------------------------
-- invalid tags -> utl?
-- spawn_weight* values
-- spawnchacne values

require('Module:No globals')
local m_util = require('Module:Util')
local m_cargo = require('Module:Cargo')

local m_game = mw.loadData('Module:Game')

-- Should we use the sandbox version of our submodules?
local use_sandbox = m_util.misc.maybe_sandbox('Area')

local f_infocard = require('Module:Infocard')._main

-- The cfg table contains all localisable strings and configuration, to make it
-- easier to port this module to another wiki.
local cfg = use_sandbox and mw.loadData('Module:Area/config/sandbox') or mw.loadData('Module:Area/config')

local i18n = cfg.i18n

-- ----------------------------------------------------------------------------
-- Utility & helper functions
-- ----------------------------------------------------------------------------

local h = {}

--
-- Functions for processing tpl_args
--
h.proc = {}
h.proc.factory = {}

function h.proc.factory.list(args)
    args = args or {}
    return function (tpl_args, value)
        return m_util.cast.table(value, {
            pattern = args.pattern,
            callback = args.callback,
        })
    end
end

local factory = {}

function factory.display_value(k, args)
    return function(tpl_args)
        return tpl_args[k]
    end
end

local util = {}
util.display = {}
function util.display.multiple_areas(tpl_args, area_ids)
    local out = {}
    for _, area_id in ipairs(area_ids) do
        out[#out+1] = util.display.single_area(tpl_args, area_id)
    end
    return table.concat(out, '<br>')
end

function util.display.single_area(tpl_args, area_id)
    if tpl_args.areas[area_id] then
        if tpl_args.areas[area_id]['areas.main_page'] then
            return string.format('[[%s|%s]]', tpl_args.areas[area_id]['areas.main_page'], tpl_args.areas[area_id]['areas.name'])
        else
            return string.format('%s ([[%s|%s]])', tpl_args.areas[area_id]['areas.name'], tpl_args.areas[area_id]['areas._pageName'], area_id)
        end
    else
        return area_id
    end
end

-- ----------------------------------------------------------------------------
-- Argument & display mapping
-- ----------------------------------------------------------------------------
local display  = {}

local argument_map = {
    table = 'areas',
    
    order = {
        'main_page',
        'id',
        'name',
        'act',
        'area_level',
        'level_restriction_max',
        'area_type_tags',
        'tags',
        'loading_screen',
        'connection_ids',
        'parent_area_id',
        'modifier_ids',
        -- populated via modifier ids
        'stat_text',
        'boss_monster_ids',
        'monster_ids',
        'entry_text',
        'entry_npc',
        'flavour_text',
        'screenshot_ext',
        'screenshot',
        'vaal_area_spawn_chance',
        'vaal_area_ids',
        'strongbox_spawn_chance',
        'strongbox_max_count',
        'strongbox_rarity_weight',
        -- those four are parsed via the argument above
        'strongbox_weight_normal',
        'strongbox_weight_magic',
        'strongbox_weight_rare',
        'strongbox_weight_unique',
        'is_map_area',
        'is_unique_map_area',
        'is_legacy_map_area',
        'is_town_area',
        'is_hideout_area',
        'is_vaal_area',
        'is_labyrinth_area',
        'is_labyrinth_airlock_area',
        'is_labyrinth_boss_area',
        'has_waypoint',
        'mainpage_categories',
        
        -- Non argument, but passed to the script
        'areas',
    },
    
    --
    -- User supplied arguments
    -- 
    fields = {
        main_page = {
            field = 'main_page', 
            type = 'Page',
            func = function(tpl_args, value)
                if value ~= nil then
                    local page = mw.title.new(value)
                    if page == nil then
                        error(string.format(i18n.errors.main_page_is_invalid, value))
                    elseif not page.exists then
                        error(string.format(i18n.errors.main_page_does_not_exist, value))
                    else
                        -- dont need the title object anymore
                        --tpl_args.main_page = page
                    end
                end
                return value
            end
        },
        
        --
        -- Can be populated by PyPoE
        --
        id = {
            field = 'id',
            type = 'String',
            func = nil,
        },
        name = {
            field = 'name',
            type = 'String',
            func = nil,
        },
        act = {
            field = 'act',
            type = 'Integer',
        },
        area_level = {
            field = 'area_level',
            type = 'Integer',
        },
        level_restriction_max = {
            field = 'level_restriction_max',
            type = 'Integer',
            default = 100,
        },
        area_type_tags = {
            field = 'area_type_tags',
            type = 'List (,) of String',
            func = h.proc.factory.list{
                callback = m_util.validate.factory.in_table_keys{
                    tbl = m_game.constants.tags,
                    errmsg = i18n.errors.invalid_tag,
                    errlvl = 4,
                },
            },
            default = {},
        },
        tags = {
            field = 'tags',
            type = 'List (,) of String',
            func = h.proc.factory.list{
                callback = m_util.validate.factory.in_table_keys{
                    tbl = m_game.constants.tags,
                    errmsg = i18n.errors.invalid_tag,
                    errlvl = 4,
                },
            },
            default = {},
        },
        loading_screen = {
            field = 'loading_screen',
            type = 'Page',
            func = function (tpl_args, value)
                if value ~= nil then
                    -- value contains loading id
                    tpl_args.loading_screen_infobox = string.format(i18n.images.loading_screen_infobox, value)
                    
                    return string.format(i18n.images.loading_screen, value)
                end
            end,
        },
        connection_ids = {
            field = 'connection_ids',
            type = 'List (,) of String',
            default = {},
        },
        parent_area_id = {
            field = 'parent_area_id',
            type = 'String',
        },
        modifier_ids = {
            field = 'modifier_ids',
            type = 'List (,) of String',
            func = function(tpl_args, value)
                if value == nil then
                    return
                end
                tpl_args.mods = m_cargo.array_query{
                    tables={'mods'},
                    fields={'mods.stat_text'},
                    id_field='mods.id',
                    id_array=value
                }
                
                return value
            end,
            default = {},
        },
        stat_text = {
            field = 'stat_text',
            type = 'Text',
            func = function (tpl_args, value)
                if tpl_args.mods == nil then
                    return
                end
                local text = {}
                for page, row in pairs(tpl_args.mods) do
                    if row['mods.stat_text'] ~= '' then
                        text[#text+1] = row['mods.stat_text']
                    end
                end
                return table.concat(text, '<br>')
            end,
        },
        monster_ids = {
            field = 'monster_ids',
            type = 'List (,) of String',
            default = {},
        },
        boss_monster_ids = {
            field = 'boss_monster_ids',
            type = 'List (,) of String',
            func = function(tpl_args, value)
                if value == nil then
                    return
                end
                
                -- Format the id so it follows cargo standards:
                local id = {}
                for i, v in ipairs(value) do 
                    id[#id+1] = string.format('"%s"', v)
                end 
                
                -- Query monster data:
                tpl_args._boss_monster_ids = m_cargo.query(
                    {'monsters', 'main_pages'},
                    {
                        'monsters._pageName', 
                        'monsters.name', 
                        'monsters.metadata_id',
                        'main_pages._pageName',
                    },
                    {
                        join='monsters.metadata_id=main_pages.id',
                        where=string.format(
                            'monsters.metadata_id IN (%s)', 
                            table.concat(id, ', ')
                        ),
                    }
                )
                
                return value
            end,
            default = {},
        },
        entry_text = {
            field = 'entry_text',
            type = 'Text',
        },
        entry_npc = {
            field = 'entry_npc',
            type = 'String',
        },
        flavour_text = {
            field = 'flavour_text',
            type = 'Text',
        },
        screenshot_ext = {
            func = nil,
            type = 'String',
            default = 'jpg',
        },
        screenshot = {
            field = 'screenshot',
            type = 'Page',
            func = function(tpl_args, value)
                if tpl_args.name ~= nil then
                    -- value contains screenshot id
                    local name = value or tpl_args.main_page or tpl_args.name
                    tpl_args.screenshot = string.format(i18n.images.screenshot, name, tpl_args.screenshot_ext)
                    tpl_args.screenshot_infobox = string.format(i18n.images.screenshot_infobox, name, tpl_args.screenshot_ext)
                end
            end,
        },
        --
        -- Spawn chances
        --
        vaal_area_ids = {
            field = 'vaal_area_ids',
            type = 'List (,) of String',
            default = {},
        },
        vaal_area_spawn_chance = {
            field = 'vaal_area_spawn_chance',
            type = 'Integer',
            default = 0,
        },
        strongbox_spawn_chance = {
            field = 'strongbox_spawn_chance',
            type = 'Integer',
            default = 0,
        },
        strongbox_max_count = {
            field = 'strongbox_max_count',
            type = 'Integer',
            default = 0,
        },
        -- fields are handled for this below
        strongbox_rarity_weight = {
            func = function (tpl_args, value)
                local weights = m_util.string.split(tpl_args['strongbox_rarity_weight'] or '', ', ')
                
                for index, rarity in ipairs(m_game.constants.rarity_order) do
                    local value = tonumber(weights[index]) or 0
                    -- will be read later
                    tpl_args[string.format('strongbox_weight_%s', rarity)] = value
                end
            end,
        },
        strongbox_weight_normal = {
            field = 'strongbox_weight_normal',
            type = 'Integer',
        },
        strongbox_weight_magic = {
            field = 'strongbox_weight_magic',
            type = 'Integer',
        },
        strongbox_weight_rare = {
            field = 'strongbox_weight_rare',
            type = 'Integer',
        },
        strongbox_weight_unique = {
            field = 'strongbox_weight_unique',
            type = 'Integer',
        },
        --
        -- Area flags
        --
        is_map_area = {
            field = 'is_map_area',
            type = 'Boolean',
            default = false,
        },
        is_unique_map_area = {
            field = 'is_unique_map_area',
            type = 'Boolean',
            default = false,
        },
        is_legacy_map_area = {
            field = 'is_legacy_map_area',
            type = 'Boolean',
            func = function(tpl_args, value)
                if tpl_args.is_map_area or tpl_args.is_unique_map_area then
                    for _, pattern in ipairs(cfg.legacy_map_area_id_patterns) do
                        if string.find(tpl_args.id, pattern) then
                            return true
                        end
                    end
                end
                return false
            end,
            default = false,
        },
        is_town_area = {
            field = 'is_town_area',
            type = 'Boolean',
            default = false,
        },
        is_hideout_area = {
            field = 'is_hideout_area',
            type = 'Boolean',
            default = false,
        },
        is_vaal_area = {
            field = 'is_vaal_area',
            type = 'Boolean',
            default = false,
        },
        is_labyrinth_area = {
            field = 'is_labyrinth_area',
            type = 'Boolean',
            default = false,
        },
        is_labyrinth_airlock_area = {
            field = 'is_labyrinth_airlock_area',
            type = 'Boolean',
            default = false,
        },
        is_labyrinth_boss_area = {
            field = 'is_labyrinth_boss_area',
            type = 'Boolean',
            default = false,
        },
        has_waypoint = {
            field = 'has_waypoint',
            type = 'Boolean',
            default = false,
        },
        mainpage_categories = {
            field = 'mainpage_categories',
            type = 'List (,) of String',
            func = function (tpl_args, value)
                -- Category handling for main page only by adding the categories to a cargo field:
                -- Do notice the plural form.
                -- -- Areas, Act X areas/Map areas, Unique map areas 
                local cats = {
                    'Category:Areas', 
                }
                local cats_ini_len = #cats
                for _, key in ipairs(display.area_type) do
                    if tpl_args[key] == true then
                        cats[#cats+1] = string.format('Category:%ss', i18n.tooltips[key])
                    end
                end
                if #cats == cats_ini_len then 
                    cats[#cats+1] = string.format('Category:Act %s areas', tpl_args.act)
                end
                return cats
            end
        },
        --
        -- Handled elsewhere
        --
        release_version = {
            field = 'release_version',
            type = 'String',
            skip = true,
        },
        removal_version = {
            field = 'removal_version',
            type = 'String',
            skip = true,
        },
        infobox_html = {
            field = 'infobox_html',
            type = 'Text',
            skip = true,
        },
        --
        -- Not argument to the template, but still parsing arguments
        --
        areas = {
            func = function(tpl_args, value)
                local query_ids = {}
                for _, arg in ipairs({'parent_area_id', 'connection_ids', 'vaal_area_ids'}) do 
                    if type(tpl_args[arg]) == 'table' then
                        for _, tbl_id in ipairs(tpl_args[arg]) do
                            query_ids[tbl_id] = true
                        end
                    elseif tpl_args[arg] then
                        query_ids[tpl_args[arg]] = true
                    end
                end
                
                local query_ids_trimmed = {}
                for k, _ in pairs(query_ids) do
                    query_ids_trimmed[#query_ids_trimmed+1] = k
                end
                
                local results=m_cargo.array_query{
                    tables={'areas'},
                    fields={'areas._pageName', 'areas.name', 'areas.main_page'},
                    id_field='areas.id',
                    id_array=query_ids_trimmed,
                    warning_on_missing=true,
                }
                local areas = {}
                for _, data in ipairs(results) do
                    areas[data['areas.id']] = data
                end
                
                if tpl_args.is_vaal_area then
                    local spawn_areas = {}
                    results = m_cargo.query(
                        {'areas'},
                        {'areas._pageName', 'areas.id', 'areas.name', 'areas.main_page'},
                        {
                            -- TODO using a workaround since HOLDS is bricked
                            -- Don't show connected areas without a main_page to avoid showing disabled or areas which are not relevant
                            where=string.format([[
areas.vaal_area_ids__full LIKE "%%%s%%"
AND areas.main_page IS NOT NULL
AND areas.vaal_area_spawn_chance > 0
]], tpl_args.id),
                            -- area id for story areas basically orders them by appearance, should be good enough
                            orderBy='areas.id ASC',
                        }
                    )
                    
                    for _, data in ipairs(results) do
                        table.insert(spawn_areas, data['areas.id'])
                        areas[data['areas.id']] = data
                    end
                    tpl_args.vaal_spawn_areas = spawn_areas
                end
                
                return areas
            end,
        },
    },
}

display.area_type = {
    'is_map_area',
    'is_unique_map_area',
    'is_town_area',
    'is_hideout_area',
    'is_vaal_area',
    'is_labyrinth_area',
    'is_labyrinth_airlock_area',
    'is_labyrinth_boss_area',
}

display.table_map = {
    {
        args = {
            id = {
            },
        },
        header = i18n.headers.id,
        func = function(tpl_args)
            return string.format('[[%s|%s]]', mw.title.getCurrentTitle().fullText, tpl_args.id)
        end,
    },
    {
        args = {
            act = {
            },
        },
        header = i18n.headers.act,
        func = factory.display_value('act'),
    }, 
    {
        args = {
            area_level = {
            },
        },
        header = i18n.headers.area_level,
        func = factory.display_value('area_level'),
    },
    {
        args = {
            level_restriction_max = {
                hide = 100,
            },
        },
        header = i18n.headers.level_restriction_max,
        func = factory.display_value('level_restriction_max'),
    },
    {
        args = {
            boss_monster_ids = {
            },
        },
        header = i18n.headers.boss_monster_ids,
        func = function(tpl_args)
            local out = {}
            for _,v in ipairs(tpl_args._boss_monster_ids) do 
                local page = v['main_pages._pageName'] or v['monsters._pageName']
                local name = v['monsters.name'] or v['monsters.metadata_id']
                out[#out+1] = string.format('[[%s|%s]]', page, name)
            end 
            return table.concat(out, '<br>')
        end,
    },
    {
        args = {
            area_type_tags = {
            },
        },
        header = i18n.headers.area_type_tags,
        func = function(tpl_args)
            return table.concat(tpl_args.area_type_tags, ', ')
        end,
    },
    {
        args = {
            tags = {
            },
        },
        header = i18n.headers.tags,
        func = function(tpl_args)
            return table.concat(tpl_args.tags, ', ')
        end,
    },
    {
        args = {
            entry_text = {},
            entry_npc = {},
        },
        header = i18n.headers.entry_messsage,
        func = function(tpl_args)
            return m_util.html.poe_color('quest',  -- Any other alternatives for spoken text?
                string.format(i18n.tooltips.entry_message, tpl_args.entry_npc, tpl_args.entry_text) 
            )
        end,
    },
    {
        args = {
            parent_area_ids = {},
        },
        header = i18n.headers.parent_area,
        func = function(tpl_args)
            return util.display.single_area(tpl_args, tpl_args.parent_area)
        end,
    },
    {
        args = {
            connection_ids = {},
        },
        header = i18n.headers.connections,
        func = function(tpl_args)
            return util.display.multiple_areas(tpl_args, tpl_args.connection_ids)
        end,
    },
    {
        args = {
            vaal_area_ids = {},
        },
        header = i18n.headers.vaal_areas,
        func = function(tpl_args)
            return util.display.multiple_areas(tpl_args, tpl_args.vaal_area_ids)
        end,
    },
    -- virtual field created by querying other area data to find where a vaal area is used
    {
        args = {
            is_vaal_area = {},
            vaal_spawn_areas = {},
        },
        header = i18n.headers.vaal_spawn_areas,
        func = function(tpl_args)
            return util.display.multiple_areas(tpl_args, tpl_args.vaal_spawn_areas)
        end,
    },
}

display.list_map = {
    {
        args = {
            flavour_text = {},
        },
        func = function(tpl_args)
            return m_util.html.poe_color('flavour', tpl_args.flavour_text)
        end,
    },
    {
        args = {
            loading_screen_infobox = {},
        },
        func = factory.display_value('loading_screen_infobox'),
    },
    {
        args = {
            screenshot_infobox = {},
        },
        func = factory.display_value('screenshot_infobox'),
    },
    {
        args = {
            stat_text = {},
        },
        func = function(tpl_args)
            return m_util.html.poe_color('mod', tpl_args.stat_text)
        end,
    },
}

-- ----------------------------------------------------------------------------
-- display functions
-- ----------------------------------------------------------------------------

local d = {}

function d.intro_text(tpl_args)
    --[[
    Display an introductory text about the area.
    ]]
    local out = {}
    if mw.ustring.find(tpl_args['id'], '_') then
        out[#out+1] = mw.getCurrentFrame():expandTemplate{
            title='Incorrect title', 
            args = {title=tpl_args['id']} 
        }
    end
    
    if tpl_args['name'] then
        out[#out+1] = string.format(
            i18n.intro.text_with_name, 
            tpl_args['id'], 
            tpl_args['main_page'] or tostring(mw.title.getCurrentTitle()), 
            tpl_args['name']
        )
    else 
        out[#out+1] = string.format(
            i18n.intro.text_without_name,
            tpl_args['id']
        )
    end 

    local connected_areas = {}
    for _, arg in ipairs({'connection_ids', 'vaal_area_ids'}) do  
        if tpl_args[arg] then
            for _, id in ipairs(tpl_args[arg]) do
                if tpl_args.areas[id] then 
                    connected_areas[#connected_areas+1] = string.format(
                        '<li>[[%s|%s]] (%s)</li>', 
                        tostring(tpl_args.areas[id]['areas._pageName']), 
                        id, 
                        tostring(tpl_args.areas[id]['areas.name'])
                    )
                end
            end
        end
    end
    if #connected_areas > 0 then 
        out[#out+1] = string.format(
            i18n.intro.connections .. '<ul>%s</ul>', 
            table.concat(connected_areas)
        )
    end
    
    return table.concat(out)
end

function d._check_args(tpl_args, data)
    local continue = true
    if data.args ~= nil then
        for key, key_data in pairs(data.args) do
            if tpl_args[key] == nil or (type(tpl_args[key]) == 'table' and #tpl_args[key] == 0) then
                continue = false
                break
            elseif type(key_data.hide) == 'table' then
                local br = false
                for _, value in ipairs(key_data.hide) do
                    if tpl_args[key] == value then
                        br = true
                        break
                    end
                end
                if br then
                    continue = false
                    break
                end
            elseif key_data.hide ~= nil and tpl_args[key] == key_data.hide then
                continue = false
                break
            end
        end
    end
    return continue
end

function d.area_box(tpl_args)
    --[[
    Display the area info box.
    ]]
    
    local infocard_args = {}
    
    infocard_args.header = tpl_args.name
    
    -- Subheader
    local out = {}
    for _, key in ipairs(display.area_type) do
        if tpl_args[key] == true then
            out[#out+1] = i18n.tooltips[key]
        end
    end

    if #out > 0 then
        infocard_args.subheader = table.concat(out, ', ')
    else
        infocard_args.subheader = i18n.tooltips.area
    end
    
    -- Side header
    if tpl_args.is_town_area then
        infocard_args.headerright = i18n.images.waypoint_town
    elseif tpl_args.has_waypoint then
        infocard_args.headerright = i18n.images.waypoint_yes
    else
        infocard_args.headerright = i18n.images.waypoint_no
    end
    
    -- Main sections, loop through
    local tbl = mw.html.create('table')
    for _, data in ipairs(display.table_map) do
        if d._check_args(tpl_args, data) then
            tbl
                :tag('tr')
                    :tag('th')
                        :wikitext(data.header or '')
                        :done()
                    :tag('td')
                        :wikitext(data.func(tpl_args) or '')
                        :done()
                    :done()
        end
    end
    
    infocard_args[1] = tostring(tbl)
    
    local i = 2
    for _, data in ipairs(display.list_map) do
        if d._check_args(tpl_args, data) then
            infocard_args[i] = data.func(tpl_args)
            i = i + 1
        end
    end

    return f_infocard(infocard_args)
end

function d.subobject_box(tpl_args)
    --[[
    Display the subobject box.
    ]]
    local container = mw.html.create('div')
    container
        :attr('class', 'modbox floatright')
        
    -- spawn weight table 
    local tbl = container:tag('table')
    tbl
        :attr('class', 'wikitable sortable')
        -- :attr('style', 'style="width: 100%;"')
        :tag('tr')
            :tag('th')
                :attr('colspan', 3)
                :wikitext('Spawn Weights')
                :done()
            :done()
        :tag('tr')
            :tag('th')
                :wikitext('#')
                :done()
            :tag('th')
                :wikitext('Tag')
                :done()
            :tag('th')
                :wikitext('Has spawn weight')
                :done()
            :done()
        :done()
        
    local i = 0
    local value = nil
    repeat
        i = i + 1
        value = {
            tag = tpl_args[string.format('spawn_weight%s_tag', i)],
            value = tpl_args[string.format('spawn_weight%s_value', i)],
        }
        
        if value.tag then
            tbl
                :tag('tr')
                    :tag('td')
                        :wikitext(i)
                        :done()
                    :tag('td')
                        :wikitext(value.tag)
                        :done()
                    :tag('td')
                        :wikitext(value.value)
                        :done()
                    :done()
                :done()
        end
    until value.tag == nil
    
    return tostring(container)
end

-- ----------------------------------------------------------------------------
-- Main functions
-- ----------------------------------------------------------------------------

local function _area(tpl_args)
    --[[
    This function adds cargo tables and displays information about the 
    area.
    
    Examples
    --------
    = p.area{
        id = '1_1_1', 
        name = 'The Twilight Strand', 
        act = 1, 
        level = 1, 
        tags = 'no_tempests, area_with_water', 
        loading_screen = 'Act1', 
        connection_ids = '1_1_town',
        parent_area_id = '1_1_town',  
        boss_monster_ids = 'Metadata/Monsters/ZombieBoss/ZombieBossHillockNormal', 
        flavour_text = 'Hope was drowned here.', 
        main_page = 'The Twilight Strand (Act 1)'
    }  
    = p.area{
        id = '1_1_1', 
        name = 'The Twilight Strand', 
        act = 1, 
        level = 1, 
        tags = 'no_tempests, area_with_water', 
        loading_screen = 'Act1', 
        connection_ids = '1_1_town',
        parent_area_id = '1_1_town',  
        boss_monster_ids = 'Metadata/Monsters/ZombieBoss/ZombieBossHillockNormal', 
        flavour_text = 'Hope was drowned here.', main_page = 'The Twilight Strand (Act 1)'
    }
    = p.area{
        id = '1_4_2', 
        name = 'The Dried Lake', 
        act = '4', 
        level = '34', 
        area_type_tags = 'shore', 
        tags = 'act_boss_area', 
        loading_screen = 'Act4', 
        connection_ids = '1_4_town', 
        parent_area_id = '1_4_town', 
        boss_monster_ids = 'Metadata/Monsters/Voll/VollBoss, Metadata/Monsters/SkeletonSoldier/SkeletonSoldierRangedBoss', 
        vaal_area_spawn_chance = '18', 
        vaal_area_ids = '1_SideArea4_2, 1_SideArea4_4', 
        strongbox_spawn_chance = '30', 
        strongbox_max = '2', strongbox_rarity_weight = '50, 50, 50, 1', 
        flavour_text = 'Bones of betrayal, ashes of purity.', main_page = 'The Dried Lake'
    }
    ]]
    
    --
    -- Shared args
    --
    -- Handle release_version and removal_version
    m_util.args.version(tpl_args)
    
    local cargo_values = m_util.args.from_cargo_map{
        tpl_args=tpl_args,
        table_map=argument_map,
        rtr=true,
    }
    
    -- Parse spawn weights
    m_util.args.spawn_weight_list(tpl_args)
    
    -- Display only on main pages:
    local out = {}
    out[#out+1] = d.area_box(tpl_args)
    
    -- Property to store what's output to main pages:
    cargo_values['infobox_html'] = out[1]
    
    -- Set all semantic properties:
    mw.logObject('Cargo:' .. m_cargo.store(cargo_values))

    -- mw.logObject(tpl_args)
    -- Display only on data page:
    out[#out+1] = d.subobject_box(tpl_args)
    out[#out+1] = d.intro_text(tpl_args)

    -- Attach to table
    mw.getCurrentFrame():expandTemplate{title = i18n.templates.attach_template}

    -- Category handling for the local data page:
    out[#out+1] = m_util.misc.add_category({i18n.categories.area_data})
    
    -- Output of function
    return table.concat(out)
end

local function _query_area_info(tpl_args)
    --[[
    Queries and displays the area infobox. 
    
    ]]
    -- = p.query_area_info{conditions = '[[Is area id::test]]', cats='yes'}
    
    if tpl_args.where == nil then
        return
    end
    
    tpl_args.cats = m_util.cast.boolean(tpl_args.cats)
    
    local results = m_cargo.query(
        {'areas'},
        {'areas.infobox_html', 'areas.mainpage_categories'},
        {
            where=tpl_args.where,
            orderBy=tpl_args.order_by,
        }
    )
    
    local out = {}
    local cats = {}
    
    for _, row in ipairs(results) do
        out[#out+1] = row['areas.infobox_html']
        if row['areas.mainpage_categories'] ~= '' then
            for _, cat in ipairs(m_util.string.split(row['areas.mainpage_categories'], ',')) do
                cats[#cats+1] = string.gsub(cat, '[Cc]ategory:', '')
            end
        end
    end
    if tpl_args.cats then
        return table.concat(out) .. m_util.misc.add_category(cats)
    else
        return table.concat(out)
    end
end

-- ----------------------------------------------------------------------------
-- Exported functions
-- ----------------------------------------------------------------------------

local p = {}

p.table_areas = m_cargo.declare_factory{data=argument_map}

--
-- Template:Area
-- 
p.area = m_util.misc.invoker_factory(_area, {
    wrappers = cfg.wrappers.area,
})

--
-- Template:Query area infoboxes
-- 
p.query_area_info = m_util.misc.invoker_factory(_query_area_info, {
    wrappers = cfg.wrappers.query_area_info,
})

return p