Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.
(Redirected from Module:DifficultiesSloppy)

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

-- Module:Difficulties
local p = {}

-- Helper to capitalize first letter of each word (after converting underscores to spaces)
local function title_case(str)
    if not str or str == '' then
        return str
    end
    local spaced_str = mw.ustring.gsub(str, '_', ' ')
    -- Use mw.ustring.gsub with a pattern to find words and capitalize their first letter
    -- %f[%a] is a frontier pattern matching a position where a non-alphabetic char is followed by an alphabetic one (or start of string)
    return mw.ustring.gsub(spaced_str, '%f[%a](%a+)', function(word)
        return mw.ustring.upper(mw.ustring.sub(word, 1, 1)) .. mw.ustring.sub(word, 2)
    end)
end

-- Simple ucfirst for first letter only, no underscore handling
local function ucfirst_first_char(str)
    if not str or str == '' then
        return str
    end
    return mw.ustring.upper(mw.ustring.sub(str, 1, 1))
        .. mw.ustring.sub(str, 2)
end


local function trim(s)
    return s and mw.text.trim(s) or nil
end

local function is_true(v)
    if v == nil then
        return false
    end
    local s = mw.text.trim(tostring(v)):lower()
    return s == 'true' or s == 'yes' or s == 'y' or s == '1'
end

local function split_list(s)
    local out = {}
    if not s then
        return out
    end
    for token in mw.ustring.gmatch(s, '([^,;|]+)') do
        local t = trim(token)
        if t and t ~= '' then
            table.insert(out, t)
        end
    end
    return out
end

local function normalize_hex(hex)
    if not hex then
        return nil
    end
    local s = trim(hex)
    if not s or s == '' then
        return nil
    end
    s = s:gsub('^#', '') -- Remove leading # if present
    s = mw.ustring.upper(s) -- Ensure it's uppercase for consistent matching
    -- Lua patterns do not support {6}. Use %x (hex digit) repeated.
    if mw.ustring.match(s, '^%x%x%x%x%x%x$') then
        return '#' .. s
    end
    -- Optional: support 3-digit shorthand like FFF
    if mw.ustring.match(s, '^%x%x%x$') then
        local r = s:sub(1, 1)
        local g = s:sub(2, 2)
        local b = s:sub(3, 3)
        return '#' .. r .. r .. g .. g .. b .. b
    end
    return nil
end

local function make_link(display_text, link_arg)
    if not display_text or display_text == '' then
        return ''
    end
    if not link_arg or link_arg == '' then
        return mw.text.nowiki(display_text)
    end
    local link = trim(link_arg)
    if not link or link == '' then
        return mw.text.nowiki(display_text)
    end
    if mw.ustring.match(link, '^%[%[.*%]%]$') then
        return link
    end
    if mw.ustring.match(link, '^[Hh][Tt][Tt][Pp][Ss]?://') or mw.ustring.match(link, '^//') then
        return '[' .. link .. ' ' .. display_text .. ']'
    end
    return '[[' .. link .. '|' .. display_text .. ']]'
end

local function collect_difficulty_names(args)
    local names, seen = {}, {}

    -- Helper to standardize name for internal use (lowercase snake_case)
    local function standardize_name_for_lookup(raw_name)
        if not raw_name then return nil end
        local cleaned = mw.ustring.gsub(raw_name, '%s+', '_') -- spaces to underscores
        return mw.ustring.lower(cleaned) -- to lowercase
    end

    -- 1) explicit list (preferred)
    for _, n in ipairs(split_list(args.difficulties or args.list)) do
        local key = standardize_name_for_lookup(n)
        if key and not seen[key] then
            table.insert(names, key)
            seen[key] = true
        end
    end

    -- 2) any foo_index discovered
    for k, _ in pairs(args) do
        local name_match = mw.ustring.match(k, '^([%a_][%w_]*)_index$')
        if name_match then
            local key = standardize_name_for_lookup(name_match)
            if key and not seen[key] then
                table.insert(names, key)
                seen[key] = true
            end
        end
    end

    -- 3) any other parameters that imply a difficulty (e.g., 'novice=true', 'Very_Hard_hex=F00')
    for k, _ in pairs(args) do
        local base_name_match = mw.ustring.match(k, '^([%a_][%w_]*)(?:_.*)?$')
        if base_name_match then
            local key = standardize_name_for_lookup(base_name_match)
            if key and key ~= 'difficulties' and key ~= 'list' and
               not mw.ustring.match(key, '^completions?_as_of$') and
               not mw.ustring.match(key, '^as_of(_date)?$')
            then
                if not seen[key] then
                    table.insert(names, key)
                    seen[key] = true
                end
            end
        end
    end

    return names
