Module:StreetBrawlItems: Difference between revisions

From The Deadlock Wiki
Jump to navigation Jump to search
Wrap rendered groups in flex rows so cards and hero icons have consistent spacing (with help from vergir-bot LLM)
Vergir (talk | contribs)
Hero section: move styling to classes (Template:HeroStreetBrawl/styles.css), sort items by slot then cost then name, wrap groups for spacing (with help from vergir-bot LLM)
Line 4: Line 4:
local items = mw.loadJsonData("Data:ItemData.json")
local items = mw.loadJsonData("Data:ItemData.json")
local heroes = mw.loadJsonData("Data:HeroData.json")
local heroes = mw.loadJsonData("Data:HeroData.json")
-- Slot display order: Weapon -> Vitality (Armor) -> Spirit (Tech)
local SLOT_RANK = { Weapon = 1, Armor = 2, Tech = 3 }


local NAME_TO_KEY, KEY_TO_NAME, RELEASED = {}, {}, {}
local NAME_TO_KEY, KEY_TO_NAME, RELEASED = {}, {}, {}
Line 28: Line 31:
if it.StreetBrawl == true then return 5 end
if it.StreetBrawl == true then return 5 end
return it.Tier
return it.Tier
end
local function islot(code) local it = items[code]; return it and it.Slot end
local function icost(code)
local it = items[code]; local c = it and it.Cost
return type(c) == "number" and c or math.huge
end
-- Sort order: slot (Weapon -> Armor -> Tech), then cost ascending, then name.
local function item_less(a, b)
local ra = SLOT_RANK[islot(a)] or 99
local rb = SLOT_RANK[islot(b)] or 99
if ra ~= rb then return ra < rb end
local ca, cb = icost(a), icost(b)
if ca ~= cb then return ca < cb end
return (iname(a) or a) < (iname(b) or b)
end
end


Line 40: Line 58:
end
end


-- Lays rendered members out in a wrapping flex row with a consistent gap.
-- Lays rendered members out in a container with the given class (styled via CSS).
local function flexrow(parts, gap, center)
local function classdiv(parts, class)
if #parts == 0 then return "" end
if #parts == 0 then return "" end
local align = center and "center" or "flex-start"
return '<div class="' .. class .. '">' .. table.concat(parts) .. '</div>'
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
end


Line 97: Line 98:
if not style or style == "" then style = (group == "common") and "icon" or "card" end
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 collapse = (frame.args.collapse == "yes" or frame.args.collapse == "true")
local label = frame.args.label or ""


local favored, unfavored, special = hero_sets(hkey)
local favored, unfavored, special = hero_sets(hkey)
Line 109: Line 111:
if iok(c) and (tier == nil or itier(c) == tier) and pred(c) then codes[#codes + 1] = c end
if iok(c) and (tier == nil or itier(c) == tier) and pred(c) then codes[#codes + 1] = c end
end
end
table.sort(codes, function(a, b) return (iname(a) or a) < (iname(b) or b) end)
table.sort(codes, item_less)


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
local is_icon = (style == "icon")
if #parts == 0 then return "" end
local inner = flexrow(parts, is_icon and "4px 10px" or "8px", is_icon)
 
return wrap(inner, frame.args.label, nil, collapse)
if collapse then
local head = (label ~= "" and label) or "&nbsp;"
return '{| class="wikitable mw-collapsible mw-collapsed sb-common"\n'
.. '! class="sb-common-head" | ' .. head .. '\n|-\n| '
.. classdiv(parts, "sb-icons") .. '\n|}'
end
 
local row_class = (style == "icon") and "sb-icons" or "sb-cards"
local out = '<div class="sb-group">'
if label ~= "" then out = out .. '<div class="sb-label">' .. label .. '</div>' end
return out .. classdiv(parts, row_class) .. '</div>'
end
end


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


local parts = {}
local parts = {}
Line 137: Line 149:
local note = frame.args.note
local note = frame.args.note
if note and note ~= "" then out[#out + 1] = "''" .. note .. "''" end
if note and note ~= "" then out[#out + 1] = "''" .. note .. "''" end
out[#out + 1] = flexrow(parts, "8px", false)
out[#out + 1] = classdiv(parts, "sb-cards")
return table.concat(out, "\n")
return table.concat(out, "\n")
end
end
Line 161: Line 173:
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
local inner = flexrow(parts, "4px 10px", true)
if #parts == 0 then return "" end
return wrap(inner, frame.args.label, frame.args.note, false)
 
local pre = ""
local label = frame.args.label
if label and label ~= "" then pre = pre .. "'''" .. label .. "'''<br>" end
local note = frame.args.note
if note and note ~= "" then pre = pre .. "''" .. note .. "''<br>" end
return pre .. classdiv(parts, "sb-icons")
end
end



Revision as of 13:38, 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")

-- Slot display order: Weapon -> Vitality (Armor) -> Spirit (Tech)
local SLOT_RANK = { Weapon = 1, Armor = 2, Tech = 3 }

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 islot(code) local it = items[code]; return it and it.Slot end
local function icost(code)
	local it = items[code]; local c = it and it.Cost
	return type(c) == "number" and c or math.huge
end

-- Sort order: slot (Weapon -> Armor -> Tech), then cost ascending, then name.
local function item_less(a, b)
	local ra = SLOT_RANK[islot(a)] or 99
	local rb = SLOT_RANK[islot(b)] or 99
	if ra ~= rb then return ra < rb end
	local ca, cb = icost(a), icost(b)
	if ca ~= cb then return ca < cb end
	return (iname(a) or a) < (iname(b) or b)
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 container with the given class (styled via CSS).
local function classdiv(parts, class)
	if #parts == 0 then return "" end
	return '<div class="' .. class .. '">' .. table.concat(parts) .. '</div>'
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 label = frame.args.label or ""

	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, item_less)

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

	if collapse then
		local head = (label ~= "" and label) or "&nbsp;"
		return '{| class="wikitable mw-collapsible mw-collapsed sb-common"\n'
			.. '! class="sb-common-head" | ' .. head .. '\n|-\n| '
			.. classdiv(parts, "sb-icons") .. '\n|}'
	end

	local row_class = (style == "icon") and "sb-icons" or "sb-cards"
	local out = '<div class="sb-group">'
	if label ~= "" then out = out .. '<div class="sb-label">' .. label .. '</div>' end
	return out .. classdiv(parts, row_class) .. '</div>'
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, item_less)

	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] = classdiv(parts, "sb-cards")
	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
	if #parts == 0 then return "" end

	local pre = ""
	local label = frame.args.label
	if label and label ~= "" then pre = pre .. "'''" .. label .. "'''<br>" end
	local note = frame.args.note
	if note and note ~= "" then pre = pre .. "''" .. note .. "''<br>" end
	return pre .. classdiv(parts, "sb-icons")
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