Module:StormforgedController: Difference between revisions

From Against the Storm Official Wiki
m (Updated a few comments and an error-type message.)
(finished refactoring for list and inline display versions)
 
(11 intermediate revisions by the same user not shown)
Line 1: Line 1:
---
---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Serves the Stormforged Cornerstones template by capturing input and using it to control the display of data.
--- Created by onken.
--- DateTime: 4/29/2024 9:27 PM
---
---
---@module StormforgedController
---@module StormforgedController
Line 13: Line 11:
local AltarEffectsData = require("Module:AltarEffectsData")
local AltarEffectsData = require("Module:AltarEffectsData")
local PerksData = require("Module:PerksData")
local PerksData = require("Module:PerksData")
local ControllerUtilities = require("Module:ControllerUtilities")


--endregion
--endregion
Line 20: Line 20:
--region Private constants
--region Private constants


local ARG_ID = "id"
local ARG_ID_LIST = "id"
local ARG_NAME = "name"
local ARG_NAME = "name"
local ARG_DESCRIPTION = "description"
local ARG_DESCRIPTION = "description"
local ARG_UPGRADABLE_ID = "upgrades_from_id"
local ARGS_EXCLUDE_LIST = "exclude"
local ARGS_EXCLUDE_LIST = "exclude"
local ARG_CAPTION = "caption"
local ARG_CAPTION = "caption"
local ARG_SKIP_PRICES = "skip_prices"
local ARG_SKIP_PRICES = "skip_prices"
local ARG_SKIP_PRICES_FLAG_VALUE = "skip"
local ARG_SKIP_FLAG_VALUE = "skip"
local ARG_SHOW_ID = "show_id"
local ARG_SHOW_DESCRIPTION = "show_description"
local ARG_SHOW_REGULAR = "show_regular"
local ARG_SHOW_FLAG_VALUE = "show"
local ARG_DISPLAY_OVERRIDE = "display"
local ARG_DISPLAY_OVERRIDE_OPTION_LIST = "list"
local ARG_DISPLAY_OVERRIDE_OPTION_INLINE = "inline"
local ARG_LIST_TYPE = "list_type"


--local DISPLAY_OPTION_TABLE = "table"
local TEMPLATE_PARAMETER_CAPTION = ARG_CAPTION
 
local TEMPLATE_PARAMETER_ID = ARG_ID_LIST
local TEMPLATE_PARAMETER_ID = "id"
local TEMPLATE_PARAMETER_DESC = ARG_DESCRIPTION
local TEMPLATE_PARAMETER_DESC = "description"
local TEMPLATE_PARAMETER_PRICE_META = "price_in_meta_resources"
local TEMPLATE_PARAMETER_PRICE_META = "price_in_meta_resources"
local TEMPLATE_PARAMETER_PRICE_VILL = "price_in_villagers"
local TEMPLATE_PARAMETER_PRICE_VILLAGER = "price_in_villagers"
local TEMPLATE_PARAMETER_UPGRA_META = "price_for_upgrade_in_meta_resources"
local TEMPLATE_PARAMETER_UPGRADE_META = "price_for_upgrade_in_meta_resources"
local TEMPLATE_PARAMETER_UPGRA_VILL = "price_for_upgrade_in_villagers"
local TEMPLATE_PARAMETER_UPGRADE_VILLAGER = "price_for_upgrade_in_villagers"
local TEMPLATE_PARAMETER_SHOW_ID = ARG_SHOW_ID
local TEMPLATE_PARAMETER_UPGRADE_FROM = "upgrades_from"
local TEMPLATE_PARAMETER_LIST_TYPE = ARG_LIST_TYPE
local TEMPLATE_PARAMETER_ICON_SIZE = "icon_size"
local TEMPLATE_PARAMETER_ICON_SIZE_DEFAULT = "none"


local TEMPLATE_CONTAINER_START = "StormforgedCornerstonesTable"
local TEMPLATE_TABLE_BASE = "StormforgedCornerstonesTable"
local TEMPLATE_CONTAINER_SUFFIX_ROW = "/row"
local TEMPLATE_TABLE_SUFFIX_ROW = "/row"
local TEMPLATE_CONTAINER_SUFFIX_END = "/end"
local TEMPLATE_TABLE_SUFFIX_END = "/end"
local TEMPLATE_CONTAINER_SKIP_PRICES_VARIANT = "/SkipPrices"
local TEMPLATE_TABLE_SKIP_PRICES_VARIANT = "/SkipPrices"
 
local TEMPLATE_LIST_ITEM = "StormforgedCornerstonesList/item"
local ERROR_PREFIX_NAME_NOT_FOUND = "StormforgedController attempted to retrieve the display name of a Stormforged Cornerstones, but it was not found. Double-check that the data is up-to-date and loaded correctly:"
local TEMPLATE_INLINE_LINK = "pl"


--endregion
--endregion
Line 51: Line 64:


local currentFrame = {}
local currentFrame = {}
local altarIDs = {}
local altarIDs = {}
local isIDAlreadyAdded = {}


--endregion
--endregion
Line 59: Line 74:
--region Private methods
--region Private methods


--- Helper comparison function to give to table.sort to sort the perk names alphabetically that correspond to the identifiers of Stormforged Cornerstones.
--- Runs through the list of perk IDs and checks that a name exists for each one. If not, it throws an error identifying the offending ID.
---
---
---@param altarID1 string the identifier of a Stormforged Cornerstone
--- Works from the member variable altarIDs
---@param altarID2 string the identifier of a Stormforged Cornerstone
---@return boolean to sort the corresponding display names alphabetically
local function compareNames(altarID1, altarID2)
 
    local name1 = PerksData.getNameByID(altarID1)
    local name2 = PerksData.getNameByID(altarID2)
 
    if not name1 then
        error(ERROR_PREFIX_NAME_NOT_FOUND .. " id=" .. altarID1 .. ".")
    end
    if not name2 then
        error(ERROR_PREFIX_NAME_NOT_FOUND .. " id=" .. altarID2 .. ".")
 
    end
 
    return name1 < name2
