WARFRAME Wiki
WARFRAME Wiki
m (should be good now)
m (For some reason Sonicor still shows 0 procs/s in comp table; using Table.size() instead of #)
Line 279: Line 279:
 
local statusChance = attackEntry['StatusChance'] or 0
 
local statusChance = attackEntry['StatusChance'] or 0
 
local multishot = attackEntry['Multishot'] or 1
 
local multishot = attackEntry['Multishot'] or 1
local numForcedProcs = attackEntry['ForcedProcs'] and #attackEntry['ForcedProcs'] or 0
+
local numForcedProcs = attackEntry['ForcedProcs'] and Table.size(attackEntry['ForcedProcs']) or 0
 
return (statusChance + numForcedProcs) * multishot
 
return (statusChance + numForcedProcs) * multishot
 
end,
 
end,
Line 286: Line 286:
 
local statusChance = attackEntry['StatusChance'] or 0
 
local statusChance = attackEntry['StatusChance'] or 0
 
local multishot = attackEntry['Multishot'] or 1
 
local multishot = attackEntry['Multishot'] or 1
local numForcedProcs = attackEntry['ForcedProcs'] and #attackEntry['ForcedProcs'] or 0
+
local numForcedProcs = attackEntry['ForcedProcs'] and Table.size(attackEntry['ForcedProcs']) or 0
 
local fireRate = (attackEntry['ChargeTime'] ~= nil) and (1 / attackEntry['ChargeTime']) or attackEntry['FireRate']
 
local fireRate = (attackEntry['ChargeTime'] ~= nil) and (1 / attackEntry['ChargeTime']) or attackEntry['FireRate']
 
return (statusChance + numForcedProcs) * multishot * fireRate
 
return (statusChance + numForcedProcs) * multishot * fireRate

Revision as of 22:30, 27 July 2021


Weapons contains all of WARFRAME's non-modular weapon data.

Usage

Template

In template and articles: {{#invoke:Weapons|function|input1|input2|...}}

Navigation

Quick navigation to submodules:

Product Backlog

Name Type Status Priority Assignee Description Date Issued Last Update
Module:StatObject as OOP paradigm Dev Planning Low

Currently our usage of Module:StatObject is as a static class with statRead and statFormat functions. Update Module:Weapons and Module:StatObject so we can 'instantiate' an actual StatObject object that takes in a weapon table entry as an argument. This way we can just do:

local StatObject = require('Module:StatObject') -- Base class
local WeaponData = require('Module:Weapons/data')

-- Doing some metaprogramming to extend functionality of StatObject class
StatObject.default = {
    Name = { nil, 'Weapon Name: %s' },  -- Sample definition for Name field getter/formatter
    ...
}

local BratonStatObject = StatObject(WeaponData['Braton'])

-- Get raw Name value "Braton" instead of StatObject.statRead(WeaponData['Braton'], 'Name')
local name = BratonStatObject.Name
-- Get formatted Name value "Weapon Name: Braton" (as defined in StatObject.default) instead of StatObject.statFormat(WeaponData['Braton'], 'Name')
print(BratonStatObject.Name)
mw.log(BratonStatObject.Name)
local formattedName = tostring(BratonStatObject.Name)
-- If the above is not possible in Lua then maybe add a __call metamethod to Name key to return its formatted value
formattedName = BratonStatObject.Name()
-- Or add a format() function to instantiated StatObject's metatable, passing in key name as argument
formattedName = BratonStatObject:format('Name')
22:01, 5 December 2022 (UTC)
Include attack name/context in Module:Weapons/ppdata Dev Planning Low

Update Module:Weapons/ppdata/seeder to add attack names associated with the respective stats used for comparing so that Module:Weapons/characteristics can add additional context to the stat comparisons. See https://warframe.fandom.com/wiki/Quassus?commentId=4400000000003635575. For Quassus Quassus's case, JatKusar Jat Kusar has a base 35% crit chance, but since we are comparing against non-normal attacks, Quassus Quassus's Ethereal Daggers will have second highest crit chance (30%) behind TenetExec Tenet Exec's slam shockwaves (38%).

22:09, 5 August 2022 (UTC)
Weapon and Attack classes Refactor and Dev Planning Low
  • Create a new Weapon and Attack class that can be instantiated by passing in a weapon table entry and attack table entry respectively. Each of these classes should contain a statRead() and statFormat() function that can be called to return an particular weapon stat, aggregate data, or computed stat based on /data contents.
21:18, 18 January 2022 (UTC)
ExplosionDelay key Refactor Planning Low
  • Update this to store a table with two values: shortest delay time and longest delay time. This is to support Kompressa Kompressa's variable delay time. For weapons that have a single delay time, use the same value for both table elements.
22:10, 6 January 2022 (UTC)
Reload key Refactor Planning Low
  • Move Reload key to attack tables? Nagantaka and Ambassador have two different reload times depending on attack.
  • 21:35, 19 January 2022 (UTC) update: User:Gigamicro suggests that any duplicate keys nested in Attack table entries should 'override' the base values for the weapon. In this case, add a another Reload key under the appropriate attack that has a different value than the Reload key in the main weapon entry.
Augments in /data Refactor New Low
  • Remove weapon augments list from /data and use Module:Mods and/or Module:Mods/data instead to fetch augment mods data.
    • Would require dev work in M:Mods/data too to index by mod type.
01:37, 31 May 2021 (UTC)
Data validation Dev/database Active Medium

Create Module:Weapons/data/validate subpage of /data for data validation functions

  • Include type checking for each column/attribute
  • Include checking if a table entry has the required keys (the minimum number of keys needed to support basic features in Module:Weapons)
  • Include boundary checking for stat values (e.g. CritChance cannot be negative)
01:37, 31 May 2021 (UTC) 23:33, 1 August 2021 (UTC)
Error handling Clean up New Medium

Change all return statements with "ERROR" to either assert() or error() to standardize error handling.

  • Error messages should be in the form of "functionName(argument names): argument value 1 is not a valid number".
01:37, 31 May 2021 (UTC)
Update database schema Database Active Medium User:Cephalon Scientia

Reworking how attacks are stored in tables for flexibility. Should have one Attack column that contains multiple tables, each representing a unique attack for that weapon. Would probably improve/simplify Weapon Comparison and Template:WeaponInfoboxAutomatic in displaying multiple attacks of a weapon. Right now we are hacking the use of ChargeAttack and SecondaryAttack for some attacks that are not necessarily charged or alt-fire (see Deconstructor's entry in the database). Also include forced proc data for all possible attacks (e.g. Glaives, some forced Impact weapons, etc.).

23:33, 1 August 2021 (UTC) update: There are lots of changes to these tables as I slowly create validation functions to check what keys-value pairs are needed or not, see documentation in Module:Weapons/data/doc for possible key-value pairs. Right now, attacks are stored in generic Attack1, Attack2, ... keys when we were changing the names of attack keys (e.g. NormalAttack became Attack1).

21:18, 18 January 2022 (UTC) update: User:Gigamicro implemented a new attack key (should be named Attacks to match key naming convention) that points to an array of attack tables. Attack1 to Attack9 keys are still in the data, just we now have a new way of indexing the same attack data by reference.

21:35, 19 January 2022 (UTC) update: Attack1 to Attack9 are now depreciated and removed from data tables. All optional keys are now explicitly stored in database (but are still optional b/c we set default values for getter functions in Module:Weapons. Some error clean up is still needed but for the most part, all weapon submodules and weapon tooltips should properly use Attacks table.

01:50, 31 May 2021 (UTC) 21:35, 19 January 2022 (UTC)
Unit tests Testing Archived High User:Cephalon Scientia

Add unit tests in Module:Weapons/testcases for each function in Module:Weapons. See Module:Math/testcases for examples and https://dev.fandom.com/wiki/Global_Lua_Modules/Testharness for documentation on how to format tests.

20:29, 31 July 2021 (UTC) update: Do not feel like it is appropriate to add unit tests using Module:TestHarness to most of the functions in this module since they mostly pertain to building wikitext to display to the reader. We can add a Module:Weapons/testcases subpage for visual tests to ensure rendered wikitext is not broken. Otherwise, I think it is more important to validate the data in Module:Weapons/data which are being formatted and displayed to the reader.

02:01, 31 May 2021 (UTC) 20:29, 31 July 2021 (UTC)

Finished Issues

Name Type Status Priority Assignee Description Date Issued Completion Date
Advantages/disadvantages Refactor Completed Low User:Cephalon Scientia
  • Move advantages/disadvantages builder to a new submodule page for organization.
06:05, 3 October 2021 (UTC) 17:47, 2 November 2021 (UTC)
Railjack Weapons Dev/Edit/Database Long-term support Medium User:Cephalon Scientia
  • Officially support Railjack weapons being in database. This allows Railjack weapons to make use of our infobox builder and do automatic comparisons.
  • Update /data with current user-contributed data in Railjack/Armaments

02:32, 6 September 2021 (UTC) update: Added most Railjack turrets and ordnances to /data. Missing Zetki Photor MK II, Zetki Carcinnox MK II, and Zetki Apoc MK I. First two are not obtainable in-game at this time since they are missing from drop tables, but are still in players' inventories.

00:12, 29 September 2021 (UTC) update: Added Zetki Photor MK II, Zetki Carcinnox MK II, and Zetki Apoc MK I stats according to the Mobile Export.

22:17, 2 August 2021 (UTC) 00:12, 29 September 2021 (UTC)
Update Conclave database schema Database Long-term support Low User:Cephalon Scientia

Remove keys that represent PvE stats as they are irrelevant to PvP. Most other key-value pairs (except those in attack tables) are shared with /data and can be looped through to add shared key-value pairs.

06:18, 10 August 2021 (UTC) 03:10, 16 August 2021 (UTC)
Clean up Clean up Completed Medium User:Cephalon Scientia
  • Remove unused functions. Also remove redundant functions that can be otherwise be used as as simple condition check (e.g. HasAttack() and DoNotHasAttack()).
  • Standardize styling to WARFRAME Wiki:Programming Standards.
01:37, 31 May 2021 (UTC) 06:20, 10 August 2021 (UTC)
Refactoring Refactor Long-term support Medium User:Cephalon Scientia

Refactor these functionalities for code reuse, better performance, better maintainability, and etc.:

  • Weapon comparison tables (see Weapon Comparison) ✔️
    • Includes Conclave ✔️
    • 00:19, 7 August 2021 (UTC) update: Weapon comparison tables are moved to Module:Weapons/comptable submodule for seperations of concerns design
  • Polarity table (see Polarity) ✔️
  • Mastery table (see Mastery Rank) ✔️
  • Highest physical damage type table (see Damage/Impact Damage) ✔️
  • Getter function(s) for weapon statistics/properties ✔️
  • Weapon gallery ✔️
17:43, 3 June 2021 (UTC) 00:19, 7 August 2021 (UTC)
Documentation Documentation New High

Add LuaDoc-style documentation for all functions.

01:37, 31 May 2021 (UTC) 06:35, 31 July 2021 (UTC)
Comparison tables and comparing two weapons Refactor Completed High User:Cephalon Scientia

Refactor BuildCompRow(), BuildCompTable(), BuildGunComparisonString, and their sister functions.

  • BuildCompRow() should have at max 2 nested if/else blocks
  • Do not remove functions such as getCompTableGuns(frame) and p.getCompTableArchGuns(frame) as those will be exposed to articles. Do not think there is a way to pass in table arguments in {{#invoke:}} calls so we could not have a single table builder function. Not true, can pass a multitude of named arguments and use frame.args['argName'] to get those arguments.

20:53, 29 July 2021 (UTC) update: We now use getValue(Weapon, keyName, attackName) and dictionaries (e.g. GUN_KEY_MAP) that contain simple getter functions to get specific weapon stat values. No more complicated nested if/else blocks.

01:37, 31 May 2021 (UTC) 20:53, 29 July 2021 (UTC)
getAttackValue() Refactor Completed Medium User:Cephalon Scientia

Refactor getAttackValue(Weapon, Attack, ValName, giveDefault, asString, forTable).

  • If/else blocks can be converted into a dictionary or objects that can be constructed through the use of tables and metatables.

20:53, 29 July 2021 (UTC) update: getAttackValue() has been removed and its functionality is moved to getValue(Weapon, keyName, attackName) using dictionaries (e.g. GUN_KEY_MAP) that contain simple getter functions.

01:37, 31 May 2021 (UTC) 20:53, 29 July 2021 (UTC)
p.getRivenDispositionTable() Refactor Completed Low
  • Optimized p.getRivenDispositionTable() so we don't literally have to perform over a thousand loops through each exact possible Disposition value to find weapons with that exact Disposition.
  • Remove p.getRivenDispositionList() since it is not invoked by itself on pages and was used by old p.getRivenDispositionTable().
3:42, 21 July 2021 (UTC) 4:49, 21 July 2021 (UTC)
p.buildDamageTypeTable(frame) Refactor Completed Low
  • Use multi-line strings for table building.
  • Updated validate function to only get non-Kitgun weapons and only display damage type distribution of Attack1 or the attack listed in TooltipAttackDisplay.
  • Refactored to modern programming standards.
01:37, 31 May 2021 (UTC) 21:54, 20 July 2021 (UTC)
p.buildAutoboxCategories(frame) Refactor Completed Medium

Implement a map/dictionary for mapping traits and trigger types to category link.

01:37, 31 May 2021 (UTC) 21:54, 20 July 2021 (UTC)
Weapon nav Dev Completed Medium User:FINNER

Add a new function that constructs the same navbox as Template:WeaponNav. Goal is to reduce memory used by calling Template:Weapon 400+ times on every weapon page as well as automating navbox updates whenever a new weapon is added. Right now, Template:Weapon uses ~11MB, sometimes ~20MB on pages like Volnus Prime.

Weapon navigation box generator resides in Module:Weapons/nav.

03:24, 5 June 2021 (UTC) 6:07, 7 June 2021 (UTC)
Weapon infobox Dev Completed Medium User:FINNER

Migrate wikitext from Template:WeaponInfoboxAutomatic into a infobox builder function.

  • This will probably remove the need for getValue() and their equilvalents.

Weapon infobox generator resides in Module:Weapons/infobox.

01:37, 31 May 2021 (UTC) 22:14, 8 June 2021 (UTC)
High lua memory usage Dev/Debugging Completed High User:FINNER

Some weapon pages have unusually high memory usage for Lua scripts, this will be problematic the moment we add new weapons to Template:WeaponNav:

Normally, memory usage is ~37 MB which is why this is odd.

This issue has been fixed when we now generate Template:WeaponNav using this module, instead of calling Template:Weapon 400+ times per page.

02:04, 1 June 2021 (UTC) 18:03, 7 June 2021 (UTC)

Forked Repos

Documentation

Package items

weapons._attackLoop(Weapon) (function)
Loops through all possible attacks that a weapon may have.
Parameter: Weapon Weapon entry as seen in /data (table)
Returns:
  • An iterator function that returns the key-value pair of next attack entry (function)
  • Original weapon entry passed into function (table)
weapons.buildDamageTypeTable(frame) (function)
Builds a table that lists out all weapons with a certain damage type and the percentage that it makes up of their base damage of the attack specified in their tooltip on the wiki.
Parameter: frame Frame object (table)
Returns: Wikitext of resultant wikitable (string)
weapons.getCompTableGuns() (function)
Builds comparison table of gun stats as seen on Weapon Comparison.
weapons.getCompTableConclaveGuns() (function)
Builds comparison table of gun Conclave stats as seen on Weapon Comparison/Conclave.
weapons.getCompTableMelees() (function)
Builds comparison table of melee stats as seen on Weapon Comparison.
weapons.getCompTableConclaveMelees() (function)
Builds comparison table of melee conclave stats as seen on Weapon Comparison/Conclave.
weapons.getCompTableArchMelees() (function)
Builds comparison table of arch-melee stats as seen on Weapon Comparison.
weapons.getCompTableSpeedGuns(frame) (function)
Builds comparison table of projectile flight speeds as seen on Projectile Speed.
Parameter: frame Frame object (table)
Returns: Wikitext of resultant wikitable (string)
weapons.getCompTableSpeedMelees() (function)
Builds comparison table of glaive melees' projectile flight speeds as seen on Projectile Speed.

Created with Docbunto

See Also

Code


--- '''Weapons''' contains all of [[WARFRAME]]'s non-modular [[Weapons|weapon]] data.<br />
--  
--  @module     weapons
--  @alias      p
--  @author [[User:Falterfire|Falterfire]]
--  @attribution    [[User:Flaicher|Flaicher]]
--  @attribution    [[User:FINNER|FINNER]]
--  @attribution    [[User:Cephalon Scientia|Cephalon Scientia]]
--  @attribution    [[User:Gigamicro|Gigamicro]]
--  @attribution    [[User:Synthtech|Synthtech]]
--  @attribution    [[User:Calenhed|Calenhed]]
--  @image      
--  @require    [[Module:Icon]]
--  @require    [[Module:Math]]
--  @require    [[Module:String]]
--	@require	[[Module:Table]]
--  @require    [[Module:Mods/data]]
--  @require    [[Module:Modular/data]]
--  @require    [[Module:Stances/data]]
--  @require    [[Module:Tooltips]]
--  @require    [[Module:Version]]
--  @require    [[Module:Weapons/data]]
--  @require    [[Module:Weapons/Conclave/data]]
--  @release    stable
--  <nowiki>

-- TODO: Remove Icon._Proc and replace with a function call to M:DamageTypes
local p = {}

local ConclaveData = mw.loadData [[Module:Weapons/Conclave/data]]
local ModData = mw.loadData [[Module:Mods/data]]
local ModularData = mw.loadData [[Module:Modular/data]]
local StanceData = mw.loadData [[Module:Stances/data]]
local WeaponData = mw.loadData [[Module:Weapons/data]]

local Icon = require [[Module:Icon]]
local Math = require [[Module:Math]]
local String = require [[Module:String]]
local Tooltip = require [[Module:Tooltips]]
local Version = require [[Module:Version]]
local Table = require [[Module:Table]]
local Polarity = require [[Module:Polarity]]

-- TODO: Could use M:DamageTypes instead of this table?
local Elements = {
    "Impact", "Puncture", "Slash", "Heat", "Cold", "Toxin", 
    "Electricity", "Blast", "Corrosive", "Radiation", "Magnetic", "Gas", 
    "Viral", "Void", "True"
}
-- TODO: Could use M:DamageTypes instead of this table?
local Physical = {"Impact", "Puncture", "Slash"}
local VariantList = {
    "Prime", "Prisma", "Wraith", "Vandal", "Vaykor", "Synoid", 
    "Telos", "Secura", "Sancti", "Rakta", "Mara", "MK1", "Kuva"
}

--If Type is not nil, get damage weapon deals of that type
--If it deals no damage of that type, return 0 instead of nil
--It Type is nil, return total damage
local function GetDamage(Attack, Type, withMultishot)
    if (Attack == nil or Attack.Damage == nil) then return 0 end
    
    local pCount = 1
    if (withMultishot and Attack.Multishot ~= nil) then pCount = Attack.Multishot end
    if (Type == nil) then
        local total = 0
            for i, d in Table.skpairs(Attack.Damage) do
                total = total + d
            end
        return total * (withMultishot and pCount or 1)
    end
    
    if (Type == "Physical") then
        local Impact = Attack.Damage["Impact"] ~= nil and Attack.Damage["Impact"] or 0
        local Puncture = Attack.Damage["Puncture"] ~= nil and Attack.Damage["Puncture"] or 0
        local Slash = Attack.Damage["Slash"] ~= nil and Attack.Damage["Slash"] or 0
        return (Impact + Puncture + Slash) * (withMultishot and pCount or 1)
    elseif (Type == "Element") then
        for dType, dmg in Table.skpairs(Attack.Damage) do
            if (not Table.contains(Physical, dType) or dmg <= 0) then
                return dmg * (withMultishot and pCount or 1)
            end
        end
        return 0
    elseif (Attack.Damage[Type] == nil) then
        return 0
    end
    return Attack.Damage[Type] * (withMultishot and pCount or 1)
end

local function getDamageBias(attackEntry)
    if (attackEntry.Damage ~= nil and Table.size(attackEntry.Damage) > 0) then
        local totalDmg = 0
        local bestDmg = 0
        local bestElement = nil
        local elemCount = 0
        for damageType, dmg in pairs(attackEntry.Damage) do
            if (dmg > bestDmg) then
                bestDmg = dmg
                bestElement = damageType
            end
            totalDmg = totalDmg + dmg
            if (dmg > 0) then
                elemCount = elemCount + 1
            end
        end
        -- Make sure there are two damage instances that are above zero
        -- Exception for physical damage types
        if (elemCount > 0) then
            return (bestDmg / totalDmg), bestElement
        end
		error('getDamageBias(Attack): '..
			'Damage key in Attack entry has no key-value pairs'..mw.dumpObject(attackEntry))
	end
	error('getDamageBias(Attack): Attack entry has no Damage key')
end

--If the attack has at least two damage types,
--  Returns something like "58.3% Slash"
--If it doesn't, returns nil
local function getDamageBiasString(Attack)
    local bestPercent, bestElement = getDamageBias(Attack)
    local result = Math.percentage(bestPercent)
    return result..' '..Icon._Proc(bestElement)
end

-- For now using what the tooltips is displaying for DPS calculations
-- I know it is not accurate b/c the other columns in comparison table may use a different Attack table by default
-- Calculates the derived damage stats commonly used in comparing gun performance.
--			calculateGunDerivedDamage
--			{table} Weapon Weapon table data as seen in M:Weapons/data
--			{number} Returns five number stats in a tuple: 
--				* Total damage: final damage when accounting multishot; same as arsenal display
--				* Average shot: damage per single input
--				* Average burst DPS: damage per second without reloading
--				* Average sustained DPS: damage per second w/ reloading
--				* Average lifetime damage: total damage that can be dealt in a single magazine + reserve ammo w/o picking up ammo drops
local function calculateGunDerivedDamage(Weapon)
    local TooltipAttack = Weapon[Weapon['TooltipAttackDisplay'] or 'Attack1'] or {}
    
    local totalDamage = 0
	
    for damageType, value in pairs(TooltipAttack['Damage'] or {}) do
    	if (damageType == 'MinProgenitorBonus') then
		    -- For a more competitive comparison, assume that Lich weapons have 
    		-- max base damage bonus (+60% of a Progenitor Warframe's element);
    		-- The value stored in 'MinProgenitorBonus' is based on the minimum +25% bonus
    		totalDamage = totalDamage + (value * 0.6/0.25)	
    	else
    		totalDamage = totalDamage + value
    	end
    end
    totalDamage = totalDamage * (TooltipAttack['Multishot'] or 1)
    
    local critChance = TooltipAttack['CritChance']
    local critMultiplier = TooltipAttack['CritMultiplier']
    -- If an attack uses the charge trigger, use that instead since 
	-- theoretically players want to charge an attack for more damage/DPS
    local fireRate = TooltipAttack['ChargeTime'] and (1 / TooltipAttack['ChargeTime']) or 
    	TooltipAttack['FireRate']
    local reloadTime = Weapon['Reload']
    local magazine = Weapon['Magazine']
    local maxAmmo = Weapon['MaxAmmo']
    
    -- TODO: If we are defining average shot as average damage dealt per a single attack input
    -- than this is is inaccurate for burst-fire attacks (being picky on wording, but it is 
    -- for more accurate calculations)
    local avgShot = totalDamage * (1 + critChance * (critMultiplier - 1))
    -- Extra one needed in calculation to account for initial filled mag
    -- If there is no reserve ammo, that means that weapon can deal an infinite amount of damage theoretically
    local avgLifetimeDmg = maxAmmo < math.huge and avgShot * (magazine / (TooltipAttack['AmmoCost'] or 1)) * (1 + (maxAmmo / magazine)) or math.huge
    
    -- Need to ignore the first shot of guns since it is instantaneous and is 
    -- not affected by fire rate (which causes the delay between shots)
    local numShotPerMag = magazine / (TooltipAttack['AmmoCost'] or 1)
    local avgBurst = avgShot * fireRate
    local avgSustained

	-- If weapons have a magazine of 1, effective fire rate will be the inverse of reload time
    if (magazine == 1) then
    	-- For bows, if attack is a charged attack, account for the charge time which
    	-- should decrease DPS
    	avgSustained = avgShot * (1 / (reloadTime + (TooltipAttack['ChargeTime'] or 0)))
    else
	    avgSustained = (avgBurst * numShotPerMag) / ((numShotPerMag - 1) + (fireRate * reloadTime))
	end
    
    return totalDamage, avgShot, avgBurst, avgSustained, avgLifetimeDmg
end

local ATTACK_KEY_MAP = {
	Impact = function(attackEntry) return attackEntry['Damage']['Impact'] or 0 end,
	Puncture = function(attackEntry) return attackEntry['Damage']['Puncture'] or 0 end,
	Slash = function(attackEntry) return attackEntry['Damage']['Slash'] or 0 end,
	AttackName = function(attackEntry) return attackEntry['AttackName'] or 'Normal' end,
	AmmoCost = function(attackEntry) return attackEntry['AmmoCost'] or 1 end,
	BurstCount = function(attackEntry) return attackEntry['BurstCount'] end,
	BurstFireRate = function(attackEntry) return attackEntry['BurstFireRate'] end,
	CompTableFireRate = function(attackEntry)
		-- if (attackEntry['FireRate'] ~= nil and attackEntry['ChargeTime'] ~= nil) then
		-- 	return attackEntry['FireRate'] > (1 / attackEntry['ChargeTime']) and attackEntry['FireRate'] or 
		-- 		Math.round(1 / attackEntry['ChargeTime'], 0.01)
		if (attackEntry['ChargeTime'] ~= nil) then
			return 1 / attackEntry['ChargeTime']
		end
		return attackEntry['FireRate']
	end,
	Damage = function(attackEntry) return GetDamage(attackEntry, nil, false) end,
	DamageBias = function(attackEntry) return getDamageBiasString(attackEntry) end,
	ChargeTime = function(attackEntry) return attackEntry['ChargeTime'] end,
	CritChance = function(attackEntry) return Math.percentage(attackEntry['CritChance']) end,
	-- Space added between value and 'x' so that wikitables can automatically recognize 
	-- that these can be sorted as numeric values
	CritMultiplier = function(attackEntry) return (attackEntry['CritMultiplier'])..'x' end,
	Falloff = function(attackEntry) return attackEntry['Falloff']['Reduction'] end,
	FireRate = function(attackEntry) return attackEntry['FireRate'] end,
	HeadshotMultiplier = function(attackEntry)
		return attackEntry['HeadshotMultiplier'] and (attackEntry['HeadshotMultiplier'])..' x' or '1 x'
	end,
	Multishot = function(attackEntry) return attackEntry['Multishot'] or 1 end,
	PelletName = function(attackEntry) return attackEntry['PelletName'] end,
	PunchThrough = function(attackEntry) return attackEntry['PunchThrough'] or 0 end,
	Radius = function(attackEntry) return attackEntry['Radius'] end,
	ShotSpeed = function(attackEntry) return attackEntry['ShotSpeed'] end,
	ShotType = function(attackEntry) return attackEntry['ShotType'] end,
	StatusChance = function(attackEntry) return Math.percentage(attackEntry['StatusChance']) end,
}

local SHARED_KEY_MAP = {
	Class = function(weaponEntry) return weaponEntry['Class'] end,
	Disposition = function(weaponEntry)
		return weaponEntry['Disposition'] ~= nil and weaponEntry['Disposition'] or 'N/A'
	end,
	Family = function(weaponEntry) return weaponEntry['Family'] end,
	Introduced = function(weaponEntry)
		return weaponEntry['Introduced'] and Version._getVersionDate(weaponEntry['Introduced']) or 'N/A'
	end,
	Link = function(weaponEntry) return '[['..weaponEntry['Link']..']]' end,
	Mastery = function(weaponEntry) return weaponEntry['Mastery'] or 'N/A' end,
	Name = function(weaponEntry) return '[['..weaponEntry['Name']..']]' end,
	NameLink = function(weaponEntry) return '[['..weaponEntry['Link']..'|'..weaponEntry['Name']..']]' end,
	Polarities = function(weaponEntry) return Polarity._pols(weaponEntry['Polarities']) end,
	Traits = function(weaponEntry)
		local traitString = {}
		for _, trait in ipairs(weaponEntry['Traits']) do
			table.insert(traitString, trait)
		end
		return table.concat(traitString, ', ')
	end,
	Type = function(weaponEntry) return weaponEntry['Type'] end
}

local GUN_KEY_MAP = {
	Accuracy = function(weaponEntry)
		if (weaponEntry['Accuracy'] ~= nil) then return weaponEntry['Accuracy'] end
		return weaponEntry[weaponEntry['TooltipAttackDisplay'] or 'Attack1']['Accuracy']
	end,
	AmmoType = function(weaponEntry)
		if (weaponEntry['AmmoType'] ~= nil) then
			return weaponEntry['AmmoType']
		elseif(weaponEntry['Type'] == 'Secondary') then
			return 'Pistol'
		elseif(weaponEntry['Type'] == 'Primary') then
			local class = Weapon['Class']
            if (class == 'Rifle') then
	            return 'Rifle'
	        elseif (class == 'Shotgun') then
	            return 'Shotgun'
	        elseif (class == 'Bow') then
	            return 'Bow'
	        elseif (class == 'Sniper Rifle' or class == 'Launcher') then
	            return "Sniper"
	        end
        end
		return ''
	end,
	AvgProcCount = function(weaponEntry)
		local attackEntry = weaponEntry[weaponEntry['TooltipAttackDisplay'] or 'Attack1']
		local statusChance = attackEntry['StatusChance'] or 0
		local multishot = attackEntry['Multishot'] or 1
		local numForcedProcs = attackEntry['ForcedProcs'] and Table.size(attackEntry['ForcedProcs']) or 0
		return (statusChance + numForcedProcs) * multishot
	end,
	AvgProcPerSec = function(weaponEntry)
		local attackEntry = weaponEntry[weaponEntry['TooltipAttackDisplay'] or 'Attack1']
		local statusChance = attackEntry['StatusChance'] or 0
		local multishot = attackEntry['Multishot'] or 1
		local numForcedProcs = attackEntry['ForcedProcs'] and Table.size(attackEntry['ForcedProcs']) or 0
		local fireRate = (attackEntry['ChargeTime'] ~= nil) and (1 / attackEntry['ChargeTime']) or attackEntry['FireRate']
		return (statusChance + numForcedProcs) * multishot * fireRate
	end,
	AvgShotDmg = function(weaponEntry)
		local totalDamage, avgShot = calculateGunDerivedDamage(weaponEntry)
		return avgShot
	end,
	BurstDps = function(weaponEntry)
		local totalDamage, avgShot, avgBurst = calculateGunDerivedDamage(weaponEntry)
		return avgBurst
	end,
	ExilusPolarity = function(weaponEntry) return Polarity._polarity(weaponEntry['ExilusPolarity']) end,
	IsSilent = function(weaponEntry) return weaponEntry['IsSilent'] end,
	Magazine = function(weaponEntry) return weaponEntry['Magazine'] end,
	MaxAmmo = function(weaponEntry) return weaponEntry['MaxAmmo'] end,
	Range = function(weaponEntry) return weaponEntry['Range'] end,
	Reload = function(weaponEntry) return weaponEntry['Reload'] end,
	ReloadDelay = function(weaponEntry) return weaponEntry['ReloadDelay'] or 0 end,
	ReloadDelayEmpty = function(weaponEntry) return weaponEntry['ReloadDelayEmpty'] or 0 end,
	ReloadStyle = function(weaponEntry) return weaponEntry['ReloadStyle'] end,
	Spool = function(weaponEntry) return weaponEntry['Spool'] or 0 end,
	SustainedDps = function(weaponEntry)
		local totalDamage, avgShot, avgBurst, avgSustained = calculateGunDerivedDamage(weaponEntry)
		return avgSustained
	end,
	Trigger = function(weaponEntry) return weaponEntry['Trigger'] end
}

local MELEE_KEY_MAP = {
	BlockAngle = function(weaponEntry) return weaponEntry['BlockAngle'] or 0 end,
	ComboDur = function(weaponEntry) return weaponEntry['ComboDur'] or 0 end,
	FollowThrough = function(weaponEntry) return weaponEntry['FollowThrough'] or 0 end,
	HeavyAttack = function(weaponEntry) return weaponEntry['HeavyAttack'] or 0 end,
	HeavyElement = function(weaponEntry) return weaponEntry['HeavyElement'] end,
	HeavySlamAttack = function(weaponEntry) return weaponEntry['HeavySlamAttack'] end,
	HeavySlamElement = function(weaponEntry) return weaponEntry['HeavySlamElement'] end,
	HeavyRadialDmg = function(weaponEntry) return weaponEntry['HeavyRadialDmg'] or 0 end,
	HeavyRadialElement = function(weaponEntry) return weaponEntry['HeavyRadialElement'] end,
	HeavySlamRadius = function(weaponEntry) return weaponEntry['HeavySlamRadius'] or 0 end,
	MeleeRange = function(weaponEntry) return weaponEntry['MeleeRange'] or 0 end,
	SlamAttack = function(weaponEntry) return weaponEntry['SlamAttack'] or 0 end,
	SlamElement = function(weaponEntry) return weaponEntry['SlamElement'] end,
	SlamRadialDmg = function(weaponEntry) return weaponEntry['SlamRadialDmg'] or 0 end,
	SlamRadialElement = function(weaponEntry) return weaponEntry['SlamRadialElement'] end,
	SlamRadialProc = function(weaponEntry) return weaponEntry['SlamRadialProc'] end,
	SlamRadius = function(weaponEntry) return weaponEntry['SlamRadius'] or 0 end,
	SlideAttack = function(weaponEntry) return weaponEntry['SlideAttack'] or 0 end,
	SlideElement = function(weaponEntry) return weaponEntry['SlideElement'] end,
	Stances = function(weaponEntry) return getWeaponStanceList(weaponEntry) end,
	StancePolarity = function(weaponEntry)
		return weaponEntry['StancePolarity'] ~= nil and
			Polarity._polarity(weaponEntry['StancePolarity']) or 'N/A'
	end,
	WindUp = function(weaponEntry) return weaponEntry['WindUp'] or 0 end
}

-- For mapping weapon traits or types to a category link
local categoryMap = {
	Primary = '[[Category:Primary Weapons]]',
	Secondary = '[[Category:Secondary Weapons]]',
	Melee = '[[Category:Melee Weapons]]',
	['Arch-Melee'] = '[[Category:Archwing Melee]]',
	['Arch-Gun'] = '[[Category:Archwing Gun]]',
	['Arch-Gun (Atmosphere)'] = '[[Category:Archwing Gun]]',
	Kitgun = '[[Category:Kitgun]]',
	Zaw = '[[Category:Zaw]]',
	['Railjack Armament'] = '[[Category:Railjack]]',
	Gear = '[[Category:Gear]]',
	
	Rifle = '[[Category:Assault Rifle]]',
	['Sniper Rifle'] = '[[Category:Sniper Rifle]]',
	Shotgun = '[[Category:Shotgun]]',
	Pistol = '[[Category:Pistol]]',
	['Dual Pistols'] = '[[Category:Dual Pistols]]',
	Bow = '[[Category:Bow]]',
	Launcher = '[[Category:Launcher]]',
	['Arm-Cannon'] = '[[Category:Arm-Cannon]]',
	['Speargun'] = '[[Category:Speargun]]',
	Thrown = '[[Category:Thrown]]',
	['Shotgun Sidearm'] = '[[Category:Shotgun Sidearm]]',
	
	Prime = '[[Category:Prime Weapons]]',
	['Never Vaulted'] = '[[Category:Never Vaulted]]',
	Vaulted = '[[Category:Vaulted]]',
	Wraith = '[[Category:Wraith]]',
	Vandal = '[[Category:Vandal]]',
	-- Maybe replace with 'Kuva' category? Though technically Broken Scepter is a "Kuva" weapon which
	-- is why this distinction is made (in the past, editors mislabeled this trait)
	['Kuva Lich'] = '[[Category:Kuva Lich]]',
	Prisma = '[[Category:Prisma]]',
	
	Grineer = '[[Category:Grineer Weapons]]',
	Corpus = '[[Category:Corpus Weapons]]',
	Infested = '[[Category:Infested Weapons]]',
	Tenno = '[[Category:Tenno Weapons]]',
	Sentient = '[[Category:Sentient Weapons]]',
	Entrati = '[[Category:Entrati Weapons]]',
	Baro = '[[Category:Baro Ki\'Teer Offering]]',
	Syndicate = '[[Category:Syndicate Offerings]]',
	['Invasion Reward'] = '[[Category:Invasion Reward]]',
	
	['Alt Fire'] = '[[Category:Weapons with Alt Fire]]',
	['AoE'] = '[[Category:Weapons with Area of Effect]][[Category:Self Interrupt Weapons]]',
	
	Active = '[[Category:Active]]',
	Auto = '[[Category:Automatic]]',
	['Auto-Spool'] = '[[Category:Automatic]]',
	Burst = '[[Category:Burst Fire]]',
	['Auto-Burst'] = '[[Category:Burst Fire]]',
	['Auto Charge'] = '[[Category:Charge]]',
	Charge = '[[Category:Charge]]',
	Duplex = '[[Category:Duplex Fire]]',
	['Semi-Auto'] = '[[Category:Semi-Automatic]]',
	Held = '[[Category:Continuous Weapons]]'
}

-- Builds a CSV table of all WARFRAME's guns with the exception of Kitguns.
--	function		p.csvGunComparisonTable
--	param			{table} frame Frame object
--	returns		{string} Preformatted text of CSV text
function p.csvGunComparisonTable(frame)
	-- Weapon types to show in resultant table
	local weaponTypesFilter = { 'Primary', 'Secondary', 'Robotic', 'Arch-Gun', 'Arch-Gun (Atmosphere)', 'Amp' }
	local tableEntryTemplate = {}	-- Would look like '%s,%s,%s'
	local tableHeader = { 
		'Name',
		'Trigger',
		'AttackName',
		'Impact',
		'Puncture',
		'Slash',
		'Cold',
		'Electricity',
		'Heat',
		'Toxin',
		'Blast',
		'Corrosive',
		'Gas',
		'Magnetic',
		'Radiation',
		'Viral',
		'Void',
		'MinProgenitorBonus',
		'Damage',
		'TotalDmg',
		'CritChance',
		'CritMultiplier',
		'AvgShotDmg',
		'BurstDps',
		'SustainedDps',
		'LifetimeDmg',
		'StatusChance',
		'AvgProcCount',
		'AvgProcPerSec',
		'Multishot',
		'FireRate',
		'Disposition',
		'Mastery',
		'Magazine',
		'MaxAmmo',
		'Reload',
		'ShotType',
		'PunchThrough',
		'Accuracy',
		'Introduced',
		'IntroducedDate',
		'Type',
		'Class',
		'AmmoType'
	}
	
	for i, _ in ipairs(tableHeader) do table.insert(tableEntryTemplate, '%s,') end
	tableEntryTemplate[#tableEntryTemplate] = '%s'	-- Last column
	tableEntryTemplate = table.concat(tableEntryTemplate)
	
	local csvResult = { '<pre>' }
    table.insert(csvResult, string.format(tableEntryTemplate, unpack(tableHeader)))
	
	for weaponName, weaponData in Table.skpairs(WeaponData['Weapons']) do
		if (Table.contains(weaponTypesFilter, weaponData['Type'])) then
			-- Going through all the possible Attack keys and adding them to CSV
			-- (TODO: consolidate these keys into one Attack table with key-value pairs)
			for i = 1, 9, 1 do
				if (weaponData['Attack'..i] ~= nil) then
					local totalDamage, avgShot, avgBurst, avgSustained, avgLifetimeDmg = calculateGunDerivedDamage(weaponData)
					local weaponAttack = weaponData['Attack'..i]
					
					local baseDamage = 0
					for damageType, damageValue in pairs(weaponAttack['Damage']) do
						baseDamage = baseDamage + damageValue
					end
					
					local tableEntryValues = { 
						weaponName,
						tostring(weaponData['Trigger']),
						tostring(weaponAttack['AttackName'] or 'Normal'),
						tostring(weaponAttack['Damage']['Impact'] or 0),
						tostring(weaponAttack['Damage']['Puncture'] or 0),
						tostring(weaponAttack['Damage']['Slash'] or 0),
						tostring(weaponAttack['Damage']['Cold'] or 0),
						tostring(weaponAttack['Damage']['Electricity'] or 0),
						tostring(weaponAttack['Damage']['Heat'] or 0),
						tostring(weaponAttack['Damage']['Toxin'] or 0),
						tostring(weaponAttack['Damage']['Blast'] or 0),
						tostring(weaponAttack['Damage']['Corrosive'] or 0),
						tostring(weaponAttack['Damage']['Gas'] or 0),
						tostring(weaponAttack['Damage']['Magnetic'] or 0),
						tostring(weaponAttack['Damage']['Radiation'] or 0),
						tostring(weaponAttack['Damage']['Viral'] or 0),
						tostring(weaponAttack['Damage']['Void'] or 0),
						tostring(weaponAttack['Damage']['MinProgenitorBonus'] or 0),
						tostring(baseDamage),
						tostring(totalDamage),
						tostring(weaponAttack['CritChance']),
						tostring(weaponAttack['CritMultiplier']),
						tostring(avgShot),
						tostring(avgBurst),
						tostring(avgSustained),
						tostring(avgLifetimeDmg),
						tostring(weaponAttack['StatusChance']),
						tostring((weaponAttack['StatusChance'] or 0) * (weaponAttack['Multishot'] or 1)),
						tostring(GUN_KEY_MAP['AvgProcPerSec'](weaponData)),
						tostring(weaponAttack['Multishot'] or 1),
						-- If an attack uses the charge trigger, use that instead since 
						-- theoretically players want to charge an attack for more damage/DPS
						tostring(weaponAttack['ChargeTime'] and (1 / weaponAttack['ChargeTime']) or weaponAttack['FireRate']),
						tostring(weaponData['Disposition']),
						tostring(weaponData['Mastery']),
						tostring(weaponData['Magazine']),
						tostring(weaponData['MaxAmmo']),
						tostring(weaponData['Reload']),
						tostring(weaponAttack['ShotType']),
						tostring(weaponAttack['PunchThrough'] or 0),
						tostring(weaponData['Accuracy']),
						tostring(weaponData['Introduced'] and Version._getVersion(weaponData['Introduced'])['Name'] or nil),
						tostring(weaponData['Introduced'] and Version._getVersionDate(weaponData['Introduced']) or nil),
						tostring(weaponData['Type']),
						tostring(weaponData['Class']),
						tostring((weaponData['Type'] == 'Secondary' and 'Pistol') or weaponData['AmmoType'] or weaponData['Class'])
					}
					local tableEntry = string.format(tableEntryTemplate, unpack(tableEntryValues))
					table.insert(csvResult, tableEntry)
				end
			end
		end
	end
    
    table.insert(csvResult, '</pre>')
    return table.concat(csvResult, '\n')
end

function p.isVariant(WeapName)
    for i, var in pairs(VariantList) do
        if (string.find(WeapName, var)) then
            local baseName = string.gsub(WeapName, " ?"..var.." ?-?", "")
            return true, var, baseName
        end
    end
    return false, "Base", WeapName
end

function p._buildName(baseName, variant)
    if (variant == nil or variant == 'Base' or variant == '') then
        return baseName
    elseif (baseName == 'Laser Rifle' and variant == 'Prime') then
    	return variant..' '..baseName	-- Laser Rifle has a primed version called 'Prime Laser Rifle'
    elseif (variant == 'Prime' or variant == 'Wraith' or variant == 'Vandal') then
        return baseName..' '..variant
    elseif (variant == 'MK1') then
        return 'MK1-'..baseName
    else
        return variant..' '..baseName
    end
end

--It's a bit of a mess, but this is for compressing a list with variants
--So if a list has Braton, Braton Prime, and MK1-Braton it'll list as
--                  Braton (MK1, Prime)
function p._shortLinkList(Weapons, tooltip)
    --First grabbing all the pieces and stashing them in a table
    local baseNames = {}
    for key, weap in Table.skpairs(Weapons) do
        local isVar, varType, baseName = p.isVariant(weap.Name)
        if (baseNames[baseName] == nil) then baseNames[baseName] = {} end
        table.insert(baseNames[baseName], varType)
    end
    
    --Then the fun part: Pulling the table together
    local result = {}
    for baseName, variants in Table.skpairs(baseNames) do
        --So first, check if "Base" is in the list
        --Because if it isn't, list all variants separately
        
        if (Table.contains(variants, "Base")) then
            table.sort(variants)
            --First, get the basic version
            local thisRow = ""
            if (tooltip) then
                thisRow = "{{Weapon|"..baseName.."}}"
            else
                thisRow = "[["..baseName.."]]"
            end
            --then, if there are variants...
            if (Table.size(variants) > 1) then
                --List them in parentheses one at a time
                thisRow = thisRow.." ("
                local count = 0
                for i, varName in pairs(variants) do
                    if (varName ~= "Base") then
                        if (count > 0) then thisRow = thisRow..", " end
                        if (tooltip) then
                            thisRow = thisRow.."{{Weapon|"..p._buildName(baseName, varName).."|"..varName.."}}"
                        else
                            thisRow = thisRow.."[["..p._buildName(baseName, varName).."|"..varName.."]]"
                        end
                        count = count + 1
                    end
                end
                thisRow = thisRow..")"
            end
            table.insert(result, thisRow)
        else
            for i, varName in pairs(variants) do
                if (tooltip) then
                    table.insert(result, "{{Weapon|"..p._buildName(baseName, varName).."}}")
                else
                    table.insert(result, "[["..p._buildName(baseName, varName).."]]")
                end
            end
        end
    end
    return result
end

function p._getWeapon(weaponName)
    local weapon = WeaponData["Weapons"][weaponName]
    if weapon ~= nil then
    	return weapon
    end
    
    for key, Weapon in Table.skpairs(WeaponData["Weapons"]) do
        if (Weapon.Name == WeapName) then return Weapon end
    end
    error('p._getWeapon(weaponName): "'..weaponName..'" does not exist in M:Weapons/data')
end

function p._getConclaveWeapon(weaponName)
    local weapon = ConclaveData["Weapons"][weaponName]
    if (weapon ~= nil and weapon.Name == weaponName) then
    	return weapon
    end
    
    for key, Weapon in Table.skpairs(ConclaveData["Weapons"]) do
        if (Weapon.Name == WeapName or key == WeapName) then
            return Weapon
        end
    end
    error('p._getConclaveWeapon(weaponName): "'..weaponName..'" does not exist in M:Weapons/Conclave/data')
end

local function getAttack(Weapon, AttackType)
    if (Weapon == nil or AttackType == nil) then return end
    if (type(Weapon) == "string") then
        Weapon = p._getWeapon(Weapon)
    end
    if (not AttackType) then
        return Weapon.Attack1 or Weapon.Damage and Weapon
    end
    return Weapon[AttackType:find 'Attack' and AttackType or 'Attack'..AttackType]
end

-- TODO: Remove this boolean check function
local function hasAttack(Weapon, AttackType)
    if (getAttack(Weapon, AttackType) ~= nil) then 
        return true 
    end
    return nil
end

-- TODO: Remove this boolean check function
local function dontHasAttack(Weapon, AttackType)
    if (getAttack(Weapon, AttackType) ~= nil) then 
        return nil 
	end
    return true
end

--- Loops through all possible attacks that a weapon may have.
--	@function		p._attackLoop
--	@param			{table} Weapon Weapon entry as seen in <code>/data</code>
--	@returns		{function} An iterator function that returns the key-value pair of next attack entry
--	@returns		{table} Original weapon entry passed into function
function p._attackLoop(Weapon)
    if (Weapon == nil) then
        return function() return nil end
    end
    local function nextAttack(t, k)
        if not k then return '1', t['Damage'] and t or t['Attack1'] end
        local v
        repeat
            k, v = next(t,k)
        until type(v) == 'table' and v['Damage']
        return k, v
    end
    return nextAttack, Weapon
end

local function getAugments(Weapon)
    local name = Weapon.Name ~= nil and Weapon.Name or Weapon
    local augments = {}
    for i, Augment in pairs(WeaponData["Augments"]) do
        for j, WeapName in pairs(Augment.Weapons) do
            if (WeapName == name) then
                table.insert(augments, Augment)
            end
        end
    end
    return augments
end

-- Returns all melee weapons.
-- If weapType is not nil, only grab for a specific type
-- For example, if weapType is "Nikana", only pull Nikanas
-- Else grab all melee weapons
local function getMeleeWeapons(weapClass, PvP)
    local weaps = {}
    for i, weap in Table.skpairs(WeaponData["Weapons"]) do
        if ((weap.Ignore == nil or not weap.Ignore) and weap.Type ~= nil and weap.Type == "Melee") then
            local classMatch = (weapClass == nil or weap.Class == weapClass)
            local pvpMatch = (PvP == nil or (PvP and weap.Conclave ~= nil and weap.Conclave))
            if (classMatch and pvpMatch) then
                table.insert(weaps, weap)
            end
        end
    end
    
    return weaps
end

--As above, but for Conclave stats
local function getConclaveMeleeWeapons(weapClass, PvP)
    local weaps = {}
    local weapClasses = {}
    if (weapClass ~= nil) then
        weapClasses = String.split(weapClass, ",")
    end
    
    for i, weap in Table.skpairs(ConclaveData["Weapons"]) do
        if ((weap.Ignore == nil or not weap.Ignore) and weap.Type ~= nil and weap.Type == "Melee") then
            local classMatch = (weapClass == nil or Table.contains(weapClasses, weap.Class))
            local pvpMatch = (PvP == nil or (PvP and weap.Conclave ~= nil and weap.Conclave))
            if (classMatch and pvpMatch) then
                table.insert(weaps, weap)
            end
        end
    end
    
    return weaps
end

--Learning new things... Trying to allow sending in an arbitrary function
function p._getWeapons(validateFunction)
    local weaponList = {}
    for weaponName, weaponEntry in Table.skpairs(WeaponData["Weapons"]) do
        if (not Table.contains(WeaponData["IgnoreInCount"], weaponName) and
        		(weaponEntry.Ignore == nil or not weaponEntry.Ignore) and validateFunction(weaponEntry)) then
            table.insert(weaponList, weaponEntry)
        end
    end
    return weaponList
end

--Same as getWeapons, but for Conclave data
function p.getConclaveWeapons(validateFunction)
    local weaps = {}
    for i, weap in Table.skpairs(ConclaveData["Weapons"]) do
        if ((weap.Ignore == nil or not weap.Ignore) and validateFunction(weap)) then
            table.insert(weaps, weap)
        end
    end
    return weaps
end

local function getStances(weapType, pvpOnly)
    local stanceTable = {}
    
    for stanceName, Stance in pairs(StanceData) do
        local typeMatch = (weapType == nil or weapType == Stance.WeaponType)
        local pvpMatch = (pvpOnly ~= nil and pvpOnly) or (Stance.ConclaveOnly == nil or not Stance.ConclaveOnly)
        if (typeMatch and pvpMatch) then
            stanceTable[stanceName] = Stance
        end
    end
    
    return stanceTable
end

--Returns the damage string as it's formatted in a comparison row
--So instead of '0', returns '-', and appends the icon for an element if necessary
local function GetDamageString(Attack, Type, ByPellet)
    if (ByPellet == nil) then ByPellet = false end
    if (Attack == nil or Attack.Damage == nil) then return "" end
    
    local pCount = 1
    if (ByPellet and Attack.Multishot ~= nil) then pCount = Attack.Multishot end
    if (Type == nil) then
    	local damageString = nil
        for damageType, val in pairs(Attack.Damage) do
        	-- When there are more than one element in weapon's attack distribution,
        	-- just return the base damage with no icon
        	if (damageString ~= nil) then
        		return Math.round(GetDamage(Attack, nil, ByPellet), 0.01)
        	end
            damageString = Icon._Proc(damageType).." "..Math.round(val / pCount, 0.01)
        end
        -- Return damage string with icon for weapons that deal only a single element
        return damageString
    end
    
    local thisVal = GetDamage(Attack, Type, ByPellet)
    if (thisVal == 0) then
        return ""
    else
        return Math.round(thisVal, 0.01)
    end
end

local function getWeaponStanceList(Weapon)
    if (Weapon == nil or Weapon.Type ~= "Melee") then return nil end
    local stanceTable = {}
    if Weapon.Class == "Exalted Weapon" then
        stanceTable = getStances(Weapon.Name, Weapon.Conclave)
    else 
        stanceTable = getStances(Weapon.Class, Weapon.Conclave)
    end
    
    local result = ""
    
    for stanceName, Stance in pairs(stanceTable) do
        if (string.len(result) > 0) then
            result = result.."<br/>"
        end
        
        local polarity = ""
        local link = ""
        if Weapon.Class == "Exalted Weapon" then
            link = "[["..ModData["Mods"][stanceName].Link.."|"..stanceName.."]]"
        else
            polarity = " ("..Polarity._polarity(ModData["Mods"][stanceName].Polarity)..")"
            link = "[["..stanceName.."]]"
        end
        
        -- Adding tooltip
        result = result..Tooltip.getIcon(stanceName, "Mods")
        
        -- If this is a PvP Stance, add the disclaimer
        if (Stance.ConclaveOnly ~= nil and Stance.ConclaveOnly) then
            result = result.." (PvP Only)"
        end
    end
    
    return result
end
	
-- Gets the value of a certain stat of a weapon
local function getValue(Weapon, keyName, attackName)
	return SHARED_KEY_MAP[keyName] ~= nil and SHARED_KEY_MAP[keyName](Weapon) or
		GUN_KEY_MAP[keyName] ~= nil and GUN_KEY_MAP[keyName](Weapon) or 
		MELEE_KEY_MAP[keyName] ~= nil and MELEE_KEY_MAP[keyName](Weapon) or
		ATTACK_KEY_MAP[keyName] ~= nil and ATTACK_KEY_MAP[keyName](Weapon[attackName or Weapon['TooltipAttackDisplay'] or 'Attack1']) or
    	error('getValue(Weapon, keyName, attackName): Cannot get keyName "'..keyName..'" in '..mw.dumpObject(Weapon))
end

function p.getStanceWeaponList(frame)
    local stanceName = frame.args ~= nil and frame.args[1] or frame
    local Stance = StanceData[stanceName]
    assert(Stance ~= nil, "p.getStanceWeaponList(frame): "..stanceName.." not found")
    
    local weaps = getMeleeWeapons(Stance.WeaponType, Stance.ConclaveOnly)
    local result = ""
    
    for i, weap in Table.skpairs(weaps) do
        if (string.len(result) > 0) then
            result = result.."\n"
        end
        
        if (Stance.ConclaveOnly) then
            result = result.."*[[Conclave:"..weap.Name.."|"..weap.Name.."]]"
        else
            result = result.."*[["..weap.Name.."]]"
        end
        
        if (weap.StancePolarity == ModData["Mods"][stanceName].Polarity) then
            result = result.." ✓"
        end
    end
    
    return result
end

-- Used on Template:MeleeCategory
--Returns a list of stances for a weapon
--Adds the polarity for each stance
function p.getWeaponStanceList(frame)
    local WeaponName = frame.args ~= nil and frame.args[1] or frame
    local Weapon = p._getWeapon(WeaponName)
    return getWeaponStanceList(Weapon)
end

local function getWeaponGallery(Weapons)
    local result = "{| style=\"margin:auto;text-align:center;\""
    local nameRow = ""
    for i, Weapon in pairs(Weapons) do
        local theImage = Weapon.Image ~= nil and Weapon.Image or "Panel.png"
        if ((i - 1) % 4 == 0) then 
            result = result..nameRow.."\n|-" 
            nameRow = "\n|-"
        end
        result = result.."\n| style=\"width:165px\" |[[File:"..theImage.."|150px|link="..Weapon.Name.."]]"
        nameRow = nameRow.."\n| style=\"vertical-align: text-top;\" |[["..Weapon.Name.."]]"
    end
    result = result..nameRow
    result = result.."\n|}"
    return result
end

-- Used on Template:MeleeCategory
function p.getMeleeWeaponGallery(frame)
    local Type = frame.args ~= nil and frame.args[1] or frame
    if (Type == nil) then
        return ""
    end
    local WeapArray = getMeleeWeapons(Type)
    local result = "=="..Type.." Weapons==\n"
    result = result..getWeaponGallery(WeapArray)
    return result
end

-- Used on [[Mastery Rank]]
function p.getWeaponCount(frame)
    local Type = frame.args ~= nil and frame.args[1] or frame
    local getFullList = frame.args ~= nil and frame.args[2]
    local getAll = frame.args ~= nil and frame.args[3]
    local count = 0
    local fullList = ""
    for i, val in Table.skpairs(WeaponData["Weapons"]) do
        if (not Table.contains(WeaponData["IgnoreInCount"], i) and getAll ~= "true") or getAll == "true" then
            if (Type == nil or Type == "") then
                count = count + 1
                if (getFullList ~= nil) then fullList = fullList.."\n# "..val.Name end
            elseif (Type == "Warframe") then
                if ((val.Type == "Primary" or val.Type == "Secondary" or val.Type == "Melee") and val.Class ~= "Exalted Weapon") then
                    count = count + 1
                    if (getFullList ~= nil) then fullList = fullList.."\n# "..val.Name end
                end
            elseif (Type == "Archwing") then
                if ((val.Type == "Arch-Gun" or val.Type == "Arch-Melee") and val.Class ~= "Exalted Weapon") then
                    count = count + 1
                    if (getFullList ~= nil) then fullList = fullList.."\n# "..val.Name end
                end
            elseif (Type == "Rest") then
                if (val.Type ~= "Arch-Gun" and val.Type ~= "Arch-Melee" and val.Type ~= "Primary" and 
                		val.Type ~= "Secondary" and val.Type ~= "Melee" and val.Class ~= "Exalted Weapon") then
                    count = count + 1
                    if (getFullList ~= nil) then fullList = fullList.."\n# "..val.Name end
                end
            elseif (val.Type == Type and val.Class ~= "Exalted Weapon") then
                count = count + 1
                if (getFullList ~= nil) then fullList = fullList.."\n# "..val.Name end
            end
        end
    end
    if (getFullList ~= nil) then return fullList end
    return count
end

local function getSecondaryCategory(weapon)
    local class = getValue(weapon, "Class")
    if (class == "Thrown") then
        return "Thrown"
    elseif (class == "Dual Shotguns" or class == "Shotgun Sidearm") then
        return "Shotgun"
    else
        local trigger = getValue(weapon, "Trigger")
        if (trigger == "Semi-Auto" or trigger == "Burst") then
            return "Semi-Auto"
        elseif (trigger == "Auto" or trigger == "Auto-Spool") then
            return "Auto"
        end
    end
    return "Other"
end

local function getPrimaryCategory(weapon)
    local class = getValue(weapon, "Class")
    if (class == "Shotgun") then
        return "Shotgun"
    elseif (class == "Bow") then
        return "Bow"
    elseif (class == "Sniper Rifle") then
        return "Sniper"
    elseif (class == "Rifle") then
        local trigger = getValue(weapon, "Trigger")
        if (trigger == "Semi-Auto" or trigger == "Burst") then
            return "Semi-Auto"
        elseif (trigger == "Auto" or trigger == "Auto-Spool") then
            return "Auto"
        end
    end
    return "Other"
end

-- Used on [[Polarity]] page
function p.getPolarityTable(frame)
    local tableResult = { [[
{| style="width: 100%; border-collapse: collapse;" cellpadding="2" border="1"
|+ '''Weapons with Innate Polarities (ignoring Stance and Exilus slots)'''
! colspan="2" |Primaries
! colspan="2" |Secondaries
! colspan="2" |Melees
! colspan="2" |Arch-Guns
! colspan="2" |Arch-Melees]] }
    
    local filterBy = function(weaponType)
    	return function(weaponEntry) 
    		return (weaponEntry['Type'] == weaponType) and string.len(getValue(weaponEntry, "Polarities")) > 0
    	end
    end
    
    local meleeEntries = p._getWeapons(filterBy('Melee'))
    local pistolEntries = p._getWeapons(filterBy('Secondary'))
    local primaryEntries = p._getWeapons(filterBy('Primary'))
    local archGunEntries = p._getWeapons(filterBy('Arch-Gun'))
    local archMeleeEntries = p._getWeapons(filterBy('Arch-Melee'))
    
    local meleeCount = Table.size(meleeEntries)
    local pistolCount = Table.size(pistolEntries)
    local primaryCount = Table.size(primaryEntries)
    local archGunCount = Table.size(archGunEntries)
    local archMeleeCount = Table.size(archMeleeEntries)
    
    local maxLen = meleeCount
    if (pistolCount > maxLen) then maxLen = pistolCount end
    if (primaryCount > maxLen) then maxLen = primaryCount end
    if (archGunCount > maxLen) then maxLen = archGunCount end
    if (archMeleeCount > maxLen) then maxLen = archMeleeCount end
    
    for i = 1, maxLen, 1 do
        table.insert(tableResult, '|-')
        if (i <= primaryCount) then
            table.insert(tableResult, '| [['..primaryEntries[i]['Name']..']] ||'..Polarity._pols(primaryEntries[i]['Polarities']))
        else
            table.insert(tableResult, '| ||')
        end
        if (i <= pistolCount) then
            table.insert(tableResult, '| [['..pistolEntries[i]['Name']..']] ||'..Polarity._pols(pistolEntries[i]['Polarities']))
        else
            table.insert(tableResult, '| ||')
        end
        if (i <= meleeCount) then
            table.insert(tableResult, '| [['..meleeEntries[i]['Name']..']] ||'..Polarity._pols(meleeEntries[i]['Polarities']))
        else
            table.insert(tableResult, '| ||')
        end
        if (i <= archGunCount) then
            table.insert(tableResult, '| [['..archGunEntries[i]['Name']..']] ||'..Polarity._pols(archGunEntries[i]['Polarities']))
        else
            table.insert(tableResult, '| ||')
        end
        if (i <= archMeleeCount) then
            table.insert(tableResult, '| [['..archMeleeEntries[i]['Name']..']] ||'..Polarity._pols(archMeleeEntries[i]['Polarities']))
        else
            table.insert(tableResult, '| ||')
        end
    end
    table.insert(tableResult, '|}')
    return table.concat(tableResult, '\n')
end

local function buildCompareString(Val1, Val2, ValName, Digits, Addon, Words, Start)
    if (Val1 == nil or Val2 == nil) then
        return ""
    end
    local V1Str = Val1
    local V2Str = Val2
    if (Digits ~= nil) then
        V1Str = Math.round(Val1, Digits)
		V2Str = Math.round(Val2, Digits)
    end
    if (Addon ~= nil) then
        V1Str = V1Str..Addon
        V2Str = V2Str..Addon
    end
    local bigWord = Words ~= nil and Words[1] or "Higher"
    local smallWord = Words ~= nil and Words[2] or "Lower"
    local start = Start ~= nil and Start or "\n**"
    
    if (Val1 > Val2) then
        return start.." "..bigWord.." "..ValName.." ("..V1Str.." vs. "..V2Str..")"
    elseif (Val2 > Val1) then
        return start.." "..smallWord.." "..ValName.." ("..V1Str.." vs. "..V2Str..")"
    else
        return ""
    end
end

local function buildDamageTypeComparisonString(Attack1, Attack2)
    local result = ""
    --ipairs iterates in the order given in Elements, so IPS is always first
    for i, element in ipairs(Elements) do
        local damage1 = Attack1.Damage[element]
        local damage2 = Attack2.Damage[element]
        if (damage1 ~= nil or damage2 ~= nil) then
            if (damage1 == nil) then
                damage1 = 0
            end
            if (damage2 == nil) then
                damage2 = 0
            end
            result = result..buildCompareString(damage1, damage2, Icon._Proc(element, "text").." damage", 0.01, nil, {"Higher", "Lower"}, "\n***")
        end
    end
    return result
end

-- TODO: Refactor function contents to reduce number of nil checks (hasAttack() functions)
-- TODO: The above TODO can be done in conjunction with changing all the "Attack1" and "Attack2" keys
-- to a singular "Attack" key with indexed table elements
local function buildGunComparisonString(Weapon1, Weapon2, Conclave)
    local result = ""
    if (Conclave) then
        result = "* "..Weapon1.Name..", compared to [[Conclave:"..Weapon2.Name.."|"..Weapon2.Name.."]]:"
    else
        result = "* "..Weapon1.Name..", compared to [["..Weapon2.Name.."]]:"
    end
	
    if (hasAttack(Weapon1, "Attack1") and hasAttack(Weapon2, "Attack1")) then
        local Att1 = getAttack(Weapon1, "Attack1")
        local Att2 = getAttack(Weapon2, "Attack1")
        local dmgString = ""
        dmgString = dmgString..buildCompareString(GetDamage(Att1), GetDamage(Att2), "base damage", 0.01)
        dmgString = dmgString..buildDamageTypeComparisonString(Att1, Att2)
        if (string.len(dmgString) > 0 and GetDamage(Att1) == GetDamage(Att2)) then
            dmgString = "\n**Equal base damage, but different composition:"..dmgString
        end
        dmgString = dmgString..buildCompareString(GetDamage(Att1) * (Att1.Multishot or 1), GetDamage(Att2) * (Att2.Multishot or 1), "total damage", 0.01)
        result = result..dmgString
    end
    if (hasAttack(Weapon1, "Attack3")and hasAttack(Weapon2, "Attack3")) then
        local Att1 = getAttack(Weapon1, "Attack3")
        local Att2 = getAttack(Weapon2, "Attack3")
        if (Att1.CritChance ~= nil and Att2.CritChance ~= nil) then
            if (dontHasAttack(Weapon1, "Attack1")and dontHasAttack(Weapon2, "Attack1")) then
                result = result..buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "[[critical chance]]", 0.01, "%")
            end
            if (hasAttack(Weapon1, "Attack1")and hasAttack(Weapon2, "Attack1")) then
                result = result..buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "charged [[critical chance]]", 0.01, "%")
            end
        end
        if (dontHasAttack(Weapon1, "Attack1")and dontHasAttack(Weapon2, "Attack1")) then
            result = result..buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "[[critical multiplier]]", 0.01, "x")
        end
        if (hasAttack(Weapon1, "Attack1")and hasAttack(Weapon2, "Attack1")) then
            result = result..buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "charged [[critical multiplier]]", 0.01, "x")
        end
        if (Att1.StatusChance ~= nil and Att2.StatusChance ~= nil) then
            if (dontHasAttack(Weapon1, "Attack1")and dontHasAttack(Weapon2, "Attack1")) then
                result = result..buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance *
                100), "[[status chance]]", 0.01, "%")
            end
            if (hasAttack(Weapon1, "Attack1")and hasAttack(Weapon2, "Attack1")) then
                result = result..buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance *
                100), "charged [[status chance]]", 0.01, "%")
            end
        end
        if (dontHasAttack(Weapon1, "Attack1")and dontHasAttack(Weapon2, "Attack1")) then
            result = result..buildCompareString(GetDamage(Weapon1.Attack3), GetDamage(Weapon2.Attack3), "attack damage", 0.01)
        end
        if (hasAttack(Weapon1, "Attack1")and hasAttack(Weapon2, "Attack1")) then
            result = result..buildCompareString(GetDamage(Weapon1.Attack3), GetDamage(Weapon2.Attack3), "charge attack damage", 0.01)
        end
        -- TODO: Find a better way of finding out if an attack is a charge attack; maybe add Trigger = "Charge" to all of those attacks?
        -- result = result..buildCompareString(getValue(Weapon1, "ChargeTime", "Attack3"), getValue(Weapon2, "ChargeTime", "Attack3"), "charge time", 0.01, " s", {"Slower", "Faster"})
    end
    if (hasAttack(Weapon1, "Attack5") and hasAttack(Weapon2, "Attack5")) then
        result = result..buildCompareString(GetDamage(Weapon1.Attack5), GetDamage(Weapon2.Attack5), "area attack damage", 0.01)
    end
    if (hasAttack(Weapon1, "Attack3") and hasAttack(Weapon2, "Attack3")) then
        result = result..buildCompareString(GetDamage(Weapon1.Attack2), GetDamage(Weapon2.Attack2), "secondary attack damage", 0.01)
    --test code to fix stradavar wrong comparison
        local Att1 = getAttack(Weapon1, "Attack3")
        local Att2 = getAttack(Weapon2, "Attack3")
        if (Att1.CritChance ~= nil and Att2.CritChance ~= nil) then
            result = result..buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "secondary mode [[critical chance]]", 0.01, "%")
        end
        result = result..buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "secondary mode [[critical multiplier]]", 0.01, "x")

        if (Att1.StatusChance ~= nil and Att2.StatusChance ~= nil) then
            result = result..buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance * 100), "secondary mode [[status chance]]", 0.01, "%")
        end
        result = result..buildCompareString(Att1.FireRate, Att2.FireRate, "secondary mode [[fire rate]]", 0.01, " round(s)/sec")
    --end of test code
    end

