мНет описания правки |
мНет описания правки |
||
Строка 1: | Строка 1: | ||
+ | -- ---------------------------------------------------------------------------- |
||
− | local p = {} |
||
+ | -- Includes |
||
+ | -- ---------------------------------------------------------------------------- |
||
local getArgs = require('Module:Arguments').getArgs |
local getArgs = require('Module:Arguments').getArgs |
||
Строка 7: | Строка 9: | ||
local mwlanguage = mw.language.getContentLanguage() |
local mwlanguage = mw.language.getContentLanguage() |
||
+ | --- define here to avoid errors |
||
− | -- |
||
+ | local data = {} |
||
− | -- Data |
||
+ | local map = {} |
||
− | -- |
||
+ | |||
+ | -- TODO: |
||
+ | -- skill_id field link to data page |
||
+ | -- aspects: % mana cost |
||
+ | |||
+ | -- ---------------------------------------------------------------------------- |
||
+ | -- i18n |
||
+ | -- ---------------------------------------------------------------------------- |
||
local i18n = { |
local i18n = { |
||
skill_icon = 'File:%s skill icon.png', |
skill_icon = 'File:%s skill icon.png', |
||
+ | skill_screenshot = 'File:%s skill screenshot.jpg', |
||
+ | |||
+ | 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', |
||
+ | damage_effectiveness = 'Damage Effectiveness', |
||
+ | stored_uses = 'Stored Uses', |
||
+ | 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_stored_uses = 'Vaal Stored Uses', |
||
+ | damage_multiplier = 'Damage Multiplier', |
||
+ | }, |
||
+ | |||
+ | progression = { |
||
+ | skill_id = 'Skill Id', |
||
+ | 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', |
||
+ | damage_effectiveness = 'Damage Effectiveness', |
||
+ | stored_uses = 'Stored Uses', |
||
+ | 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_stored_uses = 'Vaal Stored Uses', |
||
+ | damage_multiplier = 'Damage Multiplier', |
||
+ | }, |
||
args = { |
args = { |
||
Строка 35: | Строка 90: | ||
radius_tertiary = 'radius_tertiary', |
radius_tertiary = 'radius_tertiary', |
||
radius_tertiary_description = 'radius_tertiary_description', |
radius_tertiary_description = 'radius_tertiary_description', |
||
+ | skill_screenshot = 'skill_screenshot', |
||
-- skill_levels |
-- skill_levels |
||
Строка 66: | Строка 122: | ||
} |
} |
||
+ | -- ---------------------------------------------------------------------------- |
||
− | -- |
||
+ | -- Helper functions |
||
− | -- Data |
||
+ | -- ---------------------------------------------------------------------------- |
||
− | -- |
||
+ | local h = {} |
||
+ | function h.map_to_arg(tpl_args, frame, properties, prefix_in, map, level) |
||
− | local data = {} |
||
+ | if map.order then |
||
− | data.cast = {} |
||
+ | for _, key in ipairs(map.order) do |
||
− | data.cast.wrap = function(f) |
||
+ | 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) |
||
+ | for _, stat_type in ipairs(map.stats) do |
||
+ | local type_prefix = string.format('%s%s%s', prefix_in, stat_type.prefix_in, i18n.args.infix_stat) |
||
+ | tpl_args.skill_levels[level][stat_type.out] = {} |
||
+ | 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, i18n.args.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][stat_type.out][#tpl_args.skill_levels[level][stat_type.out]+1] = stat |
||
+ | |||
+ | m_util.cargo.store(frame, { |
||
+ | _table = map.skill_stats_per_level.table, |
||
+ | [map.skill_stats_per_level.fields.level.field] = level, |
||
+ | [map.skill_stats_per_level.fields.id.field] = stat.id, |
||
+ | [map.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 |
||
+ | 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) |
return function(tpl_args, frame, value) |
||
if value == nil then |
if value == nil then |
||
Строка 82: | Строка 225: | ||
end |
end |
||
− | + | h.display = {} |
|
+ | h.display.factory = {} |
||
+ | function h.display.factory.value(args) |
||
+ | return function (tpl_args, frame) |
||
+ | if args.fmt then |
||
+ | return string.format(args.fmt, tpl_args[args.key]) |
||
+ | else |
||
+ | return tpl_args[args.key] |
||
+ | 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 map.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 |
||
+ | -- ---------------------------------------------------------------------------- |
||
+ | |||
+ | |||
map.stats = { |
map.stats = { |
||
{ |
{ |
||
Строка 98: | Строка 290: | ||
map.static = { |
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'}, |
+ | 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'}, |
fields = { |
fields = { |
||
-- GrantedEffects.dat |
-- GrantedEffects.dat |
||
Строка 107: | Строка 299: | ||
func = nil, |
func = nil, |
||
}, |
}, |
||
− | --[[is_support_gem = { |
||
− | name = i18n.args.is_support_gem, |
||
− | field = 'is_support_gem', |
||
− | type = 'Boolean', |
||
− | func = data.cast.wrap(m_util.cast.boolean), |
||
− | }, |
||
− | support_gem_letter = { |
||
− | name = i18n.args.support_gem_letter, |
||
− | field = 'support_gem_letter', |
||
− | type = 'String (size=2)', |
||
− | func = nil, |
||
− | },-]]-- |
||
-- Active Skills.dat |
-- Active Skills.dat |
||
cast_time = { |
cast_time = { |
||
Строка 124: | Строка 304: | ||
field = 'cast_time', |
field = 'cast_time', |
||
type = 'Float', |
type = 'Float', |
||
− | func = |
+ | func = h.cast.wrap(m_util.cast.number), |
}, |
}, |
||
gem_description = { |
gem_description = { |
||
Строка 171: | Строка 351: | ||
field = 'projectile_speed', |
field = 'projectile_speed', |
||
type = 'Integer', |
type = 'Integer', |
||
− | func = |
+ | func = h.cast.wrap(m_util.cast.number), |
}, |
}, |
||
-- Misc data derieved from stats |
-- Misc data derieved from stats |
||
Строка 191: | Строка 371: | ||
field = 'has_percentage_mana_cost', |
field = 'has_percentage_mana_cost', |
||
type = 'Boolean', |
type = 'Boolean', |
||
− | func = |
+ | func = h.cast.wrap(m_util.cast.boolean), |
default = false, |
default = false, |
||
}, |
}, |
||
Строка 198: | Строка 378: | ||
field = 'has_reservation_mana_cost', |
field = 'has_reservation_mana_cost', |
||
type = 'Boolean', |
type = 'Boolean', |
||
− | func = |
+ | func = h.cast.wrap(m_util.cast.boolean), |
default = false, |
default = false, |
||
}, |
}, |
||
Строка 205: | Строка 385: | ||
field = 'radius', |
field = 'radius', |
||
type = 'Integer', |
type = 'Integer', |
||
− | func = |
+ | func = h.cast.wrap(m_util.cast.number), |
}, |
}, |
||
radius_description = { |
radius_description = { |
||
Строка 217: | Строка 397: | ||
field = 'radius_secondary', |
field = 'radius_secondary', |
||
type = 'Integer', |
type = 'Integer', |
||
− | func = |
+ | func = h.cast.wrap(m_util.cast.number), |
}, |
}, |
||
radius_secondary_description = { |
radius_secondary_description = { |
||
Строка 230: | Строка 410: | ||
field = 'radius_tertiary', |
field = 'radius_tertiary', |
||
type = 'Integer', |
type = 'Integer', |
||
− | func = |
+ | func = h.cast.wrap(m_util.cast.number), |
}, |
}, |
||
radius_tertiary_description = { |
radius_tertiary_description = { |
||
Строка 242: | Строка 422: | ||
field = 'max_level', |
field = 'max_level', |
||
type = 'Integer', |
type = 'Integer', |
||
+ | }, |
||
+ | html = { |
||
+ | field = 'html', |
||
+ | type = 'Text', |
||
+ | }, |
||
+ | skill_screenshot = { |
||
+ | name = i18n.args.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, |
||
}, |
}, |
||
}, |
}, |
||
Строка 248: | Строка 453: | ||
map.progression = { |
map.progression = { |
||
table = 'skill_levels', |
table = 'skill_levels', |
||
− | order = { |
+ | 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'}, |
fields = { |
fields = { |
||
level = { |
level = { |
||
Строка 261: | Строка 466: | ||
field = 'level_requirement', |
field = 'level_requirement', |
||
type = 'Integer', |
type = 'Integer', |
||
− | func = |
+ | func = h.cast.wrap(m_util.cast.number), |
− | header = m_util.html.abbr('[[Image:Level_up_icon_small.png|link=| |
+ | header = m_util.html.abbr('[[Image:Level_up_icon_small.png|link=|Lvl.]]', 'Required Level', 'nounderline'), |
}, |
}, |
||
dexterity_requirement = { |
dexterity_requirement = { |
||
Строка 268: | Строка 473: | ||
field = 'dexterity_requirement', |
field = 'dexterity_requirement', |
||
type = 'Integer', |
type = 'Integer', |
||
− | func = |
+ | func = h.cast.wrap(m_util.cast.number), |
− | header = m_util.html.abbr('[[Image:DexterityIcon_small.png|link=| |
+ | header = m_util.html.abbr('[[Image:DexterityIcon_small.png|link=|dexterity]]', 'Required Dexterity', 'nounderline'), |
}, |
}, |
||
strength_requirement = { |
strength_requirement = { |
||
Строка 275: | Строка 480: | ||
field = 'strength_requirement', |
field = 'strength_requirement', |
||
type = 'Integer', |
type = 'Integer', |
||
− | func = |
+ | func = h.cast.wrap(m_util.cast.number), |
− | header = m_util.html.abbr('[[Image:StrengthIcon_small.png|link=| |
+ | header = m_util.html.abbr('[[Image:StrengthIcon_small.png|link=|strength]]', 'Required Strength', 'nounderline'), |
}, |
}, |
||
intelligence_requirement = { |
intelligence_requirement = { |
||
Строка 282: | Строка 487: | ||
field = 'intelligence_requirement', |
field = 'intelligence_requirement', |
||
type = 'Integer', |
type = 'Integer', |
||
− | func = |
+ | func = h.cast.wrap(m_util.cast.number), |
− | header = m_util.html.abbr('[[Image:IntelligenceIcon_small.png|link=| |
+ | header = m_util.html.abbr('[[Image:IntelligenceIcon_small.png|link=|intelligence]]', 'Required Intelligence', 'nounderline'), |
}, |
}, |
||
mana_multiplier = { |
mana_multiplier = { |
||
Строка 289: | Строка 494: | ||
field = 'mana_multiplier', |
field = 'mana_multiplier', |
||
type = 'Float', |
type = 'Float', |
||
− | func = |
+ | func = h.cast.wrap(m_util.cast.number), |
header = 'Mana<br>Multiplier', |
header = 'Mana<br>Multiplier', |
||
fmt = '%s%%', |
fmt = '%s%%', |
||
Строка 297: | Строка 502: | ||
field = 'critical_strike_chance', |
field = 'critical_strike_chance', |
||
type = 'Float', |
type = 'Float', |
||
− | func = |
+ | func = h.cast.wrap(m_util.cast.number), |
header = 'Critical<br>Strike<br>Chance', |
header = 'Critical<br>Strike<br>Chance', |
||
fmt = '%s%%', |
fmt = '%s%%', |
||
Строка 305: | Строка 510: | ||
field = 'mana_cost', |
field = 'mana_cost', |
||
type = 'Integer', |
type = 'Integer', |
||
− | func = |
+ | func = h.cast.wrap(m_util.cast.number), |
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 |
||
Строка 313: | Строка 518: | ||
end |
end |
||
end, |
end, |
||
− | fmt = function (tpl_args, frame |
+ | fmt = function (tpl_args, frame) |
− | local str |
||
if tpl_args.has_percentage_mana_cost then |
if tpl_args.has_percentage_mana_cost then |
||
str = '%s%%' |
str = '%s%%' |
||
Строка 321: | Строка 525: | ||
end |
end |
||
− | return |
+ | return str |
end, |
end, |
||
}, |
}, |
||
Строка 328: | Строка 532: | ||
field = 'damage_effectiveness', |
field = 'damage_effectiveness', |
||
type = 'Float', |
type = 'Float', |
||
− | func = |
+ | func = h.cast.wrap(m_util.cast.number), |
header = 'Damage<br>Effectiveness', |
header = 'Damage<br>Effectiveness', |
||
fmt = '%s%%', |
fmt = '%s%%', |
||
Строка 336: | Строка 540: | ||
field = 'stored_uses', |
field = 'stored_uses', |
||
type = 'Integer', |
type = 'Integer', |
||
− | func = |
+ | func = h.cast.wrap(m_util.cast.number), |
header = 'Stored<br>Uses', |
header = 'Stored<br>Uses', |
||
}, |
}, |
||
Строка 343: | Строка 547: | ||
field = 'cooldown', |
field = 'cooldown', |
||
type = 'Float', |
type = 'Float', |
||
− | func = |
+ | func = h.cast.wrap(m_util.cast.number), |
header = 'Cooldown', |
header = 'Cooldown', |
||
fmt = '%ss', |
fmt = '%ss', |
||
Строка 351: | Строка 555: | ||
field = 'vaal_souls_requirement', |
field = 'vaal_souls_requirement', |
||
type = 'Integer', |
type = 'Integer', |
||
− | func = |
+ | func = h.cast.wrap(m_util.cast.number), |
header = 'Vaal<br>souls', |
header = 'Vaal<br>souls', |
||
}, |
}, |
||
Строка 358: | Строка 562: | ||
field = 'vaal_stored_uses', |
field = 'vaal_stored_uses', |
||
type = 'Integer', |
type = 'Integer', |
||
− | func = |
+ | func = h.cast.wrap(m_util.cast.number), |
header = 'Stored<br>Uses', |
header = 'Stored<br>Uses', |
||
}, |
}, |
||
Строка 365: | Строка 569: | ||
field = 'damage_multiplier', |
field = 'damage_multiplier', |
||
type = 'Float', |
type = 'Float', |
||
− | func = |
+ | func = h.cast.wrap(m_util.cast.number), |
header = m_util.html.abbr('Damage<br>Multiplier', 'Deals x% of Base Damage'), |
header = m_util.html.abbr('Damage<br>Multiplier', 'Deals x% of Base Damage'), |
||
fmt = '%s%%', |
fmt = '%s%%', |
||
Строка 374: | Строка 578: | ||
field = 'experience', |
field = 'experience', |
||
type = 'Integer', |
type = 'Integer', |
||
− | func = |
+ | func = h.cast.wrap(m_util.cast.number), |
hide = true, |
hide = true, |
||
}, |
}, |
||
Строка 418: | Строка 622: | ||
} |
} |
||
+ | data.infobox_table = { |
||
− | -- |
||
+ | { |
||
− | -- Helper functions |
||
+ | header = i18n.infobox.active_skill_name, |
||
− | -- |
||
+ | func = h.display.factory.value{key='active_skill_name'}, |
||
− | local h = {} |
||
+ | }, |
||
− | |||
+ | { |
||
− | function h.map_to_arg(tpl_args, frame, properties, prefix_in, map, level) |
||
+ | header = i18n.infobox.skill_id, |
||
− | if map.order then |
||
− | + | func = function (tpl_args, frame) |
|
+ | return string.format('[[%s|%s]]', mw.title.getCurrentTitle().fullText, tpl_args.skill_id) |
||
− | row = map.fields[key] |
||
− | + | end |
|
+ | }, |
||
− | local val = tpl_args[prefix_in .. row.name] |
||
+ | { |
||
− | if row.func ~= nil then |
||
− | + | 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 |
||
− | 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 |
||
end |
end |
||
− | end |
+ | end, |
− | + | }, |
|
+ | { |
||
− | end |
||
+ | header = i18n.infobox.cast_time, |
||
− | |||
+ | func = h.display.factory.value{key='cast_time'}, |
||
− | function h.stats(tpl_args, frame, prefix_in, level) |
||
+ | }, |
||
− | for _, stat_type in ipairs(map.stats) do |
||
+ | { |
||
− | local type_prefix = string.format('%s%s%s', prefix_in, stat_type.prefix_in, i18n.args.infix_stat) |
||
+ | header = i18n.infobox.item_class_restrictions, |
||
− | tpl_args.skill_levels[level][stat_type.out] = {} |
||
− | + | func = function (tpl_args, frame) |
|
+ | if tpl_args.item_class_restrictions == nil then |
||
− | local stat_id_key = string.format('%s%s_%s', type_prefix, i, i18n.args.id) |
||
+ | return |
||
− | local stat_val_key = string.format('%s%s_%s', type_prefix, i, i18n.args.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][stat_type.out][#tpl_args.skill_levels[level][stat_type.out]+1] = stat |
||
− | |||
− | m_util.cargo.store(frame, { |
||
− | _table = map.skill_stats_per_level.table, |
||
− | [map.skill_stats_per_level.fields.level.field] = level, |
||
− | [map.skill_stats_per_level.fields.id.field] = stat.id, |
||
− | [map.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 |
||
+ | local ul = mw.html.create('ul') |
||
− | end |
||
+ | for _, class in ipairs(i18n.infobox.item_class_restrictions) do |
||
− | end |
||
+ | ul:tag('li') |
||
− | end |
||
+ | :wikitext(string.format('[[%s]]', class)) |
||
− | |||
− | 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 = pdata.fmt(tpl_args, frame, value) |
||
end |
end |
||
− | + | return tostring(ul) |
|
− | + | end, |
|
+ | }, |
||
− | :tag('td') |
||
+ | { |
||
− | :wikitext(value) |
||
− | + | header = i18n.infobox.projectile_speed, |
|
+ | func = h.display.factory.value{key='projectile_speed'}, |
||
− | end |
||
+ | }, |
||
− | end |
||
+ | { |
||
+ | 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.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.damage_multiplier, |
||
+ | func = h.display.factory.range_value{key='damage_multiplier'}, |
||
+ | }, |
||
+ | { |
||
+ | 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 = {} |
||
− | -- |
||
− | -- For Шаблон:Skill |
||
− | -- |
||
p.table_skills = m_util.cargo.declare_factory{data=map.static} |
p.table_skills = m_util.cargo.declare_factory{data=map.static} |
||
p.table_skill_levels = m_util.cargo.declare_factory{data=map.progression} |
p.table_skill_levels = m_util.cargo.declare_factory{data=map.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=map.skill_stats_per_level} |
||
+ | -- |
||
+ | -- Template:Skill |
||
+ | -- |
||
function p.skill(frame, tpl_args) |
function p.skill(frame, tpl_args) |
||
if tpl_args == nil then |
if tpl_args == nil then |
||
Строка 533: | Строка 758: | ||
-- Handle level progression |
-- Handle level progression |
||
− | + | local i = 0 |
|
+ | repeat |
||
+ | i = i + 1 |
||
local prefix = i18n.args.prefix_level .. i |
local prefix = i18n.args.prefix_level .. i |
||
− | + | local level = m_util.cast.boolean(tpl_args[prefix]) |
|
+ | if level == true then |
||
-- Don't need this anymore |
-- Don't need this anymore |
||
tpl_args[prefix] = nil |
tpl_args[prefix] = nil |
||
Строка 554: | Строка 782: | ||
h.stats(tpl_args, frame, prefix, i) |
h.stats(tpl_args, frame, prefix, i) |
||
end |
end |
||
+ | until level ~= true |
||
− | end |
||
+ | -- 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 |
-- handle static progression |
||
properties = { |
properties = { |
||
Строка 562: | Строка 792: | ||
h.map_to_arg(tpl_args, frame, properties, i18n.args.prefix_static, map.progression, 0) |
h.map_to_arg(tpl_args, frame, properties, i18n.args.prefix_static, map.progression, 0) |
||
m_util.cargo.store(frame, properties) |
m_util.cargo.store(frame, properties) |
||
− | mw.logObject(properties) |
||
-- Handle static arguments |
-- Handle static arguments |
||
properties = { |
properties = { |
||
_table = map.static.table, |
_table = map.static.table, |
||
+ | |||
[map.static.fields.max_level.field] = tpl_args.max_level |
[map.static.fields.max_level.field] = tpl_args.max_level |
||
} |
} |
||
Строка 573: | Строка 803: | ||
h.stats(tpl_args, frame, i18n.args.prefix_static, 0) |
h.stats(tpl_args, frame, i18n.args.prefix_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[map.static.fields.html.field] = infobox |
||
m_util.cargo.store(frame, properties) |
m_util.cargo.store(frame, properties) |
||
+ | |||
+ | -- |
||
+ | -- |
||
+ | -- |
||
+ | local container = mw.html.create('span') |
||
+ | container:attr('class', 'skill-box-page-container') |
||
+ | container:wikitext(infobox) |
||
+ | if tpl_args.skill_screenshot then |
||
+ | container:wikitext(string.format('[[%s]]', tpl_args.skill_screenshot)) |
||
+ | end |
||
+ | |||
+ | return tostring(container) .. m_util.misc.add_category({'Skill data'}) |
||
end |
end |
||
Строка 622: | Строка 900: | ||
query.where = string.format('skill.skill_id="%s"', tpl_args.skill_id) |
query.where = string.format('skill.skill_id="%s"', tpl_args.skill_id) |
||
result = m_util.cargo.query({'skill'}, fields, query) |
result = m_util.cargo.query({'skill'}, fields, query) |
||
− | if #result |
+ | if #result == 0 then |
error('Couldn\'t find a page for the specified skill id') |
error('Couldn\'t find a page for the specified skill id') |
||
end |
end |
||
Строка 642: | Строка 920: | ||
end |
end |
||
− | skill_data["skill.has_reservation_mana_cost"] = |
+ | skill_data["skill.has_reservation_mana_cost"] = h.cast.wrap(m_util.cast.boolean)(skill_data["skill.has_reservation_mana_cost"]) |
query.where=string.format('skill_levels._pageName="%s"', skill_data['skill._pageName']) |
query.where=string.format('skill_levels._pageName="%s"', skill_data['skill._pageName']) |
||
Строка 661: | Строка 939: | ||
if #result == 0 then |
if #result == 0 then |
||
− | error(' |
+ | error('No gem level progression data found') |
end |
end |
||
Строка 677: | Строка 955: | ||
head |
head |
||
:tag('th') |
:tag('th') |
||
− | :wikitext(' |
+ | :wikitext('Level') |
:done() |
:done() |
||
Строка 707: | Строка 985: | ||
head |
head |
||
:tag('th') |
:tag('th') |
||
− | :wikitext(m_util.html.abbr(' |
+ | :wikitext(m_util.html.abbr('Exp.', 'Experience Needed to Level Up')) |
:done() |
:done() |
||
:tag('th') |
:tag('th') |
||
− | :wikitext(m_util.html.abbr(' |
+ | :wikitext(m_util.html.abbr('Total Exp.', 'Total experience needed')) |
:done() |
:done() |
||
end |
end |
||
Строка 778: | Строка 1056: | ||
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 |
||
− | empty = '[[ |
+ | empty = '[[Category:Pages with broken skill progression tables]]' |
break |
break |
||
end |
end |
||
Строка 791: | Строка 1069: | ||
end |
end |
||
end |
end |
||
+ | |||
+ | -- ---------------------------------------------------------------------------- |
||
+ | -- Debug |
||
+ | -- ---------------------------------------------------------------------------- |
||
p._debug = {} |
p._debug = {} |
Версия от 14:56, 25 мая 2018
Этот модуль зависит от следующих других модулей: |
Описание
Модуль для обработки умений с поддержкой Semantic MediaWiki.
Шаблоны Skill
Модуль:Item2
Все шаблоны, определенные в Модуль:Skill:
{{Skill}}
{{Skill progression}}
Модуль:Skill link
Все шаблоны, определенные в Модуль:Skill link:
Вышеприведенная документация извлекается из Модуль:Skill/doc.
Редакторы могут экспериментировать в sandbox этого модуля и в testcases страницах.
Подстраницы этого модуля.
Редакторы могут экспериментировать в sandbox этого модуля и в testcases страницах.
Подстраницы этого модуля.
-- ----------------------------------------------------------------------------
-- Includes
-- ----------------------------------------------------------------------------
local getArgs = require('Module:Arguments').getArgs
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 map = {}
-- 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',
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',
damage_effectiveness = 'Damage Effectiveness',
stored_uses = 'Stored Uses',
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_stored_uses = 'Vaal Stored Uses',
damage_multiplier = 'Damage Multiplier',
},
progression = {
skill_id = 'Skill Id',
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',
damage_effectiveness = 'Damage Effectiveness',
stored_uses = 'Stored Uses',
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_stored_uses = 'Vaal Stored Uses',
damage_multiplier = 'Damage Multiplier',
},
args = {
-- skills
skill_id = 'skill_id',
is_support_gem = 'is_support_gem',
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'
},
}
-- ----------------------------------------------------------------------------
-- 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)
for _, stat_type in ipairs(map.stats) do
local type_prefix = string.format('%s%s%s', prefix_in, stat_type.prefix_in, i18n.args.infix_stat)
tpl_args.skill_levels[level][stat_type.out] = {}
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, i18n.args.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][stat_type.out][#tpl_args.skill_levels[level][stat_type.out]+1] = stat
m_util.cargo.store(frame, {
_table = map.skill_stats_per_level.table,
[map.skill_stats_per_level.fields.level.field] = level,
[map.skill_stats_per_level.fields.id.field] = stat.id,
[map.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
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)
if args.fmt then
return string.format(args.fmt, tpl_args[args.key])
else
return tpl_args[args.key]
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 map.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
-- ----------------------------------------------------------------------------
map.stats = {
{
prefix_in = '',
out = 'stats',
quality = false,
},
{
prefix_in = i18n.args.prefix_quality_stat,
out = 'quality_stats',
quality = true,
},
}
map.static = {
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'},
fields = {
-- GrantedEffects.dat
skill_id = {
name = i18n.args.skill_id,
field = 'skill_id',
type = 'String',
func = nil,
},
-- Active Skills.dat
cast_time = {
name = i18n.args.cast_time,
field = 'cast_time',
type = 'Float',
func = h.cast.wrap(m_util.cast.number),
},
gem_description = {
name = i18n.args.gem_description,
field = 'description',
type = 'Text',
func = nil,
},
active_skill_name = {
name = i18n.args.active_skill_name,
field = 'active_skill_name',
type = 'String',
func = nil,
},
skill_icon = {
name = i18n.args.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_restriction = {
name = i18n.args.item_class_restriction,
field = 'item_class_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
local result = m_util.table.find_in_nested_array{value=v,key='full', tbl=m_game.constants.item.class}
if result == nil then
error(string.format('Invalid item class: %s', v))
end
end
return value
end,
},
-- Projectiles.dat - manually mapped to the skills
projectile_speed = {
name = i18n.args.projectile_speed,
field = 'projectile_speed',
type = 'Integer',
func = h.cast.wrap(m_util.cast.number),
},
-- Misc data derieved from stats
stat_text = {
name = i18n.args.stat_text,
field = 'stat_text',
type = 'Text',
func = nil,
},
quality_stat_text = {
name = i18n.args.quality_stat_text,
field = 'quality_stat_text',
type = 'Text',
func = nil,
},
-- Misc data currently not from game data
has_percentage_mana_cost = {
name = i18n.args.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 = i18n.args.has_reservation_mana_cost,
field = 'has_reservation_mana_cost',
type = 'Boolean',
func = h.cast.wrap(m_util.cast.boolean),
default = false,
},
radius = {
name = i18n.args.radius,
field = 'radius',
type = 'Integer',
func = h.cast.wrap(m_util.cast.number),
},
radius_description = {
name = i18n.args.radius_description,
field = 'radius_description',
type = 'Text',
func = nil,
},
radius_secondary = {
name = i18n.args.radius_secondary,
field = 'radius_secondary',
type = 'Integer',
func = h.cast.wrap(m_util.cast.number),
},
radius_secondary_description = {
name = i18n.args.radius_secondary_description,
field = 'radius_secondary_description',
type = 'Text',
func = nil,
},
-- not sure if any skill actually has 3 radius componets
radius_tertiary = {
name = i18n.args.radius_tertiary,
field = 'radius_tertiary',
type = 'Integer',
func = h.cast.wrap(m_util.cast.number),
},
radius_tertiary_description = {
name = i18n.args.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 = i18n.args.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,
},
},
}
map.progression = {
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'},
fields = {
level = {
name = nil,
field = 'level',
type = 'Integer',
func = nil,
header = nil,
},
level_requirement = {
name = i18n.args.level_requirement,
field = 'level_requirement',
type = 'Integer',
func = h.cast.wrap(m_util.cast.number),
header = m_util.html.abbr('[[Image:Level_up_icon_small.png|link=|Lvl.]]', 'Required Level', 'nounderline'),
},
dexterity_requirement = {
name = i18n.args.dexterity_requirement,
field = 'dexterity_requirement',
type = 'Integer',
func = h.cast.wrap(m_util.cast.number),
header = m_util.html.abbr('[[Image:DexterityIcon_small.png|link=|dexterity]]', 'Required Dexterity', 'nounderline'),
},
strength_requirement = {
name = i18n.args.strength_requirement,
field = 'strength_requirement',
type = 'Integer',
func = h.cast.wrap(m_util.cast.number),
header = m_util.html.abbr('[[Image:StrengthIcon_small.png|link=|strength]]', 'Required Strength', 'nounderline'),
},
intelligence_requirement = {
name = i18n.args.intelligence_requirement,
field = 'intelligence_requirement',
type = 'Integer',
func = h.cast.wrap(m_util.cast.number),
header = m_util.html.abbr('[[Image:IntelligenceIcon_small.png|link=|intelligence]]', 'Required Intelligence', 'nounderline'),
},
mana_multiplier = {
name = i18n.args.mana_multiplier,
field = 'mana_multiplier',
type = 'Float',
func = h.cast.wrap(m_util.cast.number),
header = 'Mana<br>Multiplier',
fmt = '%s%%',
},
critical_strike_chance = {
name = i18n.args.critical_strike_chance,
field = 'critical_strike_chance',
type = 'Float',
func = h.cast.wrap(m_util.cast.number),
header = 'Critical<br>Strike<br>Chance',
fmt = '%s%%',
},
mana_cost = {
name = i18n.args.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 'Mana<br>Reserved'
else
return 'Mana<br>Cost'
end
end,
fmt = function (tpl_args, frame)
if tpl_args.has_percentage_mana_cost then
str = '%s%%'
else
str = '%s'
end
return str
end,
},
damage_effectiveness = {
name = i18n.args.damage_effectiveness,
field = 'damage_effectiveness',
type = 'Float',
func = h.cast.wrap(m_util.cast.number),
header = 'Damage<br>Effectiveness',
fmt = '%s%%',
},
stored_uses = {
name = i18n.args.stored_uses,
field = 'stored_uses',
type = 'Integer',
func = h.cast.wrap(m_util.cast.number),
header = 'Stored<br>Uses',
},
cooldown = {
name = i18n.args.cooldown,
field = 'cooldown',
type = 'Float',
func = h.cast.wrap(m_util.cast.number),
header = 'Cooldown',
fmt = '%ss',
},
vaal_souls_requirement = {
name = i18n.args.vaal_souls_requirement,
field = 'vaal_souls_requirement',
type = 'Integer',
func = h.cast.wrap(m_util.cast.number),
header = 'Vaal<br>souls',
},
vaal_stored_uses = {
name = i18n.args.vaal_stored_uses,
field = 'vaal_stored_uses',
type = 'Integer',
func = h.cast.wrap(m_util.cast.number),
header = 'Stored<br>Uses',
},
damage_multiplier = {
name = i18n.args.damage_multiplier,
field = 'damage_multiplier',
type = 'Float',
func = h.cast.wrap(m_util.cast.number),
header = m_util.html.abbr('Damage<br>Multiplier', 'Deals x% of Base Damage'),
fmt = '%s%%',
},
-- from gem experience, optional
experience = {
name = i18n.args.experience,
field = 'experience',
type = 'Integer',
func = h.cast.wrap(m_util.cast.number),
hide = true,
},
stat_text = {
name = i18n.args.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',
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', 'damage_multiplier'}
map.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',
},
is_quality_stat = {
field = 'is_quality_stat',
type = 'Boolean',
},
},
}
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_restrictions == nil then
return
end
local ul = mw.html.create('ul')
for _, class in ipairs(i18n.infobox.item_class_restrictions) do
ul:tag('li')
:wikitext(string.format('[[%s]]', class))
end
return tostring(ul)
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.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.damage_multiplier,
func = h.display.factory.range_value{key='damage_multiplier'},
},
{
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=map.static}
p.table_skill_levels = m_util.cargo.declare_factory{data=map.progression}
p.table_skill_stats_per_level = m_util.cargo.declare_factory{data=map.skill_stats_per_level}
--
-- Template:Skill
--
function p.skill(frame, tpl_args)
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] = {},
}
-- Handle level progression
local i = 0
repeat
i = i + 1
local prefix = i18n.args.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 .. i18n.args.experience] ~= nil then
tpl_args.max_level = i
end
properties = {
_table = map.progression.table,
[map.progression.fields.level.field] = i
}
h.map_to_arg(tpl_args, frame, properties, prefix, map.progression, i)
m_util.cargo.store(frame, properties)
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 = map.progression.table,
[map.progression.fields.level.field] = 0
}
h.map_to_arg(tpl_args, frame, properties, i18n.args.prefix_static, map.progression, 0)
m_util.cargo.store(frame, properties)
-- Handle static arguments
properties = {
_table = map.static.table,
[map.static.fields.max_level.field] = tpl_args.max_level
}
h.map_to_arg(tpl_args, frame, properties, '', map.static)
h.stats(tpl_args, frame, i18n.args.prefix_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[map.static.fields.html.field] = infobox
m_util.cargo.store(frame, properties)
--
--
--
local container = mw.html.create('span')
container:attr('class', 'skill-box-page-container')
container:wikitext(infobox)
if tpl_args.skill_screenshot then
container:wikitext(string.format('[[%s]]', tpl_args.skill_screenshot))
end
return tostring(container) .. m_util.misc.add_category({'Skill data'})
end
function p.progression(frame)
local tpl_args = getArgs(frame, {
parentFirst = true
})
frame = m_util.misc.get_frame(frame)
--
tpl_args.stat_format = {}
local prefix
for i=1, 9 do
prefix = 'c' .. i .. '_'
local statfmt = {
header = tpl_args[prefix .. 'header'],
abbr = tpl_args[prefix .. 'abbr'],
pattern_extract = tpl_args[prefix .. 'pattern_extract'],
pattern_value = tpl_args[prefix .. 'pattern_value'],
counter = 0,
}
if m_util.table.has_all_value(statfmt, {'header', 'abbr', 'pattern_extract', 'pattern_value'}) then
break
end
if m_util.table.has_one_value(statfmt, {'header', 'abbr', 'pattern_extract', 'pattern_value'}) then
error(string.format('All formatting keys must be specified for index "%s"', 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 result
local query = {
groupBy = 'skill._pageID',
}
local skill_data
local fields = {
'skill._pageName',
'skill.has_reservation_mana_cost',
}
if tpl_args.skill_id then
query.where = string.format('skill.skill_id="%s"', tpl_args.skill_id)
result = m_util.cargo.query({'skill'}, fields, query)
if #result == 0 then
error('Couldn\'t find a page for the specified skill id')
end
skill_data = result[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)
result = m_util.cargo.query({'skill'}, fields, query)
if #result == 0 then
error('Couldn\'t find the queried data on the skill page')
end
skill_data = result[1]
end
skill_data["skill.has_reservation_mana_cost"] = h.cast.wrap(m_util.cast.boolean)(skill_data["skill.has_reservation_mana_cost"])
query.where=string.format('skill_levels._pageName="%s"', skill_data['skill._pageName'])
fields = {}
for _, pdata in pairs(map.progression.fields) do
fields[#fields+1] = string.format('skill_levels.%s', pdata.field)
end
result = m_util.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 #result == 0 then
error('No gem level progression data found')
end
headers = {}
for i, row in ipairs(result) do
for k, v in pairs(row) do
headers[k] = true
end
end
local tbl = mw.html.create('table')
tbl:attr('class', 'wikitable 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 = map.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('Exp.', 'Experience Needed to Level Up'))
:done()
:tag('th')
:wikitext(m_util.html.abbr('Total Exp.', 'Total experience needed'))
:done()
end
local tblrow
local lastexp = 0
local experience
for i, row in ipairs(result) 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 = map.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 (i.e. due to game updates)
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 empty = ''
for _, statfmt in ipairs(tpl_args.stat_format) do
if statfmt.counter == 0 then
empty = '[[Category:Pages with broken skill progression tables]]'
break
end
end
return empty .. tostring(tbl)
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