end
 
--- Runs through the list of perk IDs and checks that a name exists for each one. If not, it throws an error identifying the offending ID.
---
---
---@param list table the list of unique identifiers for which to look up names
local function validateNamesFromIDs()
local function validateNames(list)


     for _, id in ipairs(list) do
     for _, id in ipairs(altarIDs) do


       --Validate the names. This should never happen, but this is here to save hours of troubleshooting.
       --Validate the names. This should never happen, but this is here to save hours of troubleshooting.
Line 95: Line 90:
end
end


--- Takes a list of possible IDs and only loads the valid ones (Stormforged ones) to the member variable altardIDs. Skips all ids present in the exclude list.
--- Takes a list of possible IDs and only loads the valid ones (Stormforged ones) to the member variable altarIDs. Skips all ids present in the exclude list.
---
---
---@param newIDs table a list of possible IDs to add to main list
---@param newIDs table a list of possible IDs to add to main list
Line 102: Line 97:


     for _, id in ipairs(newIDs) do
     for _, id in ipairs(newIDs) do
         if AltarEffectsData.isIDValid(id) and not excludeList[id] then
         if AltarEffectsData.isIDValid(id) and not excludeList[id] and not isIDAlreadyAdded[id] then
 
             table.insert(altarIDs, id)
             table.insert(altarIDs, id)
            isIDAlreadyAdded[id] = true
         end
         end
     end
     end
end
end


--- Requests the lists of IDs from both AltarEffects and Perks data models, applies some post-processing, and stores them in the member variable.
--- Requests the desired lists of IDs from both AltarEffects and Perks data models, applies some post-processing, and stores them in the member variable altarIDs.
---
---
---@param selectedID string the id of the cornerstone to include
---@param selectList table list of ids of the cornerstone to include
---@param searchName string search term to look in cornerstone names
---@param searchName string search term to look in cornerstone names
---@param searchDesc string search term to look in cornerstone descriptions
---@param searchDesc string search term to look in cornerstone descriptions
---@param regularID string the id of the cornerstone that gets upgraded to a Stormforged version
---@param excludeList table list of ids
---@param excludeList table list of ids
local function loadIDs(selectedID, searchName, searchDesc, excludeList)
local function loadIDs(selectList, searchName, searchDesc, regularID, excludeList)


     if not altarIDs or #altarIDs < 1 then
     altarIDs = {}
    isIDAlreadyAdded = {}
    local attemptedSearch = false


        altarIDs = {}
    -- Load the selected IDs
        local attemptedSearch = false
    if selectList then
 
        loadStormforgedIDs(selectList, excludeList)
        -- Load the selected ID
        -- It should always be true, but it can be blank.
        if selectedID and selectedID ~= "" then
        if #selectList > 0 then
            loadStormforgedIDs( { selectedID }, excludeList )
             attemptedSearch = true
             attemptedSearch = true
         end
         end
    end


        -- Add any name matches
    -- Add any name matches
        if searchName and searchName ~= "" then
    if searchName and searchName ~= "" then
            local newIDs = PerksData.getAllPerkIDsWhereName(searchName)
        local newIDs = PerksData.getAllPerkIDsWhereName(searchName)
            loadStormforgedIDs(newIDs, excludeList)
        loadStormforgedIDs(newIDs, excludeList)
            attemptedSearch = true
        attemptedSearch = true
        end
    end


        -- Add any description matches
    -- Add any description matches
        if searchDesc and searchDesc ~= "" then
    if searchDesc and searchDesc ~= "" then
            local newIDs = PerksData.getAllPerkIDsWhereDescription(searchDesc)
        local newIDs = PerksData.getAllPerkIDsWhereDescription(searchDesc)
            loadStormforgedIDs(newIDs, excludeList)
        loadStormforgedIDs(newIDs, excludeList)
            attemptedSearch = true
        attemptedSearch = true
        end
    end


        -- Or, if no searching was even attempted (regardless of result), show them all
    -- Add any upgrade matches
        if not attemptedSearch then
    if regularID and regularID ~= "" then
            altarIDs = AltarEffectsData.getAllAltarIDs()
        loadStormforgedIDs({ AltarEffectsData.getUpgradeWhereRegularID(regularID) }, excludeList)
         end
         attemptedSearch = true
     end
     end


     if #altarIDs > 0 then
     -- Or, if no searching was even attempted (regardless of result), show them all.
        -- Remove all IDs that aren't for Stormforged Cornerstones--there were probably extra pulled from PerksData if there were search terms.
    if not attemptedSearch then
         validateNames(altarIDs)
         local newIDs = AltarEffectsData.getAllAltarIDs()
 
         loadStormforgedIDs(newIDs, excludeList)
         --Alphabetize by name
        table.sort(altarIDs, compareNames)
 
     end
     end
end


 
    if #altarIDs > 0 then
---expandExclusions
        -- Remove all IDs that aren't for Stormforged Cornerstones--there will be extra regular Perks pulled from PerksData whenever there are search terms.
---@param exclusions string the list of ids, separated by commas
        validateNamesFromIDs()
---@return table the ids now as keys in the table and each value is `true`
        -- Alphabetize by name.
local function expandExclusions(exclusions)
        table.sort(altarIDs, PerksData.compareNames)
 
    local excludeList = {}
    for idToExclude in string.gmatch(exclusions, "[^,]+") do
        excludeList[idToExclude] = true
     end
     end
    return excludeList
end
end


