Module:Goodbox: Difference between revisions

From Against the Storm Official Wiki
m (added error check before displaying anything)
(was calling the wrong species link template. fixed now; icons should be bigger)
 
(19 intermediate revisions by the same user not shown)
Line 1: Line 1:
---
---
--- Module to create an infobox for displaying on a good's wiki page.
--- Retrieves data for the specified resource and sends the data over to the view, another template.
---
--- Shows the facts from the data about the specified good in an easy-to-read
--- table, with a large icon, information, categories, and the flavor text.
---
--- This should be invoked from Template:Goodbox with the parameter of the
--- good whose infobox should be shown, for example, with {{PAGENAME}}.
---
---
--- @module Goodbox
--- @module Goodbox
Line 12: Line 6:




--region Dependencies


local GoodsData = require("Module:GoodsData")
local GoodsData = require("Module:GoodsData")
local SpeciesData = mw.loadData("Module:SpeciesData")
local ControllerUtilities = require("Module:ControllerUtilities")


local ViewTemplate = "Template:Goodbox/view"


--endregion


--region  Private constants


local ARG_GOOD_NAME = "name"
local ARG_GOOD_PURPOSE = "purpose"
local ARG_GOOD_SPECIES_PREF = "species_preference"


local TITLE_ID = "ID"
--region Private constants
local TITLE_CATEGORY = "Inventory Category"
local TITLE_EATABLE = "Eatable"
local TITLE_BURNABLE = "Burnable"
local TITLE_BURN_TIME = "Fuel burn time"
local TITLE_SELL_VALUE = "Value when sold"
local TITLE_BUY_VALUE = "Price when bought"


local PATTERN_CAPTURE_BEGINNING_WITHIN_BRACKETS = "^%[(.-)%]"
local ARG_NAME = "name"
local PATTERN_CAPTURE_BEGINNING_AFTER_UNDERSCORE = "^_(.-)%s"
local PATTERN_CAPTURE_FIRST_WORD = "^(%w-)%s"
local PATTERN_FORMAT_TWO_DECIMALS = "%1.2f"


local NBSP = " "
local PARAM_TITLE = "title"
local PARAM_SUBTITLE = "subtitle"
local PARAM_DESCRIPTION = "description"
local PARAM_ICON_FILENAME = "iconfilename"
local PARAM_PREFERENCES = "preferences"
local PARAM_RECIPES = "recipes"
local PARAM_BURN_TIME = "burntime"
local PARAM_SELL_VALUE = "sellvalue"
local PARAM_SELL_VALUE_AFTER_PRESTIGE_PENALTY = "sellvaluep10"
local PARAM_BUY_VALUE = "buyvalue"
local PARAM_ID = "id"


local WIKIMARKUP_MED_ICON = "|32px|alt="
--endregion


local ICON_FILENAMES_CATEGORIES = {
["Building Materials"] = "Icon_UI_CategoryBuilding.png",
["Consumable Items"] = "Icon_UI_CategoryConsumable.png",
["Crafting Resources"] = "Icon_UI_CategoryCrafting.png",
["Food"] = "Icon UI CategoryFood.png",
["Fuel & Exploration"] = "Icon_UI_CategoryFuel.png",
["Trade Goods"] = "Icon_UI_CategoryTrade.png"
}


local CSS_INFOBOX_INLINE_STYLE_TABLE = {
["float"] = "right",
["clear"] = "right",
["width"] = "256px",
["border"] = "1px solid #a2a9b1",
["border-spacing"] = "2px",
["margin"] = "0.5em 0 0.5em 1em"
}


local CSS_BOTTOM_BORDER_INLINE_STYLE = {
--region Private methods
["border-bottom"] = "1px solid #a2a9b1"
}


--endregion
---renderPreferences takes the ID of a resource and looks up what species prefers it to satisfy one of their needs, calls external view templates to convert those species into usable content strings for display, and returns those strings concatenated together for display by the calling scope.
---@param id string the unique ID of the resource
---@return string a content string ready for display
local function renderPreferences(id)


    local frame = mw.getCurrentFrame()
    local goodName = GoodsData.getGoodNameByID(id)


    local concatenatedStrings = ""
    for speciesID, species in pairs(SpeciesData.species) do
        for _, need in ipairs(species[SpeciesData.NEEDS]) do


