Module:Resource: Difference between revisions

From Against the Storm Official Wiki
m added more uses of utility methods
m stack now skips blank-string arguments safely
 
(20 intermediate revisions by the same user not shown)
Line 5: Line 5:
---@class Resource
---@class Resource
---@field _id ResourceID Unique ID code of the resource.
---@field _id ResourceID Unique ID code of the resource.
---@field _displayName string The display name in-game.
---@field _displayName ResourceName The display name in-game.
---@field _description string The in-game descriptive text, including sprite icons, newlines, and escape characters.
---@field _description string The in-game descriptive text, including sprite icons, newlines, and escape characters.
---@field _iconFilename Filename The file name of the icon for this resource.
---@field _iconFilename Filename The file name of the icon for this resource.
Line 17: Line 17:
-- The ID code of a good or resource.
-- The ID code of a good or resource.
---@alias ResourceID string
---@alias ResourceID string
-- The display name of a resource.
---@alias ResourceName string


-- Currency in-game.
-- Currency in-game.
---@alias Amber number
---@alias Amber number
-- The ID and amount of a good or service.
---@alias ResourcePair {_id: ResourceID, _amount: Amount}


-- The resource category based on the info bar.<br>
-- The resource category based on the info bar.<br>
Line 25: Line 31:
---@enum CategoryResource
---@enum CategoryResource
local CATEGORY_RESOURCE = {
local CATEGORY_RESOURCE = {
  -- Resources in the info bar.
   Building = "Building Materials",
   Building = "Building Materials",
   Consumable = "Consumable Items",
   Consumable = "Consumable Items",
Line 33: Line 38:
   Trade = "Trade Goods"
   Trade = "Trade Goods"
}
}


--#region Dependencies
--#region Dependencies


local RESOURCES_DATA_FILE = "Module:Resource/resources_data"


local Wiki_Utility = require("Module:Wiki_Utility")
local Wiki_Utility = require("Module:Wiki_Utility")
local icon = Wiki_Utility.renderIcon
local icon = Wiki_Utility.renderIcon
local nowrap = Wiki_Utility.surroundWithNoWrap
local nowrap = Wiki_Utility.surroundWithNoWrap
local classes = Wiki_Utility.surroundWithClasses
local wrapClasses = Wiki_Utility.surroundWithClasses
local link = Wiki_Utility.renderWikiLink
local link = Wiki_Utility.renderWikiLink
local NBSP = Wiki_Utility.NBSP
local NBSP = Wiki_Utility.NBSP
local standards = Wiki_Utility.StandardizedSizes
local isValidIconSize = Wiki_Utility.isValidIconSize


--#endregion Dependencies
--#endregion Dependencies
--#region Constants
local MIN_ICON_SIZE = 16
--#endregion Constants


--#region Private Members
--#region Private Members


---@type Resource[]
---@type table<ResourceID, Resource>
local resourceData = nil
local resourceData
 
---@type table<ResourceName, ResourceID>
local mapNamesToIDs


local function data()
local function data()
   if not resourceData then
   if not resourceData then
     resourceData = mw.loadData("Module:Resource/resources_data")
    mapNamesToIDs = {}
     resourceData = mw.loadData(RESOURCES_DATA_FILE)
    for id, resource in pairs(resourceData) do
      mapNamesToIDs[resource._displayName] = id
    end
   end
   end
   return resourceData
   return resourceData
Line 60: Line 85:


-- Finds a resource by its display name.
-- Finds a resource by its display name.
---@param resourceName string display name of the resource
---@param resourceName ResourceName display name
---@return Resource|nil foundResource or nil if not found
---@return Resource|nil foundResource or nil if not found
local function findName(resourceName)
local function findName(resourceName)
Line 73: Line 98:
end
end