--- Ensures that if a caption is not defined, there is a suitable fallback in case the author has specified an id, name, or search term to use, so readers know what they're looking at.
--- Ensures that if a caption is not defined, there is a suitable fallback in case the author has specified an id, name, or search term to use, so readers know what they're looking at.
Line 176: Line 165:
--- If no caption was specified and also none of those filtering parameters, then leave it up to the template by returning a blank caption.
--- If no caption was specified and also none of those filtering parameters, then leave it up to the template by returning a blank caption.
---
---
---@param selectedID string the first search parameter, an id
---@param desiredCaption string the caption requested, if any
---@param selectList table list of ids of the cornerstone to include
---@param searchName string the second search parameter, a name
---@param searchName string the second search parameter, a name
---@param searchDesc string the third search parameter, a term in a description
---@param searchDesc string the third search parameter, a term in a description
---@param desiredCaption string the caption requested, if any
---@param regularID string the id of the cornerstone that gets upgraded to a Stormforged version
---@return string an appropriate caption, or blank if it should be default
---@return string an appropriate caption, or blank if it should be default
local function resolveCaption(selectedID, searchName, searchDesc, desiredCaption)
local function resolveCaption(desiredCaption, selectList, searchName, searchDesc, regularID)


    -- No matter what, if the author provided a caption, use that.
     if desiredCaption and desiredCaption ~= "" then
     if desiredCaption and desiredCaption ~= "" then
         return desiredCaption
         return desiredCaption
     end
     end


     if selectedID and selectedID ~= "" then
     -- If no selection, delegate to template.
         return "Stormforged Cornerstone with id '" .. selectedID .. "'"
    local addedToText = false
 
    desiredCaption = "" .. #altarIDs .. " Stormforged Cornerstones"
 
    if selectList and #selectList > 0 then
         if #selectList == 1 then
            desiredCaption = desiredCaption .. " with ID '" .. selectList[1] .. "'"
        else
            desiredCaption = desiredCaption .. " with selected IDs"
        end
        addedToText = true
     end
     end
     -- Do this first since mentioning could also imply name, but not the other way around.
 
     if searchName and searchName ~= "" then
        if addedToText then
            desiredCaption = desiredCaption .. " and"
        end
        desiredCaption = desiredCaption .. " named '" .. searchName .. "'"
        addedToText = true
    end
 
     if searchDesc and searchDesc ~= "" then
     if searchDesc and searchDesc ~= "" then
         return "Stormforged Cornerstones mentioning '" .. searchDesc .. "'"
         if addedToText then
            desiredCaption = desiredCaption .. " and"
        end
        desiredCaption = desiredCaption .. " mentioning '" .. searchDesc .. "'"
        addedToText = true
    end
 
    if regularID and regularID ~= "" then
        if addedToText then
            desiredCaption = desiredCaption .. " and"
        end
        desiredCaption = desiredCaption .. " upgraded from '" .. regularID .. "'"
        addedToText = true
     end
     end
     if searchName and searchName ~= "" then
 
         return "Stormforged Cornerstones named '" .. searchName .. "'"
     if excludeList and #excludeList > 0 then
        desiredCaption = desiredCaption .. " (with exclusions)"
        addedToText = true
    end
 
    if addedToText then
        return desiredCaption
    else
        -- Delegate to the template for the default
         return ""
     end
     end
end
---resolveStartTemplate
---@param isSkippingPrices boolean true if skipping variant should be used
---@return string the template name to use
local function resolveTableStartTemplate(isSkippingPrices)
    local suffixIfSkipping = (isSkippingPrices and TEMPLATE_TABLE_SKIP_PRICES_VARIANT) or ""
    return TEMPLATE_TABLE_BASE .. suffixIfSkipping
end
---resolveRowTemplate
---@param isSkippingPrices boolean true if skipping variant should be used
---@return string the template name to use
local function resolveTableRowTemplate(isSkippingPrices)
    local suffixIfSkipping = (isSkippingPrices and TEMPLATE_TABLE_SKIP_PRICES_VARIANT) or ""
    return TEMPLATE_TABLE_BASE .. TEMPLATE_TABLE_SUFFIX_ROW .. suffixIfSkipping
end
---resolveEndTemplate
---@return string the template name to use
local function resolveTableEndTemplate()


    -- Delegate to the template for the default
     return TEMPLATE_TABLE_BASE .. TEMPLATE_TABLE_SUFFIX_END
     return ""
end
end


Line 205: Line 259:
---
---
---@param desiredCaption string the caption for the table, if any
---@param desiredCaption string the caption for the table, if any
---@param skippingPrice string the flag for skipping prices in the table, if any
---@param isSkippingPrices boolean true if skipping variant should be used
---@return string the wiki markup to display, now longer
---@param argsForTableTemplate table a set of arguments ready to pass through to the called template
local function makeMarkupForTableStart(desiredCaption, skippingPrice)
---@return string the wiki markup assembled by the view (other templates)
local function makeMarkupForTableStart(desiredCaption, isSkippingPrices, argsForTableTemplate)


     local suffixIfSkipping = (skippingPrice == ARG_SKIP_PRICES_FLAG_VALUE and TEMPLATE_CONTAINER_SKIP_PRICES_VARIANT) or ""
     argsForTableTemplate[TEMPLATE_PARAMETER_CAPTION] = desiredCaption
    local startTemplate = TEMPLATE_CONTAINER_START .. suffixIfSkipping


     return currentFrame:expandTemplate{
     return currentFrame:expandTemplate{ title = resolveTableStartTemplate(isSkippingPrices), args = argsForTableTemplate }
        title = startTemplate,
        args = { caption = desiredCaption, skip_prices = skippingPrice } }
end
end


