Module:BubbleGraph

From IdleOn MMO Wiki
Revision as of 00:17, 12 December 2024 by BHY4A (talk | contribs)

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

local BubbleGraph = {}

function round(num, numDecimalPlaces)
    local mult = 10 ^ (numDecimalPlaces or 0)
    return math.floor(num * mult + 0.5) / mult
end

function round_format(num)
    local str = tostring(num)
    return str:find("%.") and str:gsub("0+$", ""):gsub("%.$", "") or str
end

function BubbleGraph.calculate_bonus(func, level, x1, x2)
    local result = 0
    if func == "add" then
        if x2 ~= 0 then
            result = (((x1 + x2) / x2 + 0.5 * (level - 1)) / (x1 / x2)) * level * x1
        else
            result = level * x1
        end
    elseif func == "decay" then
        result = (level * x1) / (level + x2)
    elseif func == "intervalAdd" then
        result = x1 + math.floor(level / x2)
    elseif func == "decayMulti" then
        result = 1 + (level * x1) / (level + x2)
    elseif func == "bigBase" then
        result = x1 + x2 * level
    elseif func == "addLower" then
        result = x1 + x2 * (level + 1)
    elseif func == "decayLower" or func == "decayMultiLower" then
        result = x1 * (level + 1) / (level + 1 + x2) - x1 * level / (level + x2)
    elseif func == "bigBaseLower" then
        result = x2
    elseif func == "intervalAddLower" then
        result = math.max(math.floor((level + 1) / x2), 0) - math.max(math.floor(level / x2), 0)
    elseif func == "reduce" then
        result = x1 - x2 * level
    elseif func == "reduceLower" then
        result = x1 - x2 * (level + 1)
    else
        result = 0
    end
    return round(result, 3)
end

