Path of Exile Wiki

Please consider helping keep the wiki up to date. Check the to-do list of updates needed for version 3.14.0.

Game data exports will becoming later as the technical changes in addition to regular changes take some more time.

READ MORE

Path of Exile Wiki
m (Reverted edits by Aggixx (talk) to last revision by OmegaK2)
No edit summary
(25 intermediate revisions by 3 users not shown)
Line 4: Line 4:
   
 
local getArgs = require('Module:Arguments').getArgs
 
local getArgs = require('Module:Arguments').getArgs
  +
local m_cargo = require('Module:Cargo')
 
local m_util = require('Module:Util')
 
local m_util = require('Module:Util')
 
local m_game = require('Module:Game')
 
local m_game = require('Module:Game')
Line 11: Line 12:
 
--- define here to avoid errors
 
--- define here to avoid errors
 
local data = {}
 
local data = {}
local map = {}
+
local tables = {}
  +
  +
-- TODO:
  +
-- skill_id field link to data page
  +
-- aspects: % mana cost
   
 
-- ----------------------------------------------------------------------------
 
-- ----------------------------------------------------------------------------
Line 20: Line 25:
 
skill_icon = 'File:%s skill icon.png',
 
skill_icon = 'File:%s skill icon.png',
 
skill_screenshot = 'File:%s skill screenshot.jpg',
 
skill_screenshot = 'File:%s skill screenshot.jpg',
  +
  +
-- Intro texts:
  +
intro_named_id = "'''%s''' is the internal id of the [[skill]] '''%s'''.\n",
  +
intro_unnamed_id = "'''%s''' is the internal id of an unnamed [[skill]].\n",
  +
  +
errors = {
  +
all_format_keys_specified = 'All formatting keys must be specified for index "%s"',
  +
no_results_for_skill_id = "Couldn't find a page for the specified skill id",
  +
no_results_for_skill_page = "Couldn't find the queried data on the skill page",
  +
missing_level_data = 'No gem level progression data found',
  +
},
  +
  +
categories = {
  +
broken_progression_table = 'Pages with broken skill progression tables'
  +
},
 
 
 
infobox = {
 
infobox = {
Line 36: Line 56:
 
mana_cost = 'Mana Cost',
 
mana_cost = 'Mana Cost',
 
mana_reserved = 'Mana Reserved',
 
mana_reserved = 'Mana Reserved',
damage_effectiveness = 'Damage Effectiveness',
+
attack_speed_multiplier = 'Attack Speed',
  +
damage_effectiveness = 'Effectiveness of Added Damage',
 
stored_uses = 'Stored Uses',
 
stored_uses = 'Stored Uses',
 
cooldown = 'Cooldown',
 
cooldown = 'Cooldown',
vaal_souls_requirement = m_util.html.abbr('Vaal Souls', 'Vaal Souls cost is based on the first part of the game. Multiply with 1.5 or 2 to get part 2 / end game values'),
+
vaal_souls_requirement = 'Vaal Souls',
 
vaal_stored_uses = 'Vaal Stored Uses',
 
vaal_stored_uses = 'Vaal Stored Uses',
  +
vaal_soul_gain_prevention_time = 'Soul Gain Prevention',
 
damage_multiplier = 'Damage Multiplier',
 
damage_multiplier = 'Damage Multiplier',
  +
duration = 'Base duration',
 
},
 
},
 
 
 
progression = {
 
progression = {
  +
level_requirement = m_util.html.abbr('[[Image:Level_up_icon_small.png|link=|Lvl.]]', 'Required Level', 'nounderline'),
skill_id = 'Skill Id',
 
  +
dexterity_requirement = m_util.html.abbr('[[Image:DexterityIcon_small.png|link=|dexterity]]', 'Required Dexterity', 'nounderline'),
cast_time = 'Cast Time',
 
  +
strength_requirement = m_util.html.abbr('[[Image:StrengthIcon_small.png|link=|strength]]', 'Required Strength', 'nounderline'),
item_class_restrictions = 'Item Class<br>Restrictions',
 
  +
intelligence_requirement = m_util.html.abbr('[[Image:IntelligenceIcon_small.png|link=|intelligence]]', 'Required Intelligence', 'nounderline'),
projectile_speed = 'Projectile Speed',
 
radius = 'Radius',
+
mana_multiplier = 'Mana<br>Multiplier',
radius_secondary = 'Radius 2',
+
critical_strike_chance = 'Critical<br>Strike<br>Chance',
radius_tertiary = 'Radius 3',
+
mana_cost = 'Mana<br>Cost',
level_requirement = 'Level Req.',
+
mana_reserved = 'Mana<br>Reserved',
mana_multiplier = 'Mana Multiplier',
+
attack_speed_multiplier = 'Attack<br>Speed<br>Multiplier',
critical_strike_chance = 'Critical Strike Chance',
+
damage_effectiveness = 'Damage<br>Effectiveness',
mana_cost = 'Mana Cost',
+
stored_uses = 'Stored<br>Uses',
mana_reserved = 'Mana Reserved',
 
damage_effectiveness = 'Damage Effectiveness',
 
stored_uses = 'Stored Uses',
 
 
cooldown = 'Cooldown',
 
cooldown = 'Cooldown',
vaal_souls_requirement = m_util.html.abbr('Vaal Souls', 'Vaal Souls cost is based on the first part of the game. Multiply with 1.5 or 2 to get part 2 / end game values'),
+
vaal_souls_requirement = 'Vaal<br>souls',
vaal_stored_uses = 'Vaal Stored Uses',
+
vaal_stored_uses = 'Stored<br>Uses',
  +
vaal_soul_gain_prevention_time = 'Soul<br>Prevention<br>Time',
damage_multiplier = 'Damage Multiplier',
 
  +
damage_multiplier = m_util.html.abbr('Damage<br>Multiplier', 'Deals x% of Base Damage'),
},
 
  +
duration = m_util.html.abbr('Base duration', 'Base duration is x seconds'),
 
args = {
+
exp_short = 'Exp.',
  +
exp_long = 'Experience needed to level up',
-- skills
 
skill_id = 'skill_id',
+
tot_exp_short = 'Total Exp.',
is_support_gem = 'is_support_gem',
+
tot_exp_long = 'Total experience needed',
support_gem_letter = 'support_gem_letter',
 
cast_time = 'cast_time',
 
gem_description = 'gem_description',
 
active_skill_name = 'active_skill_name',
 
skill_icon = 'skill_icon',
 
item_class_restriction = 'item_class_restriction',
 
projectile_speed = 'projectile_speed',
 
stat_text = 'stat_text',
 
quality_stat_text = 'quality_stat_text',
 
has_percentage_mana_cost = 'has_percentage_mana_cost',
 
has_reservation_mana_cost = 'has_reservation_mana_cost',
 
radius = 'radius',
 
radius_description = 'radius_description',
 
radius_secondary = 'radius_secondary',
 
radius_secondary_description = 'radius_secondary_description',
 
radius_tertiary = 'radius_tertiary',
 
radius_tertiary_description = 'radius_tertiary_description',
 
skill_screenshot = 'skill_screenshot',
 
 
-- skill_levels
 
level_requirement = 'level_requirement',
 
dexterity_requirement = 'dexterity_requirement',
 
intelligence_requirement = 'intelligence_requirement',
 
strength_requirement = 'strength_requirement',
 
mana_multiplier = 'mana_multiplier',
 
critical_strike_chance = 'critical_strike_chance',
 
mana_cost = 'mana_cost',
 
damage_effectiveness = 'damage_effectiveness',
 
stored_uses = 'stored_uses',
 
cooldown = 'cooldown',
 
vaal_souls_requirement = 'vaal_souls_requirement',
 
vaal_stored_uses = 'vaal_stored_uses',
 
damage_multiplier = 'damage_multiplier',
 
experience = 'experience',
 
stat_text = 'stat_text',
 
quality_stat_text = 'quality_stat_text',
 
 
-- skill_stats_per_level
 
id = 'id',
 
value = 'value',
 
 
-- prefixes
 
prefix_level = 'level',
 
prefix_static = 'static_',
 
prefix_quality_stat = 'quality_',
 
infix_stat = 'stat'
 
 
},
 
},
 
}
 
}
Line 153: Line 127:
   
 
function h.stats(tpl_args, frame, prefix_in, level)
 
function h.stats(tpl_args, frame, prefix_in, level)
  +
local type_prefix = string.format('%s%s', prefix_in, 'stat')
for _, stat_type in ipairs(map.stats) do
 
  +
tpl_args.skill_levels[level]['stats'] = {}
local type_prefix = string.format('%s%s%s', prefix_in, stat_type.prefix_in, i18n.args.infix_stat)
 
  +
for i=1, 8 do
tpl_args.skill_levels[level][stat_type.out] = {}
 
  +
local stat_id_key = string.format('%s%s_%s', type_prefix, i, 'id')
for i=1, 8 do
 
local stat_id_key = string.format('%s%s_%s', type_prefix, i, i18n.args.id)
+
local stat_val_key = string.format('%s%s_%s', type_prefix, i, 'value')
  +
local stat = {
local stat_val_key = string.format('%s%s_%s', type_prefix, i, i18n.args.value)
 
local stat = {
+
id = tpl_args[stat_id_key],
id = tpl_args[stat_id_key],
+
value = tonumber(tpl_args[stat_val_key]),
  +
}
value = tonumber(tpl_args[stat_val_key]),
 
}
+
if stat.id ~= nil and stat.value ~= nil then
  +