--- Calls the view (other templates) to render a single row of the table with data based on the provided identifier.
--- Calls the view (other templates) to render a single row of the table with data based on the provided identifier.
---
---
---@param skippingPrice string the flag for skipping prices in the table, if any
---@param id string the unique identifier of a Stormforged Cornerstone to use to add data to a new table row
---@param id string the unique identifier of a Stormforged Cornerstone to use to add data to a new table row
---@return string the wiki markup to display, now longer
---@param isSkippingPrices boolean true if skipping variant should be used
local function makeMarkupPerRow(skippingPrice, id)
---@param argsForTableTemplate table a set of arguments ready to pass through to the called template
---@return string the wiki markup assembled by the view (other templates)
local function makeMarkupPerRow(id, isSkippingPrices, argsForTableTemplate)


     local rowTemplate = TEMPLATE_CONTAINER_START .. TEMPLATE_CONTAINER_SUFFIX_ROW
     argsForTableTemplate[TEMPLATE_PARAMETER_ID] = id
    argsForTableTemplate[TEMPLATE_PARAMETER_DESC] = PerksData.getDescriptionByID(id)


     templateParameters = {
     argsForTableTemplate[TEMPLATE_PARAMETER_UPGRADE_FROM] = AltarEffectsData.getRegularVersionEffectID(id)
        [TEMPLATE_PARAMETER_ID] = id,
        [TEMPLATE_PARAMETER_DESC] = PerksData.getDescriptionByID(id)
    }


     if skippingPrice ~= ARG_SKIP_PRICES_FLAG_VALUE then
     -- This is default, but adds a lot of extra template overhead if it's not needed, so only add it if necessary.
 
    if not isSkippingPrices then
         templateParameters[TEMPLATE_PARAMETER_PRICE_META] = AltarEffectsData.getPriceInMetaResources(id)
         argsForTableTemplate[TEMPLATE_PARAMETER_PRICE_META] = AltarEffectsData.getPriceInMetaResources(id)
         templateParameters[TEMPLATE_PARAMETER_PRICE_VILL] = AltarEffectsData.getPriceInVillagers(id)
         argsForTableTemplate[TEMPLATE_PARAMETER_PRICE_VILLAGER] = AltarEffectsData.getPriceInVillagers(id)
         templateParameters[TEMPLATE_PARAMETER_UPGRA_META] = AltarEffectsData.getUpgradePriceInMetaResources(id)
         argsForTableTemplate[TEMPLATE_PARAMETER_UPGRADE_META] = AltarEffectsData.getUpgradePriceInMetaResources(id)
         templateParameters[TEMPLATE_PARAMETER_UPGRA_VILL] = AltarEffectsData.getUpgradePriceInVillagers(id)
         argsForTableTemplate[TEMPLATE_PARAMETER_UPGRADE_VILLAGER] = AltarEffectsData.getUpgradePriceInVillagers(id)
 
    else
        rowTemplate = rowTemplate .. TEMPLATE_CONTAINER_SKIP_PRICES_VARIANT
     end
     end


     return currentFrame:expandTemplate{
     return currentFrame:expandTemplate{ title = resolveTableRowTemplate(isSkippingPrices), args = argsForTableTemplate }
        title = rowTemplate, args = templateParameters }
end
end


--- Handles assembling the rows for all of the IDs in the member variable list altarIDs.
--- Handles assembling the rows for all of the IDs in the member variable list altarIDs.
---
---
---@param skippingPrice string the flag for skipping prices in the table, if any
---@param isSkippingPrices boolean true if skipping variant should be used
---@return string the wiki markup to display, now longer
---@param argsForTableTemplate table a set of arguments ready to pass through to the called template
local function makeMarkupForTableRows(skippingPrice)
---@return string the wiki markup assembled by the view (other templates)
local function makeMarkupForTableRows(isSkippingPrices, argsForTableTemplate)


     local markup = ""
     local markup = ""
     for _, id in ipairs(altarIDs) do
     for _, id in ipairs(altarIDs) do
         markup = markup .. makeMarkupPerRow(skippingPrice, id)
         markup = markup .. makeMarkupPerRow(id, isSkippingPrices, argsForTableTemplate)
     end
     end


Line 262: Line 310:
--- Calls the view (other templates) to render the end of the table.
--- Calls the view (other templates) to render the end of the table.
---
---
---@return string the wiki markup to display, now longer
---@return string the wiki markup assembled by the view (other templates)
local function makeMarkupForTableEnd()
local function makeMarkupForTableEnd()


    local endTemplate = TEMPLATE_CONTAINER_START .. TEMPLATE_CONTAINER_SUFFIX_END
     return currentFrame:expandTemplate{ title = resolveTableEndTemplate(), args = {} }
 
     return currentFrame:expandTemplate{
        title = endTemplate,
        args = {} }
end
end


Line 276: Line 320:
--- Calls several methods to build pieces of the table's markup based on the author's requests.
--- Calls several methods to build pieces of the table's markup based on the author's requests.
---
---
---@param selectedID string the id of the cornerstone to include
---@param searchName string search term to look in cornerstone names
---@param searchDesc string search term to look in cornerstone descriptions
---@param excludeList table list of ids
---@param desiredCaption string override the table caption
---@param desiredCaption string override the table caption
---@param skippingPrice string flag if some columns should be skipped
---@param isSkippingPrices boolean true if skipping variant should be used
---@param argsForTableTemplate table a set of arguments ready to pass through to the called template
---@return string the wiki markup assembled by the view (other templates)
local function renderTable(desiredCaption, isSkippingPrices, argsForTableTemplate)
 
    local startMarkup = makeMarkupForTableStart(desiredCaption, isSkippingPrices, argsForTableTemplate)
    local rowsMarkup =  makeMarkupForTableRows(isSkippingPrices, argsForTableTemplate)
 
    return startMarkup .. rowsMarkup .. makeMarkupForTableEnd()
end
 
---makeMarkupPerListITem
---@param id string the id of the cornerstone to display
---@param listType string the type of list to create
---@param isShowingID boolean true when needing to show the ID column
---@return string the wiki markup assembled by the view (other templates)
local function makeMarkupPerListItem(id, listType, isShowingID)
 
    local templateParameters = {
        [TEMPLATE_PARAMETER_ID] = id,
        [TEMPLATE_PARAMETER_LIST_TYPE] = listType,
        [TEMPLATE_PARAMETER_SHOW_ID] = isShowingID and ARG_SHOW_FLAG_VALUE
    }
 
    return currentFrame:expandTemplate{ title = TEMPLATE_LIST_ITEM, args = templateParameters }
end
 
