WARFRAME Wiki
WARFRAME Wiki
(Using Tooltip.getTip() instead of Tooltip.getIcon() to avoid lazy loading of images (looks funny to see a butch of relic icons pop up as you scroll down page))
m (Undo revision 2207192 by Cephalon Scientia (talk) nvm misunderstood what the function did)
Tag: Undo
Line 188: Line 188:
 
if(dropRarity ~= nil) then
 
if(dropRarity ~= nil) then
 
local relicText = relic.Tier.." "..relic.Name
 
local relicText = relic.Tier.." "..relic.Name
local relicString = Tooltip.getTip(relicText, "Void").." "..dropRarity
+
local relicString = Tooltip.getIcon(relicText, "Void").." "..dropRarity
 
if(relic.Vaulted ~= nil) then
 
if(relic.Vaulted ~= nil) then
 
relicString = relicString.." ([[Prime Vault|V]])"
 
relicString = relicString.." ([[Prime Vault|V]])"
Line 464: Line 464:
 
if dropRarity ~= nil then
 
if dropRarity ~= nil then
 
local relicText = drop.Tier.." "..drop.Name
 
local relicText = drop.Tier.." "..drop.Name
local relicString = Tooltip.getTip(relicText, "Void").." "..dropRarity
+
local relicString = Tooltip.getIcon(relicText, "Void").." "..dropRarity
 
if drop.Vaulted then
 
if drop.Vaulted then
 
relicString = relicString.." ([[Prime Vault|V]])"
 
relicString = relicString.." ([[Prime Vault|V]])"

Revision as of 01:55, 14 June 2021


Void retrieves drop table data of opening Void Relics. Originally built when the Void rewarded Primed parts and blueprints.

On this Wiki, Void is used in:


Usage

Module

local Void = require('Module:Void')

local function func(input)
    return Void.getRelicTotal()
end

Product Backlog

Name Type Status Priority Assignee Description Date Issued Last Update
Data validation Dev New Low

Add validation functions for RelicData and PrimeData in Module:Void/data/validate. This is to ensure the accuracy of our data based on known patterns and/or rules.

19:21, 30 June 2021 (UTC)
Refactor and clean up Dev Active Medium

Remove unused functions and perform some refactoring, especially with triple loop situations (though not entirely clear how to optimize table accesses since when we are building wikitables, we loop through all the data)

04:28, 16 October 2021 (UTC) update: Removed p._getItemName() which was used to convert item names from M:Void/data from SCREAMING UPPER CASE to Title Case. This is not needed as all names are stored in Title Case to avoid extra function calls.

21:36, 22 June 2021 (UTC) 04:28, 16 October 2021 (UTC)

Finished Issues

Name Type Status Priority Assignee Description Date Issued Completion Date
Luafy Template:RelicInfobox, Template:RelicTable, and Template:RelicTable/Check Dev & Refactor Active Medium User:Cephalon Scientia

Luafy the contents of these templates which will also remove the need for p.getRelicDrop(frame), p._getItemIconForDrop(itemName), and p.getDucatValue(frame)

02:15, 24 June 2021 (UTC) 18:55, 30 June 2021 (UTC)
Ducat values in /data Dev Completed Medium User:Cephalon Scientia

Add a new "DucatValue" key to each prime part in the PrimeData subtable of M:Void/data.

3:56, 23 June 2021 (UTC) 02:11, 24 June 2021 (UTC)
Documentation Documentation Completed High User:Cephalon Scientia

Add LuaDoc-style documentation for all functions.

21:36, 22 June 2021 (UTC)
Merging Module:VoidByReward to Module:Void Dev Completed Medium User:Cephalon Scientia

Migrating all functions from M:VoidByReward to this module.

21:36, 22 June 2021 (UTC)
Relic infobox Dev Active Medium User:Cephalon Scientia

Luafy the contents of Template:RelicInfobox which use parser functions to render the infobox and message boxes.

21:36, 22 June 2021 (UTC)
Introduced and vaulted keys Dev Completed High User:Cephalon Scientia

