Module:Monster

-- -- 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', boss = 'Boss', },   tooltips = { name = 'Name', rarity = 'Rarity', experience_multiplier = 'Base Experience Multiplier', health_multiplier = 'Base Health Multiplier', damage_multiplier = 'Base Damage Multiplier', attack_speed = 'Base Attack Speed', critical_strike_chance = 'Base Critical Strike Chance', minimum_attack_distance = 'Minimum Attack Distance', maximum_attack_distance = 'Maximum Attack Distance', difficulty = 'Act', resistances = 'Resistances', part1 = '1-5', part2 = '5-10', maps = '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 = 'Internal tags', metadata_id = 'Metadata id', monster_type_id = 'Monster type id', area = 'Area', monster_level = 'Level', skills = 'Skills', life = 'Life', damage = 'Damage', aps = 'Attacks per second', critical_strike_chance_total = 'Critical strike chance', armour = 'Armour rating', evasion = 'Evasion rating', accuracy = 'Accuracy rating', experience = 'Experience', summon_life = 'Summon life', monster_data = 'Monster data',

},

intro = { text_with_name = "%s is the internal id for the %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) --       Add mod ids in an ordered way.

if type(value) ~= 'table' then value = {value} end

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, more and less 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, less=function(stats) --[[           Calculate the product of the less terms.            Values should be in percent.

Should only be used for stats that defines directions in the stat id. Prefer the more function instead. ]]           local out = 1 for _, v in ipairs(stats['less']) 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, less=function(stats) local st = {} for _, v in ipairs(stats['less']) 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.stat_match(stats, strings, result) --[[   Match strings to ids in result and append them to stats.

Parameters --   stats : Array, required. Array to append values to. strings : array, required. Array of ids to to match result to. result : array, required. Row result from a cargo_query. Must contain 'mod_stats.id' and 'mod_stats.max'.

Examples stats = { added={2}, increased={0}, more={0}, }   strings = { added={'base_maximum_life'}, increased={'maximum_life_+%'}, more={'maximum_life_+%_final'}, }   result = { ['mod_stats.id'] = 'maximum_life_+%', ['mod_stats.max'] = 100, }   h.stat_match(stats, strings, result) mw.logObject(stats) ]]

for k, stat_ids in pairs(strings) do       for _, stat_id in ipairs(stat_ids) do            -- Match the stat. TODO: Is there a smarter way? Can't just find -- the pattern within the string though since increased and more -- are so similar. if stat_id == result['mod_stats.id'] then if stats[k] == nil then stats[k] = {} end stats[k][#stats[k]+1] = result['mod_stats.max'] -- TODO: add range. end end end end

function h.stat_format(args) --[[   Format the stat value.

Parameters --   args : Array, required. Array of arguments to modify the stat format. Supported keys are: args.fmt args.level args.value args.value_verbose ]]

local fmt = '%0.2f' if args.value > 10000 then fmt = '%0.3E' elseif args.value > 100 then fmt = '%0.0f' end return m_util.html.abbr(       string.format(args.fmt or fmt, args.value),        string.format('Lvl. %0.0f: %s', args.level, args.value_verbose)   ) end

function h.stat_calc_per_monster_level(tpl_args, frame, f_stats, f_strings) --[[   Calculate the total stat value per monster level.

Parameters --   f_stats : Function, required. Function returning an array of stat values per monster level. f_strings : function, required. Function returning an array of stat ids to match to cargo results per monster level. ]]

-- Stop if no monster level was found: if #tpl_args.monster_level == 0 then return nil end

-- Calculate the total stat value for each monster level: local out = {} for i, result in ipairs(tpl_args._mod_data['monster_level']) do       -- Initial stats for monsters: local stats = f_stats(tpl_args, frame, result)

-- Append matching stats from the modifiers: for _, modid in ipairs(tpl_args._mods) do           local mod = tpl_args._mod_data[modid] for _, v in ipairs(mod) do               h.stat_match(                    stats,                    f_strings(tpl_args, frame, result),                    v                ) end end

-- Calculate the total stat value and format the output: out[i] = h.stat_format{ level=result['monster_base_stats.level'], value=h.stat_calc(stats), value_verbose=h.stat_calc_verbose(stats), }   end

return table.concat(out, ', ') end

function h.intro_text(tpl_args, frame) --   Display an introductory text about the monster data. 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

function h.info_box(tpl_args, frame, tbl_view) -- Create the infobox: local container = mw.html.create('div') container :attr('class', 'modbox floatright')

local tbl = container:tag('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

return tostring(container) end

function h.stat_box(tpl_args, frame) --   Display the stat box. local container = mw.html.create('div') container :attr('class', 'modbox floatright')

-- stat table local tbl = container:tag('table') tbl :attr('class', 'wikitable sortable') -- :attr('style', 'style="width: 100%;"') :tag('tr') :tag('th') :attr('colspan', 4) :wikitext('Stats') :done :done :tag('tr') :tag('th') :wikitext('#') :done :tag('th') :wikitext('Stat Id') :done :tag('th') :wikitext('Min') :done :tag('th') :wikitext('Max') :done :done :done

local i = 0 for _, modid in ipairs(tpl_args._mods) do       local mod = tpl_args._mod_data[modid] for k, v in ipairs(mod) do           if v['mod_stats.id'] then i = i + 1

local linked_stat = v['mod_stats.id'] if v['mod_stats._pageName'] then linked_stat = string.format(                       '%s',                        v['mod_stats._pageName'],                        v['mod_stats.id']                    ) end

tbl :tag('tr') :tag('td') :wikitext(i) :done :tag('td') :wikitext(linked_stat) :done :tag('td') :wikitext(v['mod_stats.min']) :done :tag('td') :wikitext(v['mod_stats.max']) :done :done :done end end end

return tostring(container) 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', 'items'},                    {                        'areas.name',                        'areas.id',                        'areas.area_level',                        'areas.boss_monster_ids',                        'areas.modifier_ids',                        'areas.main_page',                        'areas._pageName',                        'maps.area_level',                        'maps._pageName',                        'items.drop_enabled'                    },                    {                        join=                            areas.id=maps.area_id,                            maps._pageID=items._pageID                       ,                        where=m_cargo.replace_holds{                            string=string.format( CASE WHEN maps.area_level IS NOT NULL THEN                                       areas.boss_monster_ids HOLDS "%s"                                        AND items.drop_enabled = True                                    ELSE                                        areas.boss_monster_ids HOLDS "%s"                                    END                                , value, value ),                           mode='regex'                        },                        orderBy='maps.area_level, areas.area_level'                    }                )

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] or {}

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)

