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:50, 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

local p = {}
local html = mw.html
local text = mw.text

-- 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 = {}
  local 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
    -- Sort qualifiers alphabetically
    table.sort(qualifiers, function(a, b) return a < b 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 = {}
  local 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 sequence (ignore flags)
      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.upper(prefixFlag)
        if prefixCount and prefixCount ~= 1 then tpl = tpl .. "|" .. prefixCount end
        tpl = tpl .. "}}"
        table.insert(content, frame:preprocess(tpl))
      end

      -- Build dice link
      if diceCount then
        local displayDice = (diceCount == 1) and ("d" .. diceFaces) or (diceCount .. "d" .. diceFaces)
        local linkLabel = displayDice
        -- Embed prefix in label when type='none' and no qualifiers
        if prefixFlag and typ == "none" and #qualifiers == 0 then
          linkLabel = (prefixCount or 1) .. string.upper(prefixFlag) .. displayDice
        end
        local link = string.format("[https://dice.run/#/d/%dd%s %s]", diceCount, diceFaces, linkLabel)
        table.insert(content, link)
      elseif prefixFlag and not diceCount then
        -- Pure prefix outputs template always
        table.insert(content, frame:preprocess("{{" .. string.upper(prefixFlag) .. "}}"))
      end

      -- Qualifier labels
      for _, q in ipairs(qualifiers) do
        table.insert(content, frame:preprocess(" {{L|" .. capitalize(typ) .. "|" .. capitalize(q) .. "}}"))
      end
    end
  end

  -- Combine quick-roll and inner wikitext
  local quickRoll = table.concat(quickParts, " ")
  local inner = table.concat(content)

  -- Construct span via mw.html for proper parsing
  local span = html.create('span')
    :addClass('dice')
    :attr('data-full-roll', fullRoll)
    :attr('data-quick-roll', quickRoll)
    :attr('data-type', typ)
    :wikitext(inner)

  -- Return raw output
  return span:allDone()
end

return p