--test code to fix tiberon prime comparison
    if Weapon1 == "Tiberon Prime" or Weapon2 == "Tiberon Prime" then
        if ((hasAttack(Weapon1, "Attack1") and hasAttack(Weapon2, "Attack2") and dontHasAttack(Weapon1, "Attack2")) or (hasAttack(Weapon1, "Attack2") and hasAttack(Weapon2, "Attack1") and dontHasAttack(Weapon2, "Attack2")))then
            local Att1 = getAttack(Weapon1, "Attack1")
            local Att2 = getAttack(Weapon2, "Attack1")
            if (Att1.CritChance ~= nil and Att2.CritChance ~= nil) then
                result = result..buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "[[critical chance]]", 0.01, "%")
            end
            result = result..buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "[[critical multiplier]]", 0.01, "x")

            if (Att1.StatusChance ~= nil and Att2.StatusChance ~= nil) then
                result = result..buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance * 100), "[[status chance]]", 0.01, "%")
            end
        end
    end


--end of test code to fix tiberon prime comparison

    if (hasAttack(Weapon1, "Attack1") and hasAttack(Weapon2, "Attack1") ) then
        local Att1 = getAttack(Weapon1, "Attack1")
        local Att2 = getAttack(Weapon2, "Attack1")
        if (Att1.CritChance ~= nil and Att2.CritChance ~= nil) then
            local ifCCAdded = 0
            if (dontHasAttack(Weapon1, "Attack3")and dontHasAttack(Weapon2, "Attack3") and dontHasAttack(Weapon1, "Attack2")and dontHasAttack(Weapon2, "Attack2")) then
                result = result..buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "[[critical chance]]", 0.01, "%")
                ifCCAdded = 1
            end
            if (hasAttack(Weapon1, "Attack3")and hasAttack(Weapon2, "Attack3")) then
                result = result..buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "base [[critical chance]]", 0.01, "%")
                ifCCAdded = 1
            end
            if (hasAttack(Weapon1, "Attack2")and hasAttack(Weapon2, "Attack2")) then
                result = result..buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "primary [[critical chance]]", 0.01, "%")
                ifCCAdded = 1
            end
            if (hasAttack(Weapon1, "Attack1")and hasAttack(Weapon2, "Attack1")) and ifCCAdded == 0 then
                result = result..buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "[[critical chance]]", 0.01, "%")
            end
        end
        local ifCDAdded = 0
        if (dontHasAttack(Weapon1, "Attack3")and dontHasAttack(Weapon2, "Attack3") and dontHasAttack(Weapon1, "Attack2")and dontHasAttack(Weapon2, "Attack2")) then
            result = result..buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "[[critical multiplier]]", 0.01, "x")
            ifCDAdded = 1
        end
        if (hasAttack(Weapon1, "Attack3")and hasAttack(Weapon2, "Attack3")) then
            result = result..buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "base [[critical multiplier]]", 0.01, "x")
            ifCDAdded = 1
        end
        if (hasAttack(Weapon1, "Attack2")and hasAttack(Weapon2, "Attack2")) then
            result = result..buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "primary [[critical multiplier]]", 0.01, "x")
            ifCDAdded = 1
        end
        if (hasAttack(Weapon1, "Attack1")and hasAttack(Weapon2, "Attack1")) and ifCDAdded == 0 then
            result = result..buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "[[critical multiplier]]", 0.01, "x")
        end
        if (Att1.StatusChance ~= nil and Att2.StatusChance ~= nil) then
            local ifStatusAdded = 0
            if (dontHasAttack(Weapon1, "Attack3")and dontHasAttack(Weapon2, "Attack3") and dontHasAttack(Weapon1, "Attack2")and dontHasAttack(Weapon2, "Attack2")) then
                result = result..buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance * 100), "[[status chance]]", 0.01, "%")
                ifStatusAdded = 1
            end
            if (hasAttack(Weapon1, "Attack3")and hasAttack(Weapon2, "Attack3")) then
                result = result..buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance * 100), "base [[status chance]]", 0.01, "%")
                ifStatusAdded = 1
            end
            if (hasAttack(Weapon1, "Attack2")and hasAttack(Weapon2, "Attack2")) then
                result = result..buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance * 100), "primary [[status chance]]", 0.01, "%")
                ifStatusAdded = 1
            end
            if (hasAttack(Weapon1, "Attack1")and hasAttack(Weapon2, "Attack1")) and ifStatusAdded == 0 then
                result = result..buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance * 100), "[[status chance]]", 0.01, "%")
            end
        end
        result = result..buildCompareString(Att1.FireRate, Att2.FireRate, "[[fire rate]]", 0.01, " round(s)/sec")
        result = result..buildCompareString(Att1.Multishot or 1, Att2.Multishot or 1, "[[multishot]]", 1, " projectile(s)")
        
        --Handling Damage Falloff
        if (Att1.Falloff ~= nil and Att2.Falloff == nil) then
            result = result.."\n** "..Att1.Falloff.StartRange.."m - "..Att1.Falloff.EndRange.."m damage falloff"
            if (Att1.Falloff.Reduction ~= nil) then
               result = result.." with up to "..(Att1.Falloff.Reduction * 100).."% damage reduction"
            end
        elseif (Att2.Falloff ~= nil and Att1.Falloff == nil) then
            result = result.."\n** No "..Att2.Falloff.StartRange.."m - "..Att2.Falloff.EndRange.."m damage falloff"
            if (Att2.Falloff.Reduction ~= nil) then
                result = result.." with up to "..(Att2.Falloff.Reduction * 100).."% damage reduction"
            end
        elseif (Att1.Falloff ~= nil and Att2.Falloff ~= nil) then
            result = result..buildCompareString(Att1.Falloff.StartRange, Att2.Falloff.StartRange, "range before damage falloff starts", 0.01, "m", {"Longer", "Shorter"})
            result = result..buildCompareString(Att1.Falloff.EndRange, Att2.Falloff.EndRange, "range before damage falloff ends", 0.01, "m", {"Longer", "Shorter"})
            if (Att1.Falloff.Reduction ~= nil and Att2.Falloff.Reduction ~= nil) then
                result = result..buildCompareString(Att1.Falloff.Reduction * 100, Att2.Falloff.Reduction * 100, "max damage reduction due to falloff", 0.01, "%", {"Larger", "Smaller"})
            end
        end
    end

    result = result..buildCompareString(Weapon1.Magazine, Weapon2.Magazine, "magazine", 1, " round(s)", {"Larger", "Smaller"})
    result = result..buildCompareString(Weapon1.MaxAmmo, Weapon2.MaxAmmo, "max ammo capacity", 1, " round(s)", {"Larger", "Smaller"})
    --If this is a weapon that regenerates ammo, flip the comparison
    if (Weapon1.ReloadStyle ~= nil and Weapon1.ReloadStyle == "Regenerate") then
        result = result..buildCompareString(Weapon1.Reload, Weapon2.Reload, "[[Reload|reload time]]", 0.01, " round(s)/sec", {"Faster", "Slower"})
    else
        result = result..buildCompareString(Weapon1.Reload, Weapon2.Reload, "[[Reload|reload time]]", 0.01, " s", {"Slower", "Faster"})
    end
    result = result..buildCompareString(Weapon1.Spool, Weapon2.Spool, "spool-up", 1, " round(s)", {"Slower", "Faster"})
    local Acc1 = getValue(Weapon1, "Accuracy")
    local Acc2 = getValue(Weapon2, "Accuracy")
    if (type(Acc1) == "number" and type(Acc2) == "number") then
        result = result..buildCompareString(Acc1, Acc2, "accurate", 0.01, nil, {"More", "Less"})
    end

    --Handling Syndicate radial effects
    if (Weapon1.SyndicateEffect ~= nil and Weapon2.SyndicateEffect == nil) then
        result = result.."\n** Innate [["..Weapon1.SyndicateEffect.."]] effect"
    elseif (Weapon2.SyndicateEffect ~= nil and Weapon1.SyndicateEffect == nil) then
        result = result.."\n** Lack of an innate [["..Weapon2.SyndicateEffect.."]] effect"
    elseif (Weapon1.SyndicateEffect ~= nil and Weapon2.SyndicateEffect ~= nil and Weapon1.SyndicateEffect ~= Weapon2.SyndicateEffect2) then
        result = result.."\n** Different innate [[Syndicate Radial Effects|Syndicate Effect]]: [["..Weapon1.SyndicateEffect.."]] vs. [["..Weapon2.SyndicateEffect.."]]"
    end

    --Handling Polarities
    local Pol1 = Weapon1.Polarities ~= nil and Weapon1.Polarities or {}
    local Pol2 = Weapon2.Polarities ~= nil and Weapon2.Polarities or {}
    local count1 = Table.size(Pol1)
    local count2 = Table.size(Pol2)
    local isDifferent = count1 ~= count2
    if (not isDifferent and count1 > 0) then
        table.sort(Pol1, function(x, y) return x < y end)
        table.sort(Pol2, function(x, y) return x < y end)
        for i, pol in pairs(Pol1) do
            if (pol ~= Pol2[i]) then
                isDifferent = true
            end
        end
    end

    if (isDifferent) then
        result = result.."\n** Different polarities ("..getValue(Weapon1, "Polarities").." vs. "..getValue(Weapon2, "Polarities")..")"
    end

    result = result..buildCompareString(Weapon1.Mastery, Weapon2.Mastery, "[[Mastery Rank]] required", 1)
    result = result..buildCompareString(Weapon1.Disposition, Weapon2.Disposition, "[[disposition]]", 0.01)
    result = result..buildCompareString(getValue(Weapon1, "AvgShotDmg"), getValue(Weapon2, "AvgShotDmg"), "average damage per shot", 0.01)
    result = result..buildCompareString(getValue(Weapon1, "BurstDps"), getValue(Weapon2, "BurstDps"), "burst DPS", 0.01)
    result = result..buildCompareString(getValue(Weapon1, "SustainedDps"), getValue(Weapon2, "SustainedDps"), "sustained DPS", 0.01)
    return result