--region Private methods
            if need[SpeciesData.NEED_NAME] == goodName then


---
                local speciesString = frame:expandTemplate{
--- Builds using the provided wikiInfobox a subtable to lay out headers and
                    title = "Species_link/view",
--- fields.
                    args = {
---
                        ["iconfilename"] = species[SpeciesData.ICON_FILENAME] .. ".png",
---@param wikiInfobox table mw.html object into which we're building this
                        ["name"] = speciesID,
---@param goodName string the name of the good the infobox is about
                        ["iconsize"] = "medium",
---@param purpose string the parameter provided to the template
                        ["display"] = "notext",
---@param speciesPref string the parameter provided to the template
                    }, }
function makeInnerTable(wikiInfobox, goodName, purpose, speciesPref)


-- Grab the data we'll use to populate this.
                -- Separate multiple entries with a space
local goodID = GoodsData.getGoodID(goodName)
                if #concatenatedStrings > 1 then
local goodCategory = GoodsData.getGoodCategory(goodName)
                    concatenatedStrings = concatenatedStrings .. " "
local goodIsEatable = GoodsData.isGoodEatable(goodName)
                end
local goodIsBurnable = GoodsData.isGoodBurnable(goodName)
local goodBurnTime = GoodsData.getGoodBurnTime(goodName)
local goodSellValue, goodBuyValue = GoodsData.getGoodValue(goodName)
local categoryIcon


-- Start the inner table
                concatenatedStrings = concatenatedStrings .. speciesString
local innerTable = wikiInfobox:tag("tr"):tag("td"):tag("table")
            end
innerTable:newline()


-- Start with the things not provided by the data.
        end
if purpose and purpose ~= "" then
    end
innerTable:tag("tr")
:tag("th"):wikitext(toTitleCase(ARG_GOOD_PURPOSE)):done()
:tag("td"):wikitext(purpose):done()
:done():newline()
end
if speciesPref and speciesPref ~= "" then
innerTable:tag("tr"):css(CSS_BOTTOM_BORDER_INLINE_STYLE)
:tag("th"):wikitext(toTitleCase(ARG_GOOD_SPECIES_PREF)):done()
:tag("td"):wikitext(speciesPref):done()
:done():newline()
end


-- Inventory category (this is the same as the subtitle for now)
    if #concatenatedStrings < 1 then
if goodCategory then
        return nil
categoryIcon = "[[File:" .. ICON_FILENAMES_CATEGORIES[goodCategory] .. WIKIMARKUP_MED_ICON .. goodCategory .. "]]"
    end
innerTable:tag("tr"):css(CSS_BOTTOM_BORDER_INLINE_STYLE)
:tag("th"):wikitext(TITLE_CATEGORY):done()
:tag("td"):wikitext(categoryIcon .. NBSP .. goodCategory):done()
:done():newline()
end
-- Uses
if goodIsEatable then
innerTable:tag("tr")
:tag("th"):wikitext(TITLE_EATABLE):done()
:tag("td"):wikitext(goodIsEatable and "Yes" or "No"):done()
:done():newline()
end
if goodIsBurnable then
innerTable:tag("tr")
:tag("th"):wikitext(TITLE_BURNABLE):done()
:tag("td"):wikitext(goodIsBurnable and "Yes" or "No"):done()
:done():newline()
end
if goodIsBurnable and goodBurnTime then
innerTable:tag("tr"):css(CSS_BOTTOM_BORDER_INLINE_STYLE)
:tag("th"):wikitext(TITLE_BURN_TIME):done()
:tag("td"):wikitext(goodBurnTime):done()
:done():newline()
end
-- Trade values.
if goodSellValue then
innerTable:tag("tr")
:tag("th"):wikitext(TITLE_SELL_VALUE):done()
:tag("td"):wikitext(toDecimal(goodSellValue)):done()
:done():newline()
end
if goodBuyValue then
innerTable:tag("tr"):css(CSS_BOTTOM_BORDER_INLINE_STYLE)
:tag("th"):wikitext(TITLE_BUY_VALUE):done()
:tag("td"):wikitext(toDecimal(goodBuyValue)):done()
:done():newline()
end
-- End with the ID.
if goodID then
innerTable:tag("tr")
:tag("th"):wikitext(TITLE_ID):done()
:tag("td"):wikitext("\'" .. goodID .. "\'"):done()
:done():newline()
end


