Path of Exile Wiki

Wiki поддерживается сообществом, поэтому подумайте над тем, чтобы внести свой вклад.

ПОДРОБНЕЕ

Path of Exile Wiki
(Attempt to define monster rarity. Added monster skill links. Added monster screenshot. Moved stuff around in the infobox to resemble the old infobox more. Show monster type id. Check for bosses.)
Нет описания правки
Строка 47: Строка 47:
 
 
 
intro = {
 
intro = {
text_with_name = "'''%s''' is the internal id for the [[%s|%s]] [[monster]]. ",
+
text_with_name = "'''%s''' это внутренний идентификатор [[монстр]]а [[%s|%s]]. ",
text_without_name = "'''%s''' is the internal id of an unnamed [[monster]]. ",
+
text_without_name = "'''%s''' это внутренний идентификатор безымянного [[монстр]]а. ",
 
},
 
},
 
 

Версия от 20:00, 13 октября 2019

-- ----------------------------------------------------------------------------
-- Imports
-- ----------------------------------------------------------------------------
local m_cargo = require('Module:Cargo')
local getArgs = require('Module:Arguments').getArgs
local m_util = require('Module:Util')
local m_game = require('Module:Game')
local f_skill_link = require('Module:Skill link').skill_link

local p = {}

-- ----------------------------------------------------------------------------
-- Strings
-- ----------------------------------------------------------------------------

local i18n = {
    cats = {
        data = 'Monster data'
    },
    tooltips = {
        name = 'Название',
        rarity = 'Редкость',
        experience_multiplier = 'Базовый множитель опыта', 
        health_multiplier = 'Базовый множитель здоровья',
        damage_multiplier = 'Базовый множитель урона',
        attack_speed = 'Базовая скорость атаки',
        critical_strike_chance = 'Базовый шанс критического удара',
        minimum_attack_distance = 'Минимальное расстояние атаки',
        maximum_attack_distance = 'Максимальное расстояние атаки',
        difficulty = 'Акт',
        resistances = 'Сопротивления',
        part1 = '[[Акт 1|1]]-[[Акт 5|5]]',
        part2 = '[[Акт 5|5]]-[[Акт 10|10]]',
        maps = '[[Акт 10|10]]-',
        fire = m_game.constants.damage_types.fire.short_upper,
        cold = m_game.constants.damage_types.cold.short_upper,
        lightning = m_game.constants.damage_types.lightning.short_upper,
        chaos = m_game.constants.damage_types.chaos.short_upper,
        stat_text = 'Свойства',
        size = 'Размер',
        model_size_multiplier = 'Множитель размера модели',
        tags = 'Внутренние метки',
        metadata_id = 'Metadata id',
        monster_type_id = 'Monster type id',
        areas = 'Области',
    },
    
    intro = {
        text_with_name = "'''%s''' — это внутренний идентификатор [[монстр]]а [[%s|%s]]. ",
        text_without_name = "'''%s''' — это внутренний идентификатор безымянного [[монстр]]а. ",
    },
    
    errors = {
        invalid_rarity_id = 'The rarity id "%s" is invalid. Acceptable values are "normal", "magic", "rare" and "unique".',
    },
}
-- ----------------------------------------------------------------------------
-- Helpers
-- ----------------------------------------------------------------------------
local h = {}

