Module:GameSkills: Difference between revisions
From SpiritVale Wiki
More actions
No edit summary |
No edit summary |
||
| Line 873: | Line 873: | ||
end | end | ||
-- PLUGIN: | -- PLUGIN: SkillType (Hero Bar Slot 2) - 2 rows x 3 cells (desktop + mobile). | ||
function PLUGINS. | -- Rules: | ||
local | -- - If skill is non-damaging, hide Damage/Element/Hits. | ||
-- - If Hits is empty, hide Hits. | |||
-- - If Combo is empty, hide Combo. | |||
-- Ordering: | |||
-- - Desktop: Damage, Element, Hits, Target, Cast, Combo | |||
-- - Mobile: Damage, Element, Target, Cast, Hits, Combo (CSS reorder) | |||
function PLUGINS.SkillType(rec, ctx) | |||
local typeBlock = (type(rec.Type) == "table") and rec.Type or {} | |||
local mech = (type(rec.Mechanics) == "table") and rec.Mechanics or {} | |||
local level = ctx.level or 1 | |||
local level = ctx.level or 1 | |||
local maxLevel = ctx.maxLevel or 1 | local maxLevel = ctx.maxLevel or 1 | ||
local | local hideDamageBundle = (ctx.nonDamaging == true) | ||
-- valName: extract a display string from typical {Name/ID/Value} objects. | |||
-- NOTE: Includes number support so Hits=2 (number) doesn't get dropped. | |||
local function valName(x) | |||
if x == nil then return nil end | |||
local | if type(x) == "table" then | ||
if x.Name and x.Name ~= "" then return tostring(x.Name) end | |||
if x.ID and x.ID ~= "" then return tostring(x.ID) end | |||
if x.Value ~= nil then return tostring(x.Value) end | |||
end | |||
if type(x) == "number" then | |||
return tostring(x) | |||
end | |||
if type(x) == "string" and x ~= "" then | |||
return x | |||
end | |||
return nil | |||
end | end | ||
-- hitsDisplay: find + render Hits from multiple possible structured locations. | |||
local function hitsDisplay() | |||
local h = | |||
typeBlock.Hits or typeBlock["Hits"] or typeBlock["Hit Count"] or typeBlock["Hits Count"] or | |||
mech.Hits or mech["Hits"] or mech["Hit Count"] or mech["Hits Count"] or | |||
rec.Hits or rec["Hits"] | |||
if h == nil or isNoneLike(h) then | |||
return nil | |||
end | |||
-- ValuePair-style table (Base/Per Level) => dynamic series | |||
if type(h) == "table" then | |||
if h.Base ~= nil or h["Per Level"] ~= nil or type(h["Per Level"]) == "table" then | |||
return displayFromSeries(seriesFromValuePair(h, maxLevel), level) | |||
end | |||
-- Unit block {Value, Unit} | |||
if h.Value ~= nil then | |||
local t = formatUnitValue(h) | |||
return t and mw.text.nowiki(t) or nil | |||
end | |||
-- Fallback name extraction | |||
local vn = valName(h) | |||
if vn and not isNoneLike(vn) then | |||
return mw.text.nowiki(vn) | |||
local | |||
if | |||
end | end | ||
end | end | ||
-- Scalar number/string | |||
if type(h) == "number" then | |||
return mw.text.nowiki(fmtNum(h)) | |||
end | |||
if type(h) == "string" then | |||
local t = trim(h) | |||
return (t and not isNoneLike(t)) and mw.text.nowiki(t) or nil | |||
end | |||
return nil | |||
end | end | ||
-- | -- comboDisplay: render Combo as a compact text block (Type (+ details)). | ||
local | local function comboDisplay() | ||
local c = (type(mech.Combo) == "table") and mech.Combo or nil | |||
if not c then return nil end | |||
local typ = trim(c.Type) | |||
if not typ or isNoneLike(typ) then | |||
return nil | |||
end | |||
local details = {} | |||
local pct = formatUnitValue(c.Percent) | |||
if pct and not isZeroish(pct) then | |||
table.insert(details, mw.text.nowiki(pct)) | |||
end | |||
local dur = formatUnitValue(c.Duration) | |||
if dur and not isZeroish(dur) then | |||
table.insert(details, mw.text.nowiki(dur)) | |||
end | |||
if #details > 0 then | |||
return mw.text.nowiki(typ) .. " (" .. table.concat(details, ", ") .. ")" | |||
if | |||
end | end | ||
return | return mw.text.nowiki(typ) | ||
end | end | ||
local | local grid = mw.html.create("div") | ||
local | grid:addClass("sv-type-grid") | ||
grid:addClass("sv-compact-root") | |||
local added = false | |||
-- addChunk: add one labeled value cell (key drives CSS ordering). | |||
local function addChunk(key, label, valueHtml) | |||
if valueHtml == nil or valueHtml == "" then return end | |||
added = true | |||
local chunk = grid:tag("div") | |||
:addClass("sv-type-chunk") | |||
:addClass("sv-type-" .. tostring(key)) | |||
:attr("data-type-key", tostring(key)) | |||
chunk:tag("div") | |||
:addClass("sv-type-label") | |||
:wikitext(mw.text.nowiki(label)) | |||
chunk:tag("div") | |||
:addClass("sv-type-value") | |||
:wikitext(valueHtml) | |||
end | |||
-- Damage + Element + Hits bundle (hidden when non-damaging) | |||
if not hideDamageBundle then | |||
local | local dmg = valName(typeBlock.Damage or typeBlock["Damage Type"]) | ||
local | local ele = valName(typeBlock.Element or typeBlock["Element Type"]) | ||
local hits = hitsDisplay() | |||
if | if dmg and not isNoneLike(dmg) then | ||
addChunk("damage", "Damage", mw.text.nowiki(dmg)) | |||
end | |||
if ele and not isNoneLike(ele) then | |||
addChunk("element", "Element", mw.text.nowiki(ele)) | |||
end | |||
if hits then | |||
addChunk("hits", "Hits", hits) | |||
end | end | ||
end | end | ||
local | -- Target + Cast | ||
local tgt = valName(typeBlock.Target or typeBlock["Target Type"]) | |||
local cst = valName(typeBlock.Cast or typeBlock["Cast Type"]) | |||
if tgt and not isNoneLike(tgt) then | |||
addChunk("target", "Target", mw.text.nowiki(tgt)) | |||
end | |||
if cst and not isNoneLike(cst) then | |||
addChunk("cast", "Cast", mw.text.nowiki(cst)) | |||
end | |||
-- Combo | |||
local combo = comboDisplay() | |||
if combo then | |||
addChunk("combo", "Combo", combo) | |||
end | end | ||
return { | return { | ||
inner = tostring(grid), | inner = added and tostring(grid) or "", | ||
classes = "module- | classes = "module-skill-type", | ||
} | } | ||
end | end | ||
| Line 1,161: | Line 1,186: | ||
-- PLUGIN: QuickStats (Hero Module Slot 2) - 3x2 grid (range/area/cost/cast/cd/duration). | -- PLUGIN: QuickStats (Hero Module Slot 2) - 3x2 grid (range/area/cost/cast/cd/duration). | ||
-- NOTE: Hits does NOT live here (it lives in SkillType). | |||
function PLUGINS.QuickStats(rec, ctx) | function PLUGINS.QuickStats(rec, ctx) | ||
local level = ctx.level or 1 | local level = ctx.level or 1 | ||
| Line 1,265: | Line 1,291: | ||
-- - Flags (deduped) | -- - Flags (deduped) | ||
-- - Special mechanics (mech.Effects) | -- - Special mechanics (mech.Effects) | ||
-- NOTE: Combo | -- NOTE: Combo lives in SkillType (Hero Bar Slot 2). | ||
function PLUGINS.SpecialMechanics(rec, ctx) | function PLUGINS.SpecialMechanics(rec, ctx) | ||
local level = ctx.level or 1 | local level = ctx.level or 1 | ||
| Line 1,279: | Line 1,305: | ||
local flagSet = {} | local flagSet = {} | ||
local denyFlags = { | local denyFlags = { | ||
["self centered"] = true, | ["self centered"] = true, | ||
["self-centred"] = true, | ["self-centred"] = true, | ||
["bond"] = true, | ["bond"] = true, | ||
["combo"] = true, | ["combo"] = true, | ||
| Line 1,402: | Line 1,426: | ||
inner = tostring(root), | inner = tostring(root), | ||
classes = "module-special-mechanics", | classes = "module-special-mechanics", | ||
} | |||
end | |||
-- PLUGIN: LevelSelector (Hero Module Slot 4) - JS level slider. | |||
function PLUGINS.LevelSelector(rec, ctx) | |||
local level = ctx.level or 1 | |||
local maxLevel = ctx.maxLevel or 1 | |||
local inner = mw.html.create("div") | |||
inner:addClass("sv-level-ui") | |||
inner:tag("div") | |||
:addClass("sv-level-label") | |||
:wikitext("Level <span class=\"sv-level-num\">" .. tostring(level) .. "</span> / " .. tostring(maxLevel)) | |||
local slider = inner:tag("div"):addClass("sv-level-slider") | |||
if tonumber(maxLevel) and tonumber(maxLevel) > 1 then | |||
slider:tag("input") | |||
:attr("type", "range") | |||
:attr("min", "1") | |||
:attr("max", tostring(maxLevel)) | |||
:attr("value", tostring(level)) | |||
:addClass("sv-level-range") | |||
:attr("aria-label", "Skill level select") | |||
else | |||
inner:addClass("sv-level-ui-single") | |||
slider:addClass("sv-level-slider-single") | |||
end | |||
return { | |||
inner = tostring(inner), | |||
classes = "module-level-selector", | |||
} | } | ||
end | end | ||