Module:Test: Difference between revisions

From Elwiki
(Bot: Automated import of articles *** existing text overwritten ***)
No edit summary
Line 1: Line 1:
require('Module:CommonFunctions');
-- pystart
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 = {}


-- Main process
-- Main process
function p.main(frame)
function p.main(frame)
     local args = getArgs(frame)
     local args = getArgs(frame);
     local out
     local is_rose = args[1] == 'Rose' and args.ecp == 'true';


     function inArgs(key)
     -- Argument init
        if args[key] ~= nil then
    local traits = args[3] or args.traits
            return true
    if (traits == nil) then
         end
         traits = '-, -'
     end
     end
 
     traits = split(traits);
     local modes = { 'PvE', 'PvP' }
     for k, v in ipairs(traits) do
 
         traits[k] = trim(v)
     -- Define the schema for the table
    local tableSchema = {}
    for _, mode in ipairs(modes) do
         tableSchema[mode] = {}
     end
     end
    local skill = args[2] or args.skill
    local default_multiplier = 100


     function forEach(func)
     -- Dictionary for headers
         for _, mode in ipairs(modes) do
    local prop = { 'MP Usage', 'Cooldown', 'Duration', 'MP Recovery', 'Max Hits', 'Effects' }
             func(mode)
    if is_rose then
         for k, v in ipairs(prop) do
             prop[k] = v:gsub('MP', 'ECP')
         end
         end
     end
     end


     -- Function to create a new table with the desired schema
     local prop_short = { 'mp', 'cd', 'duration', 'mp_recovery', 'hits', 'effects', 'chance' }
    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 STR = {
     local OPTIONS = {
         LIGHT = 'Light',
         do_table = args[1] == 'true',
         CRITICAL = 'Critical',
         character = args[2] or args.char or 'Elsword',
         REVERSED = 'Reversed',
         format = args.format ~= 'false',
         HEAVY = 'Heavy',
         no_max = args.no_max == 'true',
         HASTE = 'Haste',
         is_append = args.append ~= nil,
         REGEN1 = 'Regenerating (1)',
         append_index = args.append and tonumber(split(args.append)[1]),
         REGEN2 = 'Regenerating (2)',
         append_name = args.append and split(args.append)[2],
         KB1 = 'Killing Blow (1)',
         combine_suffix = args.combine_suffix and (' ' .. args.combine_suffix) or '',
         KB2 = 'Killing Blow (2)',
         combine = (function()
         RUTHLESS = 'Ruthless',
            local output = {}
         POWERFUL = 'Powerful',
            if not args.combine then
         USEFUL = 'Useful',
                return nil
         SEC = ' Seconds',
            end
         MP = ' MP',
            for _, passive_key in ipairs(split(args.combine)) do
         ECP = ' ECP',
                table.insert(output, tonumber(passive_key))
         PERSISTENT = 'Persistent',
            end
         PERSISTENT2 = 'Persistent2'
            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 details = {
     local BASIC_DAMAGE = createDamageDataTable()
        { STR.LIGHT, STR.CRITICAL, STR.REVERSED },
        { STR.HEAVY, STR.HASTE,    STR.REGEN2,  STR.RUTHLESS, STR.POWERFUL, STR.REVERSED },
        { STR.KB1 },
        { STR.REGEN1 },
        { STR.USEFUL, STR.PERSISTENT2 },
        { STR.KB2 }
     }


     -- Define a table with trait names and their values to apply.
     -- Default values
     local TRAITS = {
     local DEFAULT = {
         -- An empty trait so we keep the original values there.
         [STR.HEAVY] = {
        {
             cd = 120
             key = '',
            name = 'Normal',
            value = 1
         },
         },
         {
         [STR.LIGHT] = {
             key = 'enhanced',
             mp = 80
            name = 'Enhanced (Trait)',
            value = args.enhanced ~= nil and 0.8
         },
         },
         {
         [STR.CRITICAL] = {
             key = 'empowered',
             mp = (args['def_ignore1'] ~= nil or args['def_ignore2'] ~= nil) and 100 or 120
            name = 'Empowered',
            value = args.empowered == 'true' and 1.2 or tonumber(args.empowered) or false
         },
         },
         {
         [STR.HASTE] = {
             key = 'useful',
             cd = 80
            name = 'Useful',
            value = (args.hits_useful or args.avg_hits_useful) and (args.useful_penalty or args.useful or 0.8) or false
         },
         },
         {
         [STR.RUTHLESS] = {
            key = 'heavy',
             cd = 200
            name = 'Heavy',
            value = args.heavy ~= nil and 1.44
        }
    }
 
    function eval(s)
        return frame:preprocess('{{#expr:' .. s .. '}}')
    end
 
    -- A table with user-requested passive skills (empty by default).
    local PASSIVES = {}
    -- 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 '',
                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
 
    -- Set basic hit count to 1 for all damage.
    for k, v in ipairs(args.dmg) do
        if not args.hits then
            args.hits = {}
        end
        if not args.hits[k] then
            args.hits[k] = 1
        end
    end
 
    -- Set basic hit count to 1 for all cancel damage.
    if args.cancel_dmg then
        for k, v in ipairs(args.cancel_dmg) do
            if not args.cancel_hits then
                args.cancel_hits = {}
            end
            if not args.cancel_hits[k] then
                args.cancel_hits[k] = 1
            end
        end
    end
 
    -- Store a configuration that will tell the main function how to behave given different inputs.
    -- It will always take the first value if available. If not, fall back to the other (recursively).
    local BASE_DAMAGE_CONFIG = {
        total_damage = {
            damage_numbers = { 'dmg' },
            hit_counts = { 'hits' },
            provided = { 'dmg' }
         },
         },
         total_damage_awk = {
         [STR.POWERFUL] = {
             damage_numbers = { 'awk_dmg', 'dmg' },
             cd = 150
            hit_counts = { 'awk_hits', 'hits' },
            provided = { 'awk_dmg', 'awk_hits' }
         },
         },
         avg_damage = {
         [STR.REGEN1] = {
             damage_numbers = { 'dmg' },
             chance = 50,
            hit_counts = { 'avg_hits', 'hits' },
             mp = 50
             provided = { 'avg_hits' }
         },
         },
         avg_damage_awk = {
         [STR.REGEN2] = {
             damage_numbers = { 'awk_dmg', 'dmg' },
             chance = 50,
            hit_counts = { 'avg_awk_hits', 'awk_hits', 'avg_hits', 'hits' },
             cd = 50
             provided = { 'avg_awk_hits', args.awk_dmg and 'avg_hits' or nil }
         },
         },
         -- Store the logic for Useful traits
         [STR.REVERSED] = {
        total_damage_useful = {
             mp = 60,
             damage_numbers = { 'dmg' },
             cd = 150
             hit_counts = { 'hits_useful', 'hits' },
            provided = { 'hits_useful' }
         },
         },
         total_damage_awk_useful = {
         [STR.USEFUL] = {
             damage_numbers = { 'awk_dmg', 'dmg' },
             dmg = 80
            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' }
         },
         },
        [STR.PERSISTENT2] = {
            dmg = 100
        }
     }
     }


     local DAMAGE_CONFIG = {}
     local function color(char)
    function handleCancel()
         char = char or args[1] or args.char or 'Elsword'
         local processed_keys = {}
         return char:gsub('/', '')
         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
        return processed_keys
     end
     end


     if args.cancel_dmg then
     local trait_table = mw.html.create('div'):attr('class', 'content-table'):tag('table'):attr({
         handleCancel()
        cellpadding = '5',
         DAMAGE_CONFIG = table.fuse(BASE_DAMAGE_CONFIG, DAMAGE_CONFIG)
         border = '1',
     else
        class = 'colortable-' .. color()
         DAMAGE_CONFIG = BASE_DAMAGE_CONFIG
    }):css({
         ['border-collapse'] = 'collapse',
        ['text-align'] = 'center'
    })
 
     function new_row()
         return trait_table:tag('tr')
     end
     end


     -- Inherits values from args if not provided, but usage suggests that they're meant to be generated.
     -- headers
     function inherit(mode)
     local tr1 = new_row()
        local prefix = mode == 'PvE' and '' or string.lower(mode .. '_')
    local tr2 = new_row()
        for config_key, config_value in pairs(DAMAGE_CONFIG) do
    local tr3 = new_row()
            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.
    local has_pvp_values = false
                    if main_arg_values then
    for k, v in pairs(args) do
                        local i = 1
        if string.find(k, '_pvp') then
                        --[[
            has_pvp_values = true
                            Loop over all damage and attempt to inherit in chain.
            break
                            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]
                                -- 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
     end


     forEach(inherit)
     local tr4
 
    -- If pvp values exist, we need the row to place them.
     local DAMAGE_PARSED = createDamageDataTable()
     if has_pvp_values and not tr4 then
    function parseConfig(mode)
        tr4 = new_row()
         local prefix = mode == 'PvE' and '' or string.lower(mode .. '_')
        -- Add indicator headers
         for config_key, config_value in pairs(DAMAGE_CONFIG) do
         tr1:tag('th'):wikitext('Mode'):attr('rowspan', 2)
             for k, v in pairs(config_value) do
         tr3:tag('td'):wikitext(frame:expandTemplate {
                local output_value = v
             title = 'PvE'
                for _, v2 in ipairs(v) do
        })
                    local arg_from_template = args[prefix .. v2] or args[v2]
        tr4:tag('td'):wikitext(frame:expandTemplate {
                    if arg_from_template ~= nil then
            title = 'PvP'
                        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


    forEach(parseConfig)
     -- Loop through 2 input traits.
 
     for trait_count, trait_name in ipairs(traits) do
     -- Detected "count", for skills like Clementine, Enough Mineral, etc.
         local th = tr1:tag('th'):wikitext(trait_name:gsub('Persistent2', 'Persistent') .. ' ' .. skill);
     function doEachDamage()
        local th_effect;
         local WITH_EACH = table.deep_copy(DAMAGE_PARSED)
        local th_skilltext;
        for mode, mode_content in pairs(DAMAGE_PARSED) do
        local default_value = DEFAULT[trait_name] or {};
            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
        -- Check if detail fields are required and which.
                    WITH_EACH[mode][damage_key] = new_value
        for detail_key, detail in ipairs(details) do
                 end
            if not th_effect then
                 th_effect = tr2:tag('th'):wikitext('Attribute Effect');
             end
             end
        end
            if indexOf(trait_name, detail) then
        return WITH_EACH
                local th_detail = tr2:tag('th'):wikitext(prop[detail_key]);
    end
                th:attr('colspan', tonumber(th:getAttr('colspan') or 1) + 1)
 
    if args.count then
        DAMAGE_PARSED = doEachDamage()
    end
 
    function doBasicDamage()
        for mode, mode_content in pairs(DAMAGE_PARSED) do
            for damage_key, damage_value in pairs(mode_content) do
                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
    end


    doBasicDamage()
        -- Unnamed argument.
        local unnamed = split(args[3 + trait_count]);
        local MP_ARG = args['mp' .. trait_count] or args['mp_recovery' .. trait_count] or default_value['mp_recovery'] or
            default_value['mp'];
        local ECP_ARG = nil


    -- Adding missing cancel part damage to full, so that repetition wouldn't be a problem.
         if is_rose then
    function addCancelDamage()
             unnamed[2] = unnamed[1] or args['mp' .. trait_count]
         for mode, mode_content in pairs(BASIC_DAMAGE) do
            ECP_ARG = unnamed[1] or args['mp' .. trait_count]
             for damage_key, damage_value in pairs(mode_content) do
             MP_ARG = nil
                local cancel_candidate = BASIC_DAMAGE[mode]['cancel_' .. damage_key]
                if string.find(damage_key, 'total_') and cancel_candidate then
                    BASIC_DAMAGE[mode][damage_key] = damage_value + cancel_candidate
                end
             end
         end
         end
    end
    if args.cancel_dmg then
        addCancelDamage()
    end


    local WITH_TRAITS = createDamageDataTable()
         -- Append contents.
    function doTraits()
         for detail_key, detail in ipairs(details) do
         -- Handle traits here
             if not th_skilltext then
         for mode, mode_content in pairs(BASIC_DAMAGE) do
                 th_skilltext = tr3:tag('td'):attr('rowspan', has_pvp_values and 2 or 1):wikitext(frame:expandTemplate {
             for damage_key, damage_value in pairs(mode_content) do
                     title = 'SkillText',
                 for _, trait in pairs(TRAITS) do
                     args = {
                    --[[
                        trait_name,
                    Suffix all damage values with existing traits.
                        unnamed[1],
                    Useful already has the prefix, so only multiply with its value.
                        unnamed[2],
                     Also, we don't want other traits to multiply with Useful,
                        MP = args['def_ignore' .. trait_count] ~= nil and 'Energy' or MP_ARG,
                     so we skip those situations, as impossible in-game.
                        ECP = ECP_ARG,
                    --]]
                        CD = args['cd' .. trait_count] or default_value['cd'],
                    if (trait.value and trait.key ~= 'useful') or (string.find(damage_key, 'useful') and trait.key == 'useful') then
                         DURATION = args['duration' .. trait_count],
                         WITH_TRAITS[mode][damage_key .. ((trait.key == 'useful' or trait.key == '') and "" or ('_' .. trait.key))] =
                        CHANCE = args['chance' .. trait_count] or default_value['chance'],
                            damage_value * trait.value
                        DAMAGE = args['dmg' .. trait_count] or default_value['dmg'],
                     end
                        DEF_IGNORE = args['def_ignore' .. trait_count],
                 end
                        DEF_IGNORE_PVP = args['pvp_def_ignore' .. trait_count]
                     }
                 });
             end
             end
        end
    end


    doTraits()
            function doDetail()
                for _, modeSuffix in ipairs({ '', '_pvp' }) do
                    if indexOf(trait_name, detail) then
                        local short_detail = prop_short[detail_key]
                        local short_detail_improved = short_detail
                        local is_pvp = modeSuffix == '_pvp'


    local WITH_PASSIVES = createDamageDataTable()
                        -- Fix Regen 1.
                        if short_detail == 'mp_recovery' then
                            short_detail_improved = 'mp'
                        end


    --[[
                        local detail_content = args[short_detail_improved .. trait_count]
    Generates passives with every possible combinations of all subsets.
                         local multiplier = detail_content or default_value[short_detail_improved] or 100
    For example: 3 passives are given, so it will generate the following:
                         local suffix = ''
    (1), (2), (3), (1, 2), (1, 3), (1, 2, 3), (2, 3)
                         if short_detail_improved == 'mp' then
    ]]
                            suffix = STR.MP
    function doPassives()
                            if is_rose then
        for mode, mode_content in pairs(WITH_TRAITS) do
                                suffix = STR.ECP
            for damage_key, damage_value in pairs(mode_content) do
                             end
                local combinations = { {} }
                        elseif short_detail == 'cd' or short_detail == 'duration' then
                for passive_key, passive in pairs(PASSIVES) do
                             suffix = STR.SEC
                    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
                    end
                    WITH_PASSIVES[mode][damage_key .. name_suffix] = damage_value * passive_multiplier
                end
            end
        end
    end


    doPassives()
                        local base_detail = args[short_detail_improved .. modeSuffix]


    local RANGE = {
                        local function calcEndValue(base)
        min_count = args.range_min_count and args.range_min_count[1] or 1,
                            local end_value;
        max_count = args.range_max_count and args.range_max_count[1] or 1,
                            if base and tonumber(base) == nil then
        PvE = {
                                end_value = base
            min = args.range_min and args.range_min[1] or 1,
                            else
            max = args.range_max and args.range_max[1] or 1
                                end_value = (tonumber(multiplier) / 100) * tonumber(base or -1)
        },
                            end
        PvP = {
                            if tonumber(end_value) ~= nil and tonumber(end_value) < 0 then
            min = args.range_min and (args.range_min[2] or args.range_min[1]) or 1,
                                end_value = '-'
            max = args.range_max and (args.range_max[2] or args.range_max[1]) or 1
                            end
        }
                            return end_value .. suffix
    }
                        end
 
    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
 
    doDamageBuffRange()
 
    local FINAL_DAMAGE = WITH_RANGE
 
    -- Helper function to iterate over traits.
    function checkTraits(settings)
        local output
        if not settings then
            output = false
        else
            output = settings.output or {}
        end
 
        for trait_index, trait in ipairs(TRAITS) do
            if trait.value ~= false and trait_index ~= 1 then
                if settings and type(settings.action) == 'function' then
                    settings.action(trait, output, settings)
                else
                    return true
                end
            end
        end
        return output
    end
 
    -- Helper function to iterate over passives.
    function checkPassives(settings)
        local output = settings.output or {}
        local PASSIVES_WITH_COMBINED = table.deep_copy(PASSIVES)
 
        -- Handle combined passives properly.
        if OPTIONS.combine then
            table.insert(PASSIVES_WITH_COMBINED, {
                is_combined = true
            })
        end
 
        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
        return output
    end


    -- Generate the table
                        local end_value = calcEndValue(base_detail)
    local TABLE = mw.html.create('table'):attr({
        cellpadding = 5,
        border = 1,
        style = 'border-collapse: collapse; text-align: center',
        class = 'colortable-' .. OPTIONS.character
    })


    -- Our table structure
                        local enhanced_detail = args[short_detail_improved .. modeSuffix .. '_enhanced']
    local TABLE_CONTENT = {
                        if enhanced_detail ~= nil then
        {
                            end_value = end_value .. "<br/>'''" .. frame:expandTemplate {
            type = 'extra',
                                title = 'Tt',
            text = { 'Average' },
                                args = { calcEndValue(enhanced_detail) .. "'''",
            is_visible = OPTIONS.no_max,
                                    "Final Enhanced Skill" }
            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.suffix, passive.exist))
                         end
                         end
                        table.insert(output, table.concat(combo, '/') .. OPTIONS.combine_suffix)
                    else
                        table.insert(output, link(passive.name, passive.alias, 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].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',
                        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 TABLE:new()
                        if (is_pvp and base_detail) or not is_pvp then
        return self:tag('tr')
                            local detail_text = end_value
    end
                            if end_value == '-' and args['detail' .. trait_count] then
 
                                detail_text = args
    function returnDamageInOrder()
                                    ['detail' .. trait_count]
        local main_key = 'damage'
                            end
        local all_list = {}
 
        -- Initialize current list with main key
        local current_list = { main_key }


        for i = #TABLE_CONTENT, 1, -1 do
                            if end_value ~= '-' then
            local current_row = TABLE_CONTENT[i]
                                local detail_cell = (is_pvp and tr4 or tr3):tag('td'):wikitext(detail_text):attr('rowspan', has_pvp_values and (not args[short_detail_improved .. '_pvp']) and 2 or 1);
            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
                             end
                            table.insert(new_list, new_key)
                         end
                         end
                     end
                     end
                 end
                 end
                -- Append new_list to all_list
                for _, new_key in ipairs(new_list) do
                    table.insert(all_list, sortPassives(new_key))
                end
            end
        end
        -- 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
        return all_list
    end
    function doInitialCell(new_row)
        return new_row:tag('th'):wikitext('Mode')
    end
    function doHeaders()
        local current_multiplier = 0 -- Keeps track of the number of cells to spawn
        local initial_header_cell    -- The leftmost cell that says "Mode"
        local iterations = 0        -- Keeps track of iterations that successfully rendered something. Required to tell the initial cell how many columns to span.
        for row_index, row in ipairs(TABLE_CONTENT) do
            if row.is_visible then
                local new_row = TABLE:new()
                local next_multiplier = 0
                -- 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
        end
        -- Apply rowspan of the same value as iteration count.
        initial_header_cell:attr('rowspan', iterations)
    end
    -- 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
    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
             doDetail()
                    -- 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', '&#35;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


    function doTable()
     return tostring(trait_table);
        doHeaders()
        forEach(doContentByMode)
    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 = ''
    if OPTIONS.bug then
        bug = frame:expandTemplate {
            title = 'SkillText',
            args = { 'FreeTraining' }
        }
    end
 
    -- Transform into variables
    local variables = doVariables(frame, FINAL_DAMAGE, OPTIONS.prefix)
 
    if out ~= nil then
        return inspect_dump(frame, out)
    end
 
     return variables .. bug .. (OPTIONS.do_table and tostring(TABLE) or '')
end
end


return p
return p
-- pyend

Revision as of 18:26, 2 August 2023

Documentation for this module may be created at Module:Test/doc

-- pystart
require('Module:CommonFunctions')
local getArgs = require('Module:Arguments').getArgs
local p = {}

-- Main process
function p.main(frame)
    local args = getArgs(frame);
    local is_rose = args[1] == 'Rose' and args.ecp == 'true';

    -- Argument init
    local traits = args[3] or args.traits
    if (traits == nil) then
        traits = '-, -'
    end
    traits = split(traits);
    for k, v in ipairs(traits) do
        traits[k] = trim(v)
    end
    local skill = args[2] or args.skill
    local default_multiplier = 100

    -- Dictionary for headers
    local prop = { 'MP Usage', 'Cooldown', 'Duration', 'MP Recovery', 'Max Hits', 'Effects' }
    if is_rose then
        for k, v in ipairs(prop) do
            prop[k] = v:gsub('MP', 'ECP')
        end
    end

    local prop_short = { 'mp', 'cd', 'duration', 'mp_recovery', 'hits', 'effects', 'chance' }

    local STR = {
        LIGHT = 'Light',
        CRITICAL = 'Critical',
        REVERSED = 'Reversed',
        HEAVY = 'Heavy',
        HASTE = 'Haste',
        REGEN1 = 'Regenerating (1)',
        REGEN2 = 'Regenerating (2)',
        KB1 = 'Killing Blow (1)',
        KB2 = 'Killing Blow (2)',
        RUTHLESS = 'Ruthless',
        POWERFUL = 'Powerful',
        USEFUL = 'Useful',
        SEC = ' Seconds',
        MP = ' MP',
        ECP = ' ECP',
        PERSISTENT = 'Persistent',
        PERSISTENT2 = 'Persistent2'
    }

    local details = {
        { STR.LIGHT, STR.CRITICAL, STR.REVERSED },
        { STR.HEAVY, STR.HASTE,    STR.REGEN2,  STR.RUTHLESS, STR.POWERFUL, STR.REVERSED },
        { STR.KB1 },
        { STR.REGEN1 },
        { STR.USEFUL, STR.PERSISTENT2 },
        { STR.KB2 }
    }

    -- Default values
    local DEFAULT = {
        [STR.HEAVY] = {
            cd = 120
        },
        [STR.LIGHT] = {
            mp = 80
        },
        [STR.CRITICAL] = {
            mp = (args['def_ignore1'] ~= nil or args['def_ignore2'] ~= nil) and 100 or 120
        },
        [STR.HASTE] = {
            cd = 80
        },
        [STR.RUTHLESS] = {
            cd = 200
        },
        [STR.POWERFUL] = {
            cd = 150
        },
        [STR.REGEN1] = {
            chance = 50,
            mp = 50
        },
        [STR.REGEN2] = {
            chance = 50,
            cd = 50
        },
        [STR.REVERSED] = {
            mp = 60,
            cd = 150
        },
        [STR.USEFUL] = {
            dmg = 80
        },
        [STR.PERSISTENT2] = {
            dmg = 100
        }
    }

    local function color(char)
        char = char or args[1] or args.char or 'Elsword'
        return char:gsub('/', '')
    end

    local trait_table = mw.html.create('div'):attr('class', 'content-table'):tag('table'):attr({
        cellpadding = '5',
        border = '1',
        class = 'colortable-' .. color()
    }):css({
        ['border-collapse'] = 'collapse',
        ['text-align'] = 'center'
    })

    function new_row()
        return trait_table:tag('tr')
    end

    -- headers
    local tr1 = new_row()
    local tr2 = new_row()
    local tr3 = new_row()

    local has_pvp_values = false
    for k, v in pairs(args) do
        if string.find(k, '_pvp') then
            has_pvp_values = true
            break
        end
    end

    local tr4
    -- If pvp values exist, we need the row to place them.
    if has_pvp_values and not tr4 then
        tr4 = new_row()
        -- Add indicator headers
        tr1:tag('th'):wikitext('Mode'):attr('rowspan', 2)
        tr3:tag('td'):wikitext(frame:expandTemplate {
            title = 'PvE'
        })
        tr4:tag('td'):wikitext(frame:expandTemplate {
            title = 'PvP'
        })
    end

    -- Loop through 2 input traits.
    for trait_count, trait_name in ipairs(traits) do
        local th = tr1:tag('th'):wikitext(trait_name:gsub('Persistent2', 'Persistent') .. ' ' .. skill);
        local th_effect;
        local th_skilltext;
        local default_value = DEFAULT[trait_name] or {};

        -- Check if detail fields are required and which.
        for detail_key, detail in ipairs(details) do
            if not th_effect then
                th_effect = tr2:tag('th'):wikitext('Attribute Effect');
            end
            if indexOf(trait_name, detail) then
                local th_detail = tr2:tag('th'):wikitext(prop[detail_key]);
                th:attr('colspan', tonumber(th:getAttr('colspan') or 1) + 1)
            end
        end

        -- Unnamed argument.
        local unnamed = split(args[3 + trait_count]);
        local MP_ARG = args['mp' .. trait_count] or args['mp_recovery' .. trait_count] or default_value['mp_recovery'] or
            default_value['mp'];
        local ECP_ARG = nil

        if is_rose then
            unnamed[2] = unnamed[1] or args['mp' .. trait_count]
            ECP_ARG = unnamed[1] or args['mp' .. trait_count]
            MP_ARG = nil
        end

        -- Append contents.
        for detail_key, detail in ipairs(details) do
            if not th_skilltext then
                th_skilltext = tr3:tag('td'):attr('rowspan', has_pvp_values and 2 or 1):wikitext(frame:expandTemplate {
                    title = 'SkillText',
                    args = {
                        trait_name,
                        unnamed[1],
                        unnamed[2],
                        MP = args['def_ignore' .. trait_count] ~= nil and 'Energy' or MP_ARG,
                        ECP = ECP_ARG,
                        CD = args['cd' .. trait_count] or default_value['cd'],
                        DURATION = args['duration' .. trait_count],
                        CHANCE = args['chance' .. trait_count] or default_value['chance'],
                        DAMAGE = args['dmg' .. trait_count] or default_value['dmg'],
                        DEF_IGNORE = args['def_ignore' .. trait_count],
                        DEF_IGNORE_PVP = args['pvp_def_ignore' .. trait_count]
                    }
                });
            end

            function doDetail()
                for _, modeSuffix in ipairs({ '', '_pvp' }) do
                    if indexOf(trait_name, detail) then
                        local short_detail = prop_short[detail_key]
                        local short_detail_improved = short_detail
                        local is_pvp = modeSuffix == '_pvp'

                        -- Fix Regen 1.
                        if short_detail == 'mp_recovery' then
                            short_detail_improved = 'mp'
                        end

                        local detail_content = args[short_detail_improved .. trait_count]
                        local multiplier = detail_content or default_value[short_detail_improved] or 100
                        local suffix = ''
                        if short_detail_improved == 'mp' then
                            suffix = STR.MP
                            if is_rose then
                                suffix = STR.ECP
                            end
                        elseif short_detail == 'cd' or short_detail == 'duration' then
                            suffix = STR.SEC
                        end

                        local base_detail = args[short_detail_improved .. modeSuffix]

                        local function calcEndValue(base)
                            local end_value;
                            if base and tonumber(base) == nil then
                                end_value = base
                            else
                                end_value = (tonumber(multiplier) / 100) * tonumber(base or -1)
                            end
                            if tonumber(end_value) ~= nil and tonumber(end_value) < 0 then
                                end_value = '-'
                            end
                            return end_value .. suffix
                        end

                        local end_value = calcEndValue(base_detail)

                        local enhanced_detail = args[short_detail_improved .. modeSuffix .. '_enhanced']
                        if enhanced_detail ~= nil then
                            end_value = end_value .. "<br/>'''" .. frame:expandTemplate {
                                title = 'Tt',
                                args = { calcEndValue(enhanced_detail) .. "'''",
                                    "Final Enhanced Skill" }
                            }
                        end

                        if (is_pvp and base_detail) or not is_pvp then
                            local detail_text = end_value
                            if end_value == '-' and args['detail' .. trait_count] then
                                detail_text = args
                                    ['detail' .. trait_count]
                            end

                            if end_value ~= '-' then
                                local detail_cell = (is_pvp and tr4 or tr3):tag('td'):wikitext(detail_text):attr('rowspan', has_pvp_values and (not args[short_detail_improved .. '_pvp']) and 2 or 1);
                            end
                        end
                    end
                end
            end

            doDetail()
        end
    end

    return tostring(trait_table);
end

return p
-- pyend