Path of Exile Wiki

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

ПОДРОБНЕЕ

Path of Exile Wiki
(functions need to return value)
Нет описания правки
Строка 14: Строка 14:
   
 
local i18n = {
 
local i18n = {
  +
cats = {
  +
data = 'Monster data'
 
},
 
tooltips = {
 
tooltips = {
 
name = 'Название',
 
name = 'Название',
Строка 39: Строка 42:
 
areas = 'Areas',
 
areas = 'Areas',
 
},
 
},
  +
  +
intro = {
  +
text_with_name = "'''%s''' is the internal id for the [[%s|%s]] [[monster]]. ",
  +
text_without_name = "'''%s''' is the internal id of an unnamed [[monster]]. ",
  +
},
  +
 
errors = {
 
errors = {
 
invalid_rarity_id = 'The rarity id "%s" is invalid. Acceptable values are "normal", "magic", "rare" and "unique".',
 
invalid_rarity_id = 'The rarity id "%s" is invalid. Acceptable values are "normal", "magic", "rare" and "unique".',
Строка 161: Строка 170:
 
 
 
return table.concat(out, ' * ')
 
return table.concat(out, ' * ')
end
+
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
 
-- Tables
Строка 228: Строка 266:
 
 
 
if tpl_args.monster_type['monster_types.tags'] then
 
if tpl_args.monster_type['monster_types.tags'] then
for _, tag in ipairs(m_util.string.split(tpl_args.monster_type['monster_types.tags'], ',%s+')) do
+
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
 
tpl_args.tags[#tpl_args.tags+1] = tag
 
end
 
end
Строка 749: Строка 795:
 
{
 
{
 
join='mods._pageID=mod_stats._pageID',
 
join='mods._pageID=mod_stats._pageID',
where=string.format(
+
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"',
+
mods.domain = 3
  +
AND mods.generation_type = 3
  +
AND mods.id LIKE "Monster%s%%"
  +
AND mod_stats.id = "monster_life_+%%_final_from_rarity"
  +
]],
 
id
 
id
 
),
 
),
Строка 872: Строка 922:
 
end
 
end
 
 
out = {tostring(tbl)}
+
local out = {
  +
tostring(tbl),
  +
h.intro_text(tpl_args, frame),
  +
}
 
for _, data in ipairs(list_view) do
 
for _, data in ipairs(list_view) do
 
out[#out+1] = data.func(tpl_args, frame)
 
out[#out+1] = data.func(tpl_args, frame)
 
end
 
end
  +
 
  +
local cats = {
return table.concat(out)
 
  +
i18n.cats.data,
  +
}
  +
return table.concat(out) .. m_util.misc.add_category(cats)
 
end
 
end
   

Версия от 11:26, 22 сентября 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 p = {}

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

local i18n = {
    cats = {
        data = 'Monster data'
    },
    tooltips = {
        name = 'Название',
        experience_multiplier = 'Базовый множитель опыта', 
        health_multiplier = 'Базовый множитель здоровья',
        damage_multiplier = 'Базовый множитель урона',
        attack_speed = 'Базовая скорость атаки',
        critical_strike_chance = 'Базовый шанс критического удара',
        minimum_attack_distance = 'Минимальное расстояние атаки',
        maximum_attack_distance = 'Максимальное расстояние атаки',
        difficulty = 'Сложность',
        resistances = 'Сопротивления',
        part1 = '[[Акт 1]]-<br>[[Акт 5]]',
        part2 = '[[Акт 5]]-<br>[[Акт 10]]',
        maps = 'После<br>[[Акт 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 = 'Effects from modifiers',
        size = 'Object size',
        model_size_multiplier = 'Model size multiplier',
        tags = 'Внутренние метки',
        metadata_id = 'Metadata id',
        areas = 'Areas',
    },
    
    intro = {
        text_with_name = "'''%s''' is the internal id for the [[%s|%s]] [[monster]]. ",
        text_without_name = "'''%s''' is the internal id of an unnamed [[monster]]. ",
    },
    
    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
        tpl_args._mods[id] = true
    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'},
    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',
        },
        -- rarity_id = {
            -- field = 'rarity_id',
            -- type = 'String',
            -- func = function (tpl_args, frame)
                -- 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
            -- end
        -- },
    
        --
        -- Processing fields
        --
        mods = {
            func = function (tpl_args, frame) 
                local mlist = {}
                for key, _ in pairs(tpl_args._mods) do
                    mlist[#mlist+1] = key
                end
                
                tpl_args._mods = m_cargo.array_query{
                    tables={'mods'},
                    fields={'mods.stat_text'},
                    id_field='mods.id',
                    id_array=mlist
                }
            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 = display.value{arg='name'},
    },
    {
        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.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()
                    
            for _, k in ipairs({'part1', 'part2', 'maps'}) do
                local tr = tbl:tag('tr')
                tr
                    :tag('th')
                        :wikitext(i18n.tooltips[k])
                        :done()
                for _, element in ipairs({'fire', 'cold', 'lightning', 'chaos'}) do
                    tr
                        :tag('td')
                            :attr('class', 'tc -' .. element)
                            :wikitext(tpl_args.monster_type[string.format('monster_resistances.%s_%s', k, element)])
                            :done()
                end
            end
            
            return tostring(tbl)
        end,
    },
    {
        header = i18n.tooltips.stat_text,
        func = function (tpl_args, frame)
            if #tpl_args._mods == 0 then
                return
            end
            
            local out = {}
            for _, row in ipairs(tpl_args._mods) do
                out[#out+1] = row['mods.stat_text']
            end
            
            return table.concat(out, '<br>')
        end,
    },
    {
        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.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 = 'Life',
        func = function(tpl_args, frame)
            -- Should this be stored to cargo? 
            
            local id = tpl_args.rarity_id or 'normal'
                       
            -- 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? item2 
            -- has 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"
                        ]], 
                        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.' .. tostring(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,
    },
}

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',
        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 
    m_cargo.parse_field_arguments{
        tpl_args=tpl_args,
        frame=frame,
        table_map=tables.monsters,
    }
    
    local tbl = mw.html.create('table')
    tbl
        :attr('class', 'wikitable')
        
    for _, data in ipairs(tbl_view) do
        local v = data.func(tpl_args, frame)
        
        if v ~= nil and v ~= '' then
            tbl
                :tag('tr')
                    :tag('th')
                        :wikitext(data.header)
                        :done()
                    :tag('td')
                        :wikitext(v)
                        :done()
                    :done()
        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