Module:Buildings table

From Beyond These Stars Official Wiki
Revision as of 01:49, 30 September 2026 by Aeredor (talk | contribs) (Can now filter on category. Updated to use latest interface for Module:Building_data)

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

--[[
Test in Scribunto console with:
=p.render()
=p.render( { args = { Category = "Construction" } } )
]]--
local p = {}



local Building_Data_Module = require("Module:Building_data")
local Resource_Data_Module = require("Module:Resource_data")



--#region MARK: html builders

---@diagnostic disable-next-line: duplicate-doc-alias
---@alias HtmlNode table

local TOP_LEVEL_CLASS = "bts-table-buildings"
local TABLE_CLASS = "wikitable sortable"
local UNSORTABLE_CLASS = "unsortable"

local RESOURCE_LIST_CLASS = "bts-resource-list"
local RESOURCE_ICON_CLASS = "bts-resource-icon"

local LARGE_ICON_SIZE = "96px"
local SMALL_ICON_SIZE = "32px"

local TEXT_IS_POWERED = "Yes [[File:Resource_Power.png|32px|Power]]"
local TEXT_IS_NOT_POWERED = "No"



--- Adds the header row to the provided html node.
---@param wikitable HtmlNode wikitable
---@param buildings Building[]
local function add_header_row(wikitable, buildings)
  local header = wikitable:tag("tr")
  header:tag("th"):addClass(UNSORTABLE_CLASS):wikitext("Icon")
  header:tag("th"):wikitext("Name")
  header:tag("th"):wikitext("Category")
  header:tag("th"):wikitext("Workers")
  header:tag("th"):wikitext("Production")
  header:tag("th"):wikitext("Req. Power?")
  header:tag("th"):addClass(UNSORTABLE_CLASS):wikitext("Construction Costs")
  header:tag("th"):wikitext("Tech Prerequisite")
end



--- Resolves a file name for the icon for the building.
---@param building Building
---@return string|nil file_name
local function resolve_icon_file_name(building)
  if building and building.portrait_icon then
    local portrait_file_name = "File:" .. building.portrait_icon .. ".png"
    return portrait_file_name
  end
  if building and building.icon then
    local vector_file_name = "File:" .. building.icon .. ".png"
    return vector_file_name
  end
  return nil
end



--- Renders markup for an icon for the building.
---@param building Building
---@return string wikitext
local function render_icon(building)
  if not building or not building.icon then return "" end
  local file_name = resolve_icon_file_name(building)
  if not file_name then return "" end
  local icon_size = LARGE_ICON_SIZE -- width instead of typical height because vectors and portraits are different and comingled
  local name = building.display_name
  local link_attr = "link=" .. building.display_name
  return "[[" .. file_name .. "|" .. icon_size .. "|" .. name .. "|" .. link_attr .. "]]"
end



--- Adds an icon for a resource to the provided html table row.
---@param tr HtmlNode tr
---@param resource ResourceRecord
local function add_resource_icon(tr, resource)
  local icon_markup = ""
  if resource and resource.icon then
    local file_name = "File:" .. resource.icon .. ".png"
    local icon_size = "x" .. SMALL_ICON_SIZE
    local name = resource.display_name
    local link_attr = "link=" .. resource.display_name
    icon_markup = "[[" .. file_name .. "|" .. icon_size .. "|" .. name .. "|" .. link_attr .. "]]"
  end
  tr:tag("td"):tag("span"):addClass(RESOURCE_ICON_CLASS):wikitext(icon_markup)
end

--- Adds a link for a resource to the provided html table row.
---@param tr HtmlNode tr
---@param resource ResourceRecord
local function add_resource_link(tr, resource)
  local link_markup = ""
  if resource and resource.display_name then
    link_markup = "[[" .. resource.display_name .. "]]"
  end
  tr:tag("td"):wikitext(link_markup)
end



