Module:GameSkills: Difference between revisions
From SpiritVale Wiki
More actions
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 | -- Layout: | ||
-- Row 1: | -- Row 1: Slot 1 + Slot 2 (Icon + SkillType) | ||
-- Row 2: | -- Row 2: Slot 3 + Slot 4 (Description + Placeholder) | ||
-- Row 3: | -- Row 3: Slot 5 + Slot 6 (SourceType + QuickStats) | ||
-- Row 4: | -- Row 4: Slot 7 + Slot 8 (SpecialMechanics + LevelSelector) | ||
-- | -- | ||
-- Requires Common.js: | -- Requires Common.js: | ||
| Line 476: | Line 476: | ||
---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ||
local HERO_SLOT_ASSIGNMENT = { | |||
local | [1] = "IconName", | ||
[2] = "SkillType", | |||
[3] = "Description", | |||
[4] = "Placeholder", | |||
[5] = "SourceType", | |||
[6] = "QuickStats", | |||
[7] = "SpecialMechanics", | |||
[8] = "LevelSelector", | |||
} | } | ||
| Line 498: | Line 491: | ||
---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ||
-- | -- slotBox: standardized wrapper for all hero card slots. | ||
local function | 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 extraClasses then | ||
| Line 550: | Line 512: | ||
end | end | ||
if isEmpty then | if opts.isEmpty then | ||
box:addClass(" | box:addClass("sv-slot--empty") | ||
end | end | ||
local body = box:tag("div"):addClass(" | 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 { | |||
inner = tostring(wrap), | |||
classes = "module-icon-name", | |||
} | |||
end | end | ||
| Line 1,112: | Line 1,074: | ||
end | end | ||
return { | |||
inner = added and tostring(grid) or "", | |||
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", "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] | |||
if type(fn) ~= "function" then | |||
return nil | |||
return | |||
end | end | ||
local ok, out = pcall(fn, rec, ctx) | |||
local | if not ok then | ||
if not | return nil | ||
return | |||
end | end | ||
return normalizeResult(out) | |||
return | |||
end | end | ||
-- | -- isEmptySlotContent: true when a slot has no meaningful content. | ||
local function | -- 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( | 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 | ||
-- | -- renderHeroSlot: render a standardized hero slot by plugin assignment. | ||
local function renderHeroSlot(slotIndex, rec, ctx) | |||
local pluginName = HERO_SLOT_ASSIGNMENT[slotIndex] | |||
local function | |||
local pluginName = | |||
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 | if not res or isEmptySlotContent(res.inner) then | ||
return nil | return nil | ||
end | end | ||
| Line 1,694: | Line 1,645: | ||
---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ||
-- | -- buildHeroSlotsUI: build the standardized 4-row slot grid (2 columns). | ||
local function buildHeroSlotsUI(rec, ctx) | |||
local function | |||
local grid = mw.html.create("div") | local grid = mw.html.create("div") | ||
grid:addClass("sv-slot-grid") | |||
grid:addClass("sv- | |||
local slots = {} | local slots = {} | ||
for | for slot = 1, 8 do | ||
slots[slot] = renderHeroSlot(slot, rec, ctx) | |||
end | end | ||
local | local hasSlots = false | ||
for _, pair in ipairs( | 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( | grid:wikitext(slotBox(pair[1], left.classes, left.inner, { isEmpty = false })) | ||
grid:wikitext( | grid:wikitext(slotBox(pair[2], right.classes, right.inner, { isEmpty = false })) | ||
elseif left then | elseif left then | ||
grid:wikitext( | grid:wikitext(slotBox(pair[1], left.classes, left.inner, { isFull = true })) | ||
elseif right then | elseif right then | ||
grid:wikitext( | grid:wikitext(slotBox(pair[2], right.classes, right.inner, { isFull = true })) | ||
end | end | ||
end | end | ||
end | end | ||
if not | if not hasSlots then | ||
return "" | return "" | ||
end | end | ||
| Line 1,742: | Line 1,681: | ||
end | end | ||
-- | -- addHeroSlotsRow: add the standardized slot grid into the infobox table. | ||
local function | local function addHeroSlotsRow(tbl, slotsUI) | ||
if not | if not slotsUI or slotsUI == "" then | ||
return | return | ||
end | end | ||
local row = tbl:tag("tr") | local row = tbl:tag("tr") | ||
row:addClass(" | 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(" | cell:addClass("sv-slot-cell") | ||
cell:wikitext( | cell:wikitext(slotsUI) | ||
end | end | ||
| Line 1,806: | Line 1,745: | ||
end | end | ||
-- Standardized slot grid | |||
addHeroSlotsRow(root, buildHeroSlotsUI(rec, ctx)) | |||
-- Users (hide on direct skill page) | -- Users (hide on direct skill page) | ||