Module:GamePassives: Difference between revisions
From SpiritVale Wiki
More actions
No edit summary |
No edit summary |
||
| (9 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
-- Module:GamePassives | -- Module:GamePassives | ||
-- | -- | ||
-- | -- Phase 6.5+ Slot/Grid architecture (aligned with Module:GameSkills). | ||
-- | -- | ||
-- Layout: | |||
-- Row 1: Slot 1 + Slot 2 (IconName + Description) | |||
-- Row 2: Slot 3 + Slot 4 (LevelSelector + PassiveEffects) | |||
-- | |||
-- Requires Common.js: | |||
-- - updates .sv-dyn spans via data-series | |||
-- - updates .sv-level-num + data-level on .sv-passive-card | |||
-- - binds to input.sv-level-range inside each card | |||
-- | -- | ||
-- Usage (single passive): | -- Usage (single passive): | ||
| Line 18: | Line 26: | ||
---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ||
-- | -- Data cache | ||
---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ||
| Line 24: | Line 32: | ||
local function getPassives() | local function getPassives() | ||
if not passivesCache then | |||
passivesCache = GameData.loadPassives() | |||
end | |||
return passivesCache | |||
end | end | ||
---------------------------------------------------------------------- | |||
-- Small utilities | |||
---------------------------------------------------------------------- | |||
local function getArgs(frame) | local function getArgs(frame) | ||
local parent = frame:getParent() | |||
return (parent and parent.args) or frame.args | |||
return parent.args | |||
end | end | ||
local function | local function trim(s) | ||
if type(s) ~= "string" then | |||
return nil | |||
end | |||
s = mw.text.trim(s) | |||
return (s ~= "" and s) or nil | |||
end | |||
local function toNum(v) | |||
if type(v) == "number" then | |||
return v | |||
end | |||
if type(v) == "string" then | |||
return tonumber(v) | |||
end | |||
if type(v) == "table" and v.Value ~= nil then | |||
return toNum(v.Value) | |||
end | |||
return nil | return nil | ||
end | end | ||
local function | local function clamp(n, lo, hi) | ||
if type(n) ~= "number" then | |||
return | return lo | ||
end | |||
if n < lo then return lo end | |||
if n > hi then return hi end | |||
return n | |||
end | |||
local function fmtNum(n) | |||
if type(n) ~= "number" then | |||
return (n ~= nil) and tostring(n) or nil | |||
end | |||
if math.abs(n - math.floor(n)) < 1e-9 then | |||
return tostring(math.floor(n)) | |||
end | |||
local s = string.format("%.4f", n) | |||
s = mw.ustring.gsub(s, "0+$", "") | |||
s = mw.ustring.gsub(s, "%.$", "") | |||
return s | |||
end | |||
local function isNoneLike(v) | |||
if v == nil then return true end | |||
local s = mw.text.trim(tostring(v)) | |||
if s == "" then return true end | |||
s = mw.ustring.lower(s) | |||
return (s == "none" or s == "no" or s == "n/a" or s == "na" or s == "null") | |||
end | |||
local function isFlatList(list) | |||
if type(list) ~= "table" or #list == 0 then | |||
return false | |||
end | |||
local first = tostring(list[1]) | |||
for i = 2, #list do | |||
if tostring(list[i]) ~= first then | |||
return false | |||
end | |||
end | |||
return true | |||
end | |||
---------------------------------------------------------------------- | |||
-- Dynamic spans (JS-driven) | |||
---------------------------------------------------------------------- | |||
local function dynSpan(series, level) | |||
if type(series) ~= "table" or #series == 0 then | |||
return nil | |||
end | |||
level = clamp(level or #series, 1, #series) | |||
local span = mw.html.create("span") | |||
span:addClass("sv-dyn") | |||
span:attr("data-series", mw.text.jsonEncode(series)) | |||
span:wikitext(mw.text.nowiki(series[level] or "")) | |||
return tostring(span) | |||
end | |||
local function seriesFromValuePair(block, maxLevel) | |||
if type(block) ~= "table" then | |||
return nil | |||
end | |||
local base = block.Base | |||
local per = block["Per Level"] | |||
local function pickUnit(v) | |||
if type(v) == "table" and v.Unit and v.Unit ~= "" then | |||
return v.Unit | |||
end | |||
return nil | |||
end | |||
local unit = pickUnit(base) or pickUnit(per) | |||
local function fmtAny(v) | |||
if type(v) == "table" and v.Value ~= nil then | |||
if v.Unit then | |||
return tostring(v.Value) .. " " .. tostring(v.Unit) | |||
end | |||
return tostring(v.Value) | |||
end | |||
return (v ~= nil) and tostring(v) or nil | |||
end | |||
local series = {} | |||
if type(per) == "table" and #per > 0 then | |||
for lv = 1, maxLevel do | |||
local raw = per[lv] or per[#per] | |||
local one = fmtAny(raw) | |||
if one == nil or isNoneLike(raw) or isNoneLike(one) or one == "0" then | |||
one = "—" | |||
end | |||
series[lv] = one | |||
end | |||
return series | |||
end | |||
if type(per) == "table" and #per == 0 then | |||
local one = fmtAny(base) | |||
if one == nil or isNoneLike(base) or isNoneLike(one) or one == "0" then | |||
one = "—" | |||
end | |||
for lv = 1, maxLevel do | |||
series[lv] = one | |||
end | |||
return series | |||
end | |||
local baseN = toNum(base) or 0 | |||
local perN = toNum(per) | |||
if perN ~= nil then | |||
for lv = 1, maxLevel do | |||
local total = baseN + (perN * lv) | |||
local v = unit and { Value = total, Unit = unit } or total | |||
local one = fmtAny(v) | |||
if one == nil or total == 0 then | |||
one = "—" | |||
end | |||
series[lv] = one | |||
end | |||
return series | |||
end | |||
local raw = (base ~= nil) and base or per | |||
local one = fmtAny(raw) | |||
if one == nil then | |||
return nil | |||
end | |||
if one == "0" or isNoneLike(raw) then | |||
one = "—" | |||
end | |||
for lv = 1, maxLevel do | |||
series[lv] = one | |||
end | |||
return series | |||
end | end | ||
local function | local function displayFromSeries(series, level) | ||
if type(series) ~= "table" or #series == 0 then | |||
return nil | |||
end | |||
local any = false | |||
for _, v in ipairs(series) do | |||
if v ~= "—" then | |||
any = true | |||
break | |||
end | |||
end | |||
if not any then | |||
return nil | |||
end | |||
if isFlatList(series) then | |||
return mw.text.nowiki(series[1]) | |||
end | |||
return dynSpan(series, level) | |||
end | end | ||
-- | ---------------------------------------------------------------------- | ||
-- Lookups | |||
---------------------------------------------------------------------- | |||
local function getPassiveById(id) | local function getPassiveById(id) | ||
id = trim(id) | |||
if not id then return nil end | |||
local dataset = getPassives() | |||
local byId = dataset.byId or {} | |||
return byId[id] | |||
end | end | ||
local function findPassiveByName(name) | local function findPassiveByName(name) | ||
name = trim(name) | |||
if not name then return nil end | |||
local dataset = getPassives() | |||
for _, rec in ipairs(dataset.records or {}) do | |||
if type(rec) == "table" then | |||
local display = rec["External Name"] or rec.Name or rec["Display Name"] | |||
if display == name then | |||
return rec | |||
end | |||
end | |||
end | |||
return nil | return nil | ||
end | |||
local function resolveDisplayName(rec) | |||
if rec | if type(rec) ~= "table" then | ||
return tostring(rec or "") | |||
end | end | ||
return rec["External Name"] or rec.Name or rec["Display Name"] or rec["Internal Name"] or rec.ID or "Unknown Passive" | |||
end | end | ||
---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ||
-- | -- User matching (for auto lists on class pages) | ||
---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ||
local function | local function passiveMatchesUser(rec, userName) | ||
if type(rec) ~= "table" or not userName or userName == "" then | |||
return false | |||
end | |||
local users = rec.Users | |||
if type(users) ~= "table" then | |||
return false | |||
end | |||
local userLower = mw.ustring.lower(userName) | |||
local function listHas(list) | |||
if type(list) ~= "table" then | |||
return false | |||
end | |||
for _, v in ipairs(list) do | |||
if type(v) == "string" and mw.ustring.lower(v) == userLower then | |||
return true | |||
end | |||
end | |||
return false | |||
end | |||
return listHas(users.Classes) or listHas(users.Summons) or listHas(users.Monsters) or listHas(users.Events) | |||
end | end | ||
---------------------------------------------------------------------- | |||
-- Slot config | |||
---------------------------------------------------------------------- | |||
local HERO_SLOT_ASSIGNMENT = { | |||
[1] = "IconName", | |||
[2] = "Description", | |||
[3] = "LevelSelector", | |||
[4] = "PassiveEffects", | |||
} | |||
local PLUGINS = {} | |||
---------------------------------------------------------------------- | |||
-- Slot scaffolds | |||
---------------------------------------------------------------------- | |||
local function slotBox(slot, extraClasses, innerHtml, opts) | |||
opts = opts or {} | |||
local box = mw.html.create("div") | |||
box:addClass("sv-slot") | |||
box:addClass("sv-slot--" .. tostring(slot)) | |||
box:attr("data-hero-slot", tostring(slot)) | |||
if opts.isFull then | |||
box:addClass("sv-slot--full") | |||
end | end | ||
if extraClasses then | |||
if type(extraClasses) == "string" then | |||
box:addClass(extraClasses) | |||
elseif type(extraClasses) == "table" then | |||
for _, c in ipairs(extraClasses) do box:addClass(c) end | |||
end | |||
end | |||
if opts.isEmpty then | |||
box:addClass("sv-slot--empty") | |||
end | |||
local body = box:tag("div"):addClass("sv-slot__body") | |||
if innerHtml and innerHtml ~= "" then | |||
body:wikitext(innerHtml) | |||
end | |||
return tostring(box) | |||
end | end | ||
local function | local function normalizeResult(res) | ||
if res == nil then return nil end | |||
return nil | if type(res) == "string" then | ||
return { inner = res, classes = nil } | |||
end | |||
if type(res) == "table" then | |||
local inner = res.inner | |||
if type(inner) ~= "string" then | |||
inner = (inner ~= nil) and tostring(inner) or "" | |||
end | |||
return { inner = inner, classes = res.classes } | |||
end | |||
return { inner = tostring(res), classes = nil } | |||
end | |||
local function safeCallPlugin(name, rec, ctx) | |||
local fn = PLUGINS[name] | |||
if type(fn) ~= "function" then | |||
return nil | |||
end | |||
local ok, out = pcall(fn, rec, ctx) | |||
if not ok then | |||
return nil | |||
end | |||
return normalizeResult(out) | |||
end | |||
local function isEmptySlotContent(inner) | |||
if | if inner == nil then return true end | ||
local raw = tostring(inner) | |||
for _, pat in ipairs({ "sv%-dyn", "data%-series", "sv%-level%-range", "sv%-level%-slider", "sv%-level%-ui" }) do | |||
if mw.ustring.find(raw, pat) then | |||
return false | |||
if | |||
end | end | ||
end | |||
local trimmed = mw.text.trim(raw) | |||
if trimmed == "" or trimmed == "—" then | |||
return true | |||
end | |||
local withoutTags = mw.text.trim(mw.ustring.gsub(trimmed, "<[^>]+>", "")) | |||
return (withoutTags == "" or withoutTags == "—") | |||
end | |||
local function renderHeroSlot(slotIndex, rec, ctx) | |||
local pluginName = HERO_SLOT_ASSIGNMENT[slotIndex] | |||
if not pluginName then | |||
return nil | |||
end | |||
local res = safeCallPlugin(pluginName, rec, ctx) | |||
if not res or isEmptySlotContent(res.inner) then | |||
return nil | |||
end | |||
return { | |||
inner = res.inner, | |||
local | classes = res.classes, | ||
if | } | ||
end | |||
local function buildHeroSlotsUI(rec, ctx) | |||
local grid = mw.html.create("div") | |||
grid:addClass("sv-slot-grid") | |||
local slots = {} | |||
for slot = 1, 4 do | |||
slots[slot] = renderHeroSlot(slot, rec, ctx) | |||
end | |||
local hasSlots = false | |||
for _, pair in ipairs({ { 1, 2 }, { 3, 4 } }) do | |||
local left = slots[pair[1]] | |||
local right = slots[pair[2]] | |||
if left or right then | |||
hasSlots = true | |||
if left and right then | |||
grid:wikitext(slotBox(pair[1], left.classes, left.inner, { isEmpty = false })) | |||
grid:wikitext(slotBox(pair[2], right.classes, right.inner, { isEmpty = false })) | |||
elseif left then | |||
grid:wikitext(slotBox(pair[1], left.classes, left.inner, { isFull = true })) | |||
elseif right then | |||
grid:wikitext(slotBox(pair[2], right.classes, right.inner, { isFull = true })) | |||
end | |||
end | end | ||
end | |||
if not hasSlots then | |||
return "" | |||
end | |||
return tostring(grid) | |||
end | |||
local function addHeroSlotsRow(tbl, slotsUI) | |||
if not slotsUI or slotsUI == "" then | |||
return | |||
end | end | ||
local row = tbl:tag("tr") | |||
row:addClass("sv-slot-row") | |||
local cell = row:tag("td") | |||
cell:attr("colspan", 2) | |||
cell:addClass("sv-slot-cell") | |||
cell:wikitext(slotsUI) | |||
end | end | ||
local | ---------------------------------------------------------------------- | ||
-- Plug-ins | |||
---------------------------------------------------------------------- | |||
function PLUGINS.IconName(rec) | |||
local icon = rec.Icon | |||
local title = resolveDisplayName(rec) | |||
local notesList = {} | |||
if type(rec.Notes) == "table" then | |||
for _, note in ipairs(rec.Notes) do | |||
local n = trim(note) | |||
if n then | |||
table.insert(notesList, mw.text.nowiki(n)) | |||
end | |||
end | |||
elseif type(rec.Notes) == "string" then | |||
local n = trim(rec.Notes) | |||
if n then | |||
notesList = { mw.text.nowiki(n) } | |||
end | |||
end | |||
local req = rec.Requirements or {} | |||
local reqSkillsRaw = (type(req["Required Skills"]) == "table") and req["Required Skills"] or {} | |||
local reqWeaponsRaw = (type(req["Required Weapons"]) == "table") and req["Required Weapons"] or {} | |||
local reqStancesRaw = (type(req["Required Stances"]) == "table") and req["Required Stances"] or {} | |||
local reqSkills = {} | |||
for _, rs in ipairs(reqSkillsRaw) do | |||
if type(rs) == "table" then | |||
local nameReq = rs["Skill External Name"] or rs["Skill Internal Name"] or rs["Skill Name"] or rs["Skill ID"] or "Unknown" | |||
local lvlReq = rs["Required Level"] | |||
if lvlReq then | |||
table.insert(reqSkills, string.format("%s (Lv.%s)", mw.text.nowiki(nameReq), mw.text.nowiki(tostring(lvlReq)))) | |||
else | |||
table.insert(reqSkills, mw.text.nowiki(nameReq)) | |||
end | |||
end | |||
end | |||
local reqWeapons = {} | |||
for _, w in ipairs(reqWeaponsRaw) do | |||
local wn = trim(w) | |||
if wn then table.insert(reqWeapons, mw.text.nowiki(wn)) end | |||
end | |||
local reqStances = {} | |||
for _, s in ipairs(reqStancesRaw) do | |||
local sn = trim(s) | |||
if sn then table.insert(reqStances, mw.text.nowiki(sn)) end | |||
end | end | ||
local hasNotes = (#notesList > 0) | |||
local hasReq = (#reqSkills > 0) or (#reqWeapons > 0) or (#reqStances > 0) | |||
local wrap = mw.html.create("div") | |||
wrap:addClass("sv-herobar-1-wrap") | |||
wrap:addClass("sv-tip-scope") | |||
local | local iconBox = wrap:tag("div") | ||
iconBox:addClass("sv-herobar-icon") | |||
if icon and icon ~= "" then | |||
iconBox:wikitext(string.format("[[File:%s|80px|link=]]", icon)) | |||
if | |||
end | end | ||
local textBox = wrap:tag("div") | |||
textBox:addClass("sv-herobar-text") | |||
local titleRow = textBox:tag("div") | |||
titleRow:addClass("sv-herobar-title-row") | |||
local | local titleBox = titleRow:tag("div") | ||
titleBox:addClass("spiritvale-infobox-title") | |||
titleBox:wikitext(title) | |||
if hasNotes then | |||
local notesBtn = mw.html.create("span") | |||
notesBtn:addClass("sv-tip-btn sv-tip-btn--notes") | |||
notesBtn:attr("role", "button") | |||
notesBtn:attr("tabindex", "0") | |||
notesBtn:attr("data-sv-tip", "notes") | |||
notesBtn:attr("aria-label", "Notes") | |||
notesBtn:attr("aria-expanded", "false") | |||
notesBtn:tag("span"):addClass("sv-ico sv-ico--info"):attr("aria-hidden", "true"):wikitext("i") | |||
titleRow:node(notesBtn) | |||
end | |||
if hasReq then | |||
local pillRow = wrap:tag("div") | |||
pillRow:addClass("sv-pill-row") | |||
pillRow:addClass("sv-pill-row--req") | |||
local pill = pillRow:tag("span") | |||
pill:addClass("sv-pill sv-pill--req sv-tip-btn") | |||
pill:attr("role", "button") | |||
pill:attr("tabindex", "0") | |||
pill:attr("data-sv-tip", "req") | |||
pill:attr("aria-label", "Requirements") | |||
pill:attr("aria-expanded", "false") | |||
pill:wikitext("Requirements") | |||
end | end | ||
local | |||
if hasNotes then | |||
local notesContent = wrap:tag("div") | |||
table. | notesContent:addClass("sv-tip-content") | ||
notesContent:attr("data-sv-tip-content", "notes") | |||
notesContent:tag("div"):addClass("sv-tip-title"):wikitext("Notes") | |||
notesContent:tag("div"):wikitext(table.concat(notesList, "<br />")) | |||
end | end | ||
table. | |||
if hasReq then | |||
local reqContent = wrap:tag("div") | |||
reqContent:addClass("sv-tip-content") | |||
reqContent:attr("data-sv-tip-content", "req") | |||
if #reqSkills > 0 then | |||
local section = reqContent:tag("div") | |||
section:addClass("sv-tip-section") | |||
section:tag("span"):addClass("sv-tip-label"):wikitext("Required Skills") | |||
section:tag("div"):wikitext(table.concat(reqSkills, "<br />")) | |||
end | |||
if #reqWeapons > 0 then | |||
local section = reqContent:tag("div") | |||
section:addClass("sv-tip-section") | |||
section:tag("span"):addClass("sv-tip-label"):wikitext("Required Weapons") | |||
section:tag("div"):wikitext(table.concat(reqWeapons, ", ")) | |||
end | |||
if #reqStances > 0 then | |||
local section = reqContent:tag("div") | |||
section:addClass("sv-tip-section") | |||
section:tag("span"):addClass("sv-tip-label"):wikitext("Required Stances") | |||
section:tag("div"):wikitext(table.concat(reqStances, ", ")) | |||
end | |||
end | end | ||
return { | |||
inner = tostring(wrap), | |||
classes = "module-icon-name", | |||
} | |||
end | |||
function PLUGINS.Description(rec) | |||
local desc = trim(rec.Description) | |||
if not desc then | |||
return nil | |||
end | |||
local body = mw.html.create("div") | |||
body:addClass("sv-description") | |||
body:wikitext(string.format("''%s''", desc)) | |||
return { | |||
inner = tostring(body), | |||
classes = "module-description", | |||
} | |||
end | end | ||
-- | function PLUGINS.LevelSelector(rec, ctx) | ||
local level = ctx.level or 1 | |||
---- | local maxLevel = ctx.maxLevel or 1 | ||
local inner = mw.html.create("div") | |||
inner:addClass("sv-level-ui") | |||
inner:tag("div") | |||
:addClass("sv-level-label") | |||
:wikitext("Level <span class=\"sv-level-num\">" .. tostring(level) .. "</span> / " .. tostring(maxLevel)) | |||
local | local slider = inner:tag("div"):addClass("sv-level-slider") | ||
if tonumber(maxLevel) and tonumber(maxLevel) > 1 then | |||
slider:tag("input") | |||
:attr("type", "range") | |||
:attr("min", "1") | |||
:attr("max", tostring(maxLevel)) | |||
:attr("value", tostring(level)) | |||
:addClass("sv-level-range") | |||
:attr("aria-label", "Skill level select") | |||
else | |||
inner:addClass("sv-level-ui-single") | |||
slider:addClass("sv-level-slider-single") | |||
end | |||
return { | |||
inner = tostring(inner), | |||
classes = "module-level-selector", | |||
} | |||
end | |||
function PLUGINS.PassiveEffects(rec, ctx) | |||
if type( | local effects = rec["Passive Effects"] | ||
if type(effects) ~= "table" or #effects == 0 then | |||
return nil | |||
end | end | ||
for _, | |||
local root = mw.html.create("div") | |||
root:addClass("sv-passive-effects") | |||
root:addClass("sv-compact-root") | |||
local tbl = root:tag("div") | |||
tbl:addClass("sv-pe-table") | |||
local added = false | |||
local maxLevel = ctx.maxLevel or 1 | |||
local level = ctx.level or 1 | |||
for _, eff in ipairs(effects) do | |||
if type(eff) == "table" then | |||
local t = eff.Type or {} | |||
local label = t.Name or t.ID or eff.ID or "Unknown" | |||
local valueBlock = eff.Value | |||
local display | |||
if type(valueBlock) == "table" and (valueBlock.Base ~= nil or valueBlock["Per Level"] ~= nil or type(valueBlock["Per Level"]) == "table") then | |||
display = displayFromSeries(seriesFromValuePair(valueBlock, maxLevel), level) | |||
elseif valueBlock ~= nil then | |||
display = mw.text.nowiki(tostring(valueBlock)) | |||
end | |||
if not display and type(valueBlock) == "table" then | |||
local expr = valueBlock.Expression or valueBlock.expression | |||
if expr and trim(tostring(expr)) then | |||
display = mw.text.nowiki(tostring(expr)) | |||
end | |||
end | |||
if not display then | |||
local expr = eff.Expression or eff.expression | |||
if expr and trim(tostring(expr)) then | |||
display = mw.text.nowiki(tostring(expr)) | |||
end | |||
end | |||
local qual = eff.Weapon or eff["Weapon"] or eff["Weapon Type"] or eff.Stance or eff["Stance"] or eff["Stance Type"] | |||
if display and qual and trim(tostring(qual)) then | |||
display = tostring(display) .. " (" .. mw.text.nowiki(tostring(qual)) .. ")" | |||
end | |||
if display then | |||
added = true | |||
local row = tbl:tag("div") | |||
row:addClass("sv-pe-row") | |||
row:tag("div"):addClass("sv-pe-label"):wikitext(mw.text.nowiki(label)) | |||
row:tag("div"):addClass("sv-pe-value"):wikitext(display) | |||
end | |||
end | |||
end | end | ||
if not added then | |||
return nil | |||
end | |||
return { | |||
inner = tostring(root), | |||
classes = "module-passive-effects", | |||
} | |||
end | end | ||
| Line 329: | Line 729: | ||
---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ||
local function buildInfobox(rec) | local function buildInfobox(rec, opts) | ||
opts = opts or {} | |||
local maxLevel = tonumber(rec["Max Level"]) or 1 | |||
if maxLevel < 1 then maxLevel = 1 end | |||
local level = clamp(maxLevel, 1, maxLevel) | |||
local ctx = { | |||
maxLevel = maxLevel, | |||
level = level, | |||
} | |||
local root = mw.html.create("table") | |||
root:addClass("spiritvale-passive-infobox") | |||
root:addClass("sv-passive-card") | |||
root:addClass("sv-slot-card") | |||
root:attr("data-max-level", tostring(maxLevel)) | |||
root:attr("data-level", tostring(level)) | |||
if opts.inList then | |||
root:addClass("sv-passive-inlist") | |||
end | |||
local internalId = trim(rec["Internal Name"] or rec.InternalID or rec.ID) | |||
if internalId then | |||
root:attr("data-passive-id", internalId) | |||
end | |||
addHeroSlotsRow(root, buildHeroSlotsUI(rec, ctx)) | |||
return tostring(root) | |||
end | |||
---------------------------------------------------------------------- | |||
-- Public: list all passives for a given user/class | |||
---------------------------------------------------------------------- | |||
function p.listForUser(frame) | |||
local args = getArgs(frame) | |||
local userName = args.user or args[1] | |||
if not userName or userName == "" then | |||
userName = mw.title.getCurrentTitle().text | |||
end | |||
if not userName or userName == "" then | |||
return "<strong>No user name provided to Passive list.</strong>" | |||
end | |||
local dataset = getPassives() | |||
local matches = {} | |||
for _, rec in ipairs(dataset.records or {}) do | |||
if passiveMatchesUser(rec, userName) then | |||
table.insert(matches, rec) | |||
end | |||
end | |||
if #matches == 0 then | |||
return string.format( | |||
"<strong>No passives found for:</strong> %s", | |||
mw.text.nowiki(userName) | |||
) | |||
end | |||
local parts = {} | |||
for _, rec in ipairs(matches) do | |||
local title = resolveDisplayName(rec) | |||
table.insert(parts, string.format("=== %s ===\n%s", title, buildInfobox(rec, { inList = true }))) | |||
end | |||
return table.concat(parts, "\n") | |||
end | |||
---------------------------------------------------------------------- | |||
-- Public: single-passive or auto-list dispatcher | |||
---------------------------------------------------------------------- | |||
function p.infobox(frame) | |||
local args = getArgs(frame) | |||
local raw1 = args[1] | |||
local name = args.name or raw1 | |||
local id = args.id | |||
or | |||
local rec | |||
if | if name and name ~= "" then | ||
rec = findPassiveByName(name) | |||
end | end | ||
if not rec and id and id ~= "" then | |||
rec = getPassiveById(id) | |||
end | |||
if not rec then | |||
local pageTitle = mw.title.getCurrentTitle() | |||
local pageName = pageTitle and pageTitle.text or "" | |||
local noExplicitArgs = | |||
(not raw1 or raw1 == "") and | |||
(not args.name or args.name == "") and | |||
(not id or id == "") | |||
if noExplicitArgs then | |||
return p.listForUser(frame) | |||
end | |||
if name and name ~= "" and name == pageName and (not id or id == "") then | |||
return p.listForUser(frame) | |||
end | |||
local label = name or id or "?" | |||
return string.format( | |||
"<strong>Unknown passive:</strong> %s[[Category:Pages with unknown passive|%s]]", | |||
mw.text.nowiki(label), | |||
label | |||
) | |||
end | |||
return buildInfobox(rec) | |||
end | end | ||
return p | return p | ||