Adding Introduced and Vaulted key corresponding to the versions that they were first introduced and last vaulted for each relic table entry.

21:36, 22 June 2021 (UTC)
Access relic data by item and part names Dev Completed High User:Cephalon Scientia

Adding a new subtable part of /data that uses item and part names to get the relics that they are dropped from; think of it as the 'inverse' of the original relic data table.

21:36, 22 June 2021 (UTC)

Documentation

Package items

void.isVaulted(relicName) (function)
Checks if a relic is vaulted or not.
Parameter: relicName Relic name (string)
Returns: True if relic is vaulted, false otherwise (boolean)
void.getItemName(itemStr) (function)
Converts item names to their proper Prime names. For example, 'LATRON' becomes 'Latron Prime'.
Parameter: itemStr Item name (string)
Returns: Prime name (string)
void.getPartName(partStr, keepBlueprint) (function)
Converts Prime part names in data to proper casing.
Parameters:
  • partStr Item name (string)
  • keepBlueprint If true, adds 'blueprint' to end of result, false otherwise. Default value is true. (boolean; optional)
Returns: Name of Prime part (string)
void.getRelic(tier, name) (function)
Gets the relic data
Parameters:
  • tier Relic tier (e.g. 'Axi') (string)
  • name Relic name (e.g. 'A1') (string)
Returns: A table containing relic's drops and vaulted info. Returns nil if no data for that relic is found. (table or nil)
void.getRelicDropRarity(relic, item, part) (function)
Returns the rarity if a relic drops a part.
Parameters:
  • relic Relic table entry (table)
  • item Name of item (string)
  • part Name of item part (string)
Returns: The rarity of drop; returns nil if relic does not drop specified item (string or nil)
void.getPartIconForDrop(drop) (function)
Returns the part icon for a drop. For example, "Braton Prime Barrel" returns Primebarrel
Parameter: drop Item name (string)
Returns: Icon in the form of a wikitext link (string)
void.getItemIconForDrop(drop) (function)
Returns the item icon for a drop. For example, "Braton Prime Barrel" returns File:PrimeBraton.png
Parameter: drop Item name (string)
Returns: Icon in the form of a wikitext link (string)
void.item(item_type, item_part, relic_tier) (function)
Returns the relics in which the item is dropped from.
Parameters:
  • item_type (string)
  • item_part (string)
  • relic_tier (string)
Returns: (string)
void._item(item_type, item_part, relic_tier) (function)
Returns the relics in which the item is dropped from.
Parameters:
  • item_type (string)
  • item_part (string)
  • relic_tier (string)
Returns: (string)
void.getRelicDrop(relicName, rarity, number) (function)
Gets the drop table of a relic.
Parameters:
  • relicName (string)
  • rarity (string)
  • number (number)
Returns: (string)
void.getRelicTotal() (function)
Gets the total number of relics in the game.
Parameter: *frame.args Options for what relic types to include are "unvaulted", "vaulted", "baro", or nil (string; optional)
Returns: The total count of all relics (number)
void.getDucatValue(itemName, partName) (function)
Gets the ducat value of a Prime part or blueprint.
Parameters:
  • itemName Prime item name (string)
  • partName Part name (string)
Returns: The ducat value of that Prime part/blueprint (number)
void._getDucatValue(itemName, partName, data) (function)
Gets the ducat value of a Prime part or blueprint.
Parameters:
  • itemName Prime item name (string)
  • partName Part name (string)
  • data Relic data (table)
Returns: The ducat value of that Prime part/blueprint (number)
void.getTotalDucats(tierName) (function)
Gets the total ducat value of all Prime parts and blueprints.
Parameter: tierName Tier name if want to filter by a specific relic tier (string; optional)
Returns: The total ducat value (number)
void.ducatRelicList(tierName, listMode) (function)
Builds a table of all Prime parts, the relics they are in, and their ducat value as seen https://warframe. fandom.com/wiki/Ducats/Prices/Lith
Parameters:
  • tierName Tier name if want to filter by a specific relic tier (string; optional)
  • listMode If nil, displays only unvaulted item; if "All", displays all items, including vaulted ones (string; optional)
