Module:Area: Difference between revisions

From Path of Exile 2 Wiki
Jump to navigation Jump to search
>OmegaK2
m (84 revisions imported)
 
(66 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
-- ----------------------------------------------------------------------------
-- Imports
-- ----------------------------------------------------------------------------


require('Module:No globals')
local m_util = require('Module:Util')
local m_util = require('Module:Util')
local getArgs = require('Module:Arguments').getArgs
local m_cargo = require('Module:Cargo')
local m_game = require('Module:Game')
local f_infocard = require('Module:Infocard')._main


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


-- Strings
-- Should we use the sandbox version of our submodules?
local i18n = {
local use_sandbox = m_util.misc.maybe_sandbox('Area')
    images = {
        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]]',
    },


    args = {
local f_infocard = require('Module:Infocard')._main
        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',
        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
        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 = {
        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',
    },
}


-- ----------------------------------------------------------------------------
-- The cfg table contains all localisable strings and configuration, to make it
-- Constats
-- 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 c = {}
local i18n = cfg.i18n
-- TODO: test
c.max_area_query = 8


-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
Line 113: Line 34:
-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------


local factory = {}
local h = {}
function factory.arg_list(k, args)
 
     return function (tpl_args, frame)
--
         if tpl_args[k] ~= nil then
-- Functions for processing tpl_args
             tpl_args[k] = m_util.string.split(tpl_args[k], ', ')
--
         end
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
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 131: 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
        local name
         if tpl_args.areas[area_id]['areas.main_page'] then
         if tpl_args.areas[area_id]['Has main page'] ~= '' then
             return string.format('[[%s|%s]]', tpl_args.areas[area_id]['areas.main_page'], tpl_args.areas[area_id]['areas.name'])
             name = string.format('[[%s|%s]]', tpl_args.areas[area_id]['Has main page'], tpl_args.areas[area_id]['Has name'])
         else
         else
             name = tpl_args.areas[area_id]['Has name']
             return string.format('%s ([[%s|%s]])', tpl_args.areas[area_id]['areas.name'], tpl_args.areas[area_id]['areas._pageName'], area_id)
         end
         end
        return string.format('%s ([[%s|%s]])', name, tpl_args.areas[area_id][1], area_id)
     else
     else
         return area_id
         return area_id
Line 156: Line 85:
-- Argument & display mapping
-- Argument & display mapping
-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
local display  = {}