function BubbleGraph.generate_graph(x1, x2, func, bubble_color, bubble_name, bubble_number)
    local max_levels = 1000
    local step = 20
    local graph_width = 1330.8
    local graph_height = 250
    local points = {}
    local threshold_level = x2 * 90 / (100 - 90)
    local max_bonus = 0

    for level = 1, max_levels do
        if level == threshold_level then
            local bonus = BubbleGraph.calculate_bonus(func, level, x1, x2)
            table.insert(points, {level = level, bonus = bonus})
        elseif level == 1 or level % step == 0 then
            local bonus = BubbleGraph.calculate_bonus(func, level, x1, x2)
            table.insert(points, {level = level, bonus = bonus})
            if bonus > max_bonus then
                max_bonus = bonus
            end
        end
    end

    local column_color_start, column_color_end, background_color_start, background_color_end, bubble_image_name

    if bubble_color == "orange" then
        column_color_start = "#ffe787"
        column_color_end = "#690e0e"
        background_color_start = "#ffffff"
        background_color_end = "#ef7500"
        bubble_image_name = "OrangeBubble" .. bubble_number
    elseif bubble_color == "green" then
        column_color_start = "#bfffab"
        column_color_end = "#09591a"
        background_color_start = "#f7f7ff"
        background_color_end = "#3fe855"
        bubble_image_name = "GreenBubble" .. bubble_number
    elseif bubble_color == "purple" then
        column_color_start = "#fcc1ff"
        column_color_end = "#350b6a"
        background_color_start = "#fffdfa"
        background_color_end = "#ca51ee"
        bubble_image_name = "PurpleBubble" .. bubble_number
    elseif bubble_color == "yellow" then
        column_color_start = "#f7ffbd"
        column_color_end = "#714200"
        background_color_start = "#f7fffa"
        background_color_end = "#ecc200"
        bubble_image_name = "YellowBubble" .. bubble_number
    end

    local container =
        mw.html.create("div"):css(
        {
            width = graph_width .. "px",
            height = graph_height + 100 .. "px",
            position = "relative",
            ["font-family"] = "'Idleon'",
            ["text-align"] = "center",
            ["margin-bottom"] = "50px"
        }
    )
    
    if bubble_name and bubble_number == nil then
	else
    local bubble_header =
        mw.html.create("div"):css(
        {
            position = "absolute",
            top = "-10px",
            left = "50%",
            width = graph_width + 8 .. "px",
            ["font-size"] = "35px",
            ["transform"] = "translateX(-50%)"
        }
    ):wikitext(string.format("[[File:%s.png|link=]]%s", bubble_image_name, bubble_name))

    container:node(bubble_header)
    end

    local y_axis =
        mw.html.create("div"):css(
        {
            position = "absolute",
            top = "20%",
            left = "-2%",
            height = graph_height .. "px",
            ["writing-mode"] = "sideways-lr"
        }
    ):wikitext("Bonus")
    container:node(y_axis)

    local root =
        mw.html.create("div"):css(
        {
            width = graph_width .. "px",
            height = graph_height .. "px",
            display = "flex",
            ["align-items"] = "flex-end",
            position = "absolute",
            top = "60px",
            ["background-image"] = string.format(
                "linear-gradient(%s, %s)",
                background_color_start,
                background_color_end
            ),
            border = "1px solid #000",
            ["border-radius"] = "3px",
            padding = "3px"
        }
    )

    for _, point in ipairs(points) do
        local height = math.floor((point.bonus / max_bonus) * graph_height)

        if func == "decay" or func == "decayMulti" then
            if point.level >= threshold_level then
                local reduction_factor = math.exp(-((point.level - threshold_level) / 100))
                height = height * reduction_factor
            end
        end

        local gradient_ratio
        local color

        if point.level <= threshold_level then
            gradient_ratio = (point.level - 1) / (threshold_level - 1)
            color =
                string.format(
                "#%02x%02x%02x",
                math.floor(
                    tonumber(column_color_start:sub(2, 3), 16) +
                        gradient_ratio *
                            (tonumber(column_color_end:sub(2, 3), 16) - tonumber(column_color_start:sub(2, 3), 16))
                ),
                math.floor(
                    tonumber(column_color_start:sub(4, 5), 16) +
                        gradient_ratio *
                            (tonumber(column_color_end:sub(4, 5), 16) - tonumber(column_color_start:sub(4, 5), 16))
                ),
                math.floor(
                    tonumber(column_color_start:sub(6, 7), 16) +
                        gradient_ratio *
                            (tonumber(column_color_end:sub(6, 7), 16) - tonumber(column_color_start:sub(6, 7), 16))
                )
            )
        else
            gradient_ratio = (point.level - threshold_level) / (max_levels - threshold_level)
            color =
                string.format(
                "#%02x%02x%02x",
                math.floor(
                    tonumber(column_color_end:sub(2, 3), 16) +
                        gradient_ratio *
                            (tonumber(column_color_start:sub(2, 3), 16) - tonumber(column_color_end:sub(2, 3), 16))
                ),
                math.floor(
                    tonumber(column_color_end:sub(4, 5), 16) +
                        gradient_ratio *
                            (tonumber(column_color_start:sub(4, 5), 16) - tonumber(column_color_end:sub(4, 5), 16))
                ),
                math.floor(
                    tonumber(column_color_end:sub(6, 7), 16) +
                        gradient_ratio *
                            (tonumber(column_color_start:sub(6, 7), 16) - tonumber(column_color_end:sub(6, 7), 16))
                )
            )
        end

        local column =
            mw.html.create("div"):css(
            {
                flex = "1",
                height = height .. "px",
                ["background-color"] = color,
                position = "relative"
            }
        ):attr("title", string.format("Level: %d\nBonus: %s", point.level, round_format(round(point.bonus, 3))))

        local bonus_value = round(point.bonus, 3)
        local height_DN = 60
        local padding_top = 5
        local padding_bottom = 5

        local top_value, bottom_value
        if height >= height_DN + padding_top then
            top_value = padding_top
        else
            bottom_value = padding_bottom
        end

        column:tag("span"):css(
            {
                position = "absolute",
                top = top_value and string.format("%dpx", top_value) or nil,
                bottom = bottom_value and string.format("%dpx", bottom_value) or nil,
                ["writing-mode"] = "sideways-lr",
                color = "#fff",
                ["text-shadow"] = "-.065em 0 #000, 0 .065em #000, .065em 0 #000, 0 -.065em #000",
                left = "50%",
                ["transform"] = "translateX(-50%)"
            }
        ):wikitext(round_format(bonus_value))

        column:tag("span"):css(
            {
                position = "relative",
                bottom = "-100%",
                ["font-size"] = "10px"
            }
        ):wikitext(point.level)

        root:node(column)
    end

    container:node(root)

    local x_axis =
        mw.html.create("div"):css(
        {
            position = "absolute",
            top = "95%",
            width = graph_width + 8 .. "px"
        }
    ):wikitext("Level")

    container:node(x_axis)
    return tostring(container)
end

function BubbleGraph.render(frame)
    local args = frame.args or frame:getParent().args
    local x1 = tonumber(args.x1)
    local x2 = tonumber(args.x2)
    local func = args.func
    local bubble_color = args.bubble_color or args.color
    local bubble_name = args.bubble_name or args.name or ''
    local bubble_number = args.bubble_number or args.number or ''

    return BubbleGraph.generate_graph(x1, x2, func, bubble_color, bubble_name, bubble_number)
end

return BubbleGraph