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
Line 873: Line 873:
end
end


-- PLUGIN: ReservedInfo (Hero Bar Slot 2 placeholder) - kept for future.
-- PLUGIN: SkillType (Hero Bar Slot 2) - 2 rows x 3 cells (desktop + mobile).
function PLUGINS.ReservedInfo(rec, ctx)
-- Rules:
local wrap = mw.html.create("div")
--  - If skill is non-damaging, hide Damage/Element/Hits.
wrap:addClass("sv-herobar-2-wrap")
--  - If Hits is empty, hide Hits.
return {
--  - If Combo is empty, hide Combo.
inner = tostring(wrap),
-- Ordering:
classes = "module-herobar-2",
--  - Desktop: Damage, Element, Hits, Target, Cast, Combo
}
--  - Mobile:  Damage, Element, Target, Cast, Hits, Combo (CSS reorder)
end
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 {}


-- PLUGIN: LevelSelector (Hero Module Slot 4) - JS level slider.
local level   = ctx.level or 1
function PLUGINS.LevelSelector(rec, ctx)
local level = ctx.level or 1
local maxLevel = ctx.maxLevel or 1
local maxLevel = ctx.maxLevel or 1


local inner = mw.html.create("div")
local hideDamageBundle = (ctx.nonDamaging == true)
inner:addClass("sv-level-ui")


inner:tag("div")
-- valName: extract a display string from typical {Name/ID/Value} objects.
:addClass("sv-level-label")
-- NOTE: Includes number support so Hits=2 (number) doesn't get dropped.
:wikitext("Level <span class=\"sv-level-num\">" .. tostring(level) .. "</span> / " .. tostring(maxLevel))
local function valName(x)
 
if x == nil then return nil end
local slider = inner:tag("div"):addClass("sv-level-slider")
if type(x) == "table" then
 
if x.Name and x.Name ~= "" then return tostring(x.Name) end
if tonumber(maxLevel) and tonumber(maxLevel) > 1 then
if x.ID and x.ID ~= "" then return tostring(x.ID) end
slider:tag("input")
if x.Value ~= nil then return tostring(x.Value) end
:attr("type", "range")
end
:attr("min", "1")
if type(x) == "number" then
:attr("max", tostring(maxLevel))
return tostring(x)
:attr("value", tostring(level))
end
:addClass("sv-level-range")
if type(x) == "string" and x ~= "" then
:attr("aria-label", "Skill level select")
return x
else
end
inner:addClass("sv-level-ui-single")
return nil
slider:addClass("sv-level-slider-single")
end
end


return {
-- hitsDisplay: find + render Hits from multiple possible structured locations.
inner = tostring(inner),
local function hitsDisplay()
classes = "module-level-selector",
local h =
}
typeBlock.Hits or typeBlock["Hits"] or typeBlock["Hit Count"] or typeBlock["Hits Count"] or
end
mech.Hits or mech["Hits"] or mech["Hit Count"] or mech["Hits Count"] or
rec.Hits or rec["Hits"]


-- PLUGIN: QuickStats (Hero Module Slot 2) - 3x2 grid (range/area/cost/cast/cd/duration).
if h == nil or isNoneLike(h) then
-- NOTE: Hits intentionally does NOT belong here (it lives in SkillType).
return nil
function PLUGINS.QuickStats(rec, ctx)
end
local level = ctx.level or 1
local maxLevel = ctx.maxLevel or 1
local promo = ctx.promo


local mech = (type(rec) == "table" and type(rec.Mechanics) == "table") and rec.Mechanics or {}
-- ValuePair-style table (Base/Per Level) => dynamic series
local bt  = (type(mech["Basic Timings"]) == "table") and mech["Basic Timings"] or {}
if type(h) == "table" then
local rc  = (type(mech["Resource Cost"]) == "table") and mech["Resource Cost"] or {}
if h.Base ~= nil or h["Per Level"] ~= nil or type(h["Per Level"]) == "table" then
return displayFromSeries(seriesFromValuePair(h, maxLevel), level)
end


local function dash() return "—" end
-- Unit block {Value, Unit}
if h.Value ~= nil then
local t = formatUnitValue(h)
return t and mw.text.nowiki(t) or nil
end


-- Range (0 => —)
-- Fallback name extraction
local rangeVal = nil
local vn = valName(h)
if mech.Range ~= nil and not isNoneLike(mech.Range) then
if vn and not isNoneLike(vn) then
local n = toNum(mech.Range)
return mw.text.nowiki(vn)
if n ~= nil then
if n ~= 0 then
rangeVal = mw.text.nowiki(formatUnitValue(mech.Range) or tostring(mech.Range))
end
else
local t = mw.text.trim(tostring(mech.Range))
if t ~= "" and not isNoneLike(t) then
rangeVal = mw.text.nowiki(t)
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