local results = m_cargo.map_results_to_id{ results=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',                            'mod_stats._pageName',                        },                        {                            join='mods._pageID=mod_stats._pageID',                            where=string.format(                                    mods.domain = 3                                AND mods.generation_type = 3                                AND mods.id REGEXP "Monster%s[0-9]*$"                                , tpl_args.rarity_id ),                       }                    ),                    field='mods.id', keep_id_field=false, }               for modid, mod in pairs(results) do                    h.add_mod_id(tpl_args, frame, modid) tpl_args._mod_data[modid] = mod end

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

tpl_args._mod_data = {} if #mlist > 0 then 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.min',                                'mod_stats.max',                                'mod_stats._pageName',                            },                            {                                join=                                    mods._pageID=mod_stats._pageID                               ,                                where=string.format(                                    mods.id IN (%s)                                , table.concat(mlist, ',')), }                       ),                        field='mods.id',                        keep_id_field=false,                    }                end            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', },       armour = { field = 'armour', 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(               '%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) local image_name = tpl_args.name or tpl_args.monster_type_id image_name = string.gsub(image_name, '[%[%]]', '') return string.format(               '',                image_name            ) end, },   -- {        -- header = i18n.tooltips.rarity, -- func = display.value{arg='rarity'}, -- },   {        header = i18n.tooltips.area, func = function(tpl_args, frame) local out = {} for i, v in ipairs(tpl_args.monster_usages) do               out[#out+1] = string.format(                    '%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.monster_level, func = function(tpl_args, frame) -- Get monster level from the area level unless it's been -- user defined. local monster_level = {} if tpl_args.monster_level then monster_level = m_util.string.split(tpl_args.monster_level, ',') else 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 end tpl_args.monster_level = monster_level

-- Add monster stats specific to monster level: if #tpl_args.monster_level > 0 then tpl_args._mod_data['monster_level'] = m_cargo.query(                   {                        'monster_base_stats',                        'monster_life_scaling',                        'monster_map_multipliers',                    },                    {                        'monster_base_stats.level',

-- Life: 'monster_base_stats.life', 'monster_life_scaling.magic', 'monster_life_scaling.rare', 'monster_map_multipliers.life', 'monster_map_multipliers.boss_life',

-- Damage: 'monster_base_stats.damage', 'monster_map_multipliers.damage', 'monster_map_multipliers.boss_damage',

'monster_base_stats.armour', 'monster_base_stats.evasion', 'monster_base_stats.accuracy', 'monster_base_stats.experience', 'monster_base_stats.summon_life', },                   {                        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, ', ')                        ), }               )            end

