Praxia Wiki
Wiki › Module:PraxiaLeaderboard

Module:PraxiaLeaderboard

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

local p = {}

local API =
    "https://www.empireofpraxia.online/ws/leaderboard?skill="

local MAX_LEVEL = 140

local SKILLS = {
    { key = "combat", label = "Combat" },
    { key = "mining", label = "Mining" },
    { key = "woodcutting", label = "Woodcutting" },
    { key = "totem_carving", label = "Totem Carving" },
    { key = "forging", label = "Forging" }
}


local function toNumber(value)

    return tonumber(value) or 0

end


local function formatNumber(value)

    local text =
        tostring(
            math.floor(
                toNumber(value)
            )
        )

    while true do

        local formatted, replacements =
            text:gsub(
                "^(-?%d+)(%d%d%d)",
                "%1,%2"
            )

        text = formatted

        if replacements == 0 then
            break
        end

    end

    return text

end


local function getLeaderboard(skillKey)

    local url =
        API .. mw.uri.encode(skillKey)

    local data, errors =
        mw.ext.externalData.getExternalData(url)

    if errors then

        return nil,
            table.concat(errors, "; ")

    end

    if not data then

        return nil,
            "No leaderboard data was returned."

    end

    local rows =
        data.__json

    if type(rows) ~= "table" then

        return nil,
            "The API response was not recognized as JSON."

    end

    return rows, nil

end


local function findPlayer(rows, username)

    local wanted =
        mw.ustring.lower(
            mw.text.trim(username)
        )

    for rank, player in ipairs(rows) do

        local currentName =
            mw.ustring.lower(
                mw.text.trim(
                    tostring(
                        player.username or ""
                    )
                )
            )

        if currentName == wanted then

            return {
                rank = rank,
                level = toNumber(player.level),
                experience =
                    toNumber(player.experience)
            }

        end

    end

    return nil

end


local function renderSkillRow(skill, player)

    local row =
        mw.html.create("div")
            :addClass("zone-skill")

    local top =
        row:tag("div")
            :addClass("zone-skill-row")

    local label =
        top:tag("span")
            :wikitext(skill.label)

    if player then

        label
            :tag("span")
            :addClass("zone-rank")
            :wikitext(" #" .. player.rank)

    end

    local values =
        top:tag("span")

    local percentage = 0

    if player then

        values
            :tag("b")
            :wikitext(player.level)

        values
            :tag("span")
            :addClass("zone-xp")
            :wikitext(
                " " ..
                formatNumber(
                    player.experience
                ) ..
                " XP"
            )

        percentage =
            math.floor(
                math.min(
                    100,
                    math.max(
                        0,
                        player.level /
                        MAX_LEVEL *
                        100
                    )
                ) + 0.5
            )

    else

        values
            :tag("b")
            :wikitext("Unranked")

    end

    row
        :tag("div")
        :addClass("zone-track")
            :tag("div")
            :addClass("zone-fill")
            :css(
                "--zone-width",
                percentage .. "%"
            )

    return tostring(row)

end


local function getUsername(frame)

    return mw.text.trim(
        frame.args.player
        or frame:getParent().args.player
        or ""
    )

end


function p.skills(frame)

    local username =
        getUsername(frame)

    if username == "" then

        return
            '<div class="zone-loading">' ..
            'No player username was supplied.' ..
            '</div>'

    end

    local output = {}
    local errors = {}
    local found = 0

    for _, skill in ipairs(SKILLS) do

        local rows, fetchError =
            getLeaderboard(skill.key)

        local player = nil

        if rows then

            player =
                findPlayer(
                    rows,
                    username
                )

        elseif fetchError then

            table.insert(
                errors,
                skill.label ..
                ": " ..
                fetchError
            )

        end

        if player then
            found = found + 1
        end

        table.insert(
            output,
            renderSkillRow(
                skill,
                player
            )
        )

    end

    if found == 0 and #errors > 0 then

        return
            '<div class="zone-loading">' ..
            'The official leaderboard could not be reached.' ..
            '</div>' ..

            '<div class="zone-sync-status">' ..
            mw.text.nowiki(
                table.concat(
                    errors,
                    " | "
                )
            ) ..
            '</div>'

    end

    table.insert(
        output,
        '<div class="zone-sync-status">' ..
        'Live from the ' ..
        '[https://www.empireofpraxia.online/#ranks ' ..
        'official leaderboard]' ..
        '</div>'
    )

    return table.concat(output)

end


function p.level(frame)

    local username =
        getUsername(frame)

    if username == "" then
        return "Level unavailable"
    end

    local rows =
        getLeaderboard("combat")

    if not rows then
        return "Level unavailable"
    end

    local player =
        findPlayer(
            rows,
            username
        )

    if not player then
        return "Combat unranked"
    end

    return
        "Level " ..
        player.level

end


return p