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:GameInfo/Skills: Difference between revisions

From SpiritVale Wiki
No edit summary
Tags: Mobile edit Mobile web edit
No edit summary
Tags: Mobile edit Mobile web edit
Line 1: Line 1:
-- Module:GameInfo/Skills
-- Module:GameInfo/Skills
-- Phase 4.1 — Schema 1 renderer (legacy shape)
-- Phase 4.1 — Dual schema renderer (Schema 1 + Schema 2)
--
--
-- Keyword pills policy:
-- DEFINITIONS POLICY (strict):
--   Accept ONLY "Domain\u007CKey" in JSON.
-- - Only accept Definition tokens as JSON strings using \u007C, e.g. "Damage\u007CPiercing".
--   After jsonDecode, this becomes "Domain|Key" and is expanded via {{def|Domain|Key}}.
-- - After jsonDecode, this becomes "Damage|Piercing" and is expanded via {{def|Damage|Piercing}}.
--   No support for:
-- - No support for:
--    - "<nowiki>...</nowiki>" wrapped data
--    * <nowiki>...</nowiki> wrapped |data=
--    - "Domain\\u007CKey" (double-escaped)
--    * double-escaped pipes (\\u007C)
--    - "{{def|...}}" template-shaped strings inside JSON
--    * template-shaped strings inside JSON (e.g. "{{def|Damage|Piercing}}")
--
--
-- This module intentionally does NOT attempt any workarounds.
-- Schema 2 is adapted into the internal Schema-1-ish render shape to preserve DOM/CSS/JS contracts.


local p = {}
local p = {}
Line 68: Line 68:
return nil, "json is not object", raw
return nil, "json is not object", raw
end
end
if decoded.schema ~= 1 then
 
return nil, "unsupported schema=" .. tostring(decoded.schema), raw
local schema = decoded.schema
if schema ~= 1 and schema ~= 2 then
return nil, "unsupported schema=" .. tostring(schema), raw
end
end
return decoded, nil, ""
return decoded, nil, ""
end
end
Line 153: Line 156:
end
end


local function _meta_lines(lines)
-- Strict def token renderer (Domain|Key only).
local function _render_def_token(frame, s, noicon)
s = _trim(s)
if s == "" then return nil end
 
local bar = s:find("|", 1, true)
if not bar then
return mw.text.nowiki(s)
end
 
local domain = _trim(s:sub(1, bar - 1))
local key    = _trim(s:sub(bar + 1))
if domain == "" or key == "" then
return mw.text.nowiki(s)
end
 
local args = { domain, key }
if noicon then
args.noicon = "1"
end
 
return frame:expandTemplate{ title = "def", args = args }
end
 
local function _meta_lines_plain(lines)
lines = _safe_tbl(lines)
lines = _safe_tbl(lines)
local wrap = mw.html.create("span"):addClass("sv-meta-lines")
local wrap = mw.html.create("span"):addClass("sv-meta-lines")
Line 170: Line 197:
end
end


-- Keywords pill policy: ONLY Domain|Key (coming from Domain\u007CKey in JSON).
local function _meta_lines_def(frame, lines)
lines = _safe_tbl(lines)
local wrap = mw.html.create("span"):addClass("sv-meta-lines")
local any = false
for _, ln in ipairs(lines) do
local s = _trim(ln)
if s ~= "" then
-- Meta row has its own icon, so def should be noicon=1 when token is used.
local wt = _render_def_token(frame, s, true) or mw.text.nowiki(s)
wrap:tag("span"):wikitext(wt)
any = true
end
end
if not any then
wrap:tag("span"):wikitext("—")
end
return wrap
end
 
-- Keywords pill policy: ONLY Domain|Key tokens (from Domain\u007CKey in JSON).
local function _render_keyword_pill(frame, pill_text)
local function _render_keyword_pill(frame, pill_text)
pill_text = _trim(pill_text)
pill_text = _trim(pill_text)
Line 177: Line 223:
end
end


local bar = pill_text:find("|", 1, true)
local wt = _render_def_token(frame, pill_text, false)
if bar then
if wt then return wt end
local domain = _trim(pill_text:sub(1, bar - 1))
local key    = _trim(pill_text:sub(bar + 1))
if domain ~= "" and key ~= "" then
return frame:expandTemplate{ title = "def", args = { domain, key } }
end
end


return mw.text.nowiki(pill_text)
return mw.text.nowiki(pill_text)
Line 281: Line 321:
local li = ul:tag("li")
local li = ul:tag("li")


local label_node = _meta_lines(it.label_lines)
local label_node = _meta_lines_plain(it.label_lines)


local link = _safe_tbl(it.link)
local link = _safe_tbl(it.link)
Line 343: Line 383:
end
end


local function _build_meta_row(meta_row)
local function _build_meta_row(frame, meta_row)
meta_row = _safe_tbl(meta_row)
meta_row = _safe_tbl(meta_row)


Line 363: Line 403:


local wrap = card:tag("div"):addClass("sv-meta-textwrap")
local wrap = card:tag("div"):addClass("sv-meta-textwrap")
wrap:tag("div"):addClass("sv-meta-text"):node(_meta_lines(cell.label_lines))
wrap:tag("div"):addClass("sv-meta-text"):node(_meta_lines_def(frame, cell.label_lines))
end
end


Line 369: Line 409:
end
end


