Module:GameInfo/Skills: Difference between revisions
From SpiritVale Wiki
More actions
No edit summary Tags: Mobile edit Mobile web edit |
No edit summary Tags: Mobile edit Mobile web edit |
||
| Line 395: | Line 395: | ||
local body = pop:tag("div"):addClass("sv-tip-pop-body") | local body = pop:tag("div"):addClass("sv-tip-pop-body") | ||
_render_notes_body(body, notes_wt) | _render_notes_body(body, notes_wt) | ||
return wrap | |||
end | |||
-- ---------------------------------------------------------------------------- | |||
-- OVERCAP (Schema 2): Option A (Universal Popups) | |||
-- Goal: | |||
-- - Show a compact trigger near the level UI: "Overcap +N" | |||
-- - Popup lists sources (links) and the level bonus each provides. | |||
-- - If a source omits levels and it's the only missing one, infer from (max-default). | |||
-- - If multiple sources omit levels, show "(+?)" for those items. | |||
-- - No new slider CSS required; uses existing sv-tip / sv-tip-pop styling + Common.js popups. | |||
-- ---------------------------------------------------------------------------- | |||
local function _normalize_overcap_sources(sources, total_bonus) | |||
sources = _safe_tbl(sources) | |||
local out = {} | |||
local missing = 0 | |||
local explicit_sum = 0 | |||
for _, s in ipairs(sources) do | |||
local page = "" | |||
local lv = nil | |||
if type(s) == "string" then | |||
page = _trim(s) | |||
elseif type(s) == "table" then | |||
-- Accept {page="X", levels=2} and also array-ish { "X", 2 } | |||
page = _safe_str(s.page or s[1] or "", "") | |||
lv = _to_int(s.levels or s.level or s[2], nil) | |||
end | |||
if page ~= "" then | |||
if lv and lv > 0 then | |||
explicit_sum = explicit_sum + lv | |||
else | |||
lv = nil | |||
missing = missing + 1 | |||
end | |||
table.insert(out, { page = page, levels = lv }) | |||
end | |||
end | |||
local remaining = (_to_int(total_bonus, 0) or 0) - explicit_sum | |||
if remaining < 0 then remaining = 0 end | |||
-- If exactly one source is missing levels, infer it from the remaining bonus. | |||
if missing == 1 and remaining > 0 then | |||
for _, it in ipairs(out) do | |||
if it.levels == nil then | |||
it.levels = remaining | |||
break | |||
end | |||
end | |||
end | |||
return out | |||
end | |||
local function _build_overcap_tip(root_id, default_cap, max_cap, sources) | |||
default_cap = _to_int(default_cap, 1) or 1 | |||
max_cap = _to_int(max_cap, default_cap) or default_cap | |||
if max_cap <= default_cap then return nil end | |||
local total = max_cap - default_cap | |||
local tip_id = root_id .. "-overcap" | |||
local norm = _normalize_overcap_sources(sources, total) | |||
-- If no sources provided, still show the badge, but mark unknown. | |||
local show_unknown = (#norm == 0) | |||
local wrap = mw.html.create("span") | |||
:addClass("sv-tip") | |||
:addClass("sv-overcap-tip") | |||
local btn = wrap:tag("span") | |||
:addClass("sv-tip-btn") | |||
:addClass("sv-overcap-btn") | |||
:attr("role", "button") | |||
:attr("tabindex", "0") | |||
:attr("data-sv-toggle", "1") | |||
-- Click-only near slider (avoid hover spam while dragging) | |||
:attr("data-sv-pop", "click") | |||
:attr("data-sv-pop-size", "sm") | |||
:attr("aria-label", "Overcap") | |||
:attr("aria-controls", tip_id) | |||
:attr("aria-expanded", "false") | |||
:wikitext(mw.text.nowiki("Overcap +" .. tostring(total))) | |||
local pop = wrap:tag("div") | |||
:addClass("sv-tip-pop") | |||
:addClass("sv-hidden") | |||
:attr("hidden", "hidden") | |||
:attr("id", tip_id) | |||
:attr("aria-label", "Overcap") | |||
local head = pop:tag("div"):addClass("sv-tip-pop-head") | |||
head:tag("div"):addClass("sv-tip-pop-title"):wikitext("Overcap sources") | |||
local body = pop:tag("div"):addClass("sv-tip-pop-body") | |||
-- Summary line (always helpful) | |||
body:tag("div"):addClass("sv-overcap-summary"):wikitext(mw.text.nowiki( | |||
"Raises max from " .. tostring(default_cap) .. " to " .. tostring(max_cap) .. " (+" .. tostring(total) .. ")." | |||
)) | |||
local ul = body:tag("ul"):addClass("sv-disclose-list") | |||
if show_unknown then | |||
ul:tag("li"):wikitext("Source unknown") | |||
else | |||
for _, it in ipairs(norm) do | |||
local li = ul:tag("li") | |||
local page = _safe_str(it.page, "") | |||
if page ~= "" then | |||
li:wikitext(string.format("[[%s|%s]]", page, mw.text.nowiki(page))) | |||
else | |||
li:wikitext("—") | |||
end | |||
if it.levels ~= nil and it.levels > 0 then | |||
li:wikitext(mw.text.nowiki(" (+" .. tostring(it.levels) .. ")")) | |||
else | |||
-- Multiple sources but missing per-source split | |||
li:wikitext(mw.text.nowiki(" (+?)")) | |||
end | |||
end | |||
end | |||
return wrap | return wrap | ||
| Line 748: | Line 879: | ||
end | end | ||
local function _build_level(default_level, max_level, min_level) | local function _build_level(root_id, default_level, max_level, min_level, overcap_obj) | ||
local min = _to_int(min_level, 1) | local min = _to_int(min_level, 1) | ||
local default = _to_int(default_level, 1) | local default = _to_int(default_level, 1) | ||
| Line 767: | Line 898: | ||
label:wikitext(" / ") | label:wikitext(" / ") | ||
label:tag("span"):addClass("sv-level-max"):wikitext(tostring(max)) | label:tag("span"):addClass("sv-level-max"):wikitext(tostring(max)) | ||
-- Overcap badge + popup (Schema 2) | |||
if type(overcap_obj) == "table" and max > default then | |||
local oc = _safe_tbl(overcap_obj) | |||
local tip = _build_overcap_tip(root_id, default, max, oc.sources) | |||
if tip then ui:node(tip) end | |||
elseif max > default then | |||
-- Defensive fallback (in case a future schema sets max>default but omits overcap object) | |||
local tip = _build_overcap_tip(root_id, default, max, nil) | |||
if tip then ui:node(tip) end | |||
end | |||
local slider_wrap = root:tag("div"):addClass("sv-level-slider") | local slider_wrap = root:tag("div"):addClass("sv-level-slider") | ||
| Line 1,208: | Line 1,350: | ||
if reqrow then box.top:node(reqrow) end | if reqrow then box.top:node(reqrow) end | ||
local level_panel, actual_default = _build_level(default_level, max_level, 1) | local level_panel, actual_default = _build_level(root_id, default_level, max_level, 1, nil) | ||
box.bottom:node(level_panel) | box.bottom:node(level_panel) | ||
| Line 1,242: | Line 1,384: | ||
box.top:addClass("sv-skill-top") | box.top:addClass("sv-skill-top") | ||
box.bottom:addClass("sv-skill-bottom") | box.bottom:addClass("sv-skill-bottom") | ||
-- Helpful attributes (optional / future-proof) | |||
box.root:attr("data-level-default", tostring(default_level)) | |||
do | |||
local oc = _safe_tbl(_safe_tbl(payload.level).overcap) | |||
if next(oc) ~= nil then | |||
local safe_oc = { max = _to_int(oc.max, max_level), sources = _safe_tbl(oc.sources) } | |||
box.root:attr("data-level-overcap", mw.text.jsonEncode(safe_oc)) | |||
end | |||
end | |||
box.top:node(_build_identity(frame, root_id, identity, notes_wt)) | box.top:node(_build_identity(frame, root_id, identity, notes_wt)) | ||
| Line 1,249: | Line 1,401: | ||
if reqrow then box.top:node(reqrow) end | if reqrow then box.top:node(reqrow) end | ||
local level_panel, actual_default = _build_level(default_level, max_level, min_level) | local level_panel, actual_default = _build_level(root_id, default_level, max_level, min_level, _safe_tbl(payload.level).overcap) | ||
box.bottom:node(level_panel) | box.bottom:node(level_panel) | ||