Path of Exile Wiki

Please consider helping keep the wiki up to date. Check the to-do list of updates needed for version 3.14.0.

Game data exports will becoming later as the technical changes in addition to regular changes take some more time.

READ MORE

Path of Exile Wiki
(Use util wraps instead of cargo directly)
(Vaal areas -> Vaal side areas)
 
(17 intermediate revisions by 2 users not shown)
Line 16: Line 16:
 
local m_game = require('Module:Game')
 
local m_game = require('Module:Game')
 
local f_infocard = require('Module:Infocard')._main
 
local f_infocard = require('Module:Infocard')._main
  +
local m_cargo = require('Module:Cargo')
  +
   
 
-- ----------------------------------------------------------------------------
 
-- ----------------------------------------------------------------------------
Line 32: Line 34:
 
screenshot_infobox = '[[File:%s area screenshot.%s|250px]]',
 
screenshot_infobox = '[[File:%s area screenshot.%s|250px]]',
 
},
 
},
 
 
args = {
 
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 = {
 
errors = {
 
invalid_tag = '%s is not a valid tag',
 
invalid_tag = '%s is not a valid tag',
Line 84: Line 51:
 
is_town_area = 'Town area',
 
is_town_area = 'Town area',
 
is_hideout_area = 'Hideout area',
 
is_hideout_area = 'Hideout area',
is_vaal_area = 'Vaal area',
+
is_vaal_area = 'Vaal side area',
is_master_daily_area = 'Master daily spawn area',
 
 
is_labyrinth_area = 'Labyrinth area',
 
is_labyrinth_area = 'Labyrinth area',
 
is_labyrinth_airlock_area = 'Labyrinth airlock area',
 
is_labyrinth_airlock_area = 'Labyrinth airlock area',
Line 97: Line 63:
 
id = 'Id',
 
id = 'Id',
 
act = 'Act',
 
act = 'Act',
area_level = 'Area level',
+
area_level = m_util.html.abbr('Area level', 'May be overriden on areas created by items such as maps'),
 
level_restriction_max = m_util.html.abbr('Max level', 'Characters above this level can not enter this zone.'),
 
level_restriction_max = m_util.html.abbr('Max level', 'Characters above this level can not enter this zone.'),
 
area_type_tags = 'Area type tags',
 
area_type_tags = 'Area type tags',
Line 104: Line 70:
 
connections = 'Connections',
 
connections = 'Connections',
 
-- monsters
 
-- monsters
-- boss monsters
+
boss_monster_ids = 'Bosses',
vaal_areas = 'Vaal Areas',
+
vaal_areas = 'Vaal Side Areas',
  +
vaal_spawn_areas = 'Spawnable in'
 
},
 
},
 
}
 
}
Line 139: Line 106:
 
function util.display.single_area(tpl_args, frame, area_id)
 