end

-- Robust argument getter for base_suffix style parameters (e.g., "very_hard_hex")
-- Checks common casing variations for the 'base' part of the parameter.
-- 'base_lower_snake' is the internal lowercase_snake_case name (e.g., "very_hard")
local function get_arg(args, base_lower_snake, suffix)
    if not suffix then
        return nil
    end

    -- Prioritize direct lowercase_snake_case key, then ucfirst first char
    local check_keys = {
        base_lower_snake .. '_' .. suffix,                          -- e.g., very_hard_hex
        ucfirst_first_char(base_lower_snake) .. '_' .. suffix,     -- e.g., Very_hard_hex
    }

    -- If the base name contains an underscore, also check the Title_Case version
    if mw.ustring.find(base_lower_snake, '_') then
        table.insert(check_keys, title_case(base_lower_snake) .. '_' .. suffix) -- e.g., Very_Hard_hex
    end

    for _, key in ipairs(check_keys) do
        if args[key] ~= nil then
            return args[key]
        end
    end
    return nil
end

-- Specific function to check for difficulty_name=true/yes/1 (without suffix)
local function get_arg_difficulty_boolean(args, base_lower_snake)
    return is_true(args[base_lower_snake]) or is_true(args[ucfirst_first_char(base_lower_snake)])
end


