Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Module:Dice

From Teriock
Revision as of 23:27, 19 June 2025 by Gpe (talk | contribs) (// via Wikitext Extension for VSCode)

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

-- Module:Dice
-- Processes dice input strings and outputs HTML spans with roll links, templates, and data attributes
-- Attempts to preprocess generated wikitext when available, with fallback to raw output

local p = {}
local html = mw.html
local getFrame = mw.getCurrentFrame

-- Trim whitespace from both ends
local function trim(s)
  return (s:gsub("^%s*(.-)%s*$", "%1"))
end

-- Split the full roll string into parts and operators (+, -)
local function splitParts(s)
  local parts, i = {}, 1
  while i <= #s do
    local c = s:sub(i,i)
    if c == "+" or c == "-" then
      table.insert(parts, c)
      i = i + 1
    elseif c == " " then
      i = i + 1
    else
      local j = s:find("[%+%-]", i)
      if j then
        table.insert(parts, trim(s:sub(i, j-1)))
        i = j
      else
        table.insert(parts, trim(s:sub(i)))
        break
      end
    end
  end
  return parts
end

-- Capitalize first letter
local function capitalize(s)
  return (s:gsub("^%l", string.upper))
end

-- Parse an individual part into prefix, dice, and qualifiers
local function parsePart(part)
  local s = trim(part)
  local prefixCount, prefixFlag, diceCount, diceFaces, qualifiers = nil, nil, nil, nil, {}

  -- Extract qualifiers in [brackets]
  local main, qualStr = s:match("^(.-)%[([^%]]+)%]$")
  if qualStr then
    s = trim(main)
    for q in qualStr:gmatch("([^ ]+)") do
      table.insert(qualifiers, q)
    end
  end

  -- Extract parenthesized prefix, e.g. (2@p) or (@f)
  local pref = s:match("^%(([%d]*@%a+)%)")
  if pref then
    local num, flag = pref:match("^(%d*)@(%a+)")
    prefixCount = tonumber(num) or 1
    prefixFlag = flag
    s = s:gsub("^%b()", "", 1)
  else
    -- Pure prefix without dice
    local pure = s:match("^@(%a+)$")
    if pure then
      prefixCount = 1
      prefixFlag = pure
      s = ""
    end
  end

  -- Extract dice, e.g. 2d6 or d8
  if s:match("^d%d+") then
    diceCount = 1
    diceFaces = s:match("^d(%d+)")
  elseif s:match("^%d+d%d+") then
    local cnt, faces = s:match("^(%d+)d(%d+)")
    diceCount = tonumber(cnt)
    diceFaces = faces
  end

  return prefixCount, prefixFlag, diceCount, diceFaces, qualifiers
end

-- Main entry for #invoke:Dice|d|<roll>|<type>
function p.d(frame)
  local fullRoll = frame.args[1] or ""
  local typ = frame.args[2] or "none"
  local parts = splitParts(fullRoll)
  local quickParts, content = {}, {}

  for _, part in ipairs(parts) do
    if part == "+" or part == "-" then
      table.insert(quickParts, part)
      table.insert(content, " " .. part .. " ")
    else
      local prefixCount, prefixFlag, diceCount, diceFaces, qualifiers = parsePart(part)
      -- Build quick-roll
      if diceCount then
        table.insert(quickParts, diceCount .. "d" .. diceFaces)
      end
      -- Prefix template for non-'none' types
      if prefixFlag and typ ~= "none" and diceCount then
        local tpl = string.format("{{%s%s}}",
          string.upper(prefixFlag), prefixCount and prefixCount ~= 1 and "|"..prefixCount or "")
        table.insert(content, tpl)
      end
      -- Dice link or standalone prefix
      if diceCount then
        local display = diceCount == 1 and ("d"..diceFaces) or (diceCount.."d"..diceFaces)
        local label = display
        if prefixFlag and typ == "none" and #qualifiers == 0 then
          label = (prefixCount or 1) .. string.upper(prefixFlag) .. display
        end
        table.insert(content,
          string.format("[https://dice.run/#/d/%dd%s %s]", diceCount, diceFaces, label)
        )
      elseif prefixFlag and typ ~= "none" then
        table.insert(content, string.format("{{%s}}", string.upper(prefixFlag)))
      end
      -- Qualifier labels
      for _, q in ipairs(qualifiers) do
        table.insert(content, string.format("{{L|%s|%s}}", capitalize(typ), capitalize(q)))
      end
    end
  end

  local inner = table.concat(content)
  local quick = table.concat(quickParts, " ")

  -- Build span
  local raw = html.create('span')
    :addClass('dice')
    :attr('data-full-roll', fullRoll)
    :attr('data-quick-roll', quick)
    :attr('data-type', typ)
    :wikitext(inner)
    :allDone()

  -- Attempt to preprocess; fallback to raw if unavailable
  local f = frame or getFrame()
  if f and type(f.preprocess) == 'function' then
    return f:preprocess(raw)
  else
    return raw
  end
end

return p