function util.display.single_area(tpl_args, frame, 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 194: Line 161:
 
'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 224: Line 190:
 
end
 
end
 
end
 
end
return page
+
return value
 
end
 
end
 
},
 
},
Line 257: Line 223:
 
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 = m_util.cast.factory.assoc_table('area_type_tags', {
 
tbl = m_game.constants.tags,
 
tbl = m_game.constants.tags,
 
errmsg = i18n.errors.invalid_tag,
 
errmsg = i18n.errors.invalid_tag,
Line 266: Line 232:
 
field = 'tags',
 
field = 'tags',
 
type = 'List (,) of String',
 
type = 'List (,) of String',
func = m_util.cast.factory.assoc_table(i18n.args.tags, {
+
func = m_util.cast.factory.assoc_table('tags', {
 
tbl = m_game.constants.tags,
 
tbl = m_game.constants.tags,
 
errmsg = i18n.errors.invalid_tag,
 
errmsg = i18n.errors.invalid_tag,
Line 299: Line 265:
 
return
 
return
 
end
 
end
tpl_args.mods = m_util.cargo.array_query{
+
tpl_args.mods = m_cargo.array_query{
 
tables={'mods'},
 
tables={'mods'},
 
fields={'mods.stat_text'},
 
fields={'mods.stat_text'},
Line 334: Line 300:
 
field = 'boss_monster_ids',
 
field = 'boss_monster_ids',
 
type = 'List (,) of String',
 
type = 'List (,) of String',
  +
func = function(tpl_args, frame, 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 = {},
 
},
 
},
Line 350: Line 347:
 
screenshot_ext = {
 
screenshot_ext = {
 
func = nil,
 
func = nil,
 
type = 'String',
 
default = 'jpg',
 
default = 'jpg',
 
},
 
},
Line 389: Line 387:
 
strongbox_rarity_weight = {
 
strongbox_rarity_weight = {
 
func = function (tpl_args, frame)
 
func = function (tpl_args, frame)
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
 
-- will be read later
 
-- will be read later
tpl_args.strongbox_rarity_weight[data.long_lower] = value
+
tpl_args[string.format('strongbox_weight_%s', rarity)] = value
 
end
 
end
 
end,
 
end,
Line 441: Line 437:
 
is_vaal_area = {
 
is_vaal_area = {
 
field = 'is_vaal_area',
 
field = 'is_vaal_area',
type = 'Boolean',
 
default = false,
 
},
 
is_master_daily_area = {
 
field = 'is_is_master_daily_area',
 
 
type = 'Boolean',
 
type = 'Boolean',
 
default = false,
 
default = false,
Line 530: Line 521:
 
end
 
end
 
 
local results=m_util.cargo.array_query{
+
local results=m_cargo.array_query{
 
tables={'areas'},
 
tables={'areas'},
 
fields={'areas._pageName', 'areas.name', 'areas.main_page'},
 
fields={'areas._pageName', 'areas.name', 'areas.main_page'},
Line 540: Line 531:
 
for _, data in ipairs(results) do
 
for _, data in ipairs(results) do
 
areas[data['areas.id']] = data
 
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
 
end
 
 
Line 554: Line 570:
 
'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 595: Line 610:
 
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, frame)
  +
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 653: Line 684:
 
func = function(tpl_args, frame)
 
func = function(tpl_args, frame)
 
return util.display.multiple_areas(tpl_args, frame, tpl_args.vaal_area_ids)
 
return util.display.multiple_areas(tpl_args, frame, 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, frame)
  +
return util.display.multiple_areas(tpl_args, frame, tpl_args.vaal_spawn_areas)
 
end,
 
end,
 
},
 
},
Line 901: Line 943:
 
local p = {}
 
local p = {}
   
p.table_areas = m_util.cargo.declare_factory{data=argument_map}
+
p.table_areas = m_cargo.declare_factory{data=argument_map}
   
 
function p.area(frame)
 
function p.area(frame)
Line 950: Line 992:
 
strongbox_spawn_chance = '30',
 
strongbox_spawn_chance = '30',
 
strongbox_max = '2', strongbox_rarity_weight = '50, 50, 50, 1',
 
strongbox_max = '2', strongbox_rarity_weight = '50, 50, 50, 1',
flavour_text = 'Bones of betrayal, ashes of purity.', main_page = 'The Dried Lake'}
+
flavour_text = 'Bones of betrayal, ashes of purity.', main_page = 'The Dried Lake'
  +
}
 
]]
 
]]
 
 
Line 990: Line 1,033:
 
 
 
-- Set all semantic properties:
 
-- Set all semantic properties:
mw.logObject('Cargo:' .. m_util.cargo.store(frame, cargo_values))
+
mw.logObject('Cargo:' .. m_cargo.store(frame, 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, frame)
Line 1,017: Line 1,060:
 
tpl_args.cats = m_util.cast.boolean(tpl_args.cats)
 
tpl_args.cats = m_util.cast.boolean(tpl_args.cats)
 
 
local results = m_util.cargo.query(
+
local results = m_cargo.query(
 
{'areas'},
 
{'areas'},
 
{'areas.infobox_html', 'areas.mainpage_categories'},
 
{'areas.infobox_html', 'areas.mainpage_categories'},

Latest revision as of 05:53, 12 June 2021

Template info icon Module documentation[view] [edit] [history] [purge]

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

-- SMW powered area module

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

-- ----------------------------------------------------------------------------
-- Imports
-- ----------------------------------------------------------------------------

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


-- ----------------------------------------------------------------------------
-- Localization
-- ----------------------------------------------------------------------------

-- Strings
local i18n = {
    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]]',
        screenshot = 'File:%s area screenshot.%s',
        screenshot_infobox = '[[File:%s area screenshot.%s|250px]]',
    },
    
    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',
    },
    intro = {
        text_with_name = "'''%s''' is the internal id for the [[%s|%s]] [[area]]. ",
        text_without_name = "'''%s''' is the internal id of an unnamed [[area]]. ",
        connections = 'It is connected to the following areas:',
    },
    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 side 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 = m_util.html.abbr('Area level', 'May be overriden on areas created by items such as maps'),
        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_monster_ids = 'Bosses',
        vaal_areas = 'Vaal Side Areas',
        vaal_spawn_areas = 'Spawnable in'
    },
}

-- ----------------------------------------------------------------------------
-- Constats
-- ----------------------------------------------------------------------------

local c = {}

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

local factory = {}

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

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

function util.display.single_area(tpl_args, frame, 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_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, frame, 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 = m_util.cast.factory.assoc_table('area_type_tags', {
                tbl = m_game.constants.tags,
                errmsg = i18n.errors.invalid_tag,
                key_out = 'area_type_tags',
            }),
        },
        tags = {
            field = 'tags',
            type = 'List (,) of String',
            func = m_util.cast.factory.assoc_table('tags', {
                tbl = m_game.constants.tags,
                errmsg = i18n.errors.invalid_tag,
                key_out = 'tags',
            }),
        },
        loading_screen = {
            field = 'loading_screen',
            type = 'Page',
            func = function (tpl_args, frame, loading_id)
                if loading_id ~= nil then
                    tpl_args.loading_screen_infobox = string.format(i18n.images.loading_screen_infobox, loading_id)
                    
                    return string.format(i18n.images.loading_screen, loading_id)
                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, frame, 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, frame)
                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, frame, 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, frame, screenshot_id)
                if tpl_args.name ~= nil then
                    local name = screenshot_id 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, frame)
                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_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, frame, 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, frame, 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, frame)
            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, frame)
            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, frame)
            return table.concat(tpl_args.area_type_tags, ', ')
        end,
    },
    {
        args = {
            tags = {
            },
        },
        header = i18n.headers.tags,
        func = function(tpl_args, frame)
            return table.concat(tpl_args.tags, ', ')
        end,
    },
    {
        args = {
            entry_text = {},
            entry_npc = {},
        },
        header = i18n.headers.entry_messsage,
        func = function(tpl_args, frame)
            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, frame)
            return util.display.single_area(tpl_args, frame, tpl_args.parent_area)
        end,
    },
    {
        args = {
            connection_ids = {},
        },
        header = i18n.headers.connections,
        func = function(tpl_args, frame)
            return util.display.multiple_areas(tpl_args, frame, tpl_args.connection_ids)
        end,
    },
    {
        args = {
            vaal_area_ids = {},
        },
        header = i18n.headers.vaal_areas,
        func = function(tpl_args, frame)
            return util.display.multiple_areas(tpl_args, frame, 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, frame)
            return util.display.multiple_areas(tpl_args, frame, tpl_args.vaal_spawn_areas)
        end,
    },
}

