| Latest revision |
Your text |
| Line 1: |
Line 1: |
| 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 }
| |
| -- Card scale passed to ItemBox.
| |
| local CARD_SIZE = "0.7"
| |
|
| |
| local NAME_TO_KEY = {}
| |
| for key, h in pairs(heroes) do
| |
| if type(h) == "table" and h.Name then
| |
| NAME_TO_KEY[mw.ustring.lower(h.Name)] = key
| |
| 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)
| |
| local name = iname(code); if not name then return nil end
| |
| -- These hero pages place hundreds of cards at once, so the cards render in
| |
| -- lowGraphics mode: ItemBox swaps its paper texture for a single cacheable
| |
| -- overlay, which keeps scrolling smooth. See [[Template:ItemBox]].
| |
| local args = { name, size = CARD_SIZE, lowGraphics = "true" }
| |
| -- 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
| |
|
| |
| -- 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
| |
|
| |
| -- A tier/section heading: a plain div carrying the skin's own heading wrapper
| |
| -- classes, so it inherits real heading styling while staying out of the table
| |
| -- of contents (and can be hidden inside the gate).
| |
| local function heading_div(label)
| |
| return '<div class="mw-heading mw-heading4 sb-tier-head">' .. label .. '</div>'
| |
| end
| |
|
| |
| local function hero_sets(hkey)
| |
| local favored, unfavored, counter = {}, {}, {}
| |
| for _, o in ipairs(sb.Outliers) do
| |
| if o.Hero == hkey then
| |
| if o.Bucket == "Good" then favored[o.Item] = true
| |
| elseif type(o.Weight) == "number" and o.Weight < 1 then unfavored[o.Item] = true end
| |
| if o.Counter ~= nil then counter[#counter + 1] = o.Item end
| |
| end
| |
| end
| |
| return favored, unfavored, counter
| |
| end
| |
|
| |
| -- Returns sorted high (favored), average (pool), low (unfavored) item-code lists for a tier.
| |
| local function collect(hkey, tier)
| |
| local favored, unfavored = hero_sets(hkey)
| |
| local high, average, 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 average[#average + 1] = c end
| |
| end
| |
| end
| |
| table.sort(high, item_less)
| |
| table.sort(average, item_less)
| |
| table.sort(low, item_less)
| |
| return high, average, low
| |
| end
| |
|
| |
| -- Builds one tier as conditional columns:
| |
| -- High chance (favored) takes 35%, the average column fills the rest.
| |
| -- Low chance (unfavored) is dropped when empty; a narrow 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, hi, avg, lo)
| |
| local high, average, low = collect(hkey, tier)
| |
|
| |
| local function col(codes, cls, head)
| |
| local parts = {}
| |
| for _, c in ipairs(codes) do local r = render_item(frame, c); if r then parts[#parts + 1] = r end end
| |
| return '<div class="sb-col ' .. cls .. '">'
| |
| .. '<div class="sb-col-head">' .. head .. '</div>'
| |
| .. '<div class="sb-col-body sb-col-cards">' .. table.concat(parts) .. '</div>'
| |
| .. '</div>'
| |
| end
| |
|
| |
| local out = {}
| |
| if #high > 0 then
| |
| out[#out + 1] = col(high, "sb-col-high sb-w-high", hi)
| |
| end
| |
| out[#out + 1] = col(average, "sb-col-average sb-w-fill", avg)
| |
| 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="sb-tier">' .. table.concat(out) .. '</div>'
| |
| end
| |
|
| |
| -- {{#invoke:StreetBrawlItems|tiers|HERO|gate=ID|high=..|average=..|low=..|tier1=..|...|tier5=..}}
| |
| -- All five tiers under styled (non-TOC) headings. All display text is supplied by the
| |
| -- caller. With gate=ID, the Tier 1 heading and everything after it is wrapped in an open
| |
| -- <div id=ID class="sb-gate">, whose inner <div class="sb-gate-body"> the caller must
| |
| -- close after any trailing content. The id sits on the outer wrapper so following the
| |
| -- anchor lands on the Tier 1 heading rather than the columns below it.
| |
| function p.tiers(frame)
| |
| local hkey = hero_key(frame.args[1]); if not hkey then return "" end
| |
| local hi = frame.args.high or ""
| |
| local avg = frame.args.average or ""
| |
| local lo = frame.args.low or ""
| |
| local gate = frame.args.gate
| |
| if gate == "" then gate = nil end
| |
|
| |
| local out = {}
| |
| for t = 1, 5 do
| |
| local head = heading_div(frame.args["tier" .. t] or "")
| |
| local cols = tier_columns(frame, hkey, t, hi, avg, lo)
| |
| if t == 1 and gate then
| |
| out[#out + 1] = '<div id="' .. gate .. '" class="sb-gate">\n'
| |
| .. head .. "\n"
| |
| .. '<div class="sb-gate-body">\n'
| |
| .. cols
| |
| else
| |
| out[#out + 1] = head .. "\n" .. cols
| |
| end
| |
| end
| |
| return table.concat(out, "\n\n")
| |
| end
| |
|
| |
| -- {{#invoke:StreetBrawlItems|hero_counter|HERO|heading=..|note=..}}
| |
| -- The whole counter block (heading + note + cards), or "" when the hero has none.
| |
| 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); 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_div(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|counters_table}}
| |
| -- Every hero that has counter items, one row each, sorted by hero name. Items
| |
| -- within a row are listed alphabetically. Built as HTML rather than pipe-table
| |
| -- markup so the invoke does not have to sit at the start of a line.
| |
| function p.counters_table(frame)
| |
| local by_hero, rows = {}, {}
| |
| for _, o in ipairs(sb.Outliers) do
| |
| if o.Counter ~= nil and iok(o.Item) then
| |
| local hero = heroes[o.Hero]
| |
| if hero and hero.Name then
| |
| local row = by_hero[o.Hero]
| |
| if not row then
| |
| row = { name = hero.Name, items = {}, seen = {} }
| |
| by_hero[o.Hero] = row
| |
| rows[#rows + 1] = row
| |
| end
| |
| -- Guard against a hero/item pair being listed twice.
| |
| if not row.seen[o.Item] then
| |
| row.seen[o.Item] = true
| |
| row.items[#row.items + 1] = iname(o.Item)
| |
| end
| |
| end
| |
| end
| |
| end
| |
| if #rows == 0 then return "" end
| |
| table.sort(rows, function(a, b) return a.name < b.name end)
| |
|
| |
| local tbl = mw.html.create("table"):addClass("wikitable")
| |
| local head = tbl:tag("tr")
| |
| head:tag("th"):wikitext("Hero")
| |
| head:tag("th"):wikitext("Items")
| |
|
| |
| for _, row in ipairs(rows) do
| |
| table.sort(row.items)
| |
| local icons = {}
| |
| for _, n in ipairs(row.items) do
| |
| icons[#icons + 1] = frame:expandTemplate{title = "ItemIcon", args = {n}}
| |
| end
| |
| local tr = tbl:tag("tr")
| |
| tr:tag("td"):wikitext(frame:expandTemplate{title = "HeroIcon", args = {row.name}})
| |
| tr:tag("td"):wikitext(table.concat(icons, ", "))
| |
| end
| |
| return tostring(tbl)
| |
| end
| |
|
| |
| return p
| |