-- Renders a link to the given resource's wiki page.
-- Renders a link to the given resource's wiki page.<br>
-- If the specified iconSize is too small, the icon will be ommitted instead of drawn so small as to be unrecognizable.
---@param resource Resource must not be nil
---@param resource Resource must not be nil
---@param size string size of the icon including units, e.g., "x16px"
---@param iconSize string|nil size of the icon including units, e.g., "x16px", or nil for default
---@param needsIcon boolean
---@param needsIcon boolean|nil or nil for default
---@param needsText boolean
---@param needsText boolean|nil or nil for default
---@param targetElementID string|nil #name or #id of the DOM element to link directly to, if any
---@return Wikitext wikitext
---@return Wikitext wikitext
---@private
local function resourceLink(resource, iconSize, needsIcon, needsText, targetElementID)
function Resource.resourceLink(resource, size, needsIcon, needsText)
  iconSize = iconSize or standards.small
  needsIcon = needsIcon or (needsIcon == nil and true)
  needsText = needsText or (needsText == nil and true)
   local wikitext = ""
   local wikitext = ""
   if needsIcon then
  local isValidSize, sizeN = isValidIconSize(iconSize)
     wikitext = wikitext .. classes(icon(resource._iconFilename, size, resource._displayName, resource._displayName), "ats-link-resource")
   if needsIcon and isValidSize and sizeN >= MIN_ICON_SIZE then
    -- Make it a height if it's not already.
    if not iconSize:match("^x") then
      iconSize = "x" .. iconSize
    end
    local directLink = resource._displayName .. (targetElementID and ("" .. targetElementID) or "")
     wikitext = wikitext .. wrapClasses(icon(resource._iconFilename, iconSize, directLink, resource._displayName), "ats-link-resource", sizeN and sizeN < 23 and "ats-flag-small" or nil)
   end
   end
   if needsIcon and needsText then
   if (needsIcon and isValidSize and sizeN >= MIN_ICON_SIZE) and needsText then
     wikitext = wikitext .. NBSP
     wikitext = wikitext .. NBSP
   end
   end
   if needsText then
   if needsText then
     wikitext = wikitext .. link(resource._displayName, resource._displayName)
     wikitext = wikitext .. link(resource._displayName, resource._displayName, targetElementID)
   end
   end
   return nowrap(wikitext)
   return nowrap(wikitext)
Line 95: Line 130:


--#endregion Private Methods
--#endregion Private Methods


--#region Public Methods
--#region Public Methods
-- Checks if the given ID is a valid resource ID.
---@param id ResourceID
---@return boolean
function Resource.isGood(id)
  return data()[id] ~= nil
end
-- Finds a resource's ID by its display name.
---@param displayName ResourceName
---@return ResourceID|nil
function Resource.getID(displayName)
  data()
  return mapNamesToIDs[displayName]
end
-- Gets the specified resource's display name.
---@param id ResourceID
---@return ResourceName
function Resource.getName(id)
  return data()[id]._displayName