local argument_map = {
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
     -- User supplied arguments
     --  
     --  
     main_page = {
     fields = {
        property = 'Has main page',
        main_page = {
        func = function(tpl_args, frame)
            field = 'main_page',
            local page = tpl_args.main_page
            type = 'Page',
            if page ~= nil then
            func = function(tpl_args, value)
                page = mw.title.new(tpl_args.main_page)
                if value ~= nil then
                if page == nil then
                    local page = mw.title.new(value)
                    error(string.format(i18n.errors.main_page_is_invalid, tpl_args.main_page))
                    if page == nil then
                elseif not page.exists then
                        error(string.format(i18n.errors.main_page_is_invalid, value))
                    error(string.format(i18n.errors.main_page_does_not_exist, tpl_args.main_page))
                    elseif not page.exists then
                else
                        error(string.format(i18n.errors.main_page_does_not_exist, value))
                    -- dont need the title object anymore
                    else
                    --tpl_args.main_page = page
                        -- dont need the title object anymore
                        --tpl_args.main_page = page
                    end
                 end
                 end
                return value
             end
             end
         end
         },
    },
       
   
        --
    --
        -- Can be populated by PyPoE
    -- Can be populated by PyPoE
        --
    --
        id = {
    id = {
            field = 'id',
        property = 'Is area id',
            type = 'String',
        func = nil,
            func = nil,
    },
        },
    name = {
        name = {
        property = 'Has name',
            field = 'name',
        func = nil,
            type = 'String',
    },
            func = nil,
    act = {
        },
        property = 'Has act',
        act = {
        func = m_util.cast.factory.number(i18n.args.act, {key_out='act'}),
            field = 'act',
    },
            type = 'Integer',
    area_level = {
        },
        property = 'Has area level',
        area_level = {
        func = m_util.cast.factory.number(i18n.args.area_level, {key_out='area_level'}),
            field = 'area_level',
    },
            type = 'Integer',
    level_restriction_max = {
        },
        property = 'Has maximum level restriction',
        level_restriction_max = {
        func = m_util.cast.factory.number(i18n.args.level_restriction_max, {key_out='level_restriction_max'}),
            field = 'level_restriction_max',
        default = 100,
            type = 'Integer',
    },
            default = 100,
    area_type_tags = {
        },
        property = 'Has area type tags',
        area_type_tags = {
        func = m_util.cast.factory.assoc_table(i18n.args.area_type_tags, {
            field = 'area_type_tags',
            tbl = m_game.constants.tags,
            type = 'List (,) of String',
            errmsg = i18n.errors.invalid_tag,
            func = h.proc.factory.list{
             key_out = 'area_type_tags',
                callback = m_util.validate.factory.in_table_keys{
        }),
                    tbl = m_game.constants.tags,
    },
                    errmsg = i18n.errors.invalid_tag,
    tags = {
                    errlvl = 4,
        property = 'Has tags',
                },
        func = m_util.cast.factory.assoc_table(i18n.args.tags, {
             },
            tbl = m_game.constants.tags,
            default = {},
            errmsg = i18n.errors.invalid_tag,
        },
             key_out = 'tags',
        tags = {
        }),
            field = 'tags',
    },
            type = 'List (,) of String',
    loading_screen = {
            func = h.proc.factory.list{
        property = 'Has loading screen image',
                callback = m_util.validate.factory.in_table_keys{
        func = function (tpl_args, frame)
                    tbl = m_game.constants.tags,
            local loading_id = tpl_args[i18n.args.loading_screen]
                    errmsg = i18n.errors.invalid_tag,
            if loading_id ~= nil then
                    errlvl = 4,
                tpl_args.loading_screen = string.format(i18n.images.loading_screen, loading_id)
                },
                tpl_args.loading_screen_infobox = string.format(i18n.images.loading_screen_infobox, loading_id)  
             },
            end
            default = {},
        end,
        },
    },
        loading_screen = {
    connection_ids = {
            field = 'loading_screen',
        property = 'Has area connection ids',
            type = 'Page',
        func = factory.arg_list(i18n.args.connection_ids, {key_out='connection_ids'}),
            func = function (tpl_args, value)
        default = {},
                if value ~= nil then
    },
                    -- value contains loading id
    parent_area_id = {
                    tpl_args.loading_screen_infobox = string.format(i18n.images.loading_screen_infobox, value)
        property = 'Has parent area id',
                   
    },
                    return string.format(i18n.images.loading_screen, value)
    modifier_ids = {
                end
        property = 'Has mod ids',
            end,
        func = factory.arg_list(i18n.args.modifier_ids, {key_out='modifier_ids'}),
        },
        default = {},
        connection_ids = {
    },
            field = 'connection_ids',
    monster_ids = {
            type = 'List (,) of String',
        property = 'Has monster ids',
            default = {},
        func = factory.arg_list(i18n.args.monster_ids, {key_out='monster_ids'}),
         },
        default = {},
         parent_area_id = {
    },
            field = 'parent_area_id',
    boss_monster_ids = {
            type = 'String',
        property = 'Has boss monster ids',
         },
         func = factory.arg_list(i18n.args.boss_monster_ids, {key_out='boss_monster_ids'}),
         modifier_ids = {
         default = {},
            field = 'modifier_ids',
    },
            type = 'List (,) of String',
    entry_text = {
            func = function(tpl_args, value)
        property = 'Has entry text message',
                if value == nil then
    },
                    return
    entry_npc = {
                 end
        property = 'Has entry npc',
                 tpl_args.mods = m_cargo.array_query{
    },
                    tables={'mods'},
    --
                    fields={'mods.stat_text'},
    -- Spawn chances
                    id_field='mods.id',
    --
                    id_array=value
    vaal_area_ids = {
                }
         property = 'Has vaal area ids',
               
        func = factory.arg_list(i18n.args.vaal_area_ids, {key_out='vaal_area_ids'}),
                return value
         default = {},
            end,
    },
            default = {},
    vaal_area_spawn_chance = {
         },
        property = 'Has vaal area spawn chance',
        stat_text = {
        func = m_util.cast.factory.number(i18n.args.vaal_area_spawn_chance, {key_out='vaal_area_spawn_chance'}),
            field = 'stat_text',
        default = 0,
            type = 'Text',
    },
            func = function (tpl_args, value)
    strongbox_spawn_chance = {
                if tpl_args.mods == nil then
        property = 'Has strongbox spawn chance',
                    return
        func = m_util.cast.factory.number(i18n.args.strongbox_spawn_chance, {key_out='strongbox_spawn_chance'}),
                end
        default = 0,
                local text = {}
    },
                for page, row in pairs(tpl_args.mods) do
    strongbox_max_count = {
                    if row['mods.stat_text'] ~= '' then
        property = 'Has maximum number of strongboxes',
                        text[#text+1] = row['mods.stat_text']
        func = m_util.cast.factory.number(i18n.args.strongbox_max_count, {key_out='strongbox_max_count'}),
                     end
        default = 0,
    },
    strongbox_rarity_weight = {
        property = nil,
        func = function (tpl_args, frame)
            local weights = m_util.string.split(tpl_args[i18n.args.strongbox_rarity_weight] or '', ', ')
           
            tpl_args.strongbox_rarity_weight = {}
           
            for index, data in ipairs(m_game.constants.item.rarity) do
                 local value = tonumber(weights[index]) or 0
                 tpl_args.strongbox_rarity_weight[data.long_lower] = value
                tpl_args._properties[string.format('Has %s rarity strongbox weight', data.long_lower)] = value
            end
        end,
    },
    --
    -- Area flags
    --
    is_map_area = {
        property = 'Is map area',
        func = m_util.cast.factory.boolean(i18n.args.is_map_area, {key_out='is_map_area'}),
        default = false,
    },
    is_unique_map_area = {
        property = 'Is unique map area',
        func = m_util.cast.factory.boolean(i18n.args.is_unique_map_area, {key_out='is_unique_map_area'}),
        default = false,
    },
    is_town_area = {
        property = 'Is town area',
        func = m_util.cast.factory.boolean(i18n.args.is_town_area, {key_out='is_town_area'}),
        default = false,
    },
    is_hideout_area = {
        property = 'Is hideout area',
        func = m_util.cast.factory.boolean(i18n.args.is_hideout_area, {key_out='is_hideout_area'}),
         default = false,
    },
    is_vaal_area = {
        property = 'Is vaal area',
        func = m_util.cast.factory.boolean(i18n.args.is_vaal_area, {key_out='is_vaal_area'}),
        default = false,
    },
    is_master_daily_area = {
        property = 'Is master daily area',
        func = m_util.cast.factory.boolean(i18n.args.is_master_daily_area, {key_out='is_master_daily_area'}),
        default = false,
    },
    is_labyrinth_area = {
        property = 'Is labyrinth area',
        func = m_util.cast.factory.boolean(i18n.args.is_labyrinth_area, {key_out='is_labyrinth_area'}),
        default = false,
    },
    is_labyrinth_airlock_area = {
        property = 'Is labyrinth airlock area',
        func = m_util.cast.factory.boolean(i18n.args.is_labyrinth_airlock_area, {key_out='is_labyrinth_airlock_area'}),
        default = false,
    },
    is_labyrinth_boss_area = {
        property = 'Is labyrinth boss area',
        func = m_util.cast.factory.boolean(i18n.args.is_labyrinth_boss_area, {key_out='is_labyrinth_boss_area'}),
        default = false,
    },
    has_waypoint = {
        property = 'Has waypoint',
        func = m_util.cast.factory.boolean(i18n.args.has_waypoint, {key_out='has_waypoint'}),
        default = false,
    },
    --
    -- Not argument to the template, but still parsing arguments
    --
    areas = {
        property = nil,
        func = function(tpl_args, frame)
            local query_ids = {}
            if tpl_args.parent_area_id then
                query_ids[tpl_args.parent_area_id] = {}
            end
            for _, arg in ipairs({'connection_ids', 'vaal_area_ids'}) do
                for _, id in ipairs(tpl_args[arg]) do
                     query_ids[id] = {}
                 end
                 end
             end
                return table.concat(text, '<br>')
              
             end,
             local i = 1
        },
             for k, _ in pairs(query_ids) do
        monster_ids = {
                 if type(k) ~= 'number' then
             field = 'monster_ids',
                     query_ids[i] = k
            type = 'List (,) of String',
                    i = i + 1
            default = {},
        },
        boss_monster_ids = {
             field = 'boss_monster_ids',
             type = 'List (,) of String',
            func = function(tpl_args, value)
                 if value == nil then
                     return
                 end
                 end
            end
               
           
                -- Format the id so it follows cargo standards:
            if #query_ids == 0 then
                local id = {}
                 tpl_args.areas = {}
                for i, v in ipairs(value) do
            else
                    id[#id+1] = string.format('"%s"', v)
                 tpl_args.areas = m_util.smw.array_query{
                end
                     frame=frame,
                  
                    property='Is area id',
                -- Query monster data:
                     id_array=query_ids,
                 tpl_args._boss_monster_ids = m_cargo.query(
                    query={
                     {'monsters', 'main_pages'},
                         '?Is area id',
                     {
                         '?Has name',
                         'monsters._pageName',  
                         '?Has main page',
                         '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
             end
             -- TODO: Error/Warning for missing areas?
        },
         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,
         },
     },
     },
}
}


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',
    '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',
    -- Non argument, but passed to the script
    'areas',
}
local display  = {}
display.area_type = {
display.area_type = {
     'is_map_area',
     'is_map_area',
Line 434: 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 441: Line 566:


display.table_map = {
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 = {
         args = {
Line 465: 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 472: 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 482: 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 492: Line 643:
         },
         },
         header = i18n.headers.entry_messsage,
         header = i18n.headers.entry_messsage,
         func = function(tpl_args, frame)
         func = function(tpl_args)
             return string.format(i18n.tooltips.entry_message, tpl_args.entry_npc, tpl_args.entry_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)
            )
         end,
         end,
     },
     },
Line 501: 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 510: 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 519: 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,
    },
}
 
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,
         end,
     },
     },
