Module:Buildables
Jump to navigation
Jump to search
Documentation for this module may be created at Module:Buildables/doc
local p = {}
local common = require("Module:Common")
-- ---------- Translation ----------
-- resolves a piece name, checking the shared Buildables bundle first with fallback to the set-specific bundle
local function translatePiece(setSlug, id, lang)
local key = id .. "-name"
return common.translate("Buildables", key, lang, false, true)
or common.translate("Buildables/" .. setSlug, key, lang)
end
-- ---------- Data ----------
-- loads dataset for the specified set
local function loadSet(setSlug)
local ok, data = pcall(require, "Module:Buildables/data/" .. setSlug)
if not ok then return nil, nil end
local categories = {}
for categorySlug in pairs(data) do
categories[#categories + 1] = categorySlug
end
table.sort(categories)
return data, categories
end
-- flattens a two-level table keyed by category into both a flat list sorted by display name, and a table keyed by id
local function flattenSet(data, categories, setSlug)
local list = {}
local byId = {}
for _, categorySlug in ipairs(categories) do
for _, piece in ipairs(data[categorySlug]) do
piece.category = categorySlug
piece.sortName = translatePiece(setSlug, piece.id):lower()
list[#list + 1] = piece
byId[piece.id] = piece
end
end
table.sort(list, function(a, b) return a.sortName < b.sortName end)
return list, byId
end
-- returns a sorted array of "Name (xN)" strings for a piece's building cost
local function formatCostItems(cost)
local items = {}
for material, quantity in pairs(cost) do
items[#items + 1] = string.format("%s (x%d)", material, quantity)
end
table.sort(items)
return items
end
-- resolves and validates the specified set
local function resolveSet(frame)
local rawSet = common.trim(frame.args[1])
if rawSet == "" then return nil, nil, nil, common.error("No set specified.") end
local setSlug = common.slugify(rawSet)
local data, categories = loadSet(setSlug)
if not data then return nil, nil, nil, common.error("Unknown set: %s", rawSet) end
return setSlug, data, categories
end
-- ---------- Rendering ----------
local function renderSourceCell(source)
local bundleName, slug = common.parseSourceToken(source)
if not bundleName then return common.error("Malformed source token: %s", source) end
local key = slug .. "-name"
return common.translate(bundleName, key, nil, true)
end
local function renderCostCell(cost)
local items = formatCostItems(cost)
-- manually generate a left-aligned bullet list
local wrapper = mw.html.create("div")
:css("display", "inline-block")
:css("text-align", "left")
local ul = wrapper:tag("ul")
for _, item in ipairs(items) do
ul:tag("li"):wikitext(item)
end
return wrapper
end
local function renderTable(list, setSlug, showSource, showCategory)
local table_ = common.newSortableTable(#list, "Buildables", "label-title")
local headerRow = table_:tag("tr")
headerRow:tag("th"):addClass("unsortable"):wikitext(common.translate("Common", "label-icon"))
headerRow:tag("th"):wikitext(common.translate("Common", "label-name"))
if showCategory then
headerRow:tag("th"):wikitext(common.translate("Common", "label-category"))
end
headerRow:tag("th"):wikitext(common.translate("Common", "label-health"))
headerRow:tag("th"):addClass("unsortable"):wikitext(common.translate("Common", "label-cost"))
if showSource then
headerRow:tag("th"):wikitext(common.translate("Common", "label-source"))
end
if #list == 0 then
local row = table_:tag("tr")
row:tag("td"):attr("colspan", 99):css("font-style", "italic"):wikitext("No matching pieces.")
return table_
end
for _, piece in ipairs(list) do
local displayName = translatePiece(setSlug, piece.id)
local row = table_:tag("tr")
row:tag("td"):css("min-width", "128px"):wikitext(string.format("[[File:%s|128px]]", piece.image))
row:tag("td"):css("white-space", "nowrap"):css("font-weight", "bold"):wikitext(displayName)
if showCategory then
local displayCategory = common.translate("Buildables", "category-" .. piece.category)
row:tag("td"):css("white-space", "nowrap"):wikitext(displayCategory)
end
row:tag("td"):css("white-space", "nowrap"):wikitext(piece.health)
row:tag("td"):css("white-space", "nowrap"):node(renderCostCell(piece.cost))
if showSource then
row:tag("td"):css("white-space", "nowrap"):wikitext(renderSourceCell(piece.source))
end
end
return table_
end
local function renderInfobox(setSlug, piece)
local displayName = translatePiece(setSlug, piece.id)
local englishName = translatePiece(setSlug, piece.id, "en")
local displayCategory = common.translate("Buildables", "category-" .. piece.category)
local englishCategory = common.translate("Buildables", "category-" .. piece.category, "en")
local displaySet = common.translate("Buildables/" .. setSlug, "set-name")
local englishSet = common.translate("Buildables/" .. setSlug, "set-name", "en")
local out = {}
out[#out + 1] = "{{BuildableInfobox"
out[#out + 1] = "|title=" .. englishName
out[#out + 1] = "|display_title=" .. displayName
out[#out + 1] = "|set=" .. englishSet
out[#out + 1] = "|display_set=" .. displaySet
out[#out + 1] = "|category=" .. englishCategory
out[#out + 1] = "|display_category=" .. displayCategory
out[#out + 1] = "|health=" .. piece.health
out[#out + 1] = "|cost=" .. table.concat(formatCostItems(piece.cost), ",")
if piece.source then
out[#out + 1] = "|source=" .. renderSourceCell(piece.source)
end
out[#out + 1] = "}}"
return table.concat(out, "\n")
end
-- ---------- Interface ----------
function p.list(frame)
local setSlug, data, categories, err = resolveSet(frame)
if err then return err end
local rawCategory = common.trim(frame.args.category)
local catFilter = rawCategory ~= "" and common.slugify(rawCategory) or ""
if catFilter ~= "" and not common.listContains(categories, catFilter) then
return common.error("Unknown category: %s", rawCategory)
end
local rawSource = common.trim(frame.args.source)
local sourceFilter = rawSource ~= "" and common.slugify(rawSource) or ""
local list
if catFilter ~= "" then
list = flattenSet(data, { catFilter }, setSlug)
else
list = flattenSet(data, categories, setSlug)
end
local filtered = {}
for _, piece in ipairs(list) do
local sourceMatch = true
if sourceFilter ~= "" then
local _, slug = common.parseSourceToken(piece.source)
sourceMatch = slug == sourceFilter
end
if sourceMatch then filtered[#filtered + 1] = piece end
end
local showSource = sourceFilter == ""
local showCategory = catFilter == ""
return tostring(renderTable(filtered, setSlug, showSource, showCategory))
end
function p.get(frame)
local setSlug, data, categories, err = resolveSet(frame)
if err then return err end
local rawName = common.trim(frame.args[2])
if rawName == "" then return common.error("No piece specified.") end
local idSlug = common.slugify(rawName)
local _, byId = flattenSet(data, categories, setSlug)
local piece = byId[idSlug]
if not piece then return common.error("Unknown piece: %s", rawName) end
return frame:preprocess(renderInfobox(setSlug, piece))
end
return p