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

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

local p = {}

local function makeBar(frame, title, content, link, flags, bottom)
    if content == '' then return '' end
    local barSpace = mw.html.create('span')
    barSpace:wikitext(frame:preprocess(' ')):css('display', 'none')
    local barTitle = mw.html.create('span')
    local barTitleClass = "{{lc:" .. title .. "}}"
    barTitleClass = barTitleClass:gsub(' ', '-')
    barTitleClass = barTitleClass:gsub('\'', '')
    barTitleClass = barTitleClass:gsub('\"', '')
    local barText = "{{ucfirst:" .. title .. "}}"
    if link then barText = "[[" .. link .. "|" .. barText .. "]]" end
    barText = barText .. ":"
    barTitle:addClass('ability-bar-title'):wikitext(frame:preprocess(barText))
        :css('font-weight', 'bold'):css('margin-right', '0.5em')
    local barContent = mw.html.create('span')
    barContent:addClass('ability-bar-content'):wikitext(content)
    local output = mw.html.create('div')
    if bottom then output = mw.html.create('span') end
    if not flags then flags = {} end
    for k, v in pairs(flags) do
        if type(v) == 'string' then
            output:addClass('flag-' .. k .. '-' .. v)
        end
    end
    output:addClass('ability-bar'):addClass('ability-bar-' .. barTitleClass)
        :attr(flags):attr('test', 'out'):node(barSpace):node(barTitle):node(
            barSpace):node(barContent):css('display', 'block')
    -- if flags then
    -- for k, v in ipairs(flags) do
    --     output:attr(k, v)
    -- end
    -- end
    return tostring(output)
end

-- === NEW: shared abilities parsing helpers ===
local function parseAbilitiesString(abilitiesString)
    local abilities = {}
    for ability in mw.text.gsplit(abilitiesString or '', ";;", true) do
        ability = mw.text.trim(ability)
        if ability ~= "" then
            local name = ability
            local limited, improved = "", ""
            local gifted, adept = false, false

            -- flags
            if mw.ustring.find(ability, "%-g%-") then gifted = true end
            if mw.ustring.find(ability, "%-a%-") then adept = true end

            -- notes (order-insensitive, tolerate any following marker)
            local limitedMatch = mw.ustring.match(ability,
                                                  "%-l%-(.-)(%-[liag]%-|$)") or
                                     mw.ustring.match(ability, "%-l%-(.+)$")
            if limitedMatch then limited = mw.text.trim(limitedMatch) end

            local improvedMatch = mw.ustring.match(ability,
                                                   "%-i%-(.-)(%-[liag]%-|$)") or
                                      mw.ustring.match(ability, "%-i%-(.+)$")
            if improvedMatch then
                improved = mw.text.trim(improvedMatch)
            end

            -- clean name (strip markers + payloads/flags)
            name = mw.ustring.gsub(name, "%-l%-.+", "")
            name = mw.ustring.gsub(name, "%-i%-.+", "")
            name = mw.ustring.gsub(name, "%-g%-", "")
            name = mw.ustring.gsub(name, "%-a%-", "")
            name = mw.text.trim(name)

            table.insert(abilities, {
                name = name,
                limited = limited,
                improved = improved,
                gifted = gifted,
                adept = adept
            })
        end
    end

    table.sort(abilities, function(a, b) return a.name < b.name end)
    return abilities
end

local function buildAETransclusions(abilities)
    local transclusions = {}
    for _, a in ipairs(abilities or {}) do
        local t = {"{{AE|", a.name}
        if a.limited ~= "" then table.insert(t, "|limited=" .. a.limited) end
        if a.improved ~= "" then
            table.insert(t, "|improved=" .. a.improved)
        end
        if a.gifted then table.insert(t, "|gifted=1") end
        if a.adept then table.insert(t, "|adept=1") end
        table.insert(t, "}}")
        table.insert(transclusions, table.concat(t))
    end
    -- no visible separators between AE templates
    return table.concat(transclusions, "")
end

-- === Body parts parsing helpers ===
local function parseBodyPartsString(bodyPartsString)
    local bodyParts = {}
    for part in mw.text.gsplit(bodyPartsString or '', ";;", true) do
        part = mw.text.trim(part)
        if part ~= "" then table.insert(bodyParts, part) end
    end

    table.sort(bodyParts)
    return bodyParts
end

local function buildBPTransclusions(bodyParts)
    local transclusions = {}
    for _, part in ipairs(bodyParts or {}) do
        table.insert(transclusions, "{{BP|" .. part .. "}}")
    end
    return table.concat(transclusions, "")
end