return table.concat(tpl_args.monster_level, ', ') 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, ' ') end, },   {        header = i18n.tooltips.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, ' ') end, },   {        header = i18n.tooltips.life, func = function(tpl_args, frame) local f_stats = function(tpl_args, frame, result) local stats = { added={ result['monster_base_stats.life'], },                   increased={ result['monster_life_scaling.' .. tpl_args.rarity_id] or 0, },                   -- more={}, m_map = (tpl_args.health_multiplier or 1) + (result['monster_map_multipliers.life'] or 0) }               if tpl_args.is_boss then stats.m_map = stats.m_map + (result['monster_map_multipliers.boss_life'] or 0)/100 end

return stats end

local f_strings = function(tpl_args, frame, result) local strings = { added={'base_maximum_life'}, increased={'maximum_life_+%', 'map_monsters_life_+%'}, more={ 'maximum_life_+%_final', 'monster_life_+%_final_from_rarity', },               }                if tpl_args.is_boss then table.insert(strings.increased, 'map_boss_maximum_life_+%') end

return strings end

return h.stat_calc_per_monster_level(tpl_args, frame, f_stats, f_strings)

end, },   {        header = i18n.tooltips.damage, func = function(tpl_args, frame) local f_stats = function(tpl_args, frame, result) local stats = { added={ result['monster_base_stats.damage'], },                   -- increased={}, -- more={}, m_map = (tpl_args.damage_multiplier or 1) + (result['monster_map_multipliers.damage'] or 0) }               if tpl_args.is_boss then stats.m_map = stats.m_map + (result['monster_map_multipliers.boss_damage'] or 0) end

return stats end

local f_strings = function(tpl_args, frame, result) local strings = { -- added={}, increased={'map_monsters_damage_+%'}, more={'monster_rarity_damage_+%_final'}, less={ 'monster_rarity_attack_cast_speed_+%_and_damage_-%_final', 'monster_base_type_attack_cast_speed_+%_and_damage_-%_final', },               }                if tpl_args.is_boss then table.insert(strings.increased, 'map_boss_damage_+%') end

return strings end

return h.stat_calc_per_monster_level(tpl_args, frame, f_stats, f_strings) end, },   {        header = i18n.tooltips.aps, func = function(tpl_args, frame) local f_stats = function(tpl_args, frame, result) local stats = { added={ tpl_args.attack_speed or 1, },               }                return stats end

local f_strings = function(tpl_args, frame, result) local strings = { increased={'map_monsters_attack_speed_+%'}, -- map_monsters_cast_speed_+% more={ 'monster_rarity_attack_cast_speed_+%_and_damage_-%_final', 'monster_base_type_attack_cast_speed_+%_and_damage_-%_final', },               }                if tpl_args.is_boss then table.insert(strings.increased, 'map_boss_attack_and_cast_speed_+%') end

return strings end

return h.stat_calc_per_monster_level(tpl_args, frame, f_stats, f_strings) end, },   {        header = i18n.tooltips.critical_strike_chance_total, func = function(tpl_args, frame) local f_stats = function(tpl_args, frame, result) local stats = { added={ tpl_args.critical_strike_chance, },               }                return stats end

local f_strings = function(tpl_args, frame, result) local strings = { increased={'map_monsters_critical_strike_chance_+%'}, }

return strings end

return h.stat_calc_per_monster_level(tpl_args, frame, f_stats, f_strings) end, },   {        header = i18n.tooltips.armour, func = function(tpl_args, frame) local f_stats = function(tpl_args, frame, result) local stats = { added={ result['monster_base_stats.armour'], },               }                return stats end

local f_strings = function(tpl_args, frame, result) local strings = { -- more={}, }

return strings end

return h.stat_calc_per_monster_level(tpl_args, frame, f_stats, f_strings) end, },   {        header = i18n.tooltips.evasion, func = function(tpl_args, frame) local f_stats = function(tpl_args, frame, result) local stats = { added={ result['monster_base_stats.evasion'], },               }                return stats end

local f_strings = function(tpl_args, frame, result) local strings = { -- more={}, }

return strings end

return h.stat_calc_per_monster_level(tpl_args, frame, f_stats, f_strings) end, },   {        header = i18n.tooltips.accuracy, func = function(tpl_args, frame) local f_stats = function(tpl_args, frame, result) local stats = { added={ result['monster_base_stats.accuracy'], },               }                return stats end

local f_strings = function(tpl_args, frame, result) local strings = { increased = {'map_monsters_accuracy_rating_+%'}, -- more={}, }

return strings end

return h.stat_calc_per_monster_level(tpl_args, frame, f_stats, f_strings) 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, func = function(tpl_args, frame) local f_stats = function(tpl_args, frame, result) local stats = { added={ result['monster_base_stats.experience'], },                   m = tpl_args.experience_multiplier, }               return stats end

local f_strings = function(tpl_args, frame, result) local strings = { increased={'monster_slain_experience_+%'}, }

return strings end

return h.stat_calc_per_monster_level(tpl_args, frame, f_stats, f_strings) end, },   {        header = i18n.tooltips.summon_life, func = function(tpl_args, frame) -- Uniques cannot be summoned: if tpl_args.rarity_id == 'unique' then return nil end

local f_stats = function(tpl_args, frame, result) local stats = { added={ result['monster_base_stats.summon_life'], },                   m = tpl_args.experience_multiplier, }               return stats end

local f_strings = function(tpl_args, frame, result) local strings = { -- increased={}, }

return strings end

return h.stat_calc_per_monster_level(tpl_args, frame, f_stats, f_strings) end, },   {        header = i18n.tooltips.metadata_id, func = display.value{arg='metadata_id'}, },

}

