Module:StreetBrawlItems: Difference between revisions

From The Deadlock Wiki
Jump to navigation Jump to search
Populate Street Brawl item-weighting helper: joins Data:StreetBrawlData.json with ItemData/HeroData, renders favored/unfavored/common/counter groups for hero and item pages (with help from vergir-bot LLM)
Wrap rendered groups in flex rows so cards and hero icons have consistent spacing (with help from vergir-bot LLM)
Line 40: Line 40:
end
end


-- Wraps a rendered list with an optional bold label / italic note, or a collapsible box.
-- Lays rendered members out in a wrapping flex row with a consistent gap.
-- Returns "" when the list is empty, so nothing (not even the label) shows.
local function flexrow(parts, gap, center)
if #parts == 0 then return "" end
local align = center and "center" or "flex-start"
return '<div style="display:flex; flex-wrap:wrap; gap:' .. gap .. '; align-items:' .. align .. '">'
.. table.concat(parts) .. '</div>'
end
 
-- Wraps a rendered row with an optional bold label / italic note, or a collapsible box.
-- Returns "" when the row is empty, so nothing (not even the label) shows.
local function wrap(inner, label, note, collapse)
local function wrap(inner, label, note, collapse)
if inner == "" then return "" end
if inner == "" then return "" end
Line 105: Line 113:
local parts = {}
local parts = {}
for _, c in ipairs(codes) do local r = render_item(frame, c, style); if r then parts[#parts + 1] = r end end
for _, c in ipairs(codes) do local r = render_item(frame, c, style); if r then parts[#parts + 1] = r end end
return wrap(table.concat(parts), frame.args.label, nil, collapse)
local is_icon = (style == "icon")
local inner = flexrow(parts, is_icon and "4px 10px" or "8px", is_icon)
return wrap(inner, frame.args.label, nil, collapse)
end
end


Line 126: Line 136:
if heading and heading ~= "" then out[#out + 1] = "=== " .. heading .. " ===" end
if heading and heading ~= "" then out[#out + 1] = "=== " .. heading .. " ===" end
local note = frame.args.note
local note = frame.args.note
if note and note ~= "" then out[#out + 1] = "''" .. note .. "''\n" end
if note and note ~= "" then out[#out + 1] = "''" .. note .. "''" end
out[#out + 1] = table.concat(parts)
out[#out + 1] = flexrow(parts, "8px", false)
return table.concat(out, "\n")
return table.concat(out, "\n")
end
end
Line 151: Line 161:
local parts = {}
local parts = {}
for _, k in ipairs(list) do local r = render_hero(frame, k); if r then parts[#parts + 1] = r end end
for _, k in ipairs(list) do local r = render_hero(frame, k); if r then parts[#parts + 1] = r end end
return wrap(table.concat(parts), frame.args.label, frame.args.note, false)
local inner = flexrow(parts, "4px 10px", true)
return wrap(inner, frame.args.label, frame.args.note, false)
end
end



Revision as of 13:18, 19 July 2026

Documentation for this module may be created at Module:StreetBrawlItems/doc

local p = {}

local sb = mw.loadJsonData("Data:StreetBrawlData.json")["item-buckets"]
local items = mw.loadJsonData("Data:ItemData.json")
local heroes = mw.loadJsonData("Data:HeroData.json")

local NAME_TO_KEY, KEY_TO_NAME, RELEASED = {}, {}, {}
for key, h in pairs(heroes) do
	if type(h) == "table" and h.Name then
		KEY_TO_NAME[key] = h.Name
		NAME_TO_KEY[mw.ustring.lower(h.Name)] = key
		if not h.InDevelopment and not h.IsDisabled then RELEASED[key] = true end
	end
end

local function hero_key(input)
	if not input then return nil end
	input = mw.text.trim(input)
	if input == "" then return nil end
	if heroes[input] then return input end
	return NAME_TO_KEY[mw.ustring.lower(input)]
end

local function iname(code) local it = items[code]; return it and it.Name or nil end
local function iok(code) local it = items[code]; return it ~= nil and it.Name ~= nil end
local function itier(code)
	local it = items[code]; if not it then return nil end
	if it.StreetBrawl == true then return 5 end
	return it.Tier
end

local function render_item(frame, code, style)
	local name = iname(code); if not name then return nil end
	if style == "icon" then return frame:expandTemplate{title = "ItemIcon", args = {name}} end
	return frame:expandTemplate{title = "ItemBox", args = {name, showTierTag = "true"}}
end
local function render_hero(frame, key)
	local name = KEY_TO_NAME[key]; if not name then return nil end
	return frame:expandTemplate{title = "HeroIcon", args = {name}}
end

-- Lays rendered members out in a wrapping flex row with a consistent gap.
local function flexrow(parts, gap, center)
	if #parts == 0 then return "" end
	local align = center and "center" or "flex-start"
	return '<div style="display:flex; flex-wrap:wrap; gap:' .. gap .. '; align-items:' .. align .. '">'
		.. table.concat(parts) .. '</div>'
end

-- Wraps a rendered row with an optional bold label / italic note, or a collapsible box.
-- Returns "" when the row is empty, so nothing (not even the label) shows.
local function wrap(inner, label, note, collapse)
	if inner == "" then return "" end
	if collapse then
		local header = (label and label ~= "") and label or "&nbsp;"
		return '{| class="wikitable mw-collapsible mw-collapsed" style="width:100%"\n'
			.. '! style="text-align:left" | ' .. header .. '\n|-\n| ' .. inner .. '\n|}'
	end
	local pre = ""
	if label and label ~= "" then pre = pre .. "'''" .. label .. "'''<br>" end
	if note and note ~= "" then pre = pre .. "''" .. note .. "''<br>" end
	return pre .. inner
end

local function hero_sets(hkey)
	local favored, unfavored, special, counter = {}, {}, {}, {}
	for _, o in ipairs(sb.Outliers) do
		if o.Hero == hkey then
			if o.Bucket == "Good" then favored[o.Item] = true; special[o.Item] = true
			elseif type(o.Weight) == "number" and o.Weight < 1 then unfavored[o.Item] = true; special[o.Item] = true end
			if o.Counter ~= nil then counter[#counter + 1] = o.Item end
		end
	end
	return favored, unfavored, special, counter
end

local function item_code(input)
	input = mw.text.trim(input or "")
	if input == "" then return nil end
	if items[input] then return input end
	for k, v in pairs(items) do
		if type(v) == "table" and v.Name == input and v.IsDisabled ~= true then return k end
	end
	for k, v in pairs(items) do
		if type(v) == "table" and v.Name == input then return k end
	end
	return nil
end

-- {{#invoke:StreetBrawlItems|hero|HERO|GROUP|TIER|label=..|collapse=yes|style=card|icon}}
-- GROUP = favored | unfavored | common   (use hero_counter for counters)
function p.hero(frame)
	local hkey = hero_key(frame.args[1]); if not hkey then return "" end
	local group = mw.text.trim(frame.args[2] or "")
	local tier = tonumber(frame.args[3])
	local style = frame.args.style
	if not style or style == "" then style = (group == "common") and "icon" or "card" end
	local collapse = (frame.args.collapse == "yes" or frame.args.collapse == "true")

	local favored, unfavored, special = hero_sets(hkey)
	local pred
	if group == "favored" then pred = function(c) return favored[c] end
	elseif group == "unfavored" then pred = function(c) return unfavored[c] end
	elseif group == "common" then pred = function(c) return not special[c] end
	else return "" end

	local codes = {}
	for _, c in ipairs(sb.AvailableItems) do
		if iok(c) and (tier == nil or itier(c) == tier) and pred(c) then codes[#codes + 1] = c end
	end
	table.sort(codes, function(a, b) return (iname(a) or a) < (iname(b) or b) end)

	local parts = {}
	for _, c in ipairs(codes) do local r = render_item(frame, c, style); if r then parts[#parts + 1] = r end end
	local is_icon = (style == "icon")
	local inner = flexrow(parts, is_icon and "4px 10px" or "8px", is_icon)
	return wrap(inner, frame.args.label, nil, collapse)
end

-- {{#invoke:StreetBrawlItems|hero_counter|HERO|heading=..|note=..}}
-- Returns the whole counter block (heading + note + cards), or "" when empty.
function p.hero_counter(frame)
	local hkey = hero_key(frame.args[1]); if not hkey then return "" end
	local _, _, _, counter = hero_sets(hkey)

	local codes = {}
	for _, c in ipairs(counter) do if iok(c) then codes[#codes + 1] = c end end
	if #codes == 0 then return "" end
	table.sort(codes, function(a, b) return (iname(a) or a) < (iname(b) or b) end)

	local parts = {}
	for _, c in ipairs(codes) do local r = render_item(frame, c, "card"); if r then parts[#parts + 1] = r end end

	local out = {}
	local heading = frame.args.heading
	if heading and heading ~= "" then out[#out + 1] = "=== " .. heading .. " ===" end
	local note = frame.args.note
	if note and note ~= "" then out[#out + 1] = "''" .. note .. "''" end
	out[#out + 1] = flexrow(parts, "8px", false)
	return table.concat(out, "\n")
end

-- {{#invoke:StreetBrawlItems|item|ITEM|GROUP|label=..|note=..}}
-- GROUP = favored | unfavored | counter
function p.item(frame)
	local code = item_code(frame.args[1]); if not code then return "" end
	local group = mw.text.trim(frame.args[2] or "")

	local fav, unf, ctr = {}, {}, {}
	for _, o in ipairs(sb.Outliers) do
		if o.Item == code and RELEASED[o.Hero] then
			if o.Bucket == "Good" then fav[#fav + 1] = o.Hero
			elseif type(o.Weight) == "number" and o.Weight < 1 then unf[#unf + 1] = o.Hero end
			if o.Counter ~= nil then ctr[#ctr + 1] = o.Hero end
		end
	end
	local list = (group == "favored" and fav) or (group == "unfavored" and unf) or (group == "counter" and ctr)
	if not list then return "" end
	table.sort(list, function(a, b) return (KEY_TO_NAME[a] or a) < (KEY_TO_NAME[b] or b) end)

	local parts = {}
	for _, k in ipairs(list) do local r = render_hero(frame, k); if r then parts[#parts + 1] = r end end
	local inner = flexrow(parts, "4px 10px", true)
	return wrap(inner, frame.args.label, frame.args.note, false)
end

-- {{#invoke:StreetBrawlItems|item_in_pool|ITEM}} -> "1" if in the Street Brawl pool, else ""
function p.item_in_pool(frame)
	local code = item_code(frame.args[1]); if not code then return "" end
	for _, c in ipairs(sb.AvailableItems) do if c == code then return "1" end end
	return ""
end

return p