Returns: Wikitext of table (string)
void.buildRelicInfobox(frame) (function)
Builds relic infobox. TODO: Figure out errors with _getVersionLink() and String.trim()
Parameter: frame (table)
Returns: Wikitext of infobox (string)

Created with Docbunto

See Also

Code


--- '''Void''' retrieves drop table data of opening [[Void Relic]]s. Originally
--  built when the [[Void]] rewarded [[Prime]]d parts and blueprints.
--  
--  On this Wiki, Void is used in:
--  * [[Module:Acquisition]]
--  
--  
--  @module     void
--  @alias      p
--  @author     [[User:ChickenBar|ChickenBar]]
--  @attribution    [[User:Flaicher|Flaicher]]
--  @attribution    [[User:FINNER|FINNER]]
--  @attribution    [[User:Falterfire|Falterfire]]
--  @attribution    [[User:Gigamicro|Gigamicro]]
--  @attribution    [[User:Synthtech|Synthtech]]
--  @image      VoidProjectionsIronD.png
--  @require    [[Module:Void/data]]
--  @require    [[Module:Icon]]
--  @require    [[Module:Shared]]
--  @require    [[Module:String]]
--  @require    [[Module:Math]]
--  @release    stable
--  <nowiki>

-- TODO: Replace any mentions of string functions from M:Shared to those from M:String

local p = {}

local VoidData = mw.loadData [[Module:Void/data]]
local Icon = require [[Module:Icon]]
local Shared = require [[Module:Shared]]
local String = require [[Module:String]]
local Math = require [[Module:Math]]
local Version = require [[Module:Version]]
local Tooltip = require [[Module:Tooltips]]

local tooltipStart = '<span class="tooltip" data-param="'
local tooltipCenter = '" data-param2="Void">'
local tooltipEnd = '</span>'

--- Checks if a relic is vaulted or not.
--  @function       p.isVaulted
--  @param          {string} relicName Relic name
--  @return         {boolean} True if relic is vaulted, false otherwise
function p.isVaulted(relicName)
    local itr = string.gmatch(relicName, '([^ ]+)')
    local tier = itr[1]
    local name = itr[2]
    return p.getRelic(tier, name)['Vaulted'] ~= nil
end

--- Converts item names to their proper Prime names. 
--  For example, 'LATRON' becomes 'Latron Prime'.
--  @function       p.getItemName
--  @param          {string} itemStr Item name
--  @return         {string} Prime name
function p.getItemName(itemStr)
    local caseItem = String.titleCase(itemStr)
    if (itemStr ~= "FORMA") then
        caseItem = caseItem.." Prime"
    end
    return caseItem
end

--- Converts Prime part names in data to proper casing.
--  @function       p.getPartName
--  @param          {string} partStr Item name
--  @param[opt]     {boolean} keepBlueprint If true, adds 'blueprint' to end of result, false otherwise. 
--                                          Default value is true.
--  @return         {string} Name of Prime part 
function p.getPartName(partStr, keepBlueprint)
    --User:Falterfire 6/19/2018:
    --      New parameter to remove ' Blueprint' if wanted
    --      IE returns 'Neuroptics' instead of 'Neuroptics Blueprint'
    if keepBlueprint == nil then keepBlueprint = true end
    local result = String.titleCase(partStr)
    if not keepBlueprint and String.contains(result, ' Blueprint') then
        result = string.gsub(result, ' Blueprint', '')
    end
    return result
end

--- Gets the relic data
--  @function       p.getRelic
--  @param          {string} tier Relic tier (e.g. 'Axi')
--  @param          {string} name Relic name (e.g. 'A1')
--  @return         {table or nil} A table containing relic's drops and vaulted info. Returns nil if no data for that relic is found.
function p.getRelic(tier, name)
    return VoidData['RelicNames'][('%s %s'):format(tier, name)]
