Module:Resource infobox

From Beyond These Stars Official Wiki
Revision as of 19:09, 10 October 2026 by Aeredor (talk | contribs) (empty number-type benefits now default to 0)

Documentation for this module may be created at Module:Resource infobox/doc

--[[
Test in Scribunto console with:
=mw.dumpObject(p.render( {} ))
=p.render({ args = { page_name = "Iron" } })
]]--
local p = {}

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



--#region html building helper functions

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

local INFOBOX_CLASS = "bts-infobox-resource"

local HEADER_CLASS = INFOBOX_CLASS .. "-header"
local HEADER_ICON_CLASS = HEADER_CLASS .. "-icon"

local TITLE_CLASS = HEADER_CLASS .. "-title"
local SUBTITLE_CLASS = HEADER_CLASS .. "-subtitle"
local SUBHEADER_CLASS = INFOBOX_CLASS .. "-subheader"

local BENEFITS_CLASS = INFOBOX_CLASS .. "-benefits"
local BENEFITS_TABLE_CLASS = BENEFITS_CLASS .. "-table"

local PRODUCTION_CLASS = INFOBOX_CLASS .. "-production"
local PRODUCTION_TABLE_CLASS = PRODUCTION_CLASS .. "-table"

local LARGE_ICON_SIZE = "128px"
local SMALL_ICON_SIZE = "32px"



--- Renders wikitext for the resource's icon.
---@param resource Resource
---@return string wikitext
local function render_icon_markup(resource)
  local icon_filename = resource.icon .. ".png"
  local image_markup = "[[File:" .. icon_filename .. "|" .. LARGE_ICON_SIZE .. "|" .. resource.name .. "|link=]]"
  return image_markup
end

--- Adds the header of the infobox to the provided html node.
---@param div HtmlNode enclosing infobox div
---@param resource Resource
local function add_header(div, resource)
  local section = div:tag("div"):addClass(HEADER_CLASS)
  section:tag("div"):addClass(TITLE_CLASS):wikitext(resource.display_name)
  section:tag("div"):addClass(HEADER_ICON_CLASS):wikitext(render_icon_markup(resource))
  if resource.category and resource.category ~= "" then
    section:tag("div"):addClass(SUBTITLE_CLASS):wikitext(resource.category .. " Resource")
  end
end



--- Adds a subheader to the provided html node.
---@param div HtmlNode enclosing infobox div
---@param text string
local function add_subheader(div, text)
  div:tag("div"):addClass(SUBHEADER_CLASS):wikitext(text)
end


--- Counts the benefits to peeps for the given resource.
---@param resource Resource
---@return number
local function count_benefits(resource)
  local count = 0
  if resource.peep_happiness_added and resource.peep_happiness_added ~= 0 then
    count = count + 1
  end
  if resource.peep_energy_added and resource.peep_energy_added ~= 0 then
    count = count + 1
  end
  if resource.peep_exhaustion_removed and resource.peep_exhaustion_removed ~= 0 then
    count = count + 1
  end
  if resource.peep_temperature_effect and resource.peep_temperature_effect ~= "" then
    count = count + 1
  end
  return count
end

--- Adds the benefits to peeps to the provided html node.
---@param div HtmlNode enclosing infobox div
---@param resource Resource
local function add_benefits_to_peeps(div, resource)
  local section = div:tag("div"):addClass(BENEFITS_CLASS)
  local count = count_benefits(resource)
  if count > 0 then
    local tbl = mw.html.create("table"):addClass(BENEFITS_TABLE_CLASS)
    local header_row = tbl:tag("tr")
    header_row:tag("th"):wikitext("Happiness")
    header_row:tag("th"):wikitext("Energy")
    header_row:tag("th"):wikitext("Exhaustion")
    if resource.peep_temperature_effect and resource.peep_temperature_effect ~= "" then
      header_row:tag("th"):wikitext("Temperature")
    end
    local row = tbl:tag("tr")
    row:tag("td"):wikitext(resource.peep_happiness_added or 0)
    row:tag("td"):wikitext(resource.peep_energy_added or 0)
    row:tag("td"):wikitext(resource.peep_exhaustion_removed or 0)
    if resource.peep_temperature_effect and resource.peep_temperature_effect ~= "" then
      row:tag("td"):wikitext(resource.peep_temperature_effect)
    end
    section:node(tbl)
  else
    section:wikitext("None")
  end
end



--- Renders the icon for a resource.
---@param resource Resource
---@return string wikitext
local function render_io_resource_icon(resource)
  local icon_filename = resource.icon .. ".png"
  local image_markup = "[[File:" .. icon_filename .. "|" .. SMALL_ICON_SIZE .. "|" .. resource.name .. "|link=]]"
  return image_markup
end

--- Splits tasks by inputs and outputs.
---@param map_of_tasks table<string, BuildingTask[]>
---@param resource_id string the id of the resource to split tasks for
---@return table<string, BuildingTask[]> inputs
---@return table<string, BuildingTask[]> outputs
local function split_tasks_by_inputs_and_outputs(map_of_tasks, resource_id)
  local inputs = {}
  local outputs = {}
  for building_name, tasks in pairs(map_of_tasks) do
    for _, task in ipairs(tasks) do
      for _, input in ipairs(task.inputs or {}) do
        if input.resource == resource_id then
          if not inputs[building_name] then
            inputs[building_name] = {}
          end
          table.insert(inputs[building_name], task)
        end
      end
      for _, output in ipairs(task.outputs or {}) do
        if output.resource == resource_id then
          if not outputs[building_name] then
            outputs[building_name] = {}
          end
          table.insert(outputs[building_name], task)
        end
      end
    end
  end
  return inputs, outputs
