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 66: Line 66:
s = mw.text.trim(s)
s = mw.text.trim(s)
if s == "" then return nil end
if s == "" then return nil end
return s
end
local function toNum(v)
if type(v) == "number" then return v end
if type(v) == "string" then return tonumber(v) end
return nil
end
local function fmtNum(n)
if type(n) ~= "number" then
return (n ~= nil) and tostring(n) or nil
end
if math.abs(n - math.floor(n)) < 1e-9 then
return tostring(math.floor(n))
end
local s = string.format("%.4f", n)
s = mw.ustring.gsub(s, "0+$", "")
s = mw.ustring.gsub(s, "%.$", "")
return s
return s
end
end


-- Handles either a scalar OR {Value=..., Unit=...}
-- Handles either a scalar OR {Value=..., Unit=...}
-- IMPORTANT: do NOT convert percent_decimal/percent_whole; just format around stored values.
local function formatUnitValue(v)
local function formatUnitValue(v)
if type(v) == "table" and v.Value ~= nil then
if type(v) == "table" and v.Value ~= nil then
Line 75: Line 95:
local val = v.Value
local val = v.Value


-- Best-effort formatting for common unit types
if unit == "percent_decimal" or unit == "percent_whole" or unit == "percent" then
if unit == "percent_decimal" then
return tostring((val or 0) * 100) .. "%"
elseif unit == "percent_whole" or unit == "percent" then
return tostring(val) .. "%"
return tostring(val) .. "%"
elseif unit == "seconds" then
elseif unit == "seconds" then
Line 170: Line 187:
-- Turns a {Base, Per Level} block into lines:
-- Turns a {Base, Per Level} block into lines:
--  - If Per Level is a list: "Name: v1 / v2 / v3 ..."
--  - If Per Level is a list: "Name: v1 / v2 / v3 ..."
--  - Else: "Name: BaseValue" and (if non-zero) "Name Per Level: PerValue"
--  - If Per Level scalar and non-zero: add "Name Per Level: X"
local function valuePairLines(name, block)
local function valuePairLines(name, block)
if type(block) ~= "table" then
if type(block) ~= "table" then
Line 179: Line 196:
local per  = block["Per Level"]
local per  = block["Per Level"]


-- Per Level list (already expanded by wikiprep)
-- Per Level list (wikiprep expansion)
if type(per) == "table" then
if type(per) == "table" then
if #per == 0 then
if #per == 0 then
Line 233: Line 250:
end
end


-- When you already have a label outside and just want the values.
local function valuePairRawText(block)
local function valuePairRawText(block)
if type(block) ~= "table" then
if type(block) ~= "table" then
Line 265: Line 281:
end
end


local function formatMainDamage(list)
local function basisLabel(entry)
if type(list) ~= "table" or #list == 0 then
local atk = entry and entry["ATK-Based"]
local matk = entry and entry["MATK-Based"]
if atk and matk then
return "Attack/Magic Attack"
elseif atk then
return "Attack"
elseif matk then
return "Magic Attack"
end
return "Damage"
end
 
local function formatDamageEntry(entry, maxLevel)
if type(entry) ~= "table" then
return nil
end
 
local baseN = toNum(entry["Base %"])
local perN  = toNum(entry["Per Level %"])
 
if baseN == nil and perN == nil then
return nil
return nil
end
end
local parts = {}
 
for _, d in ipairs(list) do
local baseText = (baseN ~= nil) and (fmtNum(baseN) .. "%") or (tostring(entry["Base %"]) .. "%")
if type(d) == "table" then
local basis = basisLabel(entry)
local kind = d.Type or "Damage"
 
local base = d["Base %"]
if perN == nil or perN == 0 or not maxLevel or maxLevel <= 0 then
local per  = d["Per Level %"]
return string.format("%s %s", baseText, basis)
local seg  = kind
local detail = {}
if base ~= nil then
table.insert(detail, string.format("Base %s%%", tostring(base)))
end
if per ~= nil then
table.insert(detail, string.format("%s%% / Lv", tostring(per)))
end
if d["ATK-Based"] then
table.insert(detail, "ATK-based")
end
if d["MATK-Based"] then
table.insert(detail, "MATK-based")
end
if #detail > 0 then
seg = seg .. " – " .. table.concat(detail, ", ")
end
table.insert(parts, seg)
end
end
end
if #parts == 0 then
 
