Module:Goodbox: Difference between revisions

From Against the Storm Official Wiki
(No longer referencing mw title since it's the title of this module not where it's being invoked from.)
(was calling the wrong species link template. fixed now; icons should be bigger)
 
(27 intermediate revisions by the same user not shown)
Line 1: Line 1:
---
---
-- Module to create 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.
--
---
-- @module Good
--- @module Goodbox
local Goodbox = {}
local Goodbox = {}


---
 
-- Dependencies
 
---
--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_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.
-- Constants
---@param id string the unique ID of the resource
---
---@return string a content string ready for display
local ARG_GOOD_NAME = "name"
local function renderPreferences(id)
local ARG_GOOD_PURPOSE = "purpose"
local ARG_GOOD_SPECIES_PREF = "species_preference"


local INDEX_GOOD_ID = 1
    local frame = mw.getCurrentFrame()
local INDEX_GOOD_NAME = 2
    local goodName = GoodsData.getGoodNameByID(id)
local INDEX_GOOD_DESCRIPTION = 3
local INDEX_GOOD_CATEGORY = 4
local INDEX_GOOD_EATABLE = 5
local INDEX_GOOD_CAN_BE_BURNED = 6
local INDEX_GOOD_BURNING_TIME = 7
local INDEX_GOOD_TRADING_SELL_VALUE = 8
local INDEX_GOOD_TRADING_BUY_VALUE = 9
local INDEX_GOOD_ICON_FILENAME = 10


local TITLE_ID = "ID"
    local concatenatedStrings = ""
local TITLE_CATEGORY = "Inventory Category"
    for speciesID, species in pairs(SpeciesData.species) do
local TITLE_TYPE = "ID Type"
        for _, need in ipairs(species[SpeciesData.NEEDS]) do
local TITLE_EATABLE = "Is eatable?"
local TITLE_BURNABLE = "Is 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 = "^%[(.-)%]"
            if need[SpeciesData.NEED_NAME] == goodName then
local PATTERN_CAPTURE_BEGINNING_AFTER_UNDERSCORE = "^_(.-)%s"
local PATTERN_CAPTURE_FIRST_WORD = "^(%w-)%s"
local PATTERN_FORMAT_TWO_DECIMALS = "%1.2f"


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


local CSS_INFOBOX_INLINE_STYLE_TABLE = {
                -- Separate multiple entries with a space
["float"] = "right",
                if #concatenatedStrings > 1 then
["clear"] = "right",
                    concatenatedStrings = concatenatedStrings .. " "
["width"] = "256px",
                end
["border"] = "1px solid #a2a9b1",
["border-spacing"] = "2px"
}


---
                concatenatedStrings = concatenatedStrings .. speciesString
-- Member variable: Good
            end
--
--
local good


        end
    end


    if #concatenatedStrings < 1 then
        return nil
    end


---
    return concatenatedStrings
