Module:Test
From Elwiki
Documentation for this module may be created at Module:Test/doc
-- pystart
require('Module:CommonFunctions')
local i18n = require('Module:I18n')
local getArgs = require('Module:Arguments').getArgs
local inspect = require('Module:Inspect').inspect
local getTranslations = i18n.getTranslations
local p = {}
-- ========================================
-- CONSTANTS
-- ========================================
local CONSTANTS = {
MODES = { 'PvE', 'PvP' },
DAMAGE_TYPES = { 'min', 'max' },
TO_SPLIT = { 'append', 'awk_alias' },
DEFAULT_TRAITS = {
-- An empty trait so we keep the original values there.
{
key = '',
name = 'Normal', -- Will be translated later
value = 1
},
{
key = 'enhanced',
name = 'Enhanced (Trait)', -- Will be translated later
value_func = function(args) return args.enhanced ~= nil and 0.8 end
},
{
key = 'empowered',
name = 'Empowered', -- Will be translated later
value_func = function(args) return args.empowered == 'true' and 1.2 or tonumber(args.empowered) or false end
},
{
key = 'useful',
name = 'Useful', -- Will be translated later
value_func = function(args)
return (args.hits_useful or args.avg_hits_useful) and
(args.useful_penalty or args.useful or 0.8) or false
end
},
{
key = 'heavy',
name = 'Heavy', -- Will be translated later
value_func = function(args) return args.heavy ~= nil and 1.44 end
}
},
BASE_DAMAGE_CONFIG = {
total_damage = {
damage_numbers = { 'dmg' },
hit_counts = { 'hits' },
provided = { 'dmg' }
},
total_damage_awk = {
damage_numbers = { 'awk_dmg', 'dmg' },
hit_counts = { 'awk_hits', 'hits' },
provided = { 'awk_dmg', 'awk_hits' }
},
avg_damage = {
damage_numbers = { 'dmg' },
hit_counts = { 'avg_hits', 'hits' },
provided = { 'avg_hits' }
},
avg_damage_awk = {
damage_numbers = { 'awk_dmg', 'dmg' },
hit_counts = { 'avg_awk_hits', 'awk_hits', 'avg_hits', 'hits' },
provided = { 'avg_awk_hits', 'awk_dmg_and_avg_hits' } -- Special marker
},
-- Store the logic for Useful traits
total_damage_useful = {
damage_numbers = { 'dmg' },
hit_counts = { 'hits_useful', 'hits' },
provided = { 'hits_useful' }
},
total_damage_awk_useful = {
damage_numbers = { 'awk_dmg', 'dmg' },
hit_counts = { 'awk_hits_useful', 'awk_hits', 'hits_useful', 'hits' },
provided = { 'awk_hits_useful' }
},
avg_damage_useful = {
damage_numbers = { 'dmg' },
hit_counts = { 'avg_hits_useful', 'hits_useful', 'avg_hits', 'hits' },
provided = { 'avg_hits_useful' }
},
avg_damage_awk_useful = {
damage_numbers = { 'awk_dmg', 'dmg' },
hit_counts = { 'avg_awk_hits_useful', 'avg_awk_hits', 'hits_useful', 'hits' },
provided = { 'avg_awk_hits_useful' }
},
}
}
-- ========================================
-- UTILITY FUNCTIONS
-- ========================================
local Utils = {}
function Utils.forEach(func)
for _, mode in ipairs(CONSTANTS.MODES) do
func(mode)
end
end
function Utils.forEachDamageType(func)
for _, damage_type in ipairs(CONSTANTS.DAMAGE_TYPES) do
func(damage_type)
end
end
function Utils.createDamageDataTable()
local newTable = {}
for _, mode in ipairs(CONSTANTS.MODES) do
newTable[mode] = {}
end
return newTable
end
function Utils.isTableNotEmpty(tbl)
return next(tbl) ~= nil
end
-- ========================================
-- INPUT PROCESSING
-- ========================================
local InputProcessor = {}
function InputProcessor.parseOptions(args)
return {
do_table = args[1] == 'true',
character = args[2] or args.char or 'Elsword',
lang_suffix = args.lang and ('/' .. args.lang) or '',
lang_append = args.lang ~= nil and args.lang ~= '',
format = args.format ~= 'false',
no_max = args.no_max == 'true',
is_append = args.append ~= nil,
append_index = args.append and tonumber(split(args.append)[1]),
append_name = args.append and split(args.append)[2],
combine_suffix = args.combine_suffix and (' ' .. args.combine_suffix) or '',
combine = (function()
local output = {}
if not args.combine then
return nil
end
for _, passive_key in ipairs(split(args.combine)) do
table.insert(output, tonumber(passive_key))
end
if #output == 0 then
return nil
end
return output
end)(),
perm_buff = {
PvE = args.perm_buff or 1,
PvP = args.pvp_perm_buff or args.perm_buff or 1
},
bug = args.bug == 'true',
dump = args.dump == 'true',
dump_table_data = args.dump_table_data == 'true',
dump_parsed = args.dump_parsed == 'true',
prefix = args.prefix,
use_avg = args.use_avg == 'true',
dmp = args.dmp == 'true' and 3 or args.dmp
}
end
function InputProcessor.parsePassives(args, frame, options)
local passives = {}
for k, v in pairs(args) do
if string.find(k, 'passive') then
local passive_name = v
local passive_title = v .. options.lang_suffix
local is_custom = string.find(k, '_define') ~= nil
local passive_index = string.match(k, "%d")
local passive_values = split(is_custom and v or
frame:preprocess('{{:' .. passive_name .. '}}{{#arrayprint:' .. passive_name .. '}}'))
local display_title
if is_custom then
passive_name = passive_values[#passive_values]
passive_values[#passive_values] = nil
elseif options.lang_append then
display_title = i18n.getTranslatedTitle(passive_title)
end
passives[tonumber(passive_index)] = {
name = passive_name,
value = passive_values[1],
value_pvp = passive_values[2],
alias = args['alias' .. passive_index] or (passive_index == options.append_index and options.append_name) or
display_title,
suffix = args['suffix' .. passive_index] and (' ' .. args['suffix' .. passive_index]) or '',
prefix = args['prefix' .. passive_index] and (args['prefix' .. passive_index] .. ' ') or '',
exist = frame:preprocess('{{#ifexist:' .. passive_name .. '|true|false}}') == 'true'
}
end
end
return passives
end
function InputProcessor.processNumericArgs(args, frame)
local processed_args = table.deep_copy(args)
for k, v in pairs(args) do
if string.match(v, '^[()+%-*/%d%s,.i]+$') then
local split_values = split(v)
for k2, v2 in pairs(split_values) do
if not string.find(v, '[a-zA-Z]+') then
split_values[k2] = frame:preprocess('{{#expr:' .. v2 .. '}}')
end
end
processed_args[k] = split_values
elseif inArrayHasValue(k, CONSTANTS.TO_SPLIT) then
processed_args[k] = split(v)
end
end
return processed_args
end
function InputProcessor.setDefaultHitCounts(args)
-- Set basic hit count to 1 for all damage.
for k, v in ipairs(args.dmg) do
if not args.hits then
args.hits = {}
end
if not args.hits[k] then
args.hits[k] = 1
end
end
-- Set basic hit count to 1 for all cancel damage.
if args.cancel_dmg then
for k, v in ipairs(args.cancel_dmg) do
if not args.cancel_hits then
args.cancel_hits = {}
end
if not args.cancel_hits[k] then
args.cancel_hits[k] = 1
end
end
end
end
-- ========================================
-- CONFIGURATION BUILDING
-- ========================================
local ConfigBuilder = {}
function ConfigBuilder.buildDamageConfig(args)
local function handleCancel()
local processed_keys = {}
for config_key, config_value in pairs(CONSTANTS.BASE_DAMAGE_CONFIG) do
if not config_key:match('cancel_') then
local new_config_value = {}
for arg_table_key, arg_table in pairs(config_value) do
local new_arg_table = {}
for _, arg in ipairs(arg_table) do
table.insert(new_arg_table, 'cancel_' .. arg)
end
new_config_value[arg_table_key] = new_arg_table
end
local new_key = 'cancel_' .. config_key
processed_keys[new_key] = new_config_value
end
end
return processed_keys
end
if args.cancel_dmg then
local cancel_configs = handleCancel()
return table.fuse(CONSTANTS.BASE_DAMAGE_CONFIG, cancel_configs)
else
return CONSTANTS.BASE_DAMAGE_CONFIG
end
end
function ConfigBuilder.buildTraits(args, translate)
local traits = {}
for _, trait_template in ipairs(CONSTANTS.DEFAULT_TRAITS) do
local trait = {
key = trait_template.key,
name = translate(trait_template.name),
value = trait_template.value or (trait_template.value_func and trait_template.value_func(args))
}
table.insert(traits, trait)
end
return traits
end
-- ========================================
-- INHERITANCE SYSTEM
-- ========================================
local InheritanceProcessor = {}
function InheritanceProcessor.applyInheritance(mainArgValues, inheritArg, mainArgValue, inheritValue)
if mainArgValue == '' then
return inheritValue
elseif mainArgValue and string.find(mainArgValue, 'i') and inheritValue then
return mainArgValue:gsub('i', inheritValue)
end
return mainArgValue
end
function InheritanceProcessor.applyInheritanceForKey(args, prefix, argTable, damageTypeIndex, damageType, eval)
local mainKey = argTable[1] .. damageType
local mainKeyPrefixed = prefix .. mainKey
local mainArgValues = args[mainKeyPrefixed]
if mainArgValues then
local i = 1
local cancelDmgLen = args.cancel_dmg and #args.cancel_dmg or 0
while i <= (#args.dmg + cancelDmgLen) do
local mainArgValue = mainArgValues[i]
for ix, inheritKey in ipairs(argTable) do
local inheritArg = args[prefix .. inheritKey .. damageType] or args[inheritKey .. damageType]
-- Basic damage/hits inheritance request detected. Ignore min/max.
if damageType and mainKey:gsub(damageType, "") == argTable[#argTable] then
inheritArg = args[prefix .. inheritKey] or args[inheritKey]
end
if inheritArg and inheritArg[i] and
(damageTypeIndex == 1 and ix ~= 1 or damageTypeIndex ~= 1) and tonumber(inheritArg[i])
then
mainArgValues[i] = InheritanceProcessor.applyInheritance(mainArgValues, inheritArg, mainArgValue,
inheritArg[i])
break
end
end
i = i + 1
end
end
end
function InheritanceProcessor.inherit(args, damageConfig, eval)
local function inheritForMode(mode)
local prefix = mode == 'PvE' and '' or string.lower(mode .. '_')
for configKey, configValue in pairs(damageConfig) do
for argTableKey, argTable in pairs(configValue) do
if argTableKey ~= 'provided' and Utils.isTableNotEmpty(argTable) then
for damageTypeIndex, damageType in ipairs({ '', '_min', '_max' }) do
InheritanceProcessor.applyInheritanceForKey(args, prefix, argTable, damageTypeIndex, damageType,
eval)
end
end
end
end
end
Utils.forEach(inheritForMode)
end
-- ========================================
-- DAMAGE CALCULATION ENGINE
-- ========================================
local DamageEngine = {}
function DamageEngine.parseConfig(args, damageConfig, options)
local damageParsed = Utils.createDamageDataTable()
local function parseConfigForMode(mode)
local prefix = mode == 'PvE' and '' or string.lower(mode .. '_')
for config_key, config_value in pairs(damageConfig) do
for k, v in pairs(config_value) do
local output_value = {}
-- When both min and max are found, we need to break from the loop.
local isValueFound = { min = false, max = false }
for _, v2 in ipairs(v) do -- This array holds the argument names with fallbacks
Utils.forEachDamageType(function(damage_type)
-- If there already is a value for this damage type (min or max), do not continue.
if isValueFound[damage_type] == true then
return
end
local arg_from_template =
args[prefix .. v2 .. '_' .. damage_type]
or args[v2 .. '_' .. damage_type]
or args[prefix .. v2]
or args[v2]
if arg_from_template ~= nil then
if k == 'provided' then
output_value = true
-- Special handling for awk_dmg_and_avg_hits marker
if v2 == 'awk_dmg_and_avg_hits' then
output_value = args.awk_dmg and args.avg_hits
end
-- Do not generate total_damage values at all if the skill can't reach them.
if string.find(config_key, 'total_') and options.no_max then
output_value = false
end
else
if type(output_value) ~= "table" then
output_value = {}
end
output_value[damage_type] = arg_from_template
end
-- Mark the value as found.
isValueFound[damage_type] = true
else
if k == 'provided' then
output_value = false
else
output_value[damage_type] = {}
end
end
end)
-- Both values found, we can now break the loop.
if isValueFound.min and isValueFound.max then
break
end
end
if damageParsed[mode][config_key] == nil then
damageParsed[mode][config_key] = {}
end
damageParsed[mode][config_key][k] = output_value
end
end
end
Utils.forEach(parseConfigForMode)
return damageParsed
end
function DamageEngine.handleEachDamage(damageParsed, args)
local withEach = table.deep_copy(damageParsed)
for mode, mode_content in pairs(damageParsed) do
for damage_key, damage_value in pairs(mode_content) do
if string.find(damage_key, 'total_') then
local new_value = table.deep_copy(damage_value)
Utils.forEachDamageType(function(damage_type)
for k, hit_count in ipairs(new_value.hit_counts[damage_type]) do
hit_count = hit_count == '' and 1 or hit_count
new_value.hit_counts[damage_type][k] = hit_count *
((string.find(damage_key, 'awk') and args.awk_count) and args.awk_count[1] or args.count[1])
end
end)
withEach[mode][damage_key:gsub("total_", "each_")] = damage_value
withEach[mode][damage_key] = new_value
end
end
end
return withEach
end
function DamageEngine.calculateBasicDamage(damageParsed)
local basicDamage = Utils.createDamageDataTable()
for mode, mode_content in pairs(damageParsed) do
for damage_key, damage_value in pairs(mode_content) do
Utils.forEachDamageType(function(damage_type)
local i = 1
local output = 0
-- Check if to even generate the damage.
if damage_value.provided then
-- Loop through damage numbers and multiply them with hits.
for k, damage_number in ipairs(damage_value.damage_numbers[damage_type]) do
local hit_count = damage_value.hit_counts[damage_type][i]
hit_count = hit_count == '' and 1 or hit_count
output = output + (damage_number * hit_count)
i = i + 1
end
-- Write the result to a separate object.
if not basicDamage[mode][damage_key] then
basicDamage[mode][damage_key] = {}
end
basicDamage[mode][damage_key][damage_type] = output
end
end)
end
end
return basicDamage
end
function DamageEngine.addCancelDamage(basicDamage, args)
if not args.cancel_dmg then
return basicDamage
end
for mode, mode_content in pairs(basicDamage) do
for damage_key, damage_value in pairs(mode_content) do
local cancel_candidate = basicDamage[mode]['cancel_' .. damage_key]
Utils.forEachDamageType(function(damage_type)
if not string.find(damage_key, 'cancel_') and cancel_candidate then
basicDamage[mode][damage_key][damage_type] = damage_value[damage_type] +
cancel_candidate[damage_type]
end
end)
end
end
return basicDamage
end
-- ========================================
-- TRAIT PROCESSING
-- ========================================
local TraitProcessor = {}
function TraitProcessor.applyTraits(basicDamage, traits)
local withTraits = Utils.createDamageDataTable()
for mode, mode_content in pairs(basicDamage) do
for damage_key, damage_value in pairs(mode_content) do
for _, trait in pairs(traits) do
if (trait.value and trait.key ~= 'useful') or (string.find(damage_key, 'useful') and trait.key == 'useful') then
Utils.forEachDamageType(function(damage_type)
local new_key = damage_key ..
((trait.key == 'useful' or trait.key == '') and "" or ('_' .. trait.key))
if not withTraits[mode][new_key] then
withTraits[mode][new_key] = {}
end
withTraits[mode][new_key][damage_type] = damage_value[damage_type] * trait.value
end)
end
end
end
end
return withTraits
end
-- ========================================
-- PASSIVE PROCESSING
-- ========================================
local PassiveProcessor = {}
function PassiveProcessor.generatePassiveCombinations(passives)
local combinations = { {} }
for passive_key, passive in pairs(passives) do
local count = #combinations
for i = 1, count do
local new_combination = { unpack(combinations[i]) }
table.insert(new_combination, passive_key)
table.insert(combinations, new_combination)
end
end
return combinations
end
function PassiveProcessor.applyPassives(withTraits, passives)
local withPassives = Utils.createDamageDataTable()
for mode, mode_content in pairs(withTraits) do
for damage_key, damage_value in pairs(mode_content) do
Utils.forEachDamageType(function(damage_type)
local combinations = PassiveProcessor.generatePassiveCombinations(passives)
for _, combination in pairs(combinations) do
local passive_multiplier = 1
local name_suffix = ''
if #combination > 0 then
table.sort(combination)
for _, passive_key in pairs(combination) do
passive_multiplier = passive_multiplier *
tonumber(passives[passive_key][mode == 'PvE' and 'value' or 'value_pvp'])
name_suffix = name_suffix .. '_passive' .. passive_key
end
end
local new_damage_key = damage_key .. name_suffix
if not withPassives[mode][new_damage_key] then
withPassives[mode][new_damage_key] = {}
end
withPassives[mode][new_damage_key][damage_type] = damage_value[damage_type] * passive_multiplier
end
end)
end
end
return withPassives
end
-- ========================================
-- RANGE PROCESSING
-- ========================================
local RangeProcessor = {}
function RangeProcessor.buildRangeConfig(args)
return {
min_count = args.range_min_count and args.range_min_count[1],
max_count = args.range_max_count and args.range_max_count[1],
PvE = {
min = args.range_min and args.range_min[1],
max = args.range_max and args.range_max[1]
},
PvP = {
min = args.range_min and (args.range_min[2] or args.range_min[1]),
max = args.range_max and (args.range_max[2] or args.range_max[1])
}
}
end
function RangeProcessor.applyRanges(withPassives, range, options, formatDamage)
local withRange = Utils.createDamageDataTable()
for mode, mode_content in pairs(withPassives) do
for damage_key, damage_value in pairs(mode_content) do
withRange[mode][damage_key] = { min = 0, max = 0 }
Utils.forEachDamageType(function(damage_type)
local range_count = range[damage_type .. '_count'] or 1
-- If min count preset, use range_max for the multiplier.
local range_multiplier = range[mode][damage_type] or
(damage_type == 'min' and range.min_count and range[mode].max) or 1
local final_range_multiplier = (1 + ((range_multiplier - 1) * range_count))
local perm_buff = options.perm_buff[mode]
local final_damage_value = damage_value[damage_type] * final_range_multiplier * perm_buff
withRange[mode][damage_key][damage_type] = not options.format and final_damage_value or
formatDamage(final_damage_value)
end)
end
end
return withRange
end
-- ========================================
-- TABLE GENERATION
-- ========================================
local TableGenerator = {}
function TableGenerator.checkTraits(traits, settings)
local output
if not settings then
output = false
else
output = settings.output or {}
end
for trait_index, trait in ipairs(traits) do
if trait.value ~= false and trait_index ~= 1 then
if settings and type(settings.action) == 'function' then
settings.action(trait, output, settings)
else
return true
end
end
end
return output
end
function TableGenerator.checkPassives(passives, options, args, link, sortPassives, settings)
local output = settings.output or {}
local PASSIVES_WITH_COMBINED = table.deep_copy(passives)
-- Handle combined passives properly.
if options.combine then
table.insert(PASSIVES_WITH_COMBINED, {
is_combined = true
})
end
for passive_index, passive in ipairs(PASSIVES_WITH_COMBINED) do
-- Determine if passive should be skipped
local skip_passive = false
if passive.is_combined then
-- Skip combined passive if any of its constituent passives are being appended
if options.is_append and options.combine and inArrayHasValue(options.append_index, options.combine) then
skip_passive = true
else
skip_passive = false
end
else
-- Regular passive logic: skip if it's appended or part of combine (unless display_separated)
skip_passive = (options.is_append and options.append_index == passive_index) or
(inArrayHasValue(passive_index, options.combine or {}) and not inArrayHasValue(passive_index, args.display_separated or {}))
end
if not skip_passive then
if type(settings.action) == 'function' then
settings.action(passive, output, passive_index)
else
return true
end
end
end
return output
end
function TableGenerator.buildTableContent(options, passives, traits, args, translate, inArgs, link, sortPassives,
fillTemplate)
return {
{
type = 'extra',
text = { translate('Average') },
is_visible = options.no_max,
no_damage = true
},
{
type = 'passives',
text = TableGenerator.checkPassives(passives, options, args, link, sortPassives, {
output = { translate('Base') },
action = function(passive, output)
if passive.is_combined then
-- Handling combined passive header name.
local combo = {}
for _, passive_key in ipairs(options.combine) do
passive = passives[passive_key]
table.insert(combo,
link(passive.name, passive.alias, passive.prefix, passive.suffix, passive.exist))
end
table.insert(output, table.concat(combo, '/') .. options.combine_suffix)
else
table.insert(output,
link(passive.name, passive.alias, passive.prefix, passive.suffix, passive.exist))
end
end
}),
keywords = TableGenerator.checkPassives(passives, options, args, link, sortPassives, {
action = function(passive, output, passive_index)
if passive.is_combined then
-- Handling combined passive damage cells.
table.insert(output, sortPassives('passive' .. table.concat(options.combine, '_passive')))
else
table.insert(output, 'passive' .. passive_index)
end
end
}),
is_visible = not options.no_max or #passives > 0
},
{
type = 'passive_appended',
text = {
translate('Normal'),
options.is_append and (function()
-- Check if the appended passive is part of a combined passive
if options.combine and inArrayHasValue(options.append_index, options.combine) then
-- Handle combined passive in append
local combo = {}
for _, passive_key in ipairs(options.combine) do
local passive = passives[passive_key]
table.insert(combo,
link(passive.name, passive.alias, passive.prefix, passive.suffix, passive.exist))
end
return table.concat(combo, '/') .. options.combine_suffix
else
-- Handle single passive in append
return link(passives[options.append_index].name,
passives[options.append_index].alias or options.append_name or nil,
passives[options.append_index].prefix,
passives[options.append_index].suffix,
passives[options.append_index].exist
)
end
end)()
},
keywords = { options.is_append and (function()
-- Generate appropriate keyword for appended passive
if options.combine and inArrayHasValue(options.append_index, options.combine) then
return sortPassives('passive' .. table.concat(options.combine, '_passive'))
else
return 'passive' .. options.append_index
end
end)() or nil },
is_visible = options.is_append or false
},
{
type = 'awakening',
text = { translate('Regular'), (function()
if options.dmp then
return link('Dynamo Point System' .. options.lang_suffix, 'Dynamo Configuration', args.awk_prefix,
options.dmp ~= 'false' and
(fillTemplate('({1} DMP)', { options.dmp })) ..
(args.awk_suffix and (' ' .. args.awk_suffix) or ''))
elseif args.awk_alias then
return link(args.awk_alias[1], args.awk_alias[2], args.awk_prefix, args.awk_suffix)
end
return link('Awakening Mode' .. options.lang_suffix, translate('Awakening Mode'), args.awk_prefix,
args.awk_suffix)
end)()
},
keywords = { 'awk' },
keyword_next_to_main_key = true,
is_visible = inArgs('awk_dmg') or inArgs('awk_hits') or inArgs('avg_awk_hits') or false
},
{
type = 'traits',
text = TableGenerator.checkTraits(traits, {
output = { translate('Normal') },
action = function(trait, output)
table.insert(output, trait.name)
end
}),
keywords = TableGenerator.checkTraits(traits, {
action = function(trait, output)
table.insert(output, trait.key)
end
}),
is_visible = TableGenerator.checkTraits(traits)
},
{
type = 'cancel',
text = {
translate('Cancel'),
translate('Full'),
},
keywords = { 'cancel' },
keyword_first = true,
is_visible = inArgs('cancel_dmg')
},
{
type = 'hit_count',
text = {
(inArgs('count') and not options.use_avg) and
(fillTemplate(translate('Per {1}'), { args.count_name or translate('Group') })) or
translate('Average'),
translate('Max')
},
keywords = (function()
if inArgs('avg_hits') or inArgs('count') then
return { (inArgs('count') and not options.use_avg) and 'each' or 'avg', 'total' }
end
return { 'total' }
end)(),
is_visible = ((inArgs('avg_hits') or inArgs('count')) and not options.no_max) or false
}
}
end
function TableGenerator.returnDamageInOrder(tableContent, options, inArgs, sortPassives)
local main_key = 'damage'
local all_list = {}
for i = #tableContent, 1, -1 do
local current_row = tableContent[i]
local new_list = {}
if not current_row.no_damage then
if i == #tableContent then
for _, keyword in ipairs(current_row.keywords) do
if not options.no_max or (options.no_max and keyword ~= 'total') then
local new_key = keyword .. '_' .. main_key
table.insert(new_list, new_key)
end
end
elseif current_row.is_visible then
for _, keyword in ipairs(current_row.keywords) do
for _, prev_key in ipairs(all_list) do
local new_key = prev_key .. '_' .. keyword
if current_row.keyword_next_to_main_key then
new_key = prev_key:gsub(main_key, main_key .. '_' .. keyword)
elseif current_row.keyword_first then
new_key = keyword .. '_' .. prev_key
end
table.insert(new_list, new_key)
end
end
end
for _, new_key in ipairs(new_list) do
table.insert(all_list, sortPassives(new_key))
end
end
end
if inArgs('cancel_dmg') then
local new_list = {}
for i, damage_key in ipairs(all_list) do
local regex = "^(%w+_)"
local prefix = 'cancel_'
local match = string.match(damage_key, regex)
if (match == prefix) then
new_list[i] = damage_key:gsub(prefix, "")
else
new_list[i] = prefix .. damage_key
end
end
all_list = new_list
end
return all_list
end
function TableGenerator.generateTable(finalDamage, tableContent, options, frame, translate, args, inArgs, sortPassives)
local TABLE = mw.html.create('table'):attr({
cellpadding = 5,
border = 1,
style = 'border-collapse: collapse; text-align: center',
class = 'colortable-' .. options.character
})
function TABLE:new()
return self:tag('tr')
end
local function doInitialCell(new_row)
return new_row:tag('th'):wikitext(translate('Mode'))
end
local function doHeaders()
local current_multiplier = 0
local initial_header_cell
local iterations = 0
for row_index, row in ipairs(tableContent) do
if row.is_visible then
local new_row = TABLE:new()
local next_multiplier = 0
if iterations == 0 and not initial_header_cell then
initial_header_cell = doInitialCell(new_row)
end
local colspan_value = 1
for k, v in ipairs(tableContent) do
if k > row_index and v.is_visible then
colspan_value = colspan_value * #v.text
end
end
for i = 1, (current_multiplier == 0 and 1 or current_multiplier), 1 do
for _, text in ipairs(row.text) do
local new_cell = new_row:tag('th')
new_cell:attr('colspan', colspan_value):wikitext(text)
next_multiplier = next_multiplier + 1
end
end
current_multiplier = next_multiplier
iterations = iterations + 1
end
end
initial_header_cell:attr('rowspan', iterations)
end
local function doRangeText(damage_number)
if damage_number and damage_number.min == damage_number.max then
damage_number = damage_number.min
elseif damage_number then
damage_number = damage_number.min ..
'<span style="white-space: nowrap;"> ~</span> ' .. damage_number.max
end
return damage_number
end
local function doContentByMode(mode)
local mode_row = TABLE:new()
mode_row:tag('td'):wikitext(frame:expandTemplate { title = translate(mode) })
local damage_entries = TableGenerator.returnDamageInOrder(tableContent, options, inArgs, sortPassives)
local last_number
local last_unique_cell
for _, damage_key in ipairs(damage_entries) do
if args.dump_names ~= 'true' then
local damage_number = finalDamage[mode][damage_key]
damage_number = doRangeText(damage_number)
if last_number ~= damage_number then
local new_cell = mode_row:tag('td'):wikitext(damage_number
or frame:expandTemplate {
title = 'color',
args = { 'red', '#ERROR' }
})
last_unique_cell = new_cell
else
last_unique_cell:attr('colspan', (last_unique_cell:getAttr('colspan') or 1) + 1)
end
last_number = damage_number
else
mode_row:tag('td'):wikitext(damage_key)
end
end
end
doHeaders()
Utils.forEach(doContentByMode)
return TABLE
end
-- ========================================
-- OUTPUT PROCESSING
-- ========================================
local OutputProcessor = {}
function OutputProcessor.generateOutput(frame, args, finalDamage, damageParsed, tableContent, options, translate,
doVariables, inspect_dump)
local out = nil
-- Dump all values if wanted.
if options.dump_table_data then
return inspect_dump(frame, tableContent)
elseif options.dump then
return inspect_dump(frame, finalDamage)
elseif options.dump_parsed then
return inspect_dump(frame, damageParsed)
end
local bug = ''
if options.bug then
bug = frame:expandTemplate {
title = translate('SkillText'),
args = { 'FreeTraining' }
}
end
-- Transform into variables
local variables = doVariables(frame, finalDamage, options.prefix)
local table_output = ''
if options.do_table then
local TABLE = TableGenerator.generateTable(finalDamage, tableContent, options, frame, translate, args,
function(key) return args[key] ~= nil end, sortPassives)
table_output = tostring(TABLE)
end
if out ~= nil then
return inspect_dump(frame, out)
end
return variables .. bug .. table_output
end
-- ========================================
-- MAIN PROCESS
-- ========================================
function p.main(frame)
local args = getArgs(frame)
local tr = getTranslations(frame, 'Template:Damage', args.lang, true)
local out
function translate(key)
return i18n.translate(tr, key)
end
function inArgs(key)
if args[key] ~= nil then
return true
end
end
-- User requested options
local OPTIONS = InputProcessor.parseOptions(args)
-- Define a table with parsed damage information of all kind.
local BASIC_DAMAGE = Utils.createDamageDataTable()
-- Define a table with trait names and their values to apply.
local TRAITS = ConfigBuilder.buildTraits(args, translate)
function eval(s)
return frame:preprocess('{{#expr:' .. s .. '}}')
end
-- A table with user-requested passive skills (empty by default).
local PASSIVES = InputProcessor.parsePassives(args, frame, OPTIONS)
InputProcessor.setDefaultHitCounts(args)
-- Store a configuration that will tell the main function how to behave given different inputs.
-- It will always take the first value if available. If not, fall back to the other (recursively).
local DAMAGE_CONFIG = ConfigBuilder.buildDamageConfig(args)
InheritanceProcessor.inherit(args, DAMAGE_CONFIG, eval)
local DAMAGE_PARSED = DamageEngine.parseConfig(args, DAMAGE_CONFIG, OPTIONS)
if args.count then
DAMAGE_PARSED = DamageEngine.handleEachDamage(DAMAGE_PARSED, args)
end
local basicDamage = DamageEngine.calculateBasicDamage(DAMAGE_PARSED)
-- Adding missing cancel part damage to full, so that repetition wouldn't be a problem.
basicDamage = DamageEngine.addCancelDamage(basicDamage, args)
local WITH_TRAITS = TraitProcessor.applyTraits(basicDamage, TRAITS)
local WITH_PASSIVES = PassiveProcessor.applyPassives(WITH_TRAITS, PASSIVES)
local RANGE = RangeProcessor.buildRangeConfig(args)
local WITH_RANGE = RangeProcessor.applyRanges(WITH_PASSIVES, RANGE, OPTIONS, formatDamage)
local FINAL_DAMAGE = WITH_RANGE
-- Build table structure
local TABLE_CONTENT = TableGenerator.buildTableContent(OPTIONS, PASSIVES, TRAITS, args, translate, inArgs, link,
sortPassives, fillTemplate)
-- Generate and return output
return OutputProcessor.generateOutput(frame, args, FINAL_DAMAGE, DAMAGE_PARSED, TABLE_CONTENT, OPTIONS, translate,
doVariables, inspect_dump)
end
return p
-- pyend