Path of Exile Wiki

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

ПОДРОБНЕЕ

Path of Exile Wiki
Нет описания правки
Нет описания правки
(не показано 17 промежуточных версий 2 участников)
Строка 16: Строка 16:
 
local i18n = {
 
local i18n = {
 
cats = {
 
cats = {
data = 'Monster data'
+
data = 'Данные монстров',
  +
boss = 'Босс',
 
},
 
},
 
tooltips = {
 
tooltips = {
Строка 48: Строка 49:
 
life = 'Здоровье',
 
life = 'Здоровье',
 
damage = 'Урон',
 
damage = 'Урон',
  +
aps = 'Attacks per second',
  +
critical_strike_chance_total = 'Critical strike chance',
  +
armour = 'Armour rating',
  +
evasion = 'Evasion rating',
  +
accuracy = 'Accuracy rating',
  +
experience = 'Experience',
  +
summon_life = 'Summon life',
  +
monster_data = 'Monster data',
  +
 
},
 
},
   
Строка 212: Строка 222:
 
function h.stat_match(stats, strings, result)
 
function h.stat_match(stats, strings, result)
 
--[[
 
--[[
Match stats to strings.
+
Match strings to ids in result and append them to stats.
  +
  +
Parameters
  +
----------
  +
stats : Array, required.
  +
Array to append values to.
  +
strings : array, required.
  +
Array of ids to to match result to.
  +
result : array, required.
  +
Row result from a cargo_query. Must contain 'mod_stats.id' and
  +
'mod_stats.max'.
   
 
Examples
 
Examples
Строка 252: Строка 272:
 
--[[
 
--[[
 
Format the stat value.
 
Format the stat value.
  +
  +
Parameters
  +
----------
  +
args : Array, required.
  +
Array of arguments to modify the stat format. Supported keys are:
  +
args.fmt
  +
args.level
  +
args.value
  +
args.value_verbose
 
]]
 
]]
   
local fmt = '%0.0f'
+
local fmt = '%0.2f'
 
if args.value > 10000 then
 
if args.value > 10000 then
 
fmt = '%0.3E'
 
fmt = '%0.3E'
  +
elseif args.value > 100 then
  +
fmt = '%0.0f'
 
end
 
end
 
return m_util.html.abbr(
 
return m_util.html.abbr(
string.format(fmt, args.value),
+
string.format(args.fmt or fmt, args.value),
 
string.format('Lvl. %0.0f: %s', args.level, args.value_verbose)
 
string.format('Lvl. %0.0f: %s', args.level, args.value_verbose)
 
)
 
)
 
end
 
end
  +
  +
function h.stat_calc_per_monster_level(tpl_args, frame, f_stats, f_strings)
  +
--[[
  +
Calculate the total stat value per monster level.
  +
  +
Parameters
  +
----------
  +
f_stats : Function, required.
  +
Function returning an array of stat values per monster level.
  +
f_strings : function, required.
  +
Function returning an array of stat ids to match to cargo results per
  +
monster level.
  +
]]
  +
  +
-- Stop if no monster level was found:
  +
if #tpl_args.monster_level == 0 then
  +
return nil
  +
end
  +
  +
-- Calculate the total stat value for each monster level:
  +
local out = {}
  +
for i, result in ipairs(tpl_args._mod_data['monster_level']) do
  +
-- Initial stats for monsters:
  +
local stats = f_stats(tpl_args, frame, result)
  +
  +
-- Append matching stats from the modifiers:
  +
for _, modid in ipairs(tpl_args._mods) do
  +
local mod = tpl_args._mod_data[modid]
  +
for _, v in ipairs(mod) do
  +
h.stat_match(
  +
stats,
  +
f_strings(tpl_args, frame, result),
  +
v
  +
)
  +
end
  +
end
  +
  +
-- Calculate the total stat value and format the output:
  +
out[i] = h.stat_format{
  +
level=result['monster_base_stats.level'],
  +
value=h.stat_calc(stats),
  +
value_verbose=h.stat_calc_verbose(stats),
  +
}
  +
end
  +
  +
return table.concat(out, ', ')
  +
end
  +
   
 
function h.intro_text(tpl_args, frame)
 
function h.intro_text(tpl_args, frame)
Строка 291: Строка 370:
   
 
return table.concat(out)
 
return table.concat(out)
  +
end
  +
  +
function h.info_box(tpl_args, frame, tbl_view)
  +
-- Create the infobox:
  +
local container = mw.html.create('div')
  +
container
  +
:attr('class', 'modbox floatright')
  +
  +
local tbl = container:tag('table')
  +
tbl
  +
:attr('class', 'wikitable')
  +
-- :attr('style', 'float:right; margin-left: 10px;')
  +
  +
for _, data in ipairs(tbl_view) do
  +
local v = data.func(tpl_args, frame)
  +
  +
if v ~= nil and v ~= '' then
  +
local tr = tbl:tag('tr')
  +
if data.header then
  +
tr:tag('th'):wikitext(data.header):done()
  +
tr:tag('td'):wikitext(v):done()
  +
else
  +
tr:tag('th'):attr('colspan', 2):wikitext(v):done()
  +
end
  +
end
  +
end
  +
  +
return tostring(container)
  +
end
  +
  +
function h.stat_box(tpl_args, frame)
  +
--[[
  +
Display the stat box.
  +
]]
  +
local container = mw.html.create('div')
  +
container
  +
:attr('class', 'modbox floatright')
  +
  +
-- stat table
  +
local tbl = container:tag('table')
  +
tbl
  +
:attr('class', 'wikitable sortable')
  +
-- :attr('style', 'style="width: 100%;"')
  +
:tag('tr')
  +
:tag('th')
  +
:attr('colspan', 4)
  +
:wikitext('Stats')
  +
:done()
  +
:done()
  +
:tag('tr')
  +
:tag('th')
  +
:wikitext('#')
  +
:done()
  +
:tag('th')
  +
:wikitext('Stat Id')
  +
:done()
  +
:tag('th')
  +
:wikitext('Min')
  +
:done()
  +
:tag('th')
  +
:wikitext('Max')
  +
:done()
  +
:done()
  +
:done()
  +
  +
local i = 0
  +
for _, modid in ipairs(tpl_args._mods) do
  +
local mod = tpl_args._mod_data[modid]
  +
for k, v in ipairs(mod) do
  +
if v['mod_stats.id'] then
  +
i = i + 1
  +
  +
local linked_stat = v['mod_stats.id']
  +
if v['mod_stats._pageName'] then
  +
linked_stat = string.format(
  +
'[[%s|%s]]',
  +
v['mod_stats._pageName'],
  +
v['mod_stats.id']
  +
)
  +
end
  +
  +
tbl
  +
:tag('tr')
  +
:tag('td')
  +
:wikitext(i)
  +
:done()
  +
:tag('td')
  +
:wikitext(linked_stat)
  +
:done()
  +
:tag('td')
  +
:wikitext(v['mod_stats.min'])
  +
:done()
  +
:tag('td')
  +
:wikitext(v['mod_stats.max'])
  +
:done()
  +
:done()
  +
:done()
  +
end
  +
end
  +
end
  +
  +
return tostring(container)
 
end
 
end
   
Строка 300: Строка 481:
 
tables.monsters = {
 
tables.monsters = {
 
table = 'monsters',
 
table = 'monsters',
  +
order = {
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'},
 
  +
'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 = {
 
fields = {
 
metadata_id = {
 
metadata_id = {
Строка 307: Строка 511:
 
func = function (tpl_args, frame, value)
 
func = function (tpl_args, frame, value)
 
tpl_args.monster_usages = m_cargo.query(
 
tpl_args.monster_usages = m_cargo.query(
{'areas', 'maps'},
+
{'areas', 'maps', 'items'},
 
{
 
{
 
'areas.name',
 
'areas.name',
Строка 313: Строка 517:
 
'areas.area_level',
 
'areas.area_level',
 
'areas.boss_monster_ids',
 
'areas.boss_monster_ids',
  +
'areas.modifier_ids',
 
'areas.main_page',
 
'areas.main_page',
 
'areas._pageName',
 
'areas._pageName',
 
'maps.area_level',
 
'maps.area_level',
  +
'maps._pageName',
  +
'items.drop_enabled'
 
},
 
},
 
{
 
{
join='areas.id=maps.area_id',
+
join=[[
where=string.format('areas.boss_monster_ids HOLDS "%s"', value),
+
areas.id=maps.area_id,
  +
maps._pageID=items._pageID
  +
]],
  +
where=m_cargo.replace_holds{
  +
string=string.format(
  +
[[
  +
CASE WHEN maps.area_level IS NOT NULL THEN
  +
areas.boss_monster_ids HOLDS "%s"
  +
AND items.drop_enabled = True
  +
ELSE
  +
areas.boss_monster_ids HOLDS "%s"
  +
END
  +
]],
  +
value,
  +
value
  +
),
  +
mode='regex'
  +
},
  +
orderBy='maps.area_level, areas.area_level'
 
}
 
}
 
)
 
)
Строка 355: Строка 580:
 
where=string.format('monster_types.id = "%s"', value),
 
where=string.format('monster_types.id = "%s"', value),
 
}
 
}
)[1]
+
)[1] or {}
   
 
if tpl_args.monster_type['monster_types.tags'] then
 
if tpl_args.monster_type['monster_types.tags'] then
Строка 535: Строка 760:
 
'mod_stats.max',
 
'mod_stats.max',
 
'mod_stats.min',
 
'mod_stats.min',
  +
'mod_stats._pageName',
 
},
 
},
 
{
 
{
Строка 584: Строка 810:
 
'mods.generation_type',
 
'mods.generation_type',
 
'mod_stats.id',
 
'mod_stats.id',
  +
'mod_stats.min',
 
'mod_stats.max',
 
'mod_stats.max',
  +
'mod_stats._pageName',
 
},
 
},
 
{
 
{
Строка 716: Строка 944:
 
evasion = {
 
evasion = {
 
field = 'evasion',
 
field = 'evasion',
  +
type = 'Integer',
  +
},
  +
armour = {
  +
field = 'armour',
 
type = 'Integer',
 
type = 'Integer',
 
},
 
},
Строка 840: Строка 1072:
 
-- header = i18n.tooltips.image,
 
-- header = i18n.tooltips.image,
 
func = function (tpl_args, frame)
 
func = function (tpl_args, frame)
  +
local image_name = tpl_args.name or tpl_args.monster_type_id
  +
image_name = string.gsub(image_name, '[%[%]]', '')
 
return string.format(
 
return string.format(
 
'[[File:%s monster screenshot.jpg|296x500px]]',
 
'[[File:%s monster screenshot.jpg|296x500px]]',
tpl_args.name or tpl_args.monster_type_id
+
image_name
 
)
 
)
 
end,
 
end,
Строка 898: Строка 1132:
 
'monster_map_multipliers.life',
 
'monster_map_multipliers.life',
 
'monster_map_multipliers.boss_life',
 
'monster_map_multipliers.boss_life',
  +
 
 
-- Damage:
 
-- Damage:
 
'monster_base_stats.damage',
 
'monster_base_stats.damage',
 
'monster_map_multipliers.damage',
 
'monster_map_multipliers.damage',
 
'monster_map_multipliers.boss_damage',
 
'monster_map_multipliers.boss_damage',
  +
 
  +
'monster_base_stats.armour',
 
'monster_base_stats.evasion',
 
'monster_base_stats.evasion',
 
'monster_base_stats.accuracy',
 
'monster_base_stats.accuracy',
Строка 964: Строка 1199:
 
header = i18n.tooltips.life,
 
header = i18n.tooltips.life,
 
func = function(tpl_args, frame)
 
func = function(tpl_args, frame)
  +
local f_stats = function(tpl_args, frame, result)
--[[
 
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 = {
 
local stats = {
 
added={
 
added={
Строка 985: Строка 1208:
 
},
 
},
 
-- more={},
 
-- more={},
m1 = tpl_args.health_multiplier or 1,
+
m_map = (tpl_args.health_multiplier or 1) + (result['monster_map_multipliers.life'] or 0)
m_map = (result['monster_map_multipliers.life'] or 0) + 1
 
 
}
 
}
 
-- Bossses have different multipliers:
 
 
if tpl_args.is_boss then
 
if tpl_args.is_boss then
-- stats.increased[#stats.increased+1] = (result['monster_map_multipliers.boss_life'] or 0)
+
stats.m_map = stats.m_map + (result['monster_map_multipliers.boss_life'] or 0)/100
stats.m_map2 = (result['monster_map_multipliers.boss_life'] or 0)/100 + 1
 
 
end
 
end
   
-- Append matching stats from the modifiers:
+
return stats
for _, modid in ipairs(tpl_args._mods) do
+
end
  +
local mod = tpl_args._mod_data[modid]
 
for _, v in ipairs(mod) do
+
local f_strings = function(tpl_args, frame, result)
h.stat_match(
+
local strings = {
stats,
+
added={'base_maximum_life'},
{
+
increased={'maximum_life_+%', 'map_monsters_life_+%'},
added={'base_maximum_life'},
+
more={
increased={'maximum_life_+%'},
+
'maximum_life_+%_final',
more={
+
'monster_life_+%_final_from_rarity',
'maximum_life_+%_final',
+
},
'monster_life_+%_final_from_rarity',
+
}
},
+
if tpl_args.is_boss then
},
+
table.insert(strings.increased, 'map_boss_maximum_life_+%')
v
 
)
 
end
 
 
end
 
end
   
-- Calculate the total stat value and format the output:
+
return strings
out[i] = h.stat_format{
 
level=result['monster_base_stats.level'],
 
value=h.stat_calc(stats),
 
value_verbose=h.stat_calc_verbose(stats),
 
}
 
 
end
 
end
   
return table.concat(out, ', ')
+
return h.stat_calc_per_monster_level(tpl_args, frame, f_stats, f_strings)
  +
 
end,
 
end,
 
},
 
},
Строка 1028: Строка 1240:
 
header = i18n.tooltips.damage,
 
header = i18n.tooltips.damage,
 
func = function(tpl_args, frame)
 
func = function(tpl_args, frame)
-- TODO: Should this be stored to cargo?
+
local f_stats = function(tpl_args, frame, result)
 
-- 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 = {
 
local stats = {
 
added={
 
added={
Строка 1045: Строка 1247:
 
-- increased={},
 
-- increased={},
 
-- more={},
 
-- more={},
m1 = tpl_args.damage_multiplier or 1,
+
m_map = (tpl_args.damage_multiplier or 1) + (result['monster_map_multipliers.damage'] or 0)
m_map = (result['monster_map_multipliers.damage'] or 0) + 1
 
 
}
 
}
  +
if tpl_args.is_boss then
  +
stats.m_map = stats.m_map + (result['monster_map_multipliers.boss_damage'] or 0)
  +
end
   
-- Bossses have different multipliers:
+
return stats
  +
end
  +
  +
local f_strings = function(tpl_args, frame, result)
  +
local strings = {
  +
-- added={},
  +
increased={'map_monsters_damage_+%'},
  +
more={'monster_rarity_damage_+%_final'},
  +
less={
  +
'monster_rarity_attack_cast_speed_+%_and_damage_-%_final',
  +
'monster_base_type_attack_cast_speed_+%_and_damage_-%_final',
  +
},
  +
}
 
if tpl_args.is_boss then
 
if tpl_args.is_boss then
stats.m_map = (result['monster_map_multipliers.boss_damage'] or 0) + 1
+
table.insert(strings.increased, 'map_boss_damage_+%')
 
end
 
end
   
-- Append matching stats from the modifiers:
+
return strings
for _, modid in ipairs(tpl_args._mods) do
+
end
  +
local mod = tpl_args._mod_data[modid]
 
  +
return h.stat_calc_per_monster_level(tpl_args, frame, f_stats, f_strings)
for _, v in ipairs(mod) do
 
h.stat_match(
+
end,
  +
},
stats,
 
  +
{
{
 
-- added={},
+
header = i18n.tooltips.aps,
  +
func = function(tpl_args, frame)
-- increased={},
 
  +
local f_stats = function(tpl_args, frame, result)
more={'monster_rarity_damage_+%_final'},
 
  +
local stats = {
less={'monster_rarity_attack_cast_speed_+%_and_damage_-%_final'},
 
},
+
added={
v
+
tpl_args.attack_speed or 1,
)
+
},
end
+
}
  +
return stats
  +
end
  +
  +
local f_strings = function(tpl_args, frame, result)
  +
local strings = {
  +
increased={'map_monsters_attack_speed_+%'}, -- map_monsters_cast_speed_+%
  +
more={
  +
'monster_rarity_attack_cast_speed_+%_and_damage_-%_final',
  +
'monster_base_type_attack_cast_speed_+%_and_damage_-%_final',
  +
},
  +
}
  +
if tpl_args.is_boss then
  +
table.insert(strings.increased, 'map_boss_attack_and_cast_speed_+%')
 
end
 
end
   
-- Calculate the total stat value and format the output:
+
return strings
out[i] = h.stat_format{
+
end
  +
level=result['monster_base_stats.level'],
 
  +
return h.stat_calc_per_monster_level(tpl_args, frame, f_stats, f_strings)
value=h.stat_calc(stats),
 
  +
end,
value_verbose=h.stat_calc_verbose(stats),
 
  +
},
  +
{
  +
header = i18n.tooltips.critical_strike_chance_total,
  +
func = function(tpl_args, frame)
  +
local f_stats = function(tpl_args, frame, result)
  +
local stats = {
  +
added={
  +
tpl_args.critical_strike_chance,
  +
},
 
}
 
}
  +
return stats
 
end
 
end
   
return table.concat(out, ', ')
+
local f_strings = function(tpl_args, frame, result)
  +
local strings = {
  +
increased={'map_monsters_critical_strike_chance_+%'},
  +
}
  +
  +
return strings
  +
end
  +
  +
return h.stat_calc_per_monster_level(tpl_args, frame, f_stats, f_strings)
  +
end,
  +
},
  +
{
  +
header = i18n.tooltips.armour,
  +
func = function(tpl_args, frame)
  +
local f_stats = function(tpl_args, frame, result)
  +
local stats = {
  +
added={
  +
result['monster_base_stats.armour'],
  +
},
  +
}
  +
return stats
  +
end
  +
  +
local f_strings = function(tpl_args, frame, result)
  +
local strings = {
  +
-- more={},
  +
}
  +
  +
return strings
  +
end
  +
  +
return h.stat_calc_per_monster_level(tpl_args, frame, f_stats, f_strings)
  +
end,
  +
},
  +
{
  +
header = i18n.tooltips.evasion,
  +
func = function(tpl_args, frame)
  +
local f_stats = function(tpl_args, frame, result)
  +
local stats = {
  +
added={
  +
result['monster_base_stats.evasion'],
  +
},
  +
}
  +
return stats
  +
end
  +
  +
local f_strings = function(tpl_args, frame, result)
  +
local strings = {
  +
-- more={},
  +
}
  +
  +
return strings
  +
end
  +
  +
return h.stat_calc_per_monster_level(tpl_args, frame, f_stats, f_strings)
  +
end,
  +
},
  +
{
  +
header = i18n.tooltips.accuracy,
  +
func = function(tpl_args, frame)
  +
local f_stats = function(tpl_args, frame, result)
  +
local stats = {
  +
added={
  +
result['monster_base_stats.accuracy'],
  +
},
  +
}
  +
return stats
  +
end
  +
  +
local f_strings = function(tpl_args, frame, result)
  +
local strings = {
  +
increased = {'map_monsters_accuracy_rating_+%'},
  +
-- more={},
  +
}
  +
  +
return strings
  +
end
  +
  +
return h.stat_calc_per_monster_level(tpl_args, frame, f_stats, f_strings)
 
end,
 
end,
 
},
 
},
Строка 1158: Строка 1475:
   
 
return tostring(tbl)
 
return tostring(tbl)
  +
end,
  +
},
  +
{
  +
header = i18n.tooltips.experience,
  +
func = function(tpl_args, frame)
  +
local f_stats = function(tpl_args, frame, result)
  +
local stats = {
  +
added={
  +
result['monster_base_stats.experience'],
  +
},
  +
m = tpl_args.experience_multiplier,
  +
}
  +
return stats
  +
end
  +
  +
local f_strings = function(tpl_args, frame, result)
  +
local strings = {
  +
increased={'monster_slain_experience_+%'},
  +
}
  +
  +
return strings
  +
end
  +
  +
return h.stat_calc_per_monster_level(tpl_args, frame, f_stats, f_strings)
  +
end,
  +
},
  +
{
  +
header = i18n.tooltips.summon_life,
  +
func = function(tpl_args, frame)
  +
-- Uniques cannot be summoned:
  +
if tpl_args.rarity_id == 'unique' then
  +
return nil
  +
end
  +
  +
local f_stats = function(tpl_args, frame, result)
  +
local stats = {
  +
added={
  +
result['monster_base_stats.summon_life'],
  +
},
  +
m = tpl_args.experience_multiplier,
  +
}
  +
return stats
  +
end
  +
  +
local f_strings = function(tpl_args, frame, result)
  +
local strings = {
  +
-- increased={},
  +
}
  +
  +
return strings
  +
end
  +
  +
return h.stat_calc_per_monster_level(tpl_args, frame, f_stats, f_strings)
  +
end,
  +
},
  +
{
  +
header = i18n.tooltips.metadata_id,
  +
func = display.value{arg='metadata_id'},
  +
},
  +
  +
}
  +
  +
local tbl_view_detailed = {
  +
{
  +
func = function(tpl_args, frame)
  +
return i18n.tooltips.monster_data
  +
end,
  +
},
  +
{
  +
header = i18n.tooltips.metadata_id,
  +
func = display.value{arg='metadata_id'},
  +
},
  +
{
  +
header = i18n.tooltips.monster_type_id,
  +
func = display.value{arg='monster_type_id'},
  +
},
  +
{
  +
header = i18n.tooltips.tags,
  +
func = function (tpl_args, frame)
  +
if tpl_args.tags == nil or #tpl_args.tags == 0 then
  +
return
  +
end
  +
  +
return table.concat(tpl_args.tags, '<br>')
 
end,
 
end,
 
},
 
},
Строка 1174: Строка 1575:
 
{
 
{
 
header = i18n.tooltips.attack_speed,
 
header = i18n.tooltips.attack_speed,
func = display.value{arg='attack_speed', fmt='%.3fs',},
+
func = display.value{arg='attack_speed', fmt='%.3fs<sup>-1</sup>',},
 
},
 
},
 
{
 
{
Строка 1195: Строка 1596:
 
header = i18n.tooltips.model_size_multiplier,
 
header = i18n.tooltips.model_size_multiplier,
 
func = display.value{arg='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 = {
 
local list_view = {
 
}
 
}
Строка 1294: Строка 1676:
 
}
 
}
   
-- Create the infobox:
+
-- Create the infoboxes:
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 = {
 
local out = {
tostring(tbl),
+
h.info_box(tpl_args, frame, tbl_view),
  +
h.info_box(tpl_args, frame, tbl_view_detailed),
  +
h.stat_box(tpl_args, frame),
 
h.intro_text(tpl_args, frame),
 
h.intro_text(tpl_args, frame),
 
}
 
}
Строка 1322: Строка 1687:
 
end
 
end
   
  +
-- Categories:
 
local cats = {
 
local cats = {
 
i18n.cats.data,
 
i18n.cats.data,
 
}
 
}
  +
local cats_type
  +
if tpl_args.is_boss then
  +
cats_type = i18n.cats.boss
  +
else
  +
cats_type = tpl_args.rarity
  +
end
  +
cats[#cats+1] = string.format('%s (%s)', string.lower(i18n.cats.data), cats_type)
  +
 
return table.concat(out) .. m_util.misc.add_category(cats)
 
return table.concat(out) .. m_util.misc.add_category(cats)
 
end
 
end

Версия от 08:35, 19 мая 2021

-- ----------------------------------------------------------------------------
-- 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 = 'Данные монстров',
        boss = 'Босс',
    },
    tooltips = {
        name = 'Название',
        rarity = 'Редкость',
        experience_multiplier = 'Базовый множитель опыта', 
        health_multiplier = 'Базовый множитель здоровья',
        damage_multiplier = 'Базовый множитель урона',
        attack_speed = 'Базовая скорость атаки',
        critical_strike_chance = 'Базовый шанс критического удара',
        minimum_attack_distance = 'Минимальное расстояние атаки',
        maximum_attack_distance = 'Максимальное расстояние атаки',
        difficulty = 'Акт',
        resistances = 'Сопротивления',
        part1 = '[[Акт 1|1]]-[[Акт 5|5]]',
        part2 = '[[Акт 5|5]]-[[Акт 10|10]]',
        maps = '[[Акт 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 = 'Эффекты от свойств',
        size = 'Размер',
        model_size_multiplier = 'Множитель размера модели',
        tags = 'Внутренние метки',
        metadata_id = 'Metadata id',
        monster_type_id = 'Monster type id',
        area = 'Область',
        monster_level = 'Уровень',
        skills = 'Умения',
        life = 'Здоровье',
        damage = 'Урон',
        aps = 'Attacks per second',
        critical_strike_chance_total = 'Critical strike chance',
        armour = 'Armour rating',
        evasion = 'Evasion rating',
        accuracy = 'Accuracy rating',
        experience = 'Experience',
        summon_life = 'Summon life',
        monster_data = 'Monster data',

    },

    intro = {
        text_with_name = "'''%s''' — это внутренний идентификатор [[монстр]]а [[%s|%s]]. ",
        text_without_name = "'''%s''' — это внутренний идентификатор безымянного [[монстр]]а. ",
    },

    errors = {
        invalid_rarity_id = 'The rarity id "%s" is invalid. Acceptable values are "normal", "magic", "rare" and "unique".',
    },
}
-- ----------------------------------------------------------------------------
-- Helpers
-- ----------------------------------------------------------------------------
local h = {}

function h.add_mod_id(tpl_args, frame, value)
    --[[
        Add mod ids in an ordered way.
    ]]

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

    for _, id in ipairs(value or {}) do
        if tpl_args._mods[id] == nil then
            tpl_args._mods[id] = true
            tpl_args._mods[#tpl_args._mods+1] = id
        end
    end

    return value
end

function h.stat_calc(stats)
    --[[
    Calculates a modified stat.

    Parameters
    ----------
    stats : List
        Associated List with added, increased, more and less as keys.

    Examples
    --------
    stats = {
        added={6,4},
        increased={100, 50}, -- [%]
        more={20, 30}, -- [%]
    }
    = h.stat_calc(stats)

    ]]
    local funcs = {
        added=function(stats)
            --[[
            Sum the added terms.
            ]]
            local out = 0
            for _, v in ipairs(stats['added']) do
                out = v + out
            end
            return out
        end,
        increased=function(stats)
            --[[
            Sum the increased terms.
            Values should be in percent.
            ]]
            local out = 1
            for _, v in ipairs(stats['increased']) do
                out = v/100 + out
            end
            return out
        end,
        more=function(stats)
            --[[
            Calculate the product of the more terms.
            Values should be in percent.
            ]]
            local out = 1
            for _, v in ipairs(stats['more']) do
                out = (1 + v/100) * out
            end
            return out
        end,
        less=function(stats)
            --[[
            Calculate the product of the less terms.
            Values should be in percent.

            Should only be used for stats that defines directions in the
            stat id. Prefer the more function instead.
            ]]
            local out = 1
            for _, v in ipairs(stats['less']) do
                out = (1 - v/100) * out
            end
            return out
        end,
    }

    local out = 1
    for k, v in pairs(stats) do
        if type(funcs[k]) == 'function' then
            out = funcs[k](stats) * out
        else
            out = v * out
        end
    end

    return out
end

h.stat_calc_verbose = function(stats)
    --[[
    Show how the stats list is calculated.
    ]]
    local verbose_funcs = {
        added=function(stats)
            local st = {}
            for _, v in ipairs(stats['added']) do
                st[#st+1] = v/1
            end
            return string.format('(%s)', table.concat(st, ' + '))
        end,
        increased=function(stats)
            local st = {}
            for _, v in ipairs(stats['increased']) do
                st[#st+1] = v/100
            end
            return string.format('(1 + %s)', table.concat(st, ' + '))
        end,
        more=function(stats)
            local st = {}
            for _, v in ipairs(stats['more']) do
                st[#st+1] = string.format('(1 + %s)', v/100)
            end
            return string.format('(%s)', table.concat(st, ' * '))
        end,
        less=function(stats)
            local st = {}
            for _, v in ipairs(stats['less']) do
                st[#st+1] = string.format('(1 - %s)', v/100)
            end
            return string.format('(%s)', table.concat(st, ' * '))
        end,
    }

    local out = {}
    for k, v in pairs(stats) do
        if type(verbose_funcs[k]) == 'function' then
            out[#out+1] = verbose_funcs[k](stats)
        else
            out[#out+1] = string.format('%s', v)
        end
    end

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

function h.stat_match(stats, strings, result)
    --[[
    Match strings to ids in result and append them to stats.

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

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

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

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

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

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

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

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

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

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

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

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

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


function h.intro_text(tpl_args, frame)
    --[[
    Display an introductory text about the monster data.
    ]]
    local out = {}
    if mw.ustring.find(tpl_args['metadata_id'], '_') then
        out[#out+1] = frame:expandTemplate{
            title='Incorrect title',
            args = {title=tpl_args['id']}
        }
    end

    if tpl_args['name'] then
        out[#out+1] = string.format(
            i18n.intro.text_with_name,
            tpl_args['metadata_id'],
            tpl_args['main_page'] or tostring(mw.title.getCurrentTitle()),
            tpl_args['name']
        )
    else
        out[#out+1] = string.format(
            i18n.intro.text_without_name,
            tpl_args['metadata_id']
        )
    end

    return table.concat(out)
end

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

    local tbl = container:tag('table')
    tbl
        :attr('class', 'wikitable')
        -- :attr('style', 'float:right; margin-left: 10px;')

    for _, data in ipairs(tbl_view) do
        local v = data.func(tpl_args, frame)

        if v ~= nil and v ~= '' then
            local tr = tbl:tag('tr')
            if data.header then
                tr:tag('th'):wikitext(data.header):done()
                tr:tag('td'):wikitext(v):done()
            else
                tr:tag('th'):attr('colspan', 2):wikitext(v):done()
            end
        end
    end

    return tostring(container)
end

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

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

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

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

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

    return tostring(container)
end

-- ----------------------------------------------------------------------------
-- Tables
-- ----------------------------------------------------------------------------
local tables = {}

tables.monsters = {
    table = 'monsters',
    order = {
        'metadata_id',
        'tags',
        'monster_type_id',
        'mod_ids',
        'part1_mod_ids',
        'part2_mod_ids',
        'endgame_mod_ids',
        'skill_ids',
        'name',
        'size',
        'minimum_attack_distance',
        'maximum_attack_distance',
        'model_size_multiplier',
        'experience_multiplier',
        'damage_multiplier',
        'health_multiplier',
        'critical_strike_chance',
        'attack_speed',
        'mods',
        'is_boss',
        'rarity_id',
        'rarity'
    },
    fields = {
        metadata_id = {
            field = 'metadata_id',
            type = 'String',
            func = function (tpl_args, frame, value)
                tpl_args.monster_usages = m_cargo.query(
                    {'areas', 'maps', 'items'},
                    {
                        'areas.name',
                        'areas.id',
                        'areas.area_level',
                        'areas.boss_monster_ids',
                        'areas.modifier_ids',
                        'areas.main_page',
                        'areas._pageName',
                        'maps.area_level',
                        'maps._pageName',
                        'items.drop_enabled'
                    },
                    {
                        join=[[
                            areas.id=maps.area_id,
                            maps._pageID=items._pageID
                        ]],
                        where=m_cargo.replace_holds{
                            string=string.format(
                                [[
                                    CASE WHEN maps.area_level IS NOT NULL THEN
                                        areas.boss_monster_ids HOLDS "%s"
                                        AND items.drop_enabled = True
                                    ELSE
                                        areas.boss_monster_ids HOLDS "%s"
                                    END
                                ]],
                                value,
                                value
                            ),
                            mode='regex'
                        },
                        orderBy='maps.area_level, areas.area_level'
                    }
                )

                return value
            end,
        },
        monster_type_id = {
            field = 'monster_type_id',
            type = 'String',
            func = function (tpl_args, frame, value)
                tpl_args.monster_type = m_cargo.query(
                    {'monster_types', 'monster_resistances'},
                    {
                        'monster_types.tags',
                        'monster_types.armour_multiplier',
                        'monster_types.evasion_multiplier',
                        'monster_types.energy_shield_multiplier',
                        'monster_types.damage_spread',
                        'monster_resistances.part1_fire',
                        'monster_resistances.part1_cold',
                        'monster_resistances.part1_lightning',
                        'monster_resistances.part1_chaos',
                        'monster_resistances.part2_fire',
                        'monster_resistances.part2_cold',
                        'monster_resistances.part2_lightning',
                        'monster_resistances.part2_chaos',
                        'monster_resistances.maps_fire',
                        'monster_resistances.maps_cold',
                        'monster_resistances.maps_lightning',
                        'monster_resistances.maps_chaos'
                    },
                    {
                        join='monster_types.monster_resistance_id = monster_resistances.id',
                        where=string.format('monster_types.id = "%s"', value),
                    }
                )[1] or {}

                if tpl_args.monster_type['monster_types.tags'] then
                    local tags = m_util.string.split(
                        tpl_args.monster_type['monster_types.tags'],
                        ',%s+'
                    )
                    -- TODO: Maybe this can be fixed earlier?
                    if tpl_args.tags == nil then
                        tpl_args.tags = {}
                    end
                    for _, tag in ipairs(tags) do
                        tpl_args.tags[#tpl_args.tags+1] = tag
                    end
                end

                return value
            end,
        },
        mod_ids = {
            field = 'mod_ids',
            type = 'List (,) of String',
            func = h.add_mod_id,
        },
        part1_mod_ids = {
            field = 'part1_mod_ids',
            type = 'List (,) of String',
            func = h.add_mod_id,
        },
        part2_mod_ids = {
            field = 'part2_mod_ids',
            type = 'List (,) of String',
            func = h.add_mod_id,
        },
        endgame_mod_ids = {
            field = 'endgame_mod_ids',
            type = 'List (,) of String',
            func = h.add_mod_id,
        },
        tags = {
            field = 'tags',
            type = 'List (,) of String',
        },
        skill_ids = {
            field = 'skill_ids',
            type = 'List (,) of String',
            --TODO
        },
        -- add base type info or just parse it?
        name = {
            field = 'name',
            type = 'String',
        },
        size = {
            field = 'size',
            type = 'Integer',
        },
        minimum_attack_distance = {
            field = 'minimum_attack_distance',
            type = 'Integer',
        },
        maximum_attack_distance = {
            field = 'maximum_attack_distance',
            type = 'Integer',
        },
        model_size_multiplier = {
            field = 'model_size_multiplier',
            type = 'Float',
        },
        experience_multiplier = {
            field = 'experience_multiplier',
            type = 'Float',
        },
        damage_multiplier = {
            field = 'damage_multiplier',
            type = 'Float',
        },
        health_multiplier = {
            field = 'health_multiplier',
            type = 'Float',
        },
        critical_strike_chance = {
            field = 'critical_strike_chance',
            type = 'Float',
        },
        attack_speed = {
            field = 'attack_speed',
            type = 'Float',
        },
        is_boss = {
            field = 'is_boss',
            type = 'Boolean',
            func = function (tpl_args, frame)
                -- If the monster is used in some area it's most likely
                -- an unique boss:
                if #tpl_args.monster_usages > 0 then
                    tpl_args.is_boss = true
                else
                    tpl_args.is_boss = false
                end
                return tpl_args.is_boss
            end,
        },
        rarity_id = {
            field = 'rarity_id',
            type = 'String',
            func = function (tpl_args, frame)
                --[[
                    Define the rarity of the monster. There's no obvious
                    parameter that can be datamined for this so this will
                    be mostly guess work.
                ]]

                -- User defined rarity takes priority:
                if tpl_args.rarity_id ~= nil then
                    if m_game.constants.rarities[tpl_args.rarity_id] == nil then
                        error(string.format(i18n.errors.invalid_rarity_id,
                                            tostring(tpl_args.rarity_id)))
                    end
                    return tpl_args.rarity_id
                end

                -- If the monster is used in some area it's most likely
                -- an unique boss:
                if tpl_args.is_boss then
                    tpl_args.rarity_id = 'unique'
                    return tpl_args.rarity_id
                end

                -- If there are no mods it's probably a normal monster:
                if #tpl_args._mods == 0 then
                    tpl_args.rarity_id = 'normal'
                    return tpl_args.rarity_id
                end

                -- Try to determine rarity from mods:
                for _, modid in ipairs(tpl_args._mods) do
                    local mod = tpl_args._mod_data[modid]
                    -- Check if the mod contains the monster rarity stat:
                    for _, v in ipairs(mod) do
                        if v['mod_stats.id'] == 'monster_rarity' then
                            -- TODO: m_game rarity id does not match the stat:
                            local int_id = tonumber(v['mod_stats.max']) + 1
                            for k, row in pairs(m_game.constants.rarities) do
                                if int_id == row['id'] then
                                    tpl_args.rarity_id = k
                                    return tpl_args.rarity_id
                                end
                            end
                        end
                    end
                end

                -- If none of the mods contains the monster rarity
                -- stat then it might be an unique:
                if tpl_args.rarity_id == nil then
                    tpl_args.rarity_id = 'unique'
                    return tpl_args.rarity_id
                end
            end,
        },
        rarity = {
            field = 'rarity',
            type = 'String',
            func = function (tpl_args, frame)

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

                return m_game.constants.rarities[tpl_args.rarity_id]['full']
            end
        },

        --
        -- Processing fields
        --
        mods = {
            func = function (tpl_args, frame)

                -- Format the mod ids for cargo queries:
                local mlist = {}
                for _, key in ipairs(tpl_args._mods) do
                    mlist[#mlist+1] = string.format('"%s"', key)
                end

                tpl_args._mod_data = {}
                if #mlist > 0 then
                    tpl_args._mod_data = m_cargo.map_results_to_id{
                        results=m_cargo.query(
                            {
                                'mods',
                                'mod_stats',
                            },
                            {
                                'mods.id',
                                'mods.stat_text',
                                'mods.generation_type',
                                'mod_stats.id',
                                'mod_stats.min',
                                'mod_stats.max',
                                'mod_stats._pageName',
                            },
                            {
                                join=[[
                                    mods._pageID=mod_stats._pageID
                                ]],
                                where=string.format([[
                                    mods.id IN (%s)
                                ]], table.concat(mlist, ',')),
                            }
                        ),
                        field='mods.id',
                        keep_id_field=false,
                    }
                end
            end,
        },
    }
}

tables.monster_types = {
    table = 'monster_types',
    order = {'id', 'tags', 'monster_resistance_id'},
    fields = {
        id = {
            field = 'id',
            type = 'String',
        },
        tags = {
            field = 'tags',
            type = 'List (,) of String',
        },
        monster_resistance_id = {
            field = 'monster_resistance_id',
            type = 'String',
        },
        armour_multiplier = {
            field = 'armour_multiplier',
            type = 'Float',
        },
        evasion_multiplier = {
            field = 'evasion_multiplier',
            type = 'Float',
        },
        energy_shield_multiplier = {
            field = 'energy_shield_multiplier',
            type = 'Float',
        },
        damage_spread = {
            field = 'damage_spread',
            type = 'Float',
        },
    }
}

tables.monster_resistances = {
    table = 'monster_resistances',
    order = {'id', 'part1_fire', 'part1_cold', 'part1_lightning',
             'part1_chaos', 'part2_fire', 'part2_cold', 'part2_lightning',
             'part2_chaos', 'maps_fire', 'maps_cold', 'maps_lightning',
             'maps_chaos'},
    fields = {
        id = {
            field = 'id',
            type = 'String',
        },
        part1_fire = {
            field = 'part1_fire',
            type = 'Integer',
        },
        part1_cold = {
            field = 'part1_cold',
            type = 'Integer',
        },
        part1_lightning = {
            field = 'part1_lightning',
            type = 'Integer',
        },
        part1_chaos = {
            field = 'part1_chaos',
            type = 'Integer',
        },
        part2_fire = {
            field = 'part2_fire',
            type = 'Integer',
        },
        part2_cold = {
            field = 'part2_cold',
            type = 'Integer',
        },
        part2_lightning = {
            field = 'part2_lightning',
            type = 'Integer',
        },
        part2_chaos = {
            field = 'part2_chaos',
            type = 'Integer',
        },
        maps_fire = {
            field = 'maps_fire',
            type = 'Integer',
        },
        maps_cold = {
            field = 'maps_cold',
            type = 'Integer',
        },
        maps_lightning = {
            field = 'maps_lightning',
            type = 'Integer',
        },
        maps_chaos = {
            field = 'maps_chaos',
            type = 'Integer',
        },
    }
}

tables.monster_base_stats = {
    table = 'monster_base_stats',
    order = {'level', 'damage', 'evasion', 'accuracy', 'life', 'experience',
             'summon_life'},
    fields = {
        level = {
            field = 'level',
            type = 'Integer',
        },
        damage = {
            field = 'damage',
            type = 'Float',
        },
        evasion = {
            field = 'evasion',
            type = 'Integer',
        },
        armour = {
            field = 'armour',
            type = 'Integer',
        },
        accuracy = {
            field = 'accuracy',
            type = 'Integer',
        },
        life = {
            field = 'life',
            type = 'Integer',
        },
        experience = {
            field = 'experience',
            type = 'Integer',
        },
        summon_life = {
            field = 'summon_life',
            type = 'Integer',
        },
        -- whole bunch of other values I have no clue about ...
    }
}

tables.monster_map_multipliers = {
    table = 'monster_map_multipliers',
    order = {'level', 'life', 'damage', 'boss_life', 'boss_damage',
             'boss_item_rarity', 'boss_item_quantity'},
    fields = {
        level = {
            field = 'level',
            type = 'Integer',
        },
        life = {
            field = 'life',
            type = 'Integer',
        },
        damage = {
            field = 'damage',
            type = 'Integer',
        },
        boss_life = {
            field = 'boss_life',
            type = 'Integer',
        },
        boss_damage = {
            field = 'boss_damage',
            type = 'Integer',
        },
        boss_item_rarity = {
            field = 'boss_item_rarity',
            type = 'Integer',
        },
        boss_item_quantity = {
            field = 'boss_item_quantity',
            type = 'Integer',
        },
    }
}

tables.monster_life_scaling = {
    table = 'monster_life_scaling',
    order = {'level', 'magic', 'rare'},
    fields = {
        level = {
            field = 'level',
            type = 'Integer',
        },
        magic = {
            field = 'magic',
            type = 'Integer',
        },
        rare = {
            field = 'rare',
            type = 'Integer',
        },
    }
}

-- ----------------------------------------------------------------------------
-- Monster box sections
-- ----------------------------------------------------------------------------

local display = {}
function display.value (args)
    return function (tpl_args, frame)
        local v
        if args.sub then
            v = tpl_args[args.sub][args.arg]
        else
            v = tpl_args[args.arg]
        end

        if v and args.fmt then
            return string.format(args.fmt, v)
        else
            return v
       end
    end
end

function display.sub_value (args)
    return function (tpl_args, frame)
        return tpl_args[args.sub][args.arg]
    end
end

local tbl_view = {
    {
        -- header = i18n.tooltips.name,
        func = function (tpl_args, frame)
            if tpl_args.name == nil then
                return
            end

            local linked_name = string.format(
                '[[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)
            local image_name = tpl_args.name or tpl_args.monster_type_id
            image_name = string.gsub(image_name, '[%[%]]', '')
            return string.format(
                '[[File:%s monster screenshot.jpg|296x500px]]',
                image_name
            )
        end,
    },
    -- {
        -- header = i18n.tooltips.rarity,
        -- func = display.value{arg='rarity'},
    -- },
    {
        header = i18n.tooltips.area,
        func = function(tpl_args, frame)
            local out = {}
            for i, v in ipairs(tpl_args.monster_usages) do
                out[#out+1] = string.format(
                    '[[%s|%s]]',
                    v['areas.main_page'] or v['areas._pageName'],
                    v['areas.name'] or v['areas.id']
                )
            end

            return table.concat(out, ', ')
        end
    },
    {
        header = i18n.tooltips.monster_level,
        func = function(tpl_args, frame)
            -- Get monster level from the area level unless it's been
            -- user defined.
            local monster_level = {}
            if tpl_args.monster_level then
                monster_level = m_util.string.split(tpl_args.monster_level, ',')
            else
                for _, v in ipairs(tpl_args.monster_usages) do
                    local lvl = v['maps.area_level'] or v['areas.area_level']
                    monster_level[#monster_level+1] = lvl
                end
            end
            tpl_args.monster_level = monster_level

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

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

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

                        'monster_base_stats.armour',
                        'monster_base_stats.evasion',
                        'monster_base_stats.accuracy',
                        'monster_base_stats.experience',
                        'monster_base_stats.summon_life',
                    },
                    {
                        join=[[
                            monster_base_stats.level=monster_life_scaling.level,
                            monster_base_stats.level=monster_map_multipliers.level
                        ]],
                        where=string.format(
                            'monster_base_stats.level IN (%s)',
                            table.concat(tpl_args.monster_level, ', ')
                        ),
                    }
                )
            end

            return table.concat(tpl_args.monster_level, ', ')
        end
    },
    {
        header = i18n.tooltips.stat_text,
        func = function (tpl_args, frame)
            local out = {}
            for _, modid in ipairs(tpl_args._mods) do
                local mod = tpl_args._mod_data[modid] or {}
                local stat_text = {}

                -- Add stat_text for each modifier, ignore duplicates:
                for _, v in ipairs(mod) do
                    if v['mods.stat_text'] then
                        if stat_text[v['mods.stat_text']] == nil then
                            stat_text[v['mods.stat_text']] = true
                            out[#out+1] = v['mods.stat_text']
                        end
                    end
                end
            end

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

                return stats
            end

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

                return strings
            end

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

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

                return stats
            end

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

                return strings
            end

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

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

                return strings
            end

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

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

                return strings
            end

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

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

                return strings
            end

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

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

                return strings
            end

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

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

                return strings
            end

            return h.stat_calc_per_monster_level(tpl_args, frame, f_stats, f_strings)
        end,
    },
    {
        header = i18n.tooltips.resistances,
        func = function (tpl_args, frame)
            local tbl = mw.html.create('table')
            tbl
                :attr('class', 'wikitable')
                :tag('tr')
                    :tag('th')
                        :wikitext(i18n.tooltips.difficulty)
                        :attr('rowspan', 2)
                        :done()
                    -- :tag('th')
                        -- :wikitext(i18n.tooltips.resistances)
                        -- :attr('colspan', 4)
                        -- :done()
                    :done()
                :tag('tr')
                    :tag('th')
                        :wikitext(i18n.tooltips.fire)
                        :done()
                    :tag('th')
                        :wikitext(i18n.tooltips.cold)
                        :done()
                    :tag('th')
                        :wikitext(i18n.tooltips.lightning)
                        :done()
                    :tag('th')
                        :wikitext(i18n.tooltips.chaos)
                        :done()
                    :done()

            local difficulties = {'part1', 'part2', 'maps'}
            local elements = {'fire', 'cold', 'lightning', 'chaos'}
            for _, k in ipairs(difficulties) do
                local tr = tbl:tag('tr')
                tr
                    :tag('th')
                        :wikitext(i18n.tooltips[k])
                        :done()
                for _, element in ipairs(elements) do
                    local field = string.format(
                        'monster_resistances.%s_%s',
                        k,
                        element
                    )
                    tr
                        :tag('td')
                            :attr('class', 'tc -' .. element)
                            :wikitext(tpl_args.monster_type[field])
                            :done()
                end
            end

            -- -- Compressed resistance table:
            -- local tbl = mw.html.create('table')
            -- local tr = tbl:tag('tr')
            -- local res = {}
            -- for _, element in ipairs(elements) do
                -- if res[element] == nil then
                    -- res[element] = {}
                -- end
                -- for _, k in ipairs(difficulties) do
                    -- local r = string.format('monster_resistances.%s_%s', k, element)
                    -- res[element][#res[element]+1] = m_util.html.abbr(
                        -- tpl_args.monster_type[r],
                        -- k
                    -- )
                -- end
                -- tr
                    -- :tag('td')
                        -- :attr('class', 'tc -' .. element)
                        -- :wikitext(table.concat(res[element], '/'))
                        -- :done()
            -- end

            return tostring(tbl)
        end,
    },
    {
        header = i18n.tooltips.experience,
        func = function(tpl_args, frame)
            local f_stats = function(tpl_args, frame, result)
                local stats = {
                    added={
                        result['monster_base_stats.experience'],
                    },
                    m = tpl_args.experience_multiplier,
                }
                return stats
            end

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

                return strings
            end

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

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

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

                return strings
            end

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

}

local tbl_view_detailed = {
    {
        func = function(tpl_args, frame)
            return i18n.tooltips.monster_data
        end,
    },
    {
        header = i18n.tooltips.metadata_id,
        func = display.value{arg='metadata_id'},
    },
    {
        header = i18n.tooltips.monster_type_id,
        func = display.value{arg='monster_type_id'},
    },
    {
        header = i18n.tooltips.tags,
        func = function (tpl_args, frame)
            if tpl_args.tags == nil or #tpl_args.tags == 0 then
                return
            end

           return table.concat(tpl_args.tags, '<br>')
        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<sup>-1</sup>',},
    },
    {
        header = i18n.tooltips.critical_strike_chance,
        func = display.value{arg='critical_strike_chance', fmt='%.2f%%',},
    },
    {
        header = i18n.tooltips.minimum_attack_distance,
        func = display.value{arg='minimum_attack_distance'},
    },
    {
        header = i18n.tooltips.maximum_attack_distance,
        func = display.value{arg='maximum_attack_distance'},
    },
    {
        header = i18n.tooltips.size,
        func = display.value{arg='size'},
    },
    {
        header = i18n.tooltips.model_size_multiplier,
        func = display.value{arg='model_size_multiplier'},
    },
}
local list_view = {
}


-- ----------------------------------------------------------------------------
-- Page views
-- ----------------------------------------------------------------------------

p.table_monsters = m_cargo.declare_factory{data=tables.monsters}
p.table_monster_types = m_cargo.declare_factory{data=tables.monster_types}
p.table_monster_resistances = m_cargo.declare_factory{data=tables.monster_resistances}
p.table_monster_base_stats = m_cargo.declare_factory{data=tables.monster_base_stats}
p.table_monster_map_multipliers = m_cargo.declare_factory{data=tables.monster_map_multipliers}
p.table_monster_life_scaling = m_cargo.declare_factory{data=tables.monster_life_scaling}

p.store_data = m_cargo.store_from_lua{tables=tables, module='Monster'}

function p.monster(frame)
    --[[
    Stores data and display infoboxes of monsters.

    Example
    -------
    = p.monster{
        metadata_id='Metadata/Monsters/Bandits/BanditBossHeavyStrike_',
        monster_type_id='BanditBoss',
        mod_ids='MonsterAttackBlock30Bypass20, MonsterExileLifeInMerciless_',
        tags='red_blood',
        skill_ids='Melee, MonsterHeavyStrike',
        name='Calaf, Headstaver',
        size=3,
        minimum_attack_distance=4,
        maximum_attack_distance=5,
        model_size_multiplier=1.15,
        experience_multiplier=1.0,
        damage_multiplier=1.0,
        health_multiplier=1.0,
        critical_strike_chance=5.0,
        attack_speed=1.35,

        rarity_id = 'unique'
    }

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

    ]]

    -- Get args
    tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = m_util.misc.get_frame(frame)

    tpl_args._mods = {}

    -- Parse and store the monster table:
    m_cargo.parse_field_arguments{
        tpl_args=tpl_args,
        frame=frame,
        table_map=tables.monsters,
    }

    -- Create the infoboxes:
    local out = {
        h.info_box(tpl_args, frame, tbl_view),
        h.info_box(tpl_args, frame, tbl_view_detailed),
        h.stat_box(tpl_args, frame),
        h.intro_text(tpl_args, frame),
    }
    for _, data in ipairs(list_view) do
        out[#out+1] = data.func(tpl_args, frame)
    end

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

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

return p