function h.add_mod_id(tpl_args, frame, value)
    for _, id in ipairs(value or {}) do
        if tpl_args._mods[id] == nil then
            tpl_args._mods[id] = true
            tpl_args._mods[#tpl_args._mods+1] = id
        end
    end
    
    return value
end

function h.stat_calc(stats)
    --[[
    Calculates a modified stat.
    
    Parameters
    ----------
    stats : List
        Associated List with added, increased and more 
        as keys.
        
    Examples
    --------
    stats = {
        added={6,4},
        increased={100, 50}, -- [%]
        more={20, 30}, -- [%]
    }
    = h.stat_calc(stats)
    
    ]]
    local funcs = {
        added=function(stats)
            --[[
            Sum the added terms.            
            ]]
            local out = 0 
            for _, v in ipairs(stats['added']) do
                out = v + out
            end
            return out
        end,
        increased=function(stats)
            --[[
            Sum the increased terms. 
            Values should be in percent.
            ]]
            local out = 1
            for _, v in ipairs(stats['increased']) do
                out = v/100 + out
            end
            return out
        end,
        more=function(stats)
            --[[
            Calculate the product of the more terms. 
            Values should be in percent.
            ]]
            local out = 1
            for _, v in ipairs(stats['more']) do
                out = (1 + v/100) * out
            end
            return out
        end,
    }
    
    local out = 1
    for k, v in pairs(stats) do
        if type(funcs[k]) == 'function' then
            out = funcs[k](stats) * out
        else
            out = v * out
        end
    end
    
    return out
end

h.stat_calc_verbose = function(stats)
    --[[
    Show how the stats list is calculated.
    ]]
    local verbose_funcs = {
        added = function(stats)
            local st = {}
            for _, v in ipairs(stats['added']) do
                st[#st+1] = v/1
            end 
            return string.format('(%s)', table.concat(st, ' + '))
        end,
        increased = function(stats)
            local st = {}
            for _, v in ipairs(stats['increased']) do
                st[#st+1] = v/100 
            end 
            return string.format('(1 + %s)', table.concat(st, ' + '))
        end,
        more = function(stats)
            local st = {}
            for _, v in ipairs(stats['more']) do
                st[#st+1] = string.format('(1 + %s)', v/100) 
            end 
            return string.format('(%s)', table.concat(st, ' * '))
        end,
    }
    
    local out = {}
    for k, v in pairs(stats) do
        if type(verbose_funcs[k]) == 'function' then
            out[#out+1] = verbose_funcs[k](stats)
        else
            out[#out+1] = string.format('%s', v)
        end
    end
    
    return table.concat(out, ' * ')
end

function h.intro_text(tpl_args, frame)
    --[[
    Display an introductory text about the monster.
    ]]
    local out = {}
    if mw.ustring.find(tpl_args['metadata_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['metadata_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['metadata_id']
        )
    end 
    
    return table.concat(out)
end


-- ----------------------------------------------------------------------------
-- Tables
-- ----------------------------------------------------------------------------
local tables = {}

tables.monsters = {
    table = 'monsters',
    order = {'metadata_id', 'tags', 'monster_type_id', 'mod_ids', 'part1_mod_ids', 'part2_mod_ids', 'endgame_mod_ids', 'skill_ids', 'name', 'size', 'minimum_attack_distance', 'maximum_attack_distance', 'model_size_multiplier', 'experience_multiplier', 'damage_multiplier', 'health_multiplier', 'critical_strike_chance', 'attack_speed', 'mods', 'is_boss', 'rarity_id', 'rarity'},
    fields = {
        metadata_id = {
            field = 'metadata_id',
            type = 'String',
            func = function (tpl_args, frame, value)
                tpl_args.monster_usages = m_cargo.query(
                    {'areas', 'maps'},
                    {             
                        'areas.name',
                        'areas.id',
                        'areas.area_level',
                        'areas.boss_monster_ids',
                        'areas.main_page',
                        'areas._pageName',
                        'maps.area_level',
                    },
                    {
                        join='areas.id=maps.area_id',
                        where=string.format('areas.boss_monster_ids HOLDS "%s"', value), 
                    }
                )
                
                return value
            end,
        },
        monster_type_id = {
            field = 'monster_type_id',
            type = 'String',
            func = function (tpl_args, frame, value)
                tpl_args.monster_type = m_cargo.query(
                    {'monster_types', 'monster_resistances'},
                    {
                        'monster_types.tags',
                        'monster_types.armour_multiplier',
                        'monster_types.evasion_multiplier',
                        'monster_types.energy_shield_multiplier',
                        'monster_types.damage_spread',
                        'monster_resistances.part1_fire', 
                        'monster_resistances.part1_cold', 
                        'monster_resistances.part1_lightning', 
                        'monster_resistances.part1_chaos', 
                        'monster_resistances.part2_fire', 
                        'monster_resistances.part2_cold', 
                        'monster_resistances.part2_lightning', 
                        'monster_resistances.part2_chaos', 
                        'monster_resistances.maps_fire', 
                        'monster_resistances.maps_cold', 
                        'monster_resistances.maps_lightning', 
                        'monster_resistances.maps_chaos'
                    },
                    {
                        join='monster_types.monster_resistance_id = monster_resistances.id',
                        where=string.format('monster_types.id = "%s"', value), 
                    }
                )[1]
                
                if tpl_args.monster_type['monster_types.tags'] then
                    local tags = m_util.string.split(
                        tpl_args.monster_type['monster_types.tags'], 
                        ',%s+'
                    )
                    -- TODO: Maybe this can be fixed earlier?
                    if tpl_args.tags == nil then 
                        tpl_args.tags = {}
                    end 
                    for _, tag in ipairs(tags) do
                        tpl_args.tags[#tpl_args.tags+1] = tag
                    end
                end
                
                return value
            end,
        },
        mod_ids = {
            field = 'mod_ids',
            type = 'List (,) of String',
            func = h.add_mod_id,
        },
        part1_mod_ids = {
            field = 'part1_mod_ids',
            type = 'List (,) of String',
            func = h.add_mod_id,
        },
        part2_mod_ids = {
            field = 'part2_mod_ids',
            type = 'List (,) of String',
            func = h.add_mod_id,
        },
        endgame_mod_ids = {
            field = 'endgame_mod_ids',
            type = 'List (,) of String',
            func = h.add_mod_id,
        },
        tags = {
            field = 'tags',
            type = 'List (,) of String',
        },
        skill_ids = {
            field = 'skill_ids',
            type = 'List (,) of String',
            --TODO
        },
        -- add base type info or just parse it?
        name = {
            field = 'name',
            type = 'String',
        },
        size = {
            field = 'size',
            type = 'Integer',
        },
        minimum_attack_distance = {
            field = 'minimum_attack_distance',
            type = 'Integer',
        },
        maximum_attack_distance = {
            field = 'maximum_attack_distance',
            type = 'Integer',
        },
        model_size_multiplier = {
            field = 'model_size_multiplier',
            type = 'Float',
        },
        experience_multiplier = {
            field = 'experience_multiplier',
            type = 'Float',
        },
        damage_multiplier = {
            field = 'damage_multiplier',
            type = 'Float',
        },
        health_multiplier = {
            field = 'health_multiplier',
            type = 'Float',
        },
        critical_strike_chance = {
            field = 'critical_strike_chance',
            type = 'Float',
        },
        attack_speed = {
            field = 'attack_speed',
            type = 'Float',
        },
        is_boss = {
            field = 'is_boss',
            type = 'Boolean',
            func = function (tpl_args, frame)
                -- If the monster is used in some area it's most likely
                -- an unique boss:
                if #tpl_args.monster_usages > 0 then
                    tpl_args.is_boss = true
                else
                    tpl_args.is_boss = false
                end
                return tpl_args.is_boss
            end,
        },
        rarity_id = {
            field = 'rarity_id',
            type = 'String',
            func = function (tpl_args, frame)
                --[[
                    Define the rarity of the monster. There's no obvious 
                    parameter that can be datamined for this so this will 
                    be mostly guess work.
                ]]
                
                -- User defined rarity takes priority:
                if tpl_args.rarity_id ~= nil then 
                    if m_game.constants.rarities[tpl_args.rarity_id] == nil then
                        error(string.format(i18n.errors.invalid_rarity_id, 
                                            tostring(tpl_args.rarity_id)))
                    end
                    return tpl_args.rarity_id
                end
            
                -- If the monster is used in some area it's most likely
                -- an unique boss:
                if tpl_args.is_boss then
                    tpl_args.rarity_id = 'unique'
                    return tpl_args.rarity_id
                end
                
                -- If there are no mods it's probably a normal monster:
                if #tpl_args._mods == 0 then
                    tpl_args.rarity_id = 'normal'
                    return tpl_args.rarity_id
                end 
                
                -- Try to determine rarity from mods:
                for _, modid in ipairs(tpl_args._mods) do
                    local mod = tpl_args._mod_data[modid]
                    -- Check if the mod contains the monster rarity stat: 
                    for _, v in ipairs(mod) do 
                        if v['mod_stats.id'] == 'monster_rarity' then
                            -- TODO: m_game rarity id does not match the stat:
                            local int_id = tonumber(v['mod_stats.max']) + 1 
                            for k, row in pairs(m_game.constants.rarities) do
                                if int_id == row['id'] then 
                                    tpl_args.rarity_id = k
                                    return tpl_args.rarity_id
                                end
                            end
                        end
                    end
                end
                
                -- If none of the mods contains the monster rarity 
                -- stat then it might be an unique:
                if tpl_args.rarity_id == nil then 
                    tpl_args.rarity_id = 'unique'
                    return tpl_args.rarity_id
                end
            end,
        },
        rarity = {
            field = 'rarity',
            type = 'String',
            func = function (tpl_args, frame)
                return m_game.constants.rarities[tpl_args.rarity_id]['full']
            end
        },
    
        --
        -- Processing fields
        --
        mods = {
            func = function (tpl_args, frame)
                
                -- Format the mod ids for cargo queries:
                local mlist = {}
                for _, key in ipairs(tpl_args._mods) do
                    mlist[#mlist+1] = string.format('"%s"', key)
                end
                if #mlist == 0 then
                    return 
                end 
                
                -- tpl_args._mods = m_cargo.array_query{
                    -- tables={'mods', 'mod_stats'},
                    -- fields={'mods.stat_text', 'mods.generation_type', 
                    --         'mod_stats.id', 'mod_stats.max'},
                    -- id_field='mods.id',
                    -- query={join='mods._pageID=mod_stats._pageID'},
                    -- id_array=mlist,
                -- }
                
                tpl_args._mod_data = m_cargo.map_results_to_id{
                    results=m_cargo.query(
                        {
                            'mods', 
                            'mod_stats',
                        },
                        {
                            'mods.id',
                            'mods.stat_text',
                            'mods.generation_type',
                            'mod_stats.id',
                            'mod_stats.max',
                        },
                        {
                            join=[[
                                mods._pageID=mod_stats._pageID
                            ]],
                            where=string.format([[
                                mods.id IN (%s)
                            ]], table.concat(mlist, ',')),
                        }
                    ),
                    field='mods.id',
                    keep_id_field=false,
                }
            end,
        },
    }
}

tables.monster_types = {
    table = 'monster_types',
    order = {'id', 'tags', 'monster_resistance_id'},
    fields = {
        id = {
            field = 'id',
            type = 'String',
        },
        tags = {
            field = 'tags',
            type = 'List (,) of String',
        },
        monster_resistance_id = {
            field = 'monster_resistance_id',
            type = 'String',
        },
        armour_multiplier = {
            field = 'armour_multiplier',
            type = 'Float',
        },
        evasion_multiplier = {
            field = 'evasion_multiplier',
            type = 'Float',
        },
        energy_shield_multiplier = {
            field = 'energy_shield_multiplier',
            type = 'Float',
        },
        damage_spread = {
            field = 'damage_spread',
            type = 'Float',
        },
    }
}

tables.monster_resistances = {
    table = 'monster_resistances',
    order = {'id', 'part1_fire', 'part1_cold', 'part1_lightning', 
             'part1_chaos', 'part2_fire', 'part2_cold', 'part2_lightning', 
             'part2_chaos', 'maps_fire', 'maps_cold', 'maps_lightning', 
             'maps_chaos'},
    fields = {
        id = {
            field = 'id',
            type = 'String',
        },
        part1_fire = {
            field = 'part1_fire',
            type = 'Integer',
        },
        part1_cold = {
            field = 'part1_cold',
            type = 'Integer',
        },
        part1_lightning = {
            field = 'part1_lightning',
            type = 'Integer',
        },
        part1_chaos = {
            field = 'part1_chaos',
            type = 'Integer',
        },
        part2_fire = {
            field = 'part2_fire',
            type = 'Integer',
        },
        part2_cold = {
            field = 'part2_cold',
            type = 'Integer',
        },
        part2_lightning = {
            field = 'part2_lightning',
            type = 'Integer',
        },
        part2_chaos = {
            field = 'part2_chaos',
            type = 'Integer',
        },
        maps_fire = {
            field = 'maps_fire',
            type = 'Integer',
        },
        maps_cold = {
            field = 'maps_cold',
            type = 'Integer',
        },
        maps_lightning = {
            field = 'maps_lightning',
            type = 'Integer',
        },
        maps_chaos = {
            field = 'maps_chaos',
            type = 'Integer',
        },
    }
}

tables.monster_base_stats = {
    table = 'monster_base_stats',
    order = {'level', 'damage', 'evasion', 'accuracy', 'life', 'experience', 
             'summon_life'},
    fields = {
        level = {
            field = 'level',
            type = 'Integer',
        },
        damage = {
            field = 'damage',
            type = 'Float',
        },
        evasion = {
            field = 'evasion',
            type = 'Integer',
        },
        accuracy = {
            field = 'accuracy',
            type = 'Integer',
        },
        life = {
            field = 'life',
            type = 'Integer',
        },
        experience = {
            field = 'experience',
            type = 'Integer',
        },
        summon_life = {
            field = 'summon_life',
            type = 'Integer',
        },
        -- whole bunch of other values I have no clue about ...
    }
}

tables.monster_map_multipliers = {
    table = 'monster_map_multipliers',
    order = {'level', 'life', 'damage', 'boss_life', 'boss_damage', 
             'boss_item_rarity', 'boss_item_quantity'},
    fields = {
        level = {
            field = 'level',
            type = 'Integer',
        },
        life = {
            field = 'life',
            type = 'Integer',
        },
        damage = {
            field = 'damage',
            type = 'Integer',
        },
        boss_life = {
            field = 'boss_life',
            type = 'Integer',
        },
        boss_damage = {
            field = 'boss_damage',
            type = 'Integer',
        },
        boss_item_rarity = {
            field = 'boss_item_rarity',
            type = 'Integer',
        },
        boss_item_quantity = {
            field = 'boss_item_quantity',
            type = 'Integer',
        },
    }
}

tables.monster_life_scaling = {
    table = 'monster_life_scaling',
    order = {'level', 'magic', 'rare'},
    fields = {
        level = {
            field = 'level',
            type = 'Integer',
        },
        magic = {
            field = 'magic',
            type = 'Integer',
        },
        rare = {
            field = 'rare',
            type = 'Integer',
        },
    }
}

-- ----------------------------------------------------------------------------
-- Monster box sections
-- ----------------------------------------------------------------------------

local display = {}
function display.value (args)
    return function (tpl_args, frame)
        local v
        if args.sub then
            v = tpl_args[args.sub][args.arg]
        else
            v = tpl_args[args.arg]
        end
        
        if v and args.fmt then
            return string.format(args.fmt, v)
        else
            return v
       end
    end
end

function display.sub_value (args)
    return function (tpl_args, frame)
        return tpl_args[args.sub][args.arg]
    end
end

local tbl_view = {
    {
        -- header = i18n.tooltips.name,
        func = function (tpl_args, frame)
            if tpl_args.name == nil then 
                return
            end
            
            local linked_name = string.format(
                '[[Monster:%s|%s]]', 
                string.gsub(tpl_args.metadata_id, '_', '~'), 
                tpl_args.name
            )
            return m_util.html.poe_color(tpl_args.rarity_id, linked_name)
        end,
    },
    {
        -- header = i18n.tooltips.image,
        func = function (tpl_args, frame)
            return string.format(
                '[[File:%s monster screenshot.jpg|296x500px]]', 
                tpl_args.name or tpl_args.monster_type_id
            )
        end,
    },
    -- {
        -- header = i18n.tooltips.rarity,
        -- func = display.value{arg='rarity'},
    -- },
    {
        header = i18n.tooltips.areas,
        func = function(tpl_args, frame)
            local out = {}
            for i, v in ipairs(tpl_args.monster_usages) do 
                out[#out+1] = string.format(
                    '[[%s|%s]]', 
                    v['areas.main_page'] or v['areas._pageName'], 
                    v['areas.name'] or v['areas.id']
                )
            end
            
            return table.concat(out, ', ')
        end 
    },
    {
        header = i18n.tooltips.stat_text,
        func = function (tpl_args, frame)
            local out = {}
            for _, modid in ipairs(tpl_args._mods) do
                local mod = tpl_args._mod_data[modid] or {}
                local stat_text = {}
                
                -- Add stat_text for each modifier, ignore duplicates:
                for _, v in ipairs(mod) do 
                    if v['mods.stat_text'] then
                        if stat_text[v['mods.stat_text']] == nil then
                            stat_text[v['mods.stat_text']] = true
                            out[#out+1] = v['mods.stat_text']
                        end
                    end
                end
            end
            
            return table.concat(out, '<br>')
        end,
    },
    {
        header = 'Skills',
        func = function (tpl_args, frame)
            local out = {}
            for _, id in ipairs(tpl_args.skill_ids or {}) do
                out[#out+1] = f_skill_link{id=id}
                if string.find(out[#out], 'class="module%-error"') then 
                    out[#out] = id
                end 
            end
            
            return table.concat(out, '<br>')
        end,
    },
    {
        header = 'Life',
        func = function(tpl_args, frame)
            -- Should this be stored to cargo? 
            
            -- Get monster level from the area level unless it's been 
            -- user defined. 
            local monster_level = {}
            for _, v in ipairs(tpl_args.monster_usages) do
                local lvl = v['maps.area_level'] or v['areas.area_level']
                monster_level[#monster_level+1] = lvl
            end
            tpl_args.monster_level = tpl_args.monster_level or table.concat(monster_level, ',')
            
            -- Stop if no monster level was found:
            if tpl_args.monster_level == nil or tpl_args.monster_level == '' then
               return nil
            end
            
            tpl_args.monster_level = m_util.string.split(tpl_args.monster_level, ',') 
            
            local results = m_cargo.query(
                {
                    'monster_base_stats', 
                    'monster_life_scaling', 
                    'monster_map_multipliers', 
                    -- 'monster'
                },
                {
                    'monster_base_stats.level',
                    'monster_base_stats.life', 
                    'monster_life_scaling.magic', 
                    'monster_life_scaling.rare',
                    'monster_map_multipliers.life',
                    'monster_map_multipliers.boss_life',
                    -- 'monster.health_multiplier',
                },
                {
                    join=[[
                        monster_base_stats.level=monster_life_scaling.level,
                        monster_base_stats.level=monster_map_multipliers.level
                    ]],
                    where=string.format(
                        'monster_base_stats.level IN (%s)', 
                        table.concat(tpl_args.monster_level, ', ')
                    ),
                }
            )
            
            -- Add monster modifiers. Min-max range necessary? Do item2 
            -- have a smarter way to set mods and stats?
            local results2 = m_cargo.query(
                {
                    'mods', 
                    'mod_stats',
                },
                {
                    'mods.domain',
                    'mods.generation_type', 
                    'mods.mod_group',
                    'mods.id',
                    'mod_stats.id',
                    'mod_stats.max',
                    'mod_stats.min',
                },
                {
                    join='mods._pageID=mod_stats._pageID',
                    where=string.format([[
                            mods.domain = 3 
                        AND mods.generation_type = 3 
                        AND mods.id LIKE "Monster%s%%" 
                        AND mod_stats.id = "monster_life_+%%_final_from_rarity"
                        ]], 
                        tpl_args.rarity_id
                    ),
                }
            )
            local mods = results2[1] or {}
            
            local out = {}
            for i, result in ipairs(results) do
                local stats = {
                    added={
                        result['monster_base_stats.life'],
                    }
                    ,
                    increased={
                        result['monster_life_scaling.' .. tpl_args.rarity_id] or 0,
                    },
                    more={
                        mods['mod_stats.max'] or 0,
                    },
                    m1 = tpl_args.health_multiplier or 1,
                    m2 = (result['monster_map_multipliers.life'] or 100)/100,
                    m3 = (result['monster_map_multipliers.boss_life'] or 100)/100,
                }
                local life = h.stat_calc(stats)
                local life_verb = h.stat_calc_verbose(stats)
                
                out[i] = m_util.html.abbr(
                    string.format('%0.0f', life), 
                    string.format(
                        'Lvl. %s: %s',
                        result['monster_base_stats.level'],
                        life_verb
                    )
                )
                end
            
            return table.concat(out, ', ')
        end,
    },
    {
        header = i18n.tooltips.resistances,
        func = function (tpl_args, frame)
            local tbl = mw.html.create('table')
            tbl
                :attr('class', 'wikitable')
                :tag('tr')
                    :tag('th')
                        :wikitext(i18n.tooltips.difficulty)
                        :attr('rowspan', 2)
                        :done()
                    -- :tag('th')
                        -- :wikitext(i18n.tooltips.resistances)
                        -- :attr('colspan', 4)
                        -- :done()
                    :done()
                :tag('tr')
                    :tag('th')
                        :wikitext(i18n.tooltips.fire)
                        :done()
                    :tag('th')
                        :wikitext(i18n.tooltips.cold)
                        :done()
                    :tag('th')
                        :wikitext(i18n.tooltips.lightning)
                        :done()
                    :tag('th')
                        :wikitext(i18n.tooltips.chaos)
                        :done()
                    :done()
            
            local difficulties = {'part1', 'part2', 'maps'}
            local elements = {'fire', 'cold', 'lightning', 'chaos'}
            for _, k in ipairs(difficulties) do
                local tr = tbl:tag('tr')
                tr
                    :tag('th')
                        :wikitext(i18n.tooltips[k])
                        :done()
                for _, element in ipairs(elements) do
                    local field = string.format(
                        'monster_resistances.%s_%s', 
                        k, 
                        element
                    )
                    tr
                        :tag('td')
                            :attr('class', 'tc -' .. element)
                            :wikitext(tpl_args.monster_type[field])
                            :done()
                end
            end
            
            -- -- Compressed resistance table:
            -- local tbl = mw.html.create('table')
            -- local tr = tbl:tag('tr')
            -- local res = {}
            -- for _, element in ipairs(elements) do
                -- if res[element] == nil then 
                    -- res[element] = {}
                -- end 
                -- for _, k in ipairs(difficulties) do
                    -- local r = string.format('monster_resistances.%s_%s', k, element)
                    -- res[element][#res[element]+1] = m_util.html.abbr(
                        -- tpl_args.monster_type[r], 
                        -- k
                    -- )
                -- end
                -- tr
                    -- :tag('td')
                        -- :attr('class', 'tc -' .. element)
                        -- :wikitext(table.concat(res[element], '/'))
                        -- :done()
            -- end
            
            return tostring(tbl)
        end,
    },
    {
        header = i18n.tooltips.experience_multiplier,
        func = display.value{arg='experience_multiplier'},
    },
    {
        header = i18n.tooltips.health_multiplier,
        func = display.value{arg='health_multiplier'},
    },
    {
        header = i18n.tooltips.damage_multiplier,
        func = display.value{arg='damage_multiplier'},
    },
    {
        header = i18n.tooltips.attack_speed,
        func = display.value{arg='attack_speed', fmt='%.3fs',},
    },
    {
        header = i18n.tooltips.critical_strike_chance,
        func = display.value{arg='critical_strike_chance', fmt='%.2f%%',},
    },
    {
        header = i18n.tooltips.minimum_attack_distance,
        func = display.value{arg='minimum_attack_distance'},
    },
    {
        header = i18n.tooltips.maximum_attack_distance,
        func = display.value{arg='maximum_attack_distance'},
    },
    {
        header = i18n.tooltips.size,
        func = display.value{arg='size'},
    },
    {
        header = i18n.tooltips.model_size_multiplier,
        func = display.value{arg='model_size_multiplier'},
    },
    {
        header = i18n.tooltips.tags,
        func = function (tpl_args, frame)
            if tpl_args.tags == nil or #tpl_args.tags == 0 then
                return
            end
            
           return table.concat(tpl_args.tags, '<br>')
        end,
    },
    {
        header = i18n.tooltips.metadata_id,
        func = display.value{arg='metadata_id'},
    },
    {
        header = i18n.tooltips.monster_type_id,
        func = display.value{arg='monster_type_id'},
    },
}

local list_view = {
}


-- ----------------------------------------------------------------------------
-- Page views
-- ----------------------------------------------------------------------------

p.table_monsters = m_cargo.declare_factory{data=tables.monsters}
p.table_monster_types = m_cargo.declare_factory{data=tables.monster_types}
p.table_monster_resistances = m_cargo.declare_factory{data=tables.monster_resistances}
p.table_monster_base_stats = m_cargo.declare_factory{data=tables.monster_base_stats}
p.table_monster_map_multipliers = m_cargo.declare_factory{data=tables.monster_map_multipliers}
p.table_monster_life_scaling = m_cargo.declare_factory{data=tables.monster_life_scaling}

p.store_data = m_cargo.store_from_lua{tables=tables, module='Monster'}

function p.monster(frame)
    --[[
    Stores data and display infoboxes of monsters.
    
    Example
    -------
    = p.monster{
        metadata_id='Metadata/Monsters/Bandits/BanditBossHeavyStrike_',
        monster_type_id='BanditBoss',
        mod_ids='MonsterAttackBlock30Bypass20',
        tags='red_blood', 
        skill_ids='Melee, MonsterHeavyStrike',
        name='Calaf, Headstaver',
        size=3,
        minimum_attack_distance=4,
        maximum_attack_distance=5,
        model_size_multiplier=1.15,
        experience_multiplier=1.0,
        damage_multiplier=1.0,
        health_multiplier=1.0,
        critical_strike_chance=5.0,
        attack_speed=1.35,
        
        rarity_id = 'unique'
    }
    ]]
    
    -- Get args
    tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = m_util.misc.get_frame(frame)
    
    tpl_args._mods = {}
        
    -- Parse and store the monster table:
    m_cargo.parse_field_arguments{
        tpl_args=tpl_args,
        frame=frame,
        table_map=tables.monsters,
    }
        
    -- Create the infobox:
    local tbl = mw.html.create('table')
    tbl
        :attr('class', 'wikitable')
        :attr('style', 'float:right; margin-left: 10px;')
        
    for _, data in ipairs(tbl_view) do
        local v = data.func(tpl_args, frame)
        
        if v ~= nil and v ~= '' then
            local tr = tbl:tag('tr')
            if data.header then 
                tr:tag('th'):wikitext(data.header):done()
                tr:tag('td'):wikitext(v):done()
            else 
                tr:tag('th'):attr('colspan', 2):wikitext(v):done()
            end
        end
    end
        
    local out = {
        tostring(tbl),
        h.intro_text(tpl_args, frame),
    }
    for _, data in ipairs(list_view) do
        out[#out+1] = data.func(tpl_args, frame)
    end
    
    local cats = {
        i18n.cats.data,
    }
    return table.concat(out) .. m_util.misc.add_category(cats)
end

return p