-- Level normalization:
-- Schema 1 level normalization:
-- - max      = absolute cap (incl. gear) if provided, else base_max
-- - max      = absolute cap (incl. gear) if provided, else base_max
-- - base_max = natural cap (no gear) if provided, else max
-- - base_max = natural cap (no gear) if provided, else max
Line 633: Line 673:


return root
return root
end
-- Schema 2 -> internal shape adapter (still strict \u007C token policy)
local function _adapt_schema2(p2)
local out = {}
out.schema = 1
out.identity = _safe_tbl(p2.identity)
out.tabs = _safe_tbl(p2.tabs)
-- skill_meta -> meta_row
out.meta_row = {}
local sm = _safe_tbl(p2.skill_meta)
for i = 1, 4 do
local cell = _safe_tbl(sm[i])
local icon_page = _safe_str(cell.icon, "")
local label_wt = _safe_str(cell.label_wt, "")
out.meta_row[i] = {
icon = { page = icon_page, alt = "" },
-- label_wt should be a strict token: Domain|Key (from Domain\u007CKey)
label_lines = label_wt ~= "" and { label_wt } or {},
}
end
-- requirements/users map -> groups[]
local function map_to_groups(map_tbl, preferred_order)
local m = _safe_tbl(map_tbl)
local used = {}
local groups = {}
for _, k in ipairs(preferred_order or {}) do
if m[k] ~= nil then
used[k] = true
table.insert(groups, tostring(k))
end
end
local rest = {}
for k, _ in pairs(m) do
k = tostring(k)
if not used[k] then table.insert(rest, k) end
end
table.sort(rest)
for _, k in ipairs(rest) do table.insert(groups, k) end
local out_groups = {}
for _, title in ipairs(groups) do
local items = _safe_tbl(m[title])
local out_items = {}
for _, it in ipairs(items) do
if type(it) == "string" then
table.insert(out_items, { label_lines = { it } })
elseif type(it) == "table" then
-- array form: ["Sacrifice","(Lv 1)"]
if #it > 0 then
local lines = {}
for _, ln in ipairs(it) do table.insert(lines, tostring(ln)) end
table.insert(out_items, { label_lines = lines })
else
-- object form (optional future): { page="X", label_lines=[...] }
local lines = _safe_tbl(it.label_lines)
local page = _safe_str(it.page, "")
local out_it = { label_lines = lines }
if page ~= "" then out_it.link = { page = page } end
table.insert(out_items, out_it)
end
end
end
table.insert(out_groups, { title = title, items = out_items })
end
return { groups = out_groups }
end
out.requirements = map_to_groups(p2.requirements, { "Skills", "Weapon Types", "Stances" })
out.users        = map_to_groups(p2.users,        { "Classes", "Summons" })
-- level: base/default/overcap.max -> base_max/max
do
local lv = _safe_tbl(p2.level)
local base = _to_int(lv.base, 1)
local natural = _to_int(lv.default, base)
local oc = _safe_tbl(lv.overcap)
local max = _to_int(oc.max, natural)
out.level = { base_max = natural, max = max }
end
-- skill_scaling -> scaling_top + core_stats
do
local sc = _safe_tbl(p2.skill_scaling)
out.scaling_top = {
damage = _safe_tbl(_safe_tbl(sc.damage).value),
scaling_lines = {},
}
-- stat_scaling -> "INT: 2% • STR: 2%" (token-safe)
local parts = {}
for _, it in ipairs(_safe_tbl(sc.stat_scaling)) do
it = _safe_tbl(it)
local tok = _safe_str(it.stat_wt, "")
local val = _safe_str(it.value, "")
if tok ~= "" and val ~= "" then
local bar = tok:find("|", 1, true)
local key = bar and _trim(tok:sub(bar + 1)) or tok
if key ~= "" then
table.insert(parts, string.upper(key) .. ": " .. val)
end
end
end
if #parts > 0 then
table.insert(out.scaling_top.scaling_lines, { text = table.concat(parts, " • ") })
end
local function add_core(label, field_key)
local fld = _safe_tbl(sc[field_key])
local v = fld.value
if type(v) ~= "table" then return end
table.insert(out.core_stats, {
key = label,
unit = _safe_str(fld.unit, ""),
value = _safe_tbl(v),
})
end
out.core_stats = {}
add_core("Cost",      "cost")
add_core("Cast Time", "cast_time")
add_core("Cooldown",  "cooldown")
add_core("Range",    "range")
add_core("Area",      "area")
add_core("Duration",  "duration")
end
-- events cards: if schema2 uses stats.cause, map to card.type for existing renderer
do
local t = _safe_tbl(out.tabs)
local ev = _safe_tbl(t.events)
local cards = _safe_tbl(ev.cards)
for _, c in ipairs(cards) do
c = _safe_tbl(c)
if c.type == nil then
local stats = _safe_tbl(c.stats)
if stats.cause ~= nil then
c.type = stats.cause
elseif stats.type ~= nil then
c.type = stats.type
end
end
end
end
return out
end
end


Line 644: Line 841:
if not payload then
if not payload then
return _err(err, debug and preview or nil)
return _err(err, debug and preview or nil)
end
if payload.schema == 2 then
payload = _adapt_schema2(payload)
end
end


Line 671: Line 872:


box.top:node(_build_identity(root_id, identity, notes_wt))
box.top:node(_build_identity(root_id, identity, notes_wt))
box.top:node(_build_meta_row(payload.meta_row))
box.top:node(_build_meta_row(frame, payload.meta_row))


local reqrow = _build_req_users(root_id, payload.requirements, payload.users)
local reqrow = _build_req_users(root_id, payload.requirements, payload.users)