end
-- Renders a table of pairs of resource IDs and amounts.
---@param pairsList ResourcePair[] array of pairs of resource IDs and amounts
---@param iconSize string |nil size of the icon including any units, e.g., `20em` or `x16px` or assumes `px` if no units, or nil if not relevant
---@vararg string|nil additional classes to add to the table, if any
---@return Wikitext wikitext
function Resource.tableStack(pairsList, iconSize, ...)
  local classes = {...}
  -- quick check to see if amount column needed
  local needsAmountColumn = false
  for _, pair in ipairs(pairsList) do
    if pair._amount and pair._amount ~= "" then
      needsAmountColumn = true
    end
  end
  local wikitext = "{|" .. ((#classes > 0) and ("class=" .. table.concat(classes, " ")) or "") .. "\n"
  for _, pair in ipairs(pairsList) do
    local resource = data()[pair._id]
    if not resource then return "[Resource table stack ID not found: " .. pair._id .. "]" end
    wikitext = wikitext .. "|-\n"
    wikitext = wikitext .. (needsAmountColumn and ("|style=text-align:right| " .. (pair._amount or "") .. "\n") or "")
    wikitext = wikitext .. "| " .. resourceLink(resource, iconSize or standards.medium, true, true, "#Product") .. "\n"
  end
  return wikitext .. "|}"
end
-- Renders a link to the given resource's wiki page by its ID.
---@param resourceID ResourceID
---@param iconSize string|nil size of the icon including units, e.g., "x16px" or nil for default
---@param needsIcon boolean|nil or nil for default
---@param needsText boolean|nil or nil for default
---@param targetElementID string|nil #name or #id of the DOM element to link directly to, if any
---@return Wikitext wikitext
function Resource.resourceLinkByID(resourceID, iconSize, needsIcon, needsText, targetElementID)
  return resourceLink(data()[resourceID], iconSize, needsIcon, needsText, targetElementID)
end


-- Resource_link template invokes this method from MediaWiki.
-- Resource_link template invokes this method from MediaWiki.
Line 104: Line 199:
   local name = frame.args.name or ""
   local name = frame.args.name or ""
   if name == "" then
   if name == "" then
     return "[Resource Link needs resourse name]"
     return "[Resource Link needs resource name]"
   end
   end
   local size = frame.args.size or ""
   local iconSize = standards[frame.args.icon] or frame.args.icon or ""
   local needsIcon = size ~= "none" and not size:match("0(px)?")
   local needsIcon = iconSize ~= "none"
   if needsIcon and not size:match("^x[0-9]+px$") then
  -- Check if the size string is valid and doesn't represent a negative or too-large number.
     return "[Resource Link size not valid: " .. size .. "]"
   if needsIcon and not isValidIconSize(iconSize) then
     return "[Resource Link size not valid: " .. iconSize .. "]"
   end
   end
   local display = frame.args.display or ""
   local display = frame.args.display or ""
Line 120: Line 216:
     return "[Resource not found: " .. name .. "]"
     return "[Resource not found: " .. name .. "]"
   end
   end
   return Resource.resourceLink(resource, size, needsIcon, needsText)
   return resourceLink(resource, iconSize, needsIcon, needsText)
end
 
function Resource.stack(frame)
  local parent = frame:getParent()
  local args = parent and parent.args or frame.args
  local iconSize = args.icon and (args.icon ~= "" and standards[args.icon] or standards.medium)
  local classes = args.classes or ""
  --@type ResourcePair[]
  local pairsList = {}
  -- handle the indeterminate number of unnamed parameters passed to the template, but skip empty ones
  for _, pair in ipairs(args) do
    if pair ~= "" then
      local amount, name = pair:match("^(%d*)%s*(.+)$")
      local resource = findName(name)
      if not resource then
        return "[Resource not found: " .. name .. "]"
      end
      pairsList[#pairsList + 1] = {_id = resource._id, _amount = amount}
    end
  end
  if #pairsList == 0 then
    return "[Resource stack needs at least one resource]"
  end
  return Resource.tableStack(pairsList, iconSize, classes)
end
end


--#endregion Public Methods
--#endregion Public Methods


return Resource
return Resource

Latest revision as of 19:53, 13 November 2025

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

-- Provides a standard way of interacting with data about goods, resources, and metaresources.
local Resource = {}

-- A single resource with attributes like ID, name, category, etc.
---@class Resource
---@field _id ResourceID Unique ID code of the resource.
---@field _displayName ResourceName The display name in-game.
---@field _description string The in-game descriptive text, including sprite icons, newlines, and escape characters.
---@field _iconFilename Filename The file name of the icon for this resource.
---@field _category CategoryResource Resource type from the info bar.
---@field _isEatable boolean `true` if the resource satisfies s villager's need for food.
---@field _isBurnable boolean `true` if the resource can be burned or sacrificed in a hearth.
---@field _burningTime integer Base number of seconds the item burns, or zero if it is not burnable, before any upgrades.
---@field _tradingSellValue Amber Base value when selling to traders, before any upgrades.
---@field _tradingBuyValue Amber Base cost when buying from traders, before any upgrades.

-- The ID code of a good or resource.
---@alias ResourceID string

-- The display name of a resource.
---@alias ResourceName string

-- Currency in-game.
---@alias Amber number

-- The ID and amount of a good or service.
---@alias ResourcePair {_id: ResourceID, _amount: Amount}

-- The resource category based on the info bar.<br>
-- Metaresources are categorized with trade goods.
---@enum CategoryResource
local CATEGORY_RESOURCE = {
  Building = "Building Materials",
  Consumable = "Consumable Items",
  Crafting = "Crafting Resources",
  Food = "Food",
  Fuel = "Fuel & Exploration",
  Trade = "Trade Goods"
}



--#region Dependencies

local RESOURCES_DATA_FILE = "Module:Resource/resources_data"

local Wiki_Utility = require("Module:Wiki_Utility")
local icon = Wiki_Utility.renderIcon
local nowrap = Wiki_Utility.surroundWithNoWrap
local wrapClasses = Wiki_Utility.surroundWithClasses
local link = Wiki_Utility.renderWikiLink
local NBSP = Wiki_Utility.NBSP
local standards = Wiki_Utility.StandardizedSizes
local isValidIconSize = Wiki_Utility.isValidIconSize

--#endregion Dependencies



--#region Constants

local MIN_ICON_SIZE = 16

--#endregion Constants



--#region Private Members

---@type table<ResourceID, Resource>
local resourceData
---@type table<ResourceName, ResourceID>
local mapNamesToIDs

local function data()
  if not resourceData then
    mapNamesToIDs = {}
    resourceData = mw.loadData(RESOURCES_DATA_FILE)
    for id, resource in pairs(resourceData) do
      mapNamesToIDs[resource._displayName] = id
    end
  end
  return resourceData
end

-- Finds a resource by its display name.
---@param resourceName ResourceName display name
---@return Resource|nil foundResource or nil if not found
local function findName(resourceName)
  local foundResource = nil
  for _, resource in pairs(data()) do
    if resource._displayName == resourceName then
      foundResource = resource
      break
    end
  end
  return foundResource
end

-- Renders a link to the given resource's wiki page.<br>
-- If the specified iconSize is too small, the icon will be ommitted instead of drawn so small as to be unrecognizable.
---@param resource Resource must not be nil
---@param iconSize string|nil size of the icon including units, e.g., "x16px", or nil for default
---@param needsIcon boolean|nil or nil for default
---@param needsText boolean|nil or nil for default
---@param targetElementID string|nil #name or #id of the DOM element to link directly to, if any
---@return Wikitext wikitext
local function resourceLink(resource, iconSize, needsIcon, needsText, targetElementID)
  iconSize = iconSize or standards.small
  needsIcon = needsIcon or (needsIcon == nil and true)
  needsText = needsText or (needsText == nil and true)
  local wikitext = ""
  local isValidSize, sizeN = isValidIconSize(iconSize)
  if needsIcon and isValidSize and sizeN >= MIN_ICON_SIZE then
    -- Make it a height if it's not already.
    if not iconSize:match("^x") then
      iconSize = "x" .. iconSize
    end
    local directLink = resource._displayName .. (targetElementID and ("" .. targetElementID) or "")
    wikitext = wikitext .. wrapClasses(icon(resource._iconFilename, iconSize, directLink, resource._displayName), "ats-link-resource", sizeN and sizeN < 23 and "ats-flag-small" or nil)
  end
  if (needsIcon and isValidSize and sizeN >= MIN_ICON_SIZE) and needsText then
    wikitext = wikitext .. NBSP
  end
  if needsText then
    wikitext = wikitext .. link(resource._displayName, resource._displayName, targetElementID)
  end
  return nowrap(wikitext)
end

--#endregion Private Methods



--#region Public Methods

-- Checks if the given ID is a valid resource ID.
---@param id ResourceID
---@return boolean
function Resource.isGood(id)
  return data()[id] ~= nil
end

-- Finds a resource's ID by its display name.
---@param displayName ResourceName
---@return ResourceID|nil
function Resource.getID(displayName)
  data()
  return mapNamesToIDs[displayName]
end

-- Gets the specified resource's display name.
---@param id ResourceID
---@return ResourceName
function Resource.getName(id)
  return data()[id]._displayName
end

-- Renders a table of pairs of resource IDs and amounts.
---@param pairsList ResourcePair[] array of pairs of resource IDs and amounts
---@param iconSize string |nil size of the icon including any units, e.g., `20em` or `x16px` or assumes `px` if no units, or nil if not relevant
---@vararg string|nil additional classes to add to the table, if any
---@return Wikitext wikitext
function Resource.tableStack(pairsList, iconSize, ...)
  local classes = {...}
  -- quick check to see if amount column needed
  local needsAmountColumn = false
  for _, pair in ipairs(pairsList) do
    if pair._amount and pair._amount ~= "" then
      needsAmountColumn = true
    end
  end
  local wikitext = "{|" .. ((#classes > 0) and ("class=" .. table.concat(classes, " ")) or "") .. "\n"
  for _, pair in ipairs(pairsList) do
    local resource = data()[pair._id]
    if not resource then return "[Resource table stack ID not found: " .. pair._id .. "]" end
    wikitext = wikitext .. "|-\n"
    wikitext = wikitext .. (needsAmountColumn and ("|style=text-align:right| " .. (pair._amount or "") .. "\n") or "")
    wikitext = wikitext .. "| " .. resourceLink(resource, iconSize or standards.medium, true, true, "#Product") .. "\n"
  end
  return wikitext .. "|}"
end

-- Renders a link to the given resource's wiki page by its ID.
---@param resourceID ResourceID
---@param iconSize string|nil size of the icon including units, e.g., "x16px" or nil for default
---@param needsIcon boolean|nil or nil for default
---@param needsText boolean|nil or nil for default
---@param targetElementID string|nil #name or #id of the DOM element to link directly to, if any
---@return Wikitext wikitext
function Resource.resourceLinkByID(resourceID, iconSize, needsIcon, needsText, targetElementID)
  return resourceLink(data()[resourceID], iconSize, needsIcon, needsText, targetElementID)
end

-- Resource_link template invokes this method from MediaWiki.
---@param frame Frame MediaWiki template context
---@return Wikitext wikitext wikitext markup to link to the resource's wiki page
function Resource.link(frame)
  local name = frame.args.name or ""
  if name == "" then
    return "[Resource Link needs resource name]"
  end
  local iconSize = standards[frame.args.icon] or frame.args.icon or ""
  local needsIcon = iconSize ~= "none"
  -- Check if the size string is valid and doesn't represent a negative or too-large number.
  if needsIcon and not isValidIconSize(iconSize) then
    return "[Resource Link size not valid: " .. iconSize .. "]"
  end
  local display = frame.args.display or ""
  if display ~= "" and display ~= "notext" then
    return "[Resource Link display override not supported: " .. display .. "]"
  end
  local needsText = display ~= "notext"
  local resource = findName(name)
  if not resource then
    return "[Resource not found: " .. name .. "]"
  end
  return resourceLink(resource, iconSize, needsIcon, needsText)
end

function Resource.stack(frame)
  local parent = frame:getParent()
  local args = parent and parent.args or frame.args
  local iconSize = args.icon and (args.icon ~= "" and standards[args.icon] or standards.medium)
  local classes = args.classes or ""
  --@type ResourcePair[]
  local pairsList = {}
  -- handle the indeterminate number of unnamed parameters passed to the template, but skip empty ones
  for _, pair in ipairs(args) do
    if pair ~= "" then
      local amount, name = pair:match("^(%d*)%s*(.+)$")
      local resource = findName(name)
      if not resource then
        return "[Resource not found: " .. name .. "]"
      end
      pairsList[#pairsList + 1] = {_id = resource._id, _amount = amount}
    end
  end
  if #pairsList == 0 then
    return "[Resource stack needs at least one resource]"
  end
  return Resource.tableStack(pairsList, iconSize, classes)
end

--#endregion Public Methods



return Resource