Jump to content
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: Difference between revisions

From Teriock
// via Wikitext Extension for VSCode
No edit summary
 
(31 intermediate revisions by the same user not shown)
Line 40: Line 40:
end
end


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


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


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


   -- Extract dice, e.g. 2d6 or d8
   -- Extract dice, e.g. 2d6 or d8
   if s:match("^d%d+") then
   if s:match("^%d*d%d+") then
    diceCount = 1
     local cnt, faces = s:match("^(%d*)d(%d+)")
    diceFaces = s:match("^d(%d+)")
     diceCount = tonumber(cnt) or 1
  elseif s:match("^%d+d%d+") then
     local cnt, faces = s:match("^(%d+)d(%d+)")
     diceCount = tonumber(cnt)
     diceFaces = faces
     diceFaces = faces
  else
    -- If not dice, check for a bare numeric constant
    local num = s:match("^(%d+)$")
    if num then
      constValue = tonumber(num)
    end
   end
   end


   return prefixCount, prefixFlag, diceCount, diceFaces, qualifiers
   return prefixCount, prefixFlag, diceCount, diceFaces, qualifiers, constValue
end
end


Line 91: Line 94:
   local typ = frame.args[2] or "none"
   local typ = frame.args[2] or "none"
   local parts = splitParts(fullRoll)
   local parts = splitParts(fullRoll)
   local quickParts = {}
   local quickParts, content = {}, {}
  local content = {}


   for _, part in ipairs(parts) do
   for _, part in ipairs(parts) do
     if part == "+" or part == "-" then
     if part == "+" or part == "-" then
      -- operator
       table.insert(quickParts, part)
       table.insert(quickParts, part)
       table.insert(content, " " .. part .. " ")
       table.insert(content, " " .. part .. " ")
     else
     else
       local prefixCount, prefixFlag, diceCount, diceFaces, qualifiers = parsePart(part)
      -- parse dice, prefix, or constant
       -- Build quick-roll (ignore flags)
       local prefixCount, prefixFlag, diceCount, diceFaces, qualifiers, constValue = parsePart(part)
      if diceCount then
 
        table.insert(quickParts, diceCount .. "d" .. diceFaces)
      -- If it's truly plain (no dice/prefix/qualifiers), keep old behavior
      end
      if not diceCount and not prefixFlag and #qualifiers == 0 and not constValue then
        table.insert(quickParts, part)
        table.insert(content, part)
       else
        -- Build quick-roll sequence (ignore flags)
        if diceCount then
          table.insert(quickParts, diceCount .. "d" .. diceFaces)
        elseif constValue then
          table.insert(quickParts, tostring(constValue))
        end


      -- Add prefix template when type is not 'none'
        -- Prefix template for typed rolls (only before dice, as before)
      if prefixFlag and typ ~= "none" and diceCount then
        if prefixFlag and typ ~= "none" and diceCount then
        local tpl = "{{" .. string.upper(prefixFlag)
          local tpl = "{{" .. string.upper(prefixFlag)
        if prefixCount and prefixCount ~= 1 then
          if prefixCount and prefixCount ~= 1 then tpl = tpl .. "|" .. prefixCount end
          tpl = tpl .. "|" .. prefixCount
          tpl = tpl .. "}}"
          table.insert(content, frame:preprocess(tpl))
         end
         end
        tpl = tpl .. "}}"
        table.insert(content, tpl)
      end


      -- Build dice link
        -- Build dice link or output constant/pure prefix
      if diceCount then
        if diceCount then
        local displayDice = (diceCount == 1) and ("d" .. diceFaces) or (diceCount .. "d" .. diceFaces)
          local displayDice = diceCount .. "d" .. diceFaces
        local linkLabel = displayDice
          local linkLabel = displayDice
        -- Embed prefix in label when no type and no qualifiers
          if prefixFlag then
        if prefixFlag and typ == "none" and #qualifiers == 0 then
            if typ == "none" and #qualifiers == 0 then
          linkLabel = (prefixCount or 1) .. string.upper(prefixFlag) .. displayDice
              local prefixLabel = string.upper(prefixFlag)
              if prefixCount and prefixCount > 1 then prefixLabel = prefixCount .. prefixLabel end
              linkLabel = prefixLabel .. "d" .. diceFaces
            else
              linkLabel = "d" .. diceFaces
            end
          end
          table.insert(content,
            string.format("[https://dice.run/#/d/%dd%s %s]", diceCount, diceFaces, linkLabel)
          )
        elseif constValue then
          -- numeric constant (e.g., 4[lethal])
          table.insert(content, tostring(constValue))
        elseif prefixFlag then
          -- pure prefix
          local tpl = "{{" .. string.upper(prefixFlag)
          if prefixCount and prefixCount ~= 1 then tpl = tpl .. "|" .. prefixCount end
          tpl = tpl .. "}}"
          table.insert(content, frame:preprocess(tpl))
        else
          -- fallback: unknown token but with qualifiers; keep original token text
          table.insert(content, part)
         end
         end
        local link = "[https://dice.run/#/d/" .. diceCount .. "d" .. diceFaces .. " " .. linkLabel .. "]"
        table.insert(content, link)
      elseif prefixFlag and not diceCount and typ ~= "none" then
        -- Pure prefix only for non-none type
        table.insert(content, "{{" .. string.upper(prefixFlag) .. "}}")
      end


      -- Add qualifier labels
        -- Qualifier labels
      if qualifiers and #qualifiers > 0 then
         for _, q in ipairs(qualifiers) do
         for _, q in ipairs(qualifiers) do
           table.insert(content, "{{L|" .. capitalize(typ) .. "|" .. capitalize(q) .. "}}")
           table.insert(content, frame:preprocess(" {{L|" .. capitalize(typ) .. "|" .. capitalize(q) .. "}}"))
         end
         end
       end
       end