--- Builds an inner list of produced resources.
---@param building Building
---@return HtmlNode node
local function build_produced_resources(building)
  local tbl = mw.html.create("table"):addClass(RESOURCE_LIST_CLASS)
  if not building or not building.tasks or #building.tasks == 0 then return tbl end
  for _, task in ipairs(building.tasks) do
    for _, output_io in ipairs(task.outputs) do
      local resource = Resource_Data_Module.get_resource(output_io.resource)
      local tr = tbl:tag("tr")
      add_resource_icon(tr, resource)
      add_resource_link(tr, resource)
    end
  end
  return tbl
end



--- Renders a statement about whether the given building requires power.
---@param building Building
---@return string wikitext
local function render_powered(building)
  if not building or not building.tasks or #building.tasks == 0 then return TEXT_IS_NOT_POWERED end
  for _, task in ipairs(building.tasks) do
    for _, input_io in ipairs(task.inputs) do
      if input_io.resource == Resource_Data_Module.POWER_RESOURCE_NAME then
        return TEXT_IS_POWERED
      end
    end
  end
  return TEXT_IS_NOT_POWERED
end



--- Builds an inner list of construction resources.
---@param building Building
---@return HtmlNode node
local function build_construction_resources(building)
  local tbl = mw.html.create("table"):addClass(RESOURCE_LIST_CLASS)
  if not building.construction_resources or building.construction_resources == {} then return tbl end
  for _, pair in ipairs(building.construction_resources) do
    local tr = tbl:tag("tr")
    tr:tag("td"):wikitext(pair.amount)
    local resource_record = Resource_Data_Module.get_resource(pair.name)
    add_resource_icon(tr, resource_record)
    add_resource_link(tr, resource_record)
  end
  return tbl
end



--- Renders a link to the prerequisite building in the tech tree for the given building.
---@param building Building
---@return string wikitext
local function render_tech_prerequisite(building)
  if not building.tech_tree_previous or building.tech_tree_previous == "" then return "" end
  local prerequisite_name = Building_Data_Module.get_name_by_id(building.tech_tree_previous)
  if not prerequisite_name then return "" end
  return "[[" .. prerequisite_name .. "]]"
end



--- Adds a row to the provided html node.
---@param building Building
---@return HtmlNode row
local function build_row(building)
  local row = mw.html.create("tr")
  row:tag("td"):wikitext(render_icon(building))
  row:tag("td"):wikitext("[[" .. building.display_name .. "]]")
  if building.category and building.category ~= "" then
    row:tag("td"):wikitext("[[:Category:" .. building.category .. " Buildings" .. "|" .. building.category .. "]]")
  else
    row:tag("td"):wikitext("")
  end
  row:tag("td"):wikitext(building.max_peeps or "")
  row:tag("td"):node(build_produced_resources(building))
  row:tag("td"):wikitext(render_powered(building))
  row:tag("td"):node(build_construction_resources(building))
  row:tag("td"):wikitext(render_tech_prerequisite(building))
  return row
end



--- Builds a table of buildings.
---@param buildings Building[]
---@return string wikitext
local function build_table(buildings)
  ---@type HtmlNode
  local div = mw.html.create("div")
  div:addClass(TOP_LEVEL_CLASS)
  local wikitable = div:tag("table"):addClass(TABLE_CLASS)
  add_header_row(wikitable, buildings)
  for _, building in ipairs(buildings) do
    wikitable:node(build_row(building))
  end
  return tostring(div)
end

--#endregion



--- `#invoke` entry point. Checks for an optional argument to filter the query.
---@param frame table Scribunto frame from `#invoke`.
---@return string wikitext table wikitext code
function p.render(frame)
  local args = frame.args
  ---@type string|nil
  local maybe_category = args[Building_Data_Module.FILTERS.CATEGORY] or args[string.lower(Building_Data_Module.FILTERS.CATEGORY)]
  local buildings
  if maybe_category and maybe_category ~= "" then
    buildings = Building_Data_Module.get_buildings_by_category(maybe_category)
  else
    buildings = Building_Data_Module.get_all_buildings()
  end
  if #buildings == 0 then return "" end
  local wikitext = build_table(buildings)
  return wikitext
end



return p