return nil
local inc = {}
for lv = 1, maxLevel do
table.insert(inc, fmtNum(perN * lv))
end
end
return table.concat(parts, "<br />")
 
-- Match requested style: "100% + 40 / 80 / 120 ... Attack Per Level"
return string.format("%s + %s %s Per Level", baseText, table.concat(inc, " / "), basis)
end
end


local function formatReflectDamage(list)
local function formatDamageList(list, maxLevel, includeTypePrefix)
if type(list) ~= "table" or #list == 0 then
if type(list) ~= "table" or #list == 0 then
return nil
return nil
end
end
local parts = {}
local parts = {}
for _, d in ipairs(list) do
for _, d in ipairs(list) do
if type(d) == "table" then
if type(d) == "table" then
local base = d["Base %"]
local txt = formatDamageEntry(d, maxLevel)
local per  = d["Per Level %"]
if txt then
local seg  = "Reflect"
if includeTypePrefix and d.Type and d.Type ~= "" then
local detail = {}
table.insert(parts, tostring(d.Type) .. ": " .. txt)
if base ~= nil then
else
table.insert(detail, string.format("Base %s%%", tostring(base)))
table.insert(parts, txt)
end
end
if per ~= nil then
table.insert(detail, string.format("%s%% / Lv", tostring(per)))
end
if #detail > 0 then
seg = seg .. " " .. table.concat(detail, ", ")
end
end
table.insert(parts, seg)
end
end
end
end
if #parts == 0 then
if #parts == 0 then
return nil
return nil
Line 334: Line 351:
return nil
return nil
end
end
local parts = {}
local parts = {}
for _, s in ipairs(list) do
for _, s in ipairs(list) do
if type(s) == "table" then
if type(s) == "table" then
local name = s["Scaling Name"] or s["Scaling ID"] or "Unknown"
local stat = s["Scaling Name"] or s["Scaling ID"] or "Unknown"
local pct  = s.Percent
local pct  = s.Percent
local seg  = name
local pctN = toNum(pct)
local detail = {}
local basis = basisLabel(s)
if pct ~= nil then
 
-- If Unit is percent_decimal, convert; otherwise assume already percent
if pctN ~= nil and pctN ~= 0 then
if s.Unit == "percent_decimal" then
-- IMPORTANT: do not convert percent values; just display stored value.
table.insert(detail, string.format("%s%%", tostring((pct or 0) * 100)))
table.insert(parts, string.format("%s%% %s Per %s", fmtNum(pctN), basis, stat))
else
elseif pct ~= nil and tostring(pct) ~= "" and tostring(pct) ~= "0" then
table.insert(detail, string.format("%s%%", tostring(pct)))
table.insert(parts, string.format("%s%% %s Per %s", tostring(pct), basis, stat))
end
end
if s["ATK-Based"] then
table.insert(detail, "ATK-based")
end
if s["MATK-Based"] then
table.insert(detail, "MATK-based")
end
if #detail > 0 then
seg = seg .. " – " .. table.concat(detail, ", ")
end
end
table.insert(parts, seg)
end
end
end
end
if #parts == 0 then
if #parts == 0 then
return nil
return nil
Line 367: Line 375:
end
end


-- Area: Distance then Size, no Effective Distance
-- Area: Distance then Size (no Effective)
local function formatArea(area)
local function formatArea(area)
if type(area) ~= "table" then
if type(area) ~= "table" then
Line 464: Line 472:
end
end


-- Percent may be scalar or {Value,Unit}
if combo.Percent ~= nil then
if combo.Percent ~= nil then
local pctText = formatUnitValue(combo.Percent)
local pctText = formatUnitValue(combo.Percent)
Line 493: Line 500:
local block = effects[name]
local block = effects[name]
if type(block) == "table" then
if type(block) == "table" then
-- Keep each effect to a single line when possible
local txt = valuePairText(name, block, ", ")
local txt = valuePairText(name, block, ", ")
if txt then
if txt then
Line 552: Line 558:
local detail = {}
local detail = {}


