Module:Damage

From Elwiki
Revision as of 22:08, 24 August 2022 by Ritsu (talk | contribs)

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

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

-- Main process
function p.main(frame)
    local args = getArgs(frame)

    function inArgs(key)
        if args[key] ~= nil then
            return true
        end
    end

    -- Collect data from the input
    local data = {}
    local data_types = {'dmg', 'pvp_dmg', 'awk_dmg', 'pvp_awk_dmg', 'hits', 'avg_hits', 'awk_hits', 'avg_awk_hits',
                        'hits_useful', 'avg_hits_useful', 'awk_hits_useful', 'avg_awk_hits_useful'}

    for k, v in spairs(data_types) do
        local i = 1
        if inArgs(v) then
            for k2, v2 in spairs(split(args[v])) do
                -- Check if proper hit count values provided. If empty string detected, inherit from 'hits'.
                if string.find(v, 'avg_') and string.find(v, '_hits') and v2 == '' then
                    data[v .. i] = data['avg_hits' .. i]
                elseif string.find(v, 'hits') and v2 == '' then
                    data[v .. i] = data['hits' .. i]
                elseif string.find(v, 'awk_dmg') and v2 == '' then
                    if string.find(v, 'pvp') then
                        data[v .. i] = data['pvp_dmg' .. i]
                    else
                        data[v .. i] = data['dmg' .. i]
                    end
                else
                    data[v .. i] = v2
                end
                i = i + 1
            end
        end
    end

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

    inheritMissing({'awk_dmg', 'pvp_awk_dmg'}, {'dmg', 'pvp_dmg'})

    -- Handle trait table
    local traits = {}

    if inArgs('heavy') then
        traits.heavy = 1.44
    end

    if inArgs('enhanced') then
        traits.enhanced = 0.8
    end

    -- Customizable for empowered, it had to be special lol.
    if inArgs('empowered') then
        if (args.empowered == 'true') then
            traits.empowered = 1.2
        else
            traits.empowered = args.empowered
        end
    end

    -- Output passives if provided
    local passives = {}
    for i = 1, 3 do
        if inArgs('passive' .. i) then
            passives[i] = args['passive' .. i]
            passives[i] = split(frame:preprocess('{{:' .. passives[i] .. '}}{{#arrayprint:' .. passives[i] .. '}}'))
        end
    end

    function list(ispvp)

        -- Define tables that hold the subsequent damage values.
        -- I know this isn't the best, but I don't want to work with nested tables in this language.
        local fvals = {}
        local tvals = {}
        local pvals = {
            [1] = {},
            [2] = {},
            [3] = {},
            [12] = {},
            [13] = {},
            [23] = {},
            [123] = {}
        }

        -- Check the specified mode and define the prefixes/suffixes first.
        local pr = ''
        local su = ''
        local p_index = 1
        if ispvp then
            p_index = 2
        end

        if (ispvp) then
            pr = 'pvp_'
            su = '_pvp'
        end

        -- Define total/average damage calculation based on damage per hit and hit amount.
        function getTotal(dmg, hits, fval, count)
            local i = 1
            fvals[fval] = 0
            for k, v in spairs(split(args.dmg)) do
                -- If 'hits' undefined, assume they're equal to 1.
                if (data[hits .. i] == nil) then
                    data[hits .. i] = 1
                end
                -- Proceed to combine
                fvals[fval] = fvals[fval] + data[dmg .. i] * data[hits .. i]
                i = i + 1
            end
            -- For skills with multiple same parts, ex. Clementine, Enough Mineral
            if count == true then
                fvals[fval] = fvals[fval] * args.count
                if inArgs('count_extra' .. su) then
                    if args.count_extra_hits == nil then
                        args.count_extra_hits = 1
                    end
                    if not string.find(fval, "each_") then
                        fvals[fval] = fvals[fval] + (args['count_extra' .. su] * args['count_extra_hits'])
                    end
                end
            end
            -- Apply Useful modifier.
            if string.find(fval, 'useful') then
                fvals[fval] = fvals[fval] * args.useful_penalty
            end
        end

        -- Actually generate the values depending on arguments provided.
        if inArgs(pr .. 'dmg') then
            if (inArgs('count')) then
                getTotal(pr .. 'dmg', 'hits', 'each_damage' .. su)
                getTotal(pr .. 'dmg', 'hits', 'total_damage' .. su, true)
            else
                getTotal(pr .. 'dmg', 'hits', 'total_damage' .. su)
            end

            if inArgs('avg_hits') then
                getTotal(pr .. 'dmg', 'avg_hits', 'avg_damage' .. su)
            end
        end

        if inArgs(pr .. 'awk_dmg') or inArrayStarts(pr .. 'awk_dmg', data) then
            getTotal(pr .. 'awk_dmg', 'awk_hits', 'total_damage_awk' .. su)

            if inArgs('avg_awk_hits') then
                getTotal(pr .. 'awk_dmg', 'avg_awk_hits', 'avg_damage_awk' .. su)
            end
        end

        -- Handling traits
        -- Useful handled separately
        if inArgs('useful_penalty') then
            getTotal(pr .. 'dmg', 'hits_useful', 'total_damage_useful' .. su)

            if inArgs('avg_hits_useful') then
                getTotal(pr .. 'dmg', 'avg_hits_useful', 'avg_damage_useful' .. su)
            end

            if inArgs(pr .. 'awk_dmg') and inArgs('awk_hits_useful') then
                getTotal(pr .. 'awk_dmg', 'awk_hits_useful', 'total_damage_awk_useful' .. su)
            end

            if inArgs('avg_awk_hits') and inArgs('avg_awk_hits_useful') then
                getTotal(pr .. 'awk_dmg', 'avg_awk_hits_useful', 'avg_damage_awk_useful' .. su)
            end
        end

        -- Multiply all values with traits and store them in another table.
        for k, v in spairs(fvals) do
            if not string.find(k, 'useful') then
                for kt, vt in spairs(traits) do
                    if inArgs(kt) then
                        local dmg_name = k .. '_' .. kt
                        if ispvp then
                            dmg_name = dmg_name:gsub(su, '') .. su
                        end
                        local dmg_formula = v * vt
                        tvals[dmg_name] = dmg_formula
                    end
                end
            end
        end

        -- Get a table of merged base & trait values
        local ftvals = fvals
        tableMerge(ftvals, tvals)

        function addPassive(num, loop_table)
            local pval_index
            if loop_table == nil then
                pval_index = num
                loop_table = ftvals
            else
                pval_index = tonumber(loop_table .. num)
                loop_table = pvals[loop_table]
            end
            for k, v in spairs(loop_table) do
                local dmg_name = k .. '_passive' .. num
                if ispvp then
                    dmg_name = dmg_name:gsub(su, '') .. su
                end
                local dmg_formula = v * passives[num][p_index]
                pvals[pval_index][dmg_name] = dmg_formula
            end
        end

        -- Add passives and combine them.
        if inArgs('passive2') then
            addPassive(2)
            if inArgs('passive3') then
                addPassive(3, 2)
            end
        end

        if inArgs('passive1') then
            addPassive(1)
            if inArgs('passive2') then
                addPassive(2, 1)
                if inArgs('passive3') then
                    addPassive(3, 12)
                end
            end
            if inArgs('passive3') then
                addPassive(3, 1)
            end
        end

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

        -- Merge all tables into one.
        tableMerge(fvals, tvals)
        for k, v in spairs(pvals) do
            tableMerge(fvals, v)
        end

        return fvals
    end

    local out = list(false)
    local out_pvp = list(true)

    -- Merge the output to a unified table.
    tableMerge(out, out_pvp)

    -- Function wrapper for vardefine syntax in MW.
    function var(name, dmg, prefix)
        local undefined_cases = (args.nomax == 'true' and string.find(name, 'total')) or (args.noavg == 'true' and string.find(name, 'avg'));
        if prefix == nil then
            prefix = ''
        else
            prefix = prefix .. '_'
        end
        if dmg == 0 or undefined_cases then
            dmg = '-%'
        else
            dmg = round(dmg)
        end
        if (args.format == 'false' or undefined_cases) then
            return '{{#vardefine:' .. prefix .. name .. '|' .. dmg .. '}}'
        else
            return '{{#vardefine:' .. prefix .. name .. '|{{formatnum:' .. dmg .. '}}%}}'
        end
    end

    -- Apply ranges.
    function getRangeCount(arg)
        if inArgs(arg) then
            data[arg] = split(args[arg])
            if data[arg][2] == nil then
                data[arg][2] = data[arg][1]
            end
        end
    end

    getRangeCount('range_min_count');
    getRangeCount('range_max_count')

    function determineRange(minmax)
        if inArgs('range_' .. minmax) then
            data['range_' .. minmax] = split(args['range_' .. minmax])
            if data['range_' .. minmax][2] == nil then
                data['range_' .. minmax][2] = data['range_' .. minmax][1]
            end
            if inArgs('range_' .. minmax .. '_count') then
                local i = 1;
                for k, v in spairs(data['range_' .. minmax]) do
                    data['range_' .. minmax][i] = 1 + data['range_' .. minmax][i] *
                                                      data['range_' .. minmax .. '_count'][i]
                    i = i + 1
                end
            end
        end
    end

    determineRange('min');
    determineRange('max');

    -- If maximum range is specified, but not minimum, and minimum count is specified.
    -- By default, it would just do the same as with max, don't want that.
    if inArgs('range_max') and not inArgs('range_min') then
        data['range_min'] = {1, 1}
        if inArgs('range_min_count') then
            local range_max_arg = split(args.range_max);
            if range_max_arg[2] == nil then
                range_max_arg[2] = range_max_arg[1]
            end
            data['range_min'] = {1 + range_max_arg[1] * data['range_min_count'][1],
                                 1 + range_max_arg[2] * data['range_min_count'][2]}
        end
    end

    local out_min = {}
    local out_max = {}

    function applyRange(minmax)
        local temp_tab = {};
        if minmax == 'min' then
            temp_tab = out_min
        else
            temp_tab = out_max
        end
        if inArgs('range_max') then
            for k, v in spairs(out) do
                if not (string.starts(k, 'min_') or string.starts(k, 'max_')) then
                    if (string.find(k, '_pvp')) then
                        temp_tab[minmax .. '_' .. k] = v * data['range_' .. minmax][2];
                    else
                        temp_tab[minmax .. '_' .. k] = v * data['range_' .. minmax][1];
                    end
                end
            end
        end
        tableMerge(out, temp_tab)
    end

    applyRange('min');
    applyRange('max');

    -- Get the actual variables with MW syntax.
    local vars = {}
    for k, v in spairs(out) do
        table.insert(vars, var(k, v, args.prefix))
    end

    -- Transform ranges to variables.
    local vars_range = {}
    if (inArgs('range_max')) then
        for k, v in spairs(out) do
            if not (string.starts(k, 'min_') or string.starts(k, 'max_')) then
                local prefix = ''
                if args.prefix ~= nil then
                    prefix = args.prefix .. '_'
                end
                table.insert(vars_range,
                    '{{#vardefine: ' .. prefix .. 'range_' .. k .. '|{{formatnum:' .. round(out_min['min_' .. k]) ..
                        '}}% ~ {{formatnum:' .. round(out_max['max_' .. k]) .. '}}%}}');
            end
        end
        indexTableMerge(vars, vars_range);
    end

    -- Dump all values if wanted.
    if args.dump == 'true' then
        local ret = {}
        for k, v in spairs(out) do
            table.insert(ret, k .. ': ' .. v)
        end
        return frame:preprocess(table.concat(ret, "<br/>"))
    else
        -- Parse all variables - end point
        return frame:preprocess('{{ ' .. table.concat(vars) .. 'trim2}}')
    end

end

return p