Module:Area

From Path of Exile 2 Wiki
Revision as of 16:52, 12 August 2017 by >OmegaK2 (Fixed error)
Jump to navigation Jump to search
Module documentation[create] [purge]
-- SMW powered area module

-- ----------------------------------------------------------------------------
-- TODO
-- ----------------------------------------------------------------------------
-- invalid tags -> utl?
-- spawn_weight* values
-- spawnchacne values
-- 
-- i18n for properties

-- ----------------------------------------------------------------------------
-- 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

-- ----------------------------------------------------------------------------
-- 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]]',
    },

    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 = {
        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',
    },
}

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

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

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

local factory = {}
function factory.arg_list(k, args)
    return function (tpl_args, frame)
        if tpl_args[k] ~= nil then
            tpl_args[k] = m_util.string.split(tpl_args[k], ', ')
        end
    end
end


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
        local name
        if tpl_args.areas[area_id]['Has main page'] ~= '' then
            name = string.format('[[%s|%s]]', tpl_args.areas[area_id]['Has main page'], tpl_args.areas[area_id]['Has name'])
        else
            name = tpl_args.areas[area_id]['Has name']
        end
        return string.format('%s ([[%s|%s]])', name, tpl_args.areas[area_id][1], area_id)
    else
        return area_id
    end
end

-- ----------------------------------------------------------------------------
-- Argument & display mapping
-- ----------------------------------------------------------------------------

local argument_map = {
    --
    -- User supplied arguments
    -- 
    main_page = {
        property = 'Has main page',
        func = function(tpl_args, frame)
            local page = tpl_args.main_page 
            if page ~= nil then
                page = mw.title.new(tpl_args.main_page)
                if page == nil then
                    error(string.format(i18n.errors.main_page_is_invalid, tpl_args.main_page))
                elseif not page.exists then
                    error(string.format(i18n.errors.main_page_does_not_exist, tpl_args.main_page))
                else
                    -- dont need the title object anymore
                    --tpl_args.main_page = page
                end
            end
        end
    },
    
    --
    -- Can be populated by PyPoE
    --
    id = {
        property = 'Is area id',
        func = nil,
    },
    name = {
        property = 'Has name',
        func = nil,
    },
    act = {
        property = 'Has act',
        func = m_util.cast.factory.number(i18n.args.act, {key_out='act'}),
    },
    area_level = {
        property = 'Has area level',
        func = m_util.cast.factory.number(i18n.args.area_level, {key_out='area_level'}),
    },
    level_restriction_max = {
        property = 'Has maximum level restriction',
        func = m_util.cast.factory.number(i18n.args.level_restriction_max, {key_out='level_restriction_max'}),
        default = 100,
    },
    area_type_tags = {
        property = 'Has area type tags',
        func = m_util.cast.factory.assoc_table(i18n.args.area_type_tags, {
            tbl = m_game.constants.tags,
            errmsg = i18n.errors.invalid_tag,
            key_out = 'area_type_tags',
        }),
    },
    tags = {
        property = 'Has tags',
        func = m_util.cast.factory.assoc_table(i18n.args.tags, {
            tbl = m_game.constants.tags,
            errmsg = i18n.errors.invalid_tag,
            key_out = 'tags',
        }),
    },
    loading_screen = {
        property = 'Has loading screen image',
        func = function (tpl_args, frame)
            local loading_id = tpl_args[i18n.args.loading_screen]
            if loading_id ~= nil then
                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
        end,
    },
    connection_ids = {
        property = 'Has area connection ids',
        func = factory.arg_list(i18n.args.connection_ids, {key_out='connection_ids'}),
        default = {},
    },
    parent_area_id = {
        property = 'Has parent area id',
    },
    modifier_ids = {
        property = 'Has mod ids',
        func = factory.arg_list(i18n.args.modifier_ids, {key_out='modifier_ids'}),
        default = {},
    },
    monster_ids = {
        property = 'Has monster ids',
        func = factory.arg_list(i18n.args.monster_ids, {key_out='monster_ids'}),
        default = {},
    },
    boss_monster_ids = {
        property = 'Has boss monster ids',
        func = factory.arg_list(i18n.args.boss_monster_ids, {key_out='boss_monster_ids'}),
        default = {},
    },
    entry_text = {
        property = 'Has entry text message',
    },
    entry_npc = {
        property = 'Has entry npc',
    },
    flavour_text = {
        property = 'Has flavour text',
    },
    screenshot_ext = {
        property = nil,
        func = nil,
        default = 'jpg',
    },
    screenshot = {
        property = 'Has screenshot',
        func = function(tpl_args, frame)
            if tpl_args.name ~= nil then
                tpl_args.screenshot = string.format(i18n.images.screenshot, tpl_args.name, tpl_args.screenshot_ext)
                tpl_args.screenshot_infobox = string.format(i18n.images.screenshot_infobox, tpl_args.name, tpl_args.screenshot_ext)
            end
        end,
    },
    --
    -- Spawn chances
    --
    vaal_area_ids = {
        property = 'Has vaal area ids',
        func = factory.arg_list(i18n.args.vaal_area_ids, {key_out='vaal_area_ids'}),
        default = {},
    },
    vaal_area_spawn_chance = {
        property = 'Has vaal area spawn chance',
        func = m_util.cast.factory.number(i18n.args.vaal_area_spawn_chance, {key_out='vaal_area_spawn_chance'}),
        default = 0,
    },
    strongbox_spawn_chance = {
        property = 'Has strongbox spawn chance',
        func = m_util.cast.factory.number(i18n.args.strongbox_spawn_chance, {key_out='strongbox_spawn_chance'}),
        default = 0,
    },
    strongbox_max_count = {
        property = 'Has maximum number of strongboxes',
        func = m_util.cast.factory.number(i18n.args.strongbox_max_count, {key_out='strongbox_max_count'}),
        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
            
            local i = 1
            for k, _ in pairs(query_ids) do
                if type(k) ~= 'number' then
                    query_ids[i] = k
                    i = i + 1
                end
            end
            
            if #query_ids == 0 then
                tpl_args.areas = {}
            else
                tpl_args.areas = m_util.smw.array_query{
                    frame=frame,
                    property='Is area id',
                    id_array=query_ids,
                    query={
                        '?Is area id',
                        '?Has name',
                        '?Has main page',
                    },
                }
            end
            -- TODO: Error/Warning for missing 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',
    'flavour_text',
    'screenshot_ext',
    'screenshot',
    'vaal_area_spawn_chance',
    'vaal_area_ids',
    'strongbox_spawn_chance',
    'strongbox_max_count',
    'strongbox_rarity_weight',
    'is_map_area',
    'is_unique_map_area',
    'is_town_area',
    'is_hideout_area',
    'is_vaal_area',
    'is_master_daily_area',
    'is_labyrinth_area',
    'is_labyrinth_airlock_area',
    'is_labyrinth_boss_area',
    'has_waypoint',
    -- Non argument, but passed to the script
    'areas',
}

local display  = {}
display.area_type = {
    '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',
}

display.table_map = {
    {
        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 = {
            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 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,
    },
}

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'),
    },
}

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

