Module:GamePassives: Difference between revisions
From SpiritVale Wiki
More actions
No edit summary |
No edit summary |
||
| Line 1: | Line 1: | ||
-- Module:GamePassives | -- Module:GamePassives | ||
-- | -- | ||
-- Renders passive skill data (from Data:passives.json) into an infobox-style table | -- Renders passive skill data (from Data:passives.json) into an | ||
-- infobox-style table and can also list all passives for a given user/class. | |||
-- | -- | ||
-- | -- Usage (single passive): | ||
-- {{Passive|Honed Blade}} | -- {{Passive|Honed Blade}} | ||
-- {{Passive|name=Honed Blade}} | -- {{Passive|name=Honed Blade}} | ||
-- {{Passive|id=CritMastery}} | -- {{Passive|id=CritMastery}} | ||
-- | |||
-- Usage (auto-list on class page, e.g. "Acolyte"): | |||
-- {{Passive}} -> lists all Acolyte passives (page name) | |||
-- {{Passive|Acolyte}} -> same, if no passive literally called "Acolyte" | |||
local GameData = require("Module:GameData") | local GameData = require("Module:GameData") | ||
| Line 34: | Line 38: | ||
end | end | ||
local function listToText(list) | local function listToText(list, sep) | ||
if type(list) ~= "table" or #list == 0 then | if type(list) ~= "table" or #list == 0 then | ||
return nil | return nil | ||
end | end | ||
return table.concat(list, ", ") | return table.concat(list, sep or ", ") | ||
end | end | ||
| Line 48: | Line 52: | ||
row:tag("th"):wikitext(label):done() | row:tag("th"):wikitext(label):done() | ||
row:tag("td"):wikitext(value):done() | row:tag("td"):wikitext(value):done() | ||
end | |||
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 | ||
| Line 77: | Line 89: | ||
-- Formatting helpers | -- Formatting helpers | ||
---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ||
local function formatBasePer(block) | |||
if type(block) ~= "table" then | |||
return nil | |||
end | |||
local parts = {} | |||
if block.Base ~= nil then | |||
table.insert(parts, string.format("Base %s", tostring(block.Base))) | |||
end | |||
if block["Per Level"] ~= nil then | |||
table.insert(parts, string.format("%s / Lv", tostring(block["Per Level"]))) | |||
end | |||
if #parts == 0 then | |||
return nil | |||
end | |||
return table.concat(parts, ", ") | |||
end | |||
local function formatPassiveEffects(list) | local function formatPassiveEffects(list) | ||
| Line 87: | Line 116: | ||
for _, eff in ipairs(list) do | for _, eff in ipairs(list) do | ||
if type(eff) == "table" then | if type(eff) == "table" then | ||
local | local t = eff.Type or {} | ||
local value | local name = t.Name or eff.ID or "Unknown" | ||
local value = eff.Value or {} | |||
local | local detail = {} | ||
if | if value.Base ~= nil then | ||
table.insert(detail, string.format("Base %s", tostring(value.Base))) | |||
end | end | ||
if value["Per Level"] ~= nil then | |||
table.insert(detail, string.format("%s / Lv", tostring(value["Per Level"]))) | |||
table.insert(detail, string.format(" | |||
end | end | ||
if | if value.Expression ~= nil and value.Expression ~= "" then | ||
table.insert(detail, | table.insert(detail, tostring(value.Expression)) | ||
end | end | ||
local seg = name | |||
if #detail > 0 then | if #detail > 0 then | ||
seg = seg .. " – " .. table.concat(detail, ", ") | seg = seg -- name | ||
.. " – " .. table.concat(detail, ", ") | |||
end | end | ||
| Line 120: | Line 146: | ||
end | end | ||
return table.concat(parts, "<br />") | return table.concat(parts, "<br />") | ||
end | end | ||
| Line 130: | Line 155: | ||
local parts = {} | local parts = {} | ||
for _, s in ipairs(list) do | for _, s in ipairs(list) do | ||
if type(s) == "table" then | if type(s) == "table" then | ||
local scope | local scope = s.Scope or "Target" | ||
local name | local name = s["Status Name"] or s["Status ID"] or "Unknown status" | ||
local dur | |||
local | local seg = scope .. " – " .. 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 | |||
if | table.insert(detail, "Fixed duration") | ||
end | end | ||
if #detail > 0 then | |||
if | seg = seg .. " (" .. table.concat(detail, ", ") .. ")" | ||
end | end | ||
if | |||
table.insert(parts, seg) | |||
end | |||
end | |||
if #parts == 0 then | |||
return nil | |||
end | |||
return table.concat(parts, "<br />") | |||
end | |||
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 Name"] | |||
local label | |||
if type(names) == "table" then | |||
label = table.concat(names, ", ") | |||
elseif type(names) == "string" then | |||
label = names | |||
else | |||
label = "Status" | |||
end | end | ||
if | local bp = formatBasePer(r) | ||
seg = seg .. " | local seg = label | ||
if bp then | |||
seg = seg .. " – " .. bp | |||
end | end | ||
table.insert(parts, seg) | |||
end | |||
end | |||
if #parts == 0 then | |||
return nil | |||
end | |||
return table.concat(parts, "<br />") | |||
end | |||
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 Name"] or ev["Skill ID"] or "Unknown skill" | |||
local seg = string.format("%s → %s", action, name) | |||
table.insert(parts, seg) | table.insert(parts, seg) | ||
end | end | ||
| Line 165: | Line 255: | ||
end | end | ||
local function | local function formatModifiers(mods) | ||
if type( | 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 | return nil | ||
end | end | ||
return table.concat( | |||
return table.concat(parts, "<br />") | |||
end | |||
---------------------------------------------------------------------- | |||
-- User matching (for auto lists on class pages) | |||
---------------------------------------------------------------------- | |||
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 | |||
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 194: | Line 347: | ||
titleText = titleText .. title | titleText = titleText .. title | ||
headerCell:wikitext(titleText) | headerCell:wikitext(titleText) | ||
------------------------------------------------------------------ | ------------------------------------------------------------------ | ||
-- | -- General | ||
------------------------------------------------------------------ | ------------------------------------------------------------------ | ||
addSectionHeader(root, "General") | |||
addRow(root, "Description", rec.Description) | addRow(root, "Description", rec.Description) | ||
addRow(root, "Max level", rec["Max Level"] and tostring(rec["Max Level"])) | addRow(root, "Max level", rec["Max Level"] and tostring(rec["Max Level"])) | ||
local users = rec.Users or {} | local users = rec.Users or {} | ||
addRow(root, "Classes", listToText(users.Classes)) | addRow(root, "Classes", listToText(users.Classes)) | ||
| Line 215: | Line 365: | ||
------------------------------------------------------------------ | ------------------------------------------------------------------ | ||
local req = rec.Requirements or {} | 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 Name"] or rs["Skill ID"] 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 | end | ||
addRow(root, "Required skills", table.concat(skillParts, ", ")) | |||
end | end | ||
addRow(root, "Required | |||
addRow(root, "Required weapons", listToText(req["Required Weapons"])) | |||
addRow(root, "Required stances", listToText(req["Required Stances"])) | |||
end | end | ||
------------------------------------------------------------------ | ------------------------------------------------------------------ | ||
-- Passive effects | -- Passive effects | ||
------------------------------------------------------------------ | ------------------------------------------------------------------ | ||
local | local peText = formatPassiveEffects(rec["Passive Effects"]) | ||
if peText then | |||
addSectionHeader(root, "Passive effects") | |||
addRow(root, "Effects", peText) | |||
end | |||
------------------------------------------------------------------ | ------------------------------------------------------------------ | ||
-- | -- Modifiers | ||
------------------------------------------------------------------ | ------------------------------------------------------------------ | ||
local | local modsText = formatModifiers(rec.Modifiers) | ||
if modsText then | |||
addSectionHeader(root, "Modifiers") | |||
addRow(root, "Flags", modsText) | |||
end | |||
------------------------------------------------------------------ | ------------------------------------------------------------------ | ||
-- | -- Status | ||
------------------------------------------------------------------ | ------------------------------------------------------------------ | ||
local | 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 | end | ||
local | ------------------------------------------------------------------ | ||
if | -- Events | ||
------------------------------------------------------------------ | |||
local eventsText = formatEvents(rec.Events) | |||
if eventsText then | |||
addSectionHeader(root, "Events") | |||
addRow(root, "Triggers", eventsText) | |||
end | end | ||
------------------------------------------------------------------ | |||
-- Notes | |||
------------------------------------------------------------------ | |||
if type(rec.Notes) == "table" and #rec.Notes > 0 then | |||
addSectionHeader(root, "Notes") | |||
addRow(root, "Notes", table.concat(rec.Notes, "<br />")) | |||
end | end | ||
return tostring(root) | |||
end | |||
---------------------------------------------------------------------- | |||
-- Public: list all passives for a given user/class | |||
---------------------------------------------------------------------- | |||
function p.listForUser(frame) | function p.listForUser(frame) | ||
local args = getArgs(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] | local userName = args.user or args[1] | ||
if not userName or userName == "" then | if not userName or userName == "" then | ||
| Line 327: | Line 482: | ||
---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ||
-- Public | -- Public: single-passive or auto-list dispatcher | ||
---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ||
| Line 343: | Line 498: | ||
local rec | local rec | ||
-- 1) Prefer display Name | -- 1) Prefer display Name | ||
if name and name ~= "" then | if name and name ~= "" then | ||
rec = findPassiveByName(name) | rec = findPassiveByName(name) | ||
end | end | ||
-- 2) Fallback: | -- 2) Fallback: internal ID | ||
if not rec and id and id ~= "" then | if not rec and id and id ~= "" then | ||
rec = getPassiveById(id) | rec = getPassiveById(id) | ||
end | end | ||
-- 3) If still | -- 3) If still nothing, decide if this is "list mode" or truly unknown. | ||
if not rec then | if not rec then | ||
local pageTitle = mw.title.getCurrentTitle() | local pageTitle = mw.title.getCurrentTitle() | ||