[dismiss]
The wiki is currently a work in progress. If you'd like to help out, please check the Community Portal and our getting started guide. Also, check out our sister project on poewiki.net.
Module:Monster
The above documentation is transcluded from Module:Monster/doc.
Editors can experiment in this module's sandbox and testcases pages.
Subpages of this module.
Editors can experiment in this module's sandbox and testcases pages.
Subpages of this module.
-- ----------------------------------------------------------------------------
-- 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 = {
tooltips = {
name = 'Name',
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 = 'Difficulty',
resistances = 'Resistances',
part1 = '[[Act 1]]-<br>[[Act 5]]',
part2 = '[[Act 5]]-<br>[[Act 10]]',
maps = 'post<br>[[Act 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',
areas = 'Areas',
},
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
-- ----------------------------------------------------------------------------
-- 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
for _, tag in ipairs(m_util.string.split(tpl_args.monster_type['monster_types.tags'], ',%s+')) 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
out = {tostring(tbl)}
for _, data in ipairs(list_view) do
out[#out+1] = data.func(tpl_args, frame)
end
return table.concat(out)
end
return p