display.list_map = {
    {
        args = {
            flavour_text = {},
        },
        func = function(tpl_args, frame)
            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, frame)
            return m_util.html.poe_color('mod', tpl_args.stat_text)
        end,
    },
}

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

local d = {}

function d.intro_text(tpl_args, frame)
    --[[
    Display an introductory text about the area.
    ]]
    local out = {}
    if mw.ustring.find(tpl_args['id'], '_') then
        out[#out+1] = frame: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, frame, 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, frame)
    --[[
    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, frame, data) then
            tbl
                :tag('tr')
                    :tag('th')
                        :wikitext(data.header or '')
                        :done()
                    :tag('td')
                        :wikitext(data.func(tpl_args, frame) 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, frame, data) then
            infocard_args[i] = data.func(tpl_args, frame)
            i = i + 1
        end
    end

    return f_infocard(infocard_args)
end

function d.subobject_box(tpl_args, frame)
    --[[
    Display the subobject box.
    ]]
    local container = mw.html.create('div')
    container
        :attr('class', 'modbox floatright')
        
    -- spawn weight table 
    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

-- ----------------------------------------------------------------------------
-- Page functions
-- ----------------------------------------------------------------------------

local p = {}

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

function p.area(frame)
    --[[
    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'
    }
    ]]
    
    
    local tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = m_util.misc.get_frame(frame)
    
    --
    -- Shared args
    --
    -- Handle release_version and removal_version
    m_util.args.version(tpl_args, {frame=frame})
    
    local cargo_values = m_util.args.from_cargo_map{
        tpl_args=tpl_args,
        frame=frame,
        table_map=argument_map,
        rtr=true,
    }
    
    -- Parse spawn weights
    m_util.args.spawn_weight_list(tpl_args, {
        frame=frame, 
    })
    
    -- Category handling for the local data page:
    local page_cats = {
        'Area data',
    }
    
    -- Display only on main pages:
    local out = {}
    out[#out+1] = d.area_box(tpl_args, frame)
    
    -- 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(frame, cargo_values))
    -- mw.logObject(tpl_args)
    -- Display only on data page:
    out[#out+1] = d.subobject_box(tpl_args, frame)
    out[#out+1] = d.intro_text(tpl_args, frame)
    
    -- Output of function
    return table.concat(out) .. m_util.misc.add_category(page_cats)
end

function p.query_area_info(frame)
    --[[
    Queries and displays the area infobox. 
    
    ]]
    -- = p.query_area_info{conditions = '[[Is area id::test]]', cats='yes'}
    local tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = m_util.misc.get_frame(frame)
    
    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

return p