Module:Sandbox/Illviljan/Area

From Path of Exile Wiki
Revision as of 13:10, 16 September 2017 by >Illviljan (Removed id texts from connecting areas, the id now links to the area data instead. Hopefully fixed categories to show on more areas than just story areas.)
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 = {
        id = 'Id',
        act = 'Act',
        area_level = 'Area level',
        level_restriction_max = m_util.html.abbr('Max level', 'Characters above this level can not enter this zone.'),
        area_type_tags = 'Area type tags',
        tags = 'Tags',
        parent_area = m_util.html.abbr('Parent Town', 'Portals will lead to the parent town'),
        connections = 'Connections',
        -- monsters
        -- boss monsters
        vaal_areas = 'Vaal Areas',
    },
}

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

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

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

local p = {}

function p.area(frame)
    -- = p.area{id = '1_1_1', name = 'The Twilight Strand', act = 1, level = 1, tags = 'no_tempests, area_with_water', loading_screen = 'Act1', connection_ids = '1_1_town',parent_area_id = '1_1_town',  boss_monster_ids = 'Metadata/Monsters/ZombieBoss/ZombieBossHillockNormal', flavour_text = 'Hope was drowned here.', main_page = 'The Twilight Strand (Act 1)'}
    -- = p.area{id = 'test', name = 'The Twilight Testing Strand', act = 1, level = 1, tags = 'no_tempests', loading_screen = 'Act1', connection_ids = '1_1_town',parent_area_id = '1_1_town',  boss_monster_ids = 'Metadata/Monsters/ZombieBoss/ZombieBossHillockNormal', flavour_text = 'Tests were drowned here.', main_page = 'The Twilight Strand (Act 1)'}
    local tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = m_util.misc.get_frame(frame)
    
    --
    -- Shared args
    --

    tpl_args._properties = {}
    
    -- handle release_version and removal_version
    m_util.args.version(tpl_args, {frame=frame, set_properties=true})
    
    -- 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
    -- Areas, Act X/Map areas, Unique map areas 
    local cats = {
        'Areas', 
    }
    local cats_ini_len = #cats
    for _, key in ipairs(display.area_type) do
        if tpl_args[key] == true then
            cats[#cats+1] = i18n.tooltips[key]
        end        
    end
    if #cats == cats_ini_len then 
        cats[#cats+1] = string.format('Act %s', 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)
    -- 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)
    
    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