Module:StreetBrawlItems: Difference between revisions

From The Deadlock Wiki
Jump to navigation Jump to search
Vergir (talk | contribs)
mNo edit summary
Vergir (talk | contribs)
mNo edit summary
Line 219: Line 219:
local theme = frame.args.theme or ""
local theme = frame.args.theme or ""
local hi = frame.args.high or "High chance"
local hi = frame.args.high or "High chance"
local mid = frame.args.middle or "Middle chance"
local mid = frame.args.middle or "Average chance"
local lo = frame.args.low or "Low chance"
local lo = frame.args.low or "Low chance"



Revision as of 14:28, 21 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 }
-- Default card scale passed to ItemBox (tier columns and counter cards).
local CARD_SIZE = "0.75"

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
-- A "real" item: exists, has a display name, and is not disabled/removed.
local function iok(code)
	local it = items[code]
	return it ~= nil and it.Name ~= nil and it.IsDisabled ~= true
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, size)
	local name = iname(code); if not name then return nil end
	if style == "icon" then return frame:expandTemplate{title = "ItemIcon", args = {name}} end
	local args = { name, size = size or CARD_SIZE }
	-- Legendary (tier 5) cards omit the tier badge; it is redundant in the Legendary tier.
	if itier(code) ~= 5 then args.showTierTag = "true" end
	return frame:expandTemplate{title = "ItemBox", args = args}
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

-- Returns sorted high (favored), normal (pool), low (unfavored) item-code lists for a tier.
local function collect(hkey, tier)
	local favored, unfavored = hero_sets(hkey)
	local high, normal, low = {}, {}, {}
	for _, c in ipairs(sb.AvailableItems) do
		if iok(c) and (tier == nil or itier(c) == tier) then
			if favored[c] then high[#high + 1] = c
			elseif unfavored[c] then low[#low + 1] = c
			else normal[#normal + 1] = c end
		end
	end
	table.sort(high, item_less)
	table.sort(normal, item_less)
	table.sort(low, item_less)
	return high, normal, low
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

-- Builds one tier as conditional columns:
--   High chance (favored) 35% and Middle chance (pool) fill the rest.
--   Low chance (unfavored) is dropped when empty; a narrow one-card column on the right
--   when High is present; a fit-content column on the right when High is absent.
local function tier_columns(frame, hkey, tier, style, size, theme, hi, mid, lo)
	local high, normal, low = collect(hkey, tier)
	local body_class = (style == "card") and "sb-col-body sb-col-cards" or "sb-col-body"
	local sep = (style == "card") and "" or "<br>"

	local function col(codes, cls, head)
		local parts = {}
		for _, c in ipairs(codes) do local r = render_item(frame, c, style, size); if r then parts[#parts + 1] = r end end
		return '<div class="sb-col ' .. cls .. '">'
			.. '<div class="sb-col-head">' .. head .. '</div>'
			.. '<div class="' .. body_class .. '">' .. table.concat(parts, sep) .. '</div>'
			.. '</div>'
	end

	local cls = "sb-tier"
	if theme ~= "" then cls = cls .. " " .. theme end

	local out = {}
	if #high > 0 then
		out[#out + 1] = col(high, "sb-col-high sb-w-high", hi)
	end
	out[#out + 1] = col(normal, "sb-col-normal sb-w-fill", mid)
	if #low > 0 then
		local low_w = (#high > 0) and "sb-low-narrow" or "sb-low-wide"
		out[#out + 1] = col(low, "sb-col-low " .. low_w, lo)
	end
	return '<div class="' .. cls .. '">' .. table.concat(out) .. '</div>'
end

-- {{#invoke:StreetBrawlItems|hero|HERO|GROUP|TIER|label=..|collapse=yes|style=card|icon}}
-- Legacy: superseded by p.tier, kept for reference.
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 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

	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|tier|HERO|TIER|style=icon|card|size=..|theme=..|layout=grid|high=..|middle=..|low=..}}
function p.tier(frame)
	local hkey = hero_key(frame.args[1]); if not hkey then return "" end
	local tier = tonumber(frame.args[2])
	local style = (frame.args.style == "card") and "card" or "icon"
	local size = frame.args.size
	local theme = frame.args.theme or ""
	local hi = frame.args.high or "High chance"
	local mid = frame.args.middle or "Average chance"
	local lo = frame.args.low or "Low chance"

	if frame.args.layout == "grid" then
		local high, normal, low = collect(hkey, tier)
		local function chips(codes, chance)
			local out = {}
			for _, c in ipairs(codes) do
				local r = render_item(frame, c, "card", size)
				if r then out[#out + 1] = '<span class="sb-chip ' .. chance .. '">' .. r .. '</span>' end
			end
			return table.concat(out)
		end
		return '<div class="sb-grid">'
			.. chips(high, "sb-chip-high")
			.. chips(normal, "sb-chip-normal")
			.. chips(low, "sb-chip-low")
			.. '</div>'
	end

	return tier_columns(frame, hkey, tier, style, size, theme, hi, mid, lo)
end

-- {{#invoke:StreetBrawlItems|tiers|HERO|style=..|size=..|theme=..}} -> all five tiers, each with a heading.
function p.tiers(frame)
	local hkey = hero_key(frame.args[1]); if not hkey then return "" end
	local style = (frame.args.style == "card") and "card" or "icon"
	local size = frame.args.size
	local theme = frame.args.theme or ""
	local hi = frame.args.high or "High chance"
	local mid = frame.args.middle or "Average chance"
	local lo = frame.args.low or "Low chance"

	local labels = { "Tier 1", "Tier 2", "Tier 3", "Tier 4", "Legendary" }
	local out = {}
	for t = 1, 5 do
		out[#out + 1] = "=== " .. labels[t] .. " ===\n" .. tier_columns(frame, hkey, t, style, size, theme, hi, mid, lo)
	end
	return table.concat(out, "\n\n")
end

-- {{#invoke:StreetBrawlItems|hero_counter|HERO|heading=..|note=..}}
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=..}}
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