end

-- TODO: use string.format() or tables when building resultant wikitext
local function buildMeleeComparisonString(Weapon1, Weapon2, Conclave)
    local result = ""
    if (Conclave) then
        result = "* "..Weapon1.Name..", compared to [[Conclave:"..Weapon2.Name.."|"..Weapon2.Name.."]]:"
    else
        result = "* "..Weapon1.Name..", compared to [["..Weapon2.Name.."]]:"
    end
    
    local dmgString = ""
    local Att1 = getAttack(Weapon1, "Attack1")
    local Att2 = getAttack(Weapon2, "Attack1")
    dmgString = dmgString..buildCompareString(GetDamage(Att1), GetDamage(Att2), "base damage", 0.01)
    dmgString = dmgString..buildDamageTypeComparisonString(Att1, Att2)
    if (string.len(dmgString) > 0 and GetDamage(Att1) == GetDamage(Att2)) then
        dmgString = "\n**Equal base damage, but different composition:"..dmgString    
    end
    result = result..dmgString
    
    
    if (Att1.CritChance ~= nil and Att2.CritChance ~= nil) then
        result = result..buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "[[critical chance]]", 0.01, "%")
    end
    result = result..buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "[[critical multiplier]]", 0.01, "x")
    
    if (Att1.StatusChance ~= nil and Att2.StatusChance ~= nil) then
        result = result..buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance * 100), "[[status chance]]", 0.01, "%")
    end
    result = result..buildCompareString(Att1.FireRate, Att2.FireRate, "[[attack speed]]", 0.01)
    -- Quick fix to passing in an empty string for polarity when comparing with an arch-melee weapon
    if (Weapon1.Type ~= "Arch-Melee" and Weapon2.Type ~= "Arch-Melee") then
	    result = result..buildCompareString(getValue(Weapon1, "ComboDur"), getValue(Weapon2, "ComboDur"), "Combo Duration", 1, " s")
	    result = result..buildCompareString(getValue(Weapon1, "BlockAngle"), getValue(Weapon2, "BlockAngle"), "Block Angle", 1, "&#176;")
        result = result..buildCompareString(Polarity._polarity(Weapon1.StancePolarity), Polarity._polarity(Weapon2.StancePolarity), "Stance Polarity", nil, nil, {"Different", "Different"})
    end
    result = result..buildCompareString(getValue(Weapon1, "MeleeRange"), getValue(Weapon2, "MeleeRange"), "Range", 0.01, " m")
    
    --Handling Polarities
    local Pol1 = Weapon1.Polarities ~= nil and Weapon1.Polarities or {}
    local Pol2 = Weapon2.Polarities ~= nil and Weapon2.Polarities or {}
    local count1 = Table.size(Pol1)
    local count2 = Table.size(Pol2)
    local isDifferent = count1 ~= count2
    if (not isDifferent and count1 > 0) then
        table.sort(Pol1, function(x, y) return x < y end)
        table.sort(Pol2, function(x, y) return x < y end)
        for i, pol in pairs(Pol1) do
            if (pol ~= Pol2[i]) then
                isDifferent = true
            end
        end
    end
    
    if (isDifferent) then
        result = result.."\n** Different polarities ("..getValue(Weapon1, "Polarities").." vs. "..getValue(Weapon2, "Polarities")..")"
    end
    
    result = result..buildCompareString(Weapon1.Mastery, Weapon2.Mastery, "[[Mastery Rank]] required", 1)
    result = result..buildCompareString(Weapon1.Disposition, Weapon2.Disposition, "[[disposition]]", 0.01)
    return result
