Module:CommonFunctions: Difference between revisions

From Elwiki
No edit summary
Tag: Reverted
(Undo revision 900257 by Ritsu (talk))
Tag: Undo
Line 1: Line 1:
-- pystart
require('Module:CommonFunctions');
local getArgs = require('Module:Arguments').getArgs
local inspect = require('Module:Inspect').inspect
local inspect = require('Module:Inspect').inspect
local p = {}


-- Main process
-- in Array
function p.main(frame)
function inArray(key, array)
     local args = getArgs(frame)
     if array[key] ~= nil then
     local out
        return true
     end
end


    function inArgs(key)
function inArrayStarts(key, array)
         if args[key] ~= nil then
    for k, v in pairs(array) do
         if string.starts(k, key) then
             return true
             return true
         end
         end
     end
     end
end


    local modes = { 'PvE', 'PvP' }
function inArrayHas(key, array)
 
     for k, v in pairs(array) do
     -- Define the schema for the table
         if string.find(k, key) then
    local tableSchema = {}
            return true
    for _, mode in ipairs(modes) do
        end
         tableSchema[mode] = {}
     end
     end
end


    function forEach(func)
function inArrayHasValue(value, array)
        for _, mode in ipairs(modes) do
    for k, v in pairs(array) do
            func(mode)
        if string.find(v, value) then
            return true
         end
         end
     end
     end
end


    -- Function to create a new table with the desired schema
function indexOf(value, array)
    function createDamageDataTable()
    for i, v in ipairs(array) do
        local newTable = {}
        if v == value then
        for key, value in pairs(tableSchema) do
             return i
            if type(value) == "table" then
                newTable[key] = {}
             end
         end
         end
        return newTable
     end
     end
    return nil
end


    -- User requested options
