Module:PerksData: Difference between revisions
From Against the Storm Official Wiki
(added isValid method, removed some verification to allow things to proceed if the user enters something invalid (reserving errors for coding problems)) |
(introducing a new unique key that's a lowercase version of the id for more robust lookup) |
||
(3 intermediate revisions by the same user not shown) | |||
Line 133: | Line 133: | ||
local VALUE_DIVISOR = 10 | local VALUE_DIVISOR = 10 | ||
local INDEX_KEY = "key" | |||
local INDEX_ID = "id" | local INDEX_ID = "id" | ||
local INDEX_NAME = "displayName" | local INDEX_NAME = "displayName" | ||
Line 155: | Line 156: | ||
["legendary"] = PerksData.RARITY_LEGENDARY, | ["legendary"] = PerksData.RARITY_LEGENDARY, | ||
["mythic"] = PerksData.RARITY_MYTHIC | ["mythic"] = PerksData.RARITY_MYTHIC | ||
} | |||
local RARITIES_SORT_ORDER = { | |||
[PerksData.RARITY_UNCOMMON] = 1, | |||
[PerksData.RARITY_RARE] = 2, | |||
[PerksData.RARITY_EPIC] = 3, | |||
[PerksData.RARITY_LEGENDARY] = 4, | |||
[PerksData.RARITY_MYTHIC] = 5, | |||
} | } | ||
Line 190: | Line 199: | ||
local function cleanDescription(originalPerkDescription) | local function cleanDescription(originalPerkDescription) | ||
-- | -- replace any duplicated quotes | ||
originalPerkDescription = originalPerkDescription:gsub(" | originalPerkDescription = originalPerkDescription:gsub("""", '"') | ||
-- | -- remove any leftover quotes | ||
return originalPerkDescription:gsub(" | return originalPerkDescription:gsub(""", '"') | ||
end | end | ||
Line 254: | Line 260: | ||
-- Copy over the content, mapping unhelpful indexes into header keys | -- Copy over the content, mapping unhelpful indexes into header keys | ||
local newPerk = {} | local newPerk = {} | ||
newPerk[INDEX_KEY] = string.lower(originalPerk[INDEX_OLD_ID]) | |||
newPerk[INDEX_ID] = originalPerk[INDEX_OLD_ID] | newPerk[INDEX_ID] = originalPerk[INDEX_OLD_ID] | ||
newPerk[INDEX_NAME] = originalPerk[INDEX_OLD_NAME] | newPerk[INDEX_NAME] = originalPerk[INDEX_OLD_NAME] | ||
Line 263: | Line 270: | ||
newPerk[INDEX_SOURCES] = makeSourcesSubtable(originalPerk[INDEX_OLD_SOURCES]) | newPerk[INDEX_SOURCES] = makeSourcesSubtable(originalPerk[INDEX_OLD_SOURCES]) | ||
newPerksTable[ newPerk[ | newPerksTable[ newPerk[INDEX_KEY] ] = newPerk | ||
table.insert(perksIDs, newPerk[INDEX_ID]) | table.insert(perksIDs, newPerk[INDEX_ID]) | ||
Line 333: | Line 340: | ||
end | end | ||
return perksTable[id] | return perksTable[string.lower(id)] | ||
end | end | ||
Line 491: | Line 498: | ||
---isFromAltarByID | |||
---@param perkID string the ID of the perk | |||
---@return boolean whether the perk comes from the Forsaken Altar | |||
function PerksData.isFromAltarByID(perkID) | |||
return PerksData.isAnySource(perkID, PerksData.SOURCE_ALTAR) | |||
end | |||
Line 679: | Line 696: | ||
local list = {} | local list = {} | ||
for | for _, perk in pairs(perksTable) do | ||
if string.lower(perk[INDEX_NAME]):find(string.lower(searchTerm)) then | if string.lower(perk[INDEX_NAME]):find(string.lower(searchTerm)) then | ||
table.insert(list, | table.insert(list, perk[INDEX_ID]) | ||
end | end | ||
end | end | ||
Line 708: | Line 725: | ||
local list = {} | local list = {} | ||
for | for _, perk in pairs(perksTable) do | ||
if string.lower(perk[INDEX_DESCRIPTION]):find(string.lower(searchTerm)) then | if string.lower(perk[INDEX_DESCRIPTION]):find(string.lower(searchTerm)) then | ||
table.insert(list, | table.insert(list, perk[INDEX_ID]) | ||
end | end | ||
end | end | ||
Line 744: | Line 761: | ||
-- Don't use the internal functions, because we need to return an empty list without any errors if one or the other wasn't provided. | -- Don't use the internal functions, because we need to return an empty list without any errors if one or the other wasn't provided. | ||
local filteredIDs = {} | local filteredIDs = {} | ||
for | for _, perk in pairs(perksTable) do | ||
if not rarityToFilter or "" == rarityToFilter or perk[INDEX_RARITY] == rarityToFilter then | if not rarityToFilter or "" == rarityToFilter or perk[INDEX_RARITY] == rarityToFilter then | ||
for _, perkSource in ipairs(perk[INDEX_SOURCES]) do | for _, perkSource in ipairs(perk[INDEX_SOURCES]) do | ||
if not sourceToFilter or "" == sourceToFilter or perkSource == sourceToFilter then | if not sourceToFilter or "" == sourceToFilter or perkSource == sourceToFilter then | ||
table.insert(filteredIDs, | table.insert(filteredIDs, perk[INDEX_ID]) | ||
break | break | ||
end | end | ||
Line 777: | Line 794: | ||
end | end | ||
local perk1 = perksTable[perkID1] | local perk1 = perksTable[string.lower(perkID1)] | ||
local perk2 = perksTable[perkID2] | local perk2 = perksTable[string.lower(perkID2)] | ||
if not perk1 then | if not perk1 then | ||
error(ERROR_PREFIX_NAME_NOT_FOUND .. " id=" .. perkID1 .. ".") | error(ERROR_PREFIX_NAME_NOT_FOUND .. " id=" .. perkID1 .. ".") | ||
Line 789: | Line 805: | ||
local name1 = perk1[INDEX_NAME] | local name1 = perk1[INDEX_NAME] | ||
local name2 = perk2[INDEX_NAME] | local name2 = perk2[INDEX_NAME] | ||
if not name1 then | if not name1 then | ||
error(ERROR_PREFIX_NAME_NOT_FOUND .. " id=" .. perkID1 .. ".") | error(ERROR_PREFIX_NAME_NOT_FOUND .. " id=" .. perkID1 .. ".") | ||
Line 797: | Line 812: | ||
end | end | ||
return name1 < name2 | -- Sort by name, then rarity, then their ids (so it catches when we have Oil +2 and Oil +1 that are both Uncommon) | ||
if name1 == name2 then | |||
local rarity1 = RARITIES_SORT_ORDER[perk1[INDEX_RARITY]] | |||
local rarity2 = RARITIES_SORT_ORDER[perk2[INDEX_RARITY]] | |||
if rarity1 == rarity2 then | |||
return perk1[INDEX_ID] < perk2[INDEX_ID] | |||
else | |||
return rarity1 < rarity2 | |||
end | |||
else | |||
return name1 < name2 | |||
end | |||
end | end | ||
Latest revision as of 18:59, 10 May 2024
Documentation for this module may be created at Module:PerksData/doc
--- --- Module for compiling perks information from wiki data sources. Restructures --- the flat data tables that are produced from CsvUtils to make them more --- conducive to Lua methods that need to display the information. --- --- The standard way of using this module is a call like the following: --- --- iconFilename = PerksData.getIconFilename(perkID) --- --- This will return a string corresponding to the filename of the icon of the --- perk, including the .png extension. It is preferable to call getter methods --- with the ID of the perk rather than retrieving the entire record for the --- perk, so that your code stays protected from variations in this module. --- The getter methods are all called with the ID of the perk, not their --- plain-language display name, so there are many duplicates. --- --- * plainLanguageName = PerksData.getNameByID(id) --- * longDescription = PerksData.getDescriptionByID(id) --- * iconFilename = PerksData.getIconByID(id) --- * isFromCornerstone = PerksData.isFromCornerstoneByID(id) --- * isFromTrader = PerksData.isFromTraderByID(id) --- * isFromOrder = PerksData.isFromOrderByID(id) --- * isFromEvent = PerksData.isFromEventByID(id) --- * rarityConstant = PerksData.getRarityByID(id) --- * isMatch = PerksData.isRarityMatchByID(id, rarity) --- * isUncommon = PerksData.isRarityUncommonByID(id) --- * isRare = PerksData.isRarityRareByID(id) --- * isEpic = PerksData.isRarityEpicByID(id) --- * isLegendary = PerksData.isRarityLegendaryByID(id) --- * price = PerksData.getPriceByID(id) --- --- Note that display names, or plain language names, are very likely to have --- duplicates in the list of perks. --- --- You can also use this method, although it is more likely to break. --- --- * enumValue = PerksData.getRarity(id) --- --- The rarity constants are public and may be used. You are encouraged to --- refer directly to the constants themselves rather than comparing them to --- what might seem to be appropriate string literals, like `== "Epic"`. For --- example, use this kind of statement instead. --- --- if PerksData.getRarity(perkID) == PerksData.RARITY_EPIC then --- --- Then render your own string literal to display. This guidance may change --- in the future. --- --- There is a method to retrieve the table of sources, but it is advised to --- instead use the getter methods isFromTrader, isFromOrder, and isFromEvent --- instead. If you must get the table, there is a getAll method: --- --- * sourcesTable = PerksData.getAllSources(id) --- --- As a last resort, or if you need to transform the data structure, you can --- call the method getAllDataForPerkByID(id). This returns a whole record from --- the data table. --- --- The data table for perks has the following structure: --- --- perksTable = { --- ["perk1_ID"] = { --- ["id"] = "perk1_ID", --- ["displayName"] = "Plain Language Name", --- ["description"] = "A long string with some HTML entities too.", --- ["iconFilename"] = "Icon_filename.png", --- ["sources"] = { --- [1] = SOURCE_ORDER, for examples --- [2] = SOURCE_RELIC, --- [3] = SOURCE_CORNERSTONE, --- [4] = ... or missing if fewer --- }, --- ["rarity"] = PerksData.RARITY_EPIC, for example --- ["price"] = 99 --- }, --- ["perk2_ID"] = { --- ... --- }, --- ["perk3_ID"] = { --- ... --- }, --- ... --- } --- --- @module PerksData local PerksData = {} local CsvUtils = require("Module:CsvUtils") --region Private member variables --- Main data tables, like this: table[ID] = table containing data for that ID local perksTable --- Supporting table, list of names table[index] = ID. local perksIDs --- Lookup maps. Built once and reused on all subsequent calls within this --- session, like this: table[displayName] = ID local mapNamesToIDs --endregion --region Public enums PerksData.RARITY_UNCOMMON = "Uncommon" PerksData.RARITY_RARE = "Rare" PerksData.RARITY_EPIC = "Epic" PerksData.RARITY_LEGENDARY = "Legendary" PerksData.RARITY_MYTHIC = "Mythic" PerksData.NONE = "None" PerksData.SOURCE_ALTAR = "Altar" PerksData.SOURCE_CORNERSTONE = "Cornerstone" PerksData.SOURCE_ORDER = "Order" PerksData.SOURCE_RELIC = "Relic" PerksData.SOURCE_TRADER = "Trader" --endregion --region Private constants local DATA_TEMPLATE_NAME = "Template:Perks_csv" local VALUE_DIVISOR = 10 local INDEX_KEY = "key" local INDEX_ID = "id" local INDEX_NAME = "displayName" local INDEX_DESCRIPTION = "description" local INDEX_ICON_FILENAME = "iconFilename" local INDEX_SOURCES = "sources" local INDEX_RARITY = "rarity" local INDEX_PRICE = "price" local SWITCH_RARITIES_CHECK = { [PerksData.RARITY_UNCOMMON] = true, [PerksData.RARITY_RARE] = true, [PerksData.RARITY_EPIC] = true, [PerksData.RARITY_LEGENDARY] = true, [PerksData.RARITY_MYTHIC] = true, } local NORMALIZED_TEXT_RARITIES = { ["uncommon"] = PerksData.RARITY_UNCOMMON, ["rare"] = PerksData.RARITY_RARE, ["epic"] = PerksData.RARITY_EPIC, ["legendary"] = PerksData.RARITY_LEGENDARY, ["mythic"] = PerksData.RARITY_MYTHIC } local RARITIES_SORT_ORDER = { [PerksData.RARITY_UNCOMMON] = 1, [PerksData.RARITY_RARE] = 2, [PerksData.RARITY_EPIC] = 3, [PerksData.RARITY_LEGENDARY] = 4, [PerksData.RARITY_MYTHIC] = 5, } local SWITCH_SOURCES_CHECK = { [PerksData.SOURCE_ALTAR] = true, [PerksData.SOURCE_CORNERSTONE] = true, [PerksData.SOURCE_ORDER] = true, [PerksData.SOURCE_RELIC] = true, [PerksData.SOURCE_TRADER] = true, } local NORMALIZED_TEXT_SOURCES = { ["altar"] = PerksData.SOURCE_ALTAR, ["cornerstone"] = PerksData.SOURCE_CORNERSTONE, ["order"] = PerksData.SOURCE_ORDER, ["relic"] = PerksData.SOURCE_RELIC, ["trader"] = PerksData.SOURCE_TRADER, } local ERROR_MESSAGE_ID_MISSING = "PerksData cannot compare IDs when one or more of the IDs were missing." local ERROR_PREFIX_NAME_NOT_FOUND = "PerksData attempted to retrieve the display name of a Perk or Cornerstones, but it was not found. Double-check that the data is up-to-date and loaded correctly:" --endregion --region Private methods --- --- Removes some markup and processing leftovers from the CSV extraction. --- ---@param originalPerkDescription string the perk's description ---@return string the cleaned description local function cleanDescription(originalPerkDescription) -- replace any duplicated quotes originalPerkDescription = originalPerkDescription:gsub("""", '"') -- remove any leftover quotes return originalPerkDescription:gsub(""", '"') end --- --- Creates a new subtable containing the sources of the specified perk. --- ---@param originalPerkSources string list from which to extract sources ---@return table subtable with the sources local function makeSourcesSubtable(originalPerkSources) local sources = {} -- Sub in any HTML entities for actual commas. This was originally critical -- to have the CSV data correctly parsed, but now we have to undo it to -- correctly split up these values. originalPerkSources = originalPerkSources:gsub(",", ",") originalPerkSources = originalPerkSources:gsub(""", "") -- Split on commas. We can use the + here because there are no blanks. for sourceName in originalPerkSources:gmatch("([^,]+)") do sourceName = sourceName:gsub("%s+", "") table.insert(sources, sourceName) end return sources end --- --- Transforms the oldPerksTable returned from CSV processing to be more --- conducive to member functions looking up data. Essentially, we convert the --- text strings into tables with the same base name and an index. --- ---@param originalPerksTable table of CSV-based table, with a header row, data rows ---@return table better structured with IDs as keys local function restructurePerksTable(originalPerksTable) -- A few constants we'll need only within this function. local DATA_ROWS = 2 local INDEX_OLD_ID = 1 local INDEX_OLD_NAME = 2 local INDEX_OLD_DESCRIPTION = 3 local INDEX_OLD_ICON_FILENAME = 4 local INDEX_OLD_SOURCES = 5 local INDEX_OLD_RARITY = 6 local INDEX_OLD_PRICE = 7 perksIDs = {} mapNamesToIDs = {} local newPerksTable = {} for _, originalPerk in ipairs(originalPerksTable[DATA_ROWS]) do -- Copy over the content, mapping unhelpful indexes into header keys local newPerk = {} newPerk[INDEX_KEY] = string.lower(originalPerk[INDEX_OLD_ID]) newPerk[INDEX_ID] = originalPerk[INDEX_OLD_ID] newPerk[INDEX_NAME] = originalPerk[INDEX_OLD_NAME] newPerk[INDEX_DESCRIPTION] = cleanDescription(originalPerk[INDEX_OLD_DESCRIPTION]) newPerk[INDEX_ICON_FILENAME] = originalPerk[INDEX_OLD_ICON_FILENAME] .. ".png" newPerk[INDEX_RARITY] = originalPerk[INDEX_OLD_RARITY] newPerk[INDEX_PRICE] = originalPerk[INDEX_OLD_PRICE] / VALUE_DIVISOR newPerk[INDEX_SOURCES] = makeSourcesSubtable(originalPerk[INDEX_OLD_SOURCES]) newPerksTable[ newPerk[INDEX_KEY] ] = newPerk table.insert(perksIDs, newPerk[INDEX_ID]) -- Also populate the map for looking up IDs with display names if not mapNamesToIDs[ newPerk[INDEX_NAME] ] then mapNamesToIDs[ newPerk[INDEX_NAME] ] = {} end table.insert(mapNamesToIDs[ newPerk[INDEX_NAME] ], newPerk[INDEX_ID]) end return newPerksTable end --- --- Data loader function that uses the utility module and restructures the data --- to be easier to access for invoking methods and later method calls. This --- method is automatically called by all public member functions if the main --- data table has not yet been populated in the current session. local function loadData() -- Utility module retrieves the data as basic, flat lua tables. -- Doesn't use the header lookup. local originalPerksTable, _ = CsvUtils.luaTableFromCSV(CsvUtils.extractCSV(DATA_TEMPLATE_NAME)) -- Now restructure to be more conducive. perksTable = restructurePerksTable(originalPerksTable) end --endregion --region Public methods ---isPerkIDValid ---@param perkID string the unique identifier of a Perk or Cornerstone ---@return boolean true if there is a record with the specified ID function PerksData.isPerkIDValid(perkID) if not perkID or "" == perkID then return false else local record = PerksData.getAllDataForPerkByID(perkID) return record ~= nil end end --- --- Retrieve the whole table of data for the specified perk. Instead of this, --- you should probably be calling the individual getter methods. --- --- Throws an error if called with nil or empty string. Returns nil if the --- specified perk cannot be found. --- --- @param id string ID of the perk --- @return table containing the data for the specified perk with key-value pairs, or nil if not found function PerksData.getAllDataForPerkByID(id) -- At runtime, this should never be nil or empty. if not id or id == "" then error("Parameter is nil or empty for the perk's ID.") end if not perksTable then loadData() end return perksTable[string.lower(id)] end --- --- Get the IDs of all the Perks that are named the same thing. --- --- Returns a table of IDs, since it's unknown how many there could be. --- --- @param displayName string plain-language name of the perk --- @return table of IDs of the Perk found, or an empty table if none found function PerksData.getAllPerkIDsByName(displayName) if not perksTable then loadData() end local foundPerkIDs = mapNamesToIDs[displayName] if not foundPerkIDs or #foundPerkIDs == 0 then return {} end return foundPerkIDs end --- --- Retrieves the display name of the perk specified by its ID. --- --- Returns nil if the perk was not found --- ---@param perkID string the ID of the perk ---@return string the name of the perk function PerksData.getNameByID(perkID) local perk = PerksData.getAllDataForPerkByID(perkID) if not perk then return nil end return perk[INDEX_NAME] end --- --- Retrieves the description of the perk specified by its ID. --- --- Returns nil if the perk was not found --- ---@param perkID string the ID of the perk ---@return string the description of the perk function PerksData.getDescriptionByID(perkID) local perk = PerksData.getAllDataForPerkByID(perkID) if not perk then return nil end return perk[INDEX_DESCRIPTION] end --- --- Retrieves the display name of the perk specified by its ID. --- --- Returns nil if the perk was not found --- ---@param perkID string the ID of the perk ---@return string the icon filename of the perk, including the .png extension function PerksData.getIconByID(perkID) local perk = PerksData.getAllDataForPerkByID(perkID) if not perk then return nil end return perk[INDEX_ICON_FILENAME] end --- --- Retrieves all the sources of the perk specified by its ID. --- --- Returns nil if the perk was not found. --- ---@param perkID string the ID of the perk ---@return table of sources, strings function PerksData.getAllSourcesByID(perkID) local perk = PerksData.getAllDataForPerkByID(perkID) if not perk then return nil end return perk[INDEX_SOURCES] end --- --- Retrieves the number of sources of the perk specified by its ID. --- --- Returns nil if the perk was not found. --- ---@param perkID string the ID of the perk ---@return number of sources for this perk function PerksData.getNumberOfSourcesByID(perkID) local perk = PerksData.getAllDataForPerkByID(perkID) if not perk then return nil end local sourcesTable = perk[INDEX_SOURCES] if not sourcesTable then return nil end return #sourcesTable end --- --- Checks all the sources to see whether the specified source is found. --- ---@param function PerksData.isAnySource(perkID, sourceToCheck) local perk = PerksData.getAllDataForPerkByID(perkID) if not perk then return nil end for _, source in ipairs(perk[INDEX_SOURCES]) do if source == sourceToCheck then return true end end return false end ---isFromAltarByID ---@param perkID string the ID of the perk ---@return boolean whether the perk comes from the Forsaken Altar function PerksData.isFromAltarByID(perkID) return PerksData.isAnySource(perkID, PerksData.SOURCE_ALTAR) end --- --- Retrieves whether the perk specified by its ID comes from a Cornerstone. --- --- Returns nil if the perk was not found. --- ---@param perkID string the ID of the perk ---@return boolean whether the perk comes from a Cornerstone function PerksData.isFromCornerstoneByID(perkID) return PerksData.isAnySource(perkID, PerksData.SOURCE_CORNERSTONE) end --- --- Retrieves whether the perk specified by its ID comes from a Order. --- --- Returns nil if the perk was not found --- ---@param perkID string the ID of the perk ---@return boolean whether the perk comes from a Order function PerksData.isFromOrderByID(perkID) return PerksData.isAnySource(perkID, PerksData.SOURCE_ORDER) end --- --- Retrieves whether the perk specified by its ID comes from an Event. --- --- Returns nil if the perk was not found --- ---@param perkID string the ID of the perk ---@return boolean whether the perk comes from an Event function PerksData.isFromEventByID(perkID) return PerksData.isAnySource(perkID, PerksData.SOURCE_RELIC) end --- --- Retrieves whether the perk specified by its ID comes from a Trader. --- --- Returns nil if the perk was not found --- ---@param perkID string the ID of the perk ---@return boolean whether the perk comes from a Trader function PerksData.isFromTraderByID(perkID) return PerksData.isAnySource(perkID, PerksData.SOURCE_TRADER) end --- --- Retrieves the rarity of the perk specified by its ID. --- --- Returns nil if the perk was not found. --- ---@param perkID string the ID of the perk ---@return string one of the rarity constants function PerksData.getRarityByID(perkID) local perk = PerksData.getAllDataForPerkByID(perkID) if not perk then return nil end return perk[INDEX_RARITY] end --- --- Retrieves whether the perk specified by its ID is of the given rarity. --- --- Returns nil if the perk was not found --- ---@param perkID string the ID of the perk ---@param rarityToCheck string of the rarity constants ---@return boolean whether the perk has the specified rarity function PerksData.isRarityMatchByID(perkID, rarityToCheck) local perk = PerksData.getAllDataForPerkByID(perkID) if not perk then return nil end return perk[INDEX_RARITY] == rarityToCheck end --- --- Retrieves whether the perk specified by its ID is of Uncommon rarity. --- --- Returns nil if the perk was not found --- ---@param perkID string the ID of the perk ---@return boolean whether the perk is Uncommon function PerksData.isRarityUncommonByID(perkID) return PerksData.isRarityMatchByID(perkID, PerksData.RARITY_UNCOMMON) end --- --- Retrieves whether the perk specified by its ID is of Rare rarity. --- --- Returns nil if the perk was not found --- ---@param perkID string the ID of the perk ---@return boolean whether the perk is Rare function PerksData.isRarityRareByID(perkID) return PerksData.isRarityMatchByID(perkID, PerksData.RARITY_RARE) end --- --- Retrieves whether the perk specified by its ID is of Epic rarity. --- --- Returns nil if the perk was not found --- ---@param perkID string the ID of the perk ---@return boolean whether the perk is Epic function PerksData.isRarityEpicByID(perkID) return PerksData.isRarityMatchByID(perkID, PerksData.RARITY_EPIC) end --- --- Retrieves whether the perk specified by its ID is of Legendary rarity. --- --- Returns nil if the perk was not found --- ---@param perkID string the ID of the perk ---@return boolean whether the perk is Legendary function PerksData.isRarityLegendaryByID(perkID) return PerksData.isRarityMatchByID(perkID, PerksData.RARITY_LEGENDARY) end --- --- Retrieves the price of the perk specified by its ID. --- --- Returns nil if the perk was not found. --- ---@param perkID string the ID of the perk ---@return number the price of the perk from a trader function PerksData.getPriceByID(perkID) local perk = PerksData.getAllDataForPerkByID(perkID) if not perk then return nil end return perk[INDEX_PRICE] end --- --- Loop through all perks and return those whose names contain the --- specified search term. --- ---@param searchTerm string terms to search for ---@return table list of perkIDs with a match in their description function PerksData.getAllPerkIDsWhereName(searchTerm) if not searchTerm then return {} end if not perksTable then loadData() end local list = {} for _, perk in pairs(perksTable) do if string.lower(perk[INDEX_NAME]):find(string.lower(searchTerm)) then table.insert(list, perk[INDEX_ID]) end end return list end --- --- Loop through all perks and return those whose descriptions contain the --- specified search term. --- ---@param searchTerm string terms to search for ---@return table list of perkIDs with a match in their description function PerksData.getAllPerkIDsWhereDescription(searchTerm) if not searchTerm then return {} end if not perksTable then loadData() end local list = {} for _, perk in pairs(perksTable) do if string.lower(perk[INDEX_DESCRIPTION]):find(string.lower(searchTerm)) then table.insert(list, perk[INDEX_ID]) end end return list end function PerksData.getAllPerkIDs() if not perksTable then loadData() end return perksIDs end --- Gets all IDs for all perks, but filtered to the specified rarity and source. --- ---@param rarityToFilter string the rarity to filter to, should be normalized already ---@param sourceToFilter string the source to filter to, should be normalized already ---@return table an array of filtered IDs function PerksData.getAllPerkIDsFilteredByRarityAndSource(rarityToFilter, sourceToFilter) if not perksTable then loadData() end -- Don't use the internal functions, because we need to return an empty list without any errors if one or the other wasn't provided. local filteredIDs = {} for _, perk in pairs(perksTable) do if not rarityToFilter or "" == rarityToFilter or perk[INDEX_RARITY] == rarityToFilter then for _, perkSource in ipairs(perk[INDEX_SOURCES]) do if not sourceToFilter or "" == sourceToFilter or perkSource == sourceToFilter then table.insert(filteredIDs, perk[INDEX_ID]) break end end end end return filteredIDs end --- A custom sorting function so that perks can be listed alphabetically via table.sort(yourTable, PerksData.compareNames). Public so controllers and other data models might reuse this. --- --- Throws errors if the IDs do not correspond to perks in the data. --- ---@param perkID1 string the ID of the first perk to compare ---@param perkID2 string the ID of the second perk to compare ---@return boolean true if the first perk should come first in the list function PerksData.compareNames(perkID1, perkID2) if not perkID1 or "" == perkID1 or not perkID2 or "" == perkID2 then error(ERROR_MESSAGE_ID_MISSING) end if not perksTable then loadData() end local perk1 = perksTable[string.lower(perkID1)] local perk2 = perksTable[string.lower(perkID2)] if not perk1 then error(ERROR_PREFIX_NAME_NOT_FOUND .. " id=" .. perkID1 .. ".") end if not perk2 then error(ERROR_PREFIX_NAME_NOT_FOUND .. " id=" .. perkID2 .. ".") end local name1 = perk1[INDEX_NAME] local name2 = perk2[INDEX_NAME] if not name1 then error(ERROR_PREFIX_NAME_NOT_FOUND .. " id=" .. perkID1 .. ".") end if not name2 then error(ERROR_PREFIX_NAME_NOT_FOUND .. " id=" .. perkID2 .. ".") end -- Sort by name, then rarity, then their ids (so it catches when we have Oil +2 and Oil +1 that are both Uncommon) if name1 == name2 then local rarity1 = RARITIES_SORT_ORDER[perk1[INDEX_RARITY]] local rarity2 = RARITIES_SORT_ORDER[perk2[INDEX_RARITY]] if rarity1 == rarity2 then return perk1[INDEX_ID] < perk2[INDEX_ID] else return rarity1 < rarity2 end else return name1 < name2 end end --- Standardizes the display of a string name of one of Perks' rarities. --- --- If the provided rarity is nil or not an accepted term (for example misspelled), this will return nil. --- ---@param rarityString string the descriptive name of the rarity of a perk or cornerstone ---@return string that same rarity, normalized to title case function PerksData.normalizeRarityText(rarityString) if not rarityString or "" == rarityString then return nil end local rarity = string.lower(rarityString) if not rarity or SWITCH_RARITIES_CHECK[rarity] then return nil end return NORMALIZED_TEXT_RARITIES[rarity] end --- Standardizes the display of a string name of one of Perks' sources. --- --- If the provided source is nil or not an accepted term (for example misspelled), this will return nil. --- ---@param sourceString string the descriptive name of the source of a perk or cornerstone ---@return string that same source, normalized to title case function PerksData.normalizeSourceText(sourceString) if not sourceString or "" == sourceString then return nil end local source = string.lower(sourceString) if not source or SWITCH_SOURCES_CHECK[source] then return nil end return NORMALIZED_TEXT_SOURCES[source] end --endregion return PerksData