-- Creates an html table to display the data in a floating box.
--
-- @param frame the template's calling context
-- @return wiki markup for the box
function Goodbox.infobox(frame)
-- Every good must have a name.
local name = frame.args[ARG_GOOD_NAME]
if not name then
error("You must specify the name of the good in the Goodbox template.")
end
-- Every Good should have a purpose.
local purpose = frame.args[ARG_GOOD_PURPOSE]
-- Specific parameters, may or may not be present.
local speciesPref = frame.args[ARG_GOOD_SPECIES_PREF]
-- Retrieve the actual data for the good.
good = GoodsData.getAllDataForGood(name)
if not good then
return "No good found for infobox."
end
local goodID = good[INDEX_GOOD_ID]
local goodName = good[INDEX_GOOD_NAME]
local goodDescription = good[INDEX_GOOD_DESCRIPTION]
local goodCategory = good[INDEX_GOOD_CATEGORY]
local goodIsEatable = good[INDEX_GOOD_EATABLE]
local goodIsBurnable = good[INDEX_GOOD_CAN_BE_BURNED]
local goodBurnTime = good[INDEX_GOOD_BURNING_TIME]
local goodSellValue = good[INDEX_GOOD_TRADING_SELL_VALUE]
local goodBuyValue = good[INDEX_GOOD_TRADING_BUY_VALUE]
local goodIconFilename = good[INDEX_GOOD_ICON_FILENAME]
local goodType = extractType(goodID)
-- Make the top of the infobox that every item has.
wikiInfobox = mw.html.create("table"):css( CSS_INFOBOX_INLINE_STYLE_TABLE )
-- Title of infobox.
if name then
wikiInfobox:tag("tr"):tag("td"):wikitext(name):done():done():newline()
end
-- Subtitle for category.
if goodCategory then
wikiInfobox:tag("tr"):tag("td"):wikitext(goodCategory):done():done():newline()
end
-- Large icon of infobox.
if goodIconFilename then
wikiInfobox:tag("tr"):tag("td"):wikitext("[[File:" .. goodIconFilename .. ".png]]"):done():done():newline()
wikiInfobox:tag("tr"):tag("td"):wikitext(name .. " icon as seen in-game"):done():done():newline()
end
-- Data fields of the infobox with labels and values
local innerTable = wikiInfobox:tag("tr"):tag("td"):tag("table")
innerTable:newline()
if purpose then
innerTable:tag("tr"):tag("th"):wikitext(toTitleCase(ARG_GOOD_PURPOSE)):done()
:tag("td"):wikitext(purpose):done():done():newline()
end
if speciesPref then
innerTable:tag("tr"):tag("th"):wikitext(toTitleCase(ARG_GOOD_SPECIES_PREF)):done()
:tag("td"):wikitext(speciesPref):done():done():newline()
end
-- Data provided by the CSV
if goodCategory then
innerTable:tag("tr"):tag("th"):wikitext(TITLE_CATEGORY):done()
:tag("td"):wikitext(goodCategory):done():done():newline()
end
-- Eatable is superfluous when the category is food.
--if goodIsEatable then
-- innerTable:tag("tr"):tag("th"):wikitext(TITLE_EATABLE):done()
-- :tag("td"):wikitext(goodIsEatable):done():done():newline()
--end
-- Burnable is superfluous but we can use it to determine whether to show
-- how long the fuel burns.
-- if goodIsBurnable then
-- innerTable:tag("tr"):tag("th"):wikitext(TITLE_BURNABLE):done()
-- :tag("td"):wikitext(goodIsBurnable):done():done():newline()
-- end
if goodIsBurnable == "True" and goodBurnTime then
innerTable:tag("tr"):tag("th"):wikitext(TITLE_BURN_TIME):done()
:tag("td"):wikitext(goodBurnTime):done():done():newline()
end
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"):tag("th"):wikitext(TITLE_BUY_VALUE):done()
:tag("td"):wikitext(toDecimal(goodBuyValue)):done():done():newline()
end
if goodID then
innerTable:tag("tr"):tag("th"):wikitext(TITLE_ID):done()
:tag("td"):wikitext("\"" .. goodID .. "\""):done():done():newline()
end
innerTable:done()
wikiInfobox:done():done():newline()
if goodDescription then
wikiInfobox:tag("tr"):tag("td"):wikitext(goodDescription):done():done():newline()
end
return wikiInfobox
end
end






---
-- Building interface:
-- Extracts the beginning of the ID into the type of good. For example, when
-- getCategory
-- ID = [Vessel] Pottery, the type is Vessel.
-- getCityScore
--
-- getConstructionCosts (as [goodName] = stack size)
-- @param goodID the ID of the good from the data
-- getConstructionTime
-- @return the type of good
-- getDescription
function extractType(goodID)
-- getIcon
-- isMovable
local typecode = goodID:match(PATTERN_CAPTURE_BEGINNING_WITHIN_BRACKETS)
-- getName
-- getNumberOfWorkplaces
if not typecode then
-- getSize (as "X x Y")
typecode = goodID:match(PATTERN_CAPTURE_BEGINNING_AFTER_UNDERSCORE)
-- getStorage
end
local function buildViewParameters(id)
 
if not typecode then
    local frame = mw.getCurrentFrame()
typecode = goodID:match(PATTERN_CAPTURE_FIRST_WORD)
 
end
    local viewParameters = {}
    viewParameters[PARAM_TITLE] = GoodsData.getName(id)
return typecode
    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
end






function toDecimal(value)
---findBoxData
--- Handles data only, does not interface with the view.
return string.format(PATTERN_FORMAT_TWO_DECIMALS, value)
---@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
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.
---
---
-- 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 name = frame.args[ARG_NAME]
    if not name then
local newTitle = title:gsub("(%a)([%w_']*)", function(first, rest)
        error("You must specify the name of the good or resource. Please see the template documentation for how to use the parameters.")
return first:upper() .. rest:lower()
    end
end)
 
    local viewParameters = findBoxData(name)
newTitle, _ = newTitle:gsub("_", " ")
 
    return frame:expandTemplate{ title = ViewTemplate, args = viewParameters }
return newTitle
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