-- String starts
    local OPTIONS = {
function string.starts(String, Start)
        do_table = args[1] == 'true',
    return string.sub(String, 1, string.len(Start)) == Start
        character = args[2] or args.char or 'Elsword',
end
        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.
function string.ends(str, ending)
    local BASIC_DAMAGE = createDamageDataTable()
     return ending == "" or str:sub(-#ending) == ending
 
end
    -- Define a table with trait names and their values to apply.
     local TRAITS = {
        -- An empty trait so we keep the original values there.
        {
            key = '',
            name = 'Normal',
            value = 1
        },
        {
            key = 'enhanced',
            name = 'Enhanced (Trait)',
            value = args.enhanced ~= nil and 0.8
        },
        {
            key = 'empowered',
            name = 'Empowered',
            value = args.empowered == 'true' and 1.2 or tonumber(args.empowered) or false
        },
        {
            key = 'useful',
            name = 'Useful',
            value = (args.hits_useful or args.avg_hits_useful) and (args.useful_penalty or args.useful or 0.8) or false
        },
        {
            key = 'heavy',
            name = 'Heavy',
            value = args.heavy ~= nil and 1.44
        }
    }


    function eval(s)
-- Implement sorted loop through array.
        return frame:preprocess('{{#expr:' .. s .. '}}')
function spairs(t)
    local keys = {}
    for k in pairs(t) do
        keys[#keys + 1] = k
     end
     end
 
     table.sort(keys)
     -- A table with user-requested passive skills (empty by default).
     local i = 0
    local PASSIVES = {}
     return function()
    -- A table with non-numeric arguments to split.
         i = i + 1
     local TO_SPLIT = { 'append', 'awk_alias' }
         if keys[i] then
 
             return keys[i], t[keys[i]]
     for k, v in pairs(args) do
         if string.find(k, 'passive') then
            --[[
            Fix up the passives and put them into a separate table.
            |passive1=... |passive2=... -> { passive1, passive2 }
            --]]
            local passive_name = v
            local is_custom = string.find(k, '_define') ~= nil
            local passive_index = string.match(k, "%d")
            local passive_values = split(is_custom and v or
                frame:preprocess('{{:' .. passive_name .. '}}{{#arrayprint:' .. passive_name .. '}}'));
 
            if is_custom then
                passive_name = passive_values[#passive_values]
                passive_values[#passive_values] = nil
            end
 
            PASSIVES[tonumber(passive_index)] = {
                name = passive_name,
                value = passive_values[1],
                value_pvp = passive_values[2],
                alias = args['alias' .. passive_index] or (passive_index == OPTIONS.append_index and OPTIONS.append_name),
                suffix = args['suffix' .. passive_index] and (' ' .. args['suffix' .. passive_index]) or '',
                prefix = args['prefix' .. passive_index] and (' ' .. args['prefix' .. passive_index]) or '',
                exist = frame:preprocess('{{#ifexist:' .. passive_name .. '|true|false}}') == 'true'
            }
         elseif not string.find(v, '[a-hj-zA-HJ-Z]+') then
             --[[
            Change how args are received.
            dmg = 500, 700, 800 (string) -> dmg = { 500, 700, 800 } (table)
            --]]
            local split_values = split(v)
            -- Perform automatic math on each value.
            for k2, v2 in pairs(split_values) do
                if not string.find(v, '[a-zA-Z]+') then
                    split_values[k2] = eval(v2)
                end
            end
            args[k] = split_values
        elseif inArrayHasValue(k, TO_SPLIT) then
            args[k] = split(v)
         end
         end
     end
     end
end


    -- Set basic hit count to 1 for all damage.
-- Implement merging tables
     for k, v in ipairs(args.dmg) do
function tableMerge(first_table, second_table)
         if not args.hits then
     for k, v in spairs(second_table) do
            args.hits = {}
         first_table[k] = v
        end
        if not args.hits[k] then
            args.hits[k] = 1
        end
     end
     end
end


    -- Set basic hit count to 1 for all cancel damage.
function indexTableMerge(first_table, second_table)
     if args.cancel_dmg then
     for k, v in spairs(second_table) do
        for k, v in ipairs(args.cancel_dmg) do
        table.insert(first_table, v)
            if not args.cancel_hits then
                args.cancel_hits = {}
            end
            if not args.cancel_hits[k] then
                args.cancel_hits[k] = 1
            end
        end
     end
     end
end


    -- Store a configuration that will tell the main function how to behave given different inputs.
-- Implement rounding.
    -- It will always take the first value if available. If not, fall back to the other (recursively).
function round(num, numDecimalPlaces)
     local BASE_DAMAGE_CONFIG = {
     if numDecimalPlaces == nil then
        total_damage = {
         numDecimalPlaces = 2
            damage_numbers = { 'dmg' },
    end
            hit_counts = { 'hits' },
    local mult = 10 ^ (numDecimalPlaces or 0)
            provided = { 'dmg' }
    return math.floor(num * mult + 0.5) / mult
        },
end
         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 = {}
-- Implement string trim.
    function handleCancel()
function trim(s)
        local processed_keys = {}
    return (string.gsub(s, "^%s*(.-)%s*$", "%1"))
        for config_key, config_value in pairs(BASE_DAMAGE_CONFIG) do
end
            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


    if args.cancel_dmg then
-- Implement splitting string to a table.
        handleCancel()
function split(s, delimiter, skip_empty)
        DAMAGE_CONFIG = table.fuse(BASE_DAMAGE_CONFIG, DAMAGE_CONFIG)
     if not s then
     else
         return {}
         DAMAGE_CONFIG = BASE_DAMAGE_CONFIG
     end
     end
 
     local i = 1
    -- Inherits values from args if not provided, but usage suggests that they're meant to be generated.
    if delimiter == nil then
     function inherit(mode)
        delimiter = ','
        local prefix = mode == 'PvE' and '' or string.lower(mode .. '_')
        for config_key, config_value in pairs(DAMAGE_CONFIG) do
            for arg_table_key, arg_table in pairs(config_value) do
                if arg_table_key ~= 'provided' and arg_table then
                    -- We only do this for the first (main) key
                    local main_key = arg_table[1]
                    local main_key_prefixed = prefix .. main_key
                    local main_arg_values = args[main_key_prefixed]
 
                    -- Only if the main argument values exist.
                    if main_arg_values then
                        local i = 1
                        --[[
                            Loop over all damage and attempt to inherit in chain.
                            Break the loop if a match was found. Note: For this to work, the value must be an empty string.
                            Alternatively, it can contain an "i" to template the value to inherit.
                        ]]
                        local cancel_dmg_len = args.cancel_dmg and #(args.cancel_dmg) or 0
                        while i <= (#(args.dmg) + cancel_dmg_len) do
                            local main_arg_value = main_arg_values[i]
 
                            for ix, inherit_key in ipairs(arg_table) do
                                local inherit_arg = args[prefix .. inherit_key] or args[inherit_key]
                                -- 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
 
     result = {};
    forEach(inherit)
     for match in (s .. delimiter):gmatch("(.-)" .. delimiter) do
 
        if (skip_empty == true and trim(match) ~= '') or skip_empty == nil then
     local DAMAGE_PARSED = createDamageDataTable()
            table.insert(result, i, trim(match));
     function parseConfig(mode)
            i = i + 1
        local prefix = mode == 'PvE' and '' or string.lower(mode .. '_')
        for config_key, config_value in pairs(DAMAGE_CONFIG) do
            for k, v in pairs(config_value) do
                local output_value = v
                for _, v2 in ipairs(v) do
                    local arg_from_template = args[prefix .. v2] or args[v2]
                    if arg_from_template ~= nil then
                        output_value = arg_from_template
                        if k == 'provided' then
                            output_value = true
                            -- Do not generate total_damage values at all if the skill can't reach them.
                            if string.find(config_key, 'total_') and OPTIONS.no_max then
                                output_value = false
                            end
                        end
                        break
                    else
                        if k == 'provided' then
                            output_value = false
                        else
                            output_value = {}
                        end
                    end
                end
                if DAMAGE_PARSED[mode][config_key] == nil then
                    DAMAGE_PARSED[mode][config_key] = {}
                end
                DAMAGE_PARSED[mode][config_key][k] = output_value
            end
         end
         end
     end
     end
    return result;
end


     forEach(parseConfig)
-- Title case
function titleCaseFunc(first, rest)
     return first:upper() .. rest:lower()
end


    -- Detected "count", for skills like Clementine, Enough Mineral, etc.
function titleCase(str)
    function doEachDamage()
    return string.gsub(str, "(%a)([%w_']*)", titleCaseFunc)
        local WITH_EACH = table.deep_copy(DAMAGE_PARSED)
end
        for mode, mode_content in pairs(DAMAGE_PARSED) do
            for damage_key, damage_value in pairs(mode_content) do
                if string.find(damage_key, 'total_') then
                    local new_value = table.deep_copy(damage_value)


                    for k, hit_count in ipairs(new_value.hit_counts) do
function table.containsKey(tbl, key)
                        hit_count = hit_count == '' and 1 or hit_count
    return tbl[key] ~= nil
                        new_value.hit_counts[k] = hit_count *
end
                            ((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
function table.containsValue(tbl, val)
                    WITH_EACH[mode][damage_key] = new_value
    local contains = false
                end
    for k, v in pairs(tbl) do
             end
        if (v == val) then
            contains = true
             break
         end
         end
        return WITH_EACH
     end
     end
    return contains
end


    if args.count then
function table.matches(tbl, val)
        DAMAGE_PARSED = doEachDamage()
     local matches = 0
     end
     for k, v in pairs(tbl) do
 
        if (type(v) == 'table') then
     function doBasicDamage()
            matches = matches + table.matches(v, val)
        for mode, mode_content in pairs(DAMAGE_PARSED) do
        else
            for damage_key, damage_value in pairs(mode_content) do
            if (v == val) then
                local i = 1
                matches = matches + 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
     end
    return matches
end


    doBasicDamage()
function dump(o)
 
     if type(o) == 'table' then
     -- Adding missing cancel part damage to full, so that repetition wouldn't be a problem.
        local s = '{ <br>'
    function addCancelDamage()
         for k, v in pairs(o) do
         for mode, mode_content in pairs(BASIC_DAMAGE) do
             if type(k) ~= 'number' then
             for damage_key, damage_value in pairs(mode_content) do
                 k = '"' .. k .. '"'
                 local cancel_candidate = BASIC_DAMAGE[mode]['cancel_' .. damage_key]
                if not string.find(damage_key, 'cancel_') and cancel_candidate then
                    BASIC_DAMAGE[mode][damage_key] = damage_value + cancel_candidate
                end
             end
             end
            s = s .. '[' .. k .. '] = ' .. dump(v) .. ',<br>'
         end
         end
        return s .. '}'
    else
        return tostring(o)
     end
     end
end


    if args.cancel_dmg then
function table.not_empty(tbl)
        addCancelDamage()
    return not next(tbl)
    end
end


    local WITH_TRAITS = createDamageDataTable()
function math.round(num, decimals)
    function doTraits()
    decimals = math.pow(10, decimals or 0)
        -- Handle traits here
    num = num * decimals
        for mode, mode_content in pairs(BASIC_DAMAGE) do
    if num >= 0 then
            for damage_key, damage_value in pairs(mode_content) do
        num = math.floor(num + 0.5)
                for _, trait in pairs(TRAITS) do
    else
                    --[[
        num = math.ceil(num - 0.5)
                    Suffix all damage values with existing traits.
                    Useful already has the prefix, so only multiply with its value.
                    Also, we don't want other traits to multiply with Useful,
                    so we skip those situations, as impossible in-game.
                    --]]
                    if (trait.value and trait.key ~= 'useful') or (string.find(damage_key, 'useful') and trait.key == 'useful') then
                        WITH_TRAITS[mode][damage_key .. ((trait.key == 'useful' or trait.key == '') and "" or ('_' .. trait.key))] =
                            damage_value * trait.value
                    end
                end
            end
        end
     end
     end
    return num / decimals
end


    doTraits()
function formatnum(amount)
 
     local formatted = amount
     local WITH_PASSIVES = createDamageDataTable()
     while true do
 
        formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2')
     --[[
         if (k == 0) then
    Generates passives with every possible combinations of all subsets.
             break
    For example: 3 passives are given, so it will generate the following:
    (1), (2), (3), (1, 2), (1, 3), (1, 2, 3), (2, 3)
    ]]
    function doPassives()
         for mode, mode_content in pairs(WITH_TRAITS) do
            for damage_key, damage_value in pairs(mode_content) do
                local combinations = { {} }
                for passive_key, passive in pairs(PASSIVES) do
                    local count = #combinations
                    for i = 1, count do
                        local new_combination = { unpack(combinations[i]) }
                        table.insert(new_combination, passive_key)
                        table.insert(combinations, new_combination)
                    end
                end
                for _, combination in pairs(combinations) do
                    local passive_multiplier = 1
                    local name_suffix = ''
                    if #combination > 0 then
                        table.sort(combination)
                        for _, passive_key in pairs(combination) do
                            passive_multiplier = passive_multiplier *
                                tonumber(PASSIVES[passive_key][mode == 'PvE' and 'value' or 'value_pvp'])
                            name_suffix = name_suffix .. '_passive' .. passive_key
                        end
                    end
                    WITH_PASSIVES[mode][damage_key .. name_suffix] = damage_value * passive_multiplier
                end
             end
         end
         end
     end
     end
    return formatted
end


    doPassives()
function table.deep_copy(original)
 
     local copy = {}
     local RANGE = {
     for k, v in pairs(original) do
        min_count = args.range_min_count and args.range_min_count[1] or 1,
        if type(v) == "table" then
        max_count = args.range_max_count and args.range_max_count[1] or 1,
            copy[k] = table.deep_copy(v)
        PvE = {
        else
            min = args.range_min and args.range_min[1] or 1,
            copy[k] = v
            max = args.range_max and args.range_max[1] or 1
        },
        PvP = {
            min = args.range_min and (args.range_min[2] or args.range_min[1]) or 1,
            max = args.range_max and (args.range_max[2] or args.range_max[1]) or 1
        }
     }
 
    local WITH_RANGE = createDamageDataTable()
    function doDamageBuffRange()
        -- Handle damage range here
        for mode, mode_content in pairs(WITH_PASSIVES) do
            for damage_key, damage_value in pairs(mode_content) do
                WITH_RANGE[mode][damage_key] = { min = 0, max = 0 }
                for _, range in ipairs({ 'min', 'max' }) do
                    local final_damage_value = damage_value * (1 + ((RANGE[mode][range] - 1) * RANGE[range .. '_count'])) *
                        OPTIONS.perm_buff[mode]
                    WITH_RANGE[mode][damage_key][range] = not OPTIONS.format and final_damage_value or
                        formatDamage(final_damage_value)
                end
            end
         end
         end
     end
     end
    return setmetatable(copy, getmetatable(original))
end


    doDamageBuffRange()
function dump(tbl)
 
     local ret = {}
     local FINAL_DAMAGE = WITH_RANGE
    for k, v in pairs(tbl) do
 
        table.insert(ret, k .. ': ' .. inspect(v))
    -- 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
     end
    return frame:preprocess(table.concat(ret, "<br/>"))
end


    -- Helper function to iterate over passives.
function inspect_dump(frame, tbl)
     function checkPassives(settings)
     return frame:preprocess("<pre><nowiki>" .. inspect(tbl) .. "</nowiki></pre>")
        local output = settings.output or {}
end
        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
function sortPassives(s)
            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 passives = {}
                if type(settings.action) == 'function' then
    for passive in s:gmatch("_passive%d+") do
                    settings.action(passive, output, passive_index)
        table.insert(passives, passive)
                else
                    return true
                end
            end
        end
        return output
     end
     end
 
     if #passives == 0 then
     -- Generate the table
         return s
    local TABLE = mw.html.create('table'):attr({
        cellpadding = 5,
        border = 1,
        style = 'border-collapse: collapse; text-align: center',
        class = 'colortable-' .. OPTIONS.character
    })
 
    -- Our table structure
    local TABLE_CONTENT = {
        {
            type = 'extra',
            text = { 'Average' },
            is_visible = OPTIONS.no_max,
            no_damage = true
        },
        {
            type = 'passives',
            text = checkPassives({
                output = { 'Base' },
                action = function(passive, output)
                    if passive.is_combined then
                        -- Handling combined passive header name.
                        local combo = {}
                        for _, passive_key in ipairs(OPTIONS.combine) do
                            passive = PASSIVES[passive_key]
                            table.insert(combo,
                                link(passive.name, passive.alias, passive.prefix, passive.suffix, passive.exist))
                        end
                        table.insert(output, table.concat(combo, '/') .. OPTIONS.combine_suffix)
                    else
                        table.insert(output,
                            link(passive.name, passive.alias, passive.prefix, passive.suffix, passive.exist))
                    end
                end
            }),
            keywords = checkPassives({
                action = function(passive, output, passive_index)
                    if passive.is_combined then
                        -- Handling combined passive damage cells.
                        table.insert(output, sortPassives('passive' .. table.concat(OPTIONS.combine, '_passive')))
                    else
                        table.insert(output, 'passive' .. passive_index)
                    end
                end
            }),
            is_visible = not OPTIONS.no_max or #PASSIVES > 0
        },
        {
            type = 'passive_appended',
            text = {
                'Normal',
                OPTIONS.is_append and
                link(PASSIVES[OPTIONS.append_index].name,
                    PASSIVES[OPTIONS.append_index].alias or OPTIONS.append_name or nil,
                    PASSIVES[OPTIONS.append_index].prefix,
                    PASSIVES[OPTIONS.append_index].suffix,
                    PASSIVES[OPTIONS.append_index].exist
                )
            },
            keywords = { OPTIONS.is_append and ('passive' .. OPTIONS.append_index) or nil },
            is_visible = OPTIONS.is_append or false
        },
        {
            type = 'awakening',
            text = { 'Regular', (function()
                if OPTIONS.dmp then
                    return link('Dynamo Point System', 'Dynamo Configuration', nil,
                        OPTIONS.dmp ~= 'false' and ('(' .. OPTIONS.dmp .. ' DMP)'))
                elseif args.awk_alias then
                    return link(unpack(args.awk_alias))
                end
                return link('Awakening Mode')
            end)()
            },
            keywords = { 'awk' },
            keyword_next_to_main_key = true,
            is_visible = inArgs('awk_dmg') or inArgs('awk_hits') or inArgs('avg_awk_hits') or false
        },
        {
            type = 'traits',
            text = checkTraits({
                output = { 'Normal' },
                action = function(trait, output)
                    table.insert(output, trait.name)
                end
            }),
            keywords = checkTraits({
                action = function(trait, output)
                    table.insert(output, trait.key)
                end
            }),
            is_visible = checkTraits()
        },
        {
            type = 'cancel',
            text = {
                'Cancel', 'Full'
            },
            keywords = { 'cancel' },
            keyword_first = true,
            is_visible = inArgs('cancel_dmg')
        },
        {
            type = 'hit_count',
            text = {
                (inArgs('count') and not OPTIONS.use_avg) and
                (table.concat({ 'Per', args.count_name or 'Group' }, ' ')) or 'Average',
                'Max'
            },
            keywords = (function()
                if inArgs('avg_hits') or inArgs('count') then
                    return { (inArgs('count') and not OPTIONS.use_avg) and 'each' or 'avg', 'total' }
                end
                return { 'total' }
            end)(),
            is_visible = ((inArgs('avg_hits') or inArgs('count')) and not OPTIONS.no_max) or false
        }
    }
 
    function TABLE:new()
         return self:tag('tr')
     end
     end
    table.sort(passives)
    local base = s:gsub("_passive%d+", "")
    return base .. table.concat(passives)
end


    function returnDamageInOrder()
function link(page, text, prefix, suffix, dolink)
        local main_key = 'damage'
    local suffixString = ((suffix and suffix ~= "") and (' ' .. suffix) or '')
        local all_list = {}
    local prefixString = ((prefix and prefix ~= "") and (' ' .. prefix) or '')
 
        -- Initialize current list with main key
        local current_list = { main_key }
 
        for i = #TABLE_CONTENT, 1, -1 do
            local current_row = TABLE_CONTENT[i]
            local new_list = {}
 
            -- Check if it's the first iteration. If so, append phrases.
            if not current_row.no_damage then
                if i == #TABLE_CONTENT then
                    for _, keyword in ipairs(current_row.keywords) do
                        if not OPTIONS.no_max or (OPTIONS.no_max and keyword ~= 'total') then
                            local new_key = keyword .. '_' .. main_key
                            table.insert(new_list, new_key)
                        end
                    end
                elseif current_row.is_visible then
                    -- Append suffix for each keyword in current row
                    for _, keyword in ipairs(current_row.keywords) do
                        -- Iterate through previous keys
                        for _, prev_key in ipairs(all_list) do
                            local new_key = prev_key .. '_' .. keyword
                            -- If needed, move the suffix to the rightmost of main_key.
                            if current_row.keyword_next_to_main_key then
                                new_key = prev_key:gsub(main_key, main_key .. '_' .. keyword)
                            elseif current_row.keyword_first then
                                new_key = keyword .. '_' .. prev_key
                            end
                            table.insert(new_list, new_key)
                        end
                    end
                end


                -- Append new_list to all_list
    if dolink == false then
                for _, new_key in ipairs(new_list) do
        return prefixString .. (text or page) .. suffixString
                    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
     end
    return prefixString .. '[[' .. page .. '|' .. (text or page) .. ']]' .. suffixString
end


    function doInitialCell(new_row)
local function generateCombinations(passives, n, combo, combos)
        return new_row:tag('th'):wikitext('Mode')
     if n == 0 then
     end
        table.insert(combos, combo)
 
    else
    function doHeaders()
        for i = 1, #passives do
        local current_multiplier = 0 -- Keeps track of the number of cells to spawn
            local newCombo = {}
        local initial_header_cell    -- The leftmost cell that says "Mode"
            for j = 1, #combo do
        local iterations = 0        -- Keeps track of iterations that successfully rendered something. Required to tell the initial cell how many columns to span.
                table.insert(newCombo, combo[j])
 
        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
            table.insert(newCombo, passives[i])
            generateCombinations(passives, n - 1, newCombo, combos)
         end
         end
        -- Apply rowspan of the same value as iteration count.
        initial_header_cell:attr('rowspan', iterations)
     end
     end
end


    -- Helper function to display ranges.
function formatDamage(number)
    function doRangeText(damage_number)
    local formattedDamage = number > 0 and (formatnum(math.round(number, 2)) .. '%') or '-%'
        if damage_number and damage_number.min == damage_number.max then
    return formattedDamage
            damage_number = damage_number.min
end
        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
function doVariables(frame, input, prefix)
                    -- Display ranges.
    local s = ''
                    local new_cell = mode_row:tag('td'):wikitext(damage_number
    for mode, mode_content in pairs(input) do
                        -- Error out if it doesn't exist
        for damage_name, damage_number in pairs(mode_content) do
                        or frame:expandTemplate {
            damage_number = doRangeText(damage_number)
                            title = 'color',
            local is_range = string.find(damage_number, '~')
                            args = { 'red', '&#35;ERROR' }
            s = s .. '{{#vardefine:' .. (prefix and prefix .. '_' or '') .. (is_range and 'range_' or '') .. damage_name .. (mode ~= 'PvE' and ('_' .. string.lower(mode)) or '') .. '|' .. damage_number .. '}}'
                        })
                    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
    return frame:preprocess(s)
end


    function doTable()
function table.fuse(t1,t2)
        doHeaders()
     for k, v in pairs(t2) do
        forEach(doContentByMode)
         t1[k] = v
    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
     end
 
     return t1
    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
-- pyend

Revision as of 09:44, 23 September 2023

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

local inspect = require('Module:Inspect').inspect

-- in Array
function inArray(key, array)
    if array[key] ~= nil then
        return true
    end
end

function inArrayStarts(key, array)
    for k, v in pairs(array) do
        if string.starts(k, key) then
            return true
        end
    end
end

function inArrayHas(key, array)
    for k, v in pairs(array) do
        if string.find(k, key) then
            return true
        end
    end
end

function inArrayHasValue(value, array)
    for k, v in pairs(array) do
        if string.find(v, value) then
            return true
        end
    end
end

function indexOf(value, array)
    for i, v in ipairs(array) do
        if v == value then
            return i
        end
    end
    return nil
end

-- String starts
function string.starts(String, Start)
    return string.sub(String, 1, string.len(Start)) == Start
end

function string.ends(str, ending)
    return ending == "" or str:sub(-#ending) == ending
end

-- Implement sorted loop through array.
function spairs(t)
    local keys = {}
    for k in pairs(t) do
        keys[#keys + 1] = k
    end
    table.sort(keys)
    local i = 0
    return function()
        i = i + 1
        if keys[i] then
            return keys[i], t[keys[i]]
        end
    end
end

-- Implement merging tables
function tableMerge(first_table, second_table)
    for k, v in spairs(second_table) do
        first_table[k] = v
    end
end

function indexTableMerge(first_table, second_table)
    for k, v in spairs(second_table) do
        table.insert(first_table, v)
    end
end

-- Implement rounding.
function round(num, numDecimalPlaces)
    if numDecimalPlaces == nil then
        numDecimalPlaces = 2
    end
    local mult = 10 ^ (numDecimalPlaces or 0)
    return math.floor(num * mult + 0.5) / mult
end

-- Implement string trim.
function trim(s)
    return (string.gsub(s, "^%s*(.-)%s*$", "%1"))
end

-- Implement splitting string to a table.
function split(s, delimiter, skip_empty)
    if not s then
        return {}
    end
    local i = 1
    if delimiter == nil then
        delimiter = ','
    end
    result = {};
    for match in (s .. delimiter):gmatch("(.-)" .. delimiter) do
        if (skip_empty == true and trim(match) ~= '') or skip_empty == nil then
            table.insert(result, i, trim(match));
            i = i + 1
        end
    end
    return result;
end

-- Title case
function titleCaseFunc(first, rest)
    return first:upper() .. rest:lower()
end

function titleCase(str)
    return string.gsub(str, "(%a)([%w_']*)", titleCaseFunc)
end

function table.containsKey(tbl, key)
    return tbl[key] ~= nil
end

function table.containsValue(tbl, val)
    local contains = false
    for k, v in pairs(tbl) do
        if (v == val) then
            contains = true
            break
        end
    end
    return contains
end

function table.matches(tbl, val)
    local matches = 0
    for k, v in pairs(tbl) do
        if (type(v) == 'table') then
            matches = matches + table.matches(v, val)
        else
            if (v == val) then
                matches = matches + 1
            end
        end
    end
    return matches
end

function dump(o)
    if type(o) == 'table' then
        local s = '{ <br>'
        for k, v in pairs(o) do
            if type(k) ~= 'number' then
                k = '"' .. k .. '"'
            end
            s = s .. '[' .. k .. '] = ' .. dump(v) .. ',<br>'
        end
        return s .. '}'
    else
        return tostring(o)
    end
end

function table.not_empty(tbl)
    return not next(tbl)
end

function math.round(num, decimals)
    decimals = math.pow(10, decimals or 0)
    num = num * decimals
    if num >= 0 then
        num = math.floor(num + 0.5)
    else
        num = math.ceil(num - 0.5)
    end
    return num / decimals
end

function formatnum(amount)
    local formatted = amount
    while true do
        formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2')
        if (k == 0) then
            break
        end
    end
    return formatted
end

function table.deep_copy(original)
    local copy = {}
    for k, v in pairs(original) do
        if type(v) == "table" then
            copy[k] = table.deep_copy(v)
        else
            copy[k] = v
        end
    end
    return setmetatable(copy, getmetatable(original))
end

function dump(tbl)
    local ret = {}
    for k, v in pairs(tbl) do
        table.insert(ret, k .. ': ' .. inspect(v))
    end
    return frame:preprocess(table.concat(ret, "<br/>"))
end

function inspect_dump(frame, tbl)
    return frame:preprocess("<pre><nowiki>" .. inspect(tbl) .. "</nowiki></pre>")
end

function sortPassives(s)
    local passives = {}
    for passive in s:gmatch("_passive%d+") do
        table.insert(passives, passive)
    end
    if #passives == 0 then
        return s
    end
    table.sort(passives)
    local base = s:gsub("_passive%d+", "")
    return base .. table.concat(passives)
end

function link(page, text, prefix, suffix, dolink)
    local suffixString = ((suffix and suffix ~= "") and (' ' .. suffix) or '')
    local prefixString = ((prefix and prefix ~= "") and (' ' .. prefix) or '')

    if dolink == false then
        return prefixString .. (text or page) .. suffixString
    end
    return prefixString .. '[[' .. page .. '|' .. (text or page) .. ']]' .. suffixString
end

local function generateCombinations(passives, n, combo, combos)
    if n == 0 then
        table.insert(combos, combo)
    else
        for i = 1, #passives do
            local newCombo = {}
            for j = 1, #combo do
                table.insert(newCombo, combo[j])
            end
            table.insert(newCombo, passives[i])
            generateCombinations(passives, n - 1, newCombo, combos)
        end
    end
end

function formatDamage(number)
    local formattedDamage = number > 0 and (formatnum(math.round(number, 2)) .. '%') or '-%'
    return formattedDamage
end

function doVariables(frame, input, prefix)
    local s = ''
    for mode, mode_content in pairs(input) do
        for damage_name, damage_number in pairs(mode_content) do
            damage_number = doRangeText(damage_number)
            local is_range = string.find(damage_number, '~')
            s = s .. '{{#vardefine:' .. (prefix and prefix .. '_' or '') .. (is_range and 'range_' or '') .. damage_name .. (mode ~= 'PvE' and ('_' .. string.lower(mode)) or '') .. '|' .. damage_number .. '}}'
        end
    end
    return frame:preprocess(s)
end

function table.fuse(t1,t2)
    for k, v in pairs(t2) do
        t1[k] = v
    end
    return t1
end