---makeMarkupForListItems
---@param listType string the type of list to create
---@param isShowingID boolean true when needing to show the ID column
---@return string the wiki markup assembled by the view (other templates)
local function makeMarkupForListItems(listType, isShowingID)
 
    local markup = ""
    for _, id in ipairs(altarIDs) do
        markup = markup .. makeMarkupPerListItem(id, listType, isShowingID)
    end
 
    return markup
end
 
---renderList
---@param listType string the type of list to create
---@param isShowingID boolean true when needing to show the ID column
---@return string the wiki markup assembled by the view (other templates)
---@return string the wiki markup assembled by the view (other templates)
local function renderTable(selectedID, searchName, searchDesc, excludeList, desiredCaption, skippingPrice)
local function renderList(listType, isShowingID)


     loadIDs(selectedID, searchName, searchDesc, excludeList)
     return makeMarkupForListItems(listType, isShowingID)
end


     if #altarIDs < 1 then
---makeMarkupPerLink
         return "No matching Stormforged Cornerstones found."
---@param id string the id of the cornerstone to display
---@param isShowingID boolean true when needing to show the ID column
---@return string the wiki markup assembled by the view (other templates)
local function makeMarkupPerLink(id, isShowingID)
 
    local templateParameters = {
        [TEMPLATE_PARAMETER_ID] = id,
        [TEMPLATE_PARAMETER_ICON_SIZE] = TEMPLATE_PARAMETER_ICON_SIZE_DEFAULT,
    }
 
    local link = currentFrame:expandTemplate{ title = TEMPLATE_INLINE_LINK, args = templateParameters }
 
     if isShowingID then
         return link .. " ('" .. id .. "')"
    else
        return link
    end
end
 
---makeMarkupForInlineLinks
---@param isShowingID boolean true when needing to show the ID column
---@return string the wiki markup assembled by the view (other templates)
local function makeMarkupForInlineLinks(isShowingID)
 
    local markup = ""
    for i, id in ipairs(altarIDs) do
        if i > 1 then
            markup = markup .. ", "
        end
        markup = markup .. makeMarkupPerLink(id, isShowingID)
     end
     end


     desiredCaption = resolveCaption(selectedID, searchName, searchDesc, desiredCaption)
     return markup
end
 
---renderInline
---@param isShowingID boolean true when needing to show the ID column
---@return string the wiki markup assembled by the view (other templates)
local function renderInlineLinks(isShowingID)


     return makeMarkupForTableStart(desiredCaption, skippingPrice) .. makeMarkupForTableRows(skippingPrice) .. "\n" .. makeMarkupForTableEnd()
     return makeMarkupForInlineLinks(isShowingID)
end
end


Line 307: Line 426:
function StormforgedController.main(frame)
function StormforgedController.main(frame)


     -- Extract template parameters
     -- Selection parameters
     local selectedID = frame.args[ARG_ID]
     local selectList = ControllerUtilities.expandCommaSeparatedStringsIntoTable(frame.args[ARG_ID_LIST], false)
    local excludeList = ControllerUtilities.expandCommaSeparatedStringsIntoTable(frame.args[ARGS_EXCLUDE_LIST], true)
     local searchName = frame.args[ARG_NAME]
     local searchName = frame.args[ARG_NAME]
     local searchDesc = frame.args[ARG_DESCRIPTION]
     local searchDesc = frame.args[ARG_DESCRIPTION]
     local exclusions = frame.args[ARGS_EXCLUDE_LIST]
     local regularID = frame.args[ARG_UPGRADABLE_ID]
 
    -- Display parameters
     local desiredCaption = frame.args[ARG_CAPTION]
     local desiredCaption = frame.args[ARG_CAPTION]
     local skippingPrice = frame.args[ARG_SKIP_PRICES]
     local displayOverride = frame.args[ARG_DISPLAY_OVERRIDE]
    local listType = frame.args[ARG_LIST_TYPE]
    local isSkippingPrices = ARG_SKIP_FLAG_VALUE == frame.args[ARG_SKIP_PRICES]
    local isShowingID = ARG_SHOW_FLAG_VALUE == frame.args[ARG_SHOW_ID]
 
    -- For clarity, copy each value instead of reusing the whole args table.
    local argumentsToPassThroughToTable = {
        [ARG_SKIP_PRICES] = frame.args[ARG_SKIP_PRICES],
        [ARG_SHOW_ID] = frame.args[ARG_SHOW_ID],
        [ARG_SHOW_DESCRIPTION] = frame.args[ARG_SHOW_DESCRIPTION],
        [ARG_SHOW_REGULAR] = frame.args[ARG_SHOW_REGULAR]
    }
 
    currentFrame = frame


     --TODO make displayOverride == DISPLAY_OPTION_LIST
     loadIDs(selectList, searchName, searchDesc, regularID, excludeList)
    if #altarIDs < 1 then
        return "No matching Stormforged Cornerstones found."
    end


     local excludeList = {}
     if displayOverride == ARG_DISPLAY_OVERRIDE_OPTION_LIST then
     if exclusions and exclusions ~= "" then
        return renderList(listType, isShowingID)
         excludeList = expandExclusions(exclusions)
    end
     if displayOverride == ARG_DISPLAY_OVERRIDE_OPTION_INLINE then
         return renderInlineLinks(isShowingID)
     end
     end


     currentFrame = frame
     -- Default display is table.
     return renderTable(selectedID, searchName, searchDesc, excludeList, desiredCaption, skippingPrice)
     desiredCaption = resolveCaption(desiredCaption, selectList, searchName, searchDesc, regularID)
 
    return renderTable(desiredCaption, isSkippingPrices, argumentsToPassThroughToTable)
end
end



Latest revision as of 03:38, 5 May 2024

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

---
--- Serves the Stormforged Cornerstones template by capturing input and using it to control the display of data.
---
---@module StormforgedController
local StormforgedController = {}



--region Dependencies

local AltarEffectsData = require("Module:AltarEffectsData")
local PerksData = require("Module:PerksData")

local ControllerUtilities = require("Module:ControllerUtilities")

--endregion



--region Private constants