-- Close the table.
    return concatenatedStrings
innerTable:done()
-- Close the table cell and row the inner table is in.
wikiInfobox:done():done():newline()
end
end


--endregion




-- Building interface:
-- getCategory
-- getCityScore
-- getConstructionCosts (as [goodName] = stack size)
-- getConstructionTime
-- getDescription
-- getIcon
-- isMovable
-- getName
-- getNumberOfWorkplaces
-- getSize (as "X x Y")
-- getStorage
local function buildViewParameters(id)


--region Public methods
    local frame = mw.getCurrentFrame()


---
    local viewParameters = {}
--- Creates an html html and wiki markup to display the data in an infobox.
    viewParameters[PARAM_TITLE] = GoodsData.getName(id)
---
    viewParameters[PARAM_SUBTITLE] = GoodsData.getCategory(id)
--- @param frame table from template's calling context
    viewParameters[PARAM_DESCRIPTION] = ControllerUtilities.findAndReplaceSpriteTagsWithFiles(GoodsData.getDescription(id), frame)
--- @return string of html and wiki markup
    viewParameters[PARAM_ICON_FILENAME] = GoodsData.getIcon(id)
function Goodbox.infobox(frame)
    viewParameters[PARAM_PREFERENCES] = renderPreferences(id)
    viewParameters[PARAM_RECIPES] = frame:expandTemplate{ title = "Recipe", args = { ["product"] = GoodsData.getName(id), ["display"] = "list", }, }
    local burnTime = GoodsData.getBurnTime(id) or 0
    local timeString = string.format( "%02d:%02d", math.floor(burnTime/60), math.fmod(burnTime, 60) )
    viewParameters[PARAM_BURN_TIME] = burnTime ~= 0 and timeString
    viewParameters[PARAM_SELL_VALUE] = GoodsData.getSellValue(id)
    viewParameters[PARAM_SELL_VALUE_AFTER_PRESTIGE_PENALTY] = GoodsData.getSellValueAfterPrestigePenalty(id)
    viewParameters[PARAM_BUY_VALUE] = GoodsData.getBuyValue(id)
    viewParameters[PARAM_ID] = id


-- Every good must have a name.
    -- Augment the subtitle according to preferences. This is hard-code-y, but it's simple and won't break if the categories change, it'll just get skipped.
local goodName = frame.args[ARG_GOOD_NAME]
    if viewParameters[PARAM_PREFERENCES] then
if not goodName or goodName == "" then
        if viewParameters[PARAM_SUBTITLE] == "Food" then
return "You must specify the name of the good. See [[Template:Goodbox]] for examples."
            viewParameters[PARAM_SUBTITLE] = "Food (Complex Food)"
end
        else
            if viewParameters[PARAM_SUBTITLE] == "Consumable Items" then
                viewParameters[PARAM_SUBTITLE] = "Consumable Items (Service Goods)"
            end
        end
    end


-- Use this method not to store the data but to check to see whether the
    return viewParameters
-- provided name was valid.
end
if not GoodsData.getGoodName(goodName) then
return "Infobox can't find the specified good: " .. goodName .. "."
end


-- Additional template parameters, may or may not be present
local purpose = frame.args[ARG_GOOD_PURPOSE]
local speciesPref = frame.args[ARG_GOOD_SPECIES_PREF]


-- Get the data.
local goodDescription = GoodsData.getGoodDescription(goodName)
local goodCategory = GoodsData.getGoodCategory(goodName)
local goodIconFilename = GoodsData.getGoodIcon(goodName)