-- Area
-- comboDisplay: render Combo as a compact text block (Type (+ details)).
local areaVal = formatAreaSize(mech.Area)
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 = {}


-- Timings
local pct = formatUnitValue(c.Percent)
local castVal = displayFromSeries(seriesFromValuePair(bt["Cast Time"], maxLevel), level)
if pct and not isZeroish(pct) then
local cdVal  = displayFromSeries(seriesFromValuePair(bt["Cooldown"],  maxLevel), level)
table.insert(details, mw.text.nowiki(pct))
local durVal  = displayFromSeries(seriesFromValuePair(bt["Duration"],  maxLevel), level)
end


-- Promote status duration if needed
local dur = formatUnitValue(c.Duration)
if (durVal == nil) and type(promo) == "table" and type(promo.durationBlock) == "table" then
if dur and not isZeroish(dur) then
durVal = displayFromSeries(seriesFromValuePair(promo.durationBlock, maxLevel), level)
table.insert(details, mw.text.nowiki(dur))
end
end


-- Cost: MP + HP
if #details > 0 then
local function labeledSeries(block, label)
return mw.text.nowiki(typ) .. " (" .. table.concat(details, ", ") .. ")"
local s = seriesFromValuePair(block, maxLevel)
if not s then return nil end
local any = false
for i, v in ipairs(s) do
if v ~= "" then
s[i] = tostring(v) .. " " .. label
any = true
else
s[i] = ""
end
end
end
return any and s or nil
return mw.text.nowiki(typ)
end
end


local mpS = labeledSeries(rc["Mana Cost"], "MP")
local grid = mw.html.create("div")
local hpS = labeledSeries(rc["Health Cost"], "HP")
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


local costSeries = {}
-- Damage + Element + Hits bundle (hidden when non-damaging)
for lv = 1, maxLevel do
if not hideDamageBundle then
local mp = mpS and mpS[lv] or ""
local dmg  = valName(typeBlock.Damage or typeBlock["Damage Type"])
local hp = hpS and hpS[lv] or ""
local ele  = valName(typeBlock.Element or typeBlock["Element Type"])
local hits = hitsDisplay()


if mp ~= "—" and hp ~= "—" then
if dmg and not isNoneLike(dmg) then
costSeries[lv] = mp .. " + " .. hp
addChunk("damage", "Damage", mw.text.nowiki(dmg))
elseif mp ~= "—" then
end
costSeries[lv] = mp
if ele and not isNoneLike(ele) then
elseif hp ~= "" then
addChunk("element", "Element", mw.text.nowiki(ele))
costSeries[lv] = hp
end
else
if hits then
costSeries[lv] = ""
addChunk("hits", "Hits", hits)
end
end
end
end


local costVal = displayFromSeries(costSeries, level)
-- Target + Cast
local tgt = valName(typeBlock.Target or typeBlock["Target Type"])
local cst = valName(typeBlock.Cast  or typeBlock["Cast Type"])


local grid = mw.html.create("div")
if tgt and not isNoneLike(tgt) then
grid:addClass("sv-m4-grid")
addChunk("target", "Target", mw.text.nowiki(tgt))
grid:addClass("sv-compact-root")
end
if cst and not isNoneLike(cst) then
addChunk("cast", "Cast", mw.text.nowiki(cst))
end


local function addCell(label, val)
-- Combo
local cell = grid:tag("div"):addClass("sv-m4-cell")
local combo = comboDisplay()
cell:tag("div"):addClass("sv-m4-label"):wikitext(mw.text.nowiki(label))
if combo then
cell:tag("div"):addClass("sv-m4-value"):wikitext(val or dash())
addChunk("combo", "Combo", combo)
end
end
addCell("Range",    rangeVal)
addCell("Area",      areaVal)
addCell("Cost",      costVal)
addCell("Cast Time", castVal)
addCell("Cooldown",  cdVal)
addCell("Duration",  durVal)


return {
return {
inner = tostring(grid),
inner = added and tostring(grid) or "",
classes = "module-quick-stats",
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 has been moved to SkillType (Hero Bar Slot 2).
-- 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 = {}


-- Filter out mechanics you said to remove/ignore here.
local denyFlags = {
local denyFlags = {
["self centered"] = true,
["self centered"] = true,
["self-centred"] = true,
["self-centred"] = true,
["self centered"] = 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