local ARG_ID_LIST = "id"
local ARG_NAME = "name"
local ARG_DESCRIPTION = "description"
local ARG_UPGRADABLE_ID = "upgrades_from_id"
local ARGS_EXCLUDE_LIST = "exclude"
local ARG_CAPTION = "caption"
local ARG_SKIP_PRICES = "skip_prices"
local ARG_SKIP_FLAG_VALUE = "skip"
local ARG_SHOW_ID = "show_id"
local ARG_SHOW_DESCRIPTION = "show_description"
local ARG_SHOW_REGULAR = "show_regular"
local ARG_SHOW_FLAG_VALUE = "show"
local ARG_DISPLAY_OVERRIDE = "display"
local ARG_DISPLAY_OVERRIDE_OPTION_LIST = "list"
local ARG_DISPLAY_OVERRIDE_OPTION_INLINE = "inline"
local ARG_LIST_TYPE = "list_type"

local TEMPLATE_PARAMETER_CAPTION = ARG_CAPTION
local TEMPLATE_PARAMETER_ID = ARG_ID_LIST
local TEMPLATE_PARAMETER_DESC = ARG_DESCRIPTION
local TEMPLATE_PARAMETER_PRICE_META = "price_in_meta_resources"
local TEMPLATE_PARAMETER_PRICE_VILLAGER = "price_in_villagers"
local TEMPLATE_PARAMETER_UPGRADE_META = "price_for_upgrade_in_meta_resources"
local TEMPLATE_PARAMETER_UPGRADE_VILLAGER = "price_for_upgrade_in_villagers"
local TEMPLATE_PARAMETER_SHOW_ID = ARG_SHOW_ID
local TEMPLATE_PARAMETER_UPGRADE_FROM = "upgrades_from"
local TEMPLATE_PARAMETER_LIST_TYPE = ARG_LIST_TYPE
local TEMPLATE_PARAMETER_ICON_SIZE = "icon_size"
local TEMPLATE_PARAMETER_ICON_SIZE_DEFAULT = "none"

local TEMPLATE_TABLE_BASE = "StormforgedCornerstonesTable"
local TEMPLATE_TABLE_SUFFIX_ROW = "/row"
local TEMPLATE_TABLE_SUFFIX_END = "/end"
local TEMPLATE_TABLE_SKIP_PRICES_VARIANT = "/SkipPrices"
local TEMPLATE_LIST_ITEM = "StormforgedCornerstonesList/item"
local TEMPLATE_INLINE_LINK = "pl"

--endregion



--region Private member variables

local currentFrame = {}

local altarIDs = {}
local isIDAlreadyAdded = {}

--endregion



--region Private methods

--- Runs through the list of perk IDs and checks that a name exists for each one. If not, it throws an error identifying the offending ID.
---
--- Works from the member variable altarIDs
---
local function validateNamesFromIDs()

    for _, id in ipairs(altarIDs) do

       --Validate the names. This should never happen, but this is here to save hours of troubleshooting.
        local name = PerksData.getNameByID(id)
        if not name then
            error(ERROR_PREFIX_NAME_NOT_FOUND .. "id=" .. id)
        end
    end
end

--- Takes a list of possible IDs and only loads the valid ones (Stormforged ones) to the member variable altarIDs. Skips all ids present in the exclude list.
---
---@param newIDs table a list of possible IDs to add to main list
---@param excludeList table where keys are ids to skip and the values are `true`
local function loadStormforgedIDs(newIDs, excludeList)

    for _, id in ipairs(newIDs) do
        if AltarEffectsData.isIDValid(id) and not excludeList[id] and not isIDAlreadyAdded[id] then

            table.insert(altarIDs, id)
            isIDAlreadyAdded[id] = true
        end
    end
end

--- Requests the desired lists of IDs from both AltarEffects and Perks data models, applies some post-processing, and stores them in the member variable altarIDs.
---
---@param selectList table list of ids of the cornerstone to include
---@param searchName string search term to look in cornerstone names
---@param searchDesc string search term to look in cornerstone descriptions
---@param regularID string the id of the cornerstone that gets upgraded to a Stormforged version
---@param excludeList table list of ids
local function loadIDs(selectList, searchName, searchDesc, regularID, excludeList)

    altarIDs = {}
    isIDAlreadyAdded = {}
    local attemptedSearch = false

    -- Load the selected IDs
    if selectList then
        loadStormforgedIDs(selectList, excludeList)
        -- It should always be true, but it can be blank.
        if #selectList > 0 then
            attemptedSearch = true
        end
    end

    -- Add any name matches
    if searchName and searchName ~= "" then
        local newIDs = PerksData.getAllPerkIDsWhereName(searchName)
        loadStormforgedIDs(newIDs, excludeList)
        attemptedSearch = true
    end

    -- Add any description matches
    if searchDesc and searchDesc ~= "" then
        local newIDs = PerksData.getAllPerkIDsWhereDescription(searchDesc)
        loadStormforgedIDs(newIDs, excludeList)
        attemptedSearch = true
    end

    -- Add any upgrade matches
    if regularID and regularID ~= "" then
        loadStormforgedIDs({ AltarEffectsData.getUpgradeWhereRegularID(regularID) }, excludeList)
        attemptedSearch = true
    end

    -- Or, if no searching was even attempted (regardless of result), show them all.
    if not attemptedSearch then
        local newIDs = AltarEffectsData.getAllAltarIDs()
        loadStormforgedIDs(newIDs, excludeList)
    end

    if #altarIDs > 0 then
        -- Remove all IDs that aren't for Stormforged Cornerstones--there will be extra regular Perks pulled from PerksData whenever there are search terms.
        validateNamesFromIDs()
        -- Alphabetize by name.
        table.sort(altarIDs, PerksData.compareNames)
    end
end

