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

Join the Playtest on Steam Now: SpiritVale

Module:GamePassives: Difference between revisions

From SpiritVale Wiki
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
-- Data is loaded via Module:GameData.
-- infobox-style table and can also list all passives for a given user/class.
--
--
-- Supported usage patterns (via Template:Passive):
-- Usage (single passive):
--  {{Passive|Honed Blade}}             -> uses display Name (recommended)
--  {{Passive|Honed Blade}}
--  {{Passive|name=Honed Blade}}         -> explicit Name
--  {{Passive|name=Honed Blade}}
--  {{Passive|id=CritMastery}}           -> Internal Name (power use)
--  {{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 typeName = eff.Type and eff.Type.Name or eff.ID or "Unknown"
             local t = eff.Type or {}
             local value   = eff.Value or {}
            local name = t.Name or eff.ID or "Unknown"
             local value = eff.Value or {}


             local expr = value.Expression
             local detail = {}
            local base = value.Base
            local per  = value["Per Level"]
 
            local seg = typeName


             if expr and expr ~= "" then
             if value.Base ~= nil then
                 seg = seg .. string.format(" (%s)", expr)
                 table.insert(detail, string.format("Base %s", tostring(value.Base)))
             end
             end
 
             if value["Per Level"] ~= nil then
             local detail = {}
                 table.insert(detail, string.format("%s / Lv", tostring(value["Per Level"])))
            if base then
                 table.insert(detail, string.format("Base %.2f", base))
             end
             end
             if per then
             if value.Expression ~= nil and value.Expression ~= "" then
                 table.insert(detail, string.format("%.2f / Lv", per))
                 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


    -- One per line
     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 = s.Scope or "Target"
             local scope = s.Scope or "Target"
             local name   = s["Status Name"] or s["Status ID"] or "Unknown status"
             local name = s["Status Name"] or s["Status ID"] or "Unknown status"
             local dur   = s.Duration and s.Duration.Base
 
             local chance = s.Chance and s.Chance.Base
            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


            local seg = name
             if s["Fixed Duration"] then
             if scope and scope ~= "" then
                 table.insert(detail, "Fixed duration")
                 seg = scope .. ": " .. seg
             end
             end


            local detailParts = {}
             if #detail > 0 then
             if dur then
                 seg = seg .. " (" .. table.concat(detail, ", ") .. ")"
                 table.insert(detailParts, string.format("Dur %.2f", dur))
             end
             end
             if chance then
 
                table.insert(detailParts, string.format("Chance %.2f", chance))
             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 #detailParts > 0 then
            local bp = formatBasePer(r)
                 seg = seg .. " (" .. table.concat(detailParts, ", ") .. ")"
            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 formatNotes(notes)
local function formatModifiers(mods)
     if type(notes) ~= "table" or #notes == 0 then
     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(notes, "<br />")
 
     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)


     ------------------------------------------------------------------
     ------------------------------------------------------------------
     -- Basic info
     -- 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"]))


    ------------------------------------------------------------------
    -- Users
    ------------------------------------------------------------------
     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
        if type(req["Required Skills"]) == "table" and #req["Required Skills"] > 0 then
        local skillParts = {}
            local skillParts = {}
        for _, rs in ipairs(req["Required Skills"]) do
            for _, rs in ipairs(req["Required Skills"]) do
            local name  = rs["Skill Name"] or rs["Skill ID"] or "Unknown"
                local name  = rs["Skill Name"] or rs["Skill ID"] or "Unknown"
            local level = rs["Required Level"]
                local level = rs["Required Level"]
            if level then
                if level then
                table.insert(skillParts, string.format("%s (Lv.%s)", name, level))
                    table.insert(skillParts, string.format("%s (Lv.%s)", name, level))
            else
                else
                table.insert(skillParts, name)
                    table.insert(skillParts, name)
                end
             end
             end
            addRow(root, "Required skills", table.concat(skillParts, ", "))
         end
         end
         addRow(root, "Required skills", table.concat(skillParts, ", "))
 
         addRow(root, "Required weapons", listToText(req["Required Weapons"]))
        addRow(root, "Required stances", listToText(req["Required Stances"]))
     end
     end
    addRow(root, "Required weapons", listToText(req["Required Weapons"]))
    addRow(root, "Required stances", listToText(req["Required Stances"]))


     ------------------------------------------------------------------
     ------------------------------------------------------------------
     -- Passive effects
     -- Passive effects
     ------------------------------------------------------------------
     ------------------------------------------------------------------
     local effectsText = formatPassiveEffects(rec["Passive Effects"])
     local peText = formatPassiveEffects(rec["Passive Effects"])
     addRow(root, "Passive effects", effectsText)
     if peText then
        addSectionHeader(root, "Passive effects")
        addRow(root, "Effects", peText)
    end


     ------------------------------------------------------------------
     ------------------------------------------------------------------
     -- Status interactions
     -- Modifiers
     ------------------------------------------------------------------
     ------------------------------------------------------------------
     local statusApps = formatStatusApplications(rec["Status Applications"])
     local modsText = formatModifiers(rec.Modifiers)
    addRow(root, "Status applications", statusApps)
    if modsText then
        addSectionHeader(root, "Modifiers")
        addRow(root, "Flags", modsText)
    end


     ------------------------------------------------------------------
     ------------------------------------------------------------------
     -- Notes
     -- Status
     ------------------------------------------------------------------
     ------------------------------------------------------------------
     local notesText = formatNotes(rec.Notes)
     local statusApps = formatStatusApplications(rec["Status Applications"])
     addRow(root, "Notes", notesText)
     local statusRem  = formatStatusRemoval(rec["Status Removal"])
 
    if statusApps or statusRem then
    return tostring(root)
        addSectionHeader(root, "Status effects")
end
        addRow(root, "Applies", statusApps)
 
        addRow(root, "Removes", statusRem)
local function passiveMatchesUser(rec, userName)
    if type(rec) ~= "table" or not userName or userName == "" then
        return false
     end
     end


     local users = rec.Users
    ------------------------------------------------------------------
     if type(users) ~= "table" then
    -- Events
         return false
    ------------------------------------------------------------------
     local eventsText = formatEvents(rec.Events)
     if eventsText then
        addSectionHeader(root, "Events")
         addRow(root, "Triggers", eventsText)
     end
     end


     local userLower = mw.ustring.lower(userName)
     ------------------------------------------------------------------
 
    -- Notes
     local function listHas(list)
     ------------------------------------------------------------------
        if type(list) ~= "table" then
    if type(rec.Notes) == "table" and #rec.Notes > 0 then
            return false
         addSectionHeader(root, "Notes")
         end
        addRow(root, "Notes", table.concat(rec.Notes, "<br />"))
        for _, v in ipairs(list) do
            if type(v) == "string" and mw.ustring.lower(v) == userLower then
                return true
            end
        end
        return false
     end
     end


     -- Adjust this if you *only* want Classes.
     return tostring(root)
    if listHas(users.Classes)  then return true end
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
-- 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)


     -- Preferred explicit param, then unnamed, then fall back to the current page name.
     -- 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 entry point
-- Public: single-passive or auto-list dispatcher
----------------------------------------------------------------------
----------------------------------------------------------------------


Line 343: Line 498:
     local rec
     local rec


     -- 1) Prefer display Name (what editors actually know)
     -- 1) Prefer display Name
     if name and name ~= "" then
     if name and name ~= "" then
         rec = findPassiveByName(name)
         rec = findPassiveByName(name)
     end
     end


     -- 2) Fallback: Internal Name if explicitly given
     -- 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 no record, decide: list mode or unknown passive?
     -- 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()