ElEditors, Interface administrators, Administrators
70,769
edits
No edit summary |
No edit summary Tag: Reverted |
||
Line 1: | Line 1: | ||
-- pystart | |||
require('Module:CommonFunctions'); | |||
local getArgs = require('Module:Arguments').getArgs | |||
local inspect = require('Module:Inspect').inspect | local inspect = require('Module:Inspect').inspect | ||
local p = {} | |||
-- in | -- Main process | ||
function | function p.main(frame) | ||
local args = getArgs(frame) | |||
return | local out | ||
function inArgs(key) | |||
if args[key] ~= nil then | |||
return true | |||
end | |||
end | |||
local modes = { 'PvE', 'PvP' } | |||
-- Define the schema for the table | |||
local tableSchema = {} | |||
for _, mode in ipairs(modes) do | |||
tableSchema[mode] = {} | |||
end | |||
function forEach(func) | |||
for _, mode in ipairs(modes) do | |||
func(mode) | |||
end | |||
end | |||
-- Function to create a new table with the desired schema | |||
function createDamageDataTable() | |||
local newTable = {} | |||
for key, value in pairs(tableSchema) do | |||
if type(value) == "table" then | |||
newTable[key] = {} | |||
end | |||
end | |||
return newTable | |||
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). | |||
for k, v in pairs( | local PASSIVES = {} | ||
if string. | -- A table with non-numeric arguments to split. | ||
local TO_SPLIT = { 'append', 'awk_alias' } | |||
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 | for k, v in ipairs(args.dmg) do | ||
if | if not args.hits then | ||
args.hits = {} | |||
end | |||
if not args.hits[k] then | |||
args.hits[k] = 1 | |||
end | end | ||
end | end | ||
-- Set basic hit count to 1 for all cancel damage. | |||
for k, v in | 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 | end | ||
function | -- Store a configuration that will tell the main function how to behave given different inputs. | ||
for | -- 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 | |||
new_config_value[arg_table_key] = new_arg_table | |||
end | |||
local new_key = 'cancel_' .. config_key | |||
DAMAGE_CONFIG[new_key] = new_config_value | |||
processed_keys[new_key] = true | |||
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 | end | ||
-- | -- Inherits values from args if not provided, but usage suggests that they're meant to be generated. | ||
function string. | function inherit(mode) | ||
local prefix = mode == 'PvE' and '' or string.lower(mode .. '_') | |||
for config_key, config_value in pairs(DAMAGE_CONFIG) do | |||
for arg_table_key, arg_table in pairs(config_value) do | |||
if arg_table_key ~= 'provided' and arg_table then | |||
-- We only do this for the first (main) key | |||
local main_key = arg_table[1] | |||
local main_key_prefixed = prefix .. main_key | |||
local main_arg_values = args[main_key_prefixed] | |||
-- Only if the main argument values exist. | |||
if main_arg_values then | |||
local i = 1 | |||
--[[ | |||
Loop over all damage and attempt to inherit in chain. | |||
Break the loop if a match was found. Note: For this to work, the value must be an empty string. | |||
Alternatively, it can contain an "i" to template the value to inherit. | |||
]] | |||
local cancel_dmg_len = args.cancel_dmg and #(args.cancel_dmg) or 0 | |||
while i <= (#(args.dmg) + cancel_dmg_len) do | |||
local main_arg_value = main_arg_values[i] | |||
for ix, inherit_key in ipairs(arg_table) do | |||
local inherit_arg = args[prefix .. inherit_key] or args[inherit_key] | |||
end | -- No inheritance from itself. | ||
if inherit_arg and inherit_arg[i] and inherit_arg[i] ~= '' and ix ~= 1 then | |||
-- Only inherit if empty | |||
if main_arg_value == '' then | |||
args[main_key_prefixed][i] = inherit_arg[i] | |||
break | |||
elseif main_arg_value and string.find(main_arg_value, 'i') and inherit_arg[i] then | |||
args[main_key_prefixed][i] = eval(main_arg_value:gsub('i', inherit_arg[i])) | |||
break | |||
end | |||
end | |||
end | |||
i = i + 1 | |||
end | |||
end | |||
end | |||
end | |||
end | |||
end | end | ||
local | forEach(inherit) | ||
local DAMAGE_PARSED = createDamageDataTable() | |||
if | function parseConfig(mode) | ||
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 = v | |||
for _, v2 in ipairs(v) do | |||
local arg_from_template = args[prefix .. v2] or args[v2] | |||
if arg_from_template ~= nil then | |||
output_value = arg_from_template | |||
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 | |||
end | |||
break | |||
else | |||
if k == 'provided' then | |||
output_value = false | |||
else | |||
output_value = {} | |||
end | |||
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 | ||
-- | 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) | |||
for k, hit_count in ipairs(new_value.hit_counts) do | |||
hit_count = hit_count == '' and 1 or hit_count | |||
new_value.hit_counts[k] = hit_count * | |||
((string.find(damage_key, 'awk_') and args.awk_count) and args.awk_count[1] or args.count[1]) | |||
end | |||
WITH_EACH[mode][damage_key:gsub("total_", "each_")] = damage_value | |||
WITH_EACH[mode][damage_key] = new_value | |||
end | |||
end | |||
end | |||
return WITH_EACH | |||
end | end | ||
if args.count then | |||
DAMAGE_PARSED = doEachDamage() | |||
end | end | ||
-- | function doBasicDamage() | ||
for mode, mode_content in pairs(DAMAGE_PARSED) do | |||
for damage_key, damage_value in pairs(mode_content) do | |||
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) do | |||
local hit_count = damage_value.hit_counts[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. | |||
BASIC_DAMAGE[mode][damage_key] = output | |||
end | |||
end | |||
end | |||
end | end | ||
doBasicDamage() | |||
-- | -- Adding missing cancel part damage to full, so that repetition wouldn't be a problem. | ||
function | 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] | |||
if not string.find(damage_key, 'cancel_') and cancel_candidate then | |||
BASIC_DAMAGE[mode][damage_key] = damage_value + cancel_candidate | |||
end | |||
end | |||
end | |||
end | end | ||
if | if args.cancel_dmg then | ||
addCancelDamage() | |||
end | end | ||
for | local WITH_TRAITS = createDamageDataTable() | ||
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 | |||
WITH_TRAITS[mode][damage_key .. ((trait.key == 'useful' or trait.key == '') and "" or ('_' .. trait.key))] = | |||
damage_value * trait.value | |||
end | |||
end | |||
end | |||
end | end | ||
end | end | ||
-- | doTraits() | ||
function | |||
local WITH_PASSIVES = createDamageDataTable() | |||
end | |||
--[[ | |||
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 | |||
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 | |||
WITH_PASSIVES[mode][damage_key .. name_suffix] = damage_value * passive_multiplier | |||
end | |||
end | |||
end | |||
end | |||
doPassives() | |||
local RANGE = { | |||
min_count = args.range_min_count and args.range_min_count[1] or 1, | |||
max_count = args.range_max_count and args.range_max_count[1] or 1, | |||
PvE = { | |||
min = args.range_min and args.range_min[1] or 1, | |||
max = args.range_max and args.range_max[1] or 1 | |||
}, | |||
PvP = { | |||
min = args.range_min and (args.range_min[2] or args.range_min[1]) or 1, | |||
max = args.range_max and (args.range_max[2] or args.range_max[1]) or 1 | |||
} | |||
} | |||
function | local WITH_RANGE = createDamageDataTable() | ||
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 } | |||
for _, range in ipairs({ 'min', 'max' }) do | |||
local final_damage_value = damage_value * (1 + ((RANGE[mode][range] - 1) * RANGE[range .. '_count'])) * | |||
OPTIONS.perm_buff[mode] | |||
WITH_RANGE[mode][damage_key][range] = 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 | |||
if | |||
-- Helper function to iterate over traits. | |||
function checkTraits(settings) | |||
local output | |||
if not settings then | |||
output = false | |||
else | else | ||
if ( | 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 | ||
end | end | ||
return output | |||
end | end | ||
function | -- Helper function to iterate over passives. | ||
function checkPassives(settings) | |||
local output = settings.output or {} | |||
for | local PASSIVES_WITH_COMBINED = table.deep_copy(PASSIVES) | ||
if | |||
-- 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 | |||
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 type(settings.action) == 'function' then | |||
settings.action(passive, output, passive_index) | |||
else | |||
return true | |||
end | |||
end | end | ||
end | end | ||
return | return output | ||
end | end | ||
function table. | -- Generate the table | ||
local TABLE = mw.html.create('table'):attr({ | |||
end | 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 | |||
}), | |||
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 | |||
}), | |||
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', nil, | |||
OPTIONS.dmp ~= 'false' and ('(' .. OPTIONS.dmp .. ' DMP)')) | |||
elseif args.awk_alias then | |||
return link(unpack(args.awk_alias)) | |||
end | |||
return link('Awakening Mode') | |||
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 | |||
}), | |||
keywords = checkTraits({ | |||
action = function(trait, output) | |||
table.insert(output, trait.key) | |||
end | |||
}), | |||
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 | |||
return { 'total' } | |||
end)(), | |||
is_visible = ((inArgs('avg_hits') or inArgs('count')) and not OPTIONS.no_max) or false | |||
} | |||
} | |||
function | function TABLE:new() | ||
return self:tag('tr') | |||
end | end | ||
function | 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 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 | |||
-- 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 | |||
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 | |||
table.insert(new_list, new_key) | |||
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 | ||
-- Sort the list once more, in order to swap the order of cancel & full. | |||
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 | |||
new_list[i] = prefix .. damage_key | |||
end | |||
end | |||
all_list = new_list | |||
end | end | ||
return all_list | |||
end | end | ||
function | function doInitialCell(new_row) | ||
return new_row:tag('th'):wikitext('Mode') | |||
end | end | ||
function | function doHeaders() | ||
local current_multiplier = 0 -- Keeps track of the number of cells to spawn | |||
end | 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 | |||
-- 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 | |||
--[[ | |||
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 | |||
-- 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 | |||
current_multiplier = next_multiplier | |||
iterations = iterations + 1 | |||
end | |||
end | |||
-- Apply rowspan of the same value as iteration count. | |||
initial_header_cell:attr('rowspan', iterations) | |||
end | end | ||
if | |||
return | -- 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 | |||
return damage_number | |||
end | end | ||
function | function doContentByMode(mode) | ||
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 | |||
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 | |||
last_number = damage_number | |||
else | |||
mode_row:tag('td'):wikitext(damage_key) | |||
end | end | ||
end | end | ||
end | end | ||
function | function doTable() | ||
doHeaders() | |||
return | forEach(doContentByMode) | ||
end | end | ||
doTable() | |||
-- Dump all values if wanted. | |||
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) | |||
end | |||
local bug = '' | |||
local | if OPTIONS.bug then | ||
bug = frame:expandTemplate { | |||
title = 'SkillText', | |||
args = { 'FreeTraining' } | |||
} | |||
end | end | ||
-- Transform into variables | |||
local variables = doVariables(frame, FINAL_DAMAGE, OPTIONS.prefix) | |||
if out ~= nil then | |||
return inspect_dump(frame, out) | |||
end | end | ||
return | |||
return variables .. bug .. (OPTIONS.do_table and tostring(TABLE) or '') | |||
end | end | ||
return p | |||
-- pyend |