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:GameSkills: Difference between revisions

From SpiritVale Wiki
No edit summary
No edit summary
 
(One intermediate revision by the same user not shown)
Line 3: Line 3:
-- Phase 6.5+ (Plug-in Slot Architecture)
-- Phase 6.5+ (Plug-in Slot Architecture)
--
--
-- Layout (Module 1–8):
-- Layout:
--  Row 1: Module 1 + Module 2   → Icon + Name, Skill Type strip
--  Row 1: Slot 1 + Slot 2 (Icon + SkillType)
--  Row 2: Module 3 + Module 4   → Description (Module 3), open slot (Module 4)
--  Row 2: Slot 3 + Slot 4 (Description + Placeholder)
--  Row 3: Module 5 + Module 6   → Source Types, Quick Stats
--  Row 3: Slot 5 + Slot 6 (SourceType + QuickStats)
--  Row 4: Module 7 + Module 8   → Special Mechanics, Level Selector
--  Row 4: Slot 7 + Slot 8 (SpecialMechanics + LevelSelector)
--
--
-- Requires Common.js:
-- Requires Common.js:
Line 476: Line 476:
----------------------------------------------------------------------
----------------------------------------------------------------------


-- Module row order (see layout notes above).
local HERO_SLOT_ASSIGNMENT = {
local MODULE_SLOT_ASSIGNMENT = {
[1] = "IconName",
        [1] = "IconName",
[2] = "SkillType",
        [2] = "SkillType", -- Damage/Element/Hits/Target/Cast/Combo strip
[3] = "Description",
        [3] = "Description",
[4] = "Placeholder",
        [4] = nil, -- placeholder / intentionally open
[5] = "SourceType",
        [5] = "SourceType",
[6] = "QuickStats",
        [6] = "QuickStats",
[7] = "SpecialMechanics",
        [7] = "SpecialMechanics",
[8] = "LevelSelector",
        [8] = "LevelSelector",
}
 
local MODULE_GRID_PAIRS = {
        { 3, 4 },
        { 5, 6 },
        { 7, 8 },
}
}


Line 498: Line 491:
----------------------------------------------------------------------
----------------------------------------------------------------------


-- heroBarBox: wrapper for hero-bar slot modules.
-- slotBox: standardized wrapper for all hero card slots.
local function heroBarBox(slot, extraClasses, innerHtml, isEmpty)
local function slotBox(slot, extraClasses, innerHtml, opts)
        local box = mw.html.create("div")
opts = opts or {}
        box:addClass("hero-bar-module")
        box:addClass("hero-bar-module-" .. tostring(slot))
        box:addClass("sv-module-slot")
        box:addClass("sv-module-slot-" .. tostring(slot))
        box:attr("data-hero-bar-module", tostring(slot))
        box:attr("data-module-slot", tostring(slot))
 
if slot == 2 then
box:addClass("sv-herobar-compact")
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 isEmpty then
local box = mw.html.create("div")
box:addClass("hero-bar-module-empty")
box:addClass("sv-slot")
end
box:addClass("sv-slot--" .. tostring(slot))
box:attr("data-hero-slot", tostring(slot))


local body = box:tag("div"):addClass("hero-bar-module-body")
if opts.isFull then
if innerHtml and innerHtml ~= "" then
box:addClass("sv-slot--full")
body:wikitext(innerHtml)
end
end
return tostring(box)
end
-- moduleBox: wrapper for hero-module (2x2 grid) slot modules.
local function moduleBox(slot, extraClasses, innerHtml, isEmpty)
        local box = mw.html.create("div")
        box:addClass("hero-module")
        box:addClass("hero-module-" .. tostring(slot))
        box:addClass("sv-module-slot")
        box:addClass("sv-module-slot-" .. tostring(slot))
        box:attr("data-hero-module", tostring(slot))
        box:attr("data-module-slot", tostring(slot))


if extraClasses then
if extraClasses then
Line 550: Line 512:
end
end


if isEmpty then
if opts.isEmpty then
box:addClass("hero-module-empty")
box:addClass("sv-slot--empty")
end
end


local body = box:tag("div"):addClass("hero-module-body")
local body = box:tag("div"):addClass("sv-slot__body")
if innerHtml and innerHtml ~= "" then
if innerHtml and innerHtml ~= "" then
body:wikitext(innerHtml)
body:wikitext(innerHtml)
Line 927: Line 889:
:wikitext(title)
:wikitext(title)


return {
return {
inner = tostring(wrap),
inner = tostring(wrap),
classes = "module-herobar-1",
classes = "module-icon-name",
}
}
end
end


