More actions
Content deleted Content added
// via Wikitext Extension for VSCode Tag: Reverted |
// via Wikitext Extension for VSCode Tag: Manual revert |
||
Line 1: | Line 1: | ||
local p = {} |
local p = {} |
||
local p = {} |
|||
-- ========= Category helpers ========= |
|||
-- Memoize category lookups within a single parse |
|||
local _catMemo = {} |
|||
-- Resolve a raw ability name to a proper Title in the Ability namespace |
|||
local function resolveAbilityTitle(raw) |
|||
if not raw or raw == "" then return nil end |
|||
-- If caller already gave a full title and it exists, use it as-is |
|||
local t = mw.title.new(raw) |
|||
if t and t.exists then return t end |
|||
-- Otherwise, try in the Ability namespace by id |
|||
local ns = mw.site.namespaces and mw.site.namespaces["Ability"] |
|||
if ns and ns.id then |
|||
local t2 = mw.title.new(raw, ns.id) -- "Ability:raw" |
|||
if t2 and t2.exists then return t2 end |
|||
end |
|||
-- Fallback: explicit "Ability:" prefix |
|||
if not mw.ustring.find(raw, "^Ability:") then |
|||
local t3 = mw.title.new("Ability:" .. raw) |
|||
if t3 and t3.exists then return t3 end |
|||
end |
|||
return nil |
|||
end |
|||
-- Robust category check using parent categories (handles categories added via templates) |
|||
local function pageInCategory(pagetitle, cat) |
|||
if not pagetitle or pagetitle == "" or not cat or cat == "" then return false end |
|||
local memoKey = pagetitle .. "@@" .. cat |
|||
if _catMemo[memoKey] ~= nil then |
|||
return _catMemo[memoKey] |
|||
end |
|||
local title = resolveAbilityTitle(pagetitle) |
|||
if not title then |
|||
_catMemo[memoKey] = false |
|||
return false |
|||
end |
|||
-- Follow redirects |
|||
if title.isRedirect and title.redirectTarget and title.redirectTarget.exists then |
|||
title = title.redirectTarget |
|||
end |
|||
local parents = title:getParentCategories() |
|||
if not parents then |
|||
_catMemo[memoKey] = false |
|||
return false |
|||
end |
|||
local want = "Category:" .. cat |
|||
for parentCat in pairs(parents) do |
|||
if parentCat == want then |
|||
_catMemo[memoKey] = true |
|||
return true |
|||
end |
|||
end |
|||
_catMemo[memoKey] = false |
|||
return false |
|||
end |
|||
-- ========= Rendering helper ========= |
|||
-- Build AE template from an ability object |
|||
local function aeTemplate(a) |
|||
local parts = {"{{AE|", a.name} |
|||
if a.limited and a.limited ~= "" then table.insert(parts, "|limited=" .. a.limited) end |
|||
if a.improved and a.improved ~= "" then table.insert(parts, "|improved=" .. a.improved) end |
|||
if a.gifted then table.insert(parts, "|gifted=1") end |
|||
if a.adept then table.insert(parts, "|adept=1") end |
|||
table.insert(parts, "}}") |
|||
return table.concat(parts) |
|||
end |
|||
-- ========= Main entry ========= |
|||
function p.c(frame) |
function p.c(frame) |
||
Line 87: | Line 7: | ||
local rawAbilities = args['a'] or '' |
local rawAbilities = args['a'] or '' |
||
-- Parse abilities into a structured table |
|||
-- Prepopulate defaults |
|||
local abilities = {} |
local abilities = {} |
||
local indexByName = {} |
|||
local function addAbility(obj, isDefault) |
|||
local key = mw.text.trim(obj.name or "") |
|||
if key == "" then return end |
|||
obj.name = key |
|||
if indexByName[key] then |
|||
abilities[indexByName[key]] = obj |
|||
else |
|||
table.insert(abilities, obj) |
|||
indexByName[key] = #abilities |
|||
end |
|||
if isDefault then abilities[indexByName[key]]._isDefault = true end |
|||
end |
|||
local function removeByName(name) |
|||
local i = indexByName[name] |
|||
if not i then return end |
|||
table.remove(abilities, i) |
|||
-- rebuild index |
|||
indexByName = {} |
|||
for idx, ab in ipairs(abilities) do |
|||
indexByName[ab.name] = idx |
|||
end |
|||
end |
|||
-- Defaults |
|||
local defaults = { |
|||
"Normal Intelligence", |
|||
"Normal Strength", |
|||
"Normal Movement", |
|||
"Normal Sneak", |
|||
"Normal Perception", |
|||
} |
|||
for _, n in ipairs(defaults) do |
|||
addAbility({ |
|||
name = n, limited = "", improved = "", gifted = false, adept = false |
|||
}, true) |
|||
end |
|||
-- Parse incoming abilities |
|||
local incomingNames = {} -- keep a list to check categories against (only incoming) |
|||
for ability in mw.text.gsplit(rawAbilities, ";;", true) do |
for ability in mw.text.gsplit(rawAbilities, ";;", true) do |
||
ability = mw.text.trim(ability) |
ability = mw.text.trim(ability) |
||
Line 138: | Line 16: | ||
local gifted, adept = false, false |
local gifted, adept = false, false |
||
-- flags |
|||
if mw.ustring.find(ability, "%-g%-") then gifted = true end |
if mw.ustring.find(ability, "%-g%-") then gifted = true end |
||
if mw.ustring.find(ability, "%-a%-") then adept = 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]%-|$)") |
local limitedMatch = mw.ustring.match(ability, "%-l%-(.-)(%-[liag]%-|$)") |
||
or mw.ustring.match(ability, "%-l%-(.+)$") |
or mw.ustring.match(ability, "%-l%-(.+)$") |
||
Line 150: | Line 29: | ||
if improvedMatch then improved = mw.text.trim(improvedMatch) end |
if improvedMatch then improved = mw.text.trim(improvedMatch) end |
||
-- |
-- clean name (strip markers + their payloads/flags) |
||
name = mw.ustring.gsub(name, "%-l%-.+", "") |
name = mw.ustring.gsub(name, "%-l%-.+", "") |
||
name = mw.ustring.gsub(name, "%-i%-.+", "") |
name = mw.ustring.gsub(name, "%-i%-.+", "") |
||
Line 157: | Line 36: | ||
name = mw.text.trim(name) |
name = mw.text.trim(name) |
||
table.insert(abilities, { |
|||
addAbility({ |
|||
name = name, |
name = name, |
||
limited = limited, |
limited = limited, |
||
Line 163: | Line 42: | ||
gifted = gifted, |
gifted = gifted, |
||
adept = adept |
adept = adept |
||
} |
}) |
||
table.insert(incomingNames, name) |
|||
end |
end |
||
end |
end |
||
-- Sort alphabetically by name (stable) |
|||
-- If any incoming ability is in the mapped category, remove the corresponding default |
|||
table.sort(abilities, function(a, b) return a.name < b.name end) |
|||
local catMap = { |
|||
["Normal Intelligence"] = "INT setting abilities", |
|||
["Normal Strength"] = "STR setting abilities", |
|||
["Normal Movement"] = "MOV setting abilities", |
|||
["Normal Sneak"] = "SNK setting abilities", |
|||
["Normal Perception"] = "PER setting abilities", |
|||
} |
|||
-- Build the out string (NO visible separators between AE templates) |
|||
local shouldRemove = { |
|||
local transclusions = {} |
|||
["Normal Intelligence"] = false, |
|||
for _, a in ipairs(abilities) do |
|||
["Normal Strength"] = false, |
|||
local t = {"{{AE|", a.name} |
|||
["Normal Movement"] = false, |
|||
if a.limited ~= "" then table.insert(t, "|limited=" .. a.limited) end |
|||
["Normal Sneak"] = false, |
|||
if a.improved ~= "" then table.insert(t, "|improved=" .. a.improved) end |
|||
["Normal Perception"] = false, |
|||
if a.gifted then table.insert(t, "|gifted=1") end |
|||
} |
|||
if a.adept then table.insert(t, "|adept=1") end |
|||
table.insert(t, "}}") |
|||
for _, inName in ipairs(incomingNames) do |
|||
table.insert(transclusions, table.concat(t)) |
|||
for normalName, cat in pairs(catMap) do |
|||
if not shouldRemove[normalName] and pageInCategory(inName, cat) then |
|||
shouldRemove[normalName] = true |
|||
end |
|||
end |
|||
end |
end |
||
-- Use "" (or "\n") so no semicolons are emitted |
|||
for normalName, flag in pairs(shouldRemove) do |
|||
local inner = table.concat(transclusions, "") -- <- changed from ";; " |
|||
if flag then |
|||
removeByName(normalName) |
|||
end |
|||
end |
|||
-- Sort alphabetically by name (case-insensitive, stable on original) |
|||
table.sort(abilities, function(a, b) |
|||
local aa, bb = mw.ustring.lower(a.name), mw.ustring.lower(b.name) |
|||
if aa == bb then return a.name < b.name end |
|||
return aa < bb |
|||
end) |
|||
-- Assemble output and expand the AE templates right here |
|||
local outParts = {} |
|||
for _, a in ipairs(abilities) do |
|||
table.insert(outParts, aeTemplate(a)) |
|||
end |
|||
local inner = table.concat(outParts, "") |
|||
local out = '<div class="expandable-table"><div>' .. frame:preprocess(inner) .. '</div></div>' |
local out = '<div class="expandable-table"><div>' .. frame:preprocess(inner) .. '</div></div>' |
||
return out |
return out |
||
end |
end |
Revision as of 06:04, 12 August 2025
Documentation for this module may be created at Module:C/doc
local p = {}
local p = {}
function p.c(frame)
local args = frame.args
local rawAbilities = args['a'] or ''
-- Parse abilities into a structured table
local abilities = {}
for ability in mw.text.gsplit(rawAbilities, ";;", 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 + their 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
-- Sort alphabetically by name (stable)
table.sort(abilities, function(a, b) return a.name < b.name end)
-- Build the out string (NO visible separators between AE templates)
local transclusions = {}
for _, a in ipairs(abilities) 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
-- Use "" (or "\n") so no semicolons are emitted
local inner = table.concat(transclusions, "") -- <- changed from ";; "
local out = '<div class="expandable-table"><div>' .. frame:preprocess(inner) .. '</div></div>'
return out
end
return p