Module:Damage: Difference between revisions

no edit summary
No edit summary
No edit summary
Tag: Reverted
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 8: Line 7:
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 16: Line 14:
     end
     end


     local modes = { 'PvE', 'PvP' }
    -- Collect data from the input
     local data = {}
    local data_types = {
        'dmg',
        'awk_dmg',
        'hits',
        'avg_hits',
        'awk_hits',
        'avg_awk_hits',
        'hits_useful',
        'avg_hits_useful',
        'awk_hits_useful',
        'avg_awk_hits_useful',
        'perm_buff'
    }


     -- Define the schema for the table
     -- Handle the PvP split values
    local tableSchema = {}
     for k, v in spairs(data_types) do
     for _, mode in ipairs(modes) do
         table.insert(data_types, 'pvp_' .. v)
         tableSchema[mode] = {}
     end
     end


     function forEach(func)
     for k, v in spairs(data_types) do
         for _, mode in ipairs(modes) do
        local i = 1
            func(mode)
         if inArgs(v) then
            for k2, v2 in spairs(split(args[v])) do
                -- Check for operators. If detected, evaluate.
                if string.find(v2, '*') or string.find(v2, '+') then
                    v2 = frame:preprocess('{{#expr:' .. v2 .. '}}')
                end
 
                -- Check if proper hit count values provided. If empty string detected, inherit from 'hits'.
                if string.find(v, 'avg_') and string.find(v, '_hits') and v2 == '' then
                    data[v .. i] = data['avg_hits' .. i]
                elseif string.find(v, 'hits') and v2 == '' then
                    data[v .. i] = data['hits' .. i]
                elseif string.find(v, 'awk_dmg') and v2 == '' then
                    if string.find(v, 'pvp') then
                        data[v .. i] = data['pvp_dmg' .. i]
                    else
                        data[v .. i] = data['dmg' .. i]
                    end
                else
                    data[v .. i] = v2
                end
                i = i + 1
            end
         end
         end
     end
     end


     function forEachDamageType(func)
    -- For weird skills
         for _, damage_type in ipairs({ 'min', 'max' }) do
     function inheritMissing(keyTable, inheritTable)
             func(damage_type)
        local n = 1; -- counter for the func. argument loop
        local i;
         for k_key, v_key in spairs(keyTable) do
             if inArgs(inheritTable[n]) and not inArgs(v_key) then
                i = 1
                for k, v in spairs(split(args.dmg)) do
                    data[v_key .. i] = data[inheritTable[n] .. i]
                    i = i + 1
                end
            end
            n = n + 1
         end
         end
     end
     end


     -- Function to create a new table with the desired schema
     inheritMissing({'awk_dmg', 'pvp_awk_dmg', 'awk_hits', 'avg_awk_hits'}, {'dmg', 'pvp_dmg', 'hits', 'avg_hits'})
    function createDamageDataTable()
 
        local newTable = {}
    -- Laziness
        for key, value in pairs(tableSchema) do
    if args.hits and args.awk_dmg and not args.awk_hits then
            if type(value) == "table" then
        data.awk_hits = args.hits
                newTable[key] = {}
    end
            end
 
        end
    if args.awk_dmg and args.avg_hits and not args.avg_awk_hits then
         return newTable
         data.avg_awk_hits = args.avg_hits
     end
     end


     -- User requested options
     if args.awk_dmg and args.avg_hits_useful and not args.avg_awk_hits_useful then
    local OPTIONS = {
         data.avg_awk_hits_useful = args.avg_hits_useful
        do_table = args[1] == 'true',
     end
        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.
     -- Handle trait table
     local BASIC_DAMAGE = createDamageDataTable()
     local traits = {}


     -- Define a table with trait names and their values to apply.
     if inArgs('heavy') then
    local TRAITS = {
         traits.heavy = 1.44
        -- An empty trait so we keep the original values there.
     end
        {
            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)
     if inArgs('enhanced') then
         return frame:preprocess('{{#expr:' .. s .. '}}')
         traits.enhanced = 0.8
     end
     end


     -- A table with user-requested passive skills (empty by default).
     -- Customizable for empowered, it had to be special lol.
    local PASSIVES = {}
    if inArgs('empowered') then
    -- A table with non-numeric arguments to split.
        if (args.empowered == 'true') then
     local TO_SPLIT = { 'append', 'awk_alias' }
            traits.empowered = 1.2
        else
            traits.empowered = args.empowered
        end
     end


     for k, v in pairs(args) do
     if args.useful == 'true' then
        if string.find(k, 'passive') then
        args.useful = 0.7
            --[[
    end
            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
    if args.useful_penalty == 'true' then
                passive_name = passive_values[#passive_values]
        args.useful_penalty = 0.7
                passive_values[#passive_values] = nil
    end
            end


            PASSIVES[tonumber(passive_index)] = {
    -- Output passives if provided
                name = passive_name,
    local passives = {}
                value = passive_values[1],
    for i = 1, 3 do
                value_pvp = passive_values[2],
        if inArgs('passive' .. i) then
                alias = args['alias' .. passive_index] or (passive_index == OPTIONS.append_index and OPTIONS.append_name),
            passives[i] = args['passive' .. i]
                suffix = args['suffix' .. passive_index] and (' ' .. args['suffix' .. passive_index]) or '',
            passives[i] = split(frame:preprocess('{{:' .. passives[i] .. '}}{{#arrayprint:' .. passives[i] .. '}}'))
                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.
     function list(ispvp)
    for k, v in ipairs(args.dmg) do
 
         if not args.hits then
        -- Define tables that hold the subsequent damage values.
             args.hits = {}
        -- I know this isn't the best, but I don't want to work with nested tables in this language.
        local fvals = {}
        local tvals = {}
        local pvals = {
            [1] = {},
            [2] = {},
            [3] = {},
            [12] = {},
            [13] = {},
            [23] = {},
            [123] = {}
        }
 
        -- Check the specified mode and define the prefixes/suffixes first.
        local pr = ''
        local su = ''
        local p_index = 1
         if ispvp then
             p_index = 2
         end
         end
         if not args.hits[k] then
 
             args.hits[k] = 1
         if (ispvp) then
             pr = 'pvp_'
            su = '_pvp'
         end
         end
    end


    -- Set basic hit count to 1 for all cancel damage.
        -- Define total/average damage calculation based on damage per hit and hit amount.
    if args.cancel_dmg then
        function getTotal(dmg, hits, fval, count)
        for k, v in ipairs(args.cancel_dmg) do
            -- Handle PvP prefixes/suffixes
             if not args.cancel_hits then
            if inArgs(pr .. dmg) then
                 args.cancel_hits = {}
                dmg = pr .. dmg
            end
             if inArgs(pr .. hits) then
                 hits = pr .. hits
             end
             end
             if not args.cancel_hits[k] then
 
                 args.cancel_hits[k] = 1
             if dmg == 'awk_dmg' and ispvp and not inArgs(pr .. 'awk_dmg') then
                 dmg = pr .. 'dmg'
             end
             end
        end
    end


    -- Store a configuration that will tell the main function how to behave given different inputs.
             fval = fval .. su
    -- 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 = {}
            local i = 1
    function handleCancel()
            fvals[fval] = 0
        local processed_keys = {}
            for k, v in spairs(split(args.dmg)) do
        for config_key, config_value in pairs(BASE_DAMAGE_CONFIG) do
                if -- If 'hits' defined, but 'avg_hits' not defined, inherit from 'hits'.
            if not config_key:match('cancel_') then
                (data[hits .. i] == nil and data['hits' .. i] ~= nil and hits == 'avg_hits') then
                local new_config_value = {}
                    data[hits .. i] = data['hits' .. i]
                 for arg_table_key, arg_table in pairs(config_value) do
                 elseif -- If 'hits' undefined, assume they're equal to 1.
                     local new_arg_table = {}
                (data[hits .. i] == nil) then
                    for _, arg in ipairs(arg_table) do
                     data[hits .. i] = 1
                         table.insert(new_arg_table, 'cancel_' .. arg)
                end
                -- Proceed to combine
                fvals[fval] = fvals[fval] + data[dmg .. i] * data[hits .. i] * (data[pr .. 'perm_buff' .. i] or data['perm_buff' .. i] or 1)
                i = i + 1
            end
            -- For skills with multiple same parts, ex. Clementine, Enough Mineral
            if count == true then
                fvals[fval] = fvals[fval] * args.count
                if inArgs('count_extra' .. su) then
                    if args.count_extra_hits == nil then
                         args.count_extra_hits = 1
                    end
                    if not string.find(fval, "each_") then
                        fvals[fval] = fvals[fval] + (args['count_extra' .. su] * args['count_extra_hits'])
                     end
                     end
                    new_config_value[arg_table_key] = new_arg_table
                 end
                 end
                local new_key = 'cancel_' .. config_key
            end
                 DAMAGE_CONFIG[new_key] = new_config_value
            -- Apply Useful modifier.
                processed_keys[new_key] = true
            if string.find(fval, 'useful') then
                 fvals[fval] = fvals[fval] * (args.useful_penalty or args.useful)
             end
             end
         end
         end
        return processed_keys
    end


    if args.cancel_dmg then
        -- Actually generate the values depending on arguments provided.
        handleCancel()
        if inArgs(pr .. 'dmg') then
        DAMAGE_CONFIG = table.fuse(BASE_DAMAGE_CONFIG, DAMAGE_CONFIG)
            if (inArgs('count')) then
    else
                getTotal('dmg', 'hits', 'each_damage')
         DAMAGE_CONFIG = BASE_DAMAGE_CONFIG
                getTotal('dmg', 'hits', 'total_damage', true)
    end
            else
                getTotal(pr .. 'dmg', 'hits', 'total_damage')
            end
 
            if inArgs('avg_hits') then
                getTotal('dmg', 'avg_hits', 'avg_damage')
            end
         end


    -- Helper function to check if a table is not empty
        if inArgs(pr .. 'awk_dmg') or inArrayStarts(pr .. 'awk_dmg', data) then
    local function isTableNotEmpty(tbl)
            getTotal('awk_dmg', 'awk_hits', 'total_damage_awk')
        return next(tbl) ~= nil
    end


    -- Function to apply inheritance for a specific damage type and argument
            if (inArgs('avg_hits') and (inArgs('awk_dmg') or inArgs('awk_hits'))) or inArgs('avg_awk_hits') then
    local function applyInheritance(mainArgValues, inheritArg, mainArgValue, inheritValue)
                getTotal('awk_dmg', 'avg_awk_hits', 'avg_damage_awk')
        if mainArgValue == '' then
            end
            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
        -- Handling traits
    local function applyInheritanceForKey(args, prefix, argTable, damageTypeIndex, damageType)
        -- Useful handled separately
        local mainKey = argTable[1] .. damageType
        if inArgs('useful_penalty') or inArgs('useful') then
        local mainKeyPrefixed = prefix .. mainKey
            getTotal(pr .. 'dmg', 'hits_useful', 'total_damage_useful')
        local mainArgValues = args[mainKeyPrefixed]


        if mainArgValues then
            if inArgs('avg_hits_useful') then
            local i = 1
                getTotal('dmg', 'avg_hits_useful', 'avg_damage_useful')
             local cancelDmgLen = args.cancel_dmg and #args.cancel_dmg or 0
             end


             while i <= (#args.dmg + cancelDmgLen) do
             if inArgs(pr .. 'awk_dmg') and inArgs('awk_hits_useful') then
                 local mainArgValue = mainArgValues[i]
                 getTotal('awk_dmg', 'awk_hits_useful', 'total_damage_awk_useful')
            end


                 for ix, inheritKey in ipairs(argTable) do
            if inArgs(pr .. 'avg_awk_hits') and inArgs('avg_awk_hits_useful') then
                    local inheritArg = args[prefix .. inheritKey .. damageType] or args[inheritKey .. damageType]
                 getTotal('awk_dmg', 'avg_awk_hits_useful', 'avg_damage_awk_useful')
            end
        end


                    -- Basic damage/hits inheritance request detected. Ignore min/max.
        -- Multiply all values with traits and store them in another table.
                     if damageType and mainKey:gsub(damageType, "") == argTable[#argTable] then
        for k, v in spairs(fvals) do
                         inheritArg = args[prefix .. inheritKey] or args[inheritKey]
            if not string.find(k, 'useful') then
                for kt, vt in spairs(traits) do
                     if inArgs(kt) then
                        local dmg_name = k .. '_' .. kt
                        if ispvp then
                            dmg_name = dmg_name:gsub(su, '') .. su
                        end
                        local dmg_formula = v * vt
                         tvals[dmg_name] = dmg_formula
                     end
                     end
                end
            end
        end
        -- Get a table of merged base & trait values
        local ftvals = fvals
        tableMerge(ftvals, tvals)


                    if inheritArg and inheritArg[i] and
        function addPassive(num, loop_table)
                        (damageTypeIndex == 1 and ix ~= 1 or damageTypeIndex ~= 1) and tonumber(inheritArg[i])
            local pval_index
                    then
            if loop_table == nil then
                        mainArgValues[i] = applyInheritance(mainArgValues, inheritArg, mainArgValue, inheritArg[i])
                pval_index = num
                        break
                loop_table = ftvals
                    end
            else
                pval_index = tonumber(loop_table .. num)
                loop_table = pvals[loop_table]
            end
            for k, v in spairs(loop_table) do
                local dmg_name = k .. '_passive' .. num
                if ispvp then
                    dmg_name = dmg_name:gsub(su, '') .. su
                 end
                 end
                local dmg_formula = v * passives[num][p_index]
                pvals[pval_index][dmg_name] = dmg_formula
            end
        end


                 i = i + 1
        -- Add passives and combine them.
        if inArgs('passive2') then
            addPassive(2)
            if inArgs('passive3') then
                 addPassive(3, 2)
             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
         if inArgs('passive1') then
             for argTableKey, argTable in pairs(configValue) do
             addPassive(1)
                if argTableKey ~= 'provided' and isTableNotEmpty(argTable) then
            if inArgs('passive2') then
                    for damageTypeIndex, damageType in ipairs({ '', '_min', '_max' }) do
                addPassive(2, 1)
                        applyInheritanceForKey(args, prefix, argTable, damageTypeIndex, damageType)
                if inArgs('passive3') then
                    end
                    addPassive(3, 12)
                 end
                 end
            end
            if inArgs('passive3') then
                addPassive(3, 1)
             end
             end
         end
         end
    end


    forEach(inherit)
        if inArgs('passive3') then
            addPassive(3)
        end


    local DAMAGE_PARSED = createDamageDataTable()
        -- Merge all tables into one.
    function parseConfig(mode)
         tableMerge(fvals, tvals)
         local prefix = mode == 'PvE' and '' or string.lower(mode .. '_')
         for k, v in spairs(pvals) do
         for config_key, config_value in pairs(DAMAGE_CONFIG) do
             tableMerge(fvals, v)
             for k, v in pairs(config_value) do
        end
                local output_value = {}


                -- When both min and max are found, we need to break from the loop.
        return fvals
                local isValueFound = { min = false, max = false }
    end


                for _, v2 in ipairs(v) do -- This array holds the argument names with fallbacks
    local out = list(false)
                    forEachDamageType(function(damage_type)
    local out_pvp = list(true)
                        -- 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 =
    -- Merge the output to a unified table.
                            args[prefix .. v2 .. '_' .. damage_type]
    tableMerge(out, out_pvp)
                            or args[v2 .. '_' .. damage_type]
                            or args[prefix .. v2]
                            or args[v2];


                        if arg_from_template ~= nil then
    -- Function wrapper for vardefine syntax in MW.
                            if k == 'provided' then
    function var(name, dmg, prefix)
                                output_value = true
        if prefix == nil then
                                -- Do not generate total_damage values at all if the skill can't reach them.
            prefix = ''
                                if string.find(config_key, 'total_') and OPTIONS.no_max then
        else
                                    output_value = false
            prefix = prefix .. '_'
                                end
        end
                            else
        if dmg == 0 then
                                if type(output_value) ~= "table" then
            dmg = 'N/A'
                                    output_value = {}
        else
                                end
            dmg = round(dmg)
                                output_value[damage_type] = arg_from_template
        end
                            end
        if (args.format == 'false' or dmg == 'N/A') then
                            -- Mark the value as found.
            return '{{#vardefine:' .. prefix .. name .. '|' .. dmg .. '}}'
                            isValueFound[damage_type] = true
        else
                        else
            return '{{#vardefine:' .. prefix .. name .. '|{{formatnum:' .. dmg .. '}}%}}'
                            if k == 'provided' then
        end
                                output_value = false
    end
                            else
                                output_value[damage_type] = {}
                            end
                        end
                    end)


                    -- Both values found, we can now break the loop.
    -- Apply ranges.
                    if isValueFound.min and isValueFound.max then
    function getRangeCount(arg)
                        break
        if inArgs(arg) then
                    end
            data[arg] = split(args[arg])
                end
            if data[arg][2] == nil then
                if DAMAGE_PARSED[mode][config_key] == nil then
                data[arg][2] = data[arg][1]
                    DAMAGE_PARSED[mode][config_key] = {}
                end
                DAMAGE_PARSED[mode][config_key][k] = output_value
             end
             end
         end
         end
     end
     end


     forEach(parseConfig)
     getRangeCount('range_min_count');
    getRangeCount('range_max_count')


    -- Detected "count", for skills like Clementine, Enough Mineral, etc.
     function determineRange(minmax)
     function doEachDamage()
         if inArgs('range_' .. minmax) then
         local WITH_EACH = table.deep_copy(DAMAGE_PARSED)
            data['range_' .. minmax] = split(args['range_' .. minmax])
        for mode, mode_content in pairs(DAMAGE_PARSED) do
             if data['range_' .. minmax][2] == nil then
             for damage_key, damage_value in pairs(mode_content) do
                 data['range_' .. minmax][2] = data['range_' .. minmax][1]
                 if string.find(damage_key, 'total_') then
            end
                    local new_value = table.deep_copy(damage_value)
            if inArgs('range_' .. minmax .. '_count') then
 
                local i = 1;
                    forEachDamageType(function(damage_type)
                for k, v in spairs(data['range_' .. minmax]) do
                        for k, hit_count in ipairs(new_value.hit_counts[damage_type]) do
                    data['range_' .. minmax][i] = 1 + (-1 + data['range_' .. minmax][i]) *
                            hit_count = hit_count == '' and 1 or hit_count
                                                      data['range_' .. minmax .. '_count'][i]
                            new_value.hit_counts[damage_type][k] = hit_count *
                     i = i + 1
                                ((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
     determineRange('min');
        DAMAGE_PARSED = doEachDamage()
     determineRange('max');
     end


     function doBasicDamage()
     -- If maximum range is specified, but not minimum, and minimum count is specified.
        for mode, mode_content in pairs(DAMAGE_PARSED) do
    -- By default, it would just do the same as with max, don't want that.
            for damage_key, damage_value in pairs(mode_content) do
    if inArgs('range_max') and not inArgs('range_min') then
                forEachDamageType(function(damage_type)
        data['range_min'] = {1, 1}
                    local i = 1
        if inArgs('range_min_count') then
                    local output = 0
            local range_max_arg = split(args.range_max);
                    -- Check if to even generate the damage.
            if range_max_arg[2] == nil then
                    if damage_value.provided then
                range_max_arg[2] = range_max_arg[1]
                        -- 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
            data['range_min'] = {1 + range_max_arg[1] * data['range_min_count'][1],
                                1 + range_max_arg[2] * data['range_min_count'][2]}
         end
         end
     end
     end


     doBasicDamage()
     local out_min = {}
    local out_max = {}


    -- Adding missing cancel part damage to full, so that repetition wouldn't be a problem.
     function applyRange(minmax)
     function addCancelDamage()
         local temp_tab = {};
         for mode, mode_content in pairs(BASIC_DAMAGE) do
        if minmax == 'min' then
             for damage_key, damage_value in pairs(mode_content) do
            temp_tab = out_min
                 local cancel_candidate = BASIC_DAMAGE[mode]['cancel_' .. damage_key]
        else
                forEachDamageType(function(damage_type)
            temp_tab = out_max
                     if not string.find(damage_key, 'cancel_') and cancel_candidate then
        end
                         BASIC_DAMAGE[mode][damage_key][damage_type] = damage_value[damage_type] +
        if inArgs('range_max') then
                            cancel_candidate[damage_type]
             for k, v in spairs(out) do
                 if not (string.starts(k, 'min_') or string.starts(k, 'max_')) then
                     if (string.find(k, '_pvp')) then
                         temp_tab[minmax .. '_' .. k] = v * data['range_' .. minmax][2];
                    else
                        temp_tab[minmax .. '_' .. k] = v * data['range_' .. minmax][1];
                     end
                     end
                 end)
                 end
             end
             end
         end
         end
        tableMerge(out, temp_tab)
     end
     end


     if args.cancel_dmg then
     applyRange('min');
         addCancelDamage()
    applyRange('max');
 
    -- Get the actual variables with MW syntax.
    local vars = {}
    for k, v in spairs(out) do
         table.insert(vars, var(k, v, args.prefix))
     end
     end


     local WITH_TRAITS = createDamageDataTable()
    -- Transform ranges to variables.
     function doTraits()
     local vars_range = {}
        -- Handle traits here
     if (inArgs('range_max')) then
         for mode, mode_content in pairs(BASIC_DAMAGE) do
         for k, v in spairs(out) do
             for damage_key, damage_value in pairs(mode_content) do
             if not (string.starts(k, 'min_') or string.starts(k, 'max_')) then
                for _, trait in pairs(TRAITS) do
                local prefix = ''
                    --[[
                if args.prefix ~= nil then
                    Suffix all damage values with existing traits.
                    prefix = args.prefix .. '_'
                    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
                table.insert(vars_range,
                    '{{#vardefine: ' .. prefix .. 'range_' .. k .. '|{{formatnum:' .. round(out_min['min_' .. k]) ..
                        '}}% ~ {{formatnum:' .. round(out_max['max_' .. k]) .. '}}%}}');
             end
             end
         end
         end
        indexTableMerge(vars, vars_range);
    end
    -- Dump all values if wanted.
    if args.dump == 'true' then
        local ret = {}
        for k, v in spairs(out) do
            table.insert(ret, k .. ': ' .. v)
        end
        return frame:preprocess(table.concat(ret, "<br/>"))
     end
     end


     doTraits()
     -- Parse all variables
    local parsed = frame:preprocess('{{ ' .. table.concat(vars) .. 'trim2}}')


     local WITH_PASSIVES = createDamageDataTable()
    if args[1] ~= 'true' and args.table ~= 'true' then
        return parsed
    end
 
    local char = args.char or args[2] or 'Elsword'
 
    -- Generate the table
     local tbl = mw.html.create('table'):attr({
        ['cellpadding'] = 5,
        ['border'] = 1,
        ['style'] = 'border-collapse: collapse; text-align: center',
        ['class'] = 'colortable-' .. char
    })
 
    -- For rowspan, colspan shenanigans
    function increaseSpace(el, type, num)
        if type == 'row' then
            type = 'rowspan'
        elseif type == 'col' then
            type = 'colspan'
        end
        num = num or 1
        return el:attr(type, tonumber(el:getAttr(type) or 1) + num)
    end


     --[[
     function multiplySpace(el, type, m)
    Generates passives with every possible combinations of all subsets.
         if not el then
    For example: 3 passives are given, so it will generate the following:
             return false
    (1), (2), (3), (1, 2), (1, 3), (1, 2, 3), (2, 3)
        end
    ]]
        if type == 'row' then
    function doPassives()
            type = 'rowspan'
         for mode, mode_content in pairs(WITH_TRAITS) do
        else
             for damage_key, damage_value in pairs(mode_content) do
            type = 'colspan'
                forEachDamageType(function(damage_type)
        end
                    local combinations = { {} }
        if m == nil then
                    for passive_key, passive in pairs(PASSIVES) do
             m = 2
                        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
        local span = el:getAttr(type) or 1
        return el:attr(type, tonumber(span) * m)
     end
     end


     doPassives()
     local combine = split(args.combine)
    local combine_suffix = args.combine_suffix
    local append = split(args.append)[1]
    local append_alias = split(args.append)[2]
 
    local tbl_order = {'extra', 'passives_normal', 'passives_switch', 'awk', 'traits', 'hit_count'}
    local STR = {
        BASE = 'Base',
        MODE = 'Mode',
        REGULAR = 'Regular',
        NORMAL = 'Normal',
        AWK = 'Awakening Mode',
        AVG = 'Average',
        MAX = 'Max',
        TRAIT = {'Enhanced', 'Empowered', 'Useful', 'Heavy'},
        PER = 'Per',
        INSTANCE = 'Instance'
    }
 
    local trait_args = {}
    for k, v in ipairs(STR.TRAIT) do
        table.insert(trait_args, string.lower(v))
    end
 
    local trait_count = 0
    for k, v in ipairs(trait_args) do
        if inArrayHas(v, out) then
            trait_count = trait_count + 1
        end
    end


     local RANGE = {
     local tbl_content = {
         min_count = args.range_min_count and args.range_min_count[1],
         extra = {
         max_count = args.range_max_count and args.range_max_count[1],
            mode = STR.MODE,
        PvE = {
            long = STR.AVG
             min = args.range_min and args.range_min[1],
        },
             max = args.range_max and args.range_max[1]
         passives_normal = {
            mode = STR.MODE,
            base = STR.BASE,
            combined = {},
             aliases = {args.alias1 or false, args.alias2 or false, args.alias3 or false},
             suffixes = {args.suffix1 or false, args.suffix2 or false, args.suffix3 or false}
         },
         },
         PvP = {
         passives_switch = {
             min = args.range_min and (args.range_min[2] or args.range_min[1]),
             normal = STR.NORMAL,
             max = args.range_max and (args.range_max[2] or args.range_max[1])
            hide = true
        },
        awk = {
            normal = STR.REGULAR,
            awk_link = '[[' .. STR.AWK .. ']]',
            hide = true
        },
        traits = {
             normal = STR.NORMAL,
            hide = true
        },
        hit_count = {
            avg = STR.AVG,
            max = STR.MAX,
            hide = true
         }
         }
     }
     }


     local WITH_RANGE = createDamageDataTable()
     local count_name = args.count_name or STR.INSTANCE
     function doDamageBuffRange()
     if inArgs('count') and not args.use_avg then
         -- Handle damage range here
         tbl_content.hit_count.avg = table.concat({STR.PER, count_name}, ' ')
        for mode, mode_content in pairs(WITH_PASSIVES) do
    end
            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;
    function getRowIndex(row)
                    WITH_RANGE[mode][damage_key][damage_type] = not OPTIONS.format and final_damage_value or
        for k, v in ipairs(tbl_order) do
                        formatDamage(final_damage_value)
            if row == v then
                 end)
                 return k
             end
             end
         end
         end
     end
     end


     doDamageBuffRange()
     for passive_i = 1, 3 do
        repeat
            -- Add normal passives to the first row.
            local passive_name = ''
            -- Alias for appended passives.
            if (inArgs('passive' .. passive_i)) then
                passive_name = args['passive' .. passive_i]
            end
            if indexOf(tostring(passive_i), combine) ~= nil then
                -- Add combined passives.
                tbl_content.passives_normal.combined[passive_i] = passive_name
            elseif tostring(passive_i) == append then
                -- Add switch passives.
                tbl_content.passives_switch[passive_i] = passive_name
                tbl_content.passives_switch.hide = false
                if append_alias ~= nil then
                    tbl_content.passives_switch.display_name = append_alias
                end
            elseif (inArgs('passive' .. passive_i)) then
                -- Add regular passives to the first row.
                tbl_content.passives_normal[passive_i] = passive_name
            end
        until true
    end


     local FINAL_DAMAGE = WITH_RANGE
     local ret = ''


     -- Helper function to iterate over traits.
     for trait_order, trait_arg in ipairs(trait_args) do
    function checkTraits(settings)
        -- Add traits if exist.
        local output
        if (inArgs(trait_arg)) then
        if not settings then
             tbl_content.traits[trait_arg] = args[trait_arg]
             output = false
             tbl_content.traits.hide = false
        else
             output = settings.output or {}
         end
         end
    end
    -- Add Useful trait.
    if inArgs('hits_useful') or inArgs('avg_hits_useful') then
        tbl_content.traits.useful = 'true'
        tbl_content.traits.hide = false
    end
    if inArgs('avg_hits') or inArgs('count') then
        -- Enable average/max if needed.
        tbl_content.hit_count.hide = false
    end
    if inArrayHas('awk_', args) then
        -- Enable Awakening if needed.
        tbl_content.awk.hide = false
    end
    local loop_factor, mode_th;
    local cells = {}
    local passive_normal_count = 0;
    local passive_switch_count = 0;


        for trait_index, trait in ipairs(TRAITS) do
    function hidden(level)
            if trait.value ~= false and trait_index ~= 1 then
         return tbl_content[level].hide
                if settings and type(settings.action) == 'function' then
                    settings.action(trait, output, settings)
                else
                    return true
                end
            end
        end
         return output
     end
     end


     -- Helper function to iterate over passives.
     local hit_count_table = {'total'}
     function checkPassives(settings)
     local awk_table = {''}
        local output = settings.output or {}
    local levels_exist =
        local PASSIVES_WITH_COMBINED = table.deep_copy(PASSIVES)
        (next(passives) or args.append or args.awk_hits or args.awk_dmg or args.count)
    local no_max = args.no_max == 'true'


        -- Handle combined passives properly.
    function makePassiveLink(passive, alias, suffix, nil_cond)
         if OPTIONS.combine then
         if nil_cond == nil then
             table.insert(PASSIVES_WITH_COMBINED, {
             nil_cond = true
                is_combined = true
            })
         end
         end
        if nil_cond and alias ~= nil and alias ~= false then
            alias = '|' .. alias
        else
            alias = ''
        end
        suffix = suffix or ''
        passive = '[[' .. passive .. alias .. ']]' .. suffix
        return passive
    end
    -- Begin the main loop.
    for k, type in ipairs(tbl_order) do
        repeat
            local tr = tbl:tag('tr')
            local data = tbl_content[type];
            local hide = data.hide;
            cells[type] = {}


        for passive_index, passive in ipairs(PASSIVES_WITH_COMBINED) do
            function new(wikitext, normal)
            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
                local th = tr:tag('th'):wikitext(wikitext)
                 if type(settings.action) == 'function' then
                 if normal == true then
                     settings.action(passive, output, passive_index)
                    table.insert(cells[type].normal_th, th)
                elseif normal == false then
                     table.insert(cells[type].th, th)
                 else
                 else
                     return true
                     return th
                 end
                 end
             end
             end
        end
        return output
    end


    -- Generate the table
             function multiplySpaceAll(level, num)
    local TABLE = mw.html.create('table'):attr({
                if cells[level] == nil then
        cellpadding = 5,
                     return false
        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
            }),
                num = num or 2
            keywords = checkPassives({
                 for k, v in ipairs(cells[level].th) do
                 action = function(passive, output, passive_index)
                     multiplySpace(v, 'col', num)
                    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
            }),
                 for k, v in ipairs(cells[level].normal_th) do
            is_visible = not OPTIONS.no_max or #PASSIVES > 0
                     multiplySpace(v, 'col', num)
        },
        {
            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
                return link('Awakening Mode', nil, args.awk_prefix, args.awk_suffix)
             end
             end)()
 
            },
             function reverseMultiplySpace(num, increase_mode)
             keywords = { 'awk' },
                local i = #tbl_order;
            keyword_next_to_main_key = true,
                num = num or 2
            is_visible = inArgs('awk_dmg') or inArgs('awk_hits') or inArgs('avg_awk_hits') or false
 
        },
                 local fix_for_no_max = 0
        {
                 if no_max and not levels_exist then
            type = 'traits',
                     fix_for_no_max = -1
            text = checkTraits({
                 output = { 'Normal' },
                 action = function(trait, output)
                     table.insert(output, trait.name)
                 end
                 end
            }),
 
            keywords = checkTraits({
                 while (i > 1) do
                 action = function(trait, output)
                     multiplySpaceAll(tbl_order[i - 1 + fix_for_no_max], num)
                     table.insert(output, trait.key)
                    i = i - 1
                 end
                 end
            }),
                 if increase_mode ~= false then
            is_visible = checkTraits()
                     increaseSpace(mode_th, 'row')
        },
        {
            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
                return { 'total' }
             end
             end)(),
            is_visible = ((inArgs('avg_hits') or inArgs('count')) and not OPTIONS.no_max) or false
        }
    }


    function TABLE:new()
            cells[type].normal_th = {}
        return self:tag('tr')
            cells[type].th = {}
    end


    function returnDamageInOrder()
            if (type == 'extra' and no_max) then
        local main_key = 'damage'
                mode_th = new(data.mode)
        local all_list = {}
                new(data.long, true)
            end


        -- Initialize current list with main key
            if (type == 'passives_normal') then
        local current_list = { main_key }
                if not no_max then
                    mode_th = new(data.mode)
                else
                    reverseMultiplySpace(nil, false);
                end


        for i = #TABLE_CONTENT, 1, -1 do
                if (no_max and levels_exist) or not no_max then
            local current_row = TABLE_CONTENT[i]
                    new(data.base, true)
            local new_list = {}
                end


            -- Check if it's the first iteration. If so, append phrases.
                 for i = 1, 3, 1 do
            if not current_row.no_damage then
                    local passive_link = data[i]
                 if i == #TABLE_CONTENT then
                     if (passive_link ~= nil) then
                     for _, keyword in ipairs(current_row.keywords) do
                        local suffix = ''
                         if not OPTIONS.no_max or (OPTIONS.no_max and keyword ~= 'total') then
                         if next(data.suffixes) and data.suffixes[i] ~= false then
                             local new_key = keyword .. '_' .. main_key
                             suffix = ' ' .. data.suffixes[i]
                            table.insert(new_list, new_key)
                         end
                         end
                        passive_link = makePassiveLink(passive_link, data.aliases[i], suffix, next(data.aliases))
                        new(passive_link, false)
                        passive_normal_count = passive_normal_count + 1
                     end
                     end
                 elseif current_row.is_visible then
                 end
                     -- Append suffix for each keyword in current row
 
                     for _, keyword in ipairs(current_row.keywords) do
                -- Handle combining passives.
                         -- Iterate through previous keys
                if next(data.combined) then
                        for _, prev_key in ipairs(all_list) do
                     local combined_str = ''
                            local new_key = prev_key .. '_' .. keyword
                     for k, v in spairs(data.combined) do
                            -- If needed, move the suffix to the rightmost of main_key.
                         combined_str = combined_str .. makePassiveLink(v, data.aliases[k], data.suffixes[k]) .. '/'
                            if current_row.keyword_next_to_main_key then
                    end
                                new_key = prev_key:gsub(main_key, main_key .. '_' .. keyword)
                    combined_str = combined_str:gsub('/$', '')
                            elseif current_row.keyword_first then
                    if combine_suffix then
                                 new_key = keyword .. '_' .. prev_key
                        combined_str = combined_str .. ' ' .. combine_suffix
                    end
                    new(combined_str, false)
                    passive_normal_count = passive_normal_count + 1
                end
 
            end
 
            if (type == 'passives_switch') then
                if not hidden(type) then
                    multiplySpaceAll('passives_normal')
                    -- For some reason, whenever appending is active, it misses a 1 in rowspan of this cell.
                    increaseSpace(mode_th, 'row')
                end
 
                -- Passives that appear in the second row
                loop_factor = (passive_normal_count + 1)
                for ix = 1, loop_factor, 1 do
                    for i = 1, 3, 1 do
                        if (data[i] ~= nil) then
                            new(data.normal, true);
                            local suffix = ''
                            if next(tbl_content.passives_normal.suffixes) and tbl_content.passives_normal.suffixes[i] ~= false then
                                 suffix = ' ' .. tbl_content.passives_normal.suffixes[i]
                             end
                             end
                             table.insert(new_list, new_key)
                             local passive_link = data[i]
                            passive_link = makePassiveLink(passive_link, data.display_name, suffix)
                            new(passive_link, false)
 
                            if (ix == 1) then
                                -- Count switch passives. Only one iteration.
                                passive_switch_count = passive_switch_count + 1
                            end
 
                         end
                         end
                     end
                     end
                 end
                 end
            end


                 -- Append new_list to all_list
            if (type == 'awk' and not hide) then
                 for _, new_key in ipairs(new_list) do
                 reverseMultiplySpace();
                     table.insert(all_list, sortPassives(new_key))
                 table.insert(awk_table, 'awk')
 
                loop_factor = loop_factor * (passive_switch_count + 1)
 
                for i = 1, loop_factor, 1 do
                     new(data.normal, true)
                    new(data.awk_link, false)
                 end
                 end
             end
             end
        end


        -- Sort the list once more, in order to swap the order of cancel & full.
            if (type == 'traits' and not hide) then
        if inArgs('cancel_dmg') then
                 if trait_count == 2 then
            local new_list = {}
                     reverseMultiplySpace(3);
            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
                     reverseMultiplySpace();
                end
 
                -- Manually fix certain situations.
                local has_awk = 1
                if not hidden('awk') then
                    has_awk = 2
                 end
                 end
            end
            all_list = new_list
        end


        return all_list
                local extra = 1
    end
                if hidden('awk') and not hidden('passives_switch') then
                    extra = 2
                end


    function doInitialCell(new_row)
                loop_factor = loop_factor * has_awk
        return new_row:tag('th'):wikitext('Mode')
                for i = 1, loop_factor * extra, 1 do
    end
                    local ix = 1
                    new(data.normal, true)
                    for k, trait_name in ipairs(trait_args) do
                        if data[trait_name] ~= nil then
                            new(STR.TRAIT[ix], false)
                        end
                        ix = ix + 1
                    end
                end
            end


    function doHeaders()
            if (type == 'hit_count' and not hide) then
        local current_multiplier = 0 -- Keeps track of the number of cells to spawn
                if no_max and levels_exist then
        local initial_header_cell    -- The leftmost cell that says "Mode"
                    increaseSpace(mode_th, 'row')
        local iterations = 0        -- Keeps track of iterations that successfully rendered something. Required to tell the initial cell how many columns to span.
                elseif levels_exist or (args.avg_hits and args.hits) then
                    reverseMultiplySpace();
                else
                    reverseMultiplySpace(nil, false);
                end
                local avg_or_each = 'avg'
                if inArgs('count') and not args.use_avg then
                    avg_or_each = 'each'
                end
                table.insert(hit_count_table, 1, avg_or_each)


        for row_index, row in ipairs(TABLE_CONTENT) do
                -- Some things are breaking here, so I needed to implement conditional patches.
            if row.is_visible then
                if hidden('awk') then
                local new_row = TABLE:new()
                    loop_factor = loop_factor * (passive_switch_count + 1)
                 local next_multiplier = 0
                 end


                -- Only spawn the initial cell in the first generated row.
                 if hidden('passives_switch') then
                 if iterations == 0 and not initial_header_cell then
                     loop_factor = passive_normal_count + 1
                     initial_header_cell = doInitialCell(new_row)
                 end
                 end


                 --[[
                 if (hidden('traits') and not hidden('awk')) or (not hidden('awk') and hidden('passives_switch')) then
                We need to know how the colspan will look like.
                    loop_factor = loop_factor * 2
                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.
                 loop_factor = loop_factor * (trait_count + 1)
                 for i = 1, (current_multiplier == 0 and 1 or current_multiplier), 1 do
 
                    for _, text in ipairs(row.text) do
                 if not no_max then
                         local new_cell = new_row:tag('th')
                    for i = 1, loop_factor, 1 do
                        new_cell:attr('colspan', colspan_value):wikitext(text)
                        new(data.avg, true)
                        next_multiplier = next_multiplier + 1
                         new(data.max, false)
                     end
                     end
                 end
                 end
                current_multiplier = next_multiplier
                iterations = iterations + 1
             end
             end
         end
 
        -- Apply rowspan of the same value as iteration count.
         until true
         initial_header_cell:attr('rowspan', iterations)
    end
 
    if no_max and args.avg_hits then
         hit_count_table[2] = nil
     end
     end


     -- Helper function to display ranges.
     function concat(tbl)
    function doRangeText(damage_number)
        local returned_str = ''
        if damage_number and damage_number.min == damage_number.max then
        for k, v in ipairs(tbl) do
            damage_number = damage_number.min
            local delimiter = '_'
        elseif damage_number then
            if returned_str == '' then
             damage_number = damage_number.min ..
                delimiter = ''
                 '<span style="white-space: nowrap;"> ~</span> ' .. damage_number.max
            end
             if v ~= '' then
                 returned_str = returned_str .. delimiter .. v
            end
         end
         end
         return damage_number
         return returned_str
     end
     end


     function doContentByMode(mode)
     function makeValueRows(mode_flag)
         local mode_row = TABLE:new()
         local mode_cell = 'PvE'
         mode_row:tag('td'):wikitext(frame:expandTemplate { title = mode })
         if mode_flag == true then
         local damage_entries = returnDamageInOrder()
            mode_flag = '_pvp'
        local last_number
            mode_cell = 'PvP'
         local last_unique_cell
         else
            mode_flag = ''
         end


         for _, damage_key in ipairs(damage_entries) do
         local value_row = tbl:tag('tr')
            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
        function display(name, range_flag)
                    -- Display ranges.
            local range_factor;
                    local new_cell = mode_row:tag('td'):wikitext(damage_number
            local cell_content = {}
                         -- Error out if it doesn't exist
            if range_flag == true then
                         or frame:expandTemplate {
                range_factor = 2
                            title = 'color',
            else
                            args = { 'red', '&#35;ERROR' }
                range_factor = 1
                         })
            end
                     last_unique_cell = new_cell
            for i = 1, range_factor, 1 do
                else
                local range_prefix = '';
                    last_unique_cell:attr('colspan', (last_unique_cell:getAttr('colspan') or 1) + 1)
                if range_flag == true then
                    if i == 1 then
                         range_prefix = 'min_'
                    else
                         range_prefix = 'max_'
                    end
                end
                local value = out[range_prefix .. name];
                if (value ~= nil) then
                    if (args.dump_names == 'true') then
                         table.insert(cell_content, name)
                     elseif value ~= 0 then
                        table.insert(cell_content, formatnum(math.round(value, 2)) .. '%')
                    else
                        table.insert(cell_content, 'N/A')
                    end
                 end
                 end
                 last_number = damage_number
            end
            if next(cell_content) then
                 return value_row:tag('td'):wikitext(table.concat(cell_content, '<span style="white-space:nowrap"> ~</span> '));
             else
             else
                 mode_row:tag('td'):wikitext(damage_key)
                 if args.dump_names == 'true' then
                    return value_row:tag('td'):wikitext(name)
                end
                return value_row:tag('td'):wikitext(frame:expandTemplate{
                    title = 'color',
                    args = {'red', '&#35;ERROR'}
                })
            end
 
        end
 
        local ret2 = ''
 
        value_row:tag('td'):wikitext(frame:expandTemplate{
            title = mode_cell
        })
 
        for passive_normal_i = 0, 3, 1 do
            local combine_now = tostring(passive_normal_i) == combine[1]
            if tbl_content.passives_normal[passive_normal_i] or combine_now or passive_normal_i == 0 then
 
                local passive_normal_str = 'passive' .. passive_normal_i
                if combine_now then
                    for k, v in ipairs(combine) do
                        if k ~= 1 then
                            passive_normal_str = passive_normal_str .. '_passive' .. v
                        end
                    end
                end
                if passive_normal_i == 0 then
                    passive_normal_str = ''
                end
                for passive_switch_i = 0, 3, 1 do
                    local current_switch = tbl_content.passives_switch[passive_switch_i]
                    if current_switch or passive_switch_i == 0 then
                        local passive_switch_str = 'passive' .. passive_switch_i
                        if passive_switch_i == 0 then
                            passive_switch_str = ''
                        end
                        for _, awk_str in ipairs(awk_table) do
                            for trait_i = 0, #trait_args do
                                repeat
                                    for hit_i, hit_v in ipairs(hit_count_table) do
                                        local trait_str = trait_args[trait_i] or ''
                                        local str_tbl = {'damage'}
                                        local passive_tbl = {}
                                        table.insert(str_tbl, awk_str)
                                        if tbl_content.traits[trait_str] then
                                            table.insert(str_tbl, trait_str)
                                        elseif trait_i > 0 then
                                            do
                                                break
                                            end
                                        end
                                        table.insert(str_tbl, 1, hit_v)
                                        table.insert(passive_tbl, passive_switch_str)
                                        table.insert(passive_tbl, passive_normal_str)
 
                                        table.sort(passive_tbl)
 
                                        for k, v in ipairs(passive_tbl) do
                                            table.insert(str_tbl, v)
                                        end
 
                                        if inArgs('range_max') then
                                            display(concat(str_tbl) .. mode_flag, true)
                                        else
                                            display(concat(str_tbl) .. mode_flag)
                                        end
                                    end
                                until true
                            end
                        end
                    end
                end
             end
             end
         end
         end
     end
     end


     function doTable()
     -- For debugging purposes
         doHeaders()
    if (args.debug == 'true') then
        forEach(doContentByMode)
         ret = ''
    end
        for i = 1, #tbl_order, 1 do
            ret = ret .. "'''" .. tbl_order[i] .. "''': <br/>"
            for k2, v2 in pairs(tbl_content[tbl_order[i]]) do
                if (v2 == true) then
                    v2 = 'true'
                end
                local output = tostring(v2)
                if (type(v2) == 'table') then
                    output = ''
                    output = output .. '<br/>--<br/>'
                    for k3, v3 in pairs(v2) do
                        output = output .. k3 .. ': ' .. tostring(v3) .. '<br/>'
                    end
                    output = output .. '--'
                end
                ret = ret .. k2 .. ': ' .. output .. '<br/>'
            end
            ret = ret .. '<br/>'
        end


    if OPTIONS.do_table then
         return ret
         doTable()
     end
     end


     -- Dump all values if wanted.
     makeValueRows();
    if OPTIONS.dump_table_data then
     makeValueRows(true);
        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 bug = ''
     if OPTIONS.bug then
     if args.bug == 'true' then
         bug = frame:expandTemplate {
         bug = frame:expandTemplate{
             title = 'SkillText',
             title = 'SkillText',
             args = { 'FreeTraining' }
             args = {'FreeTraining'}
         }
         }
     end
     end


     -- Transform into variables
     return parsed .. bug .. tostring(tbl)
    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