end

--- Concatenates the outputs of a task.
---@param task BuildingTask
---@return string wikitext
local function concatenate_outputs(task)
  local outputs = ""
  if not task.outputs or #task.outputs == 0 then return "" end
  local count = 0
  for _, output in ipairs(task.outputs) do
    outputs = outputs .. (count > 0 and ", " or "") .. output.resource
    count = count + 1
  end
  return outputs
end

--- Concatenates the inputs of a task.
---@param task BuildingTask
---@return string wikitext
local function concatenate_inputs(task)
  local inputs = ""
  if not task.inputs or #task.inputs == 0 then return "" end
  local count = 0
  for _, input in ipairs(task.inputs) do
    inputs = inputs .. (count > 0 and ", " or "") .. input.resource
    count = count + 1
  end
  return inputs
end

--- Adds the production input rows to the provided html node.
---@param tbl HtmlNode enclosing infobox div
---@param map_of_tasks_with_inputs table<string, BuildingTask[]>
---@param resource Resource
---@return number number of input rows added
local function add_production_input_rows(tbl, map_of_tasks_with_inputs, resource)
  local count = 0
  for building_name, tasks in pairs(map_of_tasks_with_inputs) do
    for _, task in ipairs(tasks) do
      local outputs = concatenate_outputs(task)
      for _, input in ipairs(task.inputs) do
        if input.resource == resource.name then
          local building_link = "[[" .. building_name .. "]]"
          local amount = input.value
          local resource_icon = render_io_resource_icon(resource)
          local resource_markup = amount .. "x " .. resource_icon
          tbl:tag("tr")
              :tag("td"):wikitext(building_link):done()
              :tag("td"):wikitext(resource_markup):done()
              :tag("td"):wikitext(outputs):done()
          count = count + 1
        end
      end
    end
  end
  return count
end

--- Adds the production output rows to the provided html node.
---@param tbl HtmlNode enclosing infobox div
---@param map_of_tasks_with_outputs table<string, BuildingTask[]>
---@param resource Resource
---@return number number of output rows added
local function add_production_output_rows(tbl, map_of_tasks_with_outputs, resource)
  local count = 0
  for building_name, tasks in pairs(map_of_tasks_with_outputs) do
    for _, task in ipairs(tasks) do
      local inputs = concatenate_inputs(task)
      for _, output in ipairs(task.outputs) do
        if output.resource == resource.name then
          local building_link = "[[" .. building_name .. "]]"
          local amount = output.value
          local resource_icon = render_io_resource_icon(resource)
          local resource_markup = amount .. "x " .. resource_icon
          tbl:tag("tr")
              :tag("td"):wikitext(building_link):done()
              :tag("td"):wikitext(inputs):done()
              :tag("td"):wikitext(resource_markup):done()
          count = count + 1
        end
      end
    end
  end
  return count
end

--- Adds the production to the provided html node.
---@param div HtmlNode enclosing infobox div
---@param resource Resource
local function add_production(div, resource)
  local section = div:tag("div"):addClass(PRODUCTION_CLASS)
  local building_map_of_tasks = Building_Data_Module.get_building_names_with_tasks_including_resource(resource.name)
  local map_of_tasks_with_inputs, map_of_tasks_with_outputs = split_tasks_by_inputs_and_outputs(building_map_of_tasks, resource.name)
  local tbl = mw.html.create("table"):addClass(PRODUCTION_TABLE_CLASS)
  tbl:tag("tr")
      :tag("th"):wikitext("Building"):done()
      :tag("th"):wikitext("Input"):done()
      :tag("th"):wikitext("Output"):done()
  local input_count = 0
  local output_count = 0
  if next(map_of_tasks_with_inputs) ~= nil then
    input_count = add_production_input_rows(tbl, map_of_tasks_with_inputs, resource)
  end
  if next(map_of_tasks_with_outputs) ~= nil then
    output_count = add_production_output_rows(tbl, map_of_tasks_with_outputs, resource)
  end
  if input_count > 0 or output_count > 0 then
    section:node(tbl)
  else
    section:wikitext("None")
  end
end



--- Builds the infobox wikitext and html for the given resource.
---@param resource Resource
---@return string wikitext
local function build_infobox(resource)
  ---@type HtmlNode
  local div = mw.html.create("div")
  div:addClass(INFOBOX_CLASS)
  add_header(div, resource)
  add_subheader(div, "Benefits to Peeps")
  add_benefits_to_peeps(div, resource)
  add_subheader(div, "In production")
  add_production(div, resource)
  return tostring(div)
end



--- Appends the wiki category to the provided wikitext.
---@param wikitext string
---@param resource Resource
---@return string wikitext
local function append_wiki_category(wikitext, resource)
  if not resource.category or resource.category == "" then return wikitext end
  return wikitext .. "\n[[Category:" .. resource.category .. " Resources]]"
end



--#endregion



--- `#invoke` entry point. Reads `page_name` from invoke args (not the parent 
--- template); falls back to the current page title.
---@param frame table Scribunto frame from `#invoke`.
---@return string wikitext Infobox code or empty string if no resource exists.
function p.render(frame)
  local args = frame.args or {}
  ---@type string
  local name = args[1] or args.page_name or mw.title.getCurrentTitle().fullText
  local resource = Resource_Data_Module.get_resource(name)
  if not resource then return "" end
  local wikitext = build_infobox(resource)
  wikitext = append_wiki_category(wikitext, resource)
  return wikitext
end



return p