tpl_args.skill_levels[level]['stats'][#tpl_args.skill_levels[level]['stats']+1] = stat
if stat.id ~= nil and stat.value ~= nil then
 
  +
tpl_args.skill_levels[level][stat_type.out][#tpl_args.skill_levels[level][stat_type.out]+1] = stat
 
+
if not tpl_args.test then
m_util.cargo.store(frame, {
+
m_cargo.store(frame, {
_table = map.skill_stats_per_level.table,
+
_table = tables.skill_stats_per_level.table,
[map.skill_stats_per_level.fields.level.field] = level,
+
[tables.skill_stats_per_level.fields.level.field] = level,
[map.skill_stats_per_level.fields.id.field] = stat.id,
+
[tables.skill_stats_per_level.fields.id.field] = stat.id,
[map.skill_stats_per_level.fields.value.field] = stat.value,
+
[tables.skill_stats_per_level.fields.value.field] = stat.value,
[map.skill_stats_per_level.fields.is_quality_stat.field] = stat_type.quality,
 
 
})
 
})
 
-- Nuke variables since they're remapped to skill levels
 
tpl_args[stat_id_key] = nil
 
tpl_args[stat_val_key] = nil
 
 
end
 
end
  +
  +
-- Nuke variables since they're remapped to skill levels
  +
tpl_args[stat_id_key] = nil
  +
tpl_args[stat_val_key] = nil
 
end
 
end
 
end
 
end
Line 225: Line 198:
 
function h.display.factory.value(args)
 
function h.display.factory.value(args)
 
return function (tpl_args, frame)
 
return function (tpl_args, frame)
if args.fmt then
+
args.fmt = args.fmt or tables.static.fields[args.key].fmt
return string.format(args.fmt, tpl_args[args.key])
+
local value = tpl_args[args.key]
  +
if args.fmt and value then
  +
return string.format(args.fmt, value)
 
else
 
else
return tpl_args[args.key]
+
return value
 
end
 
end
 
end
 
end
Line 245: Line 220:
 
 
 
return m_util.html.format_value(tpl_args, frame, value, {
 
return m_util.html.format_value(tpl_args, frame, value, {
fmt=args.fmt or map.progression.fields[args.key].fmt,
+
fmt=args.fmt or tables.progression.fields[args.key].fmt,
 
no_color=true,
 
no_color=true,
 
})
 
})
Line 270: Line 245:
 
-- ----------------------------------------------------------------------------
 
-- ----------------------------------------------------------------------------
   
  +
tables.static = {
 
map.stats = {
 
{
 
prefix_in = '',
 
out = 'stats',
 
quality = false,
 
},
 
{
 
prefix_in = i18n.args.prefix_quality_stat,
 
out = 'quality_stats',
 
quality = true,
 
},
 
}
 
 
map.static = {
 
 
table = 'skill',
 
table = 'skill',
order = {'skill_id', 'cast_time', 'gem_description', 'active_skill_name', 'skill_icon', 'item_class_restriction', 'projectile_speed', 'stat_text', 'quality_stat_text', 'has_percentage_mana_cost', 'has_reservation_mana_cost', 'radius', 'radius_secondary', 'radius_tertiary', 'radius_description', 'radius_secondary_description', 'radius_tertiary_description', 'skill_screenshot'},
+
order = {'skill_id', 'cast_time', 'gem_description', 'active_skill_name', 'skill_icon', 'item_class_id_restriction', 'item_class_restriction', 'projectile_speed', 'stat_text', 'quality_stat_text', 'has_percentage_mana_cost', 'has_reservation_mana_cost', 'radius', 'radius_secondary', 'radius_tertiary', 'radius_description', 'radius_secondary_description', 'radius_tertiary_description', 'skill_screenshot'},
 
fields = {
 
fields = {
 
-- GrantedEffects.dat
 
-- GrantedEffects.dat
 
skill_id = {
 
skill_id = {
name = i18n.args.skill_id,
+
name = 'skill_id',
 
field = 'skill_id',
 
field = 'skill_id',
 
type = 'String',
 
type = 'String',
Line 297: Line 258:
 
-- Active Skills.dat
 
-- Active Skills.dat
 
cast_time = {
 
cast_time = {
name = i18n.args.cast_time,
+
name = 'cast_time',
 
field = 'cast_time',
 
field = 'cast_time',
 
type = 'Float',
 
type = 'Float',
 
func = h.cast.wrap(m_util.cast.number),
 
func = h.cast.wrap(m_util.cast.number),
  +
fmt = '%ss',
 
},
 
},
 
gem_description = {
 
gem_description = {
name = i18n.args.gem_description,
+
name = 'gem_description',
 
field = 'description',
 
field = 'description',
 
type = 'Text',
 
type = 'Text',
Line 309: Line 271:
 
},
 
},
 
active_skill_name = {
 
active_skill_name = {
name = i18n.args.active_skill_name,
+
name = 'active_skill_name',
 
field = 'active_skill_name',
 
field = 'active_skill_name',
 
type = 'String',
 
type = 'String',
Line 315: Line 277:
 
},
 
},
 
skill_icon = {
 
skill_icon = {
name = i18n.args.skill_icon,
+
name = 'skill_icon',
 
field = 'skill_icon',
 
field = 'skill_icon',
 
type = 'Page',
 
type = 'Page',
Line 324: Line 286:
 
end,
 
end,
 
},
 
},
item_class_restriction = {
+
item_class_id_restriction = {
name = i18n.args.item_class_restriction,
+
name = 'item_class_id_restriction',
field = 'item_class_restriction',
+
field = 'item_class_id_restriction',
 
type = 'List (,) of String',
 
type = 'List (,) of String',
 
func = function(tpl_args, frame, value)
 
func = function(tpl_args, frame, value)
Line 334: Line 296:
 
value = m_util.string.split(value, ', ')
 
value = m_util.string.split(value, ', ')
 
for _, v in ipairs(value) do
 
for _, v in ipairs(value) do
local result = m_util.table.find_in_nested_array{value=v,key='full', tbl=m_game.constants.item.class}
+
if m_game.constants.item.classes[v] == nil then
if result == nil then
+
error(string.format('Invalid item class id: %s', v))
error(string.format('Invalid item class: %s', v))
 
 
end
 
end
 
end
 
end
 
return value
 
return value
  +
end,
  +
},
  +
item_class_restriction = {
  +
name = 'item_class_restriction',
  +
field = 'item_class_restriction',
  +
type = 'List (,) of String',
  +
func = function(tpl_args, frame, value)
  +
if tpl_args.item_class_id_restriction == nil then
  +
return
  +
end
  +
-- This function makes a localized list based on ids
  +
local item_classes = {}
  +
for _, v in ipairs(tpl_args.item_class_id_restriction) do
  +
item_classes[#item_classes+1] = m_game.constants.item.classes[v].full
  +
end
  +
  +
return item_classes
 
end,
 
end,
 
},
 
},
 
-- Projectiles.dat - manually mapped to the skills
 
-- Projectiles.dat - manually mapped to the skills
 
projectile_speed = {
 
projectile_speed = {
name = i18n.args.projectile_speed,
+
name = 'projectile_speed',
 
field = 'projectile_speed',
 
field = 'projectile_speed',
 
type = 'Integer',
 
type = 'Integer',
Line 351: Line 329:
 
-- Misc data derieved from stats
 
-- Misc data derieved from stats
 
stat_text = {
 
stat_text = {
name = i18n.args.stat_text,
+
name = 'stat_text',
 
field = 'stat_text',
 
field = 'stat_text',
 
type = 'Text',
 
type = 'Text',
Line 357: Line 335:
 
},
 
},
 
quality_stat_text = {
 
quality_stat_text = {
name = i18n.args.quality_stat_text,
+
name = 'quality_stat_text',
 
field = 'quality_stat_text',
 
field = 'quality_stat_text',
 
type = 'Text',
 
type = 'Text',
Line 364: Line 342:
 
-- Misc data currently not from game data
 
-- Misc data currently not from game data
 
has_percentage_mana_cost = {
 
has_percentage_mana_cost = {
name = i18n.args.has_percentage_mana_cost,
+
name = 'has_percentage_mana_cost',
 
field = 'has_percentage_mana_cost',
 
field = 'has_percentage_mana_cost',
 
type = 'Boolean',
 
type = 'Boolean',
Line 371: Line 349:
 
},
 
},
 
has_reservation_mana_cost = {
 
has_reservation_mana_cost = {
name = i18n.args.has_reservation_mana_cost,
+
name = 'has_reservation_mana_cost',
 
field = 'has_reservation_mana_cost',
 
field = 'has_reservation_mana_cost',
 
type = 'Boolean',
 
type = 'Boolean',
Line 378: Line 356:
 
},
 
},
 
radius = {
 
radius = {
name = i18n.args.radius,
+
name = 'radius',
 
field = 'radius',
 
field = 'radius',
 
type = 'Integer',
 
type = 'Integer',
Line 384: Line 362:
 
},
 
},
 
radius_description = {
 
radius_description = {
name = i18n.args.radius_description,
+
name = 'radius_description',
 
field = 'radius_description',
 
field = 'radius_description',
 
type = 'Text',
 
type = 'Text',
Line 390: Line 368:
 
},
 
},
 
radius_secondary = {
 
radius_secondary = {
name = i18n.args.radius_secondary,
+
name = 'radius_secondary',
 
field = 'radius_secondary',
 
field = 'radius_secondary',
 
type = 'Integer',
 
type = 'Integer',
Line 396: Line 374:
 
},
 
},
 
radius_secondary_description = {
 
radius_secondary_description = {
name = i18n.args.radius_secondary_description,
+
name = 'radius_secondary_description',
 
field = 'radius_secondary_description',
 
field = 'radius_secondary_description',
 
type = 'Text',
 
type = 'Text',
Line 403: Line 381:
 
-- not sure if any skill actually has 3 radius componets
 
-- not sure if any skill actually has 3 radius componets
 
radius_tertiary = {
 
radius_tertiary = {
name = i18n.args.radius_tertiary,
+
name = 'radius_tertiary',
 
field = 'radius_tertiary',
 
field = 'radius_tertiary',
 
type = 'Integer',
 
type = 'Integer',
Line 409: Line 387:
 
},
 
},
 
radius_tertiary_description = {
 
radius_tertiary_description = {
name = i18n.args.radius_tertiary_description,
+
name = 'radius_tertiary_description',
 
field = 'radius_tertiary_description',
 
field = 'radius_tertiary_description',
 
type = 'Text',
 
type = 'Text',
Line 424: Line 402:
 
},
 
},
 
skill_screenshot = {
 
skill_screenshot = {
name = i18n.args.skill_screenshot,
+
name = 'skill_screenshot',
 
field = 'skill_screenshot',
 
field = 'skill_screenshot',
 
type = 'Page',
 
type = 'Page',
Line 447: Line 425:
 
}
 
}
   
map.progression = {
+
tables.progression = {
 
table = 'skill_levels',
 
table = 'skill_levels',
order = {'level_requirement', 'dexterity_requirement', 'intelligence_requirement', 'strength_requirement', 'mana_multiplier', 'critical_strike_chance', 'mana_cost', 'damage_effectiveness', 'stored_uses', 'cooldown', 'vaal_souls_requirement', 'vaal_stored_uses', 'damage_multiplier', 'experience', 'stat_text', 'quality_stat_text'},
+
order = {'level_requirement', 'dexterity_requirement', 'intelligence_requirement', 'strength_requirement', 'mana_multiplier', 'critical_strike_chance', 'mana_cost', 'attack_speed_multiplier', 'damage_effectiveness', 'stored_uses', 'cooldown', 'vaal_souls_requirement', 'vaal_stored_uses', 'vaal_soul_gain_prevention_time', 'damage_multiplier', 'duration', 'experience', 'stat_text'},
 
fields = {
 
fields = {
 
level = {
 
level = {
Line 459: Line 437:
 
},
 
},
 
level_requirement = {
 
level_requirement = {
name = i18n.args.level_requirement,
+
name = 'level_requirement',
 
field = 'level_requirement',
 
field = 'level_requirement',
 
type = 'Integer',
 
type = 'Integer',
 
func = h.cast.wrap(m_util.cast.number),
 
func = h.cast.wrap(m_util.cast.number),
  +
header = i18n.progression.level_requirement,
header = m_util.html.abbr('[[Image:Level_up_icon_small.png|link=|Lvl.]]', 'Required Level', 'nounderline'),
 
 
},
 
},
 
dexterity_requirement = {
 
dexterity_requirement = {
name = i18n.args.dexterity_requirement,
+
name = 'dexterity_requirement',
 
field = 'dexterity_requirement',
 
field = 'dexterity_requirement',
 
type = 'Integer',
 
type = 'Integer',
 
func = h.cast.wrap(m_util.cast.number),
 
func = h.cast.wrap(m_util.cast.number),
  +
header = i18n.progression.dexterity_requirement,
header = m_util.html.abbr('[[Image:DexterityIcon_small.png|link=|dexterity]]', 'Required Dexterity', 'nounderline'),
 
 
},
 
},
 
strength_requirement = {
 
strength_requirement = {
name = i18n.args.strength_requirement,
+
name = 'strength_requirement',
 
field = 'strength_requirement',
 
field = 'strength_requirement',
 
type = 'Integer',
 
type = 'Integer',
 
func = h.cast.wrap(m_util.cast.number),
 
func = h.cast.wrap(m_util.cast.number),
  +
header = i18n.progression.strength_requirement,
header = m_util.html.abbr('[[Image:StrengthIcon_small.png|link=|strength]]', 'Required Strength', 'nounderline'),
 
 
},
 
},
 
intelligence_requirement = {
 
intelligence_requirement = {
name = i18n.args.intelligence_requirement,
+
name = 'intelligence_requirement',
 
field = 'intelligence_requirement',
 
field = 'intelligence_requirement',
 
type = 'Integer',
 
type = 'Integer',
 
func = h.cast.wrap(m_util.cast.number),
 
func = h.cast.wrap(m_util.cast.number),
  +
header = i18n.progression.intelligence_requirement,
header = m_util.html.abbr('[[Image:IntelligenceIcon_small.png|link=|intelligence]]', 'Required Intelligence', 'nounderline'),
 
 
},
 
},
 
mana_multiplier = {
 
mana_multiplier = {
name = i18n.args.mana_multiplier,
+
name = 'mana_multiplier',
 
field = 'mana_multiplier',
 
field = 'mana_multiplier',
 
type = 'Float',
 
type = 'Float',
 
func = h.cast.wrap(m_util.cast.number),
 
func = h.cast.wrap(m_util.cast.number),
header = 'Mana<br>Multiplier',
+
header = i18n.progression.mana_multiplier,
 
fmt = '%s%%',
 
fmt = '%s%%',
 
},
 
},
 
critical_strike_chance = {
 
critical_strike_chance = {
name = i18n.args.critical_strike_chance,
+
name = 'critical_strike_chance',
 
field = 'critical_strike_chance',
 
field = 'critical_strike_chance',
 
type = 'Float',
 
type = 'Float',
 
func = h.cast.wrap(m_util.cast.number),
 
func = h.cast.wrap(m_util.cast.number),
header = 'Critical<br>Strike<br>Chance',
+
header = i18n.progression.critical_strike_chance,
 
fmt = '%s%%',
 
fmt = '%s%%',
 
},
 
},
 
mana_cost = {
 
mana_cost = {
name = i18n.args.mana_cost,
+
name = 'mana_cost',
 
field = 'mana_cost',
 
field = 'mana_cost',
 
type = 'Integer',
 
type = 'Integer',
Line 509: Line 487:
 
header = function (skill_data)
 
header = function (skill_data)
 
if skill_data["skill.has_reservation_mana_cost"] then
 
if skill_data["skill.has_reservation_mana_cost"] then
return 'Mana<br>Reserved'
+
return i18n.progression.mana_reserved
 
else
 
else
return 'Mana<br>Cost'
+
return i18n.progression.mana_cost
 
end
 
end
 
end,
 
end,
 
fmt = function (tpl_args, frame)
 
fmt = function (tpl_args, frame)
if tpl_args.has_percentage_mana_cost then
+
if tpl_args.has_percentage_mana_cost or (tpl_args.skill_data and tpl_args.skill_data['skill.has_percentage_mana_cost']) then
 
str = '%s%%'
 
str = '%s%%'
 
else
 
else
Line 525: Line 503:
 
},
 
},
 
damage_effectiveness = {
 
damage_effectiveness = {
name = i18n.args.damage_effectiveness,
+
name = 'damage_effectiveness',
 
field = 'damage_effectiveness',
 
field = 'damage_effectiveness',
 
type = 'Float',
 
type = 'Float',
 
func = h.cast.wrap(m_util.cast.number),
 
func = h.cast.wrap(m_util.cast.number),
header = 'Damage<br>Effectiveness',
+
header = i18n.progression.damage_effectiveness,
 
fmt = '%s%%',
 
fmt = '%s%%',
 
},
 
},
 
stored_uses = {
 
stored_uses = {
name = i18n.args.stored_uses,
+
name = 'stored_uses',
 
field = 'stored_uses',
 
field = 'stored_uses',
 
type = 'Integer',
 
type = 'Integer',
 
func = h.cast.wrap(m_util.cast.number),
 
func = h.cast.wrap(m_util.cast.number),
header = 'Stored<br>Uses',
+
header = i18n.progression.stored_uses,
 
},
 
},
 
cooldown = {
 
cooldown = {
name = i18n.args.cooldown,
+
name = 'cooldown',
 
field = 'cooldown',
 
field = 'cooldown',
 
type = 'Float',
 
type = 'Float',
 
func = h.cast.wrap(m_util.cast.number),
 
func = h.cast.wrap(m_util.cast.number),
header = 'Cooldown',
+
header = i18n.progression.cooldown,
 
fmt = '%ss',
 
fmt = '%ss',
 
},
 
},
 
vaal_souls_requirement = {
 
vaal_souls_requirement = {
name = i18n.args.vaal_souls_requirement,
+
name = 'vaal_souls_requirement',
 
field = 'vaal_souls_requirement',
 
field = 'vaal_souls_requirement',
 
type = 'Integer',
 
type = 'Integer',
 
func = h.cast.wrap(m_util.cast.number),
 
func = h.cast.wrap(m_util.cast.number),
header = 'Vaal<br>souls',
+
header = i18n.progression.vaal_souls_requirement,
 
},
 
},
 
vaal_stored_uses = {
 
vaal_stored_uses = {
name = i18n.args.vaal_stored_uses,
+
name = 'vaal_stored_uses',
 
field = 'vaal_stored_uses',
 
field = 'vaal_stored_uses',
 
type = 'Integer',
 
type = 'Integer',
 
func = h.cast.wrap(m_util.cast.number),
 
func = h.cast.wrap(m_util.cast.number),
header = 'Stored<br>Uses',
+
header = i18n.progression.vaal_stored_uses,
  +
},
  +
vaal_soul_gain_prevention_time = {
  +
name = 'vaal_soul_gain_prevention_time',
  +
field = 'vaal_soul_gain_prevention_time',
  +
type = 'Float',
  +
func = h.cast.wrap(m_util.cast.number),
  +
header = i18n.progression.vaal_soul_gain_prevention_time,
  +
fmt = '%ss',
 
},
 
},
 
damage_multiplier = {
 
damage_multiplier = {
name = i18n.args.damage_multiplier,
+
name = 'damage_multiplier',
 
field = 'damage_multiplier',
 
field = 'damage_multiplier',
 
type = 'Float',
 
type = 'Float',
 
func = h.cast.wrap(m_util.cast.number),
 
func = h.cast.wrap(m_util.cast.number),
header = m_util.html.abbr('Damage<br>Multiplier', 'Deals x% of Base Damage'),
+
header = i18n.progression.damage_multiplier,
 
fmt = '%s%%',
 
fmt = '%s%%',
  +
},
  +
attack_speed_multiplier = {
  +
name = 'attack_speed_multiplier',
  +
field = 'attack_speed_multiplier',
  +
type = 'Integer',
  +
func = h.cast.wrap(m_util.cast.number),
  +
header = i18n.progression.attack_speed_multiplier,
  +
fmt = '%s%%',
  +
},
  +
duration = {
  +
name = 'duration',
  +
field = 'duration',
  +
type = 'Float',
  +
func = h.cast.wrap(m_util.cast.number),
  +
header = i18n.progression.duration,
  +
fmt = '%ss',
 
},
 
},
 
-- from gem experience, optional
 
-- from gem experience, optional
 
experience = {
 
experience = {
name = i18n.args.experience,
+
name = 'experience',
 
field = 'experience',
 
field = 'experience',
 
type = 'Integer',
 
type = 'Integer',
Line 578: Line 580:
 
},
 
},
 
stat_text = {
 
stat_text = {
name = i18n.args.stat_text,
+
name = 'stat_text',
 
field = 'stat_text',
 
field = 'stat_text',
type = 'Text',
 
func = nil,
 
hide = true,
 
},
 
quality_stat_text = {
 
name = i18n.args.quality_stat_text,
 
field = 'quality_stat_text',
 
 
type = 'Text',
 
type = 'Text',
 
func = nil,
 
func = nil,
Line 594: Line 589:
 
}
 
}
   
data.progression_display_order = {'level_requirement', 'dexterity_requirement', 'strength_requirement', 'intelligence_requirement', 'mana_multiplier', 'critical_strike_chance', 'mana_cost', 'damage_effectiveness', 'stored_uses', 'cooldown', 'vaal_souls_requirement', 'vaal_stored_uses', 'damage_multiplier'}
+
data.progression_display_order = {'level_requirement', 'dexterity_requirement', 'strength_requirement', 'intelligence_requirement', 'mana_multiplier', 'critical_strike_chance', 'mana_cost', 'damage_effectiveness', 'stored_uses', 'cooldown', 'vaal_souls_requirement', 'vaal_stored_uses', 'vaal_soul_gain_prevention_time', 'damage_multiplier', 'duration', 'attack_speed_multiplier'}
   
map.skill_stats_per_level = {
+
tables.skill_stats_per_level = {
 
table = 'skill_stats_per_level',
 
table = 'skill_stats_per_level',
 
fields = {
 
fields = {
Line 611: Line 606:
 
type = 'Integer',
 
type = 'Integer',
 
},
 
},
  +
},
is_quality_stat = {
 
  +
}
field = 'is_quality_stat',
 
  +
type = 'Boolean',
 
  +
tables.skill_quality = {
  +
table = 'skill_quality',
  +
fields = {
  +
set_id = {
  +
field = 'set_id',
  +
type = 'Integer',
  +
},
  +
weight = {
  +
field = 'weight',
  +
type = 'Integer',
  +
},
  +
stat_text = {
  +
field = 'stat_text',
  +
type = 'String',
  +
},
  +
},
  +
}
  +
  +
tables.skill_quality_stats = {
  +
table = 'skill_quality_stats',
  +
fields = {
  +
set_id = {
  +
field = 'set_id',
  +
type = 'Integer',
  +
},
  +
id = {
  +
field = 'id',
  +
type = 'String',
  +
},
  +
value = {
  +
field = 'value',
  +
type = 'Integer',
 
},
 
},
 
},
 
},
Line 625: Line 652:
 
{
 
{
 
header = i18n.infobox.skill_id,
 
header = i18n.infobox.skill_id,
func = h.display.factory.value{key='skill_id'},
+
func = function (tpl_args, frame)
  +
return string.format('[[%s|%s]]', mw.title.getCurrentTitle().fullText, tpl_args.skill_id)
  +
end
 
},
 
},
 
{
 
{
Line 642: Line 671:
 
header = i18n.infobox.item_class_restrictions,
 
header = i18n.infobox.item_class_restrictions,
 
func = function (tpl_args, frame)
 
func = function (tpl_args, frame)
if tpl_args.item_class_restrictions == nil then
+
if tpl_args.item_class_restriction == nil then
 
return
 
return
 
end
 
end
local ul = mw.html.create('ul')
+
local out = {}
for _, class in ipairs(i18n.infobox.item_class_restrictions) do
+
for _, class in ipairs(tpl_args.item_class_restriction) do
ul:tag('li')
+
out[#out+1] = string.format('[[%s]]', class)
:wikitext(string.format('[[%s]]', class))
 
 
end
 
end
return tostring(ul)
+
return table.concat(out, '<br>')
 
end,
 
end,
 
},
 
},
Line 685: Line 713:
 
header = i18n.infobox.mana_cost,
 
header = i18n.infobox.mana_cost,
 
func = h.display.factory.range_value{key='mana_cost'},
 
func = h.display.factory.range_value{key='mana_cost'},
  +
},
  +
{
  +
header = i18n.infobox.attack_speed_multiplier,
  +
func = h.display.factory.range_value{key='attack_speed_multiplier'},
 
},
 
},
 
{
 
{
Line 705: Line 737:
 
header = i18n.infobox.vaal_stored_uses,
 
header = i18n.infobox.vaal_stored_uses,
 
func = h.display.factory.range_value{key='vaal_stored_uses'},
 
func = h.display.factory.range_value{key='vaal_stored_uses'},
},
+
},
  +
{
  +
header = i18n.infobox.vaal_soul_gain_prevention_time,
  +
func = h.display.factory.range_value{key='vaal_soul_gain_prevention_time'},
  +
},
 
{
 
{
 
header = i18n.infobox.damage_multiplier,
 
header = i18n.infobox.damage_multiplier,
 
func = h.display.factory.range_value{key='damage_multiplier'},
 
func = h.display.factory.range_value{key='damage_multiplier'},
  +
},
  +
{
  +
header = i18n.infobox.duration,
  +
func = h.display.factory.range_value{key='duration'},
 
},
 
},
 
{
 
{
Line 727: Line 767:
 
local p = {}
 
local p = {}
   
p.table_skills = m_util.cargo.declare_factory{data=map.static}
+
p.table_skills = m_util.cargo.declare_factory{data=tables.static}
p.table_skill_levels = m_util.cargo.declare_factory{data=map.progression}
+
p.table_skill_levels = m_util.cargo.declare_factory{data=tables.progression}
p.table_skill_stats_per_level = m_util.cargo.declare_factory{data=map.skill_stats_per_level}
+
p.table_skill_stats_per_level = m_util.cargo.declare_factory{data=tables.skill_stats_per_level}
  +
p.table_skill_quality = m_util.cargo.declare_factory{data=tables.skill_quality}
  +
p.table_skill_quality_stats = m_util.cargo.declare_factory{data=tables.skill_quality_stats}
   
 
--
 
--
Line 735: Line 777:
 
--
 
--
 
function p.skill(frame, tpl_args)
 
function p.skill(frame, tpl_args)
  +
--[[
  +
Creates an infobox for skills.
  +
  +
Examples
  +
--------
  +
=p.skill{gem_description='Icy bolts rain down over the targeted area.', active_skill_name='Icestorm', skill_id='IcestormUniqueStaff12', cast_time=0.75, required_level=1, static_mana_cost=22, static_critical_strike_chance=6, static_damage_effectiveness=30, static_damage_multiplier=100, static_stat1_id='spell_minimum_base_cold_damage_+_per_10_intelligence', static_stat1_value=1, static_stat2_id='spell_maximum_base_cold_damage_+_per_10_intelligence', static_stat2_value=3, static_stat3_id='base_skill_effect_duration', static_stat3_value=1500, static_stat4_id='fire_storm_fireball_delay_ms', static_stat4_value=100, static_stat5_id='skill_effect_duration_per_100_int', static_stat5_value=150, static_stat6_id='skill_override_pvp_scaling_time_ms', static_stat6_value=450, static_stat7_id='firestorm_drop_ground_ice_duration_ms', static_stat7_value=500, static_stat8_id='skill_art_variation', static_stat8_value=4, static_stat9_id='base_skill_show_average_damage_instead_of_dps', static_stat9_value=1, static_stat10_id='is_area_damage', static_stat10_value=1, stat_text='Deals 1 to 3 base Cold Damage per 10 Intelligence<br>Base duration is 1.5 seconds<br>One impact every 0.1 seconds<br>0.15 seconds additional Base Duration per 100 Intelligence', quality_stat_text = nil, level1=true, level1_level_requirement=1}
  +
  +
]]
  +
 
if tpl_args == nil then
 
if tpl_args == nil then
 
tpl_args = getArgs(frame, {
 
tpl_args = getArgs(frame, {
Line 750: Line 801:
 
[0] = {},
 
[0] = {},
 
}
 
}
  +
  +
tpl_args.skill_quality = {}
  +
local i = 0
  +
repeat
  +
i = i + 1
  +
local prefix = string.format('quality_type%s', i)
  +
local q = {
  +
_table = tables.skill_quality.table,
  +
set_id = i,
  +
weight = tonumber(tpl_args[string.format('%s_weight', prefix)]),
  +
stat_text = tpl_args[string.format('%s_stat_text', prefix)],
  +
}
  +
if q.stat_text then
  +
tpl_args.skill_quality[#tpl_args.skill_quality+1] = q
  +
m_cargo.store(frame, q)
  +
  +
q.stats = {}
  +
q._table = nil
  +
local j = 0
  +
repeat
  +
j = j + 1
  +
local stat_prefix = string.format('%s_stat%s', prefix, j)
  +
local s = {
  +
_table = tables.skill_quality_stats.table,
  +
set_id = i,
  +
id = tpl_args[string.format('%s_id', stat_prefix)],
  +
value = tonumber(tpl_args[string.format('%s_value', stat_prefix)]),
  +
}
  +
if s.id and s.value then
  +
q.stats[#q.stats+1] = s
  +
m_cargo.store(frame, s)
  +
end
  +
  +
s._table = nil
  +
until s.id == nil or s.value == nil
  +
end
  +
until q.stat_text == nil
 
 
 
-- Handle level progression
 
-- Handle level progression
Line 755: Line 843:
 
repeat
 
repeat
 
i = i + 1
 
i = i + 1
local prefix = i18n.args.prefix_level .. i
+
local prefix = 'level' .. i
 
local level = m_util.cast.boolean(tpl_args[prefix])
 
local level = m_util.cast.boolean(tpl_args[prefix])
 
if level == true then
 
if level == true then
Line 763: Line 851:
 
prefix = prefix .. '_'
 
prefix = prefix .. '_'
 
 
if tpl_args[prefix .. i18n.args.experience] ~= nil then
+
if tpl_args[prefix .. 'experience'] ~= nil then
 
tpl_args.max_level = i
 
tpl_args.max_level = i
 
end
 
end
 
 
 
properties = {
 
properties = {
_table = map.progression.table,
+
_table = tables.progression.table,
[map.progression.fields.level.field] = i
+
[tables.progression.fields.level.field] = i
 
}
 
}
h.map_to_arg(tpl_args, frame, properties, prefix, map.progression, i)
+
h.map_to_arg(tpl_args, frame, properties, prefix, tables.progression, i)
m_util.cargo.store(frame, properties)
+
if not tpl_args.test then
  +
m_cargo.store(frame, properties)
  +
end
 
 
 
h.stats(tpl_args, frame, prefix, i)
 
h.stats(tpl_args, frame, prefix, i)
Line 781: Line 871:
 
-- handle static progression
 
-- handle static progression
 
properties = {
 
properties = {
_table = map.progression.table,
+
_table = tables.progression.table,
[map.progression.fields.level.field] = 0
+
[tables.progression.fields.level.field] = 0
 
}
 
}
h.map_to_arg(tpl_args, frame, properties, i18n.args.prefix_static, map.progression, 0)
+
h.map_to_arg(tpl_args, frame, properties, 'static_', tables.progression, 0)
  +
if not tpl_args.test then
m_util.cargo.store(frame, properties)
 
  +
m_cargo.store(frame, properties)
  +
end
 
 
 
-- Handle static arguments
 
-- Handle static arguments
 
properties = {
 
properties = {
_table = map.static.table,
+
_table = tables.static.table,
 
 
[map.static.fields.max_level.field] = tpl_args.max_level
+
[tables.static.fields.max_level.field] = tpl_args.max_level
 
}
 
}
 
 
h.map_to_arg(tpl_args, frame, properties, '', map.static)
+
h.map_to_arg(tpl_args, frame, properties, '', tables.static)
h.stats(tpl_args, frame, i18n.args.prefix_static, 0)
+
h.stats(tpl_args, frame, 'static_', 0)
 
 
 
 
Line 828: Line 920:
 
 
 
infobox = tostring(infobox)
 
infobox = tostring(infobox)
  +
 
 
 
--
 
--
 
-- Store data
 
-- Store data
 
--
 
--
properties[map.static.fields.html.field] = infobox
+
properties[tables.static.fields.html.field] = infobox
  +
if not tpl_args.test then
m_util.cargo.store(frame, properties)
 
  +
m_cargo.store(frame, properties)
  +
end
 
 
 
--
 
--
Line 839: Line 934:
 
--
 
--
 
local container = mw.html.create('span')
 
local container = mw.html.create('span')
container:attr('class', 'skill-box-page-container')
+
container
  +
:attr('class', 'skill-box-page-container')
container:wikitext(infobox)
 
  +
:wikitext(infobox)
 
if tpl_args.skill_screenshot then
 
if tpl_args.skill_screenshot then
container:wikitext(string.format('[[%s]]', tpl_args.skill_screenshot))
+
container
  +
:wikitext(string.format('[[%s]]', tpl_args.skill_screenshot))
  +
end
  +
  +
-- Generic messages on the page:
  +
out = {}
  +
if mw.ustring.find(tpl_args.skill_id, '_') then
  +
out[#out+1] = frame:expandTemplate{
  +
title = 'Incorrect title',
  +
args = {title=tpl_args.skill_id}
  +
} .. '\n\n\n'
  +
end
  +
if tpl_args.active_skill_name then
  +
out[#out+1] = string.format(
  +
i18n.intro_named_id,
  +
tpl_args.skill_id,
  +
tpl_args.active_skill_name
  +
)
  +
else
  +
out[#out+1] = string.format(
  +
i18n.intro_unnamed_id,
  +
tpl_args.skill_id
  +
)
 
end
 
end
 
 
return tostring(container) .. m_util.misc.add_category({'Skill data'})
+
return tostring(container) .. m_util.misc.add_category({'Skill data'}) .. '\n' .. table.concat(out)
 
end
 
end
   
 
function p.progression(frame)
 
function p.progression(frame)
  +
--[[
  +
Displays the level progression for the skill gem.
  +
  +
Examples
  +
--------
  +
= p.progression{page='Reave'}
  +
]]
  +
 
local tpl_args = getArgs(frame, {
 
local tpl_args = getArgs(frame, {
 
parentFirst = true
 
parentFirst = true
Line 854: Line 980:
 
frame = m_util.misc.get_frame(frame)
 
frame = m_util.misc.get_frame(frame)
 
 
  +
-- Parse column arguments:
--
 
 
tpl_args.stat_format = {}
 
tpl_args.stat_format = {}
 
local prefix
 
local prefix
 
for i=1, 9 do
 
for i=1, 9 do
 
prefix = 'c' .. i .. '_'
 
prefix = 'c' .. i .. '_'
local statfmt = {
+
local format_keys = {
header = tpl_args[prefix .. 'header'],
+
'header',
abbr = tpl_args[prefix .. 'abbr'],
+
'abbr',
pattern_extract = tpl_args[prefix .. 'pattern_extract'],
+
'pattern_extract',
pattern_value = tpl_args[prefix .. 'pattern_value'],
+
'pattern_value'
counter = 0,
 
 
}
 
}
  +
local statfmt = {counter = 0}
if m_util.table.has_all_value(statfmt, {'header', 'abbr', 'pattern_extract', 'pattern_value'}) then
 
  +
for _,v in ipairs(format_keys) do
  +
statfmt[v] = tpl_args[prefix .. v]
  +
end
  +
  +
if m_util.table.has_all_value(statfmt, format_keys) then
 
break
 
break
 
end
 
end
 
 
if m_util.table.has_one_value(statfmt, {'header', 'abbr', 'pattern_extract', 'pattern_value'}) then
+
if m_util.table.has_one_value(statfmt, format_keys) then
error(string.format('All formatting keys must be specified for index "%s"', i))
+
error(string.format(i18n.errors.all_format_keys_specified, i))
 
end
 
end
 
 
Line 880: Line 1,010:
 
 
 
 
local result
+
local results = {}
 
local query = {
 
local query = {
 
groupBy = 'skill._pageID',
 
groupBy = 'skill._pageID',
Line 889: Line 1,019:
 
'skill._pageName',
 
'skill._pageName',
 
'skill.has_reservation_mana_cost',
 
'skill.has_reservation_mana_cost',
  +
'skill.has_percentage_mana_cost',
 
}
 
}
 
 
 
if tpl_args.skill_id then
 
if tpl_args.skill_id then
query.where = string.format('skill.skill_id="%s"', tpl_args.skill_id)
+
query.where = string.format(
result = m_util.cargo.query({'skill'}, fields, query)
+
'skill.skill_id="%s"',
if #result == 0 then
+
tpl_args.skill_id
  +
)
error('Couldn\'t find a page for the specified skill id')
 
  +
results = m_cargo.query({'skill'}, fields, query)
  +
if #results == 0 then
  +
error(i18n.errors.no_results_for_skill_id)
 
end
 
end
skill_data = result[1]
+
skill_data = results[1]
 
else
 
else
 
if tpl_args.page then
 
if tpl_args.page then
Line 906: Line 1,040:
 
query.where = string.format('skill._pageName="%s"', page)
 
query.where = string.format('skill._pageName="%s"', page)
 
 
result = m_util.cargo.query({'skill'}, fields, query)
+
results = m_cargo.query({'skill'}, fields, query)
if #result == 0 then
+
if #results == 0 then
error('Couldn\'t find the queried data on the skill page')
+
error(i18n.errors.no_results_for_skill_page)
 
end
 
end
 
 
skill_data = result[1]
+
skill_data = results[1]
 
end
 
end
 
 
  +
tpl_args.skill_data = skill_data
skill_data["skill.has_reservation_mana_cost"] = h.cast.wrap(m_util.cast.boolean)(skill_data["skill.has_reservation_mana_cost"])
 
 
 
  +
skill_data["skill.has_reservation_mana_cost"] = m_util.cast.boolean(skill_data["skill.has_reservation_mana_cost"])
query.where=string.format('skill_levels._pageName="%s"', skill_data['skill._pageName'])
 
  +
skill_data['skill.has_percentage_mana_cost'] = m_util.cast.boolean(skill_data["skill.has_percentage_mana_cost"])
  +
  +
query.where = string.format(
  +
'skill_levels._pageName="%s"',
  +
skill_data['skill._pageName']
  +
)
 
fields = {}
 
fields = {}
for _, pdata in pairs(map.progression.fields) do
+
for _, pdata in pairs(tables.progression.fields) do
 
fields[#fields+1] = string.format('skill_levels.%s', pdata.field)
 
fields[#fields+1] = string.format('skill_levels.%s', pdata.field)
 
end
 
end
 
 
result = m_util.cargo.query(
+
results = m_cargo.query(
 
{'skill_levels'},
 
{'skill_levels'},
 
fields,
 
fields,
 
{
 
{
where=string.format('skill_levels._pageName="%s" AND skill_levels.level > 0', skill_data['skill._pageName']),
+
where=string.format(
  +
'skill_levels._pageName="%s" AND skill_levels.level > 0',
  +
skill_data['skill._pageName']
  +
),
 
groupBy='skill_levels._pageID, skill_levels.level',
 
groupBy='skill_levels._pageID, skill_levels.level',
 
orderBy='skill_levels.level ASC',
 
orderBy='skill_levels.level ASC',
Line 932: Line 1,075:
 
)
 
)
 
 
if #result == 0 then
+
if #results == 0 then
error('No gem level progression data found')
+
error(i18n.errors.missing_level_data)
 
end
 
end
 
 
 
headers = {}
 
headers = {}
for i, row in ipairs(result) do
+
for i, row in ipairs(results) do
 
for k, v in pairs(row) do
 
for k, v in pairs(row) do
 
headers[k] = true
 
headers[k] = true
Line 944: Line 1,087:
 
 
 
local tbl = mw.html.create('table')
 
local tbl = mw.html.create('table')
  +
tbl
tbl:attr('class', 'wikitable skill-progression-table')
 
  +
:attr(
  +
'class',
  +
'wikitable responsive-table skill-progression-table'
  +
)
 
 
 
local head = tbl:tag('tr')
 
local head = tbl:tag('tr')
Line 953: Line 1,100:
 
 
 
for _, key in ipairs(data.progression_display_order) do
 
for _, key in ipairs(data.progression_display_order) do
local pdata = map.progression.fields[key]
+
local pdata = tables.progression.fields[key]
 
-- TODO should be nil?
 
-- TODO should be nil?
 
if pdata.hide == nil and headers['skill_levels.' .. pdata.field] then
 
if pdata.hide == nil and headers['skill_levels.' .. pdata.field] then
Line 979: Line 1,126:
 
head
 
head
 
:tag('th')
 
:tag('th')
:wikitext(m_util.html.abbr('Exp.', 'Experience Needed to Level Up'))
+
:wikitext(m_util.html.abbr(
  +
i18n.progression.exp_short,
  +
i18n.progression.exp_long
  +
)
  +
)
 
:done()
 
:done()
 
:tag('th')
 
:tag('th')
:wikitext(m_util.html.abbr('Total Exp.', 'Total experience needed'))
+
:wikitext(m_util.html.abbr(
  +
i18n.progression.tot_exp_short,
  +
i18n.progression.tot_exp_long
  +
)
  +
)
 
:done()
 
:done()
 
end
 
end
Line 990: Line 1,145:
 
local experience
 
local experience
 
 
for i, row in ipairs(result) do
+
for i, row in ipairs(results) do
 
tblrow = tbl:tag('tr')
 
tblrow = tbl:tag('tr')
 
tblrow
 
tblrow
Line 998: Line 1,153:
 
 
 
for _, key in ipairs(data.progression_display_order) do
 
for _, key in ipairs(data.progression_display_order) do
local pdata = map.progression.fields[key]
+
local pdata = tables.progression.fields[key]
 
if pdata.hide == nil and headers['skill_levels.' .. pdata.field] then
 
if pdata.hide == nil and headers['skill_levels.' .. pdata.field] then
h.int_value_or_na(tpl_args, frame, tblrow, row['skill_levels.' .. pdata.field], pdata)
+
h.int_value_or_na(
  +
tpl_args,
  +
frame,
  +
tblrow,
  +
row['skill_levels.' .. pdata.field],
  +
pdata
  +
)
 
end
 
end
 
end
 
end
Line 1,006: Line 1,167:
 
-- stats
 
-- stats
 
if row['skill_levels.stat_text'] then
 
if row['skill_levels.stat_text'] then
stats = m_util.string.split(row['skill_levels.stat_text'], '<br>')
+
stats = m_util.string.split(
  +
row['skill_levels.stat_text'],
  +
'<br>'
  +
)
 
else
 
else
 
stats = {}
 
stats = {}
Line 1,015: Line 1,179:
 
match = {string.match(stat, statfmt.pattern_extract)}
 
match = {string.match(stat, statfmt.pattern_extract)}
 
if #match > 0 then
 
if #match > 0 then
-- TODO maybe remove stat here to avoid testing against in future loops
+
-- TODO maybe remove stat here to avoid testing
  +
-- against in future loops
 
break
 
break
 
end
 
end
Line 1,022: Line 1,187:
 
h.na(tblrow)
 
h.na(tblrow)
 
else
 
else
-- used to find broken progression (i.e. due to game updates)
+
-- used to find broken progression due to game updates
  +
-- for example:
 
statfmt.counter = statfmt.counter + 1
 
statfmt.counter = statfmt.counter + 1
 
tblrow
 
tblrow
 
:tag('td')
 
:tag('td')
:wikitext(string.format(statfmt.pattern_value, match[1], match[2], match[3], match[4], match[5]))
+
:wikitext(string.format(
  +
statfmt.pattern_value,
  +
match[1],
  +
match[2],
  +
match[3],
  +
match[4],
  +
match[5]
  +
)
  +
)
 
:done()
 
:done()
 
end
 
end
Line 1,036: Line 1,210:
 
experience = tonumber(row['skill_levels.experience'])
 
experience = tonumber(row['skill_levels.experience'])
 
if experience ~= nil then
 
if experience ~= nil then
h.int_value_or_na(tpl_args, frame, tblrow, experience - lastexp, {})
+
h.int_value_or_na(
  +
tpl_args,
  +
frame,
  +
tblrow,
  +
experience - lastexp,
  +
{}
  +
)
 
 
 
lastexp = experience
 
lastexp = experience
Line 1,046: Line 1,226:
 
end
 
end
 
 
local empty = ''
+
local cats = {}
 
 
for _, statfmt in ipairs(tpl_args.stat_format) do
 
for _, statfmt in ipairs(tpl_args.stat_format) do
 
if statfmt.counter == 0 then
 
if statfmt.counter == 0 then
  +
cats = i18n.categories.broken_progression_table
empty = '[[Category:Pages with broken skill progression tables]]'
 
 
break
 
break
 
end
 
end
 
end
 
end
 
 
return empty .. tostring(tbl)
+
return tostring(tbl) .. m_util.misc.add_category(cats)
 
end
 
end
   

Revision as of 23:06, 28 September 2020

Template info icon Module documentation[view] [edit] [history] [purge]

Overview

Module for handling skills with semantic media wiki support

Skill templates

Module:Item2

All templates defined in Module:Skill:

Module:Skill link

All templates defined in Module:Skill link:

-- ----------------------------------------------------------------------------
-- Includes
-- ----------------------------------------------------------------------------

local getArgs = require('Module:Arguments').getArgs
local m_cargo = require('Module:Cargo')
local m_util = require('Module:Util')
local m_game = require('Module:Game')

local mwlanguage = mw.language.getContentLanguage()

--- define here to avoid errors
local data = {}
local tables = {}

-- TODO:
-- skill_id field link to data page
-- aspects: % mana cost

-- ----------------------------------------------------------------------------
-- i18n
-- ----------------------------------------------------------------------------

local i18n = {
    skill_icon = 'File:%s skill icon.png',
    skill_screenshot = 'File:%s skill screenshot.jpg',
    
    -- Intro texts:
    intro_named_id = "'''%s''' is the internal id of the [[skill]] '''%s'''.\n",
    intro_unnamed_id = "'''%s''' is the internal id of an unnamed [[skill]].\n",
    
    errors = {
        all_format_keys_specified = 'All formatting keys must be specified for index "%s"',
        no_results_for_skill_id = "Couldn't find a page for the specified skill id",
        no_results_for_skill_page = "Couldn't find the queried data on the skill page",
        missing_level_data = 'No gem level progression data found',
    },
    
    categories = {
        broken_progression_table = 'Pages with broken skill progression tables'
    },
    
    infobox = {
        skill_id = 'Skill Id',
        active_skill_name = 'Name',
        skill_icon = 'Icon',
        cast_time = 'Cast Time',
        item_class_restrictions = 'Item Class<br>Restrictions',
        projectile_speed = 'Projectile Speed',
        radius = 'Radius',
        radius_secondary = 'Radius 2',
        radius_tertiary = 'Radius 3',
        level_requirement = 'Level Req.',
        mana_multiplier = 'Mana Multiplier',
        critical_strike_chance = 'Critical Strike Chance',
        mana_cost = 'Mana Cost',
        mana_reserved = 'Mana Reserved',
        attack_speed_multiplier = 'Attack Speed',
        damage_effectiveness = 'Effectiveness of Added Damage',
        stored_uses = 'Stored Uses',
        cooldown = 'Cooldown',
        vaal_souls_requirement = 'Vaal Souls',
        vaal_stored_uses = 'Vaal Stored Uses',
        vaal_soul_gain_prevention_time = 'Soul Gain Prevention',
        damage_multiplier = 'Damage Multiplier',
        duration = 'Base duration',
    },
    
    progression = {
        level_requirement = m_util.html.abbr('[[Image:Level_up_icon_small.png|link=|Lvl.]]', 'Required Level', 'nounderline'),
        dexterity_requirement = m_util.html.abbr('[[Image:DexterityIcon_small.png|link=|dexterity]]', 'Required Dexterity', 'nounderline'),
        strength_requirement = m_util.html.abbr('[[Image:StrengthIcon_small.png|link=|strength]]', 'Required Strength', 'nounderline'),
        intelligence_requirement = m_util.html.abbr('[[Image:IntelligenceIcon_small.png|link=|intelligence]]', 'Required Intelligence', 'nounderline'),
        mana_multiplier = 'Mana<br>Multiplier',
        critical_strike_chance = 'Critical<br>Strike<br>Chance',
        mana_cost = 'Mana<br>Cost',
        mana_reserved = 'Mana<br>Reserved',
        attack_speed_multiplier = 'Attack<br>Speed<br>Multiplier',
        damage_effectiveness = 'Damage<br>Effectiveness',
        stored_uses = 'Stored<br>Uses',
        cooldown = 'Cooldown',
        vaal_souls_requirement = 'Vaal<br>souls',
        vaal_stored_uses = 'Stored<br>Uses',
        vaal_soul_gain_prevention_time = 'Soul<br>Prevention<br>Time',
        damage_multiplier = m_util.html.abbr('Damage<br>Multiplier', 'Deals x% of Base Damage'),
        duration = m_util.html.abbr('Base duration', 'Base duration is x seconds'),
        exp_short = 'Exp.',
        exp_long = 'Experience needed to level up',
        tot_exp_short = 'Total Exp.',
        tot_exp_long = 'Total experience needed',
    },
}

-- ----------------------------------------------------------------------------
-- Helper functions 
-- ----------------------------------------------------------------------------
local h = {}

function h.map_to_arg(tpl_args, frame, properties, prefix_in, map, level)
    if map.order then
        for _, key in ipairs(map.order) do
            row = map.fields[key]
            if row.name then
                local val = tpl_args[prefix_in .. row.name]
                if row.func ~= nil then
                    val = row.func(tpl_args, frame, val)
                end
                if val == nil and row.default ~= nil then
                    val = row.default
                end
                if val ~= nil then    
                    if level ~= nil then
                        tpl_args.skill_levels[level][key] = val
                        -- Nuke variables since they're remapped to skill_levels
                        tpl_args[prefix_in .. row.name] = nil
                    else
                        tpl_args[row.name] = val
                    end
                    properties[row.field] = val
                end
            else
                error(string.format('Programming error, missing field %s from order keys', key))
            end
        end
    end
end

function h.stats(tpl_args, frame, prefix_in, level)
    local type_prefix = string.format('%s%s', prefix_in, 'stat')
    tpl_args.skill_levels[level]['stats'] = {}
    for i=1, 8 do
        local stat_id_key = string.format('%s%s_%s', type_prefix, i, 'id')
        local stat_val_key = string.format('%s%s_%s', type_prefix, i, 'value')
        local stat = {
            id = tpl_args[stat_id_key],
            value = tonumber(tpl_args[stat_val_key]),
        }
        if stat.id ~= nil and stat.value ~= nil then
            tpl_args.skill_levels[level]['stats'][#tpl_args.skill_levels[level]['stats']+1] = stat
            
            if not tpl_args.test then
                m_cargo.store(frame, {
                    _table = tables.skill_stats_per_level.table,
                    [tables.skill_stats_per_level.fields.level.field] = level,
                    [tables.skill_stats_per_level.fields.id.field] = stat.id,
                    [tables.skill_stats_per_level.fields.value.field] = stat.value,
                })
            end
            
            -- Nuke variables since they're remapped to skill levels
            tpl_args[stat_id_key] = nil
            tpl_args[stat_val_key] = nil
        end
    end
end

function h.na(tr)
    tr
        :tag('td')
            :attr('class', 'table-na')
            :wikitext('N/A')
            :done()
end

function h.int_value_or_na(tpl_args, frame, tr, value, pdata)
    value = tonumber(value)
    if value == nil then
        h.na(tr)
    else
        value = mwlanguage:formatNum(value)
        if pdata.fmt ~= nil then
            if type(pdata.fmt) == 'string' then
                value = string.format(pdata.fmt, value)
            elseif type(pdata.fmt) == 'function' then
                value = string.format(pdata.fmt(tpl_args, frame), value)
            end
        end
        tr
            :tag('td')
                :wikitext(value)
                :done()
    end
end

h.cast = {}
function h.cast.wrap (f)
    return function(tpl_args, frame, value)
        if value == nil then
            return nil
        else
            return f(value)
        end
    end
end

h.display = {}
h.display.factory = {}
function h.display.factory.value(args)
    return function (tpl_args, frame)
        args.fmt = args.fmt or tables.static.fields[args.key].fmt
        local value = tpl_args[args.key]
        if args.fmt and value then
            return string.format(args.fmt, value)
        else
            return value
        end
    end
end

function h.display.factory.range_value(args)
    return function (tpl_args, frame)
        local value = {
            min = tpl_args.skill_levels[0][args.key] or tpl_args.skill_levels[1][args.key],
            max = tpl_args.skill_levels[0][args.key] or tpl_args.skill_levels[tpl_args.max_level][args.key],
        }
        -- property not set for this skill
        if value.min == nil or value.max == nil then
            return
        end
        
        return m_util.html.format_value(tpl_args, frame, value, {
            fmt=args.fmt or tables.progression.fields[args.key].fmt,
            no_color=true,
        })
    end
end

function h.display.factory.radius(args)
    return function (tpl_args, frame)
        local radius = tpl_args['radius' .. args.key]
        if radius == nil then
            return
        end
        local description = tpl_args[string.format('radius%s_description', args.key)]
        if description then
            return m_util.html.abbr(radius, description)
        else
            return radius
        end
    end
end

-- ----------------------------------------------------------------------------
-- Data
-- ----------------------------------------------------------------------------

tables.static = {
    table = 'skill',
    order = {'skill_id', 'cast_time', 'gem_description', 'active_skill_name', 'skill_icon', 'item_class_id_restriction', 'item_class_restriction', 'projectile_speed', 'stat_text', 'quality_stat_text', 'has_percentage_mana_cost', 'has_reservation_mana_cost', 'radius', 'radius_secondary', 'radius_tertiary', 'radius_description', 'radius_secondary_description', 'radius_tertiary_description', 'skill_screenshot'},
    fields = {
        -- GrantedEffects.dat
        skill_id = {
            name = 'skill_id',
            field = 'skill_id',
            type = 'String',
            func = nil,
        },
        -- Active Skills.dat
        cast_time = {
            name = 'cast_time',
            field = 'cast_time',
            type = 'Float',
            func = h.cast.wrap(m_util.cast.number),
            fmt = '%ss',
        },
        gem_description = {
            name = 'gem_description',
            field = 'description',
            type = 'Text',
            func = nil,
        },
        active_skill_name = {
            name = 'active_skill_name',
            field = 'active_skill_name',
            type = 'String',
            func = nil,
        },
        skill_icon = {
            name = 'skill_icon',
            field = 'skill_icon',
            type = 'Page',
            func = function(tpl_args, frame)
                if tpl_args.active_skill_name then
                    return string.format(i18n.skill_icon, tpl_args.active_skill_name)
                end
            end,
        },
        item_class_id_restriction = {
            name = 'item_class_id_restriction',
            field = 'item_class_id_restriction',
            type = 'List (,) of String',
            func = function(tpl_args, frame, value)
                if value == nil then 
                    return nil
                end
                value = m_util.string.split(value, ', ')
                for _, v in ipairs(value) do
                    if m_game.constants.item.classes[v] == nil then
                        error(string.format('Invalid item class id: %s', v))
                    end
                end
                return value
            end,
        },
        item_class_restriction = {
            name = 'item_class_restriction',
            field = 'item_class_restriction',
            type = 'List (,) of String',
            func = function(tpl_args, frame, value)
                if tpl_args.item_class_id_restriction == nil then
                    return
                end
                -- This function makes a localized list based on ids
                local item_classes = {}
                for _, v in ipairs(tpl_args.item_class_id_restriction) do
                    item_classes[#item_classes+1] = m_game.constants.item.classes[v].full
                end
                
                return item_classes
            end,
        },
        -- Projectiles.dat - manually mapped to the skills
        projectile_speed = {
            name = 'projectile_speed',
            field = 'projectile_speed',
            type = 'Integer',
            func = h.cast.wrap(m_util.cast.number),
        },
        -- Misc data derieved from stats
        stat_text = {
            name = 'stat_text',
            field = 'stat_text',
            type = 'Text',
            func = nil,
        },
        quality_stat_text = {
            name = 'quality_stat_text',
            field = 'quality_stat_text',
            type = 'Text',
            func = nil,
        },
        -- Misc data currently not from game data
        has_percentage_mana_cost = {
            name = 'has_percentage_mana_cost',
            field = 'has_percentage_mana_cost',
            type = 'Boolean',
            func = h.cast.wrap(m_util.cast.boolean),
            default = false,
        },
        has_reservation_mana_cost = {
            name = 'has_reservation_mana_cost',
            field = 'has_reservation_mana_cost',
            type = 'Boolean',
            func = h.cast.wrap(m_util.cast.boolean),
            default = false,
        },
        radius = {
            name = 'radius',
            field = 'radius',
            type = 'Integer',
            func = h.cast.wrap(m_util.cast.number),
        },
        radius_description = {
            name = 'radius_description',
            field = 'radius_description',
            type = 'Text',
            func = nil,
        },
        radius_secondary = {
            name = 'radius_secondary',
            field = 'radius_secondary',
            type = 'Integer',
            func = h.cast.wrap(m_util.cast.number),
        },
        radius_secondary_description = {
            name = 'radius_secondary_description',
            field = 'radius_secondary_description',
            type = 'Text',
            func = nil,
        },
        -- not sure if any skill actually has 3 radius componets
        radius_tertiary = {
            name = 'radius_tertiary',
            field = 'radius_tertiary',
            type = 'Integer',
            func = h.cast.wrap(m_util.cast.number),
        },
        radius_tertiary_description = {
            name = 'radius_tertiary_description',
            field = 'radius_tertiary_description',
            type = 'Text',
            func = nil,
        },
        -- Set manually
        max_level = {
            field = 'max_level',
            type = 'Integer',
        },
        html = {
            field = 'html',
            type = 'Text',
        },
        skill_screenshot = {
            name = 'skill_screenshot',
            field = 'skill_screenshot',
            type = 'Page',
            func = function(tpl_args, frame)
                local ss
                if tpl_args.skill_screenshot_file ~= nil then
                    ss = string.format('File:%s', tpl_args.skill_screenshot_file)
                elseif tpl_args.skill_screenshot ~= nil then
                    ss = string.format(i18n.skill_screenshot, tpl_args.skill_screenshot)
                elseif tpl_args.active_skill_name then
                    -- When this parameter is set manually, we assume/expect it to be exist, but otherwise it probably doesn't and we don't need dead links in that case
                    ss = string.format(i18n.skill_screenshot, tpl_args.active_skill_name)
                    page = mw.title.new(ss)
                    if page == nil or not page.exists then
                        ss = nil
                    end
                end
                return ss
            end,
        },
    },
}

tables.progression = {
    table = 'skill_levels',
    order = {'level_requirement', 'dexterity_requirement', 'intelligence_requirement', 'strength_requirement', 'mana_multiplier', 'critical_strike_chance', 'mana_cost', 'attack_speed_multiplier', 'damage_effectiveness', 'stored_uses', 'cooldown', 'vaal_souls_requirement', 'vaal_stored_uses', 'vaal_soul_gain_prevention_time', 'damage_multiplier', 'duration', 'experience', 'stat_text'},
    fields = {
        level = {
            name = nil,
            field = 'level',
            type = 'Integer',
            func = nil,
            header = nil,
        },
        level_requirement = {
            name = 'level_requirement',
            field = 'level_requirement',
            type = 'Integer',
            func = h.cast.wrap(m_util.cast.number),
            header = i18n.progression.level_requirement,
        },
        dexterity_requirement = {
            name = 'dexterity_requirement',
            field = 'dexterity_requirement',
            type = 'Integer',
            func = h.cast.wrap(m_util.cast.number),
            header = i18n.progression.dexterity_requirement,
        },
        strength_requirement = {
            name = 'strength_requirement',
            field = 'strength_requirement',
            type = 'Integer',
            func = h.cast.wrap(m_util.cast.number),
            header = i18n.progression.strength_requirement,
        },
        intelligence_requirement = {
            name = 'intelligence_requirement',
            field = 'intelligence_requirement',
            type = 'Integer',
            func = h.cast.wrap(m_util.cast.number),
            header = i18n.progression.intelligence_requirement,
        },
        mana_multiplier = {
            name = 'mana_multiplier',
            field = 'mana_multiplier',
            type = 'Float',
            func = h.cast.wrap(m_util.cast.number),
            header = i18n.progression.mana_multiplier,
            fmt = '%s%%',
        },
        critical_strike_chance = {
            name = 'critical_strike_chance',
            field = 'critical_strike_chance',
            type = 'Float',
            func = h.cast.wrap(m_util.cast.number),
            header = i18n.progression.critical_strike_chance,
            fmt = '%s%%',
        },
        mana_cost = {
            name = 'mana_cost',
            field = 'mana_cost',
            type = 'Integer',
            func = h.cast.wrap(m_util.cast.number),
            header = function (skill_data)
                if skill_data["skill.has_reservation_mana_cost"] then
                    return i18n.progression.mana_reserved
                else
                    return i18n.progression.mana_cost
                end
            end,
            fmt = function (tpl_args, frame)
                if tpl_args.has_percentage_mana_cost or (tpl_args.skill_data and tpl_args.skill_data['skill.has_percentage_mana_cost']) then
                    str = '%s%%'
                else
                    str = '%s'
                end
            
                return str
            end,
        },
        damage_effectiveness = {
            name = 'damage_effectiveness',
            field = 'damage_effectiveness',
            type = 'Float',
            func = h.cast.wrap(m_util.cast.number),
            header = i18n.progression.damage_effectiveness,
            fmt = '%s%%',
        },
        stored_uses = {
            name = 'stored_uses',
            field = 'stored_uses',
            type = 'Integer',
            func = h.cast.wrap(m_util.cast.number),
            header = i18n.progression.stored_uses,
        },
        cooldown = {
            name = 'cooldown',
            field = 'cooldown',
            type = 'Float',
            func = h.cast.wrap(m_util.cast.number),
            header = i18n.progression.cooldown,
            fmt = '%ss',
        },
        vaal_souls_requirement = {
            name = 'vaal_souls_requirement',
            field = 'vaal_souls_requirement',
            type = 'Integer',
            func = h.cast.wrap(m_util.cast.number),
            header = i18n.progression.vaal_souls_requirement,
        },
        vaal_stored_uses = {
            name = 'vaal_stored_uses',
            field = 'vaal_stored_uses',
            type = 'Integer',
            func = h.cast.wrap(m_util.cast.number),
            header = i18n.progression.vaal_stored_uses,
        },
        vaal_soul_gain_prevention_time = {
            name = 'vaal_soul_gain_prevention_time',
            field = 'vaal_soul_gain_prevention_time',
            type = 'Float',
            func = h.cast.wrap(m_util.cast.number),
            header = i18n.progression.vaal_soul_gain_prevention_time,
            fmt = '%ss',
        },
        damage_multiplier = {
            name = 'damage_multiplier',
            field = 'damage_multiplier',
            type = 'Float',
            func = h.cast.wrap(m_util.cast.number),
            header = i18n.progression.damage_multiplier,
            fmt = '%s%%',
        },
        attack_speed_multiplier = {
            name = 'attack_speed_multiplier',
            field = 'attack_speed_multiplier',
            type = 'Integer',
            func = h.cast.wrap(m_util.cast.number),
            header = i18n.progression.attack_speed_multiplier,
            fmt = '%s%%',
        },
        duration = {
            name = 'duration',
            field = 'duration',
            type = 'Float',
            func = h.cast.wrap(m_util.cast.number),
            header = i18n.progression.duration,
            fmt = '%ss',
        },
        -- from gem experience, optional
        experience = {
            name = 'experience',
            field = 'experience',
            type = 'Integer',
            func = h.cast.wrap(m_util.cast.number),
            hide = true,
        },
        stat_text = {
            name = 'stat_text',
            field = 'stat_text',
            type = 'Text',
            func = nil,
            hide = true,
        },
    }
}

data.progression_display_order = {'level_requirement', 'dexterity_requirement', 'strength_requirement', 'intelligence_requirement', 'mana_multiplier', 'critical_strike_chance', 'mana_cost', 'damage_effectiveness', 'stored_uses', 'cooldown', 'vaal_souls_requirement', 'vaal_stored_uses', 'vaal_soul_gain_prevention_time', 'damage_multiplier', 'duration', 'attack_speed_multiplier'}

tables.skill_stats_per_level = {
    table = 'skill_stats_per_level',
    fields = {
        level = {
            field = 'level',
            type = 'Integer',
        },
        id = {
            field = 'id',
            type = 'String',
        },
        value = {
            field = 'value',
            type = 'Integer',
        },
    },
}

tables.skill_quality = {
    table = 'skill_quality',
    fields = {
        set_id = {
            field = 'set_id',
            type = 'Integer',
        },
        weight = {
            field = 'weight',
            type = 'Integer',
        },
        stat_text = {
            field = 'stat_text',
            type = 'String',
        },
    },
}

tables.skill_quality_stats = {
    table = 'skill_quality_stats',
    fields = {
        set_id = {
            field = 'set_id',
            type = 'Integer',
        },
        id = {
            field = 'id',
            type = 'String',
        },
        value = {
            field = 'value',
            type = 'Integer',
        },
    },
}

data.infobox_table = {
    {
        header = i18n.infobox.active_skill_name,
        func = h.display.factory.value{key='active_skill_name'},
    },
    {
        header = i18n.infobox.skill_id,
        func = function (tpl_args, frame)
            return string.format('[[%s|%s]]', mw.title.getCurrentTitle().fullText, tpl_args.skill_id)
        end 
    },
    {
        header = i18n.infobox.skill_icon,
        func = function (tpl_args, frame)
            if tpl_args.skill_icon then 
                return string.format('[[%s]]', tpl_args.skill_icon)
            end
        end,
    },
    {
        header = i18n.infobox.cast_time,
        func = h.display.factory.value{key='cast_time'},
    },
    {
        header = i18n.infobox.item_class_restrictions,
        func = function (tpl_args, frame)
            if tpl_args.item_class_restriction == nil then
                return
            end
            local out = {}
            for _, class in ipairs(tpl_args.item_class_restriction) do
                out[#out+1] = string.format('[[%s]]', class)
            end
            return table.concat(out, '<br>')
        end,
    },
    {
        header = i18n.infobox.projectile_speed,
        func = h.display.factory.value{key='projectile_speed'},
    },
    {
        header = i18n.infobox.radius,
        func = h.display.factory.radius{key=''},
    },
    {
        header = i18n.infobox.radius_secondary,
        func = h.display.factory.radius{key='_secondary'},
    },
    {
        header = i18n.infobox.radius_tertiary,
        func = h.display.factory.radius{key='_tertiary'},
    },
    {
        header = i18n.infobox.level_requirement,
        func = h.display.factory.range_value{key='level_requirement'},
    },
    -- ingore attrbiutes?
    {
        header = i18n.infobox.mana_multiplier,
        func = h.display.factory.range_value{key='mana_multiplier'},
    },
    {
        header = i18n.infobox.critical_strike_chance,
        func = h.display.factory.range_value{key='critical_strike_chance'},
    },
    {
        header = i18n.infobox.mana_cost,
        func = h.display.factory.range_value{key='mana_cost'},
    },
    {
        header = i18n.infobox.attack_speed_multiplier,
        func = h.display.factory.range_value{key='attack_speed_multiplier'},
    },
    {
        header = i18n.infobox.damage_effectiveness,
        func = h.display.factory.range_value{key='damage_effectiveness'},
    },
    {
        header = i18n.infobox.stored_uses,
        func = h.display.factory.range_value{key='stored_uses'},
    },
    {
        header = i18n.infobox.cooldown,
        func = h.display.factory.range_value{key='cooldown'},
    },
    {
        header = i18n.infobox.vaal_souls_requirement,
        func = h.display.factory.range_value{key='vaal_souls_requirement'},
    },
    {
        header = i18n.infobox.vaal_stored_uses,
        func = h.display.factory.range_value{key='vaal_stored_uses'},
    },
    {
        header = i18n.infobox.vaal_soul_gain_prevention_time,
        func = h.display.factory.range_value{key='vaal_soul_gain_prevention_time'},
    }, 
    {
        header = i18n.infobox.damage_multiplier,
        func = h.display.factory.range_value{key='damage_multiplier'},
    },
    {
        header = i18n.infobox.duration,
        func = h.display.factory.range_value{key='duration'},
    },
    {
        header = nil,
        func = h.display.factory.value{key='gem_description'},
        class = 'tc -gemdesc',
    },
    {
        header = nil,
        func = h.display.factory.value{key='stat_text'},
        class = 'tc -mod',
    },
}

-- ----------------------------------------------------------------------------
-- Templates
-- ----------------------------------------------------------------------------
local p = {}

p.table_skills = m_util.cargo.declare_factory{data=tables.static}
p.table_skill_levels = m_util.cargo.declare_factory{data=tables.progression}
p.table_skill_stats_per_level = m_util.cargo.declare_factory{data=tables.skill_stats_per_level}
p.table_skill_quality = m_util.cargo.declare_factory{data=tables.skill_quality}
p.table_skill_quality_stats = m_util.cargo.declare_factory{data=tables.skill_quality_stats}

--
-- Template:Skill
--
function p.skill(frame, tpl_args)
    --[[
    Creates an infobox for skills.
    
    Examples
    --------
    =p.skill{gem_description='Icy bolts rain down over the targeted area.', active_skill_name='Icestorm', skill_id='IcestormUniqueStaff12', cast_time=0.75, required_level=1, static_mana_cost=22, static_critical_strike_chance=6, static_damage_effectiveness=30, static_damage_multiplier=100, static_stat1_id='spell_minimum_base_cold_damage_+_per_10_intelligence', static_stat1_value=1, static_stat2_id='spell_maximum_base_cold_damage_+_per_10_intelligence', static_stat2_value=3, static_stat3_id='base_skill_effect_duration', static_stat3_value=1500, static_stat4_id='fire_storm_fireball_delay_ms', static_stat4_value=100, static_stat5_id='skill_effect_duration_per_100_int', static_stat5_value=150, static_stat6_id='skill_override_pvp_scaling_time_ms', static_stat6_value=450, static_stat7_id='firestorm_drop_ground_ice_duration_ms', static_stat7_value=500, static_stat8_id='skill_art_variation', static_stat8_value=4, static_stat9_id='base_skill_show_average_damage_instead_of_dps', static_stat9_value=1, static_stat10_id='is_area_damage', static_stat10_value=1, stat_text='Deals 1 to 3 base Cold Damage per 10 Intelligence<br>Base duration is 1.5 seconds<br>One impact every 0.1 seconds<br>0.15 seconds additional Base Duration per 100 Intelligence', quality_stat_text = nil, level1=true, level1_level_requirement=1}

    ]]

    if tpl_args == nil then
        tpl_args = getArgs(frame, {
            parentFirst = true
        })
    end
    frame = m_util.misc.get_frame(frame)
    
    --
    -- Args
    --
    local properties
    
    tpl_args.skill_levels = {
        [0] = {},
    }
    
    tpl_args.skill_quality = {}
    local i = 0
    repeat
        i = i + 1
        local prefix = string.format('quality_type%s', i)
        local q = {
            _table = tables.skill_quality.table,
            set_id = i,
            weight = tonumber(tpl_args[string.format('%s_weight', prefix)]),
            stat_text = tpl_args[string.format('%s_stat_text', prefix)],
        }
        if q.stat_text then
            tpl_args.skill_quality[#tpl_args.skill_quality+1] = q
            m_cargo.store(frame, q)
            
            q.stats = {}
            q._table = nil
            local j = 0
            repeat 
                j = j + 1
                local stat_prefix = string.format('%s_stat%s', prefix, j)
                local s = {
                    _table = tables.skill_quality_stats.table,
                    set_id = i,
                    id = tpl_args[string.format('%s_id', stat_prefix)],
                    value = tonumber(tpl_args[string.format('%s_value', stat_prefix)]),
                }
                if s.id and s.value then
                    q.stats[#q.stats+1] = s
                    m_cargo.store(frame, s)
                end
                
                s._table = nil
            until s.id == nil or s.value == nil 
        end
    until q.stat_text == nil
    
    -- Handle level progression
    local i = 0
    repeat 
        i = i + 1
        local prefix = 'level' .. i 
        local level = m_util.cast.boolean(tpl_args[prefix])
        if level == true then
            -- Don't need this anymore
            tpl_args[prefix] = nil
            tpl_args.skill_levels[i] = {}
            prefix = prefix .. '_'
        
            if tpl_args[prefix .. 'experience'] ~= nil then
                tpl_args.max_level = i
            end
            
            properties = {
                _table = tables.progression.table,
                [tables.progression.fields.level.field] = i
            }
            h.map_to_arg(tpl_args, frame, properties, prefix, tables.progression, i)
            if not tpl_args.test then
                m_cargo.store(frame, properties)
            end
            
            h.stats(tpl_args, frame, prefix, i)
        end
    until level ~= true
    -- If no experience is given, assume this is a non skill gem skill.
    tpl_args.max_level = tpl_args.max_level or (i - 1)
    -- handle static progression
    properties = {
        _table = tables.progression.table,
        [tables.progression.fields.level.field] = 0
    }
    h.map_to_arg(tpl_args, frame, properties, 'static_', tables.progression, 0)
    if not tpl_args.test then
        m_cargo.store(frame, properties)
    end
    
    -- Handle static arguments
    properties = {
        _table = tables.static.table,
        
        [tables.static.fields.max_level.field] = tpl_args.max_level
    }
    
    h.map_to_arg(tpl_args, frame, properties, '', tables.static)
    h.stats(tpl_args, frame, 'static_', 0)
    
    
    
    --
    -- Infobox progressing
    --
    local infobox = mw.html.create('span')
    infobox:attr('class', 'skill-box')
    
    -- tablular sections
    local tbl = infobox:tag('table')
    tbl:attr('class', 'wikitable skill-box-table')
    for _, infobox_data in ipairs(data.infobox_table) do
        local display = infobox_data.func(tpl_args, frame)
        if display then
            local tr = tbl:tag('tr')
            if infobox_data.header then
                tr
                    :tag('th')
                        :wikitext(infobox_data.header)
                        :done()
            end
            local td = tr:tag('td')
            td:wikitext(display)
            td:attr('class', infobox_data.class or 'tc -value')
            if infobox_data.header == nil then
                td:attr('colspan', 2)
            end
        end
    end
    
    infobox = tostring(infobox)

    
    --
    -- Store data
    --
    properties[tables.static.fields.html.field] = infobox
    if not tpl_args.test then
        m_cargo.store(frame, properties)
    end
    
    --
    --
    --
    local container = mw.html.create('span')
    container
        :attr('class', 'skill-box-page-container')
        :wikitext(infobox)
    if tpl_args.skill_screenshot then
        container
            :wikitext(string.format('[[%s]]', tpl_args.skill_screenshot))
    end

    -- Generic messages on the page:
    out = {}
    if mw.ustring.find(tpl_args.skill_id, '_') then
        out[#out+1] = frame:expandTemplate{ 
            title = 'Incorrect title', 
            args = {title=tpl_args.skill_id} 
        } .. '\n\n\n'
    end
    if tpl_args.active_skill_name then
        out[#out+1] = string.format(
            i18n.intro_named_id, 
            tpl_args.skill_id, 
            tpl_args.active_skill_name
        )
    else
        out[#out+1] = string.format(
            i18n.intro_unnamed_id, 
            tpl_args.skill_id
        )
    end
    
    return tostring(container) .. m_util.misc.add_category({'Skill data'}) .. '\n' .. table.concat(out) 
end

function p.progression(frame)
    --[[
        Displays the level progression for the skill gem. 
        
        Examples
        --------
        = p.progression{page='Reave'}
    ]]
    
    local tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = m_util.misc.get_frame(frame)
    
    -- Parse column arguments:
    tpl_args.stat_format = {}
    local prefix
    for i=1, 9 do
        prefix = 'c' .. i .. '_' 
        local format_keys = {
            'header', 
            'abbr', 
            'pattern_extract', 
            'pattern_value'
        }
        local statfmt = {counter = 0}
        for _,v in ipairs(format_keys) do
            statfmt[v] = tpl_args[prefix .. v]
        end 
        
        if m_util.table.has_all_value(statfmt, format_keys) then
            break
        end
        
        if m_util.table.has_one_value(statfmt, format_keys) then
            error(string.format(i18n.errors.all_format_keys_specified, i))
        end
        
        statfmt.header = m_util.html.abbr(statfmt.abbr, statfmt.header)
        statfmt.abbr = nil
        tpl_args.stat_format[#tpl_args.stat_format+1] = statfmt
    end
    
    
    local results = {}
    local query = {
        groupBy = 'skill._pageID',
    }
    local skill_data
    
    local fields = {
        'skill._pageName',
        'skill.has_reservation_mana_cost',
        'skill.has_percentage_mana_cost',
    }
    
    if tpl_args.skill_id then
        query.where = string.format(
            'skill.skill_id="%s"', 
            tpl_args.skill_id
        ) 
        results = m_cargo.query({'skill'}, fields, query)
        if #results == 0 then
            error(i18n.errors.no_results_for_skill_id)
        end
        skill_data = results[1]
    else
        if tpl_args.page then
            page = tpl_args.page
        else
            page = mw.title.getCurrentTitle().prefixedText
        end
        query.where = string.format('skill._pageName="%s"', page)
        
        results = m_cargo.query({'skill'}, fields, query)
        if #results == 0 then
            error(i18n.errors.no_results_for_skill_page)
        end
        
        skill_data = results[1]
    end
    
    tpl_args.skill_data = skill_data
    
    skill_data["skill.has_reservation_mana_cost"] = m_util.cast.boolean(skill_data["skill.has_reservation_mana_cost"])
    skill_data['skill.has_percentage_mana_cost'] = m_util.cast.boolean(skill_data["skill.has_percentage_mana_cost"])
    
    query.where = string.format(
        'skill_levels._pageName="%s"', 
        skill_data['skill._pageName']
    )
    fields = {}
    for _, pdata in pairs(tables.progression.fields) do
        fields[#fields+1] = string.format('skill_levels.%s', pdata.field)
    end
    
    results = m_cargo.query(
        {'skill_levels'}, 
        fields, 
        {
            where=string.format(
                'skill_levels._pageName="%s" AND skill_levels.level > 0', 
                skill_data['skill._pageName']
            ),
            groupBy='skill_levels._pageID, skill_levels.level',
            orderBy='skill_levels.level ASC',
        }
    )
    
    if #results == 0 then
        error(i18n.errors.missing_level_data)
    end
    
    headers = {}
    for i, row in ipairs(results) do
        for k, v in pairs(row) do
            headers[k] = true
        end
    end
    
    local tbl = mw.html.create('table')
    tbl
        :attr(
            'class', 
            'wikitable responsive-table skill-progression-table'
        )
    
    local head = tbl:tag('tr')
    head
        :tag('th')
            :wikitext('Level')
            :done()
    
    for _, key in ipairs(data.progression_display_order) do
        local pdata = tables.progression.fields[key]
        -- TODO should be nil?
        if pdata.hide == nil and headers['skill_levels.' .. pdata.field] then
            local text
            if type(pdata.header) == 'function' then
                text = pdata.header(skill_data)
            else
                text = pdata.header
            end
            head
                :tag('th')
                    :wikitext(text)
                    :done()
        end
    end
    
    for _, statfmt in ipairs(tpl_args.stat_format) do
        head
            :tag('th')
                :wikitext(statfmt.header)
                :done()
    end
    
    if headers['skill_levels.experience'] then
        head
            :tag('th')
                :wikitext(m_util.html.abbr(
                    i18n.progression.exp_short, 
                    i18n.progression.exp_long
                    )
                )
                :done()
            :tag('th')
                :wikitext(m_util.html.abbr(
                    i18n.progression.tot_exp_short, 
                    i18n.progression.tot_exp_long
                    )
                )
                :done()
    end
    
    local tblrow
    local lastexp = 0
    local experience
    
    for i, row in ipairs(results) do
        tblrow = tbl:tag('tr')
        tblrow
            :tag('th')
                :wikitext(row['skill_levels.level'])
                :done()
        
        for _, key in ipairs(data.progression_display_order) do
            local pdata = tables.progression.fields[key]
            if pdata.hide == nil and headers['skill_levels.' .. pdata.field] then
                h.int_value_or_na(
                    tpl_args, 
                    frame, 
                    tblrow, 
                    row['skill_levels.' .. pdata.field], 
                    pdata
                )
            end
        end
        
        -- stats
        if row['skill_levels.stat_text'] then
            stats = m_util.string.split(
                row['skill_levels.stat_text'], 
                '<br>'
            )
        else
            stats = {}
        end
        for _, statfmt in ipairs(tpl_args.stat_format) do
            local match = {}
            for j, stat in ipairs(stats) do
                match = {string.match(stat, statfmt.pattern_extract)}
                if #match > 0 then
                    -- TODO maybe remove stat here to avoid testing 
                    -- against in future loops
                    break
                end
            end
            if #match == 0 then
                h.na(tblrow)
            else
                -- used to find broken progression due to game updates
                -- for example:
                statfmt.counter = statfmt.counter + 1
                tblrow
                    :tag('td')
                        :wikitext(string.format(
                            statfmt.pattern_value, 
                            match[1], 
                            match[2], 
                            match[3], 
                            match[4], 
                            match[5]
                            )
                        )
                        :done()
            end
        end
        
        -- TODO: Quality stats, afaik no gems use this atm
        
        if headers['skill_levels.experience'] then
            experience = tonumber(row['skill_levels.experience'])
            if experience ~= nil then
                h.int_value_or_na(
                    tpl_args, 
                    frame, 
                    tblrow, 
                    experience - lastexp, 
                    {}
                )
                
                lastexp = experience
            else
                h.na(tblrow)
            end
            h.int_value_or_na(tpl_args, frame, tblrow, experience, {})
        end
    end
    
    local cats = {}
    for _, statfmt in ipairs(tpl_args.stat_format) do
        if statfmt.counter == 0 then
            cats = i18n.categories.broken_progression_table
            break
        end
    end
    
    return tostring(tbl) .. m_util.misc.add_category(cats) 
end

function p.map(arg)
    for key, data in pairs(map[arg].fields) do
        mw.logObject(key)
    end
end

-- ----------------------------------------------------------------------------
-- Debug
-- ----------------------------------------------------------------------------

p._debug = {}
function p._debug.order(frame)
    for _, mapping in ipairs({'static', 'progression'}) do
        for _, key in ipairs(map[mapping].order) do
            if map[mapping].fields[key] == nil then
                mw.logObject(string.format('Missing key in %s.fields: %s', mapping, key))
            end
        end
        for key, _ in pairs(map[mapping].fields) do
            local missing = true
            for _, order_key in ipairs(map[mapping].order) do
                if order_key == key then
                    missing = false
                    break
                end
            end
            if missing then
                mw.logObject(string.format('Missing key in %s.order: %s', mapping, key))
            end
        end
    end
end

return p