end

function p.buildComparison(frame)
    local weaponName1 = frame.args[1]
    local weaponName2 = frame.args[2]

    assert(weaponName1 ~= '' and weaponName2 ~= '', 'p.buildComparison(frame): Must compare two weapons')
    
    local Weapon1 = p._getWeapon(weaponName1)
    local Weapon2 = p._getWeapon(weaponName2)
    
    local comparisonString = ''
    if (Weapon1.Type == 'Melee' or Weapon1.Type == 'Arch-Melee') then
        comparisonString = buildMeleeComparisonString(Weapon1, Weapon2)
    else
        comparisonString = buildGunComparisonString(Weapon1, Weapon2)
    end
    return comparisonString..'[[Category:Automatic Comparison]]'
end

function p.buildConclaveComparison(frame)
    local weaponName1 = frame.args[1]
    local weaponName2 = frame.args[2]
    
    assert(weaponName1 ~= '' and weaponName2 ~= '', 'p.buildConclaveComparison(frame): Must compare two weapons')
    
    local Weapon1 = p._getConclaveWeapon(weaponName1)
    local Weapon2 = p._getConclaveWeapon(weaponName2)
    
    local comparisonString = ''
    if (Weapon1.Type == 'Melee' or Weapon1.Type == 'Arch-Melee') then
        comparisonString = buildMeleeComparisonString(Weapon1, Weapon2, true)
    else
        comparisonString = buildGunComparisonString(Weapon1, Weapon2, true)
    end
    return comparisonString..'[[Category:Automatic Comparison]]'