Line 1,112: Line 1,074:
end
end


return {
        return {
inner = added and tostring(grid) or "",
                inner = added and tostring(grid) or "",
classes = "module-skill-type",
                classes = "module-skill-type",
}
        }
end
 
-- PLUGIN: Description (Hero Slot 3) - primary description text.
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
 
-- PLUGIN: Placeholder (Hero Slot 4) - reserved/blank.
function PLUGINS.Placeholder()
        return nil
end
end


Line 1,235: Line 1,219:
local hasMod = (basisWord ~= nil and tostring(basisWord) ~= "")
local hasMod = (basisWord ~= nil and tostring(basisWord) ~= "")


local extra = { "skill-source-module" }
local extra = { "skill-source-module", "module-source-type" }
table.insert(extra, hasMod and "sv-has-mod" or "sv-no-mod")
table.insert(extra, hasMod and "sv-has-mod" or "sv-no-mod")


Line 1,605: Line 1,589:
-- safeCallPlugin: pcall wrapper to prevent infobox failure on plugin errors.
-- safeCallPlugin: pcall wrapper to prevent infobox failure on plugin errors.
local function safeCallPlugin(name, rec, ctx)
local function safeCallPlugin(name, rec, ctx)
local fn = PLUGINS[name]
        local fn = PLUGINS[name]
if type(fn) ~= "function" then
        if type(fn) ~= "function" then
return nil
                 return nil
end
local ok, out = pcall(fn, rec, ctx)
if not ok then
return nil
end
return normalizeResult(out)
end
 
-- renderHeroBarSlot: render a hero-bar slot by plugin assignment.
local function renderHeroBarSlot(slotIndex, rec, ctx)
        local pluginName = MODULE_SLOT_ASSIGNMENT[slotIndex]
        if not pluginName then
                 return heroBarBox(slotIndex, nil, "", true)
         end
         end
 
         local ok, out = pcall(fn, rec, ctx)
         local res = safeCallPlugin(pluginName, rec, ctx)
         if not ok then
         if not res or not res.inner or res.inner == "" then
                 return nil
                 return heroBarBox(slotIndex, nil, "", true)
         end
         end
 
         return normalizeResult(out)
         return heroBarBox(slotIndex, res.classes, res.inner, false)
end
end


-- isEmptyModuleContent: true when a module slot has no meaningful content.
-- isEmptySlotContent: true when a slot has no meaningful content.
local function isEmptyModuleContent(inner)
-- NOTE: JS placeholders (sv-dyn spans, slider markup) are considered content.
local function isEmptySlotContent(inner)
         if inner == nil then return true end
         if inner == nil then return true end


         local trimmed = mw.text.trim(tostring(inner))
        local raw = tostring(inner)
 
        -- Guard rails for JS-injected regions.
        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
                end
        end
 
         local trimmed = mw.text.trim(raw)
         if trimmed == "" or trimmed == "—" then
         if trimmed == "" or trimmed == "—" then
                 return true
                 return true
Line 1,644: Line 1,623:
end
end


-- mergeClasses: merge string/table classes with an optional extra class.
-- renderHeroSlot: render a standardized hero slot by plugin assignment.
local function mergeClasses(base, extra)
local function renderHeroSlot(slotIndex, rec, ctx)
        local list = {}
         local pluginName = HERO_SLOT_ASSIGNMENT[slotIndex]
 
        local function add(item)
                if type(item) == "string" then
                        for cls in mw.ustring.gmatch(item, "[^%s]+") do
                                table.insert(list, cls)
                        end
                elseif type(item) == "table" then
                        for _, c in ipairs(item) do
                                add(c)
                        end
                end
        end
 
        add(base)
        add(extra)
 
        if #list == 0 then
                return nil
        elseif #list == 1 then
                return list[1]
        end
 
        return list
end
 
-- renderModuleSlot: render a hero-module slot by plugin assignment.
local function renderModuleSlot(slotIndex, rec, ctx)
         local pluginName = MODULE_SLOT_ASSIGNMENT[slotIndex]
         if not pluginName then
         if not pluginName then
                 return nil
                 return nil
Line 1,680: Line 1,631:


         local res = safeCallPlugin(pluginName, rec, ctx)
         local res = safeCallPlugin(pluginName, rec, ctx)
         if not res or isEmptyModuleContent(res.inner) then
         if not res or isEmptySlotContent(res.inner) then
                 return nil
                 return nil
         end
         end
Line 1,694: Line 1,645:
----------------------------------------------------------------------
----------------------------------------------------------------------


