[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: Difference between revisions
Jump to navigation
Jump to search
>Illviljan m (#tpl_args.monster_level should be 0 when no monster levels have been found.) |
>Illviljan (Added a total damage column, moved stuff around because it's similar to the total life calculation.) |
||
Line 21: | Line 21: | ||
name = 'Name', | name = 'Name', | ||
rarity = 'Rarity', | rarity = 'Rarity', | ||
experience_multiplier = 'Base Experience Multiplier', | experience_multiplier = 'Base Experience Multiplier', | ||
health_multiplier = 'Base Health Multiplier', | health_multiplier = 'Base Health Multiplier', | ||
damage_multiplier = 'Base Damage Multiplier', | damage_multiplier = 'Base Damage Multiplier', | ||
Line 44: | Line 44: | ||
monster_type_id = 'Monster type id', | monster_type_id = 'Monster type id', | ||
areas = 'Areas', | areas = 'Areas', | ||
monster_level = 'Level' | monster_level = 'Level', | ||
skills = 'Skills', | |||
life = 'Life', | |||
damage = 'Damage', | |||
}, | }, | ||
intro = { | intro = { | ||
text_with_name = "'''%s''' is the internal id for the [[%s|%s]] [[monster]]. ", | 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]]. ", | 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".', | ||
Line 62: | Line 65: | ||
function h.add_mod_id(tpl_args, frame, value) | 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 | for _, id in ipairs(value or {}) do | ||
if tpl_args._mods[id] == nil then | if tpl_args._mods[id] == nil then | ||
Line 68: | Line 79: | ||
end | end | ||
end | end | ||
return value | return value | ||
end | end | ||
Line 75: | Line 86: | ||
--[[ | --[[ | ||
Calculates a modified stat. | Calculates a modified stat. | ||
Parameters | Parameters | ||
---------- | ---------- | ||
stats : List | stats : List | ||
Associated List with added, increased and | Associated List with added, increased, more and less as keys. | ||
Examples | Examples | ||
-------- | -------- | ||
Line 90: | Line 100: | ||
} | } | ||
= h.stat_calc(stats) | = h.stat_calc(stats) | ||
]] | ]] | ||
local funcs = { | local funcs = { | ||
added=function(stats) | added=function(stats) | ||
--[[ | --[[ | ||
Sum the added terms. | Sum the added terms. | ||
]] | ]] | ||
local out = 0 | local out = 0 | ||
for _, v in ipairs(stats['added']) do | for _, v in ipairs(stats['added']) do | ||
out = v + out | out = v + out | ||
Line 105: | Line 115: | ||
increased=function(stats) | increased=function(stats) | ||
--[[ | --[[ | ||
Sum the increased terms. | Sum the increased terms. | ||
Values should be in percent. | Values should be in percent. | ||
]] | ]] | ||
Line 116: | Line 126: | ||
more=function(stats) | more=function(stats) | ||
--[[ | --[[ | ||
Calculate the product of the more terms. | Calculate the product of the more terms. | ||
Values should be in percent. | Values should be in percent. | ||
]] | ]] | ||
Line 122: | Line 132: | ||
for _, v in ipairs(stats['more']) do | for _, v in ipairs(stats['more']) do | ||
out = (1 + v/100) * out | 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 | end | ||
return out | return out | ||
end, | end, | ||
} | } | ||
local out = 1 | local out = 1 | ||
for k, v in pairs(stats) do | for k, v in pairs(stats) do | ||
Line 135: | Line 159: | ||
end | end | ||
end | end | ||
return out | return out | ||
end | end | ||
Line 144: | Line 168: | ||
]] | ]] | ||
local verbose_funcs = { | local verbose_funcs = { | ||
added = function(stats) | added=function(stats) | ||
local st = {} | local st = {} | ||
for _, v in ipairs(stats['added']) do | for _, v in ipairs(stats['added']) do | ||
st[#st+1] = v/1 | st[#st+1] = v/1 | ||
end | end | ||
return string.format('(%s)', table.concat(st, ' + ')) | return string.format('(%s)', table.concat(st, ' + ')) | ||
end, | end, | ||
increased = function(stats) | increased=function(stats) | ||
local st = {} | local st = {} | ||
for _, v in ipairs(stats['increased']) do | for _, v in ipairs(stats['increased']) do | ||
st[#st+1] = v/100 | st[#st+1] = v/100 | ||
end | end | ||
return string.format('(1 + %s)', table.concat(st, ' + ')) | return string.format('(1 + %s)', table.concat(st, ' + ')) | ||
end, | end, | ||
more = function(stats) | more=function(stats) | ||
local st = {} | local st = {} | ||
for _, v in ipairs(stats['more']) do | for _, v in ipairs(stats['more']) do | ||
st[#st+1] = string.format('(1 + %s)', v/100) | st[#st+1] = string.format('(1 + %s)', v/100) | ||
end | 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, ' * ')) | return string.format('(%s)', table.concat(st, ' * ')) | ||
end, | end, | ||
} | } | ||
local out = {} | local out = {} | ||
for k, v in pairs(stats) do | for k, v in pairs(stats) do | ||
Line 175: | Line 206: | ||
end | end | ||
end | end | ||
return table.concat(out, ' * ') | return table.concat(out, ' * ') | ||
end | end | ||
Line 182: | Line 213: | ||
--[[ | --[[ | ||
Match stats to strings. | Match stats to strings. | ||
Examples | Examples | ||
-------- | -------- | ||
Line 202: | Line 233: | ||
mw.logObject(stats) | mw.logObject(stats) | ||
]] | ]] | ||
for k, stat_ids in pairs(strings) do | for k, stat_ids in pairs(strings) do | ||
for _, stat_id in ipairs(stat_ids) do | for _, stat_id in ipairs(stat_ids) do | ||
-- Match the stat. TODO: Is there a smarter way? Can't just find | -- Match the stat. TODO: Is there a smarter way? Can't just find | ||
-- the pattern within the string though since increased and more | -- the pattern within the string though since increased and more | ||
-- are so similar. | -- are so similar. | ||
if stat_id == result['mod_stats.id'] then | 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. | stats[k][#stats[k]+1] = result['mod_stats.max'] -- TODO: add range. | ||
end | end | ||
end | end | ||
end | end | ||
Line 222: | Line 256: | ||
if mw.ustring.find(tpl_args['metadata_id'], '_') then | if mw.ustring.find(tpl_args['metadata_id'], '_') then | ||
out[#out+1] = frame:expandTemplate{ | out[#out+1] = frame:expandTemplate{ | ||
title='Incorrect title', | title='Incorrect title', | ||
args = {title=tpl_args['id']} | args = {title=tpl_args['id']} | ||
} | } | ||
end | end | ||
if tpl_args['name'] then | if tpl_args['name'] then | ||
out[#out+1] = string.format( | out[#out+1] = string.format( | ||
i18n.intro.text_with_name, | i18n.intro.text_with_name, | ||
tpl_args['metadata_id'], | tpl_args['metadata_id'], | ||
tpl_args['main_page'] or tostring(mw.title.getCurrentTitle()), | tpl_args['main_page'] or tostring(mw.title.getCurrentTitle()), | ||
tpl_args['name'] | tpl_args['name'] | ||
) | ) | ||
else | else | ||
out[#out+1] = string.format( | out[#out+1] = string.format( | ||
i18n.intro.text_without_name, | i18n.intro.text_without_name, | ||
tpl_args['metadata_id'] | tpl_args['metadata_id'] | ||
) | ) | ||
end | end | ||
return table.concat(out) | return table.concat(out) | ||
end | end | ||
Line 260: | Line 294: | ||
tpl_args.monster_usages = m_cargo.query( | tpl_args.monster_usages = m_cargo.query( | ||
{'areas', 'maps'}, | {'areas', 'maps'}, | ||
{ | { | ||
'areas.name', | 'areas.name', | ||
'areas.id', | 'areas.id', | ||
Line 271: | Line 305: | ||
{ | { | ||
join='areas.id=maps.area_id', | join='areas.id=maps.area_id', | ||
where=string.format('areas.boss_monster_ids HOLDS "%s"', value), | where=string.format('areas.boss_monster_ids HOLDS "%s"', value), | ||
} | } | ||
) | ) | ||
return value | return value | ||
end, | end, | ||
Line 290: | Line 324: | ||
'monster_types.energy_shield_multiplier', | 'monster_types.energy_shield_multiplier', | ||
'monster_types.damage_spread', | 'monster_types.damage_spread', | ||
'monster_resistances.part1_fire', | 'monster_resistances.part1_fire', | ||
'monster_resistances.part1_cold', | 'monster_resistances.part1_cold', | ||
'monster_resistances.part1_lightning', | 'monster_resistances.part1_lightning', | ||
'monster_resistances.part1_chaos', | 'monster_resistances.part1_chaos', | ||
'monster_resistances.part2_fire', | 'monster_resistances.part2_fire', | ||
'monster_resistances.part2_cold', | 'monster_resistances.part2_cold', | ||
'monster_resistances.part2_lightning', | 'monster_resistances.part2_lightning', | ||
'monster_resistances.part2_chaos', | 'monster_resistances.part2_chaos', | ||
'monster_resistances.maps_fire', | 'monster_resistances.maps_fire', | ||
'monster_resistances.maps_cold', | 'monster_resistances.maps_cold', | ||
'monster_resistances.maps_lightning', | 'monster_resistances.maps_lightning', | ||
'monster_resistances.maps_chaos' | 'monster_resistances.maps_chaos' | ||
}, | }, | ||
{ | { | ||
join='monster_types.monster_resistance_id = monster_resistances.id', | join='monster_types.monster_resistance_id = monster_resistances.id', | ||
where=string.format('monster_types.id = "%s"', value), | where=string.format('monster_types.id = "%s"', value), | ||
} | } | ||
)[1] | )[1] | ||
if tpl_args.monster_type['monster_types.tags'] then | if tpl_args.monster_type['monster_types.tags'] then | ||
local tags = m_util.string.split( | local tags = m_util.string.split( | ||
tpl_args.monster_type['monster_types.tags'], | tpl_args.monster_type['monster_types.tags'], | ||
',%s+' | ',%s+' | ||
) | ) | ||
-- TODO: Maybe this can be fixed earlier? | -- TODO: Maybe this can be fixed earlier? | ||
if tpl_args.tags == nil then | if tpl_args.tags == nil then | ||
tpl_args.tags = {} | tpl_args.tags = {} | ||
end | end | ||
for _, tag in ipairs(tags) do | for _, tag in ipairs(tags) do | ||
tpl_args.tags[#tpl_args.tags+1] = tag | tpl_args.tags[#tpl_args.tags+1] = tag | ||
end | end | ||
end | end | ||
return value | return value | ||
end, | end, | ||
Line 415: | Line 449: | ||
func = function (tpl_args, frame) | func = function (tpl_args, frame) | ||
--[[ | --[[ | ||
Define the rarity of the monster. There's no obvious | Define the rarity of the monster. There's no obvious | ||
parameter that can be datamined for this so this will | parameter that can be datamined for this so this will | ||
be mostly guess work. | be mostly guess work. | ||
]] | ]] | ||
-- User defined rarity takes priority: | -- User defined rarity takes priority: | ||
if tpl_args.rarity_id ~= nil then | if tpl_args.rarity_id ~= nil then | ||
if m_game.constants.rarities[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, | error(string.format(i18n.errors.invalid_rarity_id, | ||
tostring(tpl_args.rarity_id))) | tostring(tpl_args.rarity_id))) | ||
end | end | ||
return tpl_args.rarity_id | return tpl_args.rarity_id | ||
end | end | ||
-- If the monster is used in some area it's most likely | -- If the monster is used in some area it's most likely | ||
-- an unique boss: | -- an unique boss: | ||
Line 435: | Line 469: | ||
return tpl_args.rarity_id | return tpl_args.rarity_id | ||
end | end | ||
-- If there are no mods it's probably a normal monster: | -- If there are no mods it's probably a normal monster: | ||
if #tpl_args._mods == 0 then | if #tpl_args._mods == 0 then | ||
tpl_args.rarity_id = 'normal' | tpl_args.rarity_id = 'normal' | ||
return tpl_args.rarity_id | return tpl_args.rarity_id | ||
end | end | ||
-- Try to determine rarity from mods: | -- Try to determine rarity from mods: | ||
for _, modid in ipairs(tpl_args._mods) do | for _, modid in ipairs(tpl_args._mods) do | ||
local mod = tpl_args._mod_data[modid] | local mod = tpl_args._mod_data[modid] | ||
-- Check if the mod contains the monster rarity stat: | -- Check if the mod contains the monster rarity stat: | ||
for _, v in ipairs(mod) do | for _, v in ipairs(mod) do | ||
if v['mod_stats.id'] == 'monster_rarity' then | if v['mod_stats.id'] == 'monster_rarity' then | ||
-- TODO: m_game rarity id does not match the stat: | -- TODO: m_game rarity id does not match the stat: | ||
local int_id = tonumber(v['mod_stats.max']) + 1 | local int_id = tonumber(v['mod_stats.max']) + 1 | ||
for k, row in pairs(m_game.constants.rarities) do | for k, row in pairs(m_game.constants.rarities) do | ||
if int_id == row['id'] then | if int_id == row['id'] then | ||
tpl_args.rarity_id = k | tpl_args.rarity_id = k | ||
return tpl_args.rarity_id | return tpl_args.rarity_id | ||
Line 459: | Line 493: | ||
end | end | ||
end | end | ||
-- If none of the mods contains the monster rarity | -- If none of the mods contains the monster rarity | ||
-- stat then it might be an unique: | -- stat then it might be an unique: | ||
if tpl_args.rarity_id == nil then | if tpl_args.rarity_id == nil then | ||
tpl_args.rarity_id = 'unique' | tpl_args.rarity_id = 'unique' | ||
return tpl_args.rarity_id | return tpl_args.rarity_id | ||
Line 472: | Line 506: | ||
type = 'String', | type = 'String', | ||
func = function (tpl_args, frame) | func = function (tpl_args, frame) | ||
local results = m_cargo.map_results_to_id{ | |||
local | |||
results=m_cargo.query( | results=m_cargo.query( | ||
{ | { | ||
'mods', | 'mods', | ||
'mod_stats', | 'mod_stats', | ||
}, | }, | ||
{ | { | ||
'mods.domain', | |||
'mods.generation_type', | |||
'mods.mod_group', | |||
'mods.id', | 'mods.id', | ||
'mod_stats.id', | 'mod_stats.id', | ||
'mod_stats.max', | 'mod_stats.max', | ||
'mod_stats.min', | |||
}, | }, | ||
{ | { | ||
join= | join='mods._pageID=mod_stats._pageID', | ||
where=string.format([[ | where=string.format([[ | ||
mods.id | mods.domain = 3 | ||
AND mods.generation_type = 3 | |||
AND mods.id REGEXP "Monster%s[0-9]*$" | |||
]], | |||
tpl_args.rarity_id | |||
), | |||
} | } | ||
), | ), | ||
Line 525: | Line 536: | ||
keep_id_field=false, | 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.max', | |||
}, | |||
{ | |||
join=[[ | |||
mods._pageID=mod_stats._pageID | |||
]], | |||
where=string.format([[ | |||
mods.id IN (%s) | |||
]], table.concat(mlist, ',')), | |||
} | |||
), | |||
field='mods.id', | |||
keep_id_field=false, | |||
} | |||
end | |||
end, | end, | ||
}, | }, | ||
Line 567: | Line 627: | ||
tables.monster_resistances = { | tables.monster_resistances = { | ||
table = 'monster_resistances', | table = 'monster_resistances', | ||
order = {'id', 'part1_fire', 'part1_cold', 'part1_lightning', | order = {'id', 'part1_fire', 'part1_cold', 'part1_lightning', | ||
'part1_chaos', 'part2_fire', 'part2_cold', 'part2_lightning', | 'part1_chaos', 'part2_fire', 'part2_cold', 'part2_lightning', | ||
'part2_chaos', 'maps_fire', 'maps_cold', 'maps_lightning', | 'part2_chaos', 'maps_fire', 'maps_cold', 'maps_lightning', | ||
'maps_chaos'}, | 'maps_chaos'}, | ||
fields = { | fields = { | ||
Line 629: | Line 689: | ||
tables.monster_base_stats = { | tables.monster_base_stats = { | ||
table = 'monster_base_stats', | table = 'monster_base_stats', | ||
order = {'level', 'damage', 'evasion', 'accuracy', 'life', 'experience', | order = {'level', 'damage', 'evasion', 'accuracy', 'life', 'experience', | ||
'summon_life'}, | 'summon_life'}, | ||
fields = { | fields = { | ||
Line 666: | Line 726: | ||
tables.monster_map_multipliers = { | tables.monster_map_multipliers = { | ||
table = 'monster_map_multipliers', | table = 'monster_map_multipliers', | ||
order = {'level', 'life', 'damage', 'boss_life', 'boss_damage', | order = {'level', 'life', 'damage', 'boss_life', 'boss_damage', | ||
'boss_item_rarity', 'boss_item_quantity'}, | 'boss_item_rarity', 'boss_item_quantity'}, | ||
fields = { | fields = { | ||
Line 732: | Line 792: | ||
v = tpl_args[args.arg] | v = tpl_args[args.arg] | ||
end | end | ||
if v and args.fmt then | if v and args.fmt then | ||
return string.format(args.fmt, v) | return string.format(args.fmt, v) | ||
Line 751: | Line 811: | ||
-- header = i18n.tooltips.name, | -- header = i18n.tooltips.name, | ||
func = function (tpl_args, frame) | func = function (tpl_args, frame) | ||
if tpl_args.name == nil then | if tpl_args.name == nil then | ||
return | return | ||
end | end | ||
local linked_name = string.format( | local linked_name = string.format( | ||
'[[Monster:%s|%s]]', | '[[Monster:%s|%s]]', | ||
string.gsub(tpl_args.metadata_id, '_', '~'), | string.gsub(tpl_args.metadata_id, '_', '~'), | ||
tpl_args.name | tpl_args.name | ||
) | ) | ||
Line 767: | Line 827: | ||
func = function (tpl_args, frame) | func = function (tpl_args, frame) | ||
return string.format( | return string.format( | ||
'[[File:%s monster screenshot.jpg|296x500px]]', | '[[File:%s monster screenshot.jpg|296x500px]]', | ||
tpl_args.name or tpl_args.monster_type_id | tpl_args.name or tpl_args.monster_type_id | ||
) | ) | ||
Line 780: | Line 840: | ||
func = function(tpl_args, frame) | func = function(tpl_args, frame) | ||
local out = {} | local out = {} | ||
for i, v in ipairs(tpl_args.monster_usages) do | for i, v in ipairs(tpl_args.monster_usages) do | ||
out[#out+1] = string.format( | out[#out+1] = string.format( | ||
'[[%s|%s]]', | '[[%s|%s]]', | ||
v['areas.main_page'] or v['areas._pageName'], | v['areas.main_page'] or v['areas._pageName'], | ||
v['areas.name'] or v['areas.id'] | v['areas.name'] or v['areas.id'] | ||
) | ) | ||
end | end | ||
return table.concat(out, ', ') | return table.concat(out, ', ') | ||
end | end | ||
}, | }, | ||
{ | { | ||
header = i18n.tooltips.monster_level, | header = i18n.tooltips.monster_level, | ||
func = function(tpl_args, frame) | func = function(tpl_args, frame) | ||
-- Get monster level from the area level unless it's been | -- Get monster level from the area level unless it's been | ||
-- user defined. | -- user defined. | ||
local monster_level = {} | local monster_level = {} | ||
if tpl_args.monster_level then | if tpl_args.monster_level then | ||
monster_level = m_util.string.split(tpl_args.monster_level, ',') | monster_level = m_util.string.split(tpl_args.monster_level, ',') | ||
else | else | ||
Line 806: | Line 866: | ||
end | end | ||
tpl_args.monster_level = monster_level | 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' | |||
}, | |||
{ | |||
'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', | |||
-- 'monster.health_multiplier', | |||
-- Damage: | |||
'monster_base_stats.damage', | |||
'monster_map_multipliers.damage', | |||
'monster_map_multipliers.boss_damage', | |||
}, | |||
{ | |||
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, ', ') | return table.concat(tpl_args.monster_level, ', ') | ||
end | end | ||
}, | }, | ||
{ | { | ||
header = i18n.tooltips.stat_text, | header = i18n.tooltips.stat_text, | ||
Line 819: | Line 915: | ||
local mod = tpl_args._mod_data[modid] or {} | local mod = tpl_args._mod_data[modid] or {} | ||
local stat_text = {} | local stat_text = {} | ||
-- Add stat_text for each modifier, ignore duplicates: | -- Add stat_text for each modifier, ignore duplicates: | ||
for _, v in ipairs(mod) do | for _, v in ipairs(mod) do | ||
if v['mods.stat_text'] then | if v['mods.stat_text'] then | ||
if stat_text[v['mods.stat_text']] == nil then | if stat_text[v['mods.stat_text']] == nil then | ||
Line 830: | Line 926: | ||
end | end | ||
end | end | ||
return table.concat(out, '<br>') | return table.concat(out, '<br>') | ||
end, | end, | ||
}, | }, | ||
{ | { | ||
header = | header = i18n.tooltips.skills, | ||
func = function (tpl_args, frame) | func = function (tpl_args, frame) | ||
local out = {} | local out = {} | ||
for _, id in ipairs(tpl_args.skill_ids or {}) do | for _, id in ipairs(tpl_args.skill_ids or {}) do | ||
out[#out+1] = f_skill_link{id=id} | out[#out+1] = f_skill_link{id=id} | ||
if string.find(out[#out], 'class="module%-error"') then | if string.find(out[#out], 'class="module%-error"') then | ||
out[#out] = id | out[#out] = id | ||
end | end | ||
end | end | ||
return table.concat(out, '<br>') | return table.concat(out, '<br>') | ||
end, | end, | ||
}, | }, | ||
{ | { | ||
header = | header = i18n.tooltips.life, | ||
func = function(tpl_args, frame) | func = function(tpl_args, frame) | ||
-- TODO: Should this be stored to cargo? | --[[ | ||
TODO: Should this be stored to cargo? | |||
]] | |||
-- Stop if no monster level was found: | -- Stop if no monster level was found: | ||
if #tpl_args.monster_level == 0 then | if #tpl_args.monster_level == 0 then | ||
return nil | return nil | ||
end | end | ||
-- Calculate the total stat value for each monster level: | |||
local out = {} | local out = {} | ||
for i, result in ipairs( | for i, result in ipairs(tpl_args._mod_data['monster_level']) do | ||
-- Initial stats for monsters: | -- Initial stats for monsters: | ||
local stats = { | local stats = { | ||
added={ | added={ | ||
result['monster_base_stats.life'], | result['monster_base_stats.life'], | ||
} | }, | ||
increased={ | increased={ | ||
result['monster_life_scaling.' .. tpl_args.rarity_id] or 0, | result['monster_life_scaling.' .. tpl_args.rarity_id] or 0, | ||
}, | }, | ||
more={ | more={ | ||
result['mod_stats.max'] or 0, | |||
}, | }, | ||
m1 = tpl_args.health_multiplier or 1, | m1 = tpl_args.health_multiplier or 1, | ||
m_map = (result['monster_map_multipliers.life'] or 100)/100 | |||
} | } | ||
-- Bossses have different multipliers: | |||
if tpl_args.is_boss then | |||
stats.m_map = (result['monster_map_multipliers.boss_life'] or 100)/100 | |||
end | |||
-- Append matching stats from the modifiers: | -- Append matching stats from the modifiers: | ||
for _, modid in ipairs(tpl_args._mods) do | for _, modid in ipairs(tpl_args._mods) do | ||
local mod = tpl_args._mod_data[modid] | local mod = tpl_args._mod_data[modid] | ||
for _, v in ipairs(mod) do | for _, v in ipairs(mod) do | ||
h.stat_match( | h.stat_match( | ||
stats, | stats, | ||
Line 944: | Line 988: | ||
added={'base_maximum_life'}, | added={'base_maximum_life'}, | ||
increased={'maximum_life_+%'}, | increased={'maximum_life_+%'}, | ||
more={'maximum_life_+%_final'}, | more={ | ||
'maximum_life_+%_final', | |||
'monster_life_+%_final_from_rarity' | |||
}, | |||
}, | }, | ||
v | v | ||
Line 950: | Line 997: | ||
end | end | ||
end | end | ||
-- Calculate the total stat value: | |||
local total_stat_value = h.stat_calc(stats) | |||
local total_stat_value_verb = h.stat_calc_verbose(stats) | |||
-- Format the output: | |||
out[i] = m_util.html.abbr( | |||
string.format('%0.0f', total_stat_value), | |||
string.format( | |||
'Lvl. %s: %s', | |||
result['monster_base_stats.level'], | |||
total_stat_value_verb | |||
) | |||
) | |||
end | |||
return table.concat(out, ', ') | |||
end, | |||
}, | |||
{ | |||
header = i18n.tooltips.damage, | |||
func = function(tpl_args, frame) | |||
-- TODO: Should this be stored to cargo? | |||
-- 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 = { | |||
added={ | |||
result['monster_base_stats.damage'], | |||
}, | |||
-- increased={}, | |||
-- more={}, | |||
m1 = tpl_args.damage_multiplier or 1, | |||
m_map = (result['monster_map_multipliers.damage'] or 100)/100 | |||
} | |||
-- Bossses have different multipliers: | |||
if tpl_args.is_boss then | |||
stats.m_map = (result['monster_map_multipliers.boss_damage'] or 100)/100 | |||
end | |||
-- 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, | |||
{ | |||
-- added={'base_maximum_life'}, | |||
-- increased={'maximum_life_+%'}, | |||
more={'monster_rarity_damage_+%_final'}, | |||
less={'monster_rarity_attack_cast_speed_+%_and_damage_-%_final'}, | |||
}, | |||
v | |||
) | |||
end | |||
end | |||
-- Calculate the total stat value: | -- Calculate the total stat value: | ||
local | local total_stat_value = h.stat_calc(stats) | ||
local | local total_stat_value_verb = h.stat_calc_verbose(stats) | ||
-- Format the output: | -- Format the output: | ||
out[i] = m_util.html.abbr( | out[i] = m_util.html.abbr( | ||
string.format('%0.0f', | string.format('%0.0f', total_stat_value), | ||
string.format( | string.format( | ||
'Lvl. %s: %s', | 'Lvl. %s: %s', | ||
result['monster_base_stats.level'], | result['monster_base_stats.level'], | ||
total_stat_value_verb | |||
) | ) | ||
) | ) | ||
end | end | ||
return table.concat(out, ', ') | return table.concat(out, ', ') | ||
end, | end, | ||
Line 999: | Line 1,110: | ||
:done() | :done() | ||
:done() | :done() | ||
local difficulties = {'part1', 'part2', 'maps'} | local difficulties = {'part1', 'part2', 'maps'} | ||
local elements = {'fire', 'cold', 'lightning', 'chaos'} | local elements = {'fire', 'cold', 'lightning', 'chaos'} | ||
Line 1,010: | Line 1,121: | ||
for _, element in ipairs(elements) do | for _, element in ipairs(elements) do | ||
local field = string.format( | local field = string.format( | ||
'monster_resistances.%s_%s', | 'monster_resistances.%s_%s', | ||
k, | k, | ||
element | element | ||
) | ) | ||
Line 1,021: | Line 1,132: | ||
end | end | ||
end | end | ||
-- -- Compressed resistance table: | -- -- Compressed resistance table: | ||
-- local tbl = mw.html.create('table') | -- local tbl = mw.html.create('table') | ||
Line 1,027: | Line 1,138: | ||
-- local res = {} | -- local res = {} | ||
-- for _, element in ipairs(elements) do | -- for _, element in ipairs(elements) do | ||
-- if res[element] == nil then | -- if res[element] == nil then | ||
-- res[element] = {} | -- res[element] = {} | ||
-- end | -- end | ||
-- for _, k in ipairs(difficulties) do | -- for _, k in ipairs(difficulties) do | ||
-- local r = string.format('monster_resistances.%s_%s', k, element) | -- local r = string.format('monster_resistances.%s_%s', k, element) | ||
-- res[element][#res[element]+1] = m_util.html.abbr( | -- res[element][#res[element]+1] = m_util.html.abbr( | ||
-- tpl_args.monster_type[r], | -- tpl_args.monster_type[r], | ||
-- k | -- k | ||
-- ) | -- ) | ||
Line 1,043: | Line 1,154: | ||
-- :done() | -- :done() | ||
-- end | -- end | ||
return tostring(tbl) | return tostring(tbl) | ||
end, | end, | ||
Line 1,089: | Line 1,200: | ||
return | return | ||
end | end | ||
return table.concat(tpl_args.tags, '<br>') | return table.concat(tpl_args.tags, '<br>') | ||
end, | end, | ||
Line 1,123: | Line 1,234: | ||
--[[ | --[[ | ||
Stores data and display infoboxes of monsters. | Stores data and display infoboxes of monsters. | ||
Example | Example | ||
------- | ------- | ||
Line 1,130: | Line 1,241: | ||
monster_type_id='BanditBoss', | monster_type_id='BanditBoss', | ||
mod_ids='MonsterAttackBlock30Bypass20, MonsterExileLifeInMerciless_', | mod_ids='MonsterAttackBlock30Bypass20, MonsterExileLifeInMerciless_', | ||
tags='red_blood', | tags='red_blood', | ||
skill_ids='Melee, MonsterHeavyStrike', | skill_ids='Melee, MonsterHeavyStrike', | ||
name='Calaf, Headstaver', | name='Calaf, Headstaver', | ||
Line 1,142: | Line 1,253: | ||
critical_strike_chance=5.0, | critical_strike_chance=5.0, | ||
attack_speed=1.35, | attack_speed=1.35, | ||
rarity_id = 'unique' | rarity_id = 'unique' | ||
} | } | ||
]] | ]] | ||
-- Get args | -- Get args | ||
tpl_args = getArgs(frame, { | tpl_args = getArgs(frame, { | ||
Line 1,152: | Line 1,263: | ||
}) | }) | ||
frame = m_util.misc.get_frame(frame) | frame = m_util.misc.get_frame(frame) | ||
tpl_args._mods = {} | tpl_args._mods = {} | ||
-- Parse and store the monster table: | -- Parse and store the monster table: | ||
m_cargo.parse_field_arguments{ | m_cargo.parse_field_arguments{ | ||
Line 1,161: | Line 1,272: | ||
table_map=tables.monsters, | table_map=tables.monsters, | ||
} | } | ||
-- Create the infobox: | -- Create the infobox: | ||
local tbl = mw.html.create('table') | local tbl = mw.html.create('table') | ||
Line 1,167: | Line 1,278: | ||
:attr('class', 'wikitable') | :attr('class', 'wikitable') | ||
:attr('style', 'float:right; margin-left: 10px;') | :attr('style', 'float:right; margin-left: 10px;') | ||
for _, data in ipairs(tbl_view) do | for _, data in ipairs(tbl_view) do | ||
local v = data.func(tpl_args, frame) | local v = data.func(tpl_args, frame) | ||
if v ~= nil and v ~= '' then | if v ~= nil and v ~= '' then | ||
local tr = tbl:tag('tr') | local tr = tbl:tag('tr') | ||
if data.header then | if data.header then | ||
tr:tag('th'):wikitext(data.header):done() | tr:tag('th'):wikitext(data.header):done() | ||
tr:tag('td'):wikitext(v):done() | tr:tag('td'):wikitext(v):done() | ||
else | else | ||
tr:tag('th'):attr('colspan', 2):wikitext(v):done() | tr:tag('th'):attr('colspan', 2):wikitext(v):done() | ||
end | end | ||
end | end | ||
end | end | ||
local out = { | local out = { | ||
tostring(tbl), | tostring(tbl), | ||
Line 1,189: | Line 1,300: | ||
out[#out+1] = data.func(tpl_args, frame) | out[#out+1] = data.func(tpl_args, frame) | ||
end | end | ||
local cats = { | local cats = { | ||
i18n.cats.data, | i18n.cats.data, |
Revision as of 14:55, 2 November 2019
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 f_skill_link = require('Module:Skill link').skill_link
local p = {}
-- ----------------------------------------------------------------------------
-- Strings
-- ----------------------------------------------------------------------------
local i18n = {
cats = {
data = 'Monster data'
},
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 = '[[Act 1|1]]-[[Act 5|5]]',
part2 = '[[Act 5|5]]-[[Act 10|10]]',
maps = '[[Act 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 = 'Internal tags',
metadata_id = 'Metadata id',
monster_type_id = 'Monster type id',
areas = 'Areas',
monster_level = 'Level',
skills = 'Skills',
life = 'Life',
damage = 'Damage',
},
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)
--[[
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 stats to strings.
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.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
-- ----------------------------------------------------------------------------
-- Tables
-- ----------------------------------------------------------------------------
local tables = {}
tables.monsters = {
table = 'monsters',
order = {'metadata_id', 'tags', 'monster_type_id', 'mod_ids', 'part1_mod_ids', 'part2_mod_ids', 'endgame_mod_ids', 'skill_ids', 'name', 'size', 'minimum_attack_distance', 'maximum_attack_distance', 'model_size_multiplier', 'experience_multiplier', 'damage_multiplier', 'health_multiplier', 'critical_strike_chance', 'attack_speed', 'mods', 'is_boss', 'rarity_id', 'rarity'},
fields = {
metadata_id = {
field = 'metadata_id',
type = 'String',
func = function (tpl_args, frame, value)
tpl_args.monster_usages = m_cargo.query(
{'areas', 'maps'},
{
'areas.name',
'areas.id',
'areas.area_level',
'areas.boss_monster_ids',
'areas.main_page',
'areas._pageName',
'maps.area_level',
},
{
join='areas.id=maps.area_id',
where=string.format('areas.boss_monster_ids HOLDS "%s"', value),
}
)
return value
end,
},
monster_type_id = {
field = 'monster_type_id',
type = 'String',
func = function (tpl_args, frame, value)
tpl_args.monster_type = m_cargo.query(
{'monster_types', 'monster_resistances'},
{
'monster_types.tags',
'monster_types.armour_multiplier',
'monster_types.evasion_multiplier',
'monster_types.energy_shield_multiplier',
'monster_types.damage_spread',
'monster_resistances.part1_fire',
'monster_resistances.part1_cold',
'monster_resistances.part1_lightning',
'monster_resistances.part1_chaos',
'monster_resistances.part2_fire',
'monster_resistances.part2_cold',
'monster_resistances.part2_lightning',
'monster_resistances.part2_chaos',
'monster_resistances.maps_fire',
'monster_resistances.maps_cold',
'monster_resistances.maps_lightning',
'monster_resistances.maps_chaos'
},
{
join='monster_types.monster_resistance_id = monster_resistances.id',
where=string.format('monster_types.id = "%s"', value),
}
)[1]
if tpl_args.monster_type['monster_types.tags'] then
local tags = m_util.string.split(
tpl_args.monster_type['monster_types.tags'],
',%s+'
)
-- TODO: Maybe this can be fixed earlier?
if tpl_args.tags == nil then
tpl_args.tags = {}
end
for _, tag in ipairs(tags) do
tpl_args.tags[#tpl_args.tags+1] = tag
end
end
return value
end,
},
mod_ids = {
field = 'mod_ids',
type = 'List (,) of String',
func = h.add_mod_id,
},
part1_mod_ids = {
field = 'part1_mod_ids',
type = 'List (,) of String',
func = h.add_mod_id,
},
part2_mod_ids = {
field = 'part2_mod_ids',
type = 'List (,) of String',
func = h.add_mod_id,
},
endgame_mod_ids = {
field = 'endgame_mod_ids',
type = 'List (,) of String',
func = h.add_mod_id,
},
tags = {
field = 'tags',
type = 'List (,) of String',
},
skill_ids = {
field = 'skill_ids',
type = 'List (,) of String',
--TODO
},
-- add base type info or just parse it?
name = {
field = 'name',
type = 'String',
},
size = {
field = 'size',
type = 'Integer',
},
minimum_attack_distance = {
field = 'minimum_attack_distance',
type = 'Integer',
},
maximum_attack_distance = {
field = 'maximum_attack_distance',
type = 'Integer',
},
model_size_multiplier = {
field = 'model_size_multiplier',
type = 'Float',
},
experience_multiplier = {
field = 'experience_multiplier',
type = 'Float',
},
damage_multiplier = {
field = 'damage_multiplier',
type = 'Float',
},
health_multiplier = {
field = 'health_multiplier',
type = 'Float',
},
critical_strike_chance = {
field = 'critical_strike_chance',
type = 'Float',
},
attack_speed = {
field = 'attack_speed',
type = 'Float',
},
is_boss = {
field = 'is_boss',
type = 'Boolean',
func = function (tpl_args, frame)
-- If the monster is used in some area it's most likely
-- an unique boss:
if #tpl_args.monster_usages > 0 then
tpl_args.is_boss = true
else
tpl_args.is_boss = false
end
return tpl_args.is_boss
end,
},
rarity_id = {
field = 'rarity_id',
type = 'String',
func = function (tpl_args, frame)
--[[
Define the rarity of the monster. There's no obvious
parameter that can be datamined for this so this will
be mostly guess work.
]]
-- User defined rarity takes priority:
if tpl_args.rarity_id ~= nil then
if m_game.constants.rarities[tpl_args.rarity_id] == nil then
error(string.format(i18n.errors.invalid_rarity_id,
tostring(tpl_args.rarity_id)))
end
return tpl_args.rarity_id
end
-- If the monster is used in some area it's most likely
-- an unique boss:
if tpl_args.is_boss then
tpl_args.rarity_id = 'unique'
return tpl_args.rarity_id
end
-- If there are no mods it's probably a normal monster:
if #tpl_args._mods == 0 then
tpl_args.rarity_id = 'normal'
return tpl_args.rarity_id
end
-- Try to determine rarity from mods:
for _, modid in ipairs(tpl_args._mods) do
local mod = tpl_args._mod_data[modid]
-- Check if the mod contains the monster rarity stat:
for _, v in ipairs(mod) do
if v['mod_stats.id'] == 'monster_rarity' then
-- TODO: m_game rarity id does not match the stat:
local int_id = tonumber(v['mod_stats.max']) + 1
for k, row in pairs(m_game.constants.rarities) do
if int_id == row['id'] then
tpl_args.rarity_id = k
return tpl_args.rarity_id
end
end
end
end
end
-- If none of the mods contains the monster rarity
-- stat then it might be an unique:
if tpl_args.rarity_id == nil then
tpl_args.rarity_id = 'unique'
return tpl_args.rarity_id
end
end,
},
rarity = {
field = 'rarity',
type = 'String',
func = function (tpl_args, frame)
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',
},
{
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.max',
},
{
join=[[
mods._pageID=mod_stats._pageID
]],
where=string.format([[
mods.id IN (%s)
]], table.concat(mlist, ',')),
}
),
field='mods.id',
keep_id_field=false,
}
end
end,
},
}
}
tables.monster_types = {
table = 'monster_types',
order = {'id', 'tags', 'monster_resistance_id'},
fields = {
id = {
field = 'id',
type = 'String',
},
tags = {
field = 'tags',
type = 'List (,) of String',
},
monster_resistance_id = {
field = 'monster_resistance_id',
type = 'String',
},
armour_multiplier = {
field = 'armour_multiplier',
type = 'Float',
},
evasion_multiplier = {
field = 'evasion_multiplier',
type = 'Float',
},
energy_shield_multiplier = {
field = 'energy_shield_multiplier',
type = 'Float',
},
damage_spread = {
field = 'damage_spread',
type = 'Float',
},
}
}
tables.monster_resistances = {
table = 'monster_resistances',
order = {'id', 'part1_fire', 'part1_cold', 'part1_lightning',
'part1_chaos', 'part2_fire', 'part2_cold', 'part2_lightning',
'part2_chaos', 'maps_fire', 'maps_cold', 'maps_lightning',
'maps_chaos'},
fields = {
id = {
field = 'id',
type = 'String',
},
part1_fire = {
field = 'part1_fire',
type = 'Integer',
},
part1_cold = {
field = 'part1_cold',
type = 'Integer',
},
part1_lightning = {
field = 'part1_lightning',
type = 'Integer',
},
part1_chaos = {
field = 'part1_chaos',
type = 'Integer',
},
part2_fire = {
field = 'part2_fire',
type = 'Integer',
},
part2_cold = {
field = 'part2_cold',
type = 'Integer',
},
part2_lightning = {
field = 'part2_lightning',
type = 'Integer',
},
part2_chaos = {
field = 'part2_chaos',
type = 'Integer',
},
maps_fire = {
field = 'maps_fire',
type = 'Integer',
},
maps_cold = {
field = 'maps_cold',
type = 'Integer',
},
maps_lightning = {
field = 'maps_lightning',
type = 'Integer',
},
maps_chaos = {
field = 'maps_chaos',
type = 'Integer',
},
}
}
tables.monster_base_stats = {
table = 'monster_base_stats',
order = {'level', 'damage', 'evasion', 'accuracy', 'life', 'experience',
'summon_life'},
fields = {
level = {
field = 'level',
type = 'Integer',
},
damage = {
field = 'damage',
type = 'Float',
},
evasion = {
field = 'evasion',
type = 'Integer',
},
accuracy = {
field = 'accuracy',
type = 'Integer',
},
life = {
field = 'life',
type = 'Integer',
},
experience = {
field = 'experience',
type = 'Integer',
},
summon_life = {
field = 'summon_life',
type = 'Integer',
},
-- whole bunch of other values I have no clue about ...
}
}
tables.monster_map_multipliers = {
table = 'monster_map_multipliers',
order = {'level', 'life', 'damage', 'boss_life', 'boss_damage',
'boss_item_rarity', 'boss_item_quantity'},
fields = {
level = {
field = 'level',
type = 'Integer',
},
life = {
field = 'life',
type = 'Integer',
},
damage = {
field = 'damage',
type = 'Integer',
},
boss_life = {
field = 'boss_life',
type = 'Integer',
},
boss_damage = {
field = 'boss_damage',
type = 'Integer',
},
boss_item_rarity = {
field = 'boss_item_rarity',
type = 'Integer',
},
boss_item_quantity = {
field = 'boss_item_quantity',
type = 'Integer',
},
}
}
tables.monster_life_scaling = {
table = 'monster_life_scaling',
order = {'level', 'magic', 'rare'},
fields = {
level = {
field = 'level',
type = 'Integer',
},
magic = {
field = 'magic',
type = 'Integer',
},
rare = {
field = 'rare',
type = 'Integer',
},
}
}
-- ----------------------------------------------------------------------------
-- Monster box sections
-- ----------------------------------------------------------------------------
local display = {}
function display.value (args)
return function (tpl_args, frame)
local v
if args.sub then
v = tpl_args[args.sub][args.arg]
else
v = tpl_args[args.arg]
end
if v and args.fmt then
return string.format(args.fmt, v)
else
return v
end
end
end
function display.sub_value (args)
return function (tpl_args, frame)
return tpl_args[args.sub][args.arg]
end
end
local tbl_view = {
{
-- header = i18n.tooltips.name,
func = function (tpl_args, frame)
if tpl_args.name == nil then
return
end
local linked_name = string.format(
'[[Monster:%s|%s]]',
string.gsub(tpl_args.metadata_id, '_', '~'),
tpl_args.name
)
return m_util.html.poe_color(tpl_args.rarity_id, linked_name)
end,
},
{
-- header = i18n.tooltips.image,
func = function (tpl_args, frame)
return string.format(
'[[File:%s monster screenshot.jpg|296x500px]]',
tpl_args.name or tpl_args.monster_type_id
)
end,
},
-- {
-- header = i18n.tooltips.rarity,
-- func = display.value{arg='rarity'},
-- },
{
header = i18n.tooltips.areas,
func = function(tpl_args, frame)
local out = {}
for i, v in ipairs(tpl_args.monster_usages) do
out[#out+1] = string.format(
'[[%s|%s]]',
v['areas.main_page'] or v['areas._pageName'],
v['areas.name'] or v['areas.id']
)
end
return table.concat(out, ', ')
end
},
{
header = i18n.tooltips.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'
},
{
'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',
-- 'monster.health_multiplier',
-- Damage:
'monster_base_stats.damage',
'monster_map_multipliers.damage',
'monster_map_multipliers.boss_damage',
},
{
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, '<br>')
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, '<br>')
end,
},
{
header = i18n.tooltips.life,
func = function(tpl_args, frame)
--[[
TODO: Should this be stored to cargo?
]]
-- 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 = {
added={
result['monster_base_stats.life'],
},
increased={
result['monster_life_scaling.' .. tpl_args.rarity_id] or 0,
},
more={
result['mod_stats.max'] or 0,
},
m1 = tpl_args.health_multiplier or 1,
m_map = (result['monster_map_multipliers.life'] or 100)/100
}
-- Bossses have different multipliers:
if tpl_args.is_boss then
stats.m_map = (result['monster_map_multipliers.boss_life'] or 100)/100
end
-- 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,
{
added={'base_maximum_life'},
increased={'maximum_life_+%'},
more={
'maximum_life_+%_final',
'monster_life_+%_final_from_rarity'
},
},
v
)
end
end
-- Calculate the total stat value:
local total_stat_value = h.stat_calc(stats)
local total_stat_value_verb = h.stat_calc_verbose(stats)
-- Format the output:
out[i] = m_util.html.abbr(
string.format('%0.0f', total_stat_value),
string.format(
'Lvl. %s: %s',
result['monster_base_stats.level'],
total_stat_value_verb
)
)
end
return table.concat(out, ', ')
end,
},
{
header = i18n.tooltips.damage,
func = function(tpl_args, frame)
-- TODO: Should this be stored to cargo?
-- 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 = {
added={
result['monster_base_stats.damage'],
},
-- increased={},
-- more={},
m1 = tpl_args.damage_multiplier or 1,
m_map = (result['monster_map_multipliers.damage'] or 100)/100
}
-- Bossses have different multipliers:
if tpl_args.is_boss then
stats.m_map = (result['monster_map_multipliers.boss_damage'] or 100)/100
end
-- 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,
{
-- added={'base_maximum_life'},
-- increased={'maximum_life_+%'},
more={'monster_rarity_damage_+%_final'},
less={'monster_rarity_attack_cast_speed_+%_and_damage_-%_final'},
},
v
)
end
end
-- Calculate the total stat value:
local total_stat_value = h.stat_calc(stats)
local total_stat_value_verb = h.stat_calc_verbose(stats)
-- Format the output:
out[i] = m_util.html.abbr(
string.format('%0.0f', total_stat_value),
string.format(
'Lvl. %s: %s',
result['monster_base_stats.level'],
total_stat_value_verb
)
)
end
return table.concat(out, ', ')
end,
},
{
header = i18n.tooltips.resistances,
func = function (tpl_args, frame)
local tbl = mw.html.create('table')
tbl
:attr('class', 'wikitable')
:tag('tr')
:tag('th')
:wikitext(i18n.tooltips.difficulty)
:attr('rowspan', 2)
:done()
-- :tag('th')
-- :wikitext(i18n.tooltips.resistances)
-- :attr('colspan', 4)
-- :done()
:done()
:tag('tr')
:tag('th')
:wikitext(i18n.tooltips.fire)
:done()
:tag('th')
:wikitext(i18n.tooltips.cold)
:done()
:tag('th')
:wikitext(i18n.tooltips.lightning)
:done()
:tag('th')
:wikitext(i18n.tooltips.chaos)
:done()
:done()
local difficulties = {'part1', 'part2', 'maps'}
local elements = {'fire', 'cold', 'lightning', 'chaos'}
for _, k in ipairs(difficulties) do
local tr = tbl:tag('tr')
tr
:tag('th')
:wikitext(i18n.tooltips[k])
:done()
for _, element in ipairs(elements) do
local field = string.format(
'monster_resistances.%s_%s',
k,
element
)
tr
:tag('td')
:attr('class', 'tc -' .. element)
:wikitext(tpl_args.monster_type[field])
:done()
end
end
-- -- Compressed resistance table:
-- local tbl = mw.html.create('table')
-- local tr = tbl:tag('tr')
-- local res = {}
-- for _, element in ipairs(elements) do
-- if res[element] == nil then
-- res[element] = {}
-- end
-- for _, k in ipairs(difficulties) do
-- local r = string.format('monster_resistances.%s_%s', k, element)
-- res[element][#res[element]+1] = m_util.html.abbr(
-- tpl_args.monster_type[r],
-- k
-- )
-- end
-- tr
-- :tag('td')
-- :attr('class', 'tc -' .. element)
-- :wikitext(table.concat(res[element], '/'))
-- :done()
-- end
return tostring(tbl)
end,
},
{
header = i18n.tooltips.experience_multiplier,
func = display.value{arg='experience_multiplier'},
},
{
header = i18n.tooltips.health_multiplier,
func = display.value{arg='health_multiplier'},
},
{
header = i18n.tooltips.damage_multiplier,
func = display.value{arg='damage_multiplier'},
},
{
header = i18n.tooltips.attack_speed,
func = display.value{arg='attack_speed', fmt='%.3fs',},
},
{
header = i18n.tooltips.critical_strike_chance,
func = display.value{arg='critical_strike_chance', fmt='%.2f%%',},
},
{
header = i18n.tooltips.minimum_attack_distance,
func = display.value{arg='minimum_attack_distance'},
},
{
header = i18n.tooltips.maximum_attack_distance,
func = display.value{arg='maximum_attack_distance'},
},
{
header = i18n.tooltips.size,
func = display.value{arg='size'},
},
{
header = i18n.tooltips.model_size_multiplier,
func = display.value{arg='model_size_multiplier'},
},
{
header = i18n.tooltips.tags,
func = function (tpl_args, frame)
if tpl_args.tags == nil or #tpl_args.tags == 0 then
return
end
return table.concat(tpl_args.tags, '<br>')
end,
},
{
header = i18n.tooltips.metadata_id,
func = display.value{arg='metadata_id'},
},
{
header = i18n.tooltips.monster_type_id,
func = display.value{arg='monster_type_id'},
},
}
local list_view = {
}
-- ----------------------------------------------------------------------------
-- Page views
-- ----------------------------------------------------------------------------
p.table_monsters = m_cargo.declare_factory{data=tables.monsters}
p.table_monster_types = m_cargo.declare_factory{data=tables.monster_types}
p.table_monster_resistances = m_cargo.declare_factory{data=tables.monster_resistances}
p.table_monster_base_stats = m_cargo.declare_factory{data=tables.monster_base_stats}
p.table_monster_map_multipliers = m_cargo.declare_factory{data=tables.monster_map_multipliers}
p.table_monster_life_scaling = m_cargo.declare_factory{data=tables.monster_life_scaling}
p.store_data = m_cargo.store_from_lua{tables=tables, module='Monster'}
function p.monster(frame)
--[[
Stores data and display infoboxes of monsters.
Example
-------
= p.monster{
metadata_id='Metadata/Monsters/Bandits/BanditBossHeavyStrike_',
monster_type_id='BanditBoss',
mod_ids='MonsterAttackBlock30Bypass20, 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'
}
]]
-- Get args
tpl_args = getArgs(frame, {
parentFirst = true
})
frame = m_util.misc.get_frame(frame)
tpl_args._mods = {}
-- Parse and store the monster table:
m_cargo.parse_field_arguments{
tpl_args=tpl_args,
frame=frame,
table_map=tables.monsters,
}
-- Create the infobox:
local tbl = mw.html.create('table')
tbl
:attr('class', 'wikitable')
:attr('style', 'float:right; margin-left: 10px;')
for _, data in ipairs(tbl_view) do
local v = data.func(tpl_args, frame)
if v ~= nil and v ~= '' then
local tr = tbl:tag('tr')
if data.header then
tr:tag('th'):wikitext(data.header):done()
tr:tag('td'):wikitext(v):done()
else
tr:tag('th'):attr('colspan', 2):wikitext(v):done()
end
end
end
local out = {
tostring(tbl),
h.intro_text(tpl_args, frame),
}
for _, data in ipairs(list_view) do
out[#out+1] = data.func(tpl_args, frame)
end
local cats = {
i18n.cats.data,
}
return table.concat(out) .. m_util.misc.add_category(cats)
end
return p