end

--- Returns the rarity if a relic drops a part.
--  @function       p.getRelicDropRarity
--  @param          {table} relic Relic table entry
--  @param          {string} item Name of item
--  @param          {string} part Name of item part
--  @return         {string or nil} The rarity of drop; returns nil if relic does not drop specified item
function p.getRelicDropRarity(relic, item, part)
    for i, drop in pairs(relic['Drops']) do
        if (drop['Item'] == item and drop['Part'] == part) then
            return drop.Rarity
        end
    end
    return nil
end

--- Returns the part icon for a drop.
--  For example, "Braton Prime Barrel" returns [[File:Primebarrel.png|38px]]
--  @function       p.getPartIconForDrop
--  @param          {string} drop Item name
--  @return         {string} Icon in the form of a wikitext link
function p.getPartIconForDrop(drop)
    local iName = p.getItemName(drop.Item)
    local pName = p.getPartName(drop.Part)
    local iconSize =''
    local primeToggle = 'Prime '
    if iName == 'Forma' then
        iconSize = '43'
    else
        iconSize = '54'
    end
    
    if iName == 'Odonata Prime' then
        if pName == 'Harness Blueprint' or pName == 'Systems Blueprint' or pName == 'Wings Blueprint' then
            primeToggle = 'Archwing '
        end
    elseif pName == 'Carapace' or pName == 'Cerebrum' or pName == 'Systems' then
        primeToggle = ''
    end
    
    local icon =''
    if(pName == 'Blueprint') then
        icon = Icon._Prime(String.titleCase(drop.Item), nil, iconSize)
    elseif iName == 'Kavasa Prime' then
        icon = Icon._Prime('Kavasa', nil,iconSize)
    else
        icon = Icon._Item(primeToggle..pName, "", iconSize)
    end
    
    return icon
end

--- Returns the item icon for a drop.
--  For example, "Braton Prime Barrel" returns [[File:PrimeBraton.png|38px]]
--  @function       p.getItemIconForDrop
--  @param          {string} drop Item name
--  @return         {string} Icon in the form of a wikitext link
function p.getItemIconForDrop(drop)
    local iName = p.getItemName(drop.Item)
    local pName = p.getPartName(drop.Part)
    local iconSize = '38'
    
    return Icon._Prime(String.titleCase(drop.Item), nil, iconSize)
end

--- Returns the relics in which the item is dropped from.
--  @function       p.item
--  @param          {string} item_type
--  @param          {string} item_part
--  @param          {string} relic_tier
--  @return         {string}
function p.item(frame)
    local item_type = frame.args[1]
    local item_part = frame.args[2]
    local relic_tier = frame.args[3]
    
    return p._item(item_type, item_part, relic_tier)
end

--- Returns the relics in which the item is dropped from.
--  @function       p._item
--  @param          {string} item_type
--  @param          {string} item_part
--  @param          {string} relic_tier
--  @return         {string}
function p._item(item_type, item_part, relic_tier)
    item_type = string.upper(item_type)
    item_part = string.upper(item_part)
    if (item_part == "HELMET BLUEPRINT") then
        item_part = "NEUROPTICS BLUEPRINT"
    end
    local locations = {}
    local vaultLocations = {}
    local i
    for i, relic in pairs(VoidData["Relics"]) do
        if (relic_tier == nil or relic.Tier == relic_tier) then
            local dropRarity = p.getRelicDropRarity(relic, item_type, item_part)
            if(dropRarity ~= nil) then
                local relicText = relic.Tier.." "..relic.Name
                local relicString = Tooltip.getIcon(relicText, "Void").." "..dropRarity
                if(relic.Vaulted ~= nil) then
                    relicString = relicString.." ([[Prime Vault|V]])"
                    table.insert(vaultLocations, relicString)
                else
                    if(relic.IsBaro == 1) then
                        relicString = relicString.." ([[Baro Ki%27Teer|B]])"
                    end
                    table.insert(locations, relicString)
                end
            end
        end
    end
    
    for _, i in pairs(vaultLocations) do
        table.insert(locations, i)
    end
    return table.concat(locations, "<br/>")