-- buildHeroBarUI: build the top hero bar (2 slots).
-- buildHeroSlotsUI: build the standardized 4-row slot grid (2 columns).
local function buildHeroBarUI(rec, ctx)
local function buildHeroSlotsUI(rec, ctx)
local bar = mw.html.create("div")
bar:addClass("hero-bar-grid")
bar:wikitext(renderHeroBarSlot(1, rec, ctx))
bar:wikitext(renderHeroBarSlot(2, rec, ctx))
return tostring(bar)
end
 
-- buildModuleGridUI: build the lower module rows (Modules 3–8, 3 rows).
local function buildModuleGridUI(rec, ctx)
         local grid = mw.html.create("div")
         local grid = mw.html.create("div")
        grid:addClass("hero-modules-grid")
         grid:addClass("sv-slot-grid")
         grid:addClass("sv-modules-grid")


         local slots = {}
         local slots = {}
         for _, pair in ipairs(MODULE_GRID_PAIRS) do
         for slot = 1, 8 do
                 for _, slot in ipairs(pair) do
                 slots[slot] = renderHeroSlot(slot, rec, ctx)
                        slots[slot] = renderModuleSlot(slot, rec, ctx)
                end
         end
         end


         local hasModules = false
         local hasSlots = false
         for _, pair in ipairs(MODULE_GRID_PAIRS) do
         for _, pair in ipairs({ { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } }) do
                 local left  = slots[pair[1]]
                 local left  = slots[pair[1]]
                 local right = slots[pair[2]]
                 local right = slots[pair[2]]


                 if left or right then
                 if left or right then
                        hasSlots = true
                         if left and right then
                         if left and right then
                                 grid:wikitext(moduleBox(pair[1], left.classes, left.inner, false))
                                 grid:wikitext(slotBox(pair[1], left.classes, left.inner, { isEmpty = false }))
                                 grid:wikitext(moduleBox(pair[2], right.classes, right.inner, false))
                                 grid:wikitext(slotBox(pair[2], right.classes, right.inner, { isEmpty = false }))
                         elseif left then
                         elseif left then
                                 grid:wikitext(moduleBox(pair[1], mergeClasses(left.classes, "hero-module-full"), left.inner, false))
                                 grid:wikitext(slotBox(pair[1], left.classes, left.inner, { isFull = true }))
                         elseif right then
                         elseif right then
                                 grid:wikitext(moduleBox(pair[2], mergeClasses(right.classes, "hero-module-full"), right.inner, false))
                                 grid:wikitext(slotBox(pair[2], right.classes, right.inner, { isFull = true }))
                         end
                         end
                        hasModules = true
                 end
                 end
         end
         end


         if not hasModules then
         if not hasSlots then
                 return ""
                 return ""
         end
         end
Line 1,742: Line 1,681:
end
end


-- addModuleGridRow: add the lower module grid into the infobox table.
-- addHeroSlotsRow: add the standardized slot grid into the infobox table.
local function addModuleGridRow(tbl, modulesUI)
local function addHeroSlotsRow(tbl, slotsUI)
         if not modulesUI or modulesUI == "" then
         if not slotsUI or slotsUI == "" then
                 return
                 return
         end
         end


         local row = tbl:tag("tr")
         local row = tbl:tag("tr")
         row:addClass("hero-modules-row")
         row:addClass("sv-slot-row")


         local cell = row:tag("td")
         local cell = row:tag("td")
         cell:attr("colspan", 2)
         cell:attr("colspan", 2)
         cell:addClass("hero-modules-cell")
         cell:addClass("sv-slot-cell")
         cell:wikitext(modulesUI)
         cell:wikitext(slotsUI)
end
end


Line 1,806: Line 1,745:
end
end


        -- Module 1–2 (Icon + Skill Type)
-- Standardized slot grid
        local heroRow = root:tag("tr")
addHeroSlotsRow(root, buildHeroSlotsUI(rec, ctx))
        heroRow:addClass("spiritvale-infobox-main")
        heroRow:addClass("sv-hero-title-row")
        heroRow:addClass("hero-title-bar")
 
local heroCell = heroRow:tag("th")
        heroCell:attr("colspan", 2)
        heroCell:addClass("sv-hero-title-cell")
        heroCell:wikitext(buildHeroBarUI(rec, ctx))
 
        -- Module grid (rows 2–4)
        addModuleGridRow(root, buildModuleGridUI(rec, ctx))


-- Users (hide on direct skill page)
-- Users (hide on direct skill page)