function p.main(frame)
    local parent = frame:getParent()
    local args = parent and parent.args or frame.args or {}
    local lang = mw.language.getContentLanguage()

    local output = mw.html.create('table')
    output:addClass('wikitable')
    output:addClass('wikitable--fluid')

    local header_row = output:tag('tr')
    header_row:tag('th'):wikitext('Icon')
    header_row:tag('th'):wikitext('Difficulty')
    header_row:tag('th'):wikitext('Stages')
    header_row:tag('th'):wikitext('Completion %')

    local as_of = trim(args.completions_as_of or args.as_of or args.as_of_date)
    local completions_header = 'Completions'
    if as_of and as_of ~= '' then
        completions_header = 'Completions (as of ' .. as_of .. ')'
    end
    header_row:tag('th'):wikitext(completions_header)

    header_row:tag('th'):wikitext('Rewards'):addClass('right')

    local names = collect_difficulty_names(args)

    local entries = {}
    for _, name in ipairs(names) do -- 'name' is now guaranteed lowercase snake_case
        local enabled = (get_arg(args, name, 'index') ~= nil)
            or (get_arg(args, name, 'display') ~= nil)
            or is_true(get_arg(args, name, 'enabled'))
            or get_arg_difficulty_boolean(args, name)

        if enabled then
            local e = {}
            e.name = name -- lowercase snake_case for internal lookup consistency
            e.index = tonumber(get_arg(args, name, 'index')) or math.huge

            -- FIX FOR DISPLAY CASING: This logic ensures correct fallback
            local explicit_display_value = get_arg(args, name, 'display')
            if explicit_display_value and trim(explicit_display_value) ~= '' then
                e.display = trim(explicit_display_value)
            else
                e.display = title_case(name) -- Fallback, now using title_case function directly
            end

            e.link_arg = trim(get_arg(args, name, 'link'))
            e.file_name = trim(get_arg(args, name, 'file_name'))

            -- FIX FOR HEX COLOR:
            -- Use the robust get_arg for all variations, then normalize.
            local raw_hex = get_arg(args, name, 'hex')
                or get_arg(args, name, 'color_hex')
                or get_arg(args, name, 'color')
            e.color_hex = normalize_hex(raw_hex) -- This should now work correctly with the fixed normalize_hex and get_arg

            e.unreleased = is_true(get_arg(args, name, 'unreleased')) -- Corrected to name_unreleased
                or is_true(args[name .. '_unreleased']) -- Backup check for consistency
                or is_true(args[ucfirst_first_char(name) .. '_unreleased']) -- Check for Ucfirst_unreleased

            e.completion_percent = trim(get_arg(args, name, 'completion'))
            e.total_completions = trim(
                get_arg(args, name, 'total_completions')
                    or get_arg(args, name, 'completions_total')
                    or get_arg(args, name, 'all_time_completions')
            )
            e.badge = trim(get_arg(args, name, 'badge'))
            e.start_arg = trim(get_arg(args, name, 'start'))
            e.end_arg = trim(get_arg(args, name, 'end'))

            table.insert(entries, e)
        end
    end

    table.sort(entries, function(a, b)
        if a.index == b.index then
            return a.display < b.display -- Secondary sort by display name
        end
        return a.index < b.index
    end)

    local inferred_next_start = 0
    local inclonlystr = ''

    for _, e in ipairs(entries) do
        local row = output:tag('tr')

        -- Icon
        local icon_cell = row:tag('td')
        if e.file_name and e.file_name ~= '' then
            icon_cell:wikitext(
                '[[File:' .. e.file_name .. '|48px|alt=Difficulty ' .. e.display .. ']]'
            )
            icon_cell:attr(
                'style',
                'border-radius:8px; overflow:hidden;'
            )
        else
            local bg = e.color_hex or '#888888' -- Fallback still exists, but e.color_hex should now be correct
            icon_cell:tag('div')
                :attr(
                    'style',
                    'width:48px; height:48px; background-color:' ..
                        bg ..
                        '; vertical-align:middle; border-radius:8px; display:inline-block;'
                )
        end

        -- Name (with optional custom link)
        local name_cell = row:tag('td')
        local name_wikitext = make_link(e.display, e.link_arg)
        if e.color_hex then
            name_cell:wikitext(name_wikitext):attr('style', 'color:' .. e.color_hex .. ';')
        else
            name_cell:wikitext(name_wikitext)
        end

        -- Stages
        local stage_cell = row:tag('td')
        local start_num = tonumber(e.start_arg)
        local end_num = tonumber(e.end_arg)

        if start_num == nil then
            start_num = inferred_next_start
        end -- Corrected: matched the 'if'

        if e.unreleased then
            stage_cell:wikitext("''Unreleased''")
        else
            local start_text = start_num ~= nil and tostring(start_num) or '??'
            local end_text = end_num ~= nil and tostring(end_num) or '??'
            stage_cell:wikitext(start_text .. ' - ' .. end_text)
        end

        if end_num ~= nil then
            inferred_next_start = end_num
        else
            inferred_next_start = start_num or inferred_next_start
        end

        -- Completion %
        local completionp_cell = row:tag('td')
        if e.completion_percent and e.completion_percent ~= '' then
            completionp_cell:wikitext(e.completion_percent .. '%')
        else
            completionp_cell:wikitext('??%')
        end

        -- Completions (all-time) with thousands separators
        local comps_cell = row:tag('td')
        if e.total_completions and e.total_completions ~= '' then
            local n = tonumber(e.total_completions)
            if n then
                comps_cell:wikitext(lang:formatNum(n))
            else
                comps_cell:wikitext(e.total_completions)
            end
        else
            comps_cell:wikitext('??')
        end

        -- Rewards (right-aligned, last column)
        local rewards_cell = row:tag('td'):addClass('right')
        if e.badge and e.badge ~= '' then
            rewards_cell:wikitext(
                '[[File:Icons Badge Small White.png|link=https://roblox.com/badges/' ..
                    e.badge ..
                    '/obbywikidifficultylink|alt=Badge|28px]]'
            )
            rewards_cell:attr('style', 'vertical-align:middle;')
        else
            rewards_cell:wikitext("''None''")
        end

        inclonlystr = inclonlystr .. '[[Category:' .. title_case(e.name) .. ']]'
    end

    return tostring(output) .. inclonlystr
end

return p