-- === Equipment parsing helpers ===
local function parseEquipmentString(equipmentString)
    local equipment = {}
    for part in mw.text.gsplit(equipmentString or '', ";;", true) do
        part = mw.text.trim(part)
        if part ~= "" then table.insert(equipment, part) end
    end

    table.sort(equipment)
    return equipment
end

local function buildEQTransclusions(equipment)
    local transclusions = {}
    for _, part in ipairs(equipment or {}) do
        table.insert(transclusions, "{{EQ|" .. part .. "}}")
    end
    return table.concat(transclusions, "")
end
-- === END helpers ===

function p.c(frame)
    local args = frame.args
    local rawAbilities = args['a'] or ''
    local rawBodyParts = args['b'] or ''
    local rawEquipment = args['e'] or ''

    -- === TAGS & CATEGORIES via TagBuilder ===
    local TagBuilder = require('Module:TagBuilder')
    local DiceEval = require('Module:DiceEval')

    local typeRaw = args['c'] or ''
    local traitRaw = args['t'] or ''
    local looks = args['l'] or ''
    local description = args['d'] or ''
    local ranks = args['r'] or ''
    local regionRaw = args['region'] or args['g'] or ''
    local name = args['name'] or ''
    local attributeSizeIncrease = args['as'] or ''
    local hpDice = args['hp'] or '1d10'
    local mpDice = args['mp'] or '1d10'
    local hpDiceNumberStep = args['hpn'] or ''
    local mpDiceNumberStep = args['mpn'] or ''
    local hpDiceNumberText = 'hit die'
    local mpDiceNumberText = 'mana die'
    if hpDiceNumberStep:len() > 0 then
    	if hpDiceNumberStep == '1' then
    		hpDiceNumberStep = ''
		else
			hpDiceNumberStep = hpDiceNumberStep .. ' '
		end
        hpDiceNumberText = hpDiceNumberStep .. 'hit dice'
    end
    if mpDiceNumberStep:len() > 0 then
    	if mpDiceNumberStep == '1' then
    		mpDiceNumberStep = ''
		else
			mpDiceNumberStep = mpDiceNumberStep .. ' '
		end
        mpDiceNumberText = mpDiceNumberStep .. 'mana dice'
    end
    local hpDiceSizeStep = args['hps'] or ''
    local mpDiceSizeStep = args['mps'] or ''
    local hpDiceMinSize = args['hpm'] or ''
    local mpDiceMinSize = args['mpm'] or ''
    if hpDiceMinSize:len() > 0 then
        hpDiceMinSize = " above " .. hpDiceMinSize
    end
    if mpDiceMinSize:len() > 0 then
        mpDiceMinSize = " above " .. mpDiceMinSize
    end
    if hpDiceSizeStep:len() > 0 then
        local hpPlural = 's'
        if hpDiceSizeStep == '1' then hpPlural = '' end
        hpDiceSizeStep = "Gain another " .. hpDiceNumberText .. " for every " ..
                             hpDiceSizeStep .. " additional size" .. hpPlural ..
                             hpDiceMinSize .. "."
    end
    if mpDiceSizeStep:len() > 0 then
        local mpPlural = 's'
        if mpDiceSizeStep == '1' then mpPlural = '' end
        mpDiceSizeStep = "Gain another " .. mpDiceNumberText .. " for every " ..
                             mpDiceSizeStep .. " additional size" .. mpPlural ..
                             mpDiceMinSize .. "."
    end
    local baseHp = '0'
    local baseMp = '0'
    local size = args['size'] or '3'
    local br = args['br'] or '0'
    local int = args['int'] or '0'
    local str = args['str'] or '0'
    local mov = args['mov'] or '0'
    local snk = args['snk'] or '0'
    local per = args['per'] or '0'

    local preAbilities = ''
    if (int == '0') then
        preAbilities = preAbilities .. "Normal Intelligence;; "
    end
    if (str == '0') then preAbilities = preAbilities .. "Normal Strength;; " end
    if (mov == '0') then preAbilities = preAbilities .. "Normal Movement;; " end
    if (snk == '0') then preAbilities = preAbilities .. "Normal Sneak;; " end
    if (per == '0') then
        preAbilities = preAbilities .. "Normal Perception;; "
    end

    local abilitiesString = preAbilities .. rawAbilities
    local opts = {
        name = name,
        ns = 'Creature',
        transparent = args.o or args.transparent
    }
    local traitOut, traitCatOut = TagBuilder.build(traitRaw, 'TagCreatures',
                                                   'traits', opts)
    local typeOut, typeCatOut = TagBuilder.build(typeRaw, 'TagCreatures',
                                                 'types', opts)
    local regionOut, regionCatOut = TagBuilder.build(regionRaw, 'TagCreatures', 'regions', opts)

    -- === Parse and render main abilities with shared helpers ===
    local abilities = parseAbilitiesString(abilitiesString)
    local inner = buildAETransclusions(abilities)

    -- === Parse body parts ===
    local bodyParts = parseBodyPartsString(rawBodyParts)
    local bodyPartsInner = buildBPTransclusions(bodyParts)

    -- === Parse equipment ===
    local equipment = parseEquipmentString(rawEquipment)
    local equipmentInner = buildEQTransclusions(equipment)

    local categoryString = '[[Category:Creatures]]'
    local statContent = ''
    local inheritedStats = false

    if (hpDice == 'x') then
        statContent = statContent ..
                          "{{Tag|Variable Hit Dice|❤️|l=:Category:Variable hit die creatures|c=#e01b24|class=hp-dice-tagged x}}"
        categoryString = categoryString ..
                             "[[Category:Variable hit die creatures]]"
        inheritedStats = true
    else
        baseHp = tostring(DiceEval._eval(hpDice))
        statContent = statContent .. "{{Tag|" .. hpDice ..
                          " Hit Dice|❤️|l=:Category:" .. hpDice ..
                          " hit die creatures|c=#e01b24|class=hp-dice-tagged " ..
                          hpDice .. "}}" .. "{{Tag|" .. baseHp ..
                          " HP|❤️|l=:Category:" .. baseHp ..
                          " HP creatures|c=#e01b24|class=cost-tagged hp" ..
                          baseHp .. "}}"
        categoryString = categoryString .. "[[Category:" .. hpDice ..
                             " hit die creatures" .. "]]"
        categoryString = categoryString .. "[[Category:" .. baseHp ..
                             " HP creatures" .. "]]"
    end
    if (mpDice == 'x') then
        statContent = statContent ..
                          "{{Tag|Variable Mana Dice|🩵|l=:Category:Variable mana die creatures|c=#3584e4|class=mp-dice-tagged x}}"
        categoryString = categoryString ..
                             "[[Category:Variable mana die creatures]]"
        inheritedStats = true
    else
        baseMp = tostring(DiceEval._eval(mpDice))
        statContent = statContent .. "{{Tag|" .. mpDice ..
                          " Mana Dice|🩵|l=:Category:" .. mpDice ..
                          " mana die creatures|c=#3584e4|class=mp-dice-tagged " ..
                          mpDice .. "}}" .. "{{Tag|" .. baseMp ..
                          " MP|🩵|l=:Category:" .. baseMp ..
                          " MP creatures|c=#3584e4|class=cost-tagged hp" ..
                          baseMp .. "}}"
        categoryString = categoryString .. "[[Category:" .. baseMp ..
                             " MP creatures" .. "]]"
        categoryString = categoryString .. "[[Category:" .. mpDice ..
                             " mana die creatures" .. "]]"
    end
    if (size == 'x') then
        statContent = statContent ..
                          "{{Tag|Variable Size|⬆️|l=:Category:Variable size creatures|c=#e4a835|class=size-tagged x}}"
        categoryString = categoryString ..
                             "[[Category:Variable size creatures]]"
        inheritedStats = true
    else
        statContent = statContent .. "{{Tag|Size " .. size ..
                          "|⬆️|l=:Category:Size " .. size ..
                          " creatures|c=#e4a835|class=size-tagged size" .. size ..
                          "}}"
        categoryString = categoryString .. "[[Category:Size " .. size ..
                             " creatures" .. "]]"
    end

    local attributeContent = ''
    attributeContent = ""
    if (int ~= 'x') then
        attributeContent = attributeContent .. "{{Tag|" .. int ..
                               "|INT|l=:Category:INT value of " .. int ..
                               " creatures}}"
        categoryString = categoryString .. "[[Category:INT value of " .. int ..
                             " creatures]]"
    end
    if (str ~= 'x') then
        attributeContent = attributeContent .. "{{Tag|" .. str ..
                               "|STR|l=:Category:STR value of " .. str ..
                               " creatures}}"
        categoryString = categoryString .. "[[Category:STR value of " .. str ..
                             " creatures]]"
    end
    if (mov ~= 'x') then
        attributeContent = attributeContent .. "{{Tag|" .. mov ..
                               "|MOV|l=:Category:MOV value of " .. mov ..
                               " creatures}}"
        categoryString = categoryString .. "[[Category:MOV value of " .. mov ..
                             " creatures]]"
    end
    if (snk ~= 'x') then
        attributeContent = attributeContent .. "{{Tag|" .. snk ..
                               "|SNK|l=:Category:SNK value of " .. snk ..
                               " creatures}}"
        categoryString = categoryString .. "[[Category:SNK value of " .. snk ..
                             " creatures]]"
    end
    if (per ~= 'x') then
        attributeContent = attributeContent .. "{{Tag|" .. per ..
                               "|PER|l=:Category:PER value of " .. per ..
                               " creatures}}"
        categoryString = categoryString .. "[[Category:PER value of " .. per ..
                             " creatures]]"
    end
    traitContent = (traitOut or '') .. "{{Tag|BR " .. br ..
                       "|⚠️|l=:Category:BR " .. br ..
                       " creatures|class=br-tagged br" .. br .. "}}"
    typeContent = (typeOut or '')
    regionContent = (regionOut or '')
    categoryString = categoryString .. "[[Category:BR " .. br .. " creatures]]"

    local out = ""
    out = out .. makeBar(frame, "Stats", statContent, nil, nil, false)
    out = out .. makeBar(frame, "Attributes", attributeContent, nil, nil, false)
    out = out .. makeBar(frame, "Traits", traitContent, nil, nil, false)
    out = out .. makeBar(frame, "Creature types", typeContent, nil, nil, false)
    out = out .. makeBar(frame, "Regions", regionContent, nil, nil, false)
    out = out .. makeBar(frame, "Looks", looks, nil, nil, false)
    out = out .. makeBar(frame, "Innate ranks", ranks, nil, nil, false)
    out = out .. makeBar(frame, "HP increase", hpDiceSizeStep, nil, nil, false)
    out = out .. makeBar(frame, "MP increase", mpDiceSizeStep, nil, nil, false)
    out = out ..
              makeBar(frame, "Attribute increase", attributeSizeIncrease, nil,
                      nil, false)
    if (inheritedStats) then
        out = out .. makeBar(frame, "Inherited stats",
                             "Variable stats are inherited from the parent {{L|Category|Creatures|creature}}.",
                             nil, nil, false)
    end
    if args['lifespan'] then
        local lifespanText = args['lifespan'] .. " years."
        categoryString = categoryString .. "[[Category:" .. args['lifespan'] ..
                             " year lifespan creatures]]"
        if args['adult'] then
            lifespanText = lifespanText .. " Adult at age " .. args['adult'] ..
                               "."
        end
        out = out .. makeBar(frame, "Lifespan", lifespanText, nil, nil, false)
    end

    out = out .. '<hr>'
    if description:len() > 0 then
        out = out .. '<span class="creature-description>' .. args['d'] ..
                  '</span><hr>'
    end

    -- main abilities block
    out = out .. makeBar(frame, "Abilities",
                         '<div class="expandable-table"><div>' ..
                             frame:preprocess(inner) .. '</div></div>', nil,
                         nil, false)

    -- === body parts block (after abilities, before equipment) ===
    if rawBodyParts ~= '' and #bodyParts > 0 then
        out = out .. '<hr>' .. makeBar(frame, "Body Parts",
                                       '<div class="expandable-table"><div>' ..
                                           frame:preprocess(bodyPartsInner) ..
                                           '</div></div>', nil, nil, false)
    end

    -- === equipment block (after body parts, before familiar abilities) ===
    if rawEquipment ~= '' and #equipment > 0 then
        out = out .. '<hr>' .. makeBar(frame, "Equipment",
                                       '<div class="expandable-table"><div>' ..
                                           frame:preprocess(equipmentInner) ..
                                           '</div></div>', nil, nil, false)
    end

    -- === familiar abilities (args['f']) using the same parsing ===
    if args['f'] and mw.text.trim(args['f']) ~= '' then
        local fAbilities = parseAbilitiesString(args['f'])
        local fInner = buildAETransclusions(fAbilities)
        -- You can wrap in a bar or just mirror the main output style; here we label it:
        out = out .. '<hr>' .. makeBar(frame, "Familiar Abilities",
                                       frame:preprocess(
                                           '<div class="expandable-table"><div>' ..
                                               fInner .. '</div></div>'), nil,
                                       nil, false)
    end

    out = out .. (traitCatOut or '') .. (typeCatOut or '') .. (regionCatOut or '')

    local page = mw.title.getCurrentTitle().text
    local namespace = frame:preprocess('{{NAMESPACE}}')
    if ((namespace == "Creature") and (page == name)) then
        out = out .. categoryString
    end

    return frame:preprocess(out)
end

return p