end

--- Gets the drop table of a relic.
--  @function       p.getRelicDrop
--  @param          {string} relicName
--  @param          {string} rarity
--  @param          {number} number
--  @return         {string}
function p.getRelicDrop(frame)
    local relicName = frame.args ~= nil and frame.args[1] or nil
    local rarity = frame.args ~= nil and frame.args[2] or nil
    local number = frame.args ~= nil and frame.args[3] or nil
    -- The number of the drop defaults to 1 if not set
    if number == nil then 
        number = 1
    elseif type(number) == 'string' then
        -- If the argument is a string, force it into being a number
        number = tonumber(number)
    end
    
    -- Return an error if any arguments are missing
    if relicName == nil or relicName == '' then
        error("ERROR: Missing argument 'Relic Name'")
    elseif rarity == nil or rarity == '' then
        error("ERROR: Missing argument 'Rarity'")
    end
    
    local bits = String.split(relicName, '%s')

    local theRelic = p.getRelic(bits[1], bits[2])
    
    -- Return an error if the relic wasn't found
    assert(theRelic ~= nil, "ERROR: Invalid relic "..relicName)
    
    local count = 0
    for i, drop in pairs(theRelic.Drops) do
        -- Loop through the drops to find drops of the chosen rarity
        if drop.Rarity == rarity then
            count = count + 1
            -- Once enough drops of the right kind have been found, return the icon + the item name
            if count == number then
                local iName = p.getItemName(drop.Item)
                local pName = p.getPartName(drop.Part, false)
                local icon = p.getItemIconForDrop(drop)
                
                return icon..' [['..iName..'|'..iName..' '..pName..']]'
            end
        end
    end

    -- If we got to here, there weren't enough drops of that rarity for this relic.
    error("ERROR: Only found "..count.." drops of "..rarity.." rarity for "..relicName)
end

--- Gets the total number of relics in the game.
--  @function       p.getRelicTotal
--  @param[opt]     {string} frame.args Options for what relic types to include are
--                           "unvaulted", "vaulted", "baro", or nil
--  @return         {number} The total count of all relics
function p.getRelicTotal(frame)
    local total = 0
 
    if (Shared.contains(frame.args, "unvaulted")) then
        for _, relic in pairs(VoidData["Relics"]) do
            if (relic.Vaulted == nil) then
                total = total + 1
            end
        end
    end
    if (Shared.contains(frame.args, "vaulted")) then
        for _, relic in pairs(VoidData["Relics"]) do
            if (relic.Vaulted ~= nil) then
                total = total + 1
            end
        end
    end
    if (Shared.contains(frame.args, "baro")) then
        for _, relic in pairs(VoidData["Relics"]) do
            if (relic.IsBaro == 1) then
                total = total + 1
            end
        end
    end
    if (frame.args[1] == nil) then
        total = Shared.tableCount(VoidData["Relics"])
    end
 
    return total
end

local function relicData()
    --This is snatched from m:VoidByReward p.byReward
    local data = {}
 
    for _, relic in pairs(VoidData["Relics"]) do
        for i, drop in pairs(relic.Drops) do
            local newObj = {Tier = relic.Tier, Name = relic.Name, Rarity = drop.Rarity, IsVaulted = relic.Vaulted ~= nil, IsBaro = relic.IsBaro == 1}
            if (data[drop.Item] == nil) then
                data[drop.Item] = {}
            end
            if(data[drop.Item][drop.Part] == nil) then
                data[drop.Item][drop.Part] = {}
            end
            table.insert(data[drop.Item][drop.Part], newObj)
        end
    end
 
    return data
end

local function checkData(data)
    if data == nil or type(data) ~= 'table' then
        local data = relicData()
        return data
    elseif type(data) == 'table' then
        return data
    end