--- Ensures that if a caption is not defined, there is a suitable fallback in case the author has specified an id, name, or search term to use, so readers know what they're looking at.
---
--- If no caption was specified and also none of those filtering parameters, then leave it up to the template by returning a blank caption.
---
---@param desiredCaption string the caption requested, if any
---@param selectList table list of ids of the cornerstone to include
---@param searchName string the second search parameter, a name
---@param searchDesc string the third search parameter, a term in a description
---@param regularID string the id of the cornerstone that gets upgraded to a Stormforged version
---@return string an appropriate caption, or blank if it should be default
local function resolveCaption(desiredCaption, selectList, searchName, searchDesc, regularID)

    -- No matter what, if the author provided a caption, use that.
    if desiredCaption and desiredCaption ~= "" then
        return desiredCaption
    end

    -- If no selection, delegate to template.
    local addedToText = false

    desiredCaption = "" .. #altarIDs .. " Stormforged Cornerstones"

    if selectList and #selectList > 0 then
        if #selectList == 1 then
            desiredCaption = desiredCaption .. " with ID '" .. selectList[1] .. "'"
        else
            desiredCaption = desiredCaption .. " with selected IDs"
        end
        addedToText = true
    end

    if searchName and searchName ~= "" then
        if addedToText then
            desiredCaption = desiredCaption .. " and"
        end
        desiredCaption = desiredCaption .. " named '" .. searchName .. "'"
        addedToText = true
    end

    if searchDesc and searchDesc ~= "" then
        if addedToText then
            desiredCaption = desiredCaption .. " and"
        end
        desiredCaption = desiredCaption .. " mentioning '" .. searchDesc .. "'"
        addedToText = true
    end

    if regularID and regularID ~= "" then
        if addedToText then
            desiredCaption = desiredCaption .. " and"
        end
        desiredCaption = desiredCaption .. " upgraded from '" .. regularID .. "'"
        addedToText = true
    end

    if excludeList and #excludeList > 0 then
        desiredCaption = desiredCaption .. " (with exclusions)"
        addedToText = true
    end

    if addedToText then
        return desiredCaption
    else
        -- Delegate to the template for the default
        return ""
    end
end

---resolveStartTemplate
---@param isSkippingPrices boolean true if skipping variant should be used
---@return string the template name to use
local function resolveTableStartTemplate(isSkippingPrices)

    local suffixIfSkipping = (isSkippingPrices and TEMPLATE_TABLE_SKIP_PRICES_VARIANT) or ""

    return TEMPLATE_TABLE_BASE .. suffixIfSkipping
end

---resolveRowTemplate
---@param isSkippingPrices boolean true if skipping variant should be used
---@return string the template name to use
local function resolveTableRowTemplate(isSkippingPrices)

    local suffixIfSkipping = (isSkippingPrices and TEMPLATE_TABLE_SKIP_PRICES_VARIANT) or ""

    return TEMPLATE_TABLE_BASE .. TEMPLATE_TABLE_SUFFIX_ROW .. suffixIfSkipping
end

---resolveEndTemplate
---@return string the template name to use
local function resolveTableEndTemplate()

    return TEMPLATE_TABLE_BASE .. TEMPLATE_TABLE_SUFFIX_END
end

--- Calls the view (other templates) to render the beginning of the table.
---
---@param desiredCaption string the caption for the table, if any
---@param isSkippingPrices boolean true if skipping variant should be used
---@param argsForTableTemplate table a set of arguments ready to pass through to the called template
---@return string the wiki markup assembled by the view (other templates)
local function makeMarkupForTableStart(desiredCaption, isSkippingPrices, argsForTableTemplate)

    argsForTableTemplate[TEMPLATE_PARAMETER_CAPTION] = desiredCaption

    return currentFrame:expandTemplate{ title = resolveTableStartTemplate(isSkippingPrices), args = argsForTableTemplate }
end

--- Calls the view (other templates) to render a single row of the table with data based on the provided identifier.
---
---@param id string the unique identifier of a Stormforged Cornerstone to use to add data to a new table row
---@param isSkippingPrices boolean true if skipping variant should be used
---@param argsForTableTemplate table a set of arguments ready to pass through to the called template
---@return string the wiki markup assembled by the view (other templates)
local function makeMarkupPerRow(id, isSkippingPrices, argsForTableTemplate)

    argsForTableTemplate[TEMPLATE_PARAMETER_ID] = id
    argsForTableTemplate[TEMPLATE_PARAMETER_DESC] = PerksData.getDescriptionByID(id)

    argsForTableTemplate[TEMPLATE_PARAMETER_UPGRADE_FROM] = AltarEffectsData.getRegularVersionEffectID(id)

    -- This is default, but adds a lot of extra template overhead if it's not needed, so only add it if necessary.
    if not isSkippingPrices then
        argsForTableTemplate[TEMPLATE_PARAMETER_PRICE_META] = AltarEffectsData.getPriceInMetaResources(id)
        argsForTableTemplate[TEMPLATE_PARAMETER_PRICE_VILLAGER] = AltarEffectsData.getPriceInVillagers(id)
        argsForTableTemplate[TEMPLATE_PARAMETER_UPGRADE_META] = AltarEffectsData.getUpgradePriceInMetaResources(id)
        argsForTableTemplate[TEMPLATE_PARAMETER_UPGRADE_VILLAGER] = AltarEffectsData.getUpgradePriceInVillagers(id)
    end

    return currentFrame:expandTemplate{ title = resolveTableRowTemplate(isSkippingPrices), args = argsForTableTemplate }
end

--- Handles assembling the rows for all of the IDs in the member variable list altarIDs.
---
---@param isSkippingPrices boolean true if skipping variant should be used
---@param argsForTableTemplate table a set of arguments ready to pass through to the called template
---@return string the wiki markup assembled by the view (other templates)
local function makeMarkupForTableRows(isSkippingPrices, argsForTableTemplate)

    local markup = ""
    for _, id in ipairs(altarIDs) do
        markup = markup .. makeMarkupPerRow(id, isSkippingPrices, argsForTableTemplate)
    end

    return markup
end

--- Calls the view (other templates) to render the end of the table.
---
---@return string the wiki markup assembled by the view (other templates)
local function makeMarkupForTableEnd()

    return currentFrame:expandTemplate{ title = resolveTableEndTemplate(), args = {} }