local dur = s.Duration
if type(s.Duration) == "table" then
if type(dur) == "table" then
local t = valuePairText("Duration", s.Duration, "; ")
local t = valuePairText("Duration", dur, "; ")
if t then table.insert(detail, t) end
if t then
table.insert(detail, t)
end
end
end


local ch = s.Chance
if type(s.Chance) == "table" then
if type(ch) == "table" then
local t = valuePairText("Chance", s.Chance, "; ")
local t = valuePairText("Chance", ch, "; ")
if t then table.insert(detail, t) end
if t then
table.insert(detail, t)
end
end
end


Line 625: Line 625:
local action = ev.Action or "On event"
local action = ev.Action or "On event"
local name  = ev["Skill Internal Name"] or ev["Skill External Name"] or "Unknown skill"
local name  = ev["Skill Internal Name"] or ev["Skill External Name"] or "Unknown skill"
local seg    = string.format("%s → %s", action, name)
table.insert(parts, string.format("%s → %s", action, name))
table.insert(parts, seg)
end
end
end
end
Line 721: Line 720:
headerRow:addClass("spiritvale-infobox-main")
headerRow:addClass("spiritvale-infobox-main")


-- Left cell: icon + name
local leftCell = headerRow:tag("th")
local leftCell = headerRow:tag("th")
leftCell:addClass("spiritvale-infobox-main-left")
leftCell:addClass("spiritvale-infobox-main-left")
Line 736: Line 734:
:wikitext(title)
:wikitext(title)


-- Right cell: italic description
local rightCell = headerRow:tag("td")
local rightCell = headerRow:tag("td")
rightCell:addClass("spiritvale-infobox-main-right")
rightCell:addClass("spiritvale-infobox-main-right")
Line 753: Line 750:
------------------------------------------------------------------
------------------------------------------------------------------
addSectionHeader(root, "General")
addSectionHeader(root, "General")
addRow(root, "Max level", rec["Max Level"] and tostring(rec["Max Level"]))
addRow(root, "Max Level", rec["Max Level"] and tostring(rec["Max Level"]))


-- Hide Users when the skill is rendered on its own page
-- Hide Users on:
--  - list pages (opts.showUsers=false)
--  - the skill's own page (handled by caller)
if showUsers then
if showUsers then
local users = rec.Users or {}
local users = rec.Users or {}
Line 785: Line 784:
end
end
end
end
addRow(root, "Required skills", table.concat(skillParts, ", "))
addRow(root, "Required Skills", table.concat(skillParts, ", "))
end
end


addRow(root, "Required weapons", listToText(req["Required Weapons"]))
addRow(root, "Required Weapons", listToText(req["Required Weapons"]))
addRow(root, "Required stances", listToText(req["Required Stances"]))
addRow(root, "Required Stances", listToText(req["Required Stances"]))
end
end


Line 801: Line 800:
local dt = typeBlock["Damage Type"]
local dt = typeBlock["Damage Type"]
if type(dt) == "table" and dt.Name then
if type(dt) == "table" and dt.Name then
addRow(root, "Damage type", dt.Name)
addRow(root, "Damage Type", dt.Name)
end
end


Line 816: Line 815:
local ct = typeBlock["Cast Type"]
local ct = typeBlock["Cast Type"]
if type(ct) == "table" and ct.Name then
if type(ct) == "table" and ct.Name then
addRow(root, "Cast type", ct.Name)
addRow(root, "Cast Type", ct.Name)
end
end
end
end
Line 834: Line 833:


if mech["Autocast Multiplier"] ~= nil then
if mech["Autocast Multiplier"] ~= nil then
addRow(root, "Autocast multiplier", tostring(mech["Autocast Multiplier"]))
addRow(root, "Autocast Multiplier", tostring(mech["Autocast Multiplier"]))
end
end


Line 841: Line 840:


