Module:DifficultiesV2
From Obby Wiki
More actions
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