-- Make the top of the infobox that every item has.
---findBoxData
wikiInfobox = mw.html.create("table"):css(CSS_INFOBOX_INLINE_STYLE_TABLE)
--- Handles data only, does not interface with the view.
-- with a title
---@param name string the name of the resource about which to retrieve data
wikiInfobox:tag("tr")
---@return table the essential data about the resource extracted
:tag("td"):wikitext(goodName):done()
local function findBoxData(name)
:done():newline()
-- and a subtitle, showing the category
if goodCategory then
wikiInfobox:tag("tr")
:tag("td"):wikitext(goodCategory):done()
:done():newline()
end
-- and a large icon.
if goodIconFilename then
wikiInfobox:tag("tr")
:tag("td"):wikitext("[[File:" .. goodIconFilename .. "]]"):done()
:done():newline()
wikiInfobox:tag("tr"):css(CSS_BOTTOM_BORDER_INLINE_STYLE)
:tag("td"):wikitext(goodName .. ", as seen in-game"):done()
:done():newline()
end


makeInnerTable(wikiInfobox, goodName, purpose, speciesPref)
    local id = GoodsData.getGoodID(name)
    if not id then
        error("No resource found with name: " .. name .. ".")
    end


-- Finish with the flavor text.
    local viewParameters = buildViewParameters(id)
if goodDescription then
    if not viewParameters then
wikiInfobox:tag("tr")
        error("No resource found with name: " .. name .. ".")
:tag("td"):wikitext(goodDescription):done()
    end
:done():newline()
end


return wikiInfobox
    return viewParameters
end
end


--endregion


function toDecimal(value)
return string.format(PATTERN_FORMAT_TWO_DECIMALS, value)
end




--region Public methods


---main
--- For calling from Template:Goodbox. After handling the frame, forwards control to the primary method, findBoxData, then calls the view template with the compiled box data. Does not interface with any data modules.
---
---
-- Capitalizes the first character of each word.
---@param frame table the calling template's context
--
---@return string wiki markup, constructed with the template view
-- @param title the title to capitalize
function Goodbox.main(frame)
-- @return the title once capitalized
function toTitleCase(title)


local newTitle = title:gsub("(%a)([%w_']*)", function(first, rest)
    local name = frame.args[ARG_NAME]
return first:upper() .. rest:lower()
    if not name then
end)
        error("You must specify the name of the good or resource. Please see the template documentation for how to use the parameters.")
    end


newTitle, _ = newTitle:gsub("_", " ")
    local viewParameters = findBoxData(name)


return newTitle
    return frame:expandTemplate{ title = ViewTemplate, args = viewParameters }
end
end


 
--endregion


return Goodbox
return Goodbox

Latest revision as of 20:14, 23 October 2024

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

---
--- Retrieves data for the specified resource and sends the data over to the view, another template.
---
--- @module Goodbox
local Goodbox = {}



--region Dependencies

local GoodsData = require("Module:GoodsData")
local SpeciesData = mw.loadData("Module:SpeciesData")

local ControllerUtilities = require("Module:ControllerUtilities")

local ViewTemplate = "Template:Goodbox/view"

--endregion



--region Private constants

local ARG_NAME = "name"

local PARAM_TITLE = "title"
local PARAM_SUBTITLE = "subtitle"
local PARAM_DESCRIPTION = "description"
local PARAM_ICON_FILENAME = "iconfilename"
local PARAM_PREFERENCES = "preferences"
local PARAM_RECIPES = "recipes"
local PARAM_BURN_TIME = "burntime"
local PARAM_SELL_VALUE = "sellvalue"
local PARAM_SELL_VALUE_AFTER_PRESTIGE_PENALTY = "sellvaluep10"
local PARAM_BUY_VALUE = "buyvalue"
local PARAM_ID = "id"

--endregion



--region Private methods