local rcText = formatResourceCost(mech["Resource Cost"])
local rcText = formatResourceCost(mech["Resource Cost"])
addRow(root, "Resource cost", rcText)
addRow(root, "Resource Cost", rcText)


local comboText = formatCombo(mech.Combo)
local comboText = formatCombo(mech.Combo)
Line 847: Line 846:


local effText = formatMechanicEffects(mech.Effects)
local effText = formatMechanicEffects(mech.Effects)
addRow(root, "Special mechanics", effText)
addRow(root, "Special Mechanics", effText)
end
end


------------------------------------------------------------------
------------------------------------------------------------------
-- Damage & Healing
-- Damage & Scaling
------------------------------------------------------------------
------------------------------------------------------------------
local dmg = rec.Damage or {}
local dmg = rec.Damage or {}
if next(dmg) ~= nil then
if next(dmg) ~= nil then
addSectionHeader(root, "Damage and scaling")
addSectionHeader(root, "Damage and Scaling")
 
local maxLevel = tonumber(rec["Max Level"]) or 0
 
-- Split healing out of Main Damage (Type == "Healing")
local main = dmg["Main Damage"]
local mainNonHeal = {}
local healOnly = {}


if dmg["Healing Present"] then
if type(main) == "table" then
addRow(root, "Healing", "Yes")
for _, d in ipairs(main) do
if type(d) == "table" and d.Type == "Healing" then
table.insert(healOnly, d)
else
table.insert(mainNonHeal, d)
end
end
end
end


local mainText = formatMainDamage(dmg["Main Damage"])
local mainText = formatDamageList(mainNonHeal, maxLevel, (#mainNonHeal > 1))
addRow(root, "Main damage", mainText)
addRow(root, "Main Damage", mainText)
 
local flatText = formatDamageList(dmg["Flat Damage"], maxLevel, false)
addRow(root, "Flat Damage", flatText)
 
local reflText = formatDamageList(dmg["Reflect Damage"], maxLevel, false)
addRow(root, "Reflect Damage", reflText)


local reflText = formatReflectDamage(dmg["Reflect Damage"])
local healText = formatDamageList(healOnly, maxLevel, false)
addRow(root, "Reflect damage", reflText)
addRow(root, "Healing", healText)


local scaleText = formatScaling(dmg.Scaling)
local scaleText = formatScaling(dmg.Scaling)
Line 886: Line 904:
local statusRem  = formatStatusRemoval(rec["Status Removal"])
local statusRem  = formatStatusRemoval(rec["Status Removal"])
if statusApps or statusRem then
if statusApps or statusRem then
addSectionHeader(root, "Status effects")
addSectionHeader(root, "Status Effects")
addRow(root, "Applies", statusApps)
addRow(root, "Applies", statusApps)
addRow(root, "Removes", statusRem)
addRow(root, "Removes", statusRem)
Line 947: Line 965:
root:addClass("spiritvale-skill-list")
root:addClass("spiritvale-skill-list")


-- IMPORTANT: On class pages / list mode, hide Users (redundant on that page).
for _, rec in ipairs(matches) do
for _, rec in ipairs(matches) do
root:wikitext(buildInfobox(rec, { showUsers = true }))
root:wikitext(buildInfobox(rec, { showUsers = false }))
end
end


Line 1,004: Line 1,023:
local label = name or id or "?"
local label = name or id or "?"
return string.format(
return string.format(
"<strong>Unknown skill:</strong> %s[[Category:Pages with unknown skill|%s]]",
"<strong>Unknown Skill:</strong> %s[[Category:Pages with unknown skill|%s]]",
mw.text.nowiki(label),
mw.text.nowiki(label),
label
label
Line 1,010: Line 1,029:
end
end


-- Hide Users only when rendered on the skill's own page
-- For single-skill rendering:
-- - hide Users if this is the skill's own page
-- - otherwise show Users
local showUsers = not isDirectSkillPage(rec)
local showUsers = not isDirectSkillPage(rec)
return buildInfobox(rec, { showUsers = showUsers })
return buildInfobox(rec, { showUsers = showUsers })