Line 530: Line 725:


local d = {}
local d = {}
function d.area_box(tpl_args, frame)
 
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 = {}
     local infocard_args = {}
      
      
     -- Top header
     infocard_args.header = tpl_args.name
    if tpl_args.name ~= nil then
        infocard_args.header = string.format('%s (%s)', tpl_args.name, tpl_args.id)
    else
        infocard_args.header = tpl_args.name
    end
      
      
     -- Subheader
     -- Subheader
Line 549: Line 823:


     if #out > 0 then
     if #out > 0 then
         infocard_args.subheader = table.concat(out, '')
         infocard_args.subheader = table.concat(out, ', ')
     else
     else
         infocard_args.subheader = i18n.tooltips.area
         infocard_args.subheader = i18n.tooltips.area
Line 566: 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
        local continue = true
         if d._check_args(tpl_args, data) then
         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
       
        if continue then
             tbl
             tbl
                 :tag('tr')
                 :tag('tr')
Line 598: 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 606: Line 855:
     infocard_args[1] = tostring(tbl)
     infocard_args[1] = tostring(tbl)
      
      
     if tpl_args.loading_screen_infobox then
     local i = 2
        infocard_args[2] = tpl_args.loading_screen_infobox
    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
     end


     return f_infocard(infocard_args)
     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
