Module:GameSkills: Difference between revisions
From SpiritVale Wiki
More actions
No edit summary |
No edit summary |
||
| Line 24: | Line 24: | ||
local function getSkills() | local function getSkills() | ||
if not skillsCache then | |||
skillsCache = GameData.loadSkills() | |||
end | |||
return skillsCache | |||
end | end | ||
local function getArgs(frame) | local function getArgs(frame) | ||
local parent = frame:getParent() | |||
if parent then | |||
return parent.args | |||
end | |||
return frame.args | |||
end | end | ||
local function listToText(list, sep) | local function listToText(list, sep) | ||
if type(list) ~= "table" or #list == 0 then | |||
return nil | |||
end | |||
return table.concat(list, sep or ", ") | |||
end | end | ||
local function addRow(tbl, label, value) | local function addRow(tbl, label, value) | ||
if value == nil or value == "" then | |||
return | |||
end | |||
local row = tbl:tag("tr") | |||
row:tag("th"):wikitext(label):done() | |||
row:tag("td"):wikitext(value):done() | |||
end | end | ||
local function addSectionHeader(tbl, label) | local function addSectionHeader(tbl, label) | ||
local row = tbl:tag("tr") | |||
local cell = row:tag("th") | |||
cell:attr("colspan", 2) | |||
cell:addClass("spiritvale-infobox-section-header") | |||
cell:wikitext(label) | |||
end | end | ||
local function trim(s) | |||
if type(s) ~= "string" then return nil end | |||
s = mw.text.trim(s) | |||
if s == "" then return nil end | |||
return s | |||
end | |||
-- Handles either a scalar OR {Value=..., Unit=...} | |||
local function formatUnitValue(v) | |||
if type(v) == "table" and v.Value ~= nil then | |||
local unit = v.Unit | |||
local val = v.Value | |||
-- Best-effort formatting for common unit types | |||
if unit == "percent_decimal" then | |||
return tostring((val or 0) * 100) .. "%" | |||
elseif unit == "percent_whole" or unit == "percent" then | |||
return tostring(val) .. "%" | |||
elseif unit == "seconds" then | |||
return tostring(val) .. "s" | |||
elseif unit == "meters" then | |||
return tostring(val) .. "m" | |||
elseif unit == "tiles" then | |||
return tostring(val) .. " tiles" | |||
elseif unit and unit ~= "" then | |||
return tostring(val) .. " " .. tostring(unit) | |||
else | |||
return tostring(val) | |||
end | |||
end | |||
if v == nil then | |||
return nil | |||
end | |||
return tostring(v) | |||
end | |||
---------------------------------------------------------------------- | |||
-- Lookups | |||
---------------------------------------------------------------------- | |||
-- Lookup by Internal Name | -- Lookup by Internal Name | ||
local function getSkillById(id) | local function getSkillById(id) | ||
id = trim(id) | |||
if not id then return nil end | |||
local dataset = getSkills() | |||
local byId = dataset.byId or {} | |||
return byId[id] | |||
end | end | ||
-- Lookup by display Name (for editors) | -- Lookup by display/external Name (for editors) | ||
local function findSkillByName(name) | local function findSkillByName(name) | ||
name = trim(name) | |||
if not name then return nil end | |||
local dataset = getSkills() | |||
-- Fast path if GameData built byName | |||
local byName = dataset.byName or {} | |||
if byName[name] then | |||
return byName[name] | |||
end | |||
-- Fallback scan (older GameData) | |||
for _, rec in ipairs(dataset.records or {}) do | |||
if type(rec) == "table" then | |||
if rec["External Name"] == name or rec["Name"] == name or rec["Display Name"] == name then | |||
return rec | |||
end | |||
end | |||
end | |||
return nil | |||
end | end | ||
| Line 89: | Line 137: | ||
-- Formatting helpers | -- Formatting helpers | ||
---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ||
local function isFlatPerLevel(baseStr, perList) | |||
if type(perList) ~= "table" or #perList == 0 then | |||
return false | |||
end | |||
-- If all per-level entries are identical, treat as "no scaling" | |||
local first = tostring(perList[1]) | |||
for i = 2, #perList do | |||
if tostring(perList[i]) ~= first then | |||
return false | |||
end | |||
end | |||
-- If base matches that identical value, definitely flat/no-op | |||
if baseStr ~= nil and tostring(baseStr) == first then | |||
return true | |||
end | |||
-- Even if base differs (sometimes Base is "0" but Lv list is constant), still no meaningful scaling | |||
return true | |||
end | |||
local function formatBasePer(block) | local function formatBasePer(block) | ||
if type(block) ~= "table" then | |||
return nil | |||
end | |||
local base = block.Base | |||
local per = block["Per Level"] | |||
local baseText = (base ~= nil) and formatUnitValue(base) or nil | |||
-- Per Level might be a scalar OR list (wikiprep expansion) | |||
if type(per) == "table" then | |||
if isFlatPerLevel(baseText, per) then | |||
-- Show only Base (or Lv1 if Base is missing) | |||
if baseText then | |||
return "Base: " .. baseText | |||
end | |||
if per[1] ~= nil then | |||
return "Lv1: " .. tostring(per[1]) | |||
end | |||
return nil | |||
end | |||
local lines = {} | |||
if baseText then | |||
table.insert(lines, "Base: " .. baseText) | |||
end | |||
for i, v in ipairs(per) do | |||
table.insert(lines, string.format("Lv%d: %s", i, tostring(v))) | |||
end | |||
return table.concat(lines, "<br />") | |||
else | |||
local parts = {} | |||
if baseText then | |||
table.insert(parts, "Base " .. baseText) | |||
end | |||
if per ~= nil then | |||
table.insert(parts, tostring(per) .. " / Lv") | |||
end | |||
if #parts == 0 then | |||
return nil | |||
end | |||
return table.concat(parts, ", ") | |||
end | |||
end | end | ||
local function formatMainDamage(list) | local function formatMainDamage(list) | ||
if type(list) ~= "table" or #list == 0 then | |||
return nil | |||
end | |||
local parts = {} | |||
for _, d in ipairs(list) do | |||
if type(d) == "table" then | |||
local kind = d.Type or "Damage" | |||
local base = d["Base %"] | |||
local per = d["Per Level %"] | |||
local seg = kind | |||
local detail = {} | |||
if base ~= nil then | |||
table.insert(detail, string.format("Base %s%%", tostring(base))) | |||
end | |||
if per ~= nil then | |||
table.insert(detail, string.format("%s%% / Lv", tostring(per))) | |||
end | |||
if d["ATK-Based"] then | |||
table.insert(detail, "ATK-based") | |||
end | |||
if d["MATK-Based"] then | |||
table.insert(detail, "MATK-based") | |||
end | |||
if #detail > 0 then | |||
seg = seg .. " – " .. table.concat(detail, ", ") | |||
end | |||
table.insert(parts, seg) | |||
end | |||
end | |||
if #parts == 0 then | |||
return nil | |||
end | |||
return table.concat(parts, "<br />") | |||
end | end | ||
local function formatReflectDamage(list) | local function formatReflectDamage(list) | ||
if type(list) ~= "table" or #list == 0 then | |||
return nil | |||
end | |||
local parts = {} | |||
for _, d in ipairs(list) do | |||
if type(d) == "table" then | |||
local base = d["Base %"] | |||
local per = d["Per Level %"] | |||
local seg = "Reflect" | |||
local detail = {} | |||
if base ~= nil then | |||
table.insert(detail, string.format("Base %s%%", tostring(base))) | |||
end | |||
if per ~= nil then | |||
table.insert(detail, string.format("%s%% / Lv", tostring(per))) | |||
end | |||
if #detail > 0 then | |||
seg = seg .. " – " .. table.concat(detail, ", ") | |||
end | |||
table.insert(parts, seg) | |||
end | |||
end | |||
if #parts == 0 then | |||
return nil | |||
end | |||
return table.concat(parts, "<br />") | |||
end | end | ||
local function formatScaling(list) | local function formatScaling(list) | ||
if type(list) ~= "table" or #list == 0 then | |||
return nil | |||
end | |||
local parts = {} | |||
for _, s in ipairs(list) do | |||
if type(s) == "table" then | |||
local name = s["Scaling Name"] or s["Scaling ID"] or "Unknown" | |||
local pct = s.Percent | |||
local seg = name | |||
local detail = {} | |||
if pct ~= nil then | |||
-- If Unit is percent_decimal, convert; otherwise assume already percent | |||
if s.Unit == "percent_decimal" then | |||
table.insert(detail, string.format("%s%%", tostring((pct or 0) * 100))) | |||
else | |||
table.insert(detail, string.format("%s%%", tostring(pct))) | |||
end | |||
end | |||
if s["ATK-Based"] then | |||
table.insert(detail, "ATK-based") | |||
end | |||
if s["MATK-Based"] then | |||
table.insert(detail, "MATK-based") | |||
end | |||
if #detail > 0 then | |||
seg = seg .. " – " .. table.concat(detail, ", ") | |||
end | |||
table.insert(parts, seg) | |||
end | |||
end | |||
if #parts == 0 then | |||
return nil | |||
end | |||
return table.concat(parts, "<br />") | |||
end | end | ||
local function formatArea(area) | local function formatArea(area) | ||
if type(area) ~= "table" then | |||
return nil | |||
end | |||
local parts = {} | |||
local size = area["Area Size"] | |||
if size and size ~= "" then | |||
table.insert(parts, "Size: " .. tostring(size)) | |||
end | |||
local dist = area["Area Distance"] | |||
local eff = area["Effective Distance"] | |||
local distText = formatBasePer(dist) | |||
if distText then | |||
table.insert(parts, "Distance: " .. distText) | |||
end | |||
local effText = formatUnitValue(eff) | |||
if effText then | |||
table.insert(parts, "Effective: " .. effText) | |||
end | |||
if #parts == 0 then | |||
return nil | |||
end | |||
return table.concat(parts, "<br />") | |||
end | end | ||
local function formatTimingBlock(bt) | local function formatTimingBlock(bt) | ||
if type(bt) ~= "table" then | |||
return nil | |||
end | |||
local parts = {} | |||
local function add(name, key) | |||
local block = bt[key] | |||
local txt = formatBasePer(block) | |||
if txt then | |||
table.insert(parts, name .. ": " .. txt) | |||
end | |||
end | |||
add("Cast Time", "Cast Time") | |||
add("Cooldown", "Cooldown") | |||
add("Duration", "Duration") | |||
if bt["Effect Cast Time"] ~= nil then | |||
table.insert(parts, "Effect Cast Time: " .. tostring(bt["Effect Cast Time"])) | |||
end | |||
if bt["Damage Delay"] ~= nil then | |||
table.insert(parts, "Damage Delay: " .. tostring(bt["Damage Delay"])) | |||
end | |||
if bt["Effect Remove Delay"] ~= nil then | |||
table.insert(parts, "Effect Remove Delay: " .. tostring(bt["Effect Remove Delay"])) | |||
end | |||
if #parts == 0 then | |||
return nil | |||
end | |||
return table.concat(parts, "<br />") | |||
end | end | ||
local function formatResourceCost(rc) | local function formatResourceCost(rc) | ||
if type(rc) ~= "table" then | |||
return nil | |||
end | |||
local parts = {} | |||
local manaTxt = formatBasePer(rc["Mana Cost"]) | |||
if manaTxt then | |||
table.insert(parts, "MP: " .. manaTxt) | |||
end | |||
local hpTxt = formatBasePer(rc["Health Cost"]) | |||
if hpTxt then | |||
table.insert(parts, "HP: " .. hpTxt) | |||
end | |||
if #parts == 0 then | |||
return nil | |||
end | |||
return table.concat(parts, "<br />") | |||
end | end | ||
local function formatCombo(combo) | local function formatCombo(combo) | ||
if type(combo) ~= "table" then | |||
return nil | |||
end | |||
local parts = {} | |||
if combo.Type then | |||
table.insert(parts, "Type: " .. tostring(combo.Type)) | |||
end | |||
local durText = formatUnitValue(combo.Duration) | |||
if durText then | |||
table.insert(parts, "Duration: " .. durText) | |||
end | |||
-- Percent may be scalar or {Value,Unit} | |||
if combo.Percent ~= nil then | |||
local pctText = formatUnitValue(combo.Percent) | |||
if pctText then | |||
table.insert(parts, "Bonus: " .. pctText) | |||
end | |||
end | |||
if #parts == 0 then | |||
return nil | |||
end | |||
return table.concat(parts, ", ") | |||
end | end | ||
local function formatMechanicEffects(effects) | local function formatMechanicEffects(effects) | ||
if type(effects) ~= "table" then | |||
return nil | |||
end | |||
local keys = {} | |||
for k, _ in pairs(effects) do | |||
table.insert(keys, k) | |||
end | |||
table.sort(keys) | |||
local parts = {} | |||
for _, name in ipairs(keys) do | |||
local block = effects[name] | |||
if type(block) == "table" then | |||
local bp = formatBasePer(block) | |||
local seg = name | |||
if bp then | |||
seg = seg .. " – " .. bp | |||
end | |||
table.insert(parts, seg) | |||
end | |||
end | |||
if #parts == 0 then | |||
return nil | |||
end | |||
return table.concat(parts, "<br />") | |||
end | end | ||
local function formatModifiers(mods) | local function formatModifiers(mods) | ||
if type(mods) ~= "table" then | |||
return nil | |||
end | |||
local parts = {} | |||
local function collect(label, sub) | |||
if type(sub) ~= "table" then | |||
return | |||
end | |||
local flags = {} | |||
for k, v in pairs(sub) do | |||
if v then | |||
table.insert(flags, k) | |||
end | |||
end | |||
table.sort(flags) | |||
if #flags > 0 then | |||
table.insert(parts, string.format("%s: %s", label, table.concat(flags, ", "))) | |||
end | |||
end | |||
collect("Movement", mods["Movement Modifiers"]) | |||
collect("Combat", mods["Combat Modifiers"]) | |||
collect("Special", mods["Special Modifiers"]) | |||
if #parts == 0 then | |||
return nil | |||
end | |||
return table.concat(parts, "<br />") | |||
end | end | ||
local function formatStatusApplications(list) | local function formatStatusApplications(list) | ||
if type(list) ~= "table" or #list == 0 then | |||
return nil | |||
end | |||
local parts = {} | |||
for _, s in ipairs(list) do | |||
if type(s) == "table" then | |||
local scope = s.Scope or "Target" | |||
local name = s["Status External Name"] or s["Status Internal Name"] or "Unknown status" | |||
local seg = scope .. " – " .. tostring(name) | |||
local detail = {} | |||
local dur = s.Duration | |||
if type(dur) == "table" then | |||
local t = formatBasePer(dur) | |||
if t then | |||
table.insert(detail, "Duration " .. t) | |||
end | |||
end | |||
local ch = s.Chance | |||
if type(ch) == "table" then | |||
local t = formatBasePer(ch) | |||
if t then | |||
table.insert(detail, "Chance " .. t) | |||
end | |||
end | |||
if s["Fixed Duration"] then | |||
table.insert(detail, "Fixed duration") | |||
end | |||
if #detail > 0 then | |||
seg = seg .. " (" .. table.concat(detail, ", ") .. ")" | |||
end | |||
table.insert(parts, seg) | |||
end | |||
end | |||
if #parts == 0 then | |||
return nil | |||
end | |||
return table.concat(parts, "<br />") | |||
end | end | ||
local function formatStatusRemoval(list) | local function formatStatusRemoval(list) | ||
if type(list) ~= "table" or #list == 0 then | |||
return nil | |||
end | |||
local parts = {} | |||
for _, r in ipairs(list) do | |||
if type(r) == "table" then | |||
local names = r["Status External Name"] | |||
local label | |||
if type(names) == "table" then | |||
label = table.concat(names, ", ") | |||
elseif type(names) == "string" then | |||
label = names | |||
else | |||
label = "Status" | |||
end | |||
local bp = formatBasePer(r) | |||
local seg = label | |||
if bp then | |||
seg = seg .. " – " .. bp | |||
end | |||
table.insert(parts, seg) | |||
end | |||
end | |||
if #parts == 0 then | |||
return nil | |||
end | |||
return table.concat(parts, "<br />") | |||
end | end | ||
local function formatEvents(list) | local function formatEvents(list) | ||
if type(list) ~= "table" or #list == 0 then | |||
return nil | |||
end | |||
local parts = {} | |||
for _, ev in ipairs(list) do | |||
if type(ev) == "table" then | |||
local action = ev.Action or "On event" | |||
local name = ev["Skill Internal Name"] or ev["Skill External Name"] or "Unknown skill" | |||
local seg = string.format("%s → %s", action, name) | |||
table.insert(parts, seg) | |||
end | |||
end | |||
if #parts == 0 then | |||
return nil | |||
end | |||
return table.concat(parts, "<br />") | |||
end | end | ||
| Line 459: | Line 583: | ||
local function skillMatchesUser(rec, userName) | local function skillMatchesUser(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 | |||
if listHas(users.Classes) then return true end | |||
if listHas(users.Summons) then return true end | |||
if listHas(users.Monsters) then return true end | |||
if listHas(users.Events) then return true end | |||
return false | |||
end | end | ||
| Line 495: | Line 619: | ||
local function buildInfobox(rec) | local function buildInfobox(rec) | ||
local root = mw.html.create("table") | |||
root:addClass("wikitable spiritvale-skill-infobox") | |||
-- ========================================================== | |||
-- Top "hero" row: icon + name (left), description (right) | |||
-- ========================================================== | |||
local icon = rec.Icon | |||
local title = rec["External Name"] or rec.Name or rec["Internal Name"] or "Unknown Skill" | |||
local desc = rec.Description or "" | |||
local headerRow = root:tag("tr") | |||
headerRow:addClass("spiritvale-infobox-main") | |||
-- Left cell: icon + name | |||
local leftCell = headerRow:tag("th") | |||
leftCell:addClass("spiritvale-infobox-main-left") | |||
local leftInner = leftCell:tag("div") | |||
leftInner:addClass("spiritvale-infobox-main-left-inner") | |||
if icon and icon ~= "" then | |||
leftInner:wikitext(string.format("[[File:%s|80px|link=]]", icon)) | |||
end | |||
leftInner:tag("div") | |||
:addClass("spiritvale-infobox-title") | |||
:wikitext(title) | |||
-- Right cell: italic description | |||
local rightCell = headerRow:tag("td") | |||
rightCell:addClass("spiritvale-infobox-main-right") | |||
local rightInner = rightCell:tag("div") | |||
rightInner:addClass("spiritvale-infobox-main-right-inner") | |||
if desc ~= "" then | |||
rightInner:tag("div") | |||
:addClass("spiritvale-infobox-description") | |||
:wikitext(string.format("''%s''", desc)) | |||
end | |||
------------------------------------------------------------------ | |||
-- General | |||
------------------------------------------------------------------ | |||
addSectionHeader(root, "General") | |||
addRow(root, "Max level", rec["Max Level"] and tostring(rec["Max Level"])) | |||
local users = rec.Users or {} | |||
addRow(root, "Classes", listToText(users.Classes)) | |||
addRow(root, "Summons", listToText(users.Summons)) | |||
addRow(root, "Monsters", listToText(users.Monsters)) | |||
addRow(root, "Events", listToText(users.Events)) | |||
------------------------------------------------------------------ | |||
-- Requirements | |||
------------------------------------------------------------------ | |||
local req = rec.Requirements or {} | |||
if (req["Required Skills"] and #req["Required Skills"] > 0) | |||
or (req["Required Weapons"] and #req["Required Weapons"] > 0) | |||
or (req["Required Stances"] and #req["Required Stances"] > 0) then | |||
addSectionHeader(root, "Requirements") | |||
if type(req["Required Skills"]) == "table" and #req["Required Skills"] > 0 then | |||
local skillParts = {} | |||
for _, rs in ipairs(req["Required Skills"]) do | |||
local name = rs["Skill External Name"] or rs["Skill Internal Name"] or "Unknown" | |||
local level = rs["Required Level"] | |||
if level then | |||
table.insert(skillParts, string.format("%s (Lv.%s)", name, level)) | |||
else | |||
table.insert(skillParts, name) | |||
end | |||
end | |||
addRow(root, "Required skills", table.concat(skillParts, ", ")) | |||
end | |||
addRow(root, "Required weapons", listToText(req["Required Weapons"])) | |||
addRow(root, "Required stances", listToText(req["Required Stances"])) | |||
end | |||
------------------------------------------------------------------ | |||
-- Type | |||
------------------------------------------------------------------ | |||
local typeBlock = rec.Type or {} | |||
if next(typeBlock) ~= nil then | |||
addSectionHeader(root, "Type") | |||
local dt = typeBlock["Damage Type"] | |||
if type(dt) == "table" and dt.Name then | |||
addRow(root, "Damage type", dt.Name) | |||
end | |||
local et = typeBlock["Element Type"] | |||
if type(et) == "table" and et.Name then | |||
addRow(root, "Element", et.Name) | |||
end | |||
local tt = typeBlock["Target Type"] | |||
if type(tt) == "table" and tt.Name then | |||
addRow(root, "Target", tt.Name) | |||
end | |||
local ct = typeBlock["Cast Type"] | |||
if type(ct) == "table" and ct.Name then | |||
addRow(root, "Cast type", ct.Name) | |||
end | |||
end | |||
------------------------------------------------------------------ | |||
-- Mechanics | |||
------------------------------------------------------------------ | |||
local mech = rec.Mechanics or {} | |||
if next(mech) ~= nil then | |||
addSectionHeader(root, "Mechanics") | |||
local rangeText = formatUnitValue(mech.Range) | |||
addRow(root, "Range", rangeText) | |||
local areaText = formatArea(mech.Area) | |||
addRow(root, "Area", areaText) | |||
if mech["Autocast Multiplier"] ~= nil then | |||
addRow(root, "Autocast multiplier", tostring(mech["Autocast Multiplier"])) | |||
end | |||
local btText = formatTimingBlock(mech["Basic Timings"]) | |||
addRow(root, "Timing", btText) | |||
local rcText = formatResourceCost(mech["Resource Cost"]) | |||
addRow(root, "Resource cost", rcText) | |||
local comboText = formatCombo(mech.Combo) | |||
addRow(root, "Combo", comboText) | |||
local effText = formatMechanicEffects(mech.Effects) | |||
addRow(root, "Special mechanics", effText) | |||
end | |||
------------------------------------------------------------------ | |||
-- Damage & Healing | |||
------------------------------------------------------------------ | |||
local dmg = rec.Damage or {} | |||
if next(dmg) ~= nil then | |||
addSectionHeader(root, "Damage and scaling") | |||
if dmg["Healing Present"] then | |||
addRow(root, "Healing", "Yes") | |||
end | |||
local mainText = formatMainDamage(dmg["Main Damage"]) | |||
addRow(root, "Main damage", mainText) | |||
local reflText = formatReflectDamage(dmg["Reflect Damage"]) | |||
addRow(root, "Reflect damage", reflText) | |||
local scaleText = formatScaling(dmg.Scaling) | |||
addRow(root, "Scaling", scaleText) | |||
end | |||
------------------------------------------------------------------ | |||
-- Modifiers | |||
------------------------------------------------------------------ | |||
local modsText = formatModifiers(rec.Modifiers) | |||
if modsText then | |||
addSectionHeader(root, "Modifiers") | |||
addRow(root, "Flags", modsText) | |||
end | |||
------------------------------------------------------------------ | |||
-- Status | |||
------------------------------------------------------------------ | |||
local statusApps = formatStatusApplications(rec["Status Applications"]) | |||
local statusRem = formatStatusRemoval(rec["Status Removal"]) | |||
if statusApps or statusRem then | |||
addSectionHeader(root, "Status effects") | |||
addRow(root, "Applies", statusApps) | |||
addRow(root, "Removes", statusRem) | |||
end | |||
------------------------------------------------------------------ | |||
-- Events | |||
------------------------------------------------------------------ | |||
local eventsText = formatEvents(rec.Events) | |||
if eventsText then | |||
addSectionHeader(root, "Events") | |||
addRow(root, "Triggers", eventsText) | |||
end | |||
------------------------------------------------------------------ | |||
-- Notes | |||
------------------------------------------------------------------ | |||
if type(rec.Notes) == "table" and #rec.Notes > 0 then | |||
addSectionHeader(root, "Notes") | |||
addRow(root, "Notes", table.concat(rec.Notes, "<br />")) | |||
end | |||
return tostring(root) | |||
end | end | ||
| Line 705: | Line 824: | ||
function p.listForUser(frame) | function p.listForUser(frame) | ||
local args = getArgs(frame) | |||
-- Prefer explicit param, then unnamed, then fall back to the current page name. | |||
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 Skill list.</strong>" | |||
end | |||
local dataset = getSkills() | |||
local matches = {} | |||
for _, rec in ipairs(dataset.records or {}) do | |||
if skillMatchesUser(rec, userName) then | |||
table.insert(matches, rec) | |||
end | |||
end | |||
if #matches == 0 then | |||
return string.format( | |||
"<strong>No skills found for:</strong> %s", | |||
mw.text.nowiki(userName) | |||
) | |||
end | |||
local root = mw.html.create("div") | |||
root:addClass("spiritvale-skill-list") | |||
for _, rec in ipairs(matches) do | |||
root:wikitext(buildInfobox(rec)) | |||
end | |||
return tostring(root) | |||
end | end | ||
| Line 748: | Line 867: | ||
function p.infobox(frame) | function p.infobox(frame) | ||
local args = getArgs(frame) | |||
-- Allow three styles: | |||
-- {{Skill|Bash}} -> args[1] = "Bash" (External Name) | |||
-- {{Skill|name=Bash}} -> args.name = "Bash" | |||
-- {{Skill|id=Bash_Internal}} -> args.id = "Bash_Internal" | |||
local raw1 = args[1] | |||
local name = args.name or raw1 | |||
local id = args.id | |||
local rec | |||
-- 1) Prefer External/Display Name | |||
if name and name ~= "" then | |||
rec = findSkillByName(name) | |||
end | |||
-- 2) Fallback: internal ID | |||
if not rec and id and id ~= "" then | |||
rec = getSkillById(id) | |||
end | |||
-- 3) If still nothing, decide if this is "list mode" or truly unknown. | |||
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 == "") | |||
-- Case A: {{Skill}} with no parameters on a page → list for that page name. | |||
if noExplicitArgs then | |||
return p.listForUser(frame) | |||
end | |||
-- Case B: {{Skill|Acolyte}} on the "Acolyte" page and no id → treat as list. | |||
if name and name ~= "" and name == pageName and (not id or id == "") then | |||
return p.listForUser(frame) | |||
end | |||
-- Otherwise, genuinely unknown skill. | |||
local label = name or id or "?" | |||
return string.format( | |||
"<strong>Unknown skill:</strong> %s[[Category:Pages with unknown skill|%s]]", | |||
mw.text.nowiki(label), | |||
label | |||
) | |||
end | |||
return buildInfobox(rec) | |||
end | end | ||
return p | return p | ||