Module:GameSkills: Difference between revisions
From SpiritVale Wiki
More actions
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 | ||
if unit == "percent_decimal" or unit == "percent_whole" or unit == "percent" then | |||
if unit == "percent_decimal" | |||
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 ..." | ||
-- - | -- - 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 ( | -- 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 | ||
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 | local function basisLabel(entry) | ||
if type( | 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 | |||
local baseText = (baseN ~= nil) and (fmtNum(baseN) .. "%") or (tostring(entry["Base %"]) .. "%") | |||
local basis = basisLabel(entry) | |||
if perN == nil or perN == 0 or not maxLevel or maxLevel <= 0 then | |||
return string.format("%s %s", baseText, basis) | |||
end | end | ||
local inc = {} | |||
for lv = 1, maxLevel do | |||
table.insert(inc, fmtNum(perN * lv)) | |||
end | end | ||
return table.concat( | |||
-- 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 | 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 | local txt = formatDamageEntry(d, maxLevel) | ||
if txt then | |||
if includeTypePrefix and d.Type and d.Type ~= "" then | |||
table.insert(parts, tostring(d.Type) .. ": " .. txt) | |||
if | else | ||
table.insert(parts, txt) | |||
end | |||
end | end | ||
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 | local stat = s["Scaling Name"] or s["Scaling ID"] or "Unknown" | ||
local pct = s.Percent | local pct = s.Percent | ||
local | local pctN = toNum(pct) | ||
local | local basis = basisLabel(s) | ||
if | |||
-- | if pctN ~= nil and pctN ~= 0 then | ||
-- IMPORTANT: do not convert percent values; just display stored value. | |||
table.insert(parts, string.format("%s%% %s Per %s", fmtNum(pctN), basis, stat)) | |||
elseif pct ~= nil and tostring(pct) ~= "" and tostring(pct) ~= "0" then | |||
table.insert(parts, string.format("%s%% %s Per %s", tostring(pct), basis, stat)) | |||
end | end | ||
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 | -- 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 | ||
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 | ||
local txt = valuePairText(name, block, ", ") | local txt = valuePairText(name, block, ", ") | ||
if txt then | if txt then | ||
| Line 552: | Line 558: | ||
local detail = {} | local detail = {} | ||
if type(s.Duration) == "table" then | |||
local t = valuePairText("Duration", s.Duration, "; ") | |||
local t = valuePairText("Duration", | if t then table.insert(detail, t) end | ||
if t then | |||
end | end | ||
if type(s.Chance) == "table" then | |||
local t = valuePairText("Chance", s.Chance, "; ") | |||
local t = valuePairText("Chance", | if t then table.insert(detail, t) end | ||
if t then | |||
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" | ||
table.insert(parts, string.format("%s → %s", action, name)) | |||
end | end | ||
end | end | ||
| Line 721: | Line 720: | ||
headerRow:addClass("spiritvale-infobox-main") | headerRow:addClass("spiritvale-infobox-main") | ||
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) | ||
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 | addRow(root, "Max Level", rec["Max Level"] and tostring(rec["Max Level"])) | ||
-- Hide Users | -- 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 | addRow(root, "Required Skills", table.concat(skillParts, ", ")) | ||
end | end | ||
addRow(root, "Required | addRow(root, "Required Weapons", listToText(req["Required Weapons"])) | ||
addRow(root, "Required | 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 | 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 | 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 | 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 | 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 | addRow(root, "Special Mechanics", effText) | ||
end | end | ||
------------------------------------------------------------------ | ------------------------------------------------------------------ | ||
-- Damage & | -- 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 | 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 | if type(main) == "table" then | ||
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 = | local mainText = formatDamageList(mainNonHeal, maxLevel, (#mainNonHeal > 1)) | ||
addRow(root, " | 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 | local healText = formatDamageList(healOnly, maxLevel, false) | ||
addRow(root, " | 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 | 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 = | 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 | "<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 | ||
-- | -- 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 }) | ||