end

---Calls the view (other templates) to render the content and appends it to the wiki markup string that will replace this controller's template.
---
--- Calls several methods to build pieces of the table's markup based on the author's requests.
---
---@param desiredCaption string override the table caption
---@param isSkippingPrices boolean true if skipping variant should be used
---@param argsForTableTemplate table a set of arguments ready to pass through to the called template
---@return string the wiki markup assembled by the view (other templates)
local function renderTable(desiredCaption, isSkippingPrices, argsForTableTemplate)

    local startMarkup = makeMarkupForTableStart(desiredCaption, isSkippingPrices, argsForTableTemplate)
    local rowsMarkup =  makeMarkupForTableRows(isSkippingPrices, argsForTableTemplate)

    return startMarkup .. rowsMarkup .. makeMarkupForTableEnd()
end

---makeMarkupPerListITem
---@param id string the id of the cornerstone to display
---@param listType string the type of list to create
---@param isShowingID boolean true when needing to show the ID column
---@return string the wiki markup assembled by the view (other templates)
local function makeMarkupPerListItem(id, listType, isShowingID)

    local templateParameters = {
        [TEMPLATE_PARAMETER_ID] = id,
        [TEMPLATE_PARAMETER_LIST_TYPE] = listType,
        [TEMPLATE_PARAMETER_SHOW_ID] = isShowingID and ARG_SHOW_FLAG_VALUE
    }

    return currentFrame:expandTemplate{ title = TEMPLATE_LIST_ITEM, args = templateParameters }
end

---makeMarkupForListItems
---@param listType string the type of list to create
---@param isShowingID boolean true when needing to show the ID column
---@return string the wiki markup assembled by the view (other templates)
local function makeMarkupForListItems(listType, isShowingID)

    local markup = ""
    for _, id in ipairs(altarIDs) do
        markup = markup .. makeMarkupPerListItem(id, listType, isShowingID)
    end

    return markup
end

---renderList
---@param listType string the type of list to create
---@param isShowingID boolean true when needing to show the ID column
---@return string the wiki markup assembled by the view (other templates)
local function renderList(listType, isShowingID)

    return makeMarkupForListItems(listType, isShowingID)
end

---makeMarkupPerLink
---@param id string the id of the cornerstone to display
---@param isShowingID boolean true when needing to show the ID column
---@return string the wiki markup assembled by the view (other templates)
local function makeMarkupPerLink(id, isShowingID)

    local templateParameters = {
        [TEMPLATE_PARAMETER_ID] = id,
        [TEMPLATE_PARAMETER_ICON_SIZE] = TEMPLATE_PARAMETER_ICON_SIZE_DEFAULT,
    }

    local link = currentFrame:expandTemplate{ title = TEMPLATE_INLINE_LINK, args = templateParameters }

    if isShowingID then
        return link .. " ('" .. id .. "')"
    else
        return link
    end
end

---makeMarkupForInlineLinks
---@param isShowingID boolean true when needing to show the ID column
---@return string the wiki markup assembled by the view (other templates)
local function makeMarkupForInlineLinks(isShowingID)

    local markup = ""
    for i, id in ipairs(altarIDs) do
        if i > 1 then
            markup = markup .. ", "
        end
        markup = markup .. makeMarkupPerLink(id, isShowingID)
    end

    return markup
end

---renderInline
---@param isShowingID boolean true when needing to show the ID column
---@return string the wiki markup assembled by the view (other templates)
local function renderInlineLinks(isShowingID)

    return makeMarkupForInlineLinks(isShowingID)
end

--endregion



--region Public methods

---main
---@param frame table the MediaWiki frame provided by the template call
---@return string wiki markup to display
function StormforgedController.main(frame)

    -- Selection parameters
    local selectList = ControllerUtilities.expandCommaSeparatedStringsIntoTable(frame.args[ARG_ID_LIST], false)
    local excludeList = ControllerUtilities.expandCommaSeparatedStringsIntoTable(frame.args[ARGS_EXCLUDE_LIST], true)
    local searchName = frame.args[ARG_NAME]
    local searchDesc = frame.args[ARG_DESCRIPTION]
    local regularID = frame.args[ARG_UPGRADABLE_ID]

    -- Display parameters
    local desiredCaption = frame.args[ARG_CAPTION]
    local displayOverride = frame.args[ARG_DISPLAY_OVERRIDE]
    local listType = frame.args[ARG_LIST_TYPE]
    local isSkippingPrices = ARG_SKIP_FLAG_VALUE == frame.args[ARG_SKIP_PRICES]
    local isShowingID = ARG_SHOW_FLAG_VALUE == frame.args[ARG_SHOW_ID]

    -- For clarity, copy each value instead of reusing the whole args table.
    local argumentsToPassThroughToTable = {
        [ARG_SKIP_PRICES] = frame.args[ARG_SKIP_PRICES],
        [ARG_SHOW_ID] = frame.args[ARG_SHOW_ID],
        [ARG_SHOW_DESCRIPTION] = frame.args[ARG_SHOW_DESCRIPTION],
        [ARG_SHOW_REGULAR] = frame.args[ARG_SHOW_REGULAR]
    }

    currentFrame = frame

    loadIDs(selectList, searchName, searchDesc, regularID, excludeList)
    if #altarIDs < 1 then
        return "No matching Stormforged Cornerstones found."
    end

    if displayOverride == ARG_DISPLAY_OVERRIDE_OPTION_LIST then
        return renderList(listType, isShowingID)
    end
    if displayOverride == ARG_DISPLAY_OVERRIDE_OPTION_INLINE then
        return renderInlineLinks(isShowingID)
    end

    -- Default display is table.
    desiredCaption = resolveCaption(desiredCaption, selectList, searchName, searchDesc, regularID)

    return renderTable(desiredCaption, isSkippingPrices, argumentsToPassThroughToTable)
end

--endregion

return StormforgedController