end

function p.buildAutoboxCategories(frame)
    local WeapName = frame.args ~= nil and frame.args[1] or frame
    local Weapon = p._getWeapon(WeapName)
    local result = { "[[Category:Automatic Weapon Box]][[Category:Weapons]]" }
    if (Weapon == nil or (Weapon.IgnoreCategories ~= nil and Weapon.IgnoreCategories)) then
        return ""
    end
    table.insert(result, categoryMap[Weapon['Class']]..categoryMap[Weapon['Trigger']]..categoryMap[Weapon['Type']])
    
    -- TODO: Archive this category b/c not sure how important it is or how often
    -- it is used; augments are moved out of M:Weapons/data and should use M:Mods/data
    local augments = getAugments(Weapon)
    if (Table.size(augments) > 0) then
        table.insert(result, "[[Category:Augmented Weapons]]")
    end
    
    -- Adding appropriate categories to page based on weapon's categorical traits
    for _, trait in pairs(Weapon['Traits'] or {}) do
    	table.insert(result, categoryMap[trait])
    end
    
    local attack = nil
    if (hasAttack(Weapon, "Attack1")) then
        attack = getAttack(Weapon, "Attack1")
    elseif (hasAttack(Weapon, "Attack3")) then
        attack = getAttack(Weapon, "Attack3")
    end
    
    if (attack ~= nil) then
        local bestPercent, bestElement = getDamageBias(attack)
        if (bestElement == "Impact" or bestElement == "Puncture" or bestElement == "Slash") then
            if (bestPercent > .38) then
                table.insert(result, "[[Category:"..bestElement.." Damage Weapons]]")
            else
                table.insert(result, "[[Category:Balanced Physical Damage Weapons]]")
            end
        end
        
        for key, value in Table.skpairs(attack.Damage) do
            if (key ~= "Impact" and key ~= "Puncture" and key ~= "Slash") then
                table.insert(result, "[[Category:"..key.." Damage Weapons]]")
            end
        end
    end
    
    return table.concat(result)
