[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 ids',
areas = 'Areas',
},
}
-- ----------------------------------------------------------------------------
-- 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
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'},
{
'areas.name',
'areas.id',
'areas.area_level',
'areas.boss_monster_ids',
'areas.main_page',
'areas._pageName',
},
{
where = string.format('areas.boss_monster_ids HOLDS "%s"', 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]
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,
},
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',
},
--
-- 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']
)
end
return table.concat(out, ', ')
end
},
{
header = 'Life',
func = function(tpl_args, frame)
-- Get monster level from the area level that
local monster_level = {}
for _, v in ipairs(tpl_args.monster_usages) do
monster_level[#monster_level+1] = v['areas.area_level'] + 2
end
tpl_args.monster_level = tpl_args.monster_level or table.concat(monster_level, ',')
if tpl_args.monster_level ~= nil then
tpl_args.monster_level = m_util.string.split(tpl_args.monster_level, ',')
end
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, ',')
),
}
)
out = {}
for i, result in ipairs(results) do
-- Base life:
local life = result['monster_base_stats.life']
-- TODO no idea how these works!
-- Available multipliers:
local multipliers = {
1 + (tpl_args.health_multiplier or 0),
1 + (result['monster_life_scaling.magic'] or 0)/100,
1 + (result['monster_life_scaling.rare'] or 0)/100,
1 + (result['monster_map_multipliers.life'] or 0)/100,
1 + (result['monster_map_multipliers.boss_life'] or 0)/100,
}
-- Calculate the final life:
for _, v in ipairs(multipliers) do
life = life * v
end
out[i] = m_util.html.abbr(
string.format('%0.0f', life),
string.format(
'Lvl. %s: %s * %s',
tpl_args.monster_level[i],
result['monster_base_stats.life'],
table.concat(multipliers, ' * ')
)
)
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}
function p.store_data(frame)
-- Get args
tpl_args = getArgs(frame, {
parentFirst = true
})
frame = m_util.misc.get_frame(frame)
if tables[tpl_args.tbl] == nil then
error(i18n.errors.invalid_table)
end
-- mw.loadData has some problems...
local data = require(string.format('Module:Monster/%s', tpl_args.tbl))
tpl_args.index_start = tpl_args.index_start or 1
tpl_args.index_end = tpl_args.index_end or #data
for i=tpl_args.index_start, tpl_args.index_end do
local row = data[i]
if row == nil then
break
end
-- get full table name
row._table = tables[tpl_args.tbl].table
m_util.cargo.store(frame, row)
end
return string.format('Attempted to store rows %s to %s in "%s" table', tpl_args.index_start, tpl_args.index_end, tpl_args.tbl)
end
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,
}
]]
-- 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