local d = {}

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)
    local infocard_args = {}
    
    -- Top header
    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
    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

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

local p = {}

function p.area(frame)
    local tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = m_util.misc.get_frame(frame)
    
    --
    -- Shared args
    --

    tpl_args._properties = {}
    
    -- parse args
    for _, k in ipairs(argument_order) do
        local data = argument_map[k]
        if data == nil then
            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
    m_util.args.weight_list(tpl_args, {
        frame=frame, 
        output_argument='spawn_weights',
    })
    
    -- Category handling for main page
    local cats = {}
    local act_cat = true
    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
    local out = d.area_box(tpl_args, frame)
    
    tpl_args._properties['Has infobox HTML'] = out
    
    -- set semantic properties
    m_util.smw.set(frame, tpl_args._properties)
    
    local page_cats = {
        'Area data',
    }
    
    -- display
    return m_util.misc.add_category(page_cats) .. out
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.sort = tpl_args.sort or 'Is area id'
    
    local results = m_util.smw.query({
        tpl_args.conditions,
        '?Has infobox HTML#',
        '?Has main page categories#',
        sort=tpl_args.sort,
    }, frame)
    
    local out = {}
    local cats = {}
    
    for _, row in ipairs(results) do
        out[#out+1] = row['Has infobox HTML']
        if row['Has main page categories'] ~= '' then
            for _, cat in ipairs(m_util.string.split(row['Has main page categories'], '<MANY>')) do
                cats[string.format('[[%s]]', cat)] = true
            end
        end
    end
    if tpl_args.cats then
        for key, value in pairs(cats) do
            cats[#cats+1] = key
        end
        return table.concat(cats) .. table.concat(out)
    else
        return table.concat(out)
    end
end

return p