end

---	Builds a table that lists out all weapons with a certain damage type
--	and the percentage that it makes up of their base damage of the attack specified
--	in their tooltip on the wiki.
--	@function		p.buildDamageTypeTable
--	@param			{table} frame Frame object
--	@returns		{string} Wikitext of resultant wikitable
function p.buildDamageTypeTable(frame)
    local damageType = frame.args ~= nil and frame.args[1] or frame
    local Weapons = {}
    local WeapArray = p._getWeapons(function(weaponEntry)
    	-- Want to ignore Kitgun entries which have 0 as placeholder damage values
    	if (weaponEntry['Type'] == 'Kitgun' or weaponEntry['Type'] == 'Primary Kitgun') then
    		return false
    	end
    	local attackEntry = getAttack(weaponEntry, weaponEntry['TooltipAttackDisplay'] or 'Attack1')
    	assert(attackEntry ~= nil, 'p.buildDamageTypeTable(frame): "'..weaponEntry.Name..'" has no attack entry for "Attack1" or attack in "TooltipAttackDisplay"')
        local dmg, element = getDamageBias(attackEntry)
        return element == damageType
    end)
    
    local function weapLink(weap)
        return weap.Link ~= nil and '[['..weap.Link..'|'..weap.Name..']]' or '[['..weap.Name..']]'
    end
    
    local procString = Icon._Proc(damageType, 'text')
    local procShort = Icon._Proc(damageType)
    local result = ''
    local tHeader = string.format([[
{| class = "listtable sortable" style="margin:auto;"
|-
! Name !! Type !! Class !! %s !! %s%%
]], procString, procShort)

    local tRows = {}
    for i, Weapon in pairs(WeapArray) do
        local thisRow = [[
|-
| %s || %s || %s || %s || data-sort-value="%s" | %s]]
        local weaponLink = weapLink(Weapon)
        assert(Weapon.Type ~= nil, 'p.buildDamageTypeTable(frame): "'..weaponLink..'" has a nil value for Type"')
        local attack = Weapon['TooltipAttackDisplay'] or 'Attack1'
        local damageTypeVal = getValue(Weapon, damageType, attack)
        local damageBias = getValue(Weapon, 'DamageBias', attack)
        thisRow = string.format(thisRow, 
        	weaponLink,
        	Weapon.Type,
        	getValue(Weapon, 'Class'),
        	damageTypeVal,
        	string.match(damageBias, '(%d*%.?%d+)%%'),
        	damageBias
    	)
        table.insert(tRows, thisRow)
    end
    result = tHeader..table.concat(tRows, '\n')..'\n|}'
    return result
end

-- Used in [[Template:EquipmentUnlock]]
function p.getMasteryShortList(frame)
    local WeaponType = frame.args[1]
    local MasteryRank = tonumber(frame.args[2])
        
    local weapArray = p._getWeapons(function(x) 
                                    if (x.Type ~= nil and x.Mastery ~= nil) then
                                        return x.Type == WeaponType and x.Mastery == MasteryRank
                                    else
                                        return false
                                    end
                                end)
    
    local result = ''
    local name = ''
    local shortList = p._shortLinkList(weapArray, true)
    for i, pair in Table.skpairs(shortList) do
        if (string.len(result) > 0) then result = result..' • ' end
        result = result..pair
    end
    return frame:preprocess(result)
end

-- Used in [[Riven Mods/Weapon Dispos]]
function p.getRivenDispositionTable(frame)
    local weaponType = frame.args[1]
    local ranges = { 1550, 1300, 1100, 899, 699, 499 }
    local result = {'{| class="article-table" border="0" cellpadding="1" cellspacing="1" style="width: 100%" \n|-'}

    for i = 1, 5 do
        table.insert(result, '\n! scope="col" style="text-align:center;"|'..Icon._Dis(ranges[i]/1000))
    end
    table.insert(result, '\n|-')
    
    for dispoRating = 5, 1, -1 do
        table.insert(result, '\n| style="vertical-align:top; font-size:small" |')
        -- Filtering out weapons that are between certain disposition ranges
    	local weapArray = p._getWeapons(
    		function(weaponEntry)
	            if (weaponEntry['Type'] ~= nil and weaponEntry['Disposition'] ~= nil) then
	                return (weaponType == 'All' or weaponEntry['Type'] == weaponType) and
	                	math.min(math.floor(5 * (weaponEntry['Disposition'] - (weaponEntry['Disposition'] < 1 and 0.3 or 0.309))), 5) == dispoRating
	            else
	                return false
            	end
            end)
        
        -- Building a list of weapons with a particular disposition rating (e.g. 5 dots)
        local weaponDispoList = {}
        -- Want to iterate in descending order so highest disposition weapon is near the top of table
        local descendingOrder = function(a, b)
        	-- a and b are number indexes
        	return weapArray[a]['Disposition'] > weapArray[b]['Disposition']
        end
        for _, weaponEntry in Table.skpairs(weapArray, descendingOrder) do
        	table.insert(weaponDispoList, '\n* [['..weaponEntry['Name']..']] ('..weaponEntry['Disposition']..')')
        end
        
        table.insert(result, table.concat(weaponDispoList))
    end
    table.insert(result, '\n|}')
    
    return table.concat(result)
end

function p.getConclaveList(frame)
    local WeaponType = frame.args[1]
        
    local weapArray = p._getWeapons(function(x) 
                                    if (x.Type ~= nil and x.Conclave ~= nil) then
                                        return x.Type == WeaponType and x.Conclave
                                    else
                                        return false
                                    end
                                end)
    
    local result = ''
    local shortList = p._shortLinkList(weapArray, false)
    for i, pair in Table.skpairs(shortList) do
        result = result..'\n* '..pair
    end
    return result
end

-- and we are back... new table building functions !

local function buildCompRow(tableHeaders, Weapon)
    local styleString = ''--'border: 1px solid lightgray;'
    local result =  {}
    local valueName = nil
    local attackName = nil
    
    for i, headerLine in ipairs(tableHeaders) do
        valueName = headerLine[1]
        attackName = headerLine[3] ~= nil and headerLine[3] or nil
        
    	local value = getValue(Weapon, valueName, attackName)
    	if (type(value) == 'number') then
    		value = Math.round(value, 0.01)
    	end
    	table.insert(result, 'style="'..styleString..'"|'..(value or 'N/A'))
    end
	return '|-\n|'..table.concat(result, '||')
end

--- Builds comparison table as seen on [[Weapon Comparison]].
--  @function       buildCompTable
local function buildCompTable(tableHeaders, Weapons)
    local styleString = 'border: 1px solid black;border-collapse: collapse;'
    local result = {}
    table.insert(result, '{| cellpadding="1" cellspacing="0" class="listtable sortable" style="font-size:11px;"')
    for _, headerLine in ipairs(tableHeaders) do
    	if (headerLine[1] == 'DamageBias' or headerLine[1] == 'CritMultiplier') then
			table.insert(result, '! style="'..styleString..'" data-sort-type="number"|'..headerLine[2])
    	else
        	table.insert(result, '! style="'..styleString..'"|'..headerLine[2])
        end
    end

    for _, Weap in pairs(Weapons) do
        local rowStr = buildCompRow(tableHeaders, Weap)
        table.insert(result, rowStr)
    end
    table.insert(result, '|}[[Category:Automatic Comparison Table]]')
    return table.concat(result, '\n')
end

--- Builds comparison table of gun stats as seen on [[Weapon Comparison]].
--  @function       p.getCompTableGuns
function p.getCompTableGuns(frame)
    local Catt = frame.args ~= nil and frame.args[1]
    local Type = frame.args ~= nil and frame.args[2] or nil
    if (Type == "All") then Type = nil end
    local WeapArray = {}
    if (Catt == "Primary") then WeapArray = p._getWeapons(function(x) 
                                if (getValue(x, "Type") == "Primary") then
                                    if (Type ~= nil) then return getPrimaryCategory(x) == Type else return true end
                                end
                                    return false
                                end)
    elseif (Catt == "Secondary") then WeapArray = p._getWeapons(function(x) 
                                if (getValue(x, "Type") == "Secondary") then
                                    if (Type ~= nil) then return getSecondaryCategory(x) == Type else return true end
                                end
                                    return false
                                end)
    elseif (Catt == "Arch-Gun") then WeapArray = p._getWeapons(function(x) 
                                	return getValue(x, "Type") == "Arch-Gun"
                                end)
    elseif (Catt == "Arch-Gun (Atmosphere)") then WeapArray = p._getWeapons(function(x) 
                            	return getValue(x, "Type") == "Arch-Gun (Atmosphere)"
                            end)
    elseif (Catt == "Robotic") then WeapArray = p._getWeapons(function(x) 
                                    return getValue(x, "Type") == "Robotic"
                                end)
    elseif (Catt == "Amp") then WeapArray = p._getWeapons(function(x) 
                                return getValue(x, "Type") == "Amp"
                            end)
    else
    	error('p.getCompTableGuns(frame): Wrong gun weapon class'..
    		'(use "Primary", "Secondary", "Arch-Gun", "Arch-Gun (Atmosphere)", "Robotic", or "Amp")'..
    		'[[Category:Invalid Comp Table]]')
    end

    local tableHeaders = { {"NameLink", "Name"} }
    table.insert(tableHeaders, { "Trigger", "[[Fire Rate|Trigger]]" })
    table.insert(tableHeaders, { "AttackName", "Attack" })
    table.insert(tableHeaders, { "DamageBias", "Main<br/>Element" })
    table.insert(tableHeaders, { "Damage", "Base<br/>[[Damage|Dmg]]" })
    table.insert(tableHeaders, { "CritChance", "[[Critical Chance|Crit]]" })
    table.insert(tableHeaders, { "CritMultiplier", "[[Critical multiplier|Crit<br/>Dmg]]" })
    table.insert(tableHeaders, { "AvgShotDmg", "Avg<br/>Shot" })
    table.insert(tableHeaders, { "BurstDps", "Burst<br/>DPS" })
    table.insert(tableHeaders, { "SustainedDps", "Sust<br/>DPS" })
    table.insert(tableHeaders, { "StatusChance", "[[Status Chance|Status]]" })
    table.insert(tableHeaders, { "AvgProcPerSec", "[[Status Chance|Avg. Procs]]/<br/>s" })
    table.insert(tableHeaders, { "CompTableFireRate", "[[Fire Rate|Fire<br/>Rate]]" })
    table.insert(tableHeaders, { "Disposition", "[[Riven Mods#Disposition|Dispo]]" })
    table.insert(tableHeaders, { "Mastery", "[[Mastery Rank|MR]]" })
    table.insert(tableHeaders, { "Magazine", "[[Ammo#Magazine Capacity|Mag<br/>Size]]" })
    table.insert(tableHeaders, { "MaxAmmo", "[[Ammo|Ammo<br/>Cap]]" })
    table.insert(tableHeaders, { "Reload", "[[Reload Speed|Reload]]" })
    table.insert(tableHeaders, { "ShotType", "Shot<br/>Type" })
    table.insert(tableHeaders, { "PunchThrough", "[[Punch Through|PT]]" })
    table.insert(tableHeaders, { "Accuracy", "[[Accuracy]]" })
    table.insert(tableHeaders, { "Introduced", "Intro" })
    
    return buildCompTable(tableHeaders, WeapArray)
end

--- Builds comparison table of gun Conclave stats as seen on [[Weapon Comparison/Conclave]].
--  @function       p.getCompTableConclaveGuns
function p.getCompTableConclaveGuns(frame)
    local Catt = frame.args ~= nil and frame.args[1]
    local Type = frame.args ~= nil and frame.args[2] or nil
    if (Type == "All") then Type = nil end
    local WeapArray = {}
    if (Catt == "Primary") then WeapArray = p.getConclaveWeapons(function(x) 
                                if (getValue(x, "Type") == "Primary") then
                                    if (Type ~= nil) then return getPrimaryCategory(x) == Type else return true end
                                end
                                    return false
                                end)
    elseif (Catt == "Secondary") then WeapArray = p.getConclaveWeapons(function(x) 
                                if (getValue(x, "Type") == "Secondary") then
                                    if (Type ~= nil) then return getSecondaryCategory(x) == Type else return true end
                                end
                                    return false
                                end)
    else
    	error('p.getCompTableConclaveGuns(frame): Wrong gun weapon class for Conclave (use "Primary" or "Secondary")[[Category:Invalid Comp Table]]')
    end
    
    local tableHeaders = { {"Name", "Name"} }
    table.insert(tableHeaders, { "Trigger", "[[Fire Rate|Trigger Type]]" })
    table.insert(tableHeaders, { "DamageBias", "Main<br/>Element" })
    table.insert(tableHeaders, { "Damage", "[[Damage]]" })
    table.insert(tableHeaders, { "HeadshotMultiplier", "HS Multiplier" })
    table.insert(tableHeaders, { "ShotType", "Shot<br/>Type" })
    table.insert(tableHeaders, { "CompTableFireRate", "[[Fire Rate]]" })
    table.insert(tableHeaders, { "Magazine", "[[Ammo#Magazine Capacity|Magazine Size]]" })
    table.insert(tableHeaders, { "Reload", "[[Reload Speed|Reload Time]]" })
    table.insert(tableHeaders, { "Mastery", "[[Mastery Rank]]" })
    table.insert(tableHeaders, { "Introduced", "Introduced" })
    
    return buildCompTable(tableHeaders,WeapArray)
end

--- Builds comparison table of melee stats as seen on [[Weapon Comparison]].
--  @function       p.getCompTableMelees
function p.getCompTableMelees(frame)
    --Changed formatting, now only takes type since only class handled is Melee
    --Keeping old formatting to avoid breaking pages
    local Type = frame.args ~= nil and frame.args[2] or nil
    if (Type == nil) then Type = frame.args ~= nil and frame.args[1] or nil end 
    if (Type == "All") then Type = nil end
    local WeapArray = {}
    WeapArray = getMeleeWeapons(Type)
    
    local tableHeaders = { {"NameLink", "Name"} }
    table.insert(tableHeaders, { "Class", "Type" })
    table.insert(tableHeaders, { "DamageBias", "Main<br/>Element" })
    table.insert(tableHeaders, { "Damage", "[[Damage|Normal]]" })
    table.insert(tableHeaders, { "HeavyAttack", "[[Melee#Heavy Attack|Heavy]]" })
    table.insert(tableHeaders, { "SlamAttack", "[[Melee#Slam Attack|Slam]]" })
    table.insert(tableHeaders, { "SlideAttack", "[[Melee#Slide Attack|Slide]]" })
    table.insert(tableHeaders, { "MeleeRange", "[[Melee#Range|Range]]" })
    table.insert(tableHeaders, { "SlamRadius", "[[Melee#Slam Attack|Slam Radius]]" })
    table.insert(tableHeaders, { "FireRate", "[[Attack Speed|Speed]]" })
    table.insert(tableHeaders, { "CritChance", "[[Critical Chance|Crit]]" })
    table.insert(tableHeaders, { "CritMultiplier", "[[Critical multiplier|Crit Dmg]]" })
    table.insert(tableHeaders, { "StatusChance", "[[Status Chance|Status]]" })
    table.insert(tableHeaders, { "Disposition", "[[Riven Mods#Disposition|Dispo]]" })
    table.insert(tableHeaders, { "FollowThrough", "[[Follow Through|Follow<br />Through]]" })
    table.insert(tableHeaders, { "BlockAngle", "[[Blocking|Block<br />Angle]]" })
    table.insert(tableHeaders, { "Mastery", "[[Mastery Rank|MR]]" })
    table.insert(tableHeaders, { "StancePolarity", "[[Stance]]" })
    table.insert(tableHeaders, { "Introduced", "Intro" })
    
    return buildCompTable(tableHeaders, WeapArray)
end

--- Builds comparison table of melee conclave stats as seen on [[Weapon Comparison/Conclave]].
--  @function       p.getCompTableConclaveMelees
function p.getCompTableConclaveMelees(frame)
    local Type = frame.args ~= nil and frame.args[1] or nil
    if (Type == "All") then Type = nil end
    local WeapArray = {}
    WeapArray = getConclaveMeleeWeapons(Type)
    
    local tableHeaders = { {"Name", "Name"} }
    table.insert(tableHeaders, { "Class", "Type" })
    table.insert(tableHeaders, { "Damage", "[[Damage|Normal]]" })
    table.insert(tableHeaders, { "SlideAttack", "[[Melee#Slide Attack|Slide]]" })
    table.insert(tableHeaders, { "FireRate", "[[Attack Speed]]" })
    table.insert(tableHeaders, { "Mastery", "[[Mastery_Rank|Mastery Rank]]" })
    table.insert(tableHeaders, { "StancePolarity", "[[Stance]]" })
    table.insert(tableHeaders, { "Introduced", "Introduced" })
    
    return buildCompTable(tableHeaders, WeapArray)
end

--- Builds comparison table of arch-melee stats as seen on [[Weapon Comparison]].
--  @function       p.getCompTableArchMelees
function p.getCompTableArchMelees(frame)
    local WeapArray = {}
    WeapArray = p._getWeapons(function(x) 
                                    return getValue(x, "Type") == "Arch-Melee"
                                end)
                                
    local tableHeaders = { {"NameLink", "Name"} }
    table.insert(tableHeaders, { "DamageBias", "Main<br/>Element" })
    table.insert(tableHeaders, { "Damage", "[[Damage|Normal]]" })
    table.insert(tableHeaders, { "FireRate", "[[Attack Speed]]" })
    table.insert(tableHeaders, { "CritChance", "[[Critical Chance]]" })
    table.insert(tableHeaders, { "CritMultiplier", "[[Critical multiplier|Critical Damage]]" })
    table.insert(tableHeaders, { "StatusChance", "[[Status Chance]]" })
    table.insert(tableHeaders, { "Mastery", "[[Mastery Rank]]" })
    table.insert(tableHeaders, { "Introduced", "Introduced" })
    
    return buildCompTable(tableHeaders, WeapArray)
end

--- Builds comparison table of projectile flight speeds as seen on [[Projectile Speed]].
--  @function       p.getCompTableSpeedGuns
--	@param			{table} frame Frame object
--	@returns		{string} Wikitext of resultant wikitable
function p.getCompTableSpeedGuns(frame)
    local weaponType = frame.args ~= nil and frame.args[1]
    local weaponClass = frame.args ~= nil and frame.args[2] or nil
    if (weaponClass == "All") then weaponClass = nil end
    local weaponList = {}
    
    if (weaponType == "Primary") then
    	weaponList = p._getWeapons(function(x) 
                            if (getValue(x, "Type") == "Primary") then
                                return (weaponClass ~= nil) and getPrimaryCategory(x) == weaponClass or true
                            end
                            return false
                        end)
    elseif (weaponType == "Secondary") then
    	weaponList = p._getWeapons(
    		function(x) 
                if (getValue(x, "Type") == "Secondary") then
                    return (weaponClass ~= nil) and getSecondaryCategory(x) == weaponClass or true
                end
                return false
            end)
    elseif (weaponType == "Robotic") then
    	weaponList = p._getWeapons(
    		function(x) 
                return getValue(x, "Type") == "Robotic"
            end)
    elseif (weaponType == "Arch-Gun") then
    	weaponList = p._getWeapons(
    		function(x) 
	            return getValue(x, "Type") == "Arch-Gun"
	        end)
    else
    	error('p.getCompTableSpeedGuns(frame): Wrong gun weapon class '..
    		'(use "Primary", "Secondary", "Robotic", or "Arch-Gun")[[Category:Invalid Comp Table]]')
    end
    
    -- special sorting for projectile weapons
    local projectileWeaponList = {}
    for k, Weapon in ipairs(weaponList) do
    	local shotType = getValue(Weapon, "ShotType")
        if (shotType == "Projectile" or shotType == "Thrown") then
            table.insert(projectileWeaponList, Weapon)
		end
    end

    local tableHeaders = { { "NameLink", "Name" } }
    table.insert(tableHeaders,{ "Class", "Class" })
    table.insert(tableHeaders,{ "ShotSpeed", "Flight Speed" })
    
    return buildCompTable(tableHeaders, projectileWeaponList)
end

--- Builds comparison table of glaive melees' projectile flight speeds as seen on [[Projectile Speed]].
--  @function       p.getCompTableSpeedMelees
function p.getCompTableSpeedMelees(frame)
    local weaponList = p._getWeapons(
    		function(x)
            	return getValue(x, "Class") == "Glaive"
        	end)

    local tableHeaders = { {"NameLink", "Name"} }
    table.insert(tableHeaders, { "ShotSpeed", "Flight Speed", "Attack2" })
    
    return buildCompTable(tableHeaders, weaponList)
end

return p