end

local function getItemRarities(itemName, partName, data)
    local data = checkData(data)
    
    local rarities = {}
    itemName = string.upper(itemName)
    partName = string.upper(partName)
    
    for n, drop in Shared.skpairs(data[itemName][partName]) do
        rarities[drop.Rarity] = true
    end
    local brarities = {}
    for r in pairs(rarities) do table.insert(brarities,r) end
    
    --[[for rar, n in pairs(rarities) do
        mw.log(rar)
    end--]]
    
    return brarities
end

--- Gets the ducat value of a Prime part or blueprint.
--  @function       p.getDucatValue
--  @param          {string} itemName Prime item name
--  @param          {string} partName Part name
--  @return         {number} The ducat value of that Prime part/blueprint
function p.getDucatValue(frame)
    --T his is just for invoking p._getDucatValue on article pages.
    local itemName = frame.args ~= nil and frame.args[1] or nil
    local partName = frame.args ~= nil and frame.args[2] or nil
    
    if itemName == nil or itemName == '' then
        return 'Item name missing'
    elseif partName == nil or partName == '' then
        return 'Part name missing'
    end
    return p._getDucatValue(itemName, partName)
end

--- Gets the ducat value of a Prime part or blueprint.
--  @function       p._getDucatValue
--  @param          {string} itemName Prime item name
--  @param          {string} partName Part name
--  @param          {table} data Relic data
--  @return         {number} The ducat value of that Prime part/blueprint
function p._getDucatValue(itemName, partName, data)
    local rarities = getItemRarities(itemName, partName, data)
    
    local exceptions = {
            akstiletto={receiver=45},
            braton={stock=15,receiver=45},
            rubico={stock=45},
            saryn={['neuroptics blueprint']=45},
            soma={blueprint=15},
            --valkyr={['systems blueprint']=100},
        };
    
    if exceptions[string.lower(itemName)] ~= nil and exceptions[string.lower(itemName)][string.lower(partName)] ~= nil then
        return exceptions[string.lower(itemName)][string.lower(partName)]
    end
    
    return not rarities and -1
        or rarities[2] and (Shared.contains(rarities, 'Common') and 25 or Shared.contains(rarities, 'Rare') and 65 or rarities[3] and 25 )
        or ({Common=15,Uncommon=45,Rare=100})[rarities[1]]
end

--- Gets the total ducat value of all Prime parts and blueprints.
--  @function       p.getTotalDucats
--  @param[opt]     {string} tierName Tier name if want to filter by a specific relic tier
--  @return         {number} The total ducat value
function p.getTotalDucats(frame)
    local tierName = frame.args ~= nil and frame.args[1]
    local data = relicData()
    local totalItemCount = 0 --counting all items
    local withoutFormaCount = 0 --counting all items excluding forma
    local totalDucats = 0 --all, including duplicates, itemDucats
    local availableDucats = 0 --total ducats for items from available relics
    local availableItems = 0 --available items
    local availableItemsEF = 0 --available items excluding forma
    local vaultedDucats = 0 --total ducats for items from vaulted relics
    local vaultedItems = 0 --vaulted items
    local vaultedItemsEF = 0 --vaulted items excluding forma
    local result = ''
    
    for item, parts in Shared.skpairs(data) do
        for part, drops in Shared.skpairs(parts) do
            for n, drop in Shared.skpairs(drops) do
                if tierName == drop.Tier or tierName == nil then
                    if drop.Vaulted then
                        vaultedItems = vaultedItems + 1
                    else
                        availableItems = availableItems + 1
                    end
                    
                    totalItemCount = totalItemCount + 1
                    if item ~= 'FORMA' then
                        local tempDucat =p._getDucatValue(item, part, data)
                        totalDucats = totalDucats + tempDucat
                        withoutFormaCount = withoutFormaCount + 1
                        if drop.Vaulted then
                            vaultedDucats = vaultedDucats + tempDucat
                            vaultedItemsEF = vaultedItemsEF + 1
                        else
                            availableDucats = availableDucats + tempDucat
                            availableItemsEF = availableItemsEF + 1
                        end
                    end
                end
            end
        end
    end
    
    if tierName then
        result = "'''Average Ducats Value'''&#58; "..Icon._Item('Ducats').."'''"..Math.round((totalDucats / totalItemCount), 0.01).."''' ("..totalItemCount..' rewards with '..withoutFormaCount..' parts)'
        result = result.."<br>'''Available'''&#58; "..Icon._Item('Ducats').."'''"..Math.round((availableDucats/availableItems), 0.01).."''' ("..availableItems..' rewards with '..availableItemsEF..' parts)'
        result = result.." | '''Vaulted'''&#58; "..Icon._Item('Ducats').."'''"..Math.round((vaultedDucats/vaultedItems), 0.01).."''' ("..vaultedItems..' rewards with '..vaultedItemsEF..' parts)'
    else
        result = "'''Total Ducats Value'''&#58; "..Icon._Item('Ducats').."'''"..Math.formatnum(totalDucats).."''' ("..totalItemCount..' rewards with '..withoutFormaCount..' parts)'
        result = result.."<br>'''Available'''&#58; "..Icon._Item('Ducats').."'''"..Math.formatnum(availableDucats).."''' ("..availableItems..' rewards with '..availableItemsEF..' parts)'
        result = result.." | '''Vaulted'''&#58; "..Icon._Item('Ducats').."'''"..Math.formatnum(vaultedDucats).."''' ("..vaultedItems..' rewards with '..vaultedItemsEF..' parts)'
    end
    
    return result