---renderPreferences takes the ID of a resource and looks up what species prefers it to satisfy one of their needs, calls external view templates to convert those species into usable content strings for display, and returns those strings concatenated together for display by the calling scope.
---@param id string the unique ID of the resource
---@return string a content string ready for display
local function renderPreferences(id)

    local frame = mw.getCurrentFrame()
    local goodName = GoodsData.getGoodNameByID(id)

    local concatenatedStrings = ""
    for speciesID, species in pairs(SpeciesData.species) do
        for _, need in ipairs(species[SpeciesData.NEEDS]) do

            if need[SpeciesData.NEED_NAME] == goodName then

                local speciesString = frame:expandTemplate{
                    title = "Species_link/view",
                    args = {
                        ["iconfilename"] = species[SpeciesData.ICON_FILENAME] .. ".png",
                        ["name"] = speciesID,
                        ["iconsize"] = "medium",
                        ["display"] = "notext",
                    }, }

                -- Separate multiple entries with a space
                if #concatenatedStrings > 1 then
                    concatenatedStrings = concatenatedStrings .. " "
                end

                concatenatedStrings = concatenatedStrings .. speciesString
            end

        end
    end

    if #concatenatedStrings < 1 then
        return nil
    end

    return concatenatedStrings
end



-- Building interface:
-- getCategory
-- getCityScore
-- getConstructionCosts (as [goodName] = stack size)
-- getConstructionTime
-- getDescription
-- getIcon
-- isMovable
-- getName
-- getNumberOfWorkplaces
-- getSize (as "X x Y")
-- getStorage
local function buildViewParameters(id)

    local frame = mw.getCurrentFrame()

    local viewParameters = {}
    viewParameters[PARAM_TITLE] = GoodsData.getName(id)
    viewParameters[PARAM_SUBTITLE] = GoodsData.getCategory(id)
    viewParameters[PARAM_DESCRIPTION] = ControllerUtilities.findAndReplaceSpriteTagsWithFiles(GoodsData.getDescription(id), frame)
    viewParameters[PARAM_ICON_FILENAME] = GoodsData.getIcon(id)
    viewParameters[PARAM_PREFERENCES] = renderPreferences(id)
    viewParameters[PARAM_RECIPES] = frame:expandTemplate{ title = "Recipe", args = { ["product"] = GoodsData.getName(id), ["display"] = "list", }, }
    local burnTime = GoodsData.getBurnTime(id) or 0
    local timeString = string.format( "%02d:%02d", math.floor(burnTime/60), math.fmod(burnTime, 60) )
    viewParameters[PARAM_BURN_TIME] = burnTime ~= 0 and timeString
    viewParameters[PARAM_SELL_VALUE] = GoodsData.getSellValue(id)
    viewParameters[PARAM_SELL_VALUE_AFTER_PRESTIGE_PENALTY] = GoodsData.getSellValueAfterPrestigePenalty(id)
    viewParameters[PARAM_BUY_VALUE] = GoodsData.getBuyValue(id)
    viewParameters[PARAM_ID] = id

    -- Augment the subtitle according to preferences. This is hard-code-y, but it's simple and won't break if the categories change, it'll just get skipped.
    if viewParameters[PARAM_PREFERENCES] then
        if viewParameters[PARAM_SUBTITLE] == "Food" then
            viewParameters[PARAM_SUBTITLE] = "Food (Complex Food)"
        else
            if viewParameters[PARAM_SUBTITLE] == "Consumable Items" then
                viewParameters[PARAM_SUBTITLE] = "Consumable Items (Service Goods)"
            end
        end
    end

    return viewParameters
end



---findBoxData
--- Handles data only, does not interface with the view.
---@param name string the name of the resource about which to retrieve data
---@return table the essential data about the resource extracted
local function findBoxData(name)

    local id = GoodsData.getGoodID(name)
    if not id then
        error("No resource found with name: " .. name .. ".")
    end

    local viewParameters = buildViewParameters(id)
    if not viewParameters then
        error("No resource found with name: " .. name .. ".")
    end

    return viewParameters
end

--endregion



--region Public methods

---main
--- For calling from Template:Goodbox. After handling the frame, forwards control to the primary method, findBoxData, then calls the view template with the compiled box data. Does not interface with any data modules.
---
---@param frame table the calling template's context
---@return string wiki markup, constructed with the template view
function Goodbox.main(frame)

    local name = frame.args[ARG_NAME]
    if not name then
        error("You must specify the name of the good or resource. Please see the template documentation for how to use the parameters.")
    end

    local viewParameters = findBoxData(name)

    return frame:expandTemplate{ title = ViewTemplate, args = viewParameters }
end

--endregion

return Goodbox