ElEditors, Interface administrators, Administrators
70,975
edits
No edit summary Tag: Reverted |
Tag: Undo |
||
Line 2: | Line 2: | ||
require('Module:CommonFunctions'); | require('Module:CommonFunctions'); | ||
local getArgs = require('Module:Arguments').getArgs | local getArgs = require('Module:Arguments').getArgs | ||
local inspect = require('Module:Inspect').inspect | |||
local p = {} | local p = {} | ||
Line 7: | Line 8: | ||
function p.main(frame) | function p.main(frame) | ||
local args = getArgs(frame) | local args = getArgs(frame) | ||
local out | |||
function inArgs(key) | function inArgs(key) | ||
Line 14: | Line 16: | ||
end | end | ||
local modes = { 'PvE', 'PvP' } | |||
local | |||
-- | -- Define the schema for the table | ||
for | local tableSchema = {} | ||
for _, mode in ipairs(modes) do | |||
tableSchema[mode] = {} | |||
end | end | ||
function forEach(func) | |||
for _, mode in ipairs(modes) do | |||
func(mode) | |||
end | |||
end | |||
function forEachDamageType(func) | |||
for _, damage_type in ipairs({ 'min', 'max' }) do | |||
func(damage_type) | |||
end | end | ||
end | end | ||
-- | -- Function to create a new table with the desired schema | ||
function | function createDamageDataTable() | ||
local | local newTable = {} | ||
for key, value in pairs(tableSchema) do | |||
for | if type(value) == "table" then | ||
if | newTable[key] = {} | ||
end | end | ||
end | end | ||
return newTable | |||
end | end | ||
-- User requested options | |||
local OPTIONS = { | |||
do_table = args[1] == 'true', | |||
character = args[2] or args.char or 'Elsword', | |||
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 | |||
} | |||
-- | -- Define a table with parsed damage information of all kind. | ||
local BASIC_DAMAGE = createDamageDataTable() | |||
-- Define a table with trait names and their values to apply. | |||
local TRAITS = { | |||
-- An empty trait so we keep the original values there. | |||
{ | |||
key = '', | |||
name = 'Normal', | |||
value = 1 | |||
}, | |||
{ | |||
key = 'enhanced', | |||
name = 'Enhanced (Trait)', | |||
value = args.enhanced ~= nil and 0.8 | |||
}, | |||
{ | |||
key = 'empowered', | |||
name = 'Empowered', | |||
value = args.empowered == 'true' and 1.2 or tonumber(args.empowered) or false | |||
}, | |||
{ | |||
key = 'useful', | |||
name = 'Useful', | |||
value = (args.hits_useful or args.avg_hits_useful) and (args.useful_penalty or args.useful or 0.8) or false | |||
}, | |||
{ | |||
key = 'heavy', | |||
name = 'Heavy', | |||
value = args.heavy ~= nil and 1.44 | |||
} | |||
} | |||
function eval(s) | |||
return frame:preprocess('{{#expr:' .. s .. '}}') | |||
end | end | ||
-- | -- A table with user-requested passive skills (empty by default). | ||
local | local PASSIVES = {} | ||
-- A table with non-numeric arguments to split. | |||
local TO_SPLIT = { 'append', 'awk_alias' } | |||
if | for k, v in pairs(args) do | ||
if string.find(k, 'passive') then | |||
--[[ | |||
Fix up the passives and put them into a separate table. | |||
|passive1=... |passive2=... -> { passive1, passive2 } | |||
--]] | |||
local passive_name = v | |||
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 .. '}}')); | |||
if is_custom then | |||
passive_name = passive_values[#passive_values] | |||
passive_values[#passive_values] = nil | |||
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), | |||
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' | |||
} | |||
elseif not string.find(v, '[a-hj-zA-HJ-Z]+') then | |||
--[[ | |||
Change how args are received. | |||
dmg = 500, 700, 800 (string) -> dmg = { 500, 700, 800 } (table) | |||
--]] | |||
local split_values = split(v) | |||
-- Perform automatic math on each value. | |||
for k2, v2 in pairs(split_values) do | |||
if not string.find(v, '[a-zA-Z]+') then | |||
split_values[k2] = eval(v2) | |||
end | |||
end | |||
args[k] = split_values | |||
elseif inArrayHasValue(k, TO_SPLIT) then | |||
args[k] = split(v) | |||
end | end | ||
end | end | ||
-- 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 | |||
if | |||
end | end | ||
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 = {} | |||
if | |||
end | end | ||
if | if not args.cancel_hits[k] then | ||
args.cancel_hits[k] = 1 | |||
end | end | ||
end | |||
end | |||
-- 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 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', args.awk_dmg and 'avg_hits' or nil } | |||
}, | |||
-- 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' } | |||
}, | |||
} | |||
local DAMAGE_CONFIG = {} | |||
function handleCancel() | |||
local processed_keys = {} | |||
for config_key, config_value in pairs(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 | end | ||
new_config_value[arg_table_key] = new_arg_table | |||
end | end | ||
local new_key = 'cancel_' .. config_key | |||
DAMAGE_CONFIG[new_key] = new_config_value | |||
processed_keys[new_key] = true | |||
end | end | ||
end | end | ||
return processed_keys | |||
end | |||
if args.cancel_dmg then | |||
handleCancel() | |||
DAMAGE_CONFIG = table.fuse(BASE_DAMAGE_CONFIG, DAMAGE_CONFIG) | |||
else | |||
DAMAGE_CONFIG = BASE_DAMAGE_CONFIG | |||
end | |||
-- Helper function to check if a table is not empty | |||
local function isTableNotEmpty(tbl) | |||
return next(tbl) ~= nil | |||
end | |||
-- Function to apply inheritance for a specific damage type and argument | |||
local function applyInheritance(mainArgValues, inheritArg, mainArgValue, inheritValue) | |||
if mainArgValue == '' then | |||
return inheritValue | |||
elseif mainArgValue and string.find(mainArgValue, 'i') and inheritValue then | |||
return eval(mainArgValue:gsub('i', inheritValue)) | |||
end | end | ||
return mainArgValue | |||
end | |||
-- Function to apply inheritance for a specific argument key | |||
local function applyInheritanceForKey(args, prefix, argTable, damageTypeIndex, damageType) | |||
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] = applyInheritance(mainArgValues, inheritArg, mainArgValue, inheritArg[i]) | |||
break | |||
end | end | ||
end | end | ||
i = i + 1 | |||
end | end | ||
end | end | ||
end | |||
-- Inherits values from args if not provided, but usage suggests that they're meant to be generated. | |||
function inherit(mode) | |||
local prefix = mode == 'PvE' and '' or string.lower(mode .. '_') | |||
for configKey, configValue in pairs(DAMAGE_CONFIG) do | |||
for argTableKey, argTable in pairs(configValue) do | |||
if argTableKey ~= 'provided' and isTableNotEmpty(argTable) then | |||
for damageTypeIndex, damageType in ipairs({ '', '_min', '_max' }) do | |||
applyInheritanceForKey(args, prefix, argTable, damageTypeIndex, damageType) | |||
end | |||
end | end | ||
end | end | ||
end | end | ||
end | |||
forEach(inherit) | |||
local DAMAGE_PARSED = createDamageDataTable() | |||
function parseConfig(mode) | |||
for | local prefix = mode == 'PvE' and '' or string.lower(mode .. '_') | ||
for config_key, config_value in pairs(DAMAGE_CONFIG) 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 | |||
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 | |||
-- 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 DAMAGE_PARSED[mode][config_key] == nil then | |||
DAMAGE_PARSED[mode][config_key] = {} | |||
end | |||
DAMAGE_PARSED[mode][config_key][k] = output_value | |||
end | end | ||
end | end | ||
end | end | ||
forEach(parseConfig) | |||
function | -- Detected "count", for skills like Clementine, Enough Mineral, etc. | ||
function doEachDamage() | |||
local WITH_EACH = table.deep_copy(DAMAGE_PARSED) | |||
for mode, mode_content in pairs(DAMAGE_PARSED) 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) | |||
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) | |||
WITH_EACH[mode][damage_key:gsub("total_", "each_")] = damage_value | |||
WITH_EACH[mode][damage_key] = new_value | |||
end | end | ||
end | end | ||
end | end | ||
return WITH_EACH | |||
end | end | ||
if args.count then | |||
DAMAGE_PARSED = doEachDamage() | |||
end | |||
function doBasicDamage() | |||
for mode, mode_content in pairs(DAMAGE_PARSED) do | |||
for damage_key, damage_value in pairs(mode_content) do | |||
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 BASIC_DAMAGE[mode][damage_key] then | |||
BASIC_DAMAGE[mode][damage_key] = {} | |||
end | |||
BASIC_DAMAGE[mode][damage_key][damage_type] = output | |||
end | |||
end) | |||
end | end | ||
end | end | ||
end | end | ||
doBasicDamage() | |||
function | -- Adding missing cancel part damage to full, so that repetition wouldn't be a problem. | ||
function addCancelDamage() | |||
for mode, mode_content in pairs(BASIC_DAMAGE) do | |||
for damage_key, damage_value in pairs(mode_content) do | |||
local cancel_candidate = BASIC_DAMAGE[mode]['cancel_' .. damage_key] | |||
forEachDamageType(function(damage_type) | |||
if not string.find(damage_key, 'cancel_') and cancel_candidate then | |||
BASIC_DAMAGE[mode][damage_key][damage_type] = damage_value[damage_type] + | |||
for | cancel_candidate[damage_type] | ||
if | |||
end | end | ||
end | end) | ||
end | end | ||
end | end | ||
end | end | ||
if args.cancel_dmg then | |||
addCancelDamage() | |||
end | end | ||
local WITH_TRAITS = createDamageDataTable() | |||
local | function doTraits() | ||
-- Handle traits here | |||
for mode, mode_content in pairs(BASIC_DAMAGE) do | |||
for damage_key, damage_value in pairs(mode_content) do | |||
for _, trait in pairs(TRAITS) do | |||
--[[ | |||
Suffix all damage values with existing traits. | |||
Useful already has the prefix, so only multiply with its value. | |||
Also, we don't want other traits to multiply with Useful, | |||
so we skip those situations, as impossible in-game. | |||
--]] | |||
if (trait.value and trait.key ~= 'useful') or (string.find(damage_key, 'useful') and trait.key == 'useful') then | |||
forEachDamageType(function(damage_type) | |||
local new_key = damage_key .. | |||
((trait.key == 'useful' or trait.key == '') and "" or ('_' .. trait.key)); | |||
if not WITH_TRAITS[mode][new_key] then | |||
WITH_TRAITS[mode][new_key] = {} | |||
end | |||
WITH_TRAITS[mode][new_key][damage_type] = damage_value[damage_type] * trait.value | |||
end) | |||
end | |||
end | end | ||
end | end | ||
end | end | ||
end | end | ||
doTraits() | |||
local WITH_PASSIVES = createDamageDataTable() | |||
--[[ | |||
Generates passives with every possible combinations of all subsets. | |||
For example: 3 passives are given, so it will generate the following: | |||
(1), (2), (3), (1, 2), (1, 3), (1, 2, 3), (2, 3) | |||
]] | |||
function doPassives() | |||
for mode, mode_content in pairs(WITH_TRAITS) do | |||
for damage_key, damage_value in pairs(mode_content) do | |||
forEachDamageType(function(damage_type) | |||
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 | |||
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 WITH_PASSIVES[mode][new_damage_key] then | |||
WITH_PASSIVES[mode][new_damage_key] = {} | |||
end | |||
WITH_PASSIVES[mode][new_damage_key][damage_type] = damage_value[damage_type] * passive_multiplier | |||
end | |||
end) | |||
end | |||
end | end | ||
end | end | ||
doPassives() | |||
local | local RANGE = { | ||
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]) | |||
} | |||
} | } | ||
local | local WITH_RANGE = createDamageDataTable() | ||
for | function doDamageBuffRange() | ||
-- Handle damage range here | |||
for mode, mode_content in pairs(WITH_PASSIVES) do | |||
for damage_key, damage_value in pairs(mode_content) do | |||
WITH_RANGE[mode][damage_key] = { min = 0, max = 0 } | |||
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; | |||
WITH_RANGE[mode][damage_key][damage_type] = not OPTIONS.format and final_damage_value or | |||
formatDamage(final_damage_value) | |||
end) | |||
end | |||
end | end | ||
end | end | ||
doDamageBuffRange() | |||
local | local FINAL_DAMAGE = WITH_RANGE | ||
function | -- Helper function to iterate over traits. | ||
function checkTraits(settings) | |||
local output | |||
if not settings then | |||
output = false | |||
else | |||
output = settings.output or {} | |||
end | 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 | end | ||
end | end | ||
return output | |||
end | end | ||
-- | -- Helper function to iterate over passives. | ||
function checkPassives(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 | end | ||
for passive_index, passive in ipairs(PASSIVES_WITH_COMBINED) do | |||
if (not OPTIONS.is_append or (OPTIONS.is_append and OPTIONS.append_index ~= passive_index)) and (not inArrayHasValue(passive_index, OPTIONS.combine or {}) or inArrayHasValue(passive_index, args.display_separated or {})) then | |||
if | if type(settings.action) == 'function' then | ||
settings.action(passive, output, passive_index) | |||
else | else | ||
return | return true | ||
end | end | ||
end | end | ||
end | |||
return output | |||
end | |||
function | -- Generate the table | ||
local TABLE = mw.html.create('table'):attr({ | |||
cellpadding = 5, | |||
border = 1, | |||
style = 'border-collapse: collapse; text-align: center', | |||
class = 'colortable-' .. OPTIONS.character | |||
}) | |||
-- Our table structure | |||
local TABLE_CONTENT = { | |||
{ | |||
type = 'extra', | |||
text = { 'Average' }, | |||
is_visible = OPTIONS.no_max, | |||
no_damage = true | |||
}, | |||
{ | |||
type = 'passives', | |||
text = checkPassives({ | |||
output = { '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 | end | ||
}), | |||
keywords = checkPassives({ | |||
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 | end | ||
}), | |||
is_visible = not OPTIONS.no_max or #PASSIVES > 0 | |||
}, | |||
{ | |||
type = 'passive_appended', | |||
text = { | |||
'Normal', | |||
OPTIONS.is_append and | |||
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 | |||
) | |||
}, | |||
keywords = { OPTIONS.is_append and ('passive' .. OPTIONS.append_index) or nil }, | |||
is_visible = OPTIONS.is_append or false | |||
}, | |||
{ | |||
type = 'awakening', | |||
text = { 'Regular', (function() | |||
if OPTIONS.dmp then | |||
return link('Dynamo Point System', 'Dynamo Configuration', args.awk_prefix, | |||
OPTIONS.dmp ~= 'false' and ('(' .. OPTIONS.dmp .. ' 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 | end | ||
end | return link('Awakening Mode', nil, 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 = checkTraits({ | |||
output = { 'Normal' }, | |||
action = function(trait, output) | |||
table.insert(output, trait.name) | |||
end | end | ||
}), | |||
keywords = checkTraits({ | |||
action = function(trait, output) | |||
table.insert(output, trait.key) | |||
end | end | ||
if | }), | ||
is_visible = checkTraits() | |||
}, | |||
{ | |||
type = 'cancel', | |||
text = { | |||
'Cancel', 'Full' | |||
}, | |||
keywords = { 'cancel' }, | |||
keyword_first = true, | |||
is_visible = inArgs('cancel_dmg') | |||
}, | |||
{ | |||
type = 'hit_count', | |||
text = { | |||
(inArgs('count') and not OPTIONS.use_avg) and | |||
(table.concat({ 'Per', args.count_name or 'Group' }, ' ')) or 'Average', | |||
'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 | end | ||
end | return { 'total' } | ||
end)(), | |||
is_visible = ((inArgs('avg_hits') or inArgs('count')) and not OPTIONS.no_max) or false | |||
} | |||
} | |||
function TABLE:new() | |||
return self:tag('tr') | |||
end | |||
function returnDamageInOrder() | |||
local main_key = 'damage' | |||
local all_list = {} | |||
-- Initialize current list with main key | |||
local current_list = { main_key } | |||
for i = #TABLE_CONTENT, 1, -1 do | |||
local current_row = TABLE_CONTENT[i] | |||
local new_list = {} | |||
-- Check if it's the first iteration. If so, append phrases. | |||
if not current_row.no_damage then | |||
if i == #TABLE_CONTENT then | |||
for _, keyword in ipairs(current_row.keywords) do | |||
if | 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 | ||
end | end | ||
elseif current_row.is_visible then | |||
-- Append suffix for each keyword in current row | |||
for _, keyword in ipairs(current_row.keywords) do | |||
-- Iterate through previous keys | |||
for _, prev_key in ipairs(all_list) do | |||
for | local new_key = prev_key .. '_' .. keyword | ||
-- If needed, move the suffix to the rightmost of main_key. | |||
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 | end | ||
table.insert(new_list, new_key) | |||
end | end | ||
end | end | ||
end | end | ||
-- Append new_list to all_list | |||
for _, new_key in ipairs(new_list) do | |||
table.insert(all_list, sortPassives(new_key)) | |||
end | end | ||
end | end | ||
end | |||
-- Sort the list once more, in order to swap the order of cancel & full. | |||
if | if inArgs('cancel_dmg') then | ||
local new_list = {} | |||
local cancel_counter = 1 | |||
local full_counter = 2 | |||
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 | else | ||
new_list[i] = prefix .. damage_key | |||
end | end | ||
end | |||
all_list = new_list | |||
end | |||
return all_list | |||
end | |||
function doInitialCell(new_row) | |||
return new_row:tag('th'):wikitext('Mode') | |||
end | |||
function doHeaders() | |||
local current_multiplier = 0 -- Keeps track of the number of cells to spawn | |||
local initial_header_cell -- The leftmost cell that says "Mode" | |||
local iterations = 0 -- Keeps track of iterations that successfully rendered something. Required to tell the initial cell how many columns to span. | |||
for row_index, row in ipairs(TABLE_CONTENT) do | |||
if row.is_visible then | |||
local new_row = TABLE:new() | |||
local next_multiplier = 0 | |||
if | -- Only spawn the initial cell in the first generated row. | ||
if iterations == 0 and not initial_header_cell then | |||
initial_header_cell = doInitialCell(new_row) | |||
end | end | ||
--[[ | |||
We need to know how the colspan will look like. | |||
So the solution is to loop through the table again and check how many cells will be spawned. | |||
And also multiply everything, because it is exponential. | |||
]] | |||
local colspan_value = 1 | |||
for k, v in ipairs(TABLE_CONTENT) do | |||
if k > row_index and v.is_visible then | |||
colspan_value = colspan_value * #v.text | |||
end | |||
end | end | ||
-- Now we can spawn our header cells depending on what is known. | |||
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 | ||
end | end | ||
current_multiplier = next_multiplier | |||
iterations = iterations + 1 | |||
end | end | ||
end | |||
-- Apply rowspan of the same value as iteration count. | |||
initial_header_cell:attr('rowspan', iterations) | |||
end | end | ||
function | -- Helper function to display ranges. | ||
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 | end | ||
return | return damage_number | ||
end | end | ||
function | function doContentByMode(mode) | ||
local | local mode_row = TABLE:new() | ||
mode_row:tag('td'):wikitext(frame:expandTemplate { title = mode }) | |||
local damage_entries = returnDamageInOrder() | |||
local last_number | |||
local last_unique_cell | |||
local | for _, damage_key in ipairs(damage_entries) do | ||
if args.dump_names ~= 'true' then | |||
local damage_number = FINAL_DAMAGE[mode][damage_key] | |||
damage_number = doRangeText(damage_number) | |||
if last_number ~= damage_number then | |||
-- Display ranges. | |||
local new_cell = mode_row:tag('td'):wikitext(damage_number | |||
-- Error out if it doesn't exist | |||
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 | end | ||
last_number = damage_number | |||
else | else | ||
mode_row:tag('td'):wikitext(damage_key) | |||
end | end | ||
end | end | ||
end | |||
function doTable() | |||
doHeaders() | |||
forEach(doContentByMode) | |||
end | |||
if OPTIONS.do_table then | |||
doTable() | |||
end | end | ||
-- | -- Dump all values if wanted. | ||
if | if OPTIONS.dump_table_data then | ||
return inspect_dump(frame, TABLE_CONTENT) | |||
elseif OPTIONS.dump then | |||
return inspect_dump(frame, FINAL_DAMAGE) | |||
elseif OPTIONS.dump_parsed then | |||
return inspect_dump(frame, DAMAGE_PARSED) | |||
return | |||
end | end | ||
local bug = '' | local bug = '' | ||
if | if OPTIONS.bug then | ||
bug = frame:expandTemplate{ | bug = frame:expandTemplate { | ||
title = 'SkillText', | title = 'SkillText', | ||
args = {'FreeTraining'} | args = { 'FreeTraining' } | ||
} | } | ||
end | end | ||
return | -- Transform into variables | ||
local variables = doVariables(frame, FINAL_DAMAGE, OPTIONS.prefix) | |||
if out ~= nil then | |||
return inspect_dump(frame, out) | |||
end | |||
return variables .. bug .. (OPTIONS.do_table and tostring(TABLE) or '') | |||
end | end | ||
return p | return p | ||
-- pyend | -- pyend |