end

local function ducatPriceRow(itemName, partName, tierName, data)
    local ducatValue = p._getDucatValue(itemName, partName, data)
    local sortValue = ''
    
    local function createRelicText(itemName, partName, tierName, data)
        itemName = string.upper(itemName)
        partName = string.upper(partName)
        
        local locations = {}
        local vaultLocations = {}
        for n, drop in Shared.skpairs(data[itemName][partName]) do
            if drop.Tier == tierName or tierName == nil then
                local dropRarity = drop.Rarity
                if dropRarity ~= nil then
                    local relicText = drop.Tier.." "..drop.Name
                    local relicString = Tooltip.getIcon(relicText, "Void").." "..dropRarity
                    if drop.Vaulted then
                        relicString = relicString.." ([[Prime Vault|V]])"
                        table.insert(vaultLocations, relicString)
                    else
                        if drop.IsBaro then
                            relicString = relicString.." ([[Baro Ki%27Teer|B]])"
                        end
                        table.insert(locations, relicString)
                    end
                end
            end
        end
        
        for _, i in pairs(vaultLocations) do
            table.insert(locations, i)
        end
        
        return table.concat(locations, "<br/>")
    end
        

    if itemName == nil or itemName == '' or partName == nil or partName == '' then
        return 'Please enter item and part names'
    end
    
    --first cell
    if partName == 'Blueprint' then
        sortValue = itemName..' _'..partName
    else
        sortValue = itemName..' '..partName
    end
    local cell1 = '\n|data-sort-value="'..sortValue..'"|'..Icon._Prime(itemName,partName)
    local cell2 = '\n|'..createRelicText(itemName, partName, tierName, data)
    local cell3 = '\n|data-sort-value="'..ducatValue..'"|'..Icon._Item('Ducats').."'''"..ducatValue.."'''\n|-"
    
    return cell1..cell2..cell3
end