Line 139: Line 163:
   end
   end


   -- Assemble quick-roll and content
   -- assemble quick-roll and output
   local quickRoll = table.concat(quickParts, " ")
   local quickRoll = table.concat(quickParts, " ")
   local inner = table.concat(content)
   local inner = table.concat(content)


   -- Build final span
   return html.create('span')
  local span = html.create('span')
     :addClass('dice')
     :addClass('dice')
     :attr('data-full-roll', fullRoll)
     :attr('data-full-roll', fullRoll)
Line 150: Line 173:
     :attr('data-type', typ)
     :attr('data-type', typ)
     :wikitext(inner)
     :wikitext(inner)
 
    :tag("span")
  return span:allDone()
    :addClass("metadata")
    :attr("data-full-roll", fullRoll)
    :attr("data-quick-roll", quickRoll)
    :attr("data-type", "roll")
    :attr("data-roll-type", typ)
    :done()
    :allDone()
end
end


return p
return p

Latest revision as of 20:08, 19 November 2025

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

-- 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, qualifiers, and constant
local function parsePart(part)
  local s = trim(part)
  local prefixCount, prefixFlag, diceCount, diceFaces, qualifiers = nil, nil, nil, nil, {}
  local constValue = 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
    table.sort(qualifiers)
  end

  -- Extract parenthesized prefix, e.g. (2@p), (@f), or (2 * @p)
  local rawPref = s:match("^%(([^)]+)%)")
  if rawPref then
    local num, flag = rawPref:match("^(%d+)%s*%*%s*@(%a+)$")
    if not num then num, flag = rawPref:match("^(%d*)@(%a+)$") end
    prefixCount = tonumber(num) or 1
    prefixFlag = flag
    s = s:gsub("^%b()", "", 1)
  else
    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%d+") then
    local cnt, faces = s:match("^(%d*)d(%d+)")
    diceCount = tonumber(cnt) or 1
    diceFaces = faces
  else
    -- If not dice, check for a bare numeric constant
    local num = s:match("^(%d+)$")
    if num then
      constValue = tonumber(num)
    end
  end

  return prefixCount, prefixFlag, diceCount, diceFaces, qualifiers, constValue
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
      -- operator
      table.insert(quickParts, part)
      table.insert(content, " " .. part .. " ")
    else
      -- parse dice, prefix, or constant
      local prefixCount, prefixFlag, diceCount, diceFaces, qualifiers, constValue = parsePart(part)

      -- If it's truly plain (no dice/prefix/qualifiers), keep old behavior
      if not diceCount and not prefixFlag and #qualifiers == 0 and not constValue then
        table.insert(quickParts, part)
        table.insert(content, part)
      else
        -- Build quick-roll sequence (ignore flags)
        if diceCount then
          table.insert(quickParts, diceCount .. "d" .. diceFaces)
        elseif constValue then
          table.insert(quickParts, tostring(constValue))
        end

        -- Prefix template for typed rolls (only before dice, as before)
        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 or output constant/pure prefix
        if diceCount then
          local displayDice = diceCount .. "d" .. diceFaces
          local linkLabel = displayDice
          if prefixFlag then
            if typ == "none" and #qualifiers == 0 then
              local prefixLabel = string.upper(prefixFlag)
              if prefixCount and prefixCount > 1 then prefixLabel = prefixCount .. prefixLabel end
              linkLabel = prefixLabel .. "d" .. diceFaces
            else
              linkLabel = "d" .. diceFaces
            end
          end
          table.insert(content,
            string.format("[https://dice.run/#/d/%dd%s %s]", diceCount, diceFaces, linkLabel)
          )
        elseif constValue then
          -- numeric constant (e.g., 4[lethal])
          table.insert(content, tostring(constValue))
        elseif prefixFlag then
          -- pure prefix
          local tpl = "{{" .. string.upper(prefixFlag)
          if prefixCount and prefixCount ~= 1 then tpl = tpl .. "|" .. prefixCount end
          tpl = tpl .. "}}"
          table.insert(content, frame:preprocess(tpl))
        else
          -- fallback: unknown token but with qualifiers; keep original token text
          table.insert(content, part)
        end

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

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

  return html.create('span')
    :addClass('dice')
    :attr('data-full-roll', fullRoll)
    :attr('data-quick-roll', quickRoll)
    :attr('data-type', typ)
    :wikitext(inner)
    :tag("span")
    :addClass("metadata")
    :attr("data-full-roll", fullRoll)
    :attr("data-quick-roll", quickRoll)
    :attr("data-type", "roll")
    :attr("data-roll-type", typ)
    :done()
    :allDone()
end

return p