local tbl_view_detailed = { {       func = function(tpl_args, frame) return i18n.tooltips.monster_data 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'}, },   {        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, ' ') 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-1',}, },   {        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'}, }, } 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, MonsterExileLifeInMerciless_', 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' }

= p.monster{ metadata_id='Metadata/Monsters/Atziri/Atziri', monster_type_id='Atziri', mod_ids='MonsterAtziriMapBoss, MapMonsterReducedCurseEffect, AtziriReflectCurses, AtziriMinorDamageReflect, MonsterImplicitCannotBeStunned1, CannotBeSlowedBelowValueBosses, TauntImmunityDurationMapBoss', tags='red_blood', skill_ids='AtziriMirrorImage, AtziriSummonDemons, AtziriStormCall, AtziriStormCallEmpowered, AtziriFlameblast, AtziriFlameblastEmpowered, AtziriSpearThrow, AtziriSpearThrowEmpowered', name='Atziri, Queen of the Vaal', size=4, minimum_attack_distance=4, maximum_attack_distance=16, model_size_multiplier=1.65, experience_multiplier=2.0, damage_multiplier=2.5, health_multiplier=9.36, critical_strike_chance=5.0, attack_speed=1.5, }

]]

-- 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 infoboxes: local out = { h.info_box(tpl_args, frame, tbl_view), h.info_box(tpl_args, frame, tbl_view_detailed), h.stat_box(tpl_args, frame), h.intro_text(tpl_args, frame), }   for _, data in ipairs(list_view) do        out[#out+1] = data.func(tpl_args, frame) end

-- Categories: local cats = { i18n.cats.data, }   local cats_type if tpl_args.is_boss then cats_type = i18n.cats.boss else cats_type = tpl_args.rarity end cats[#cats+1] = string.format('%s %s', cats_type, string.lower(i18n.cats.data))

return table.concat(out) .. m_util.misc.add_category(cats) end

return p