--- Builds a table of all Prime parts, the relics they are in, and their ducat value
--  as seen https://warframe.fandom.com/wiki/Ducats/Prices/Lith
--  @function		p.ducatRelicList
--  @param[opt]     {string} tierName Tier name if want to filter by a specific relic tier
--  @param[opt]     {string} listMode If nil, displays only unvaulted item; if "All", displays all items, including vaulted ones
--  @return         {string} Wikitext of table
function p.ducatRelicList(frame)
    local data = relicData()
    local tierName = frame.args ~= nil and frame.args[1] or nil
    -- Adding switch to choose only vaulted or unvaulted items to show
    local listMode = frame.args ~= nil and frame.args[2] or 'ALL'
    listMode = string.upper(listMode)
    local itemList = {}
    local result = {}
    
    for item, parts in Shared.skpairs(data) do
        if item ~= 'FORMA' then
            for part, drops in Shared.skpairs(parts) do
                for i, drop in pairs(drops) do
                    if drop.Tier == tierName or tierName == nil then
                        local tempName = ''
                        if part == 'BLUEPRINT' then
                            tempName = String.titleCase(item..'<> '..part)
                        else
                            tempName = String.titleCase(item..'<>'..part)
                        end
                        if not Shared.contains(itemList, tempName) then
                            if listMode == 'VAULTED' then
                                if drop.isBaro or drop.Vaulted then
                                    table.insert(itemList, tempName)
                                end
                            elseif listMode == 'UNVAULTED' then
                                if not drop.IsBaro and not drop.Vaulted then
                                    table.insert(itemList, tempName)
                                end
                            else
                                table.insert(itemList, tempName)
                            end
                        end
                    end
                end
            end
        end
    end
    table.sort(itemList)
    
    for num, itm in pairs(itemList) do
        local item = String.split(itm, '<>')
        item[1] = String.trim(item[1])
        item[2] = String.trim(item[2])
        table.insert(result, (ducatPriceRow(item[1], item[2], tierName, data)))
    end
    return table.concat(result)
end

--- Builds relic infobox.
--	TODO: Figure out errors with _getVersionLink() and String.trim()
--	@function		p.buildRelicInfobox
--	@param			{table} frame
--	@return			{string} Wikitext of infobox
function p.buildRelicInfobox(frame)
	assert(frame and frame.args ~= nil, 'p.buildRelicInfobox(frame): empty frame arguments')
	local relicName = frame.args[1]
	
	local Relic = VoidData['RelicNames'][relicName]
	assert(Relic ~= nil, 'p.buildRelicInfobox(frame): "'..relicName..'" does not exist in [[Module:Void/data]]')
	
	local imageMap = {
		Lith = 'VoidProjectionsIronD.png',
		Meso = 'VoidProjectionsBronzeD.png',
		Neo  = 'VoidProjectionsSilverD.png',
		Axi  = 'VoidProjectionsGoldD.png',
		Requiem = 'RequiemR0.png'
	}
	
	local getRelicStatus = function(Relic) 
		if (Relic.isBaro == nil) then
			if (Relic.Vaulted == nil) then
				return '<div style="text-align:center;">{{text|green|Available}}</div>'
			else
				return '<div style="text-align:center;">{{text|red|Vaulted}}</div>[[Category:Vaulted]]'
			end
		else
			return '<div style="text-align:center;">[[Baro Ki\'Teer|{{text|blue|Baro Ki\'Teer Exclusive}}]]</div>[[Category:Removed]]'
		end
	end
			
	local infobox = [=[
<infobox>
    <title source="name"><default><span><b>%s</b></span></default></title>
    <image source="image"><default>%s</default></image>
	<group>
	    <header>General</header>
	    <data source="release-date">
	    	<label>Release date</label><default>%s</default>
    	</data>
	    <data source="vault-date">
	    	<label>Vault date</label><default>%s</default>
	    </data>
	    <data source="baro"><default>%s</default></data>
	</group>
</infobox>
]=]

	infobox = infobox:format(
		relicName,
		imageMap[Relic.Tier],
		Version._getVersionLink(Relic.Introduced),
		Version._getVersionLink(Relic.Vaulted),
		getRelicStatus(Relic)
	)
	
	mw.log(infobox)
	return frame:preprocess(infobox)
end

p.relicTooltip = function(frame) mw.log'Remove Void.relicTooltip call' return require [[Module:Tooltips/tip]] .Void (frame.args and frame.args[1] or frame) end

return p