end


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


local p = {}
local function _area(tpl_args)
 
    --[[
function p.area(frame)
    This function adds cargo tables and displays information about the
     local tpl_args = getArgs(frame, {
    area.
         parentFirst = true
   
     })
    Examples
     frame = m_util.misc.get_frame(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_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
     --
     --
 
    -- Handle release_version and removal_version
     tpl_args._properties = {}
     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[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
          
        if data.property ~= nil and tpl_args[k] ~= nil then
            tpl_args._properties[data.property] = tpl_args[k]
        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
     -- Display only on main pages:
     local cats = {}
     local out = {}
     local act_cat = true
     out[#out+1] = d.area_box(tpl_args)
    for _, key in ipairs(display.area_type) do
        if tpl_args[key] == true then
            act_cat = false
            break
        end
    end
    if act_cat then
        cats[#cats+1] = 'Act ' .. tpl_args.act
    end
    tpl_args._properties['Has main page categories'] = m_util.misc.add_category(cats)  
      
      
     -- display
     -- Property to store what's output to main pages:
     local out = d.area_box(tpl_args, frame)
     cargo_values['infobox_html'] = out[1]
      
      
     tpl_args._properties['Has infobox HTML'] = out
     -- 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})
      
      
     -- set semantic properties
     -- Output of function
     m_util.smw.set(frame, tpl_args._properties)
     return table.concat(out)
end
 
local function _query_area_info(tpl_args)
    --[[
    Queries and displays the area infobox.  
      
      
     local page_cats = {
     ]]
        'Area data',
    -- = p.query_area_info{conditions = '[[Is area id::test]]', cats='yes'}
    }
      
      
     -- display
     if tpl_args.where == nil then
    return m_util.misc.add_category(page_cats) .. out
         return
end
     end
 
function p.query_area_info(frame)
    local tpl_args = getArgs(frame, {
         parentFirst = true
     })
    frame = m_util.misc.get_frame(frame)
      
      
     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 706: 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']
         cats[#cats+1] = string.format('[[%s]]', row['Has main page categories'])
         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
     end
   
     if tpl_args.cats then
     if tpl_args.cats then
         return table.concat(cats) .. table.concat(out)
         return table.concat(out) .. m_util.misc.add_category(cats)
     else
     else
         return table.concat(out)
         return table.concat(out)
     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 06:43, 9 October 2024

Module documentation[create] [purge]
-------------------------------------------------------------------------------
-- 
--                             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