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


-- PLUGIN: SkillType (Hero Bar Slot 2) - 2 rows x 3 cells (desktop + mobile).
-- PLUGIN: QuickStats (Hero Module Slot 2) - 3x2 grid (range/area/cost/cast/cd/duration).
-- Rules:
-- NOTE: Hits intentionally does NOT belong here (it lives in SkillType).
--  - If skill is non-damaging, hide Damage/Element/Hits.
function PLUGINS.QuickStats(rec, ctx)
--   - If Hits is empty, hide Hits.
local level = ctx.level or 1
--  - If Combo is empty, hide Combo.
local maxLevel = ctx.maxLevel or 1
-- Ordering:
local promo = ctx.promo
--  - 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 mech = (type(rec) == "table" and type(rec.Mechanics) == "table") and rec.Mechanics or {}
local maxLevel = ctx.maxLevel or 1
local bt  = (type(mech["Basic Timings"]) == "table") and mech["Basic Timings"] or {}
local rc  = (type(mech["Resource Cost"]) == "table") and mech["Resource Cost"] or {}


local hideDamageBundle = (ctx.nonDamaging == true)
local function dash() return "—" end


-- valName: extract a display string from typical {Name/ID/Value} objects.
-- Range (0 => —)
local function valName(x)
local rangeVal = nil
if x == nil then return nil end
if mech.Range ~= nil and not isNoneLike(mech.Range) then
if type(x) == "table" then
local n = toNum(mech.Range)
if x.Name and x.Name ~= "" then return tostring(x.Name) end
if n ~= nil then
if x.ID and x.ID ~= "" then return tostring(x.ID) end
if n ~= 0 then
if x.Value ~= nil then return tostring(x.Value) end
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
if type(x) == "string" and x ~= "" then
return x
end
return nil
end
end


-- hitsDisplay: find + render Hits from multiple possible structured locations.
-- Area
local function hitsDisplay()
local areaVal = formatAreaSize(mech.Area)
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
-- Timings
return nil
local castVal = displayFromSeries(seriesFromValuePair(bt["Cast Time"], maxLevel), level)
end
local cdVal  = displayFromSeries(seriesFromValuePair(bt["Cooldown"],  maxLevel), level)
local durVal  = displayFromSeries(seriesFromValuePair(bt["Duration"],  maxLevel), level)


-- ValuePair-style table (Base/Per Level) => dynamic series
-- Promote status duration if needed
if type(h) == "table" then
if (durVal == nil) and type(promo) == "table" and type(promo.durationBlock) == "table" then
if h.Base ~= nil or h["Per Level"] ~= nil or type(h["Per Level"]) == "table" then
durVal = displayFromSeries(seriesFromValuePair(promo.durationBlock, maxLevel), level)
return displayFromSeries(seriesFromValuePair(h, maxLevel), level)
end
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
-- Cost: MP + HP
local vn = valName(h)
local function labeledSeries(block, label)
if vn and not isNoneLike(vn) then
local s = seriesFromValuePair(block, maxLevel)
return mw.text.nowiki(vn)
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
end
 
return any and s or nil
-- 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 mpS = labeledSeries(rc["Mana Cost"], "MP")
local function comboDisplay()
local hpS = labeledSeries(rc["Health Cost"], "HP")
local c = (type(mech.Combo) == "table") and mech.Combo or nil
if not c then return nil end


local typ = trim(c.Type)
local costSeries = {}
if not typ or isNoneLike(typ) then
for lv = 1, maxLevel do
return nil
local mp = mpS and mpS[lv] or "—"
end
local hp = hpS and hpS[lv] or "—"
 
local details = {}


local pct = formatUnitValue(c.Percent)
if mp ~= "—" and hp ~= "—" then
if pct and not isZeroish(pct) then
costSeries[lv] = mp .. " + " .. hp
table.insert(details, mw.text.nowiki(pct))
elseif mp ~= "—" then
end
costSeries[lv] = mp
 
elseif hp ~= "—" then
local dur = formatUnitValue(c.Duration)
costSeries[lv] = hp
if dur and not isZeroish(dur) then
else
table.insert(details, mw.text.nowiki(dur))
costSeries[lv] = "—"
end
end
end


if #details > 0 then
local costVal = displayFromSeries(costSeries, level)
return mw.text.nowiki(typ) .. " (" .. table.concat(details, ", ") .. ")"
end
return mw.text.nowiki(typ)
end


local grid = mw.html.create("div")
local grid = mw.html.create("div")
grid:addClass("sv-type-grid")
grid:addClass("sv-m4-grid")
grid:addClass("sv-compact-root")
grid:addClass("sv-compact-root")


local added = false
local function addCell(label, val)
 
local cell = grid:tag("div"):addClass("sv-m4-cell")
-- addChunk: add one labeled value cell (key drives CSS ordering).
cell:tag("div"):addClass("sv-m4-label"):wikitext(mw.text.nowiki(label))
local function addChunk(key, label, valueHtml)
cell:tag("div"):addClass("sv-m4-value"):wikitext(val or dash())
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 dmg = valName(typeBlock.Damage or typeBlock["Damage Type"])
local ele = valName(typeBlock.Element or typeBlock["Element Type"])
local hits = hitsDisplay()
 
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
-- Hits: only render when present and meaningful
if hits then
addChunk("hits", "Hits", hits)
end
end
end


-- Target + Cast
addCell("Range",    rangeVal)
local tgt = valName(typeBlock.Target or typeBlock["Target Type"])
addCell("Area",      areaVal)
local cst = valName(typeBlock.Cast  or typeBlock["Cast Type"])
addCell("Cost",     costVal)
 
addCell("Cast Time", castVal)
if tgt and not isNoneLike(tgt) then
addCell("Cooldown",  cdVal)
addChunk("target", "Target", mw.text.nowiki(tgt))
addCell("Duration", durVal)
end
if cst and not isNoneLike(cst) then
addChunk("cast", "Cast", mw.text.nowiki(cst))
end
 
-- Combo (moved here)
local combo = comboDisplay()
if combo then
addChunk("combo", "Combo", combo)
end


return {
return {
inner = added and tostring(grid) or "",
inner = tostring(grid),
classes = "module-skill-type",
classes = "module-quick-stats",
}
}
end
end