WARFRAME Wiki
WARFRAME Wiki
m (fix to conclave comp table)
(Adding weapons to Category:Hit-Scan Weapons and Category:Projectile Weapons)
 
Line 1: Line 1:
  +
--- '''Weapons''' contains all of [[WARFRAME]]'s [[Weapons|weapon]] data.<br />
  +
--
  +
-- @module weapons
  +
-- @alias p
  +
-- @attribution [[User:Cephalon Scientia|Cephalon Scientia]]
  +
-- @attribution [[User:FINNER|FINNER]]
  +
-- @attribution [[User:Falterfire|Falterfire]]
  +
-- @attribution [[User:Gigamicro|Gigamicro]]
  +
-- @attribution [[User:Flaicher|Flaicher]]
  +
-- @attribution [[User:Synthtech|Synthtech]]
  +
-- @image IconPrimaryWeaponRifle.png
  +
-- @require [[Module:StatObject]]
  +
-- @require [[Module:DamageTypes]]
  +
-- @require [[Module:Polarity]]
  +
-- @require [[Module:Math]]
  +
-- @require [[Module:Table]]
  +
-- @require [[Module:Tooltips]]
  +
-- @require [[Module:Version]]
  +
-- @require [[Module:Stances/data]]
  +
-- @require [[Module:Weapons/data]]
  +
-- @require [[Module:Weapons/Conclave/data]]
  +
-- @release stable
  +
-- <nowiki>
  +
  +
-- TODO: Add LuaDoc style comments to new functions
 
local p = {}
 
local p = {}
   
local WeaponData = mw.loadData( "Module:Weapons/data" )
+
local Delay = require([[Module:Delay]])
local ModularData = mw.loadData( "Module:Modular/data" )
+
local WeaponData = Delay.require([[Module:Weapons/data]])
local ConclaveData = mw.loadData( "Module:Weapons/Conclave/data" )
+
local WarframeData = Delay.require([[Module:Warframes/data]]) -- for use in p.getListWithWarframes
local Icon = require( "Module:Icon" )
+
local ConclaveData = Delay.require([[Module:Weapons/Conclave/data]])
local Version = require( "Module:Version" )
+
local Tooltip = Delay.require([[Module:Tooltips]]) -- full, icon
local Shared = require( "Module:Shared" )
+
local Version = Delay.require([[Module:Version]]) -- _getVersion, _getVersionDate
  +
local Polarity = Delay.require([[Module:Polarity]]) -- _pols, _polarity
local Elements = {
 
  +
local Math = Delay.require([[Module:Math]]) -- formatnum
"Impact", "Puncture", "Slash", "Heat", "Cold", "Toxin",
 
  +
local Table = Delay.require([[Module:Table]]) -- size, skpairs
"Electricity", "Blast", "Corrosive", "Radiation", "Magnetic", "Gas",
 
  +
local iterationOrderArray = require([[Module:DamageTypes]]).iterationOrderArray
"Viral", "Void", "True"
 
  +
}
 
  +
-- TODO: Should decouple from localized names for internationalization
local Physical = {"Impact", "Puncture", "Slash"}
 
local UseDefaultList = {
+
local VARIANT_LIST = {
  +
"Prime", "Prisma", "Wraith", "Vandal", "Vaykor", "Synoid", "Telos", "Secura",
"NoiseLevel", "AmmoType", "MaxAmmo", "Disposition",
 
  +
"Sancti", "Rakta", "Mara", "Carmine", "Ceti", "Dex", "MK1", "Kuva", "Tenet"
"HeadshotMultiplier"
 
}
 
local VariantList = {
 
"Prime", "Prisma", "Wraith", "Vandal", "Vaykor", "Synoid",
 
"Telos", "Secura", "Sancti", "Rakta", "Mara", "MK1", "Kuva"
 
 
}
 
}
   
  +
table.unpack = table.unpack or unpack
function p.doPlural(Text, Value)
 
if(tonumber(Value) == 1) then
 
Text = string.gsub(Text, "(<.+>)", "")
 
else
 
Text = string.gsub(Text, "<(.+)>", "%1")
 
end
 
return Text
 
end
 
   
  +
local StatObject = require [[Module:StatObject]]
function p.isVariant(WeapName)
 
  +
p.__StatObject = StatObject
for i, var in pairs(VariantList) do
 
  +
local statRead = StatObject.statRead
if(string.find(WeapName, var)) then
 
  +
local statFormat = StatObject.statFormat
local baseName = string.gsub(WeapName, " ?"..var.." ?-?", "")
 
return true, var, baseName
 
end
 
end
 
   
  +
local indexes = StatObject.meta.indexes
return false, "Base", WeapName
 
  +
local ors = StatObject.meta.ors
end
 
  +
local unpacks = StatObject.meta.unpacks
   
  +
local passes = StatObject.meta.passes
function p.buildName(BaseName, Variant)
 
  +
local percent = StatObject.meta.percent
if(Variant == nil or Variant == "Base" or Variant == "") then
 
  +
local percents = StatObject.meta.percents
return BaseName
 
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
 
   
  +
--- Gets the attack entry from weapon entry.
--It's a bit of a mess, but this is for compressing a list with variants
 
  +
-- @function p._getAttack
--So if a list has Braton, Braton Prime, and MK1-Braton it'll list as
 
  +
-- @param {table} weap Weapon entry
-- Braton (MK1, Prime)
 
  +
-- @param[opt] {number|table} atk Attacks table index or Attack entry
function p.shortLinkList(Weapons, tooltip)
 
  +
-- @return {table} A single weapon+attack struct
--First grabbing all the pieces and stashing them in a table
 
  +
local function getWeaponAttack(weap, atk)
local baseNames = {}
 
  +
if type(atk) == 'number' then return StatObject.getStruct2(weap,weap.Attacks[atk]) end
for key, weap in Shared.skpairs(Weapons) do
 
  +
if weap.AttackName then return weap end
local isVar, varType, baseName = p.isVariant(weap.Name)
 
  +
if type(atk) == 'table' then return StatObject.getStruct2(weap,atk) end
if(baseNames[baseName] == nil) then baseNames[baseName] = {} end
 
  +
local key = atk or weap['_TooltipAttackDisplay'] or 1
table.insert(baseNames[baseName], varType)
 
  +
if weap.Attacks == nil then
end
 
  +
error('p._getWeaponAttack(weap, atk): Attacks table is nil in '..mw.dumpObject(weap))
 
  +
end
--Then the fun part: Pulling the table together
 
  +
return StatObject.getStruct2(weap,weap.Attacks[key])
local result = {}
 
for baseName, variants in Shared.skpairs(baseNames) do
 
--So first, check if "Base" is in the list
 
--Because if it isn"t, list all variants separately
 
 
if(Shared.contains(variants, "Base")) then
 
table.sort(variants)
 
--First, get the basic version
 
local thisRow = ""
 
if(tooltip) then
 
thisRow = "<span class=\"weapon-tooltip\" data-param=\""..baseName.."\" style=\"white-space:pre\">[["..baseName.."]]</span>"
 
else
 
thisRow = "[["..baseName.."]]"
 
end
 
--then, if there are variants...
 
if(Shared.tableCount(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.."<span class=\"weapon-tooltip\" data-param=\""..p.buildName(baseName, varName).."\" style=\"white-space:pre\">[["..p.buildName(baseName, varName).."|"..varName.."]]</span>"
 
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, "<span class=\"weapon-tooltip\" data-param=\""..p.buildName(baseName, varName).."\" style=\"white-space:pre\">[["..p.buildName(baseName, varName).."]]</span>")
 
else
 
table.insert(result, "[["..p.buildName(baseName, varName).."]]")
 
end
 
end
 
end
 
end
 
return result
 
 
end
 
end
  +
p._getAttack = getWeaponAttack
  +
p._getWeaponAttack = getWeaponAttack
   
function p.getWeapon(WeapName)
+
function p._statRead(w, a, ...)
  +
return statRead(getWeaponAttack(w, a), ...)
local weapon = WeaponData["Weapons"][WeapName]
 
 
if weapon ~= nil and weapon.Name == WeapName then
 
return weapon
 
else
 
for key, Weapon in Shared.skpairs(WeaponData["Weapons"]) do
 
if(Weapon.Name == WeapName or key == WeapName) then
 
return Weapon
 
end
 
end
 
end
 
 
return nil
 
 
end
 
end
  +
function p._statFormat(w, a, ...)
 
  +
return statFormat(getWeaponAttack(w, a), ...)
local function checkWeapon(weap, weapName, conclave)
 
if weap == nil or type(weap) ~= "table" then
 
if conclave then
 
return p.getConclaveWeapon(weapName)
 
else
 
return p.getWeapon(weapName)
 
end
 
elseif type(weap) == "table" then
 
return weap
 
end
 
 
end
 
end
  +
function p.stat(frame)
 
  +
return p._statFormat(p._getWeapon(frame.args[1] or 'Skana Prime'), nil, frame.args[2] or 'Name')
function p.weaponExists(frame)
 
local weapName = frame.args ~= nil and frame.args[1] or nil
 
 
return p._weaponExists(weapName)
 
 
end
 
end
   
  +
-- Wrapper function for use in StatObject
function p._weaponExists(weapName, weap)
 
  +
local function dmgTooltip(damageType)
local weap = checkWeapon(weap, weapName)
 
  +
return Tooltip.full(damageType, 'DamageTypes')
 
if weapName == "Dark Split-Sword" then
 
return true
 
elseif weap then
 
return true
 
end
 
 
if weapName == nil then
 
return "Enter weapon name."
 
elseif weap == nil then
 
return "No weapon "..weapName.." found."
 
end
 
 
end
 
end
   
  +
-- Defining getters/attributes whose names match the associated database key or some custom derived attribute.
function p.getLink(weapName, weap)
 
  +
-- Index key will be name of getter function and can be mapped to a single value (getter definition)
local weap = checkWeapon(weap, weapName)
 
  +
-- or a table with two values (getter and format function definitions)
local exists = p._weaponExists(weapName, weap)
 
if weapName == "Dark Split-Sword" then
 
return "Dark Split-Sword"
 
elseif exists == true then
 
local temp = weap.Link
 
if weap.Type == "Arch-Gun (Atmosphere)" then
 
local atmoTemp = Shared.splitString(weap.Name, "%s")
 
local atmoCount = Shared.tableCount(atmoTemp)
 
if atmoCount == 2 then
 
return atmoTemp[1]
 
elseif atmoCount >= 2 then
 
return table.concat(atmoTemp, " ", 1, (atmoCount-1))
 
end
 
return weap.Name
 
elseif temp then
 
return temp
 
else
 
return weap.Name
 
end
 
elseif weapName == nil then
 
return false
 
else
 
return false
 
end
 
end
 
   
  +
-- Cheatsheet on adding new keys:
function p.getConclaveWeapon(WeapName)
 
  +
-- StatName = default value -> Get raw value with the same StatName from M:Weapons/data and with no additional formatting (aka default formatting)
local weapon = ConclaveData["Weapons"][WeapName]
 
  +
-- StatName = function(self) return self.StatName + 1 end -> Define custom getter function and use default formatting
 
  +
-- StatName = { default value, '%.2f' } -> Get raw value value with same StatName from M:Weapons/data and use format string for formatting
if weapon ~= nil and weapon.Name == WeapName then
 
  +
-- StatName = { function(self) return ... end, '%.2f' } -> Define custom getter function and use format string for formatting
return weapon
 
  +
-- StatName = { function(self) return ... end, function(self, returnValue1, returnValue2, ...) return tostring(returnValue) end } - > Define custom getter and format functions
else
 
  +
-- (Note that format function will pass in return value(s) from getter as well as object self)
for key, Weapon in Shared.skpairs(ConclaveData["Weapons"]) do
 
if(Weapon.Name == WeapName or key == WeapName) then
 
return Weapon
 
end
 
end
 
end
 
   
  +
-- TODO: Put StatObject keys in alphabetical order for navigation
return nil
 
  +
StatObject.default = {
  +
AttackName = 'Normal Attack',
  +
AmmoCost = nil,
  +
AmmoPickup = function(weapAtk)
  +
return weapAtk['AmmoPickup'] or
  +
weapAtk['Slot'] == 'Primary' and 80 or
  +
weapAtk['Slot'] == 'Secondary' and 40 or
  +
weapAtk['Slot'] == 'Archgun (Atmosphere)' and 1000 or
  +
0
  +
end,
  +
DamageBias = {
  +
function(weapAtk)
  +
if not weapAtk.Damage then
  +
error('DamageBias: no Attack.Damage')
  +
return 0, 0, 0
  +
end
  +
local total, bestdmg, bestdt = 0, 0, nil
  +
for dt, dmg in pairs(weapAtk.Damage) do
  +
local dmg = dmg
  +
if dmg >= bestdmg then
  +
bestdmg, bestdt = dmg, dt
  +
end
  +
total = total + dmg
  +
end
  +
return StatObject.ucacheIn(weapAtk, 'DamageBias', { bestdmg / total, bestdt, total })
  +
end,
  +
{ percent, passes(dmgTooltip), '' }
  +
},
  +
BiasPortion = { indexes('DamageBias', 1), percent },
  +
BiasType = { indexes('DamageBias', 2), function(self, biasType) return Tooltip.icon(biasType, 'DamageTypes') end },
  +
BaseDamage = { indexes('DamageBias', 3), '%.2f' },
  +
-- More precise damage values to 4 decimal places for PvP since PvP damage is calculated
  +
-- based on a floating-point scalar. Damage quantization is more relevant in PvP so more
  +
-- precise numbers needed.
  +
PvPBaseDamage = { indexes('DamageBias', 3), '%.4f' },
  +
TotalDamage = { function(weapAtk)
  +
return statRead(weapAtk, 'BaseDamage') * statRead(weapAtk, 'Multishot')
  +
end, passes(Math.formatnum)
  +
},
  +
-- Including max +60% Progenitor bonus for Kuva/Tenet weapons
  +
TotalDamageWithProgenBonus = { function(weapAtk)
  +
return statRead(weapAtk, 'TotalDamage') * (statRead(weapAtk, 'IsLichWeapon') and 1.6 or 1)
  +
end, passes(Math.formatnum)
  +
},
  +
ChargeTime = { 0, '%.1f s' },
  +
ExplosionDelay = { 0, '%.1f s' },
  +
ExtraHeadshotDmg = { 0, percents('+%.2f%%') },
  +
Falloff = {
  +
function(weapAtk)
  +
local fo = weapAtk['Falloff'] or {}
  +
return fo.StartRange or 0, fo.EndRange or math.huge, 1 - (fo.Reduction or 1)
  +
end,
  +
{ '%.1f m (100%%) -', '%.1f m', percents('(%.2f%%)') }
  +
},
  +
FalloffStart = { indexes('Falloff', 1), '%.1f m' },
  +
FalloffEnd = { indexes('Falloff', 2), '%.1f m' },
  +
-- Damage reduction from falloff instead of damage multiplier
  +
FalloffReduction = { function(weapAtk)
  +
local _, _, falloff = statRead(weapAtk, 'Falloff')
  +
return 1 - falloff
  +
end, percent
  +
},
  +
FalloffRate = { function(weapAtk)
  +
local startdist,enddist,endpercent = statRead(weapAtk, 'Falloff')
  +
return -(enddist-startdist)/(endpercent-1)
  +
end, '%.1fm/%%'
  +
},
  +
HeadshotMultiplier = { 1, '%.1fx' },
  +
Multishot = 1,
  +
PunchThrough = { 0, '%.1f m' },
  +
ShotSpeed = { nil, function(self, shotSpeed)
  +
if shotSpeed == nil then
  +
return 'N/A'
  +
end
  +
return ('%.1f m/s'):format(shotSpeed)
 
end
 
end
  +
},
 
  +
BurstDelay = { 0, '%.4f s' },
function p.isMelee(frame)
 
  +
BurstReloadDelay = { 0, '%.2f s' },
if(frame == nil) then
 
  +
BurstsPerSec = { function(weapAtk)
return nil
 
  +
-- There is no delay after last shot in burst
end
 
  +
return 1 / ( (1 / statRead(weapAtk, 'FireRate') ) + statRead(weapAtk, 'BurstDelay') * ( statRead(weapAtk, 'BurstCount') - 1) )
local Weapon = frame.args ~= nil and frame.args[1] or frame
 
  +
end, '%.2f bursts/sec' },
if(type(Weapon) == "string") then
 
  +
CritChance = { 0, percent },
Weapon = p.getWeapon(Weapon)
 
  +
CritMultiplier = { 1, '%.2fx' },
end
 
  +
ForcedProcs = { unpacks('ForcedProcs'), function(s, ...)
 
  +
local procs = { ... }
if(Weapon == nil) then
 
  +
if procs[1] == nil then
return nil
 
  +
return 'No forced procs'
end
 
  +
end
 
  +
local result = {}
if(Weapon.Type ~= nil and (Weapon.Type == "Melee" or Weapon.Type == "Arch-Melee")) then
 
  +
for _, proc in ipairs(procs) do
return "yes"
 
  +
table.insert(result, Tooltip.full(proc, 'DamageTypes'))
end
 
  +
end
 
  +
return table.concat(result, ', ')
return nil
 
 
end
 
end
  +
},
 
  +
Radius = { 0, '%.1f m' },
function p.isArchwing(frame)
 
  +
StatusChance = { 0, percent },
if(frame == nil) then
 
  +
Disposition = {
return nil
 
  +
function(weap)
end
 
  +
local d = weap['Disposition']
local Weapon = frame.args ~= nil and frame.args[1] or frame
 
  +
-- Returning a categorical bin value of 1, 2, 3, 4, or 5 based on where disposition value
if(type(Weapon) == "string") then
 
  +
-- is on the continuous scale of 0.5-1.55. If disposition value is nil then return 0
Weapon = p.getWeapon(Weapon)
 
  +
return d or 0, type(d)=='number' and math.floor(5*(d-.3+.009*(d<1 and 1 or -1))) or 0
end
 
  +
end,
 
  +
function(s, v, d)
if(Weapon == nil) then
 
  +
return StatObject.default.Dispo[2](s, d)..(' (%.2fx)'):format(v)
return nil
 
end
+
end
  +
},
 
  +
Dispo = { indexes('Disposition', 2), function(s, d)
if(Weapon.Type ~= nil and (Weapon.Type == "Arch-Gun" or Weapon.Type == "Arch-Melee")) then
 
return "yes"
+
if d and d == d and d > 0 then
  +
return ('●'):rep(math.min(d, 5))..('○'):rep(5 - d)
end
 
  +
end
 
  +
return '×××××' -- '●○×' --> E2978F E2978B C397
return nil
 
  +
end },
  +
Introduced = { function(weap)
  +
return weap['Introduced'] and Version._getVersion(weap['Introduced'])['Name'] or 'N/A'
  +
end, passes(Version._getVersionLink)
  +
},
  +
IntroducedDate = function(weap)
  +
return weap['Introduced'] and Version._getVersionDate(weap['Introduced']) or 'N/A'
  +
end,
  +
IsLichWeapon = function(weap)
  +
return weap['IsLichWeapon'] and true or false
  +
end,
  +
Mastery = 0,
  +
Link = { nil, '[[%s]]' },
  +
Name = { nil, function(s, v) return Tooltip.full(v, 'Weapons') end },
  +
InternalName = '',
  +
NameLink = { function(weap) return weap.Link, weap.Name end, '[[%s|%s]]' },
  +
Polarities = { nil, passes(Polarity._pols) },
  +
Traits = { unpacks('Traits'), { sep = ', ' } },
  +
-- Default nil b/c some attacks don't have an associated accuracy/spread value (like AoE explosions)
  +
Accuracy = { nil, function(self, value)
  +
if (value == nil) then
  +
return 'N/A'
  +
end
  +
return value
 
end
 
end
  +
},
 
  +
-- Inverse of accuracy. Spread of 1 equates to no spread.
local function getAttack(Weapon, AttackType)
 
  +
-- Alternatively, it can be calculated by the average of min and max spread, see AvgSpread getter.
if(Weapon == nil or AttackType == nil) then
 
  +
Spread = { function(weapAtk)
return nil
 
  +
local accuracy = statRead(weapAtk, 'Accuracy')
elseif(type(Weapon) == "string") then
 
  +
return (accuracy == nil) and nil or 100 / accuracy
Weapon = p.getWeapon(Weapon)
 
  +
end, function(self, value)
end
 
  +
if (value == nil) then
if (AttackType == nil or AttackType == "Normal" or AttackType == "NormalAttack") then
 
  +
return 'N/A'
if(Weapon.NormalAttack ~= nil) then
 
  +
end
return Weapon.NormalAttack
 
  +
return value
elseif(Weapon.Damage ~= nil) then
 
return Weapon
 
else
 
return nil
 
end
 
elseif(AttackType == "Charge" or AttackType == "ChargeAttack") then
 
return Weapon.ChargeAttack
 
elseif(AttackType == "Area" or AttackType == "AreaAttack") then
 
return Weapon.AreaAttack
 
elseif(AttackType == "SecondaryArea" or AttackType == "SecondaryAreaAttack") then
 
return Weapon.SecondaryAreaAttack
 
elseif(AttackType == "Throw" or AttackType == "ThrowAttack") then
 
return Weapon.ThrowAttack
 
elseif(AttackType == "ChargedThrow" or AttackType == "ChargedThrowAttack") then
 
return Weapon.ChargedThrowAttack
 
elseif(AttackType == "Secondary" or AttackType == "SecondaryAttack") then
 
return Weapon.SecondaryAttack
 
elseif(AttackType == "Slam" or AttackType == "SlamAttack") then
 
return Weapon.SlamAttack
 
elseif(AttackType == "Heavy" or AttackType == "HeavyAttack") then
 
return Weapon.HeavyAttack
 
else
 
return nil
 
end
 
 
end
 
end
  +
},
 
local function hasAttack(Weapon, AttackType)
+
AmmoType = function(weapAtk)
  +
return weapAtk['AmmoType'] or ({
if(getAttack(Weapon, AttackType) ~= nil) then
 
  +
['Archgun (Atmosphere)'] = 'Heavy',
return true
 
  +
['Secondary'] = 'Secondary',
else
 
  +
['Primary'] = 'Primary'
return nil
 
  +
})[weapAtk['Slot']] or 'None'
end
 
  +
end,
  +
-- Not all weapons have an Exilus slot so default to nil
  +
ExilusPolarity = { nil, function(self, exilusPolarity)
  +
if (exilusPolarity == nil) then
  +
return 'N/A'
  +
end
  +
return Polarity._polarity(exilusPolarity)
 
end
 
end
  +
},
 
  +
Magazine = 1,
local function dontHasAttack(Weapon, AttackType)
 
  +
AmmoMax = { function(weapAtk)
if(getAttack(Weapon, AttackType) ~= nil) then
 
  +
if statRead(weapAtk, 'IsMelee') then
return nil
 
  +
return nil
else
 
  +
end
return true
 
  +
return weapAtk['AmmoMax'] or math.huge
end
 
  +
end, passes(Math.formatnum)
  +
},
  +
Range = { function(weapAtk)
  +
return weapAtk['Range'] or statRead(weapAtk, 'ShotType') == 'Hit-Scan' and 300 or 0
  +
end, '%.1f m'
  +
},
  +
Reload = { ors('Reload', 'RechargeTime', 0), '%.2f s' },
  +
RechargeTime = { function(weapAtk)
  +
return statRead(weapAtk, 'ReloadStyle'):find'[Rr]egen' and statRead(weapAtk, 'Magazine') / statRead(weapAtk, 'ReloadRate') or nil
  +
end, '%.2f s'
  +
},
  +
ReloadRate = { 0, '%.2f rounds/sec' }, -- Used for rechargeable weapons; not necessarily inverse of reload time b/c of presence of reload delay
  +
ReloadDelay = { function(weapAtk)
  +
return weapAtk['ReloadDelay'] or 0
  +
end, '%.2f s'
  +
},
  +
ReloadDelayEmpty = { ors('ReloadDelayEmpty', 'ReloadDelay'), '%.2f s' },
  +
-- Reload speed will be calculated as the inverse of reload time for purposes
  +
-- of keeping how we rank stats consistent for [[Module:Weapons/preprocess]]
  +
-- (larger number = higher stat; a short reload time can be expressed as fast reload
  +
-- speed which would be a larger value in magnitude)
  +
ReloadSpeed = { function(weapAtk)
  +
return 1 / statRead(weapAtk, 'Reload')
  +
end, function(str, reloadSpeed)
  +
return string.format('%.2f%% reload progress per second', reloadSpeed * 100)
  +
end },
  +
ReloadStyle = 'Magazine',
  +
Spool = { 0, '%d rounds' },
  +
SpoolStartFireRate = { 0, '%.1fx' }, -- scalar that is applied to fire rate stat for auto-spool weapons
  +
AvgSpread = { function(weapAtk)
  +
local minSpread = statRead(weapAtk, 'MinSpread')
  +
local maxSpread = statRead(weapAtk, 'MaxSpread')
  +
if (minSpread == nil) then
  +
return nil
  +
end
  +
return (minSpread + maxSpread) / 2
  +
end, function(self, value)
  +
if (value == nil) then
  +
return 'N/A'
  +
end
  +
return ('%.2f°'):format(value)
  +
end
  +
},
  +
-- Default nil b/c some attacks don't have an associated accuracy/spread value (like AoE explosions)
  +
MinSpread = { nil, function(self, value)
  +
if (value == nil) then
  +
return 'N/A'
  +
end
  +
return ('%.2f°'):format(value)
  +
end
  +
},
  +
MaxSpread = { nil, function(self, value)
  +
if (value == nil) then
  +
return 'N/A'
  +
end
  +
return ('%.2f°'):format(value)
  +
end
  +
},
  +
Trigger = 'N/A',
  +
BlockAngle = { 0, '%d&#176;' },
  +
ComboDur = { 0, '%.1f s' },
  +
FollowThrough = { 0, '%.1fx' },
  +
HeavyAttack = { 0, passes(Math.formatnum) },
  +
HeavySlamAttack = { 0, passes(Math.formatnum) },
  +
HeavyRadialDmg = { 0, passes(Math.formatnum) },
  +
HeavySlamRadius = { 0, '%.1f m' },
  +
MeleeRange = { 0, '%.2f m' },
  +
SlamAttack = { 0, passes(Math.formatnum) },
  +
SlamRadialDmg = { function(weapAtk)
  +
return weapAtk.SlamRadialDmg or 0, statRead(weapAtk, 'SlamRadialElement')
  +
end, function(self, dmg, elem)
  +
if elem then
  +
return Tooltip.icon(elem, 'DamageTypes')..' '..Math.formatnum(dmg)
  +
end
  +
return Math.formatnum(dmg)
 
end
 
end
  +
},
 
  +
SlamRadialElement = { nil, function(self, value)
 
  +
return value ~= nil and Tooltip.full(value, 'DamageTypes') or 'Same damage type distribution as normal attack'
function p.hasAttack(frame)
 
local WeapName = frame.args[1]
 
local AttackName = frame.args[2]
 
 
if(WeapName == nil) then
 
return "ERROR: No weapon name"
 
elseif(AttackName == nil) then
 
AttackName = "Normal"
 
end
 
 
local Weapon = p.getWeapon(WeapName)
 
if(Weapon == nil) then return "ERROR: No weapon "..WeapName.." found" end
 
 
return hasAttack(Weapon, AttackName)
 
 
end
 
end
  +
},
 
  +
-- Slam radial forced proc(s)
local function hasMultipleTypes(Attack)
 
  +
SlamRadialProcs = { nil, function(self, proc)
local typeCount = 0
 
  +
if type(proc)=='table' then
if(Attack ~= nil and Attack.Damage ~= nil) then
 
  +
local result = {}
for key, dmg in Shared.skpairs(Attack.Damage) do
 
  +
for _, elem in ipairs(proc) do
if(dmg > 0) then
 
  +
table.insert(result, Tooltip.full(elem, 'DamageTypes'))
typeCount = typeCount + 1
 
  +
end
end
 
  +
return table.concat(result, '<br />')
end
 
  +
else
end
 
  +
return 'N/A'
if(typeCount > 1) then return "yes" else return nil end
 
  +
end
 
end
 
end
  +
},
 
  +
SlamRadius = { 0, '%.1f m' },
function p.hasMultipleTypes(frame)
 
  +
SlideAttack = { function(weapAtk)
local WeapName = frame.args[1]
 
  +
return weapAtk.SlamRadialDmg or 0, statRead(weapAtk, 'SlideElement')
local AttackName = frame.args[2]
 
  +
end, function(self, dmg, elem)
if(AttackName == nil) then AttackName = "Normal" end
 
  +
if elem then
local attack = getAttack(WeapName, AttackName)
 
  +
return Tooltip.icon(elem, 'DamageTypes')..' '..Math.formatnum(dmg)
return hasMultipleTypes(attack)
 
  +
end
  +
return Math.formatnum(dmg)
 
end
 
end
  +
},
 
  +
SlideElement = { nil, function(self, value)
function p.attackLoop(Weapon)
 
  +
return value ~= nil and Tooltip.full(value, 'DamageTypes') or 'Same damage type distribution as normal attack'
if(Weapon == nil) then
 
return function() return nil end
 
end
 
local aType = "Normal"
 
local iterator = function()
 
if(aType == "Normal") then
 
local attack = getAttack(Weapon, aType)
 
aType = "Charge"
 
if attack ~= nil and attack.Damage ~= nil then return "Normal", attack end
 
end
 
if(aType == "Charge") then
 
local attack = getAttack(Weapon, aType)
 
aType = "Area"
 
if attack ~= nil and attack.Damage ~= nil then return "Charge", attack end
 
end
 
if(aType == "Area") then
 
local attack = getAttack(Weapon, aType)
 
aType = "SecondaryArea"
 
if attack ~= nil and attack.Damage ~= nil then return "Area", attack end
 
end
 
if(aType == "SecondaryArea") then
 
local attack = getAttack(Weapon, aType)
 
aType = "Secondary"
 
if attack ~= nil and attack.Damage ~= nil then return "SecondaryArea", attack end
 
end
 
if(aType == "Secondary") then
 
local attack = getAttack(Weapon, aType)
 
aType = "Throw"
 
if attack ~= nil and attack.Damage ~= nil then return "Secondary", attack end
 
end
 
if(aType == "Throw") then
 
local attack = getAttack(Weapon, aType)
 
aType = "ChargedThrow"
 
if attack ~= nil and attack.Damage ~= nil then return "Throw", attack end
 
end
 
if(aType == "ChargedThrow") then
 
local attack = getAttack(Weapon, aType)
 
aType = "Slam"
 
if attack ~= nil and attack.Damage ~= nil then return "ChargedThrow", attack end
 
end
 
if(aType == "Slam") then
 
local attack = getAttack(Weapon, aType)
 
aType = "Heavy"
 
if attack ~= nil and attack.Damage ~= nil then return "Slam", attack end
 
end
 
if(aType == "Heavy") then
 
local attack = getAttack(Weapon, aType)
 
aType = "end"
 
if attack ~= nil and attack.Damage ~= nil then return "Heavy", attack end
 
end
 
 
return nil
 
end
 
return iterator
 
 
end
 
end
  +
},
 
  +
--[[Stances = function(weapAtk)
function p.getStance(StanceName)
 
  +
if not statRead(weapAtk, 'IsMelee') then return end
for i, Stance in pairs(WeaponData["Stances"]) do
 
  +
return Stances._getAllStancesSameType(statRead(weapAtk, "Class"))
if(Stance.Name == StanceName) then
 
return Stance
+
-- ^ currently a local function
  +
end,--]]
end
 
  +
-- Not all weapons have an Stance slot so default to nil
end
 
  +
StancePolarity = { nil, function(self, stancePolarity)
return nil
 
  +
if (stancePolarity == nil) then
  +
return 'N/A'
  +
end
  +
return Polarity._polarity(stancePolarity)
 
end
 
end
  +
},
  +
SweepRadius = { 0, '%.2f m' },
  +
WindUp = { 0, '%.1f s' },
  +
BurstCount = 1,
  +
-- Average crit/proc count from a single attack input
  +
AvgCritCount = function(weapAtk)
  +
return statRead(weapAtk, 'CritChance') * statRead(weapAtk, 'Multishot')
  +
end,
  +
AvgCritPerSec = function(weapAtk)
  +
return statRead(weapAtk, 'AvgCritCount') * statRead(weapAtk, 'EffectiveFireRate')
  +
end,
  +
AvgProcCount = function(weapAtk)
  +
return ( statRead(weapAtk, 'StatusChance') + Table.size(weapAtk['ForcedProcs'] or {}) ) * statRead(weapAtk, 'Multishot')
  +
end,
  +
AvgProcPerSec = function(weapAtk)
  +
return statRead(weapAtk, 'AvgProcCount') * statRead(weapAtk, 'EffectiveFireRate')
  +
end,
  +
InterShotTime = function(weapAtk)
  +
local v = statRead(weapAtk, 'Magazine') == 1 and statRead(weapAtk, 'Reload') + statRead(weapAtk, 'ReloadDelayEmpty') or 0
  +
if v == 0 then v = 1 / statRead(weapAtk, 'FireRate') end
  +
return v
  +
end,
  +
EffectiveFireRate = function(weapAtk)
  +
return 1 / ( statRead(weapAtk, 'ChargeTime') + statRead(weapAtk, 'InterShotTime') )
  +
end,
  +
ShotsPerMag = function(weapAtk)
  +
-- Default to 1 "ammo cost" even if attack does not directly consume ammo (e.g. AoE hits, speargun throws, etc.)
  +
return math.floor(statRead(weapAtk, 'Magazine') / (statRead(weapAtk, 'AmmoCost') or 1))
  +
end,
  +
FireRate = { function(weapAtk)
  +
local dataFireRate = weapAtk['FireRate']
  +
if dataFireRate then return dataFireRate end
  +
-- TODO: Think we can safely remove this calculation of FireRate from BurstFireRate, BurstDelay, and BurstCount
  +
-- for burst-fire attacks since FireRate is also included for those
  +
mw.log('calculating FireRate from Burst stats for '..statRead(weapAtk, 'Name'))
  +
local count = statRead(weapAtk, 'BurstCount')
  +
local fireRate = count / (1 / statRead(weapAtk, 'BurstFireRate') + count * statRead(weapAtk, 'BurstDelay'))
  +
return fireRate
  +
end, '%.3f attacks/sec'
  +
},
  +
BurstFireRate = { function(weapAtk)
  +
return 1 / statRead(weapAtk, 'BurstDelay')
  +
end, '%.2f attacks/sec'
  +
},
  +
--[[
  +
Describing what happens when a gun in WARFRAME is fired using player-made terminology:
   
  +
A particular gun consumes a set number of ammo in order to fire a set number of shots
local function getAugments(Weapon)
 
  +
on a single player input for a particular attack.
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
 
   
  +
A single player input is defined as:
local function getFamily(FamilyName)
 
  +
* a single attack button press for semi-auto and burst trigger weapons
local familyMembers = {}
 
  +
* the moment the next shot is being fired when the attack button is being held for automatic/held trigger weapons
for i, Weapon in Shared.skpairs(WeaponData["Weapons"]) do
 
  +
* the action of holding the attack button for charge trigger weapons
if(Weapon.Family == FamilyName) then
 
  +
* for duplex-fire trigger weapons, the hold and release of the attack button counts as two inputs
table.insert(familyMembers, Weapon)
 
end
 
end
 
return familyMembers
 
end
 
   
  +
A shot is defined as the base unit of attack of a weapon when unmodded.
local function linkMeleeClass(weapClass)
 
  +
* A single attack input can launch several shots as in the case of burst-fire weapons.
if(weapClass == nil) then
 
  +
* A single shot can shoot more than one projectile, affected by the multishot stat, as in the case of shotguns.
return nil
 
  +
* A single shot can consume more than one unit of ammo (e.g. Tenora's alt-fire) or
else
 
  +
less than one unit of ammo (e.g. Ignis and most continuous weapons).
return "[[:Category:"..weapClass.."|"..weapClass.."]]"
 
end
 
end
 
   
  +
A gun can have multiple attacks which can be triggered using different buttons
--Returns all melee weapons.
 
  +
and/or types of button inputs (e.g. pressing vs. holding)
--If weapType is not nil, only grab for a specific type
 
  +
]]--
--For example, if weapType is "Nikana", only pull Nikanas
 
local function getMeleeWeapons(weapClass, PvP)
+
CalcDamage = function(weapAtk)
  +
local weapon, attack = weapAtk, weapAtk
local weaps = {}
 
  +
-- Count
local weapClasses = {}
 
  +
-- How many shots are fired in a single player input
if(weapClass ~= nil) then
 
  +
local tapShots = statRead(weapAtk, 'BurstCount')
weapClasses = Shared.splitString(weapClass, ",")
 
  +
-- How many individual player inputs can occur before depleting a magazine
end
 
  +
local magTaps = statRead(weapAtk, 'ShotsPerMag')
 
  +
-- How many additional projectiles are fired per ammo
for i, weap in Shared.skpairs(WeaponData["Weapons"]) do
 
  +
local multishot = statRead(weapAtk, 'Multishot')
if((weap.Ignore == nil or not weap.Ignore) and weap.Type ~= nil and weap.Type == "Melee") then
 
  +
-- How much ammo is contained in the magazine
local classMatch = (weapClass == nil or Shared.contains(weapClasses, weap.Class))
 
  +
local magazine = statRead(weapAtk, 'Magazine')
local pvpMatch = (PvP == nil or (PvP and weap.Conclave ~= nil and weap.Conclave))
 
  +
-- How much ammo can be drawn from reserves (or?, how much ammo can be used without picking up more)
if (classMatch and pvpMatch) then
 
  +
local ammoMax = statRead(weapAtk, 'AmmoMax')
table.insert(weaps, weap)
 
end
 
end
 
end
 
 
return weaps
 
end
 
   
  +
-- Time^-1
--As above, but for Conclave stats
 
  +
local fireRate = statRead(weapAtk, 'FireRate')
local function getConclaveMeleeWeapons(weapClass, PvP)
 
  +
-- Time
local weaps = {}
 
  +
local shotTime = statRead(weapAtk, 'InterShotTime')
local weapClasses = {}
 
  +
local chargeTime = statRead(weapAtk, 'ChargeTime')
if(weapClass ~= nil) then
 
  +
local burstDelayTime = statRead(weapAtk, 'BurstDelay')
weapClasses = Shared.splitString(weapClass, ",")
 
  +
local reloadDelayTime = statRead(weapAtk, 'ReloadDelayEmpty')
end
 
  +
local reloadTime = statRead(weapAtk, 'Reload')
 
  +
local tapTime = chargeTime + (tapShots - 1) * burstDelayTime
for i, weap in Shared.skpairs(ConclaveData["Weapons"]) do
 
  +
-- tapTime: The time between the last shot fired and the next valid attack input
if((weap.Ignore == nil or not weap.Ignore) and weap.Type ~= nil and weap.Type == "Melee") then
 
  +
-- (omitting latency of course).
local classMatch = (weapClass == nil or Shared.contains(weapClasses, weap.Class))
 
  +
-- Note that first shot of any non-charge trigger attack is instantenous
local pvpMatch = (PvP == nil or (PvP and weap.Conclave ~= nil and weap.Conclave))
 
  +
local magDepletionTime = magTaps * tapTime
if (classMatch and pvpMatch) then
 
  +
if magDepletionTime == 0 then -- If attack is not a charged attack
table.insert(weaps, weap)
 
  +
if shotTime == 0 then
end
 
  +
shotTime = 1 / fireRate
end
 
end
+
end
  +
magDepletionTime = magTaps / fireRate
 
  +
end
return weaps
 
  +
local shotDelayTime = math.max(0, shotTime - tapTime)
end
 
   
  +
-- Multiplier
--Learning new things... Trying to allow sending in an arbitrary function
 
  +
local maxProgenitorBonus = statRead(weapAtk, 'IsLichWeapon') and 1.6 or 1
function p.getWeapons(validateFunction)
 
  +
local avgCritMult = 1 + (statRead(weapAtk, 'CritMultiplier') - 1) * statRead(weapAtk, 'CritChance')
local weaps = {}
 
  +
-- Damage
for i, weap in Shared.skpairs(WeaponData["Weapons"]) do
 
  +
local biasPortion, biasType, hitDamage = statRead(weapAtk, 'DamageBias')
if((weap.Ignore == nil or not weap.Ignore) and validateFunction(weap)) then
 
  +
table.insert(weaps, weap)
 
  +
local avgDmgOnTap = hitDamage * avgCritMult * multishot * tapShots * maxProgenitorBonus
end
 
  +
local avgDmgPerMag = avgDmgOnTap * magTaps
end
 
  +
return weaps
 
  +
-- 1 is needed b/c one whole magazine is not included in reserve ammo count
  +
-- If there is no reserve ammo, that means that weapon can deal an infinite amount of damage theoretically
  +
local avgLifetimeDmg = (ammoMax ~= nil) and avgDmgPerMag * (1 + (ammoMax / magazine)) or math.huge
  +
  +
-- Damage / Time
  +
local baseDps = hitDamage * multishot / shotTime
  +
local avgSustainedDps = avgDmgPerMag / (magDepletionTime + reloadDelayTime + reloadTime) / tapShots
  +
local avgBurstDps = avgDmgOnTap / (tapTime + shotDelayTime) / tapShots
  +
-- Note that burst DPS can also be calculated as such:
  +
-- local avgBurstDps = (hitDamage * avgCritMults * maxProgenitorBonus) * multishot / shotTime
  +
-- local avgBurstDps = avgDmgPerMag / magDepletionTime
  +
  +
return StatObject.ucacheIn(weapAtk, 'CalcDamage',
  +
{ hitDamage, avgDmgOnTap, avgBurstDps, avgSustainedDps, avgLifetimeDmg, baseDps, avgDmgPerMag }
  +
)
  +
end,
  +
ShotDmg = indexes('CalcDamage', 1), -- Total damage per projectile
  +
AvgShotDmg = indexes('CalcDamage', 2), AvgTapDmg = indexes('CalcDamage', 2), -- Average total damage per each input button
  +
BurstDps = indexes('CalcDamage', 3), -- Average burst damage per second/DPS w/o reloading
  +
SustainedDps = indexes('CalcDamage', 4), -- Average sustained damage per second/DPS w/ reloading
  +
LifetimeDmg = indexes('CalcDamage', 5), -- Average total damage from entire ammo pool
  +
BaseDps = indexes('CalcDamage', 6), -- Base damage per second w/ multishot w/o crit
  +
MagDmg = indexes('CalcDamage', 7), -- Average total damage per magazine
  +
-- Average damage scaled by melee attack speed multiplier (numerator of melee DPS w/o accounting for stances and animation time)
  +
AvgDmgWithAnimSpeedMulti = function(weapAtk)
  +
if statRead(weapAtk, 'IsMelee') then
  +
-- Some melee weapons have attacks with multishot like Redeemer, Vastilok, and Quassus
  +
return statRead(weapAtk, 'BaseDamage') * statRead(weapAtk, 'Multishot') * statRead(weapAtk, 'AttackSpeed')
  +
end
  +
return 0
  +
end,
  +
AttackSpeed = { --[[ors('AttackSpeed', 'FireRate')]]function(weapAtk)
  +
if not statRead(weapAtk, 'IsMelee') then
  +
error('AttackSpeed: Cannot get AttackSpeed attribute for a non-melee weapon; use p.statRead(weapAtk, "FireRate") instead')
  +
end
  +
return statRead(weapAtk, 'FireRate')
  +
end, '%.2fx animation speed'
  +
},
  +
IsMelee = function(weapAtk) return statRead(weapAtk, 'Slot'):find('Melee') or statRead(weapAtk, 'Slot'):find('melee') ~= nil end,
  +
IsSilent = ors('IsSilent', 'IsMelee', false),
  +
HasAoEAttack = function(weap)
  +
for i, attackEntry in pairs(weap['Attacks']) do
  +
if attackEntry['ShotType'] == 'AoE' then
  +
return true
  +
end
  +
end
  +
return false
  +
end,
  +
Conclave = false,
  +
Image = { 'Panel.png', '[[File:%s|link=]]' },
  +
Attacks = ors('Attacks', p._getAttack, {}),
  +
Family = nil,
  +
FamilyList = { function(weapAtk)
  +
local family = statRead(weapAtk, 'Family')
  +
-- assert(family, 'i have no Family :\'(')
  +
if not family then return {weapAtk} end
  +
-- return family, statRead(weapAtk, 'Slot')
  +
local slot = statRead(weapAtk, 'Slot')
  +
local result = {}
  +
for _, w in pairs(WeaponData[slot] or error('FamilyList: no weapondata for slot '..(slot or '<nil>'))) do
  +
if w.Family == family then
  +
table.insert(result, w)
  +
end
  +
end
  +
table.sort(result, function(a,b) return a.Name<b.Name end)
  +
return result
  +
end, function(self, result)
  +
for i,w in ipairs(result) do
  +
result[i]=Tooltip.full(w.Name, 'Weapons', w)
  +
end
  +
return table.concat(result, '<br />')
 
end
 
end
  +
},
  +
BaseName = function(weapAtk) return weapAtk['BaseName'] or ({p._getVariant(statRead(weapAtk, 'Name'))})[3] end,
  +
-- TODO: Add comments to Explosion function for readability
  +
-- TODO: Do not rely on attack name to determine what AoE component is attached to which main direct hit component
  +
---^i suggest an explosion key with either the attack number of any corresponding explosion, nested attack tables, or some other way to make a tree
  +
-- TODO: Use ShotType = "AoE" to determine if attack entry is AoE
  +
Explosion = function(weapAtk)
  +
local weap, atk = weapAtk, weapAtk
  +
-- tbh this is a mess
  +
local explosion = weapAtk['Explosion'] or statRead(weapAtk, 'AttackName'):gsub(' Impact',''):gsub(' Contact','')..' Explosion'
  +
if type(explosion) == 'string' then
  +
explosion = weap.Attacks[tonumber(explosion:gsub('%D',''))] or explosion
  +
elseif type(explosion) == 'number' then
  +
explosion = weap.Attacks[explosion] or explosion
  +
end
  +
local explosions = {}
  +
if type(explosion) ~= 'table' then
  +
for i, v in ipairs(weap.Attacks) do
  +
if p._statRead(weapAtk, v, 'AttackName'):find 'xplosion' then
  +
if p._statRead(weapAtk, v, 'AttackName') == explosion then
  +
explosions[1] = nil
  +
explosion = v
  +
break
  +
end
  +
table.insert(explosions, v)
  +
end
  +
end
  +
explosion = explosions[1] or explosion
  +
end
  +
StatObject.pcacheIn(getWeaponAttack(weap, explosion), 'BaseAttack', atk)
  +
return StatObject.pucacheIn(weapAtk, 'Explosion', explosion)
  +
end,
  +
IsVariant = function(weap)
  +
return StatObject.pucacheIn(weap, 'IsVariant', p._isVariant(statRead(weap, 'Name')))
  +
end,
  +
Variant = indexes('IsVariant', 2),
  +
BaseName = indexes('IsVariant', 3),
  +
Categories = { function(weapAtk)
  +
local cats = { 'Weapons' }
  +
-- Adding editor-defined traits from M:Weapons/data
  +
-- Note to make sure they have a proper category page associated with a trait
  +
for _, trait in ipairs(weapAtk.Traits or {}) do
  +
table.insert(cats, trait..' Weapons')
  +
end
  +
--[=[
  +
local CATEGORY_MAP = {
  +
Primary = 'Primary Weapons',
  +
Secondary = 'Secondary Weapons',
  +
Melee = 'Melee Weapons',
  +
['Archmelee'] = 'Archwing Melee',
  +
['Archgun'] = 'Archwing Gun',
  +
['Archgun (Atmosphere)'] = 'Archwing Gun',
  +
Kitgun = 'Kitgun',
  +
Zaw = 'Zaw',
  +
['Railjack Turret'] = 'Railjack',
  +
['Railjack Armament'] = 'Railjack',
  +
Gear = 'Gear',
  +
  +
Rifle = 'Assault Rifle',
  +
['Sniper Rifle'] = 'Sniper Rifle',
  +
Shotgun = 'Shotgun',
  +
Pistol = 'Pistol',
  +
['Dual Pistols'] = 'Dual Pistols',
  +
Bow = 'Bow',
  +
Launcher = 'Launcher',
  +
['Arm-Cannon'] = 'Arm-Cannon',
  +
['Speargun'] = 'Speargun',
  +
Thrown = 'Thrown',
  +
['Shotgun Sidearm'] = 'Shotgun Sidearm',
  +
  +
Prime = 'Prime Weapons',
  +
['Never Vaulted'] = 'Never Vaulted',
  +
Vaulted = 'Vaulted',
  +
Wraith = 'Wraith',
  +
Vandal = 'Vandal',
  +
['Kuva Lich'] = 'Kuva Lich',
  +
['Kuva Lich'] = 'Kuva',
  +
Prisma = 'Prisma',
  +
  +
Grineer = 'Grineer Weapons',
  +
Corpus = 'Corpus Weapons',
  +
Infested = 'Infested Weapons',
  +
Tenno = 'Tenno Weapons',
  +
Sentient = 'Sentient Weapons',
  +
Entrati = 'Entrati Weapons',
  +
Baro = 'Baro Ki\'Teer Offering',
  +
Syndicate = 'Syndicate Offerings',
  +
['Invasion Reward'] = 'Invasion Reward',
  +
  +
['Alt Fire'] = 'Weapons with Alt Fire',
  +
['AoE'] = 'Weapons with Area of Effect]][[Category:Self Interrupt Weapons',
  +
  +
}
  +
--]=]
  +
local bias = p._getValue(weapAtk, "BiasType")
  +
table.insert(cats, bias..' Damage Weapons')
  +
  +
local class = p._getValue(weapAtk, "Class")
  +
table.insert(cats, ({
  +
["Arm Cannon"] = class,
  +
["Bow"] = class,
  +
["Crossbow"] = "Bow",
  +
["Dual Pistols"] = class,
  +
["Dual Shotguns"] = class,
  +
["Exalted Weapon"] = class,
  +
["Launcher"] = class,
  +
["Pistol"] = class,
  +
["Rifle"] = class,
  +
["Shotgun Sidearm"] = class,
  +
["Shotgun"] = class,
  +
["Sniper Rifle"] = class,
  +
["Speargun"] = class,
  +
["Thrown"] = class,
  +
["Tome"] = class,
  +
["Assault Saw"] = class,
  +
["Blade and Whip"] = class,
  +
Claws = class,
  +
Dagger = class,
  +
["Dual Daggers"] = class,
  +
["Dual Nikanas"] = class,
  +
["Dual Swords"] = class,
  +
Fist = class,
  +
Glaive = class,
  +
Gunblade = class,
  +
Hammer = class,
  +
["Heavy Blade"] = class,
  +
["Heavy Scythe"] = class,
  +
Machete = class,
  +
Nikana = class,
  +
Nunchaku = class,
  +
Polearm = class,
  +
Rapier = class,
  +
Scythe = class,
  +
Sparring = class,
  +
Staff = class,
  +
Sword = class,
  +
["Sword and Shield"] = class,
  +
["Two-Handed Nikana"] = class,
  +
Tonfa = class,
  +
Warfan = class,
  +
Whip = class,
  +
["Archgun"] = 'Archwing Gun',
  +
["Archgun (Atmosphere)"] = 'Archwing Gun',
  +
["Archmelee"] = 'Archwing Melee',
  +
["Turret"] = 'Railjack Armaments',
  +
["Ordnance"] = 'Railjack Armaments',
  +
Amp = class
  +
})[class] or "Other Weapons")
   
  +
local family = p._getValue(weapAtk, "Family")
--Special function for modular weapons
 
  +
table.insert(cats, family)
function p.getZaw(validateFunction)
 
  +
local weaps = {}
 
  +
local slot = p._getValue(weapAtk, "Slot")
for i, weap in Shared.skpairs(ModularData["Zaw"]["Strike"]) do
 
  +
table.insert(cats, slot..' Weapons')
if((weap.Ignore == nil or not weap.Ignore) and validateFunction(weap)) then
 
  +
table.insert(weaps, weap)
 
  +
-- TODO: Move all these trigger categories to be in the format "Category:Trigger Name Weapons"
end
 
  +
local trigger = p._getValue(weapAtk, "Trigger")
end
 
  +
table.insert(cats, trigger..' Weapons')
return weaps
 
  +
-- TODO: remove original trigger cats? (following snippet)
  +
table.insert(cats,(({
  +
["Active"] = trigger,
  +
["Auto Charge"] = 'Charge',
  +
["Auto"] = 'Automatic',
  +
["Auto-Burst"] = 'Burst Fire',
  +
["Auto-Spool"] = 'Automatic',
  +
["Burst"] = 'Burst Fire',
  +
["Charge"] = trigger,
  +
["Duplex"] = 'Duplex Fire',
  +
["Held"] = 'Continuous Weapons',
  +
["Semi-Auto"] = 'Semi-Automatic',
  +
})[trigger]))--modes? " Weapons"?
  +
  +
local users = p._getValue(weapAtk, "Users") or {}
  +
for _, user in ipairs(users) do table.insert(cats, user) end
  +
  +
local variant = p._getValue(weapAtk, "Variant")
  +
table.insert(cats, variant)
  +
  +
local infAmmo = p._getValue(weapAtk, "AmmoMax") == math.huge
  +
local accuracy = p._getValue(weapAtk, "Accuracy")
  +
local pinpoint = accuracy ~= nil and accuracy >= 100
  +
local regens = p._getValue(weapAtk, "ReloadRate") > 0
  +
local silent = weapAtk.IsSilent -- automatically includes
  +
local single = p._getValue(weapAtk, "Magazine") == 1 and not p._getValue(weapAtk, "IsMelee")--meh, delet?
  +
local spools = p._getValue(weapAtk, "Spool") > 0
  +
  +
local isHitScan = p._getValue(weapAtk, "ShotType") == "Hit-Scan"
  +
local isProjectile = p._getValue(weapAtk, "ShotType") == "Projectile"
  +
local isAoE = p._getValue(weapAtk, "HasAoEAttack")
  +
local isCodexSecret = p._getValue(weapAtk, "CodexSecret")
  +
local isTradable = p._getValue(weapAtk, "Tradable")
  +
local isInConclave = p._getValue(weapAtk, "Conclave")
  +
  +
-- Arbitrarily ordering misc categories
  +
if infAmmo then table.insert(cats, 'Infinite Ammo Weapons') end
  +
if pinpoint then table.insert(cats, 'Pinpoint Weapons') end
  +
if regens then table.insert(cats, 'Battery Weapons') end
  +
if silent then
  +
table.insert(cats, 'Silent Weapons')
  +
else
  +
table.insert(cats, 'Alarming Weapons')
  +
end
  +
if single then table.insert(cats, 'Single Shot Weapons') end
  +
if spools then table.insert(cats, 'Spooling Weapons') end
  +
if isHitScan then table.insert(cats, 'Hit-Scan Weapons') end
  +
if isProjectile then table.insert(cats, 'Projectile Weapons') end
  +
if isAoE then table.insert(cats, 'Weapons with Area of Effect') end
  +
if isCodexSecret then table.insert(cats, 'Codex Secret') end
  +
if isTradable then
  +
table.insert(cats, 'Tradeable Weapons')
  +
else
  +
table.insert(cats, 'Untradeable Weapons')
  +
end
  +
if isInConclave then table.insert(cats, 'Available In Conclave') end
  +
  +
return StatObject.cacheIn(weapAtk, 'Categories', cats)
  +
end, function(s, cats)
  +
local wikitextResult = { '' } -- Need to prepend a newline so first asterisk is rendered as a wikitext list
  +
local formatStr = '*[[:Category:%s|%s]][[Category:%s]]'
  +
for _, category in ipairs(cats) do
  +
table.insert(wikitextResult, formatStr:format(category, category, category))
  +
end
  +
return table.concat(wikitextResult, '\n')
 
end
 
end
  +
},
function p.getKitgun(validateFunction)
 
  +
SyndicateEffect = { '', function(s, v)
local weaps = {}
 
  +
return (v == '' or type(v) ~= 'string') and '' or Tooltip.icon(({
for i, weap in Shared.skpairs(ModularData["Kitgun"]["Chamber"]) do
 
  +
['blight'] = 'Red Veil',
if((weap.Ignore == nil or not weap.Ignore) and validateFunction(weap)) then
 
  +
['entropy'] = 'Cephalon Suda',
table.insert(weaps, weap)
 
  +
['justice'] = 'Steel Meridian',
end
 
  +
['purity'] = 'New Loka',
end
 
  +
['sequence'] = 'The Perrin Sequence',
return weaps
 
  +
['truth'] = 'Arbiters of Hexis',
  +
})[v:lower()] or 'Tenno', 'Factions')
  +
..' '..v
 
end
 
end
  +
},
function p.getKitgunPrimary(validateFunction)
 
  +
MinProgenitorBonus = function(weap) return weap.IsLichWeapon and statRead(weap, 'BaseDamage') * 0.25 or 0 end,
local weaps = {}
 
  +
ProgenitorBonus = function(weap) return weap.IsLichWeapon and statRead(weap, 'BaseDamage') * 0.6 or 0 end,
for i, weap in Shared.skpairs(ModularData["KitgunPrimary"]["Chamber"]) do
 
  +
Class = '',
if((weap.Ignore == nil or not weap.Ignore) and validateFunction(weap)) then
 
  +
SniperComboReset = { nil, '%.1f s' },
table.insert(weaps, weap)
 
  +
SniperComboMin = { nil, '%d shot(s)' },
end
 
  +
Tradable = { function(weapAtk)
end
 
  +
if type(weapAtk['Tradable'])=='number' then
return weaps
 
  +
assert(weapAtk['Tradable']<=5,
  +
'Tradable: Does not support tradeability enums beyond 5; please update [[Module:Weapons/data]] and [[Module:Weapons]] to support more tradeability edge cases')
  +
return ({
  +
[0]=false,
  +
[1]='Unranked',
  +
[2]='Parts',
  +
[3]='Lich',
  +
[4]='Built Parts',
  +
[5]='Parent',
  +
})[weapAtk['Tradable']]
  +
end
  +
return weapAtk['Tradable']
  +
end, function(s, tradable)
  +
return ({
  +
[false] = 'Untradeable',
  +
Unranked = 'Tradeable unranked w/ no Forma or Catalyst',
  +
Parts = 'Tradeable parts and/or blueprint only',
  +
Lich = 'Tradeable indirectly through [[Lich System|Lich]] trading',
  +
['Built Parts'] = 'Tradeable only fully built components, not blueprints',
  +
Parent = 'Tradeable indirectly, comes with parent companion',
  +
})[tradable] or 'Untradeable?'
 
end
 
end
  +
},
 
  +
SellPrice = { nil, function(self, sellPrice)
--Same as getWeapons, but for Conclave data
 
  +
if sellPrice == nil then
function p.getConclaveWeapons(validateFunction)
 
  +
return 'Cannot sell'
local weaps = {}
 
  +
end
for i, weap in Shared.skpairs(ConclaveData["Weapons"]) do
 
  +
return Tooltip.icon('Credits', 'Resources')..' '..Math.formatnum(sellPrice)
if((weap.Ignore == nil or not weap.Ignore) and validateFunction(weap)) then
 
table.insert(weaps, weap)
 
end
 
end
 
return weaps
 
 
end
 
end
  +
},
 
  +
DefaultUpgrades = { nil, function(self, upgradesArr)
local function asMultiplier(val)
 
  +
local result = {}
if(val == nil) then
 
  +
for _, modIndex in ipairs(upgradesArr or {}) do
return "1.0x"
 
  +
table.insert(result, Tooltip.full(modIndex, 'Mods'))
end
 
  +
end
return Shared.round(val, 2, 1).."x"
 
  +
return table.concat(result, '<br />')
 
end
 
end
  +
},
 
  +
Users = { nil, function(self, usersArr)
--Returns all melee weapons.
 
  +
local result = { '' }
--If weapType is not nil, only grab for a specific type
 
  +
for _, user in ipairs(usersArr or {}) do
--For example, if weapType is "Nikana", only pull Nikanas
 
  +
table.insert(result, '*[['..user..']]')
local function getStances(weapType, pvpOnly, weapName)
 
  +
end
local stances = {}
 
  +
return table.concat(result, '\n')
 
if weapType ~= "Exalted Weapon" then
 
for i, stance in Shared.skpairs(WeaponData["Stances"]) do
 
local classMatch = (weapType == nil or weapType == stance.Class)
 
local pvpMatch = (pvpOnly ~= nil and pvpOnly) or (stance.PvP == nil or not stance.PvP)
 
if (classMatch and pvpMatch) then
 
table.insert(stances, stance)
 
end
 
end
 
else
 
for i, stance in Shared.skpairs(WeaponData["Stances"]) do
 
local nameMatch = (weapName == nil or weapName == stance.Weapon)
 
if nameMatch then
 
table.insert(stances, stance)
 
end
 
end
 
end
 
return stances
 
 
end
 
end
  +
},
 
  +
Zoom = { unpacks('Zoom'), { sep = '<br />' } },
local function HasTrait(Weapon, Trait)
 
  +
Slot = nil,
if(Trait == nil or Weapon.Traits == nil) then
 
  +
}
return false
 
  +
-- Loops for adding to StatObject.default table
end
 
  +
-- Damage type getters:
 
  +
-- <DamageType> = damage type value
for i, theTrait in pairs(Weapon.Traits) do
 
  +
-- <DamageType>Distribution = damage type distribution as a percentage
if(theTrait == Trait) then
 
  +
-- PvP<DamageType> = damage type value with precise formatting for PvP purposes
return true
 
  +
for _, damageType in ipairs(iterationOrderArray) do
end
 
  +
StatObject.default[damageType] = {
end
 
  +
function(weapAtk) return weapAtk['Damage'][damageType] or 0 end,
 
  +
function(self, value) return Tooltip.icon(damageType, 'DamageTypes')..' '..Math.formatnum(value) end
return false
 
  +
}
  +
-- Damage distribution as a percentage
  +
StatObject.default[damageType..'Distribution'] = {
  +
function(weapAtk) return weapAtk['Damage'][damageType] / statRead(weapAtk, 'BaseDamage') end,
  +
function(self, value) return Tooltip.icon(damageType, 'DamageTypes')..' '..Math.percentage(value) end
  +
}
  +
-- More precise damage values to 4 decimal places for PvP
  +
StatObject.default['PvP'..damageType] = {
  +
function(weapAtk) return weapAtk['Damage'][damageType] or 0 end,
  +
Tooltip.icon(damageType, 'DamageTypes')..' %.4f'
  +
}
 
end
 
end
   
--If Type is not nil, get damage weapon deals of that type
+
-- TODO: Do not rely on localized name to determine a weapon's variant. Decouple localization from data
  +
--- Checks if a weapon is a variant or not.
--If it deals no damage of that type, return 0 instead of nil
 
  +
-- @function p._isVariant
--It Type is nil, return total damage
 
  +
-- @param {string} weaponName Weapon name
local function GetDamage(Attack, Type, ByPellet)
 
  +
-- @returns {boolean} True if weapon is a variant, false otherwise
if(ByPellet == nil) then ByPellet = false end
 
  +
-- @returns {string} Weapon's variant name or "Base" if weapon is not a variant
if(Attack == nil or Attack.Damage == nil) then return 0 end
 
  +
-- @returns {string} Weapon name, same as weaponName
 
  +
function p._isVariant(weaponName)
local pCount = 1
 
  +
for i, var in pairs(VARIANT_LIST) do
if(ByPellet and Attack.PelletCount ~= nil) then pCount = Attack.PelletCount end
 
if(Type == nil) then
+
if (var ~= "Dex" or weaponName ~= "Dex Pixia") then
  +
if string.find(weaponName, var) then
local total = 0
 
  +
return true, var, (string.gsub(weaponName, " ?"..var.." ?-?", ""))
for i, d in Shared.skpairs(Attack.Damage) do
 
  +
end
total = total + d
 
  +
end
end
 
  +
end
return total / pCount
 
  +
return false, "Base", weaponName
else
 
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) / pCount
 
elseif(Type == "Element") then
 
for dType, dmg in Shared.skpairs(Attack.Damage) do
 
if(not Shared.contains(Physical, dType) or dmg <= 0) then
 
return dmg / pCount
 
end
 
end
 
return 0
 
elseif(Attack.Damage[Type] == nil) then
 
return 0
 
else
 
return Attack.Damage[Type] / pCount
 
end
 
end
 
 
end
 
end
   
  +
--- Builds the full name of a weapon's variant. Does not check if it exists or not.
--Returns the damage string as it's formatted in a comparison row
 
  +
-- @function p._buildName
--So instead of '0', returns '-', and appends the icon for an element if necessary
 
  +
-- @param {string} baseName Weapon's base name (e.g. "Braton")
local function GetDamageString(Attack, Type, ByPellet)
 
  +
-- @param[opt] {string} variant Variant name (e.g. "Vandal"); if nil, returns base weapon name instead
if(ByPellet == nil) then ByPellet = false end
 
  +
-- @returns {string} Weapon's variant name (e.g. "Braton Vandal")
if(Attack == nil or Attack.Damage == nil) then return "" end
 
  +
function p._buildName(baseName, variant)
 
  +
if not variant or variant == 'Base' or variant == '' then
local pCount = 1
 
  +
return baseName
if(ByPellet and Attack.PelletCount ~= nil) then pCount = Attack.PelletCount end
 
  +
end
if(Type == nil) then
 
  +
return (({
if(not hasMultipleTypes(Attack)) then
 
  +
-- Prime Laser Rifle is an edge case for Prime naming scheme (at least in EN localization)
for key, val in pairs(Attack.Damage) do
 
  +
Prime = baseName ~= 'Laser Rifle' and '%b %v',
if(val > 0) then
 
  +
Vandal = '%b %v',
return Icon._Proc(key).." "..Shared.round(val / pCount, 2)
 
  +
Wraith = '%b %v',
end
 
  +
MK1 = '%v-%b',
end
 
  +
})[variant] or '%v %b'):gsub('%%v', variant):gsub('%%b', baseName)
return ""
 
else
 
return Shared.round(GetDamage(Attack, nil, ByPellet), {2, 1})
 
end
 
else
 
local thisVal = GetDamage(Attack, Type, ByPellet)
 
if(thisVal == 0) then
 
return ""
 
else
 
return Shared.round(thisVal, {2, 1})
 
end
 
end
 
 
end
 
end
   
  +
--- Returns a specific weapon table entry from <code>/data</code> or <code>/Conclave/data</code>.
local function GetDamageBias(Attack, IncludeSingle)
 
  +
-- @function p._getWeapon
if(IncludeSingle == nil) then IncludeSingle = false end
 
  +
-- @param {string} weaponName Weapon name
 
  +
-- @param[opt] {boolean} pvp If true, gets PvP stats of weapon instead, false otherwise; defaults to false
if(Attack.Damage ~= nil and Shared.tableCount(Attack.Damage) > 0) then
 
  +
-- @returns {table} Weapon table
local total = 0
 
  +
function p._getWeapon(weaponName, pvp)
local bestDmg = 0
 
  +
weaponName = mw.text.decode(weaponName)
local bestElement = nil
 
  +
return (pvp and ConclaveData or WeaponData)[weaponName] or
local count = 0
 
  +
error('p._getWeapon(weaponName, pvp): "'..weaponName..
for Element, Dmg in pairs(Attack.Damage) do
 
  +
'" does not exist in '..(pvp and '[[Module:Weapons/Conclave/data]]' or '[[Module:Weapons/data]]'))
if(Dmg > bestDmg) then
 
bestDmg = Dmg
 
bestElement = Element
 
end
 
total = total + Dmg
 
if(Dmg > 0) then
 
count = count + 1
 
end
 
end
 
--Make sure there are two damage instances that are above zero
 
--Exception for physical damage types
 
if(count > 1 or (IncludeSingle and count > 0)) then
 
return (bestDmg / total), bestElement
 
else
 
return nil
 
end
 
else
 
return nil
 
end
 
 
end
 
end
   
  +
--- Gets the raw value of a certain statistic of a weapon.
--If the attack has at least two damage types,
 
  +
-- @function p._getValue
-- Returns something like "58.3% Slash"
 
  +
-- @param {table} Weapon Weapon table
--If it doesn't, returns nil
 
  +
-- @param {string} key Name of key
local function GetDamageBiasString(Attack, HideType, ShowText, IncludeSingle, Color)
 
  +
-- @param[opt] {string} attack Name of attack to search through; defaults to 'Attack1' or what '_TooltipAttackDisplay' is set to
if(HideType == nil) then HideType = false end
 
  +
-- @returns {string, number} Value of statistic
if(ShowText == nil) then ShowText = "" end
 
  +
function p._getValue(weap, key, atk)--, formatted)
if(IncludeSingle == nil) then IncludeSingle = false end
 
  +
-- return (formatted and statFormat or statRead)(weap, atk, key)
 
  +
return p._statRead(weap, atk, key)
local bestPercent, bestElement = GetDamageBias(Attack, IncludeSingle)
 
if(bestPercent ~= nil) then
 
local result = Shared.asPercent(bestPercent, 0)
 
if(not HideType) then
 
result = Icon._Proc(bestElement, ShowText, Color).." "..result
 
end
 
return result
 
else
 
return nil
 
end
 
 
end
 
end
   
  +
--- Gets the formatted value of a certain statistic of a weapon to be displayed
function p.GetPolarityString(Weapon)
 
  +
-- the wiki.
if(Weapon.Polarities == nil or type(Weapon.Polarities) ~= "table" or Shared.tableCount(Weapon.Polarities) == 0) then
 
  +
-- @function p._getFormattedValue
return "None"
 
  +
-- @param {table} Weapon Weapon table
else
 
  +
-- @param {string} keyName Name of key
local resString = ""
 
  +
-- @param[opt] {string} attackName Name of attack to search through; defaults to 'Attack1'
for i, pol in pairs(Weapon.Polarities) do
 
  +
-- @returns {string} Value of statistic
local tempPol = Icon._Pol(pol)
 
  +
function p._getFormattedValue(weap, key, atk)
if tempPol == "<span style=\"color:red;\">Invalid</span>" and Weapon.Name == "Exalted Blade" then
 
  +
-- return p._getValue(Weapon, keyName, attackName, true)
resString = resString..pol
 
  +
return p._statFormat(weap, atk, key)
else
 
resString = resString..tempPol
 
end
 
end
 
return resString
 
end
 
 
end
 
end
   
  +
--- Function that returns a simpler getter function, for multiple _stat*() calls on the same weapon/attack pair.
local function GetExilusPolarity(Weapon)
 
  +
-- @function p._statReader
if(Weapon.ExilusPolarity == nil or Weapon.ExilusPolarity == "None") then
 
  +
-- @param {table} weap Weapon entry
return "None"
 
  +
-- @param {number|table} atk Attacks table index or Attack entry
else
 
  +
-- @return {function} Getter function
return Icon._Pol(Weapon.ExilusPolarity)
 
  +
function p._statReader(weap, atk)
end
 
  +
return function(...) return p._statRead(weap, atk, ...) end
 
end
 
end
   
  +
--- Function that returns a simpler getter function, for multiple _stat*() calls on the same weapon/attack pair.
local function GetStancePolarity(Weapon)
 
  +
-- @function p._statFormatter
if(Weapon.StancePolarity == nil or Weapon.StancePolarity == "None") then
 
  +
-- @param {table} weap Weapon entry
return "None"
 
  +
-- @param {number|table} atk Attacks table index or Attack entry
else
 
  +
-- @return {function} Getter function
return Icon._Pol(Weapon.StancePolarity)
 
  +
function p._statFormatter(weap, atk)
end
 
  +
return function(...) return p._statFormat(weap, atk, ...) end
 
end
 
end
   
  +
--- Returns a subset of <code>/data</code> or <code>/Conclave/data</code> based on a validation function.
local function getWeaponStanceList(Weapon)
 
  +
-- @function p._getWeapons
if(Weapon == nil or Weapon.Type ~= "Melee") then return nil end
 
  +
-- @param {function} validateFunction Function that filters out a weapon by taking in a Weapon table argument
 
  +
-- @param[opt] {string} source Name of weapon entry to use
local stances = getStances(Weapon.Class, Weapon.Conclave, Weapon.Name)
 
  +
-- @param[opt] {boolean} ignoreIgnore If true, ignores the _IgnoreEntry flag, false otherwise; defaults to false
 
  +
-- @param[opt] {function} sortFunc Custom comparison function; false -> no sorting; defaults to sorting in ascending order by weapon name
local result = ""
 
  +
-- @returns {table} Table of weapon table entries as seen in <code>/data</code>
 
  +
function p._getWeapons(validateFunction, source, opts)
for i, stance in pairs(stances) do
 
  +
opts=opts or {}
if (string.len(result) > 0) then
 
  +
local ignoreIgnore, sortFunc, pvp = opts.ignoreIgnore, opts.sortFunc, opts.pvp
result = result.."<br/>"
 
  +
validateFunction = validateFunction or function() return true end
end
 
  +
local data = pvp and ConclaveData or WeaponData
 
  +
if source then
local polarity = ""
 
  +
data = data[source]
local link = ""
 
  +
end
if Weapon.Class ~= "Exalted Weapon" then
 
polarity = " ("..Icon._Pol(stance.Polarity)..")"
 
link = "[["..stance.Name.."]]"
 
else
 
link = "[["..stance.Link.."|"..stance.Name.."]]"
 
end
 
 
result = result.."<span class=\"mod-tooltip\" data-param=\""..stance.Name.."\" style=\"white-space:pre\">"..link.."</span>"..polarity
 
--If this is a PvP Stance, add the disclaimer
 
if(stance.PvP ~= nil and stance.PvP) then
 
result = result.." (PvP Only)"
 
end
 
end
 
 
return result
 
end
 
   
  +
local weaps = {}
local function getAttackValue(Weapon, Attack, ValName, giveDefault, asString, forTable)
 
  +
for _, weap in pairs(data) do
if(giveDefault == nil) then giveDefault = false end
 
  +
if (ignoreIgnore or not weap['_IgnoreEntry']) and validateFunction(weap) then
if(asString == nil) then asString = false end
 
  +
table.insert(weaps, weap)
if(forTable == nil) then forTable = false end
 
  +
end
if(Attack == nil) then
 
  +
end
if(asString) then
 
  +
if sortFunc ~= false then
return ""
 
  +
table.sort(weaps, sortFunc or function(a, b) return a.Name < b.Name end)
else
 
  +
end
return nil
 
  +
return weaps
end
 
end
 
local regularVal = Shared.titleCase(ValName)
 
if(ValName == "Damage") then
 
if(Attack.Damage ~= nil) then
 
if(asString) then
 
return GetDamageString(Attack, nil)
 
else
 
return GetDamage(Attack)
 
end
 
elseif giveDefault then
 
return 0
 
else
 
return nil
 
end
 
elseif(ValName == "DamagePlus") then
 
if(Attack.Damage ~= nil) then
 
if(asString) then
 
if(hasMultipleTypes(Attack)) then
 
return GetDamageString(Attack, nil).." ("..GetDamageBiasString(Attack, nil, "")..")"
 
else
 
return GetDamageString(Attack, nil)
 
end
 
else
 
return GetDamage(Attack)
 
end
 
elseif giveDefault then
 
return 0
 
else
 
return nil
 
end
 
elseif(Shared.contains(Elements, regularVal) or ValName == "Physical" or ValName == "Element") then
 
if(Attack.Damage ~= nil) then
 
if(asString) then
 
if(hasMultipleTypes(Attack)) then
 
return GetDamageString(Attack, regularVal)
 
else
 
return nil
 
end
 
else
 
return GetDamage(Attack, regularVal)
 
end
 
elseif giveDefault then
 
return 0
 
else
 
return nil
 
end
 
elseif(ValName == "Accuracy") then
 
if(Attack.Accuracy ~= nil) then
 
return Attack.Accuracy
 
elseif(Attack == Weapon.NormalAttack) then
 
return Weapon.Accuracy
 
elseif giveDefault then
 
return 0
 
else
 
return nil
 
end
 
elseif(ValName == "AttackName") then
 
if(Attack.AttackName ~= nil) then
 
return Attack.AttackName
 
elseif(giveDefault) then
 
return ""
 
else
 
return nil
 
end
 
elseif(ValName == "AmmoCost") then
 
if(Attack.AmmoCost ~= nil) then
 
if(asString) then
 
return Attack.AmmoCost.." ammo per shot"
 
else
 
return Attack.AmmoCost
 
end
 
elseif(giveDefault) then
 
return 1
 
else
 
return nil
 
end
 
elseif(ValName == "BlockAngle") then
 
if(Attack.BlockAngle ~= nil) then
 
if(asString) then
 
return Attack.BlockAngle.."&#176;"
 
else
 
return Attack.BlockAngle
 
end
 
else
 
return nil
 
end
 
elseif(ValName == "BurstCount") then
 
if(Attack.BurstCount ~= nil) then
 
if(asString) then
 
local result = Attack.BurstCount.." rounds"
 
local dmg = GetDamage(Attack) * Attack.BurstCount
 
return result.." ("..Shared.round(dmg, 2, 1).." total damage)"
 
else
 
return Attack.BurstCount
 
end
 
elseif giveDefault then
 
return 0
 
else
 
return nil
 
end
 
elseif(ValName == "BurstFireRate") then
 
if(Attack.BurstFireRate ~= nil) then
 
return Attack.BurstFireRate
 
elseif giveDefault then
 
return 0
 
else
 
return nil
 
end
 
elseif(ValName == "ChargeTime") then
 
if(Attack.ChargeTime ~= nil) then
 
if(asString) then
 
return Shared.round(Attack.ChargeTime, 2, 1).." s"
 
else
 
return Attack.ChargeTime
 
end
 
elseif giveDefault then
 
return 0
 
else
 
return nil
 
end
 
elseif(ValName == "ComboDur") then
 
if(Attack.ComboDur ~= nil) then
 
return Attack.ComboDur
 
else
 
return nil
 
end
 
elseif(ValName == "CritChance") then -- note there is getValue version
 
--search in current attack, then normal,
 
if(Attack.CritChance ~= nil) then
 
if(asString) then
 
return Shared.asPercent(Attack.CritChance)
 
else
 
return Attack.CritChance
 
end
 
end
 
if(hasAttack(Weapon, "Normal")) then
 
local normAtt = getAttack(Weapon, "Normal")
 
if(normAtt.CritChance ~= nil) then
 
if(asString) then
 
return Shared.asPercent(normAtt.CritChance)
 
else
 
return normAtt.CritChance
 
end
 
end
 
end
 
if giveDefault then
 
if(asString) then
 
return Shared.asPercent(0)
 
else
 
return 0
 
end
 
else
 
return nil
 
end
 
elseif(ValName == "CritMultiplier") then -- note there is getValue version
 
--search in current attack, then normal,
 
if(Attack.CritMultiplier ~= nil) then
 
if(asString) then
 
return asMultiplier(Attack.CritMultiplier)
 
else
 
return Attack.CritMultiplier
 
end
 
end
 
if(hasAttack(Weapon, "Normal")) then
 
local normAtt = getAttack(Weapon, "Normal")
 
if(normAtt.CritMultiplier ~= nil) then
 
if(asString) then
 
return asMultiplier(normAtt.CritMultiplier)
 
else
 
return normAtt.CritMultiplier
 
end
 
end
 
end
 
if giveDefault then
 
if(asString) then
 
return asMultiplier(0)
 
else
 
return 0
 
end
 
else
 
return nil
 
end
 
elseif(ValName == "DamageBias") then
 
if(Shared.tableCount(Attack.Damage) <= 4) then
 
if (GetDamageBiasString(Attack, nil, nil, giveDefault)~=nil) then
 
return GetDamageBiasString(Attack, nil, nil, giveDefault)
 
else
 
return ""
 
end
 
else
 
if(asString or giveDefault) then
 
return ""
 
else
 
return nil
 
end
 
end
 
elseif(ValName == "BulletType") then
 
if(Attack.ShotType ~= nil) then
 
return Attack.ShotType
 
elseif(giveDefault) then
 
return "Unknown"
 
else
 
return nil
 
end
 
elseif(ValName == "Duration") then
 
if(Attack.Duration ~= nil) then
 
if(asString) then
 
return Shared.round(Attack.Duration, 1, 0).." s"
 
else
 
return Attack.Duration
 
end
 
elseif giveDefault then
 
return 0
 
else
 
return nil
 
end
 
elseif(ValName == "ElementType") then
 
if(Attack.Damage ~= nil) then
 
for dType, dmg in Shared.skpairs(Attack.Damage) do
 
if(not Shared.contains(Physical, dType) or dmg <= 0) then
 
if(asString) then
 
return Icon._Proc(dType)
 
else
 
return dType
 
end
 
end
 
end
 
elseif asString then
 
return ""
 
else
 
return nil
 
end
 
elseif(ValName =="ElementTypeName") then
 
if(Attack.Damage ~= nil) then
 
for dType, dmg in Shared.skpairs(Attack.Damage) do
 
if(not Shared.contains(Physical, dType) or dmg <= 0) then
 
return dType
 
end
 
end
 
elseif asString then
 
return ""
 
else
 
return nil
 
end
 
elseif(ValName == "Falloff") then
 
if(Attack.Falloff ~= nil) then
 
local falloffText = "Full damage up to "..Shared.round(Attack.Falloff.StartRange, 2, 1).." m"
 
falloffText = falloffText.."<br/>Min damage at "..Shared.round(Attack.Falloff.EndRange, 2, 1).." m"
 
if(Attack.Falloff.Reduction ~= nil) then
 
falloffText = falloffText.."<br/>"..Shared.asPercent(Attack.Falloff.Reduction).." max reduction"
 
end
 
return falloffText
 
else
 
return nil
 
end
 
elseif(ValName == "FireRate") then --search in current attack, then normal, then weapon supertable
 
local returnVal = 0
 
if(Attack.FireRate ~= nil) then
 
returnVal = Attack.FireRate
 
elseif(hasAttack(Weapon, "Normal")) then
 
local normAtt = getAttack(Weapon, "Normal")
 
if(normAtt.FireRate ~= nil) then
 
returnVal = normAtt.FireRate
 
end
 
elseif(Weapon.FireRate ~= nil) then
 
returnVal = Weapon.FireRate
 
end
 
if(asString) then
 
if(Weapon.Type ~= nil and Weapon.Type ~= "Melee" and Weapon.Type ~= "Arch-Melee") then
 
if(forTable) then
 
return Shared.round(returnVal, {3, 1}) -- .." rps"
 
else
 
return Shared.round(returnVal, {3, 1})..p.doPlural(" round<s> per sec", returnVal)
 
end
 
else
 
return Shared.round(returnVal, {3, 1})
 
end
 
else
 
return returnVal
 
end
 
if (giveDefault) then
 
returnVal = 0
 
else
 
return nil
 
end
 
elseif(ValName == "FollowThrough") then
 
if(Attack.FollowThrough ~= nil) then
 
if(asString) then
 
return Shared.asPercent(Attack.FollowThrough)
 
else
 
return Attack.FollowThrough
 
end
 
else
 
return nil
 
end
 
elseif(ValName == "HeadshotMultiplier") then
 
--search in current attack, then normal, and finally fall back on base weapon value if for some reason that exists
 
if(Attack.HeadshotMultiplier ~= nil) then
 
if(asString) then
 
return asMultiplier(Attack.HeadshotMultiplier)
 
else
 
return Attack.HeadshotMultiplier
 
end
 
end
 
if(hasAttack(Weapon, "Normal")) then
 
local normAtt = getAttack(Weapon, "Normal")
 
if(normAtt.HeadshotMultiplier ~= nil) then
 
if(asString) then
 
return asMultiplier(normAtt.HeadshotMultiplier)
 
else
 
return normAtt.HeadshotMultiplier
 
end
 
end
 
end
 
if(Weapon.HeadshotMultiplier ~= nil) then
 
if(asString) then
 
return asMultiplier(Weapon.HeadshotMultiplier)
 
else
 
return Weapon.HeadshotMultiplier
 
end
 
end
 
if giveDefault then
 
if(Weapon.Type ~= nil) then
 
--Setting multiplier based on default for each weapon type
 
if(Weapon.Type == "Secondary") then
 
if(Weapon.Class == "Shotgun Sidearm") then
 
return "1.2x"
 
elseif(Weapon.Class == "Crossbow" or Weapon.Class == "Thrown") then
 
return "1.5x"
 
else
 
if(Weapon.Trigger == "Auto") then
 
return "1.2x"
 
else
 
return "1.5x"
 
end
 
end
 
elseif(Weapon.Type == "Primary") then
 
if(Weapon.Class == "Shotgun") then
 
return "1.2x"
 
elseif(Weapon.Class == "Bow") then
 
return "2.0x"
 
elseif(Weapon.Class == "Sniper Rifle") then
 
return "1.4x"
 
elseif(Weapon.Class == "Launcher") then
 
return "1.0x"
 
else
 
if(Weapon.Trigger == "Auto") then
 
return "1.2x"
 
else
 
return "1.5x"
 
end
 
end
 
end
 
end
 
else
 
return nil
 
end
 
elseif(ValName == "MeleeRange") then
 
if(Attack.MeleeRange ~= nil) then
 
if(asString) then
 
return Attack.MeleeRange.." m"
 
else
 
return Attack.MeleeRange
 
end
 
elseif(giveDefault) then
 
if(asString) then
 
return "0 m"
 
else
 
return 0
 
end
 
else
 
return nil
 
end
 
elseif(ValName == "NoiseLevel") then
 
if(Attack.NoiseLevel ~= nil) then
 
return Attack.NoiseLevel
 
else
 
return nil
 
end
 
elseif(ValName == "PelletCount") then
 
if(Attack.PelletCount ~= nil) then
 
if(asString) then
 
if(Attack.PelletName ~= nil) then
 
return Attack.PelletCount.." "..Attack.PelletName.."s"
 
else
 
return Attack.PelletCount.." Pellets"
 
end
 
else
 
return Attack.PelletCount
 
end
 
elseif giveDefault then
 
return 0
 
else
 
return nil
 
end
 
elseif(ValName == "PelletName") then
 
if(Attack.PelletCount ~= nil) then
 
if(Attack.PelletName ~= nil) then
 
return Attack.PelletName
 
else
 
return "Pellet"
 
end
 
else
 
return nil
 
end
 
elseif(ValName == "PelletPlus") then
 
if(Attack.PelletCount ~= nil) then
 
local result = Attack.PelletCount.." ("
 
result = result..Shared.round(GetDamage(Attack, nil, true), 2, 1)
 
if(Attack.PelletName ~= nil) then
 
return result.." damage per "..string.lower(Attack.PelletName)..")"
 
else
 
return result.." damage per pellet)"
 
end
 
else
 
return nil
 
end
 
elseif(ValName == "ProjectileSpeed") then
 
if(Attack.ShotSpeed ~= nil) then
 
if(asString) then
 
return Attack.ShotSpeed.." m/s"
 
else
 
return Attack.ShotSpeed
 
end
 
elseif(giveDefault) then
 
if(asString) then
 
return "0 m/s"
 
else
 
return 0
 
end
 
else
 
return nil
 
end
 
elseif(ValName == "PunchThrough") then
 
if(Attack.PunchThrough ~= nil) then
 
if(asString) then
 
return Shared.round(Attack.PunchThrough, 2, 1).." m"
 
else
 
return Attack.PunchThrough
 
end
 
elseif giveDefault then
 
if(asString) then
 
return "0 m"
 
else
 
return 0
 
end
 
else
 
return nil
 
end
 
elseif(ValName == "Radius") then
 
if(Attack.Radius ~= nil) then
 
if(asString) then
 
return Shared.round(Attack.Radius, 2, 1).." m"
 
else
 
return Attack.Radius
 
end
 
elseif giveDefault then
 
if(asString) then
 
return "0 m"
 
else
 
return 0
 
end
 
else
 
return nil
 
end
 
elseif(ValName == "Range") then
 
if(Attack.Range ~= nil) then
 
if(asString) then
 
return Attack.Range.." m"
 
else
 
return Attack.Range
 
end
 
elseif(giveDefault) then
 
if(asString) then
 
return "0 m"
 
else
 
return 0
 
end
 
else
 
return nil
 
end
 
elseif(ValName == "Reload") then
 
if(Attack.Reload ~= nil) then
 
if(asString) then
 
return Shared.round(Attack.Reload, 2, 1).." s"
 
else
 
return Attack.Reload
 
end
 
elseif giveDefault then
 
return 0
 
else
 
return nil
 
end
 
elseif(ValName == "Spool") then
 
if(Attack.Spool ~= nil) then
 
if(asString) then
 
return Attack.Spool.." rounds"
 
else
 
return Attack.Spool
 
end
 
else
 
return nil
 
end
 
elseif(ValName == "Stances") then
 
return getWeaponStanceList(Weapon)
 
elseif(ValName == "StatusChance") then -- search in current attck, then normal
 
if(Attack.StatusChance ~= nil) then
 
if(asString) then
 
return Shared.asPercent(Attack.StatusChance)
 
else
 
return Attack.StatusChance
 
end
 
end
 
if(hasAttack(Weapon, "Normal")) then
 
local normAtt = getAttack(Weapon, "Normal")
 
if(normAtt.StatusChance ~= nil) then
 
if(asString) then
 
return Shared.asPercent(normAtt.StatusChance)
 
else
 
return normAtt.StatusChance
 
end
 
end
 
end
 
if giveDefault then
 
if(asString) then
 
return Shared.asPercent(0)
 
else
 
return 0
 
end
 
else
 
return nil
 
end
 
elseif(ValName == "Trigger") then -- use getValue for tables
 
-- search in current attack, then weapon supertable
 
if(Attack.Trigger ~= nil) then
 
return Attack.Trigger
 
end
 
if(Weapon.Trigger ~= nil) then
 
return Weapon.Trigger
 
end
 
if giveDefault then
 
return "Unknown"
 
else
 
return nil
 
end
 
else
 
return "ERROR: No such value "..ValName
 
end
 
 
end
 
end
   
  +
--- Returns all melee weapons. If weapType is not nil, only grab for a specific type
local function getValue(Weapon, ValName, giveDefault, asString, forTable, regenValue)
 
  +
-- For example, if weapType is "Nikana", only pull Nikanas.
if(giveDefault == nil) then giveDefault = false end
 
  +
-- @function p._getMeleeWeapons
if(asString == nil) then asString = false end
 
  +
-- @param[opt] {boolean} weapType
if(forTable == nil) then forTable = false end
 
  +
-- @param[opt] {boolean} pvp If true, only gets melee weapons available in Conclave, false otherwise; defaults to false
if(regenValue == nil) then regenValue = false end
 
  +
-- @returns {table} An array of melee weapon table entries as seen in <code>/data</code>
 
  +
function p._getMeleeWeapons(weapType,pvp)
if(type(ValName) == "table") then
 
  +
return p._getWeapons(weapType and function(weap) return weap.Class==weapType end, 'melee',{['pvp']=pvp==true})
local VName1 = ValName[1]
 
local VName2 = ValName[2]
 
if(VName1 == nil or VName2 == nil) then
 
return nil
 
end
 
 
if(VName1 == "Normal" or VName1 == "NormalAttack") then
 
return getAttackValue(Weapon, getAttack(Weapon, "Normal"), VName2, giveDefault, asString, forTable)
 
elseif(VName1 == "Area" or VName1 == "AreaAttack") then
 
return getAttackValue(Weapon, getAttack(Weapon, "Area"), VName2, giveDefault, asString, forTable)
 
elseif(VName1 == "SecondaryArea" or VName1 == "SecondaryAreaAttack") then
 
return getAttackValue(Weapon, getAttack(Weapon, "SecondaryArea"), VName2, giveDefault, asString, forTable)
 
elseif(VName1 == "Charge" or VName1 == "ChargeAttack") then
 
return getAttackValue(Weapon, getAttack(Weapon, "Charge"), VName2, giveDefault, asString, forTable)
 
elseif(VName1 == "ChargedThrow" or VName1 == "ChargedThrowAttack") then
 
return getAttackValue(Weapon, getAttack(Weapon, "ChargedThrow"), VName2, giveDefault, asString, forTable)
 
elseif(VName1 == "Throw" or Name1 == "ThrowAttack") then
 
return getAttackValue(Weapon, getAttack(Weapon, "Throw"), VName2, giveDefault, asString, forTable)
 
elseif(VName1 == "Secondary" or VName1 == "SecondaryAttack") then
 
return getAttackValue(Weapon, getAttack(Weapon, "Secondary"), VName2, giveDefault, asString, forTable)
 
elseif(VName1 == "Slam" or VName1 == "SlamAttack") then
 
return getAttackValue(Weapon, getAttack(Weapon, "SlamAttack"), VName2, giveDefault, asString, forTable)
 
elseif(VName1 == "Heavy" or VName1 == "HeavyAttack") then
 
return getAttackValue(Weapon, getAttack(Weapon, "HeavyAttack"), VName2, giveDefault, asString, forTable)
 
else
 
return "ERROR: No such attack \""..VName1.."\""
 
end
 
end
 
 
if(ValName == "Name") then
 
if(Weapon.Name ~= nil) then
 
if(forTable) then
 
if (string.find(Weapon.Name,"Atmosphere")~=nil) then
 
return "[["..string.gsub(Weapon.Name,"%s%(Atmosphere%)","").."|"..string.gsub(Weapon.Name,"osphere","").."]]"
 
else
 
return "[["..Weapon.Name.."]]"
 
end
 
else
 
return Weapon.Name
 
end
 
elseif giveDefault then
 
return ""
 
else
 
return nil
 
end
 
elseif(ValName == "ArsenalOverride") then
 
if(Weapon.ArsenalOverride ~= nil) then
 
return Weapon.ArsenalOverride
 
else
 
return nil
 
end
 
elseif(ValName == "AmmoType") then
 
if(Weapon.AmmoType ~= nil) then
 
return Weapon.AmmoType
 
elseif giveDefault then
 
if(Weapon.Type ~= nil) then
 
if(Weapon.Type == "Secondary") then
 
return "Pistol"
 
elseif(Weapon.Type == "Primary") then
 
if(Weapon.Class == nil or Weapon.Class == "Rifle") then
 
return "Rifle"
 
elseif(Weapon.Class == "Shotgun") then
 
return "Shotgun"
 
elseif(Weapon.Class == "Bow") then
 
return "Bow"
 
elseif(Weapon.Class == "Sniper Rifle" or Weapon.Class == "Launcher") then
 
return "Sniper"
 
end
 
end
 
end
 
else
 
return nil
 
end
 
elseif(ValName == "Augment") then
 
local augments = getAugments(Weapon)
 
if(Shared.tableCount(augments) > 0) then
 
local AugString = ""
 
for i, Aug in pairs(augments) do
 
if(i>1) then AugString = AugString.."<br/>" end
 
AugString = AugString.."[["..Aug.Name.."]]"
 
end
 
return AugString
 
else
 
return nil
 
end
 
elseif(ValName == "BlockAngle") then
 
if(Weapon.Class ~= nil) then
 
if(Weapon.Type == "Melee") then
 
if(Weapon.BlockAngle ~= nil) then
 
if(asString) then
 
return Weapon.BlockAngle.."&#176;"
 
else
 
return Weapon.BlockAngle
 
end
 
elseif(Weapon.Class == "Claws" or Weapon.Class == "Dagger" or Weapon.Class == "Dual Daggers" or Weapon.Class == "Glaive" or Weapon.Class == "Nunchaku" or Weapon.Class == "Rapier" or Weapon.Class == "Sparring" or Weapon.Class == "Tonfa" or Weapon.Class == "Whip") then
 
if(asString) then
 
return "35&#176;"
 
else
 
return 35
 
end
 
elseif(Weapon.Class == "Blade and Whip" or Weapon.Class == "Dual Swords" or Weapon.Class == "Fist" or Weapon.Class == "Gunblade" or Weapon.Class == "Staff" or Weapon.Class == "Sword") then
 
if(asString) then
 
return "60&#176;"
 
else
 
return.6
 
end
 
elseif(Weapon.Class == "Hammer" or Weapon.Class == "Heavy Blade" or Weapon.Class == "Machete" or Weapon.Class == "Nikana" or Weapon.Class == "Polearm" or Weapon.Class == "Scythe" or Weapon.Class == "Sword and Shield") then
 
if(asString) then
 
return "85&#176;"
 
else
 
return 85
 
end
 
end
 
 
else
 
return ""
 
end
 
else
 
return ""
 
end
 
elseif(ValName == "Class") then
 
if(Weapon.Class ~= nil) then
 
if(asString and Weapon.Type == "Melee") then
 
return "[[:Category:"..Weapon.Class.."|"..Weapon.Class.."]]"
 
else
 
return Weapon.Class
 
end
 
elseif giveDefault then
 
return ""
 
else
 
return nil
 
end
 
elseif(ValName == "ComboDur") then
 
if(Weapon.ComboDur ~= nil) then
 
if(asString) then
 
return Weapon.ComboDur.." s"
 
else
 
return Weapon.ComboDur
 
end
 
elseif giveDefault and (Weapon.Type ~= nil and Weapon.Type == "Melee") then
 
if(asString) then
 
return "2.0 s"
 
else
 
return 2.0
 
end
 
else
 
return nil
 
end
 
elseif(ValName == "Conclave") then
 
if(Weapon.Conclave ~= nil) then
 
if(asString) then
 
if(Weapon.Conclave) then return "Yes" else return "No" end
 
else
 
return Weapon.Conclave
 
end
 
elseif giveDefault then
 
return false
 
else
 
return nil
 
end
 
elseif(ValName == "Disposition") then
 
if(Weapon.Type ~= nil and Weapon.Type == "Arch-Melee" or Weapon.Type == "Emplacement") then return nil end
 
 
if(Weapon.Disposition ~= nil) then
 
if(asString) then
 
return Icon._Dis(Weapon.Disposition).."<div style=\"display:inline; position:relative; bottom:2px\">("..Weapon.Disposition..")</div>"
 
else
 
return Weapon.Disposition
 
end
 
elseif giveDefault then
 
if(asString) then
 
return "Unknown"
 
else
 
return 0
 
end
 
else
 
return nil
 
end
 
elseif(ValName == "Disposition5") then
 
if(Weapon.Type ~= nil and Weapon.Type == "Arch-Melee" or Weapon.Type == "Emplacement") then return nil end
 
 
if(Weapon.Disposition ~= nil) then
 
if (Weapon.Disposition < 0.7) then return 1
 
elseif(Weapon.Disposition < 0.9) then return 2
 
elseif(Weapon.Disposition <= 1.1) then return 3
 
elseif(Weapon.Disposition <= 1.3) then return 4
 
else return 5 end
 
elseif giveDefault then
 
if(asString) then
 
return "Unknown"
 
else
 
return 0
 
end
 
else
 
return nil
 
end
 
elseif(ValName == "ExilusPolarity") then
 
if(Weapon.ExilusPolarity ~= nil) then
 
if(asString) then
 
return Icon._Pol(Weapon.ExilusPolarity)
 
else
 
return Weapon.ExilusPolarity
 
end
 
elseif giveDefault then
 
return "None"
 
else
 
return nil
 
end
 
elseif(ValName == "Family") then
 
if(Weapon.Family ~= nil) then
 
if(asString) then
 
if(Weapon.Family ~= nil) then
 
local FamilyString = ""
 
local Family = getFamily(Weapon.Family)
 
for i, Weap in pairs(Family) do
 
if(Weap.Name ~= Weapon.Name) then
 
if(string.len(FamilyString) > 0) then FamilyString = FamilyString.."<br/>" end
 
FamilyString = FamilyString.."[["..Weap.Name.."]]"
 
end
 
end
 
return FamilyString
 
end
 
else
 
return Weapon.Family
 
end
 
elseif giveDefault then
 
return ""
 
else
 
return nil
 
end
 
elseif(ValName == "FollowThrough") then
 
if(Weapon.FollowThrough ~= nil) then
 
if(asString) then
 
return Shared.asPercent(Weapon.FollowThrough)
 
else
 
return Weapon.FollowThrough
 
end
 
else
 
return nil
 
end
 
elseif(ValName == "Image") then
 
if(Weapon.Image ~= nil) then
 
return Weapon.Image
 
elseif giveDefault then
 
return "Panel.png"
 
else
 
return nil
 
end
 
elseif(ValName == "Introduced") then
 
if(Weapon.Introduced ~= nil) then
 
-- assuming string value is a valid update version
 
return Version._getVersionLink(Weapon.Introduced)
 
elseif giveDefault then
 
return ""
 
else
 
return nil
 
end
 
elseif(ValName == "HeavyAttack") then
 
if(Weapon.HeavyAttack ~= nil) then
 
if(asString) then
 
if(Weapon.HeavyElement ~= nil) then
 
return Icon._Proc(Weapon.HeavyElement).." "..Shared.round(Weapon.HeavyAttack, 2, 1)
 
else
 
return Shared.round(Weapon.HeavyAttack, 2, 1)
 
end
 
else
 
return Weapon.HeavyAttack
 
end
 
elseif giveDefault then
 
return 0
 
else
 
return nil
 
end
 
elseif(ValName == "HeavyElement") then
 
if(Weapon.HeavyElement ~= nil) then
 
return Weapon.HeavyElement
 
elseif giveDefault then
 
return ""
 
else
 
return nil
 
end
 
elseif(ValName == "WindUp") then
 
if(Weapon.WindUp ~= nil) then
 
if(asString) then
 
return Weapon.WindUp.." s"
 
else
 
return Weapon.WindUp
 
end
 
else
 
return nil
 
end
 
elseif(ValName == "HeavySlamAttack") then
 
if(Weapon.HeavySlamAttack ~= nil) then
 
if(asString) then
 
-- heavy slam attack element is the same as normal slam attack element
 
if(Weapon.SlamElement ~= nil) then
 
return Icon._Proc(Weapon.SlamElement).." "..Shared.round(Weapon.HeavySlamAttack, 2, 1)
 
else
 
return Shared.round(Weapon.HeavySlamAttack, 2, 1)
 
end
 
else
 
return Weapon.HeavySlamAttack
 
end
 
elseif giveDefault then
 
return 0
 
else
 
return nil
 
end
 
elseif(ValName == "HeavySlamElement") then
 
if(Weapon.HeavySlamElement ~= nil) then
 
return Weapon.HeavySlamElement
 
elseif giveDefault then
 
return ""
 
else
 
return nil
 
end
 
elseif(ValName == "HeavyRadialDmg") then
 
if(Weapon.HeavyRadialDmg ~= nil) then
 
if(asString) then
 
-- heavy slam radial attack element is the same as normal slam radial attack element
 
if(Weapon.SlamRadialElement ~= nil) then
 
return Icon._Proc(Weapon.SlamRadialElement).." "..Shared.round(Weapon.HeavyRadialDmg, 2, 1)
 
else
 
return Shared.round(Weapon.HeavyRadialDmg, 2, 1)
 
end
 
else
 
return Weapon.HeavyRadialDmg
 
end
 
elseif giveDefault then
 
return 0
 
else
 
return nil
 
end
 
elseif(ValName == "HeavyRadialElement") then
 
if(Weapon.HeavyRadialElement ~= nil) then
 
return Weapon.HeavyRadialElement
 
elseif giveDefault then
 
return ""
 
else
 
return nil
 
end
 
elseif(ValName == "HeavySlamRadius") then
 
if(Weapon.HeavySlamRadius ~= nil) then
 
if(asString) then
 
return Shared.round(Weapon.HeavySlamRadius, 2, 1).." m"
 
else
 
return Weapon.HeavySlamRadius
 
end
 
elseif giveDefault then
 
return 0
 
else
 
return nil
 
end
 
elseif(ValName == "Magazine") then
 
if(Weapon.Magazine ~= nil) then
 
if(asString) then
 
if (forTable) then
 
return Weapon.Magazine
 
else
 
return Weapon.Magazine..p.doPlural(" round<s> per mag", Weapon.Magazine)
 
end
 
else
 
return Weapon.Magazine
 
end
 
elseif giveDefault then
 
if(asString) then
 
return "0 rounds per mag"
 
else
 
return 0
 
end
 
else
 
return nil
 
end
 
elseif(ValName == "Mastery") then
 
if(Weapon.Mastery ~= nil) then
 
return Weapon.Mastery
 
elseif giveDefault then
 
return 0
 
else
 
return nil
 
end
 
elseif(ValName == "MaxAmmo") then
 
local returnVal = 0
 
if(Weapon.MaxAmmo ~= nil) then
 
returnVal = Weapon.MaxAmmo
 
elseif giveDefault then
 
if(Weapon.Type ~= nil) then
 
if(Weapon.ReloadStyle == "Regenerate") then
 
returnVal = 0
 
elseif(Weapon.Type == "Secondary") then
 
returnVal = 210
 
elseif(Weapon.Type == "Primary") then
 
if(Weapon.Class == nil or Weapon.Class == "Rifle") then
 
returnVal = 540
 
elseif(Weapon.Class == "Shotgun") then
 
returnVal = 120
 
elseif(Weapon.Class == "Bow" or Weapon.Class == "Sniper Rifle") then
 
returnVal = 72
 
end
 
end
 
end
 
else
 
return nil
 
end
 
if(asString) then
 
if(returnVal > 0) then
 
return returnVal.." rounds"
 
else
 
return nil
 
end
 
else
 
if(returnVal > 0) then
 
return returnVal
 
else
 
return nil
 
end
 
end
 
elseif(ValName == "MeleeRange") then
 
if(Weapon.MeleeRange ~= nil) then
 
if(asString) then
 
return Weapon.MeleeRange.." m"
 
else
 
return Weapon.MeleeRange
 
end
 
else
 
return nil
 
end
 
elseif(ValName == "NoiseLevel") then
 
if(Weapon.NoiseLevel ~= nil) then
 
return Weapon.NoiseLevel
 
elseif giveDefault then
 
if(Weapon.Type ~= nil and Weapon.Type ~= "Melee" and Weapon.Type ~= "Arch-Melee") then
 
if(Weapon.Class ~= nil and (Weapon.Class == "Bow" or Weapon.Class == "Thrown")) then
 
return "Silent"
 
else
 
return "Alarming"
 
end
 
else
 
return nil
 
end
 
else
 
return nil
 
end
 
elseif(ValName == "Polarities") then
 
if(Weapon.Polarities ~= nil and type(Weapon.Polarities) == "table") then
 
if(asString) then
 
return p.GetPolarityString(Weapon)
 
else
 
return Weapon.Polarities
 
end
 
elseif giveDefault then
 
if(asString) then
 
return "None"
 
else
 
return {}
 
end
 
else
 
return nil
 
end
 
elseif(ValName == "Reload") then
 
if(Weapon.Reload ~= nil) then
 
if regenValue == true and Weapon.ReloadStyle == "Regenerate" then
 
return Shared.round(Weapon.Magazine / Weapon.Reload, 2, 1)
 
elseif(asString) then
 
if(Weapon.ReloadStyle ~= nil) then
 
if(Weapon.ReloadStyle == "ByRound" and Weapon.Magazine ~= nil) then
 
local result = Shared.round(Weapon.Reload / Weapon.Magazine, 2, 1).." sec per round"
 
result = result.." ("..Shared.round(Weapon.Reload, 2, 1).."s total)"
 
if(forTable) then result = Shared.round(Weapon.Reload, 2, 1).." s" end
 
return result
 
elseif(Weapon.ReloadStyle == "Regenerate") then
 
local result = Shared.round(Weapon.Reload, 2, 1).." rounds per sec"
 
if(Weapon.Magazine ~= nil) then
 
result=result.." ("..Shared.round(Weapon.Magazine / Weapon.Reload, 2, 1).."s total)"
 
if(forTable) then result=Shared.round(Weapon.Magazine / Weapon.Reload, 2, 1).." s" end
 
end
 
return result
 
end
 
end
 
return Shared.round(Weapon.Reload, 2, 1).." s"
 
else
 
return Weapon.Reload
 
end
 
elseif giveDefault then
 
return 0
 
else
 
return nil
 
end
 
elseif(ValName == "SlamAttack") then
 
if(Weapon.SlamAttack ~= nil) then
 
if(asString) then
 
if(Weapon.SlamElement ~= nil) then
 
return Icon._Proc(Weapon.SlamElement).." "..Shared.round(Weapon.SlamAttack, 2, 1)
 
else
 
return Shared.round(Weapon.SlamAttack, 2, 1)
 
end
 
else
 
return Weapon.SlamAttack
 
end
 
elseif giveDefault then
 
return 0
 
else
 
return nil
 
end
 
elseif(ValName == "SlamElement") then
 
if(Weapon.SlamElement ~= nil) then
 
return Weapon.SlamElement
 
elseif giveDefault then
 
return ""
 
else
 
return nil
 
end
 
elseif(ValName == "SlamRadialDmg") then
 
if(Weapon.SlamRadialDmg ~= nil) then
 
if(asString) then
 
if(Weapon.SlamRadialElement ~= nil) then
 
return Icon._Proc(Weapon.SlamRadialElement).." "..Shared.round(Weapon.SlamRadialDmg, 2, 1)
 
else
 
return Shared.round(Weapon.SlamRadialDmg, 2, 1)
 
end
 
else
 
return Weapon.SlamRadialDmg
 
end
 
elseif giveDefault then
 
return 0
 
else
 
return nil
 
end
 
elseif(ValName == "SlamRadialElement") then
 
if(Weapon.SlamRadialElement ~= nil) then
 
return Weapon.SlamRadialElement
 
elseif giveDefault then
 
return ""
 
else
 
return nil
 
end
 
elseif(ValName == "SlamRadialProc") then
 
if(Weapon.SlamRadialProc ~= nil) then
 
if(type(Weapon.SlamRadialProc) == "string") then
 
return Icon._Proc(Weapon.SlamRadialProc).." "..Weapon.SlamRadialProc
 
elseif(type(Weapon.SlamRadialProc) == "table") then
 
return Icon._Proc(Weapon.SlamRadialProc[1]).." "..Weapon.SlamRadialProc[1].."<br />"..
 
Icon._Proc(Weapon.SlamRadialProc[2]).." "..Weapon.SlamRadialProc[2]
 
end
 
elseif giveDefault then
 
return ""
 
else
 
return nil
 
end
 
elseif(ValName == "SlamRadius") then
 
if(Weapon.SlamRadius ~= nil) then
 
if(asString) then
 
return Shared.round(Weapon.SlamRadius, 2, 1).." m"
 
else
 
return Weapon.SlamRadius
 
end
 
elseif giveDefault then
 
return 0
 
else
 
return nil
 
end
 
elseif(ValName == "SlideAttack") then
 
if(Weapon.SlideAttack ~= nil) then
 
if(asString) then
 
if(Weapon.SlideElement ~= nil) then
 
return Icon._Proc(Weapon.SlideElement).." "..Shared.round(Weapon.SlideAttack, 2, 1)
 
else
 
return Shared.round(Weapon.SlideAttack, 2, 1)
 
end
 
else
 
return Weapon.SlideAttack
 
end
 
elseif giveDefault then
 
return 0
 
else
 
return nil
 
end
 
elseif(ValName == "SlideElement") then
 
if(Weapon.SlideElement ~= nil) then
 
return Weapon.SlideElement
 
elseif giveDefault then
 
return ""
 
else
 
return nil
 
end
 
elseif(ValName == "SniperComboReset") then
 
if(Weapon.SniperComboReset ~= nil) then
 
if(asString) then
 
return Shared.round(Weapon.SniperComboReset, 2, 1).." s"
 
else
 
return Weapon.SniperComboReset
 
end
 
elseif giveDefault then
 
return 0
 
else
 
return nil
 
end
 
elseif(ValName == "SniperComboMin") then
 
if(Weapon.SniperComboMin ~= nil) then
 
if(asString) then
 
return Weapon.SniperComboMin.." shots"
 
else
 
return Weapon.SniperComboMin
 
end
 
elseif giveDefault then
 
return 0
 
else
 
return nil
 
end
 
elseif(ValName == "Stagger") then
 
if(Weapon.Stagger ~= nil) then
 
return Weapon.Stagger
 
elseif giveDefault then
 
return "No"
 
else
 
return nil
 
end
 
elseif(ValName == "StancePolarity") then
 
if(Weapon.StancePolarity ~= nil) then
 
if(asString) then
 
return Icon._Pol(Weapon.StancePolarity)
 
else
 
return Weapon.StancePolarity
 
end
 
elseif giveDefault then
 
return "None"
 
else
 
return nil
 
end
 
elseif(ValName == "SyndicateEffect") then
 
if(Weapon.SyndicateEffect ~= nil) then
 
if(asString) then
 
return "[["..Weapon.SyndicateEffect.."]]"
 
else
 
return Weapon.SyndicateEffect
 
end
 
else
 
return nil
 
end
 
elseif(ValName == "Traits") then
 
if(Weapon.Traits ~= nil) then
 
return Weapon.Traits
 
elseif giveDefault then
 
return {}
 
else
 
return nil
 
end
 
elseif(ValName == "Trigger") then
 
-- Note there is a specific version for each attack in getAttackValue
 
if(Weapon.Trigger ~= nil) then
 
if (forTable) then -- return chargetime and burstcount only in tables
 
local trigger = Weapon.Trigger
 
if(trigger == "Charge") then
 
local cTime = getAttackValue(Weapon, getAttack(Weapon,"Charge"), "ChargeTime", false)
 
if(cTime ~= nil) then
 
return trigger.." ("..Shared.round(cTime, 2, 1).."s)"
 
else
 
return trigger
 
end
 
elseif(trigger == "Burst") then
 
local bCount = getAttackValue(Weapon, getAttack(Weapon,"Normal"), "BurstCount", false)
 
if(bCount ~= nil) then
 
return trigger.." ("..bCount..")"
 
else
 
return trigger
 
end
 
else
 
return trigger
 
end
 
else
 
return Weapon.Trigger
 
end
 
 
elseif giveDefault then
 
return "Unknown"
 
else
 
return nil
 
end
 
elseif(ValName == "CritChance") then -- Note there is a specific version for each attack in getAttackValue
 
-- search in charge attck, then normal, then secondary if still null
 
local returnVal=0
 
local Attack={}
 
if(hasAttack(Weapon, "Charge")) then
 
local chargAtt = getAttack(Weapon, "Charge")
 
if(chargAtt.CritChance ~= nil) then
 
returnVal=chargAtt.CritChance
 
Attack=chargAtt
 
end
 
end
 
if(hasAttack(Weapon, "Normal") and returnVal ==0) then
 
local normAtt = getAttack(Weapon, "Normal")
 
if(normAtt.CritChance ~= nil) then
 
returnVal=normAtt.CritChance
 
Attack=normAtt
 
end
 
end
 
if(hasAttack(Weapon, "Secondary") and returnVal ==0) then
 
local secAtt = getAttack(Weapon, "Secondary")
 
if(secAtt.CritChance ~= nil) then
 
returnVal=secAtt.CritChance
 
Attack=secAtt
 
end
 
end
 
if(asString and returnVal ~= 0) then
 
return Shared.asPercent(returnVal)
 
else
 
return returnVal
 
end
 
if giveDefault then
 
return 0
 
else
 
return nil
 
end
 
elseif(ValName == "CritMultiplier") then
 
-- Note there is a specific version for each attack in getAttackValue
 
-- search in charge attck, then normal, then secondary if still null
 
local returnVal=0
 
local Attack={}
 
if(hasAttack(Weapon, "Charge")) then
 
local chargAtt = getAttack(Weapon, "Charge")
 
if(chargAtt.CritMultiplier ~= nil) then
 
returnVal=chargAtt.CritMultiplier
 
Attack=chargAtt
 
end
 
end
 
if(hasAttack(Weapon, "Normal") and returnVal ==0) then
 
local normAtt = getAttack(Weapon, "Normal")
 
if(normAtt.CritMultiplier ~= nil) then
 
returnVal=normAtt.CritMultiplier
 
Attack=normAtt
 
end
 
end
 
if(hasAttack(Weapon, "Secondary") and returnVal ==0) then
 
local secAtt = getAttack(Weapon, "Secondary")
 
if(secAtt.CritMultiplier ~= nil) then
 
returnVal=secAtt.CritMultiplier
 
Attack=secAtt
 
end
 
end
 
if(asString and returnVal ~= 0) then
 
return asMultiplier(returnVal)
 
else
 
return returnVal
 
end
 
if giveDefault then
 
return 0
 
else
 
return nil
 
end
 
elseif(ValName == "FireRate") then
 
-- Note there is a specific version for each attack in getAttackValue
 
-- search in global weapon then normal attck, then charge, then secondary if still null
 
local returnVal=0
 
local Attack={}
 
if(Weapon.FireRate ~= nil) then
 
returnVal = Weapon.FireRate
 
end
 
if(hasAttack(Weapon, "Normal") and returnVal ==0) then
 
local normAtt = getAttack(Weapon, "Normal")
 
if(normAtt.FireRate ~= nil) then
 
returnVal=normAtt.FireRate
 
Attack=normAtt
 
end
 
end
 
if(hasAttack(Weapon, "Charge") and returnVal ==0) then
 
local chargAtt = getAttack(Weapon, "Charge")
 
if(chargAtt.FireRate ~= nil) then
 
returnVal=chargAtt.FireRate
 
Attack=chargAtt
 
end
 
end
 
if(hasAttack(Weapon, "Secondary") and returnVal ==0) then
 
local secAtt = getAttack(Weapon, "Secondary")
 
if(secAtt.FireRate ~= nil) then
 
returnVal=secAtt.FireRate
 
Attack=secAtt
 
end
 
end
 
if(asString and returnVal ~= 0) then
 
if(Weapon.Type ~= nil and Weapon.Type ~= "Melee" and Weapon.Type ~= "Arch-Melee") then
 
if(forTable) then
 
return Shared.round(returnVal, {3, 1}) -- .." rps"
 
else
 
return Shared.round(returnVal, {3, 1})..p.doPlural(" round<s> per sec", returnVal)
 
end
 
else
 
return Shared.round(returnVal, {3, 1})
 
end
 
else
 
return returnVal
 
end
 
if giveDefault then
 
return 0
 
else
 
return nil
 
end
 
elseif(ValName == "StatusChance") then -- Note there is a specific version for each attack in getAttackValue
 
-- search in charge attck, then normal, then secondary if still null
 
local returnVal=0
 
local Attack={}
 
if(hasAttack(Weapon, "Charge")) then
 
local chargAtt = getAttack(Weapon, "Charge")
 
if(chargAtt.StatusChance ~= nil) then
 
returnVal=chargAtt.StatusChance
 
Attack=chargAtt
 
end
 
end
 
if(hasAttack(Weapon, "Normal") and returnVal ==0) then
 
local normAtt = getAttack(Weapon, "Normal")
 
if(normAtt.StatusChance ~= nil) then
 
returnVal=normAtt.StatusChance
 
Attack=normAtt
 
end
 
end
 
if(hasAttack(Weapon, "Secondary") and returnVal ==0) then
 
local secAtt = getAttack(Weapon, "Secondary")
 
if(secAtt.StatusChance ~= nil) then
 
returnVal=secAtt.StatusChance
 
Attack=secAtt
 
end
 
end
 
if(asString and returnVal ~= 0) then
 
return Shared.asPercent(returnVal)
 
else
 
return returnVal
 
end
 
if giveDefault then
 
return 0
 
else
 
return nil
 
end
 
elseif(ValName == "Type") then
 
if(Weapon.Type ~= nil) then
 
return Weapon.Type
 
elseif giveDefault then
 
return ""
 
else
 
return nil
 
end
 
elseif(ValName == "Users") then
 
if(Weapon.Users ~= nil) then
 
if(asString) then
 
local result = ""
 
for i, str in pairs(Weapon.Users) do
 
if(i > 1) then result = result.."<br />" end
 
result = result..str
 
end
 
return result
 
else
 
return Weapon.Users
 
end
 
elseif giveDefault then
 
return {}
 
else
 
return nil
 
end
 
elseif(ValName == "Zoom") then
 
if(Weapon.Zoom ~= nil) then
 
if(asString) then
 
local result = ""
 
for i, str in pairs(Weapon.Zoom) do
 
if(i > 1) then result = result.."<br />" end
 
result = result..str
 
end
 
return result
 
else
 
return Weapon.Zoom
 
end
 
else
 
return nil
 
end
 
elseif(ValName == "SpecialFSpeed") then -- to show we can put very special keywords... if wanted
 
if(Weapon.Name == "Drakgoon") then
 
if(asString) then
 
return "bounce: 25 m/s"
 
else
 
return 25
 
end
 
elseif(getAttackValue(Weapon,getAttack(Weapon,"Area"),"ProjectileSpeed")~=nil) then
 
if(asString) then
 
return getAttackValue(Weapon,getAttack(Weapon,"Area"),"ProjectileSpeed",true,true)
 
else
 
return getAttackValue(Weapon,getAttack(Weapon,"Area"),"ProjectileSpeed")
 
end
 
elseif(getAttackValue(Weapon,getAttack(Weapon,"Secondary"),"ProjectileSpeed")~=nil) then
 
if(asString) then
 
return getAttackValue(Weapon,getAttack(Weapon,"Secondary"),"ProjectileSpeed",true,true)
 
else
 
return getAttackValue(Weapon,getAttack(Weapon,"Secondary"),"ProjectileSpeed")
 
end
 
elseif(giveDefault) then
 
if(asString) then
 
return "0 m/s"
 
else
 
return 0
 
end
 
else
 
return nil
 
end
 
 
else
 
--if everything failed (and it should NOT) try in the getAttackValue
 
return getAttackValue(Weapon, getAttack(Weapon, "Normal"), ValName, giveDefault, asString)
 
end
 
 
end
 
end
   
  +
--- Main frame invokable function to access any raw/computed attribute/column/key of a weapon entry.
  +
-- See default table in M:Weapons to see all valid computed attributes.
  +
-- @function p.getValue
  +
-- @param {string} weap Weapon name in EN locale
  +
-- @param {number} atk Attacks table index
  +
-- @param {string} k Key name
  +
-- @return Raw or computed value associated with k key
 
function p.getValue(frame)
 
function p.getValue(frame)
  +
-- table.unpack doesn't work on the frame object which is why this is anonymous function is needed
local WeapName = frame.args[1]
 
  +
local weap, key, atk = (function(t) return t[1], t[2], t[3] end)(frame.args)
local ValName1 = frame.args[2]
 
  +
weap = p._getWeapon(weap)
local ValName2 = frame.args[3]
 
  +
return p._getValue(weap, key, atk)
local AsString = frame.args["Raw"] == nil
 
if(WeapName == nil) then
 
return ""
 
elseif(ValName1 == nil) then
 
return "ERROR: No value selected"
 
end
 
 
local theWeap = p.getWeapon(WeapName)
 
if(theWeap == nil) then
 
return ""
 
end
 
 
local vName
 
local useDefault
 
if(ValName2 == nil or ValName2 == "") then
 
vName = ValName1
 
useDefault = Shared.contains(UseDefaultList, ValName1)
 
else
 
vName = {ValName1, ValName2}
 
useDefault = Shared.contains(UseDefaultList, ValName2)
 
end
 
 
return getValue(theWeap, vName, useDefault, AsString)
 
 
end
 
end
   
  +
--- Main frame invokable function to access any formatted attribute/column/key of a weapon entry.
function p.getConclaveValue(frame)
 
  +
-- See default table in M:Weapons to see all valid computed attributes.
local WeapName = frame.args[1]
 
  +
-- @function p.getFormattedValue
local ValName1 = frame.args[2]
 
  +
-- @param {string} weap Weapon name in EN locale
local ValName2 = frame.args[3]
 
  +
-- @param {number} atk Attacks table index
if(WeapName == nil) then
 
  +
-- @param {string} k Key name
return ""
 
  +
-- @return Formatted value associated with k key
elseif(ValName1 == nil) then
 
  +
function p.getFormattedValue(frame)
return "ERROR: No value selected"
 
  +
local weap, key, atk = (function(t) return t[1], t[2], t[3] end)(frame.args)
end
 
  +
weap = p._getWeapon(weap)
 
  +
return p._getFormattedValue(weap, key, atk)
local theWeap = p.getConclaveWeapon(WeapName)
 
if(theWeap == nil) then
 
return ""
 
end
 
 
local vName
 
local useDefault
 
if(ValName2 == nil or ValName2 == "") then
 
vName = ValName1
 
useDefault = Shared.contains(UseDefaultList, ValName1)
 
else
 
vName = {ValName1, ValName2}
 
useDefault = Shared.contains(UseDefaultList, ValName2)
 
end
 
 
return getValue(theWeap, vName, useDefault, true)
 
 
end
 
end
   
  +
--- Builds a melee weapon gallery as seen on [[Template:MeleeCategory]].
local function buildInfoboxRow(Label, Text, Collapse, Digits, Addon)
 
  +
-- @function p.getMeleeWeaponGallery
local result = ""
 
  +
-- @param {table} frame Frame object w/ first argumenting being string meleeClass
 
  +
-- @returns {string} Resultant wikitext of gallery
if(Collapse == nil) then
 
  +
function p.getMeleeWeaponGallery(frame)
Collapse = false
 
  +
local meleeClass = frame.args[1] or ''
elseif(Collapse == 1) then
 
  +
local result = { "=="..meleeClass.." Weapons==", '<gallery widths="200" position="center" spacing="small">' }
Collapse = true
 
  +
for i, weap in ipairs(p._getMeleeWeapons(meleeClass)) do
end
 
  +
table.insert(result, p._statRead(weap, nil, 'Image')..'|'..p._statFormat(weap, nil, 'Name'))
if(Text ~= nil) then
 
  +
end
result = "<div style=\"margin-left:-3px;\" "
 
  +
table.insert(result, '</gallery>')
if(Collapse) then
 
  +
return frame:preprocess(table.concat(result, '\n')) -- annoying that it needs to be preprocessed
result = result.."class=\"Infobox_Collapsible\""
 
end
 
result = result..">"
 
result = result.."\n{| style=\"width:100%;\""
 
result = result.."\n|-"
 
result = result.."\n| class=\"left\" | <span style=\"white-space:nowrap;\">"
 
result = result.."'''"..Label.."'''"
 
result = result.."</span>"
 
result = result.."\n| class=\"right\" | "
 
if(Digits == nil) then
 
result = result..Text
 
else
 
result = result..Shared.round(Text, Digits)
 
end
 
if(Addon ~= nil) then
 
result = result..p.doPlural(Addon, Text)
 
end
 
result = result.."\n|}"
 
result = result.."\n</div>"
 
end
 
return result
 
 
end
 
end
   
--Returns a list of weapons for a stance
+
--- Gets the total count of weapons as used on [[Mastery Rank#Total Mastery]].
  +
-- @function p.getWeaponCount
--Adds a ✓ if the weapon matches the polarity
 
  +
-- @param {table} frame Frame object w/ the first argument being the weaponSlot and the
function p.getStanceWeaponList(frame)
 
  +
-- second argument being a boolean to getFullList
local StanceName = frame.args ~= nil and frame.args[1] or frame
 
  +
-- @returns {number} Total count of weapons in a certain category/type
local Stance = p.getStance(StanceName)
 
  +
-- @returns {table} List of weapon names that count for mastery in a particular weapon slot
 
  +
function p._getWeaponCount(slot)
if(Stance == nil) then
 
  +
slot = slot and slot:lower()
return "ERROR: "..StanceName.." not found"
 
  +
local data = slot and WeaponData[slot] or WeaponData
end
 
  +
local fullList = {}
 
  +
local weaps = getMeleeWeapons(Stance.Class, Stance.PvP)
 
  +
for name, weapon in pairs(data) do
local result = ""
 
  +
if not weapon._IgnoreInMasteryCount then
 
  +
-- TODO: There should be a better way to determine/differentiate if a weapon is a kitgun b/c kitguns and zaws
for i, weap in Shared.skpairs(weaps) do
 
  +
-- are stored in the same M:Weapons/data/modular data store; add a new "Kitgun" or "Zaw" Trait and target that?
if (string.len(result) > 0) then
 
  +
if (slot == 'kitgun' and weapon.Slot == 'Secondary')
result = result.."\n"
 
  +
or (slot == 'zaw' and weapon.Slot == 'Melee')
end
 
  +
or (slot == 'robotic' and weapon.Slot ~= 'Hound')
 
  +
or (weapon.Slot:lower() == slot)
if(Stance.PvP) then
 
  +
or slot == nil then
result = result.."*[[Conclave:"..weap.Name.."|"..weap.Name.."]]"
 
  +
fullList[#fullList + 1] = name
else
 
  +
end
result = result.."*[["..weap.Name.."]]"
 
end
+
end
  +
end
 
if(weap.StancePolarity == Stance.Polarity) then
 
result = result.." ✓"
 
end
 
end
 
 
return result
 
end
 
   
  +
return #fullList, fullList
--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)
 
 
if(Weapon == nil) then
 
return "ERROR: "..WeaponName.." not found"
 
end
 
 
return getWeaponStanceList(Weapon)
 
end
 
 
function p.getStanceGallery(frame)
 
local StanceType = frame.args ~= nil and frame.args[1] or frame
 
local Stances = getStances(StanceType, true)
 
local result = "=="..StanceType.." [[Stance|Stance Mods]]=="
 
result = result.."\n{| style=\"margin:auto;text-align:center;\""
 
local nameRow = ""
 
for i, Stance in pairs(Stances) do
 
local theImage = Stance.Image ~= nil and Stance.Image or "Panel.png"
 
if((i - 1) % 4 == 0) then
 
result = result..nameRow.."\n|-"
 
nameRow = "\n|-"
 
end
 
result = result.."\n| style=\"width:165px\" |[[File:"..Stance.Image.."|150px|link="..Stance.Name.."]]"
 
nameRow = nameRow.."\n| style=\"vertical-align: text-top;\" |[["..Stance.Name.."]]"
 
if(Stance.PvP ~= nil and Stance.PvP) then
 
nameRow = nameRow.."<br/>([[Conclave]] only)"
 
end
 
end
 
result = result..nameRow
 
result = result.."\n|}"
 
return result
 
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
 
 
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
 
end
   
  +
--- Gets the total count of weapons as used on [[Mastery Rank#Total Mastery]].
  +
-- @function p.getWeaponCount
  +
-- @param {table} frame Frame object w/ the first argument being the weapon slot
  +
-- @return {number} Total number of weapons that can reward Mastery XP
 
function p.getWeaponCount(frame)
 
function p.getWeaponCount(frame)
local Type = frame.args ~= nil and frame.args[1] or frame
+
return (p._getWeaponCount(frame.args and frame.args[1] or nil))
local getFullList = frame.args ~= nil and frame.args[2]
 
if getFullList == "nil" then --bleh, this lovely parsing of invokes..
 
getFullList = nil
 
end
 
local getAll = frame.args ~= nil and frame.args[3]
 
local count = 0
 
local fullList = ""
 
for i, val in Shared.skpairs(WeaponData["Weapons"]) do
 
if(not Shared.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") 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") 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") then
 
count = count + 1
 
if(getFullList ~= nil) then fullList = fullList.."\n# "..val.Name end
 
end
 
else
 
if(val.Type == Type) then
 
count = count + 1
 
if(getFullList ~= nil) then fullList = fullList.."\n# "..val.Name end
 
end
 
end
 
end
 
end
 
if(getFullList ~= nil) then return fullList end
 
return count
 
 
end
 
end
   
  +
--- Builds wikitable of all weapons' innate polarities as seen on [[Polarity]].
local function getSecondaryCategory(weapon)
 
  +
-- @function p.getPolarityTable
local class = getValue(weapon, "Class", true)
 
  +
-- @param {table} frame Frame object
if(class == "Thrown") then
 
  +
-- @returns {string} Wikitext of resultant wikitable
return "Thrown"
 
  +
function p.getPolarityTable(frame)
elseif(class == "Dual Shotguns" or class == "Shotgun Sidearm") then
 
  +
local colNames = { 'Primary', 'Secondary', 'Melee', 'Archgun', 'Archmelee' }
return "Shotgun"
 
  +
local cols = {} -- Will look like: {['Primary']={},['Secondary']={},['Melee']={},['Archgun']={},['Archmelee']={},}
else
 
  +
local colOrder = {} --{cols['Primary'],cols['Secondary'],cols['Melee'],cols['Archgun'],cols['Archmelee'],}
local trigger = getValue(weapon, "Trigger", true)
 
  +
local colCounts = {}
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
 
   
  +
for i, v in ipairs(colNames) do
local function getPrimaryCategory(weapon)
 
  +
cols[v] = {}
local class = getValue(weapon, "Class", true)
 
  +
colOrder[i] = cols[v]
if(class == "Shotgun") then
 
  +
colCounts[v] = 0
return "Shotgun"
 
  +
end
elseif(class == "Bow") then
 
return "Bow"
 
elseif(class == "Sniper Rifle") then
 
return "Sniper"
 
elseif(class == "Rifle") then
 
local trigger = getValue(weapon, "Trigger", true)
 
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
 
   
  +
for _, weapon in pairs(WeaponData) do
function p.getPolarityTable()
 
  +
local pols = Table.size(weapon["Polarities"] or {})
local tHeader = ""
 
  +
local slot = weapon['Slot']
tHeader = tHeader.."\n{| style=\"width: 100%; border-collapse: collapse;\" cellpadding=\"2\" border=\"1\""
 
  +
if pols > 0 and cols[slot] then
tHeader = tHeader.."\n! colspan=\"2\" |Primary Weapons"
 
  +
table.insert(cols[slot], {
tHeader = tHeader.."\n! colspan=\"2\" |Secondary Weapons"
 
  +
'|'..p._getFormattedValue(weapon, 'NameLink'):gsub(' ?%(.*%)', '')..'||'..p._getFormattedValue(weapon, "Polarities"),
tHeader = tHeader.."\n! colspan=\"2\" |Melee Weapons"
 
  +
pols
local tRows = ""
 
  +
})
 
  +
colCounts[slot] = colCounts[slot] + 1
local Melees = p.getWeapons(function(x) return p.isMelee(x) and Shared.tableCount(getValue(x, "Polarities", true)) > 0 end)
 
  +
end
local Pistols = p.getWeapons(function(x) return x.Type == "Secondary" and Shared.tableCount(getValue(x, "Polarities", true)) > 0 end)
 
  +
end
local Primaries = p.getWeapons(function(x) return (x.Type == "Primary") and Shared.tableCount(getValue(x, "Polarities", true)) > 0 end)
 
 
local ACount = Shared.tableCount(Melees)
 
local maxLen = ACount
 
local BCount = Shared.tableCount(Pistols)
 
if(BCount > maxLen) then
 
maxLen = BCount
 
end
 
local CCount = Shared.tableCount(Primaries)
 
if(CCount > maxLen) then
 
maxLen = CCount
 
end
 
 
for i=1, maxLen, 1 do
 
tRows = tRows.."\n|-"
 
if(i <= CCount) then
 
tRows = tRows.."\n|[["..Primaries[i].Name.."]]||"..p.GetPolarityString(Primaries[i])
 
else
 
tRows = tRows.."\n| ||"
 
end
 
if(i <= BCount) then
 
tRows = tRows.."\n|[["..Pistols[i].Name.."]]||"..p.GetPolarityString(Pistols[i])
 
else
 
tRows = tRows.."\n| ||"
 
end
 
if(i <= ACount) then
 
tRows = tRows.."\n|[["..Melees[i].Name.."]]||"..p.GetPolarityString(Melees[i])
 
else
 
tRows = tRows.."\n| ||"
 
end
 
end
 
 
return tHeader..tRows.."\n|}"
 
end
 
   
  +
for i, v in ipairs(colNames) do
local function buildCompareString(Val1, Val2, ValName, Digits, Addon, Words, Start)
 
  +
colCounts[i] = colCounts[v]
if(Val1 == nil or Val2 == nil) then
 
  +
table.sort(cols[v], function(a, b)return a[2] > b[2] end)
return ""
 
end
+
end
local V1Str = Val1
 
local V2Str = Val2
 
if(Digits ~= nil) then
 
local didWork, rowStr = pcall(Shared.round, Val1, Digits)
 
if(didWork) then
 
V1Str = rowStr
 
local didWork, rowStr = pcall(Shared.round, Val2, Digits)
 
if(didWork) then
 
V2Str = rowStr
 
else
 
mw.log("Failed to round "..Val2)
 
end
 
else
 
mw.log("Failed to round "..Val1)
 
end
 
end
 
if(Addon ~= nil) then
 
V1Str = V1Str..p.doPlural(Addon, Val1)
 
V2Str = V2Str..p.doPlural(Addon, Val2)
 
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 result = {[=[
local function buildGunComparisonString(Weapon1, Weapon2, Conclave)
 
  +
{| style="width: 100%; border-collapse: collapse;" cellpadding="2" border="1"
local result = ""
 
  +
|+ '''Weapons with Innate Polarities (ignoring Stance and Exilus slots)'''
if(Conclave) then
 
  +
! colspan="2" |Primaries
result = "* "..Weapon1.Name..", compared to [[Conclave:"..Weapon2.Name.."|"..Weapon2.Name.."]]:"
 
  +
! colspan="2" |Secondaries
else
 
  +
! colspan="2" |Melees
result = "* "..Weapon1.Name..", compared to [["..Weapon2.Name.."]]:"
 
  +
! colspan="2" |Archguns
end
 
  +
! colspan="2" |Archmelees]=]}
 
  +
for i = 1, math.max(table.unpack(colCounts)) do --row
if(hasAttack(Weapon1, "Normal") and hasAttack(Weapon2, "Normal")) then
 
  +
table.insert(result, '|-')
local Att1 = getAttack(Weapon1, "Normal")
 
  +
for _, col in ipairs(colOrder) do --cell
local Att2 = getAttack(Weapon2, "Normal")
 
  +
table.insert(result,(col[i] or {'| ||'})[1])
local dmgString = ""
 
  +
end
dmgString = dmgString..buildCompareString(GetDamage(Att1), GetDamage(Att2), "base damage", {2, 1})
 
  +
end
dmgString = dmgString..buildCompareString(Att1.Damage["Impact"], Att2.Damage["Impact"], Icon._Proc("Impact", "text").." damage", {2, 1}, nil, {"Higher", "Lower"}, "\n***")
 
  +
table.insert(result, '|}')
dmgString = dmgString..buildCompareString(Att1.Damage["Puncture"], Att2.Damage["Puncture"], Icon._Proc("Puncture", "text").." damage", {2, 1}, nil, {"Higher", "Lower"}, "\n***")
 
  +
return table.concat(result, '\n')
dmgString = dmgString..buildCompareString(Att1.Damage["Slash"], Att2.Damage["Slash"], Icon._Proc("Slash", "text").." damage", {2, 1}, nil, {"Higher", "Lower"}, "\n***")
 
if(string.len(dmgString) > 0 and GetDamage(Att1) == GetDamage(Att2)) then
 
dmgString = "\n**Equal base damage, but different composition:"..dmgString
 
end
 
result = result..dmgString
 
end
 
if(hasAttack(Weapon1, "Charge")and hasAttack(Weapon2, "Charge")) then
 
local Att1 = getAttack(Weapon1, "Charge")
 
local Att2 = getAttack(Weapon2, "Charge")
 
-- local addedString = ""
 
-- if(dontHasAttack(Weapon1, "Normal")and dontHasAttack(Weapon2, "Normal")) then
 
-- addedString = "charged test"
 
-- end
 
if(Att1.CritChance ~= nil and Att2.CritChance ~= nil) then
 
if(dontHasAttack(Weapon1, "Normal")and dontHasAttack(Weapon2, "Normal")) then
 
result = result..buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "[[critical chance]]", 2, "%")
 
end
 
if(hasAttack(Weapon1, "Normal")and hasAttack(Weapon2, "Normal")) then
 
result = result..buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "charged [[critical chance]]", 2, "%")
 
end
 
end
 
if(dontHasAttack(Weapon1, "Normal")and dontHasAttack(Weapon2, "Normal")) then
 
result = result..buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "[[critical multiplier]]", 2, "x")
 
end
 
if(hasAttack(Weapon1, "Normal")and hasAttack(Weapon2, "Normal")) then
 
result = result..buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "charged [[critical multiplier]]", 2, "x")
 
end
 
if(Att1.StatusChance ~= nil and Att2.StatusChance ~= nil) then
 
if(dontHasAttack(Weapon1, "Normal")and dontHasAttack(Weapon2, "Normal")) then
 
result = result..buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance *
 
100), "[[status chance]]", 2, "%")
 
end
 
if(hasAttack(Weapon1, "Normal")and hasAttack(Weapon2, "Normal")) then
 
result = result..buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance *
 
100), "charged [[status chance]]", 2, "%")
 
end
 
end
 
if(dontHasAttack(Weapon1, "Normal")and dontHasAttack(Weapon2, "Normal")) then
 
result = result..buildCompareString(GetDamage(Weapon1.ChargeAttack), GetDamage(Weapon2.ChargeAttack), "attack damage", {2, 1})
 
end
 
if(hasAttack(Weapon1, "Normal")and hasAttack(Weapon2, "Normal")) then
 
result = result..buildCompareString(GetDamage(Weapon1.ChargeAttack), GetDamage(Weapon2.ChargeAttack), "charge attack damage", {2, 1})
 
end
 
result = result..buildCompareString(getValue(Weapon1, {"Charge", "ChargeTime"}), getValue(Weapon2, {"Charge", "ChargeTime"}), "charge time", {2, 1}, " s", {"Slower", "Faster"})
 
end
 
if(hasAttack(Weapon1, "Area") and hasAttack(Weapon2, "Area")) then
 
result = result..buildCompareString(GetDamage(Weapon1.AreaAttack), GetDamage(Weapon2.AreaAttack), "area attack damage", {2, 1})
 
end
 
if(hasAttack(Weapon1, "Secondary") and hasAttack(Weapon2, "Secondary")) then
 
result = result..buildCompareString(GetDamage(Weapon1.SecondaryAttack), GetDamage(Weapon2.SecondaryAttack), "secondary attack damage", {2, 1})
 
--test code to fix stradavar wrong comparison
 
local Att1 = getAttack(Weapon1, "Secondary")
 
local Att2 = getAttack(Weapon2, "Secondary")
 
if(Att1.CritChance ~= nil and Att2.CritChance ~= nil) then
 
result = result..buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "secondary mode [[critical chance]]", 2, "%")
 
end
 
result = result..buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "secondary mode [[critical multiplier]]", 2, "x")
 
 
if(Att1.StatusChance ~= nil and Att2.StatusChance ~= nil) then
 
result = result..buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance * 100), "secondary mode [[status chance]]", 2, "%")
 
end
 
result = result..buildCompareString(Att1.FireRate, Att2.FireRate, "secondary mode [[fire rate]]", 2, " 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, "Normal") and hasAttack(Weapon2, "Secondary") and dontHasAttack(Weapon1, "Secondary")) or (hasAttack(Weapon1, "Secondary") and hasAttack(Weapon2, "Normal") and dontHasAttack(Weapon2, "Secondary")))then
 
local Att1 = getAttack(Weapon1, "Normal")
 
local Att2 = getAttack(Weapon2, "Normal")
 
if(Att1.CritChance ~= nil and Att2.CritChance ~= nil) then
 
result = result..buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "[[critical chance]]", 2, "%")
 
end
 
result = result..buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "[[critical multiplier]]", 2, "x")
 
 
if(Att1.StatusChance ~= nil and Att2.StatusChance ~= nil) then
 
result = result..buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance * 100), "[[status chance]]", 2, "%")
 
end
 
end
 
end
 
 
 
--end of test code to fix tiberon prime comparison
 
 
if(hasAttack(Weapon1, "Normal") and hasAttack(Weapon2, "Normal") ) then
 
local Att1 = getAttack(Weapon1, "Normal")
 
local Att2 = getAttack(Weapon2, "Normal")
 
if(Att1.CritChance ~= nil and Att2.CritChance ~= nil) then
 
local ifCCAdded = 0
 
if(dontHasAttack(Weapon1, "Charge")and dontHasAttack(Weapon2, "Charge") and dontHasAttack(Weapon1, "Secondary")and dontHasAttack(Weapon2, "Secondary")) then
 
result = result..buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "[[critical chance]]", 2, "%")
 
ifCCAdded = 1
 
end
 
if(hasAttack(Weapon1, "Charge")and hasAttack(Weapon2, "Charge")) then
 
result = result..buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "base [[critical chance]]", 2, "%")
 
ifCCAdded = 1
 
end
 
if(hasAttack(Weapon1, "Secondary")and hasAttack(Weapon2, "Secondary")) then
 
result = result..buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "primary [[critical chance]]", 2, "%")
 
ifCCAdded = 1
 
end
 
if(hasAttack(Weapon1, "Normal")and hasAttack(Weapon2, "Normal")) and ifCCAdded == 0 then
 
result = result..buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "[[critical chance]]", 2, "%")
 
end
 
end
 
local ifCDAdded = 0
 
if(dontHasAttack(Weapon1, "Charge")and dontHasAttack(Weapon2, "Charge") and dontHasAttack(Weapon1, "Secondary")and dontHasAttack(Weapon2, "Secondary")) then
 
result = result..buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "[[critical multiplier]]", 2, "x")
 
ifCDAdded = 1
 
end
 
if(hasAttack(Weapon1, "Charge")and hasAttack(Weapon2, "Charge")) then
 
result = result..buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "base [[critical multiplier]]", 2, "x")
 
ifCDAdded = 1
 
end
 
if(hasAttack(Weapon1, "Secondary")and hasAttack(Weapon2, "Secondary")) then
 
result = result..buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "primary [[critical multiplier]]", 2, "x")
 
ifCDAdded = 1
 
end
 
if(hasAttack(Weapon1, "Normal")and hasAttack(Weapon2, "Normal")) and ifCDAdded == 0 then
 
result = result..buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "[[critical multiplier]]", 2, "x")
 
end
 
if(Att1.StatusChance ~= nil and Att2.StatusChance ~= nil) then
 
local ifStatusAdded = 0
 
if(dontHasAttack(Weapon1, "Charge")and dontHasAttack(Weapon2, "Charge") and dontHasAttack(Weapon1, "Secondary")and dontHasAttack(Weapon2, "Secondary")) then
 
result = result..buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance * 100), "[[status chance]]", 2, "%")
 
ifStatusAdded = 1
 
end
 
if(hasAttack(Weapon1, "Charge")and hasAttack(Weapon2, "Charge")) then
 
result = result..buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance * 100), "base [[status chance]]", 2, "%")
 
ifStatusAdded = 1
 
end
 
if(hasAttack(Weapon1, "Secondary")and hasAttack(Weapon2, "Secondary")) then
 
result = result..buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance * 100), "primary [[status chance]]", 2, "%")
 
ifStatusAdded = 1
 
end
 
if(hasAttack(Weapon1, "Normal")and hasAttack(Weapon2, "Normal")) and ifStatusAdded == 0 then
 
result = result..buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance * 100), "[[status chance]]", 2, "%")
 
end
 
end
 
result = result..buildCompareString(Att1.FireRate, Att2.FireRate, "[[fire rate]]", 2, " round<s>/sec")
 
 
--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", 2, "m", {"Longer", "Shorter"})
 
result = result..buildCompareString(Att1.Falloff.EndRange, Att2.Falloff.EndRange, "range before damage falloff ends", 2, "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", 2, "%", {"Larger", "Smaller"})
 
end
 
end
 
end
 
 
result = result..buildCompareString(Weapon1.Magazine, Weapon2.Magazine, "magazine", 0, " round<s>", {"Larger", "Smaller"})
 
result = result..buildCompareString(Weapon1.MaxAmmo, Weapon2.MaxAmmo, "max ammo capacity", 0, " 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 speed]]", 2, " round<s>/sec", {"Faster", "Slower"})
 
else
 
result = result..buildCompareString(Weapon1.Reload, Weapon2.Reload, "[[reload speed]]", 2, " s", {"Slower", "Faster"})
 
end
 
result = result..buildCompareString(Weapon1.Spool, Weapon2.Spool, "spool-up", 0, " 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, 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 = Shared.tableCount(Pol1)
 
local count2 = Shared.tableCount(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 ("..p.GetPolarityString(Weapon1).." vs. "..p.GetPolarityString(Weapon2)..")"
 
end
 
 
result = result..buildCompareString(Weapon1.Mastery, Weapon2.Mastery, "[[Mastery Rank]] required", 0)
 
result = result..buildCompareString(Weapon1.Disposition, Weapon2.Disposition, "[[disposition]]", 2)
 
return result
 
 
end
 
end
   
  +
--- Builds a table that lists out all weapons with a certain damage type
local function buildMeleeComparisonString(Weapon1, Weapon2, Conclave)
 
  +
-- @function p.buildDamageTypeTable
local result = ""
 
  +
-- @param {table} frame Frame object
if(Conclave) then
 
  +
-- @returns {string} Wikitext of resultant wikitable
result = "* "..Weapon1.Name..", compared to [[Conclave:"..Weapon2.Name.."|"..Weapon2.Name.."]]:"
 
  +
function p.buildDamageTypeTable(frame)
else
 
  +
local damageType = frame.args and frame.args[1] or frame
result = "* "..Weapon1.Name..", compared to [["..Weapon2.Name.."]]:"
 
  +
local mostly = frame.args and (frame.args[2] or '') ~= ''
end
 
 
local dmgString = ""
 
local Att1 = getAttack(Weapon1, "Normal")
 
local Att2 = getAttack(Weapon2, "Normal")
 
dmgString = dmgString..buildCompareString(GetDamage(Att1), GetDamage(Att2), "base damage", {2, 1})
 
dmgString = dmgString..buildCompareString(Att1.Damage["Impact"], Att2.Damage["Impact"], Icon._Proc("Impact", "text").." damage", {2, 1}, nil, {"Higher", "Lower"}, "\n***")
 
dmgString = dmgString..buildCompareString(Att1.Damage["Puncture"], Att2.Damage["Puncture"], Icon._Proc("Puncture", "text").." damage", {2, 1}, nil, {"Higher", "Lower"}, "\n***")
 
dmgString = dmgString..buildCompareString(Att1.Damage["Slash"], Att2.Damage["Slash"], Icon._Proc("Slash", "text").." damage", {2, 1}, nil, {"Higher", "Lower"}, "\n***")
 
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]]", 2, "%")
 
end
 
result = result..buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "[[critical multiplier]]", {2, 1}, "x")
 
 
if(Att1.StatusChance ~= nil and Att2.StatusChance ~= nil) then
 
result = result..buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance * 100), "[[status chance]]", 2, "%")
 
end
 
result = result..buildCompareString(Att1.FireRate, Att2.FireRate, "[[attack speed]]", 2)
 
result = result..buildCompareString(Icon._Pol(Weapon1.StancePolarity), Icon._Pol(Weapon2.StancePolarity), "Stance Polarity", nil, nil, {"Different", "Different"})
 
result = result..buildCompareString(getValue(Weapon1, "ComboDur", true), getValue(Weapon2, "ComboDur", true), "Combo Duration", 1, " s")
 
result = result..buildCompareString(getValue(Weapon1, "MeleeRange", true), getValue(Weapon2, "MeleeRange", true), "Range", 2, " m")
 
result = result..buildCompareString(getValue(Weapon1, "BlockAngle", true), getValue(Weapon2, "BlockAngle", true), "Block Angle", 1, "&#176;")
 
 
--Handling Polarities
 
local Pol1 = Weapon1.Polarities ~= nil and Weapon1.Polarities or {}
 
local Pol2 = Weapon2.Polarities ~= nil and Weapon2.Polarities or {}
 
local count1 = Shared.tableCount(Pol1)
 
local count2 = Shared.tableCount(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 ("..p.GetPolarityString(Weapon1).." vs. "..p.GetPolarityString(Weapon2)..")"
 
end
 
 
result = result..buildCompareString(Weapon1.Mastery, Weapon2.Mastery, "[[Mastery Rank]] required", 0)
 
result = result..buildCompareString(Weapon1.Disposition, Weapon2.Disposition, "[[disposition]]", 2)
 
return result
 
end
 
   
  +
local content = {}
function p.buildComparison(frame)
 
  +
for k,weap in pairs(WeaponData) do
local WeapName1 = frame.args[1]
 
  +
local weapAtk = getWeaponAttack(weap)--could add a loop here
local WeapName2 = frame.args[2]
 
  +
local portion, biastype, damage = statRead(weapAtk, 'DamageBias')
 
  +
local typeDmg = statRead(weapAtk, damageType)
if(WeapName1 == nil or WeapName2 == nil) then
 
  +
if damage == 0 then typeDmg = weapAtk[damageType] and 1 or 0 end--modular pieces
return "<span style=\"color:red\">ERROR: Must compare two weapons</span>"
 
  +
--Filter for
end
 
  +
--a. any of the damage type in any attack - former 'not mostly'
 
  +
--b. at least one majority damage type - former 'mostly'
local Weapon1 = p.getWeapon(WeapName1)
 
  +
--c. a majority of the damage type in the display attack - 'mostly'
if(Weapon1 == nil) then
 
  +
--d. any of the damage type in the display attack - 'not mostly'
return "<span style=\"color:red\">ERROR: Could not find "..WeapName1.."</span>"
 
  +
if biastype == damageType or not mostly and typeDmg > 0 then
end
 
  +
table.insert(content, ('| %s || %s || %s || %s || %s || data-sort-value="%s" | %s'):format(
local Weapon2 = p.getWeapon(WeapName2)
 
  +
statFormat(weapAtk, 'Name'),
if(Weapon2 == nil) then
 
  +
statRead(weapAtk, 'Slot'),
return "<span style=\"color:red\">ERROR: Could not find "..WeapName2.."</span>"
 
  +
statRead(weapAtk, 'Class'),
end
 
  +
statRead(weapAtk, 'AttackName'),
 
  +
typeDmg,
if(p.isMelee(Weapon1)) then
 
  +
portion, statFormat(weapAtk, 'DamageBias')
return buildMeleeComparisonString(Weapon1, Weapon2).."[[Category:Automatic Comparison]]"
 
  +
))
else
 
  +
end
return buildGunComparisonString(Weapon1, Weapon2).."[[Category:Automatic Comparison]]"
 
end
+
end
  +
table.sort(content)--will sort by tooltip span key
end
 
   
  +
return ([[
function p.buildConclaveComparison(frame)
 
  +
{| class = "listtable sortable" style="margin:auto;"
local WeapName1 = frame.args[1]
 
  +
|+ '''Weapons with %s%s damage'''
local WeapName2 = frame.args[2]
 
  +
|-
 
  +
! Name !! Slot !! Class !! Attack Name !! data-sort-type="number" | %s !! data-sort-type="number" | Majority
if(WeapName1 == nil or WeapName2 == nil) then
 
  +
|-
return "<span style=\"color:red\">ERROR: Must compare two weapons</span>"
 
  +
]]):format(mostly and 'mostly ' or '', damageType, Tooltip.full(damageType, 'DamageTypes'))
end
 
  +
..table.concat(content, '\n|-\n')..'\n|}'
 
local Weapon1 = p.getConclaveWeapon(WeapName1)
 
if(Weapon1 == nil) then
 
return "<span style=\"color:red\">ERROR: Could not find "..WeapName1.."</span>"
 
end
 
local Weapon2 = p.getConclaveWeapon(WeapName2)
 
if(Weapon2 == nil) then
 
return "<span style=\"color:red\">ERROR: Could not find "..WeapName2.."</span>"
 
end
 
 
if(p.isMelee(Weapon1)) then
 
return buildMeleeComparisonString(Weapon1, Weapon2, true).."[[Category:Automatic Comparison]]"
 
else
 
return buildGunComparisonString(Weapon1, Weapon2, true).."[[Category:Automatic Comparison]]"
 
end
 
 
end
 
end
   
  +
--- _isVariant adapter for p._shortLinkList
function p.buildFamilyComparison(frame)
 
  +
local function variantOf(weap)
local WeapName = frame.args ~= nil and frame.args[1] or frame
 
  +
local full, _, var, base = weap.Name, p._isVariant(weap.Name)
if(WeapName == nil) then
 
  +
return var, base, full
return "<span style=\"color:red\">ERROR: Must provide a Weapon name</span>"
 
end
 
 
local Weapon = p.getWeapon(WeapName)
 
if(Weapon == nil) then
 
return "<span style=\"color:red\">ERROR: Could not find "..WeapName.."</span>"
 
end
 
if(Weapon.Family == nil) then
 
return "<span style=\"color:red\">ERROR: "..WeapName.." doesn\"t have a family</span>"
 
end
 
 
local relatives = getFamily(Weapon.Family)
 
local result = {}
 
for i, NewWeapon in pairs(relatives) do
 
if(WeapName ~= NewWeapon.Name) then
 
if(p.isMelee(NewWeapon)) then
 
table.insert(result, buildMeleeComparisonString(Weapon, NewWeapon))
 
else
 
table.insert(result, buildGunComparisonString(Weapon, NewWeapon))
 
end
 
end
 
end
 
return table.concat(result, "\n")
 
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) then
 
return ""
 
elseif(Weapon.IgnoreCategories ~= nil and Weapon.IgnoreCategories) then
 
return ""
 
end
 
if(Weapon.Type ~= nil) then
 
if(Weapon.Type == "Arch-Melee") then
 
result = result.."[[Category:Archwing Melee]]"
 
elseif(Weapon.Type == "Arch-Gun") then
 
result = result.."[[Category:Archwing Gun]]"
 
elseif(Weapon.Type == "Arch-Gun (Atmosphere)") then
 
result = result
 
else
 
result = result.."[[Category:"..Weapon.Type.." Weapons]]"
 
end
 
end
 
if(Weapon.Class ~= nil) then
 
if(Weapon.Class == "Rifle") then
 
result = result.."[[Category:Assault Rifle]]"
 
else
 
result = result.."[[Category:"..Weapon.Class.."]]"
 
end
 
end
 
 
local augments = getAugments(Weapon)
 
if(Shared.tableCount(augments) > 0) then
 
result = result.."[[Category:Augmented Weapons]]"
 
end
 
 
if(HasTrait(Weapon, "Prime")) then
 
result = result.."[[Category:Prime]][[Category:Prime Weapons]]"
 
if(HasTrait(Weapon, "Never Vaulted")) then
 
result = result.."[[Category:Never Vaulted]]"
 
elseif(HasTrait(Weapon, "Vaulted")) then
 
result = result.."[[Category:Vaulted]]"
 
end
 
elseif(HasTrait(Weapon, "Wraith")) then
 
result = result.."[[Category:Wraith]]"
 
elseif(HasTrait(Weapon, "Vandal")) then
 
result = result.."[[Category:Vandal]]"
 
elseif(HasTrait(Weapon, "Kuva Lich")) then
 
result = result.."[[Category:Kuva Lich Weapons]]"
 
end
 
 
if(HasTrait(Weapon, "Grineer")) then
 
result = result.."[[Category:Grineer Weapons]]"
 
elseif(HasTrait(Weapon, "Corpus")) then
 
result = result.."[[Category:Corpus Weapons]]"
 
elseif(HasTrait(Weapon, "Infested")) then
 
result = result.."[[Category:Infested Weapons]]"
 
elseif(HasTrait(Weapon, "Tenno")) then
 
result = result.."[[Category:Tenno Weapons]]"
 
elseif(HasTrait(Weapon, "Sentient")) then
 
result = result.."[[Category:Sentient Weapons]]"
 
elseif(HasTrait(Weapon, "Entrati")) then
 
result = result.."[[Category:Entrati Weapons]]"
 
end
 
 
local attack = nil
 
if(hasAttack(Weapon, "Normal")) then
 
attack = getAttack(Weapon, "Normal")
 
elseif(hasAttack(Weapon, "Charge")) then
 
attack = getAttack(Weapon, "Charge")
 
end
 
 
if(attack ~= nil) then
 
local bestPercent, bestElement = GetDamageBias(attack)
 
if(bestElement == "Impact" or bestElement == "Puncture" or bestElement == "Slash") then
 
if(bestPercent > .38) then
 
result = result.."[[Category:"..bestElement.." Damage Weapons]]"
 
else
 
result = result.."[[Category:Balanced Physical Damage Weapons]]"
 
end
 
end
 
 
for key, value in Shared.skpairs(attack.Damage) do
 
if(key ~= "Impact" and key ~= "Puncture" and key ~= "Slash") then
 
result = result.."[[Category:"..key.." Damage Weapons]]"
 
end
 
end
 
end
 
 
if(hasAttack(Weapon, "Secondary")) then
 
result = result.."[[Category:Weapons with Alt Fire]]"
 
end
 
 
if(hasAttack(Weapon, "Area") or hasAttack(Weapon, "SecondaryArea")) then
 
result = result.."[[Category:Weapons with Area of Effect]][[Category:Self Interrupt Weapons]]"
 
end
 
 
return result
 
end
 
 
function p.buildAugmentTable(frame)
 
local result = ""
 
result = result.."\n{|class=\"listtable sortable\" style=\"width:100%;\""
 
result = result.."\n|-"
 
result = result.."\n! Name"
 
result = result.."\n! Category"
 
result = result.."\n! Source"
 
result = result.."\n! Weapon"
 
 
for i, Augment in pairs(WeaponData["Augments"]) do
 
local thisStr = "\n|-"
 
thisStr = thisStr.."\n| <span class=\"mod-tooltip\" data-param=\""..Augment.Name.."\" style=\"white-space:pre\">[["..Augment.Name.."]]</span>" --span class = displays the mod tooltip in the table on hover.
 
thisStr = thisStr.."\n| "..Augment.Category
 
thisStr = thisStr.."\n| [["..Augment.Source.."]]"
 
thisStr = thisStr.."\n| "
 
for i, weap in pairs(Augment.Weapons) do
 
if(i>1) then thisStr = thisStr.."<br/>" end
 
thisStr = thisStr.."[["..weap.."]]"
 
end
 
result = result..thisStr
 
end
 
 
result = result.."\n|}"
 
return result
 
end
 
 
local function noNil(val, default)
 
if(val ~= nil) then
 
return val
 
else
 
if(default ~= nil) then
 
return default
 
else
 
return ""
 
end
 
end
 
end
 
 
function p.buildDamageTypeTable(frame)
 
local DamageType = frame.args ~= nil and frame.args[1] or frame
 
local Weapons = {}
 
local WeapArray = p.getWeapons(function(x)
 
local Dmg, Element
 
if (hasAttack(x, "Normal")) then
 
Dmg, Element = GetDamageBias(getAttack(x, "Normal"), true)
 
elseif(hasAttack(x, "Charge")) then
 
Dmg, Element = GetDamageBias(getAttack(x, "Charge"), true)
 
else
 
return false
 
end
 
return Element == DamageType end)
 
 
local function weapLink(weap)
 
if weap.Link ~= nil then
 
return "[["..weap.Link.."|"..weap.Name.."]]"
 
else
 
return "[["..weap.Name.."]]"
 
end
 
end
 
 
local procString = Icon._Proc(DamageType,"text")
 
local procShort = Icon._Proc(DamageType)
 
local result = ""
 
local tHeader = "{|class = \"listtable sortable\" style=\"margin:auto;\""
 
tHeader = tHeader.."\n|-"
 
tHeader = tHeader.."\n!Name"
 
tHeader = tHeader.."\n!Type"
 
tHeader = tHeader.."\n!Class"
 
tHeader = tHeader.."\n!"..procString
 
tHeader = tHeader.."\n!"..procShort.."%"
 
local tRows = ""
 
for i, Weapon in pairs(WeapArray) do
 
local thisRow = "\n|-\n|"
 
local weaponLink = weapLink(Weapon)
 
thisRow = thisRow..weaponLink.."||"..Weapon.Type.."|| "..getValue(Weapon, "Class", true, true)
 
if(hasAttack(Weapon, "Normal")) then
 
local tempBias = getValue(Weapon, {"Normal", "DamageBias"}, true)
 
local tempBiasStripped = string.match(tempBias,"(%d*)%%")
 
thisRow = thisRow.."||"..getValue(Weapon, {"Normal", DamageType}).."||".."data-sort-value="..tempBiasStripped.."|"..tempBias
 
elseif(hasAttack(Weapon, "Charge")) then
 
local tempBias = getValue(Weapon, {"Charge", "DamageBias"}, true)
 
local tempBiasStripped = string.match(tempBias,"(%d*)%%")
 
thisRow = thisRow.."||"..getValue(Weapon, {"Charge", DamageType}).."||".."data-sort-value="..tempBiasStripped.."|"..tempBias
 
end
 
 
tRows = tRows..thisRow
 
end
 
result = tHeader..tRows.."\n|}"
 
return result
 
 
end
 
end
   
  +
--- Builds a list of weapons, with variants being next to base weapon name inside parentheses
function p.buildWeaponByMasteryRank(frame)
 
  +
-- (e.g. {{Weapon|Braton}} ({{Weapon|MK1-Braton|MK1}}, {{Weapon|Braton Prime|Prime}})).
local MasteryRank
 
  +
-- @function p._shortLinkList
local MRTable = {}
 
  +
-- @param {table} Weapon Weapon table
for i, Weapon in Shared.skpairs(WeaponData["Weapons"]) do
 
  +
-- @param {boolean} tooltip If true, adds weapon tooltips, false otherwise; defaults to false
if(Weapon.Mastery ~= nil) then
 
  +
-- @returns {string} Wikitext of resultant list
if(MRTable[Weapon.Mastery] == nil) then
 
  +
function p._shortLinkList(Weapons, tooltip)
MRTable[Weapon.Mastery] = {}
 
  +
return StatObject.shortLinkList(Weapons, variantOf, tooltip and 'Weapons')
end
 
if(MRTable[Weapon.Mastery][Weapon.Type] == nil) then
 
MRTable[Weapon.Mastery][Weapon.Type] = {}
 
end
 
table.insert(MRTable[Weapon.Mastery][Weapon.Type], Weapon)
 
end
 
end
 
local result = ""
 
for i, TypeTable in Shared.skpairs(MRTable) do
 
local thisTable = "\n==Mastery Rank "..i.." Required=="
 
if(i == 0) then
 
thisTable = "==No Mastery Rank Required=="
 
end
 
thisTable = thisTable.."\n{| style=\"width:80%;margin:auto\""
 
thisTable = thisTable.."\n|-\n| style=\"vertical-align:top;width:33%;\" |"
 
if(TypeTable["Primary"] ~= nil and Shared.tableCount(TypeTable["Primary"]) > 0) then
 
thisTable = thisTable.."\n===Primary==="
 
local tempList = p.shortLinkList(TypeTable["Primary"], false)
 
for i, text in pairs(tempList) do
 
thisTable = thisTable.."\n* "..text
 
end
 
end
 
 
thisTable = thisTable.."\n| style=\"vertical-align:top;width:33%;\" |"
 
if(TypeTable["Secondary"] ~= nil and Shared.tableCount(TypeTable["Secondary"]) > 0) then
 
thisTable = thisTable.."\n===Secondary==="
 
local tempList = p.shortLinkList(TypeTable["Secondary"], false)
 
for i, text in pairs(tempList) do
 
thisTable = thisTable.."\n* "..text
 
end
 
end
 
 
thisTable = thisTable.."\n| style=\"vertical-align:top;width:33%;\" |"
 
if(TypeTable["Melee"] ~= nil and Shared.tableCount(TypeTable["Melee"]) > 0) then
 
thisTable = thisTable.."\n===Melee==="
 
local tempList = p.shortLinkList(TypeTable["Melee"], false)
 
for i, text in pairs(tempList) do
 
thisTable = thisTable.."\n* "..text
 
end
 
end
 
thisTable = thisTable.."\n|}"
 
result = result..thisTable
 
end
 
return result
 
 
end
 
end
   
  +
--- Builds a list of weapons' mastery requirements as seen on [[Template:EquipmentUnlock]],
  +
-- [[Template:EquipmentUnlock/Primary]], [[Template:EquipmentUnlock/Secondary]],
  +
-- [[Template:EquipmentUnlock/Melee]], etc.
  +
-- @function p.getMasteryShortList
  +
-- @param {table} frame Frame object w/ first argument being a string weaponSlot
  +
-- @returns {string} Wikitext of resultant list
 
function p.getMasteryShortList(frame)
 
function p.getMasteryShortList(frame)
local WeaponType = frame.args[1]
+
local weaponSlot = frame.args[1]
local MasteryRank = tonumber(frame.args[2])
+
local masteryRank = tonumber(frame.args[2])
  +
local weapArray = p._getWeapons(function(x)
 
  +
return x.Slot == weaponSlot and x.Mastery == masteryRank
local weapArray = p.getWeapons(function(x)
 
  +
end)
if(x.Type ~= nil and x.Mastery ~= nil) then
 
  +
return table.concat(StatObject.shortLinkList(weapArray, variantOf, 'Weapons'), ' • ')
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 Shared.skpairs(shortList) do
 
if(string.len(result) > 0) then result = result.." • " end
 
result = result..pair
 
end
 
return result
 
 
end
 
end
   
function p.getRivenDispositionList(frame)
+
function p.fullList()
  +
return table.concat(StatObject.shortLinkList(WeaponData, variantOf, 'Weapons'), ' • ')
local WeaponType = frame.args[1]
 
local Disposition = tonumber(frame.args[2])
 
 
local weapArray = {}
 
if( WeaponType == "Zaw") then
 
 
weapArray = p.getZaw(function(x)
 
if(x.Disposition ~= nil) then
 
return x.Disposition == Disposition
 
else
 
return false
 
end
 
end)
 
--mw.log(weapArray)
 
--for k,v in pairs(weapArray) do mw.log(k,v) end
 
elseif( WeaponType == "Kitgun") then
 
 
weapArray = p.getKitgun(function(x)
 
if(x.Disposition ~= nil) then
 
return x.Disposition == Disposition
 
else
 
return false
 
end
 
end)
 
elseif( WeaponType == "KitgunPrimary" ) then
 
 
weapArray = p.getKitgunPrimary(function(x)
 
if(x.Disposition ~= nil) then
 
return x.Disposition == Disposition
 
else
 
return false
 
end
 
end)
 
else
 
weapArray = p.getWeapons(function(x)
 
if(x.Type ~= nil and x.Disposition ~= nil) then
 
return (WeaponType == "ALL" or x.Type == WeaponType) and x.Disposition == Disposition
 
else
 
return false
 
end
 
end)
 
end
 
 
local result = ""
 
local shortList = p.shortLinkList(weapArray, false)
 
for i, pair in Shared.skpairs(shortList) do
 
if (not string.find(pair, "(Atmosphere)")) then
 
result = result.."\n* "..pair.." ("..weapArray[i].Disposition..")"
 
end
 
end
 
return result
 
end
 
 
function p.getRivenDispositionTable(frame)
 
local WeaponType = frame.args[1]
 
 
local result = "{| class=\"article-table\" border=\"0\" cellpadding=\"1\" cellspacing=\"1\" style=\"width: 100%\""
 
result = result.."\n|-"
 
for i = 5, 1, -1 do
 
local j
 
if (i == 5) then j = 1.5
 
elseif (i == 4) then j = 1.3
 
elseif (i == 3) then j = 1.1
 
elseif (i == 2) then j = 0.89
 
else j = 0.69 end
 
result = result.."\n! scope=\"col\" style=\"text-align:center;\"|"..Icon._Dis(j)
 
end
 
result = result.."\n|-"
 
for i = 5, 1, -1 do
 
result = result.."\n| style=\"vertical-align:top; font-size:small\" |"
 
if (i == 5) then
 
for j = 1550, 1301, -1 do
 
result = result..p.getRivenDispositionList({args = {WeaponType, j/1000}}) end
 
elseif (i == 4) then
 
for j = 1300, 1101, -1 do
 
result = result..p.getRivenDispositionList({args = {WeaponType, j/1000}}) end
 
elseif (i == 3) then
 
for j = 1100, 900, -1 do
 
result = result..p.getRivenDispositionList({args = {WeaponType, j/1000}}) end
 
elseif (i == 2) then
 
for j = 899, 700, -1 do
 
result = result..p.getRivenDispositionList({args = {WeaponType, j/1000}}) end
 
else
 
for j = 699, 500, -1 do
 
result = result..p.getRivenDispositionList({args = {WeaponType, j/1000}}) end
 
end
 
end
 
result = result.."\n|}"
 
 
return result
 
 
end
 
end
   
  +
--- Builds a list of PvP weapons as seen on [[PvP#Limitations]].
  +
-- @function p.getConclaveList
  +
-- @param {table} frame Frame object w/ first argument being a string weaponSlot
  +
-- @returns {string} Wikitext of resultant list
 
function p.getConclaveList(frame)
 
function p.getConclaveList(frame)
local WeaponType = frame.args[1]
+
local weaponSlot = frame.args[1] or 'All'
  +
local weapArray = p._getWeapons(function(weap)
 
  +
return weap.Conclave == true
local weapArray = p.getWeapons(function(x)
 
  +
end, weaponSlot, {pvp=true})
if(x.Type ~= nil and x.Conclave ~= nil) then
 
  +
return '*'..table.concat(StatObject.shortLinkList(weapArray, variantOf), '\n* ')
return x.Type == WeaponType and x.Conclave
 
else
 
return false
 
end
 
end)
 
 
local result = ""
 
local shortList = p.shortLinkList(weapArray, false)
 
for i, pair in Shared.skpairs(shortList) do
 
result = result.."\n* "..pair
 
end
 
return result
 
 
end
 
end
   
  +
function p.getListWithWarframes(frame)
--Runs a bunch of things to quickly check if anything broke
 
  +
local date_str = ''
function p.checkForBugs(frame)
 
  +
local list = {'{| class="listtable sortable" style="overflow-y:scroll; max-height:500px"', '|-', '!data-sort-type="date"| Release !! Weapon !! Warframes'}
return p.buildComparison({args = {"Lato", "Lato Prime"}})..p.buildComparison({args = {"Glaive", "Glaive Prime"}})..p.getMeleeComparisonTable({})..p.getSecondaryComparisonTable({})
 
  +
local frames = {}
end
 
  +
  +
for _, warframe in pairs(WarframeData.Warframes) do
  +
if warframe.Introduced then
  +
date_str = Version._getVersionDate(warframe.Introduced)
  +
if frames[date_str] then
  +
table.insert(frames[date_str], '{{WF|' .. warframe.Name .. '}}')
  +
else
  +
frames[date_str] = {'{{WF|' .. warframe.Name .. '}}'}
  +
end
  +
end
  +
end
   
  +
for _, weapon in pairs(WeaponData) do
function p.checkElements(frame)
 
  +
date_str = Version._getVersionDate(weapon.Introduced)
local result = "wyrd"
 
for key, theWeap in Shared.skpairs(WeaponData["Weapons"]) do
 
for attName, Attack in p.attackLoop(theWeap) do
 
local elementCount = 0
 
if(Attack.Damage ~= nil) then
 
for element, dmg in Shared.skpairs(Attack.Damage) do
 
if(not Shared.contains(Physical, element)) then
 
elementCount = elementCount + 1
 
end
 
end
 
else
 
result = result.."\n"..key.." attempted to loop the "..attName.." attack"
 
end
 
if(elementCount > 1) then
 
result = result.."\n"..key.." has "..elementCount.." elements in its "..attName.." attack"
 
end
 
end
 
end
 
return result
 
end
 
   
  +
warframe = frames[date_str] or {'N/A'}
function p.checkForMissingData(frame)
 
  +
table.sort(warframe)
local result = ""
 
  +
warframe = table.concat(warframe, ', ')
for key, weapon in Shared.skpairs(WeaponData["Weapons"]) do
 
  +
if(weapon.Name == nil) then
 
  +
date_str = date_str:sub(6, 7) .. '.' .. date_str:sub(9, 10) .. '.' .. date_str:sub(0, 4)
result = result.."\n"..key.." does not have a Name"
 
end
 
if(weapon.Image == nil) then
 
result = result.."\n"..key.." does not have an Image"
 
end
 
if(weapon.Mastery == nil) then
 
result = result.."\n"..key.." does not have Mastery"
 
end
 
if(weapon.Disposition == nil and p.isArchwing(weapon) == nil) then
 
result = result.."\n"..key.." does not have Disposition"
 
end
 
if(weapon.Type == nil) then
 
result = result.."\n"..key.." does not have a Type"
 
end
 
if(weapon.Class == nil and p.isArchwing(weapon) == nil) then
 
result = result.."\n"..key.." does not have a Class"
 
end
 
if(weapon.NormalAttack ~= nil and weapon.NormalAttack.Damage == nil) then
 
result = result.."\n"..key.." does not do Normal Attacks"
 
end
 
end
 
if (result == "") then
 
result = "All weapons complete based on this test"
 
end
 
return result
 
end
 
   
  +
table.insert(list, '|-')
  +
table.insert(list, '|data-sort-value=' .. date_str .. '|{{ver|' .. weapon.Introduced .. '}} ||{{Weapon|' .. weapon.Name .. '}}||' .. warframe)
  +
end
  +
table.insert(list, '|}')
   
  +
return frame:preprocess(table.concat(list, '\n'))
function p.buildTunaWeaponTable(frame)
 
local Category = frame.args ~= nil and frame.args[1] or frame
 
local Weapons = p.getWeapons( function(x)
 
return getValue(x, "Type", true) == Category
 
end)
 
 
 
local result = "{| style=\"margin:auto;text-align:center;\""
 
local i = 0
 
for key, Weapon in Shared.skpairs(Weapons) do
 
i = i + 1
 
local theImage = Weapon.Image ~= nil and Weapon.Image or "Panel.png"
 
if((i - 1) % 7 == 0) then
 
result = result.."\n|-"
 
end
 
result = result.."\n| style=\"width:85px\" |[[File:"..Weapon.Name..".png|70px]]"
 
end
 
result = result.."\n|}"
 
return result
 
 
end
 
end
   
  +
--- Builds a disposition wikitable as seen on [[Riven Mods/Weapon Dispos]].
-- and we are back... new table building functions !
 
  +
-- @function p.getRivenDispositionTable
  +
-- @param {table} frame Frame object w/ first argument being a string weaponSlot
  +
-- @returns {string} Wikitext of resultant wikitable
  +
function p.getRivenDispositionTable(frame)
  +
local weaponSlot = frame.args[1]
  +
local result = {
  +
'{| class="article-table" border="0" cellpadding="1" cellspacing="1" style="width: 100%"',
  +
'|-',
  +
{'[[a| '}, -- Wikitable header row
  +
'|-'
  +
}
   
  +
-- local ranges = {'○○○○○', '●○○○○', '●●○○○', '●●●○○', '●●●●○', '●●●●●'}
local function BuildCompRow(Head, Weapon, UseCompDisplay)
 
  +
local dispo = {}
--User:Falterfire 6/12/18 - Adding new Comparison Display functionality
 
-- Toggled with a variable, which is false if not specified
 
if UseCompDisplay == nil then UseCompDisplay = false end
 
local styleString = ""--"border: 1px solid lightgray;"
 
local td = ""
 
local result = ""
 
local ValNameZ = nil
 
local ValName = nil
 
 
--User:Faltefire 6/12/18 - By default, use old version of code
 
if not UseCompDisplay or Weapon.ComparisonDisplay == nil then
 
local attName = ""
 
if(hasAttack(Weapon, "Charge")) then
 
attName = "Charge"
 
elseif(hasAttack(Weapon, "Normal")) then
 
attName = "Normal"
 
else
 
return ""
 
end
 
 
result = "\n|-\n|"
 
 
for i, Hline in ipairs(Head) do
 
ValName = Hline[1]
 
 
if(type(ValName) == "table" and ValName[1]=="default") then
 
ValName = {attName, ValName[2]}
 
elseif(type(ValName) == "table") then
 
ValName = {ValName[1], ValName[2]}
 
end
 
 
if(i == 1) then td = "" else td="||" end
 
if(getValue(Weapon, ValName)~=nil) then
 
--Add a data sort value if requested
 
if(Hline[2]) then
 
result = result..td.."data-sort-value=\""..getValue(Weapon, ValName, false, false, false, true).."\" style=\""..styleString.."\"|"..getValue(Weapon, ValName, true, true, true)
 
else
 
result = result..td.."style=\""..styleString.."\"|"..getValue(Weapon, ValName, true, true, true)
 
end
 
else
 
result = result..td.."style=\""..styleString.."\"|".."N/A"
 
end
 
end
 
else
 
--User:Faltefire 6/12/18 - Swap to new version if ComparisonDisplay is set for this weapon and UseCompDisplay is true
 
for i, row in pairs(Weapon.ComparisonDisplay) do
 
local attCount = Shared.tableCount(row.Attacks)
 
if attCount > 0 then
 
local rowText = "\n|-\n|"
 
local attName = row.Attacks[1]
 
for j, Hline in ipairs(Head) do
 
ValName = Hline[1]
 
local KeyName = ""
 
 
--If we're using the ComparisonDisplay, we're overriding the attempt to shunt to a different attack
 
--So always use attName + ValName, with one two exceptions: Damage and Name are overridden
 
if(type(ValName) == "table") then
 
ValName = {attName, ValName[2]}
 
KeyName = string.upper(ValName[2])
 
else
 
KeyName = string.upper(ValName)
 
end
 
 
if(j == 1) then td = "" else td="||" end
 
if KeyName == "NAME" then
 
local rowName = string.gsub(row.Name, "%[NAME%]", Weapon.Name)
 
--Replace the default name with the name from ComparisonDisplay
 
rowText = rowText..td.."style=\""..styleString.."\"|[["..Weapon.Name.."|"..rowName.."]]"
 
elseif KeyName == "DAMAGE" then
 
--For damage, go with the listed attack
 
if attCount == 1 then
 
rowText = rowText..td.."data-sort-value=\""..getValue(Weapon, {attName, "Damage"}).."\" style=\""..styleString.."\"|"..getValue(Weapon, {attName, "Damage"}, true, true, true)
 
else
 
--If multiple attacks are listed, show the combined damage
 
local attDmg = 0
 
for k, att in pairs(row.Attacks) do
 
mw.log(att)
 
attDmg = attDmg + getValue(Weapon, {att, "Damage"})
 
end
 
rowText = rowText..td.."data-sort-value=\""..attDmg.."\" style=\""..styleString.."\"|"..attDmg
 
end
 
else
 
if(getValue(Weapon, ValName) ~= nil) then
 
--Add a data sort value if requested
 
if(Hline[2]) then
 
rowText = rowText..td.."data-sort-value=\""..getValue(Weapon, ValName, false, false, false, true).."\" style=\""..styleString.."\"|"..getValue(Weapon, ValName, true, true, true)
 
else
 
rowText = rowText..td.."style=\""..styleString.."\"|"..getValue(Weapon, ValName, true, true, true)
 
end
 
else
 
rowText = rowText..td.."style = \""..styleString.."\"|".."N/A"
 
end
 
end
 
end
 
 
result = result..rowText
 
end
 
end
 
end
 
 
return result
 
end
 
   
  +
for k, weapon in pairs(WeaponData) do
local function BuildCompTable(Head, Weapons, UseCompDisplay)
 
  +
if weapon['Disposition'] and (weaponSlot == 'All' or weapon['Slot'] == weaponSlot) then
local styleString = "border: 1px solid black;border-collapse: collapse;"
 
  +
local disp = p._statFormat(weapon, nil, 'Dispo')
local tHeader = ""
 
  +
dispo[disp] = dispo[disp] or {}
tHeader = tHeader.."\n{| cellpadding=\"1\" cellspacing=\"0\" class=\"listtable sortable\" style=\"font-size:11px;\""
 
  +
table.insert(dispo[disp], weapon)
for i, Hline in ipairs(Head) do
 
if(Hline[2] == true) then
 
tHeader = tHeader.."\n! data-sort-type=\"number\" style=\""..styleString.."\"|"..Hline[3]..""
 
else
 
tHeader = tHeader.."\n! style=\""..styleString.."\"|"..Hline[3]..""
 
 
end
 
end
 
end
 
end
-- mw.log(tHeader)
 
   
  +
for str, dis in Table.skpairs(dispo) do
local tRows = ""
 
  +
table.sort(dis, function(a, b) return a['Disposition'] > b['Disposition'] end)
for i, Weap in pairs(Weapons) do
 
  +
local col = { '| style="vertical-align:top; font-size:small" |' }
local rowStr = BuildCompRow(Head, Weap, UseCompDisplay)
 
  +
for _, weap in ipairs(dis) do
tRows = tRows..rowStr
 
  +
table.insert(col, p._statFormat(weap, nil, 'NameLink')..' ('..weap['Disposition']..')')
end
 
  +
end
-- mw.log(tRows)
 
  +
table.insert(result[3], str)
return tHeader..tRows.."\n|}[[Category:Automatic Comparison Table]]"
 
  +
table.insert(result, table.concat(col, '\n* '))
end
 
  +
end
 
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", true) == "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", true) == "Secondary") then
 
if(Type ~= nil) then return getSecondaryCategory(x) == Type else return true end
 
end
 
return false
 
end)
 
elseif(Catt == "Robotic") then WeapArray = p.getWeapons(function(x)
 
return getValue(x, "Type", true) == "Robotic"
 
end)
 
else return "\n Error : Wrong Class (use Primary, Secondary, or Robotic) [[Category:Invalid Comp Table]]"
 
end
 
 
 
local Head={{"Name",false,"Name"}}
 
-- better if Name is always the first column !!!
 
table.insert(Head,{"Trigger",false,"[[Fire Rate|Trigger]]"})
 
table.insert(Head,{{"default","Damage"},true,"[[Damage|Dmg]]"})
 
table.insert(Head,{{"default", "CritChance"},true,"[[Critical Chance|Crit]]"})
 
table.insert(Head,{{"default", "CritMultiplier"},true,"[[Critical multiplier|Crit Dmg]]"})
 
table.insert(Head,{{"default", "StatusChance"},true,"[[Status Chance|Status]]"})
 
table.insert(Head,{{"default","BulletType"},false,"Projectile"})
 
table.insert(Head,{{"default", "FireRate"},true,"[[Fire Rate]]"})
 
table.insert(Head,{"Magazine",true,"[[Ammo#Magazine Capacity|Mag Size]]"})
 
table.insert(Head,{"Reload",true,"[[Reload Speed|Reload]]"})
 
table.insert(Head,{"Mastery",true,"[[Mastery Rank|MR]]"})
 
table.insert(Head,{"Disposition",true,"[[Riven Mods#Disposition|Dispo]]"})
 
table.insert(Head,{"Introduced",false,"Introduced"})
 
 
return BuildCompTable(Head, WeapArray, true)
 
end
 
 
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", true) == "Primary") and (getValue(x, "Conclave", true) == true)) 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", true) == "Secondary") and (getValue(x, "Conclave", true) == true)) then
 
if(Type ~= nil) then return getSecondaryCategory(x) == Type else return true end
 
end
 
return false
 
end)
 
else return "\n Error : Wrong Class (use Primary or Secondary) [[Category:Invalid Comp Table]]"
 
end
 
 
 
local Head={{"Name",false,"Name"}}
 
-- better if Name is always the first column !!!
 
table.insert(Head,{"Trigger",false,"[[Fire Rate|Trigger Type]]"})
 
table.insert(Head,{{"default","Damage"},true,"[[Damage]]"})
 
table.insert(Head,{"HeadshotMultiplier",true,"HS Multiplier"})
 
table.insert(Head,{{"default","BulletType"},false,"Projectile Type"})
 
table.insert(Head,{"FireRate",true,"[[Fire Rate]]"})
 
table.insert(Head,{"Magazine",true,"[[Ammo#Magazine Capacity|Magazine Size]]"})
 
table.insert(Head,{"Reload",true,"[[Reload Speed|Reload Time]]"})
 
table.insert(Head,{"Mastery",true,"[[Mastery Rank]]"})
 
table.insert(Head,{"Introduced",false,"Introduced"})
 
 
return BuildCompTable(Head,WeapArray)
 
end
 
 
function p.getCompTableArchGuns(frame)
 
local ArchType="Arch-Gun"
 
if (frame.args ~= nil) then
 
if (frame.args[1] == "Arch-Gun (Atmosphere)") then
 
ArchType="Arch-Gun (Atmosphere)"
 
end
 
end
 
 
local WeapArray = {}
 
WeapArray = p.getWeapons(function(x)
 
return getValue(x, "Type", true) == ArchType
 
end)
 
 
local Head={{"Name",false,"Name"}}
 
-- better if Name is always the first column !!!
 
table.insert(Head,{"Trigger",false,"[[Fire Rate|Trigger]]"})
 
table.insert(Head,{{"default","Damage"},true,"[[Damage|Dmg]]"})
 
table.insert(Head,{"CritChance",true,"[[Critical Chance|Crit]]"})
 
table.insert(Head,{"CritMultiplier",true,"[[Critical multiplier|Crit Dmg]]"})
 
table.insert(Head,{"StatusChance",true,"[[Status Chance|Status]]"})
 
table.insert(Head,{{"default","BulletType"},false,"Projectile"})
 
table.insert(Head,{"FireRate",true,"[[Fire Rate]]"})
 
table.insert(Head,{"Magazine",true,"[[Ammo#Magazine Capacity|Mag Size]]"})
 
table.insert(Head,{"Reload",true,"[[Reload Speed|Reload]]"})
 
table.insert(Head,{"Mastery",true,"[[Mastery Rank|MR]]"})
 
table.insert(Head,{"Disposition",true,"[[Riven Mods#Disposition|Dispo]]"})
 
table.insert(Head,{"Introduced",false,"Introduced"})
 
 
return BuildCompTable(Head,WeapArray)
 
end
 
 
 
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 addClass = Type == nil or Shared.contains(Type, ",")
 
 
local Head={{"Name",false,"Name"}}
 
-- better if Name is always the first column !!!
 
table.insert(Head,{"Class",false,"Type"})
 
table.insert(Head,{{"Normal","Damage"},true,"[[Damage|Normal]]"})
 
table.insert(Head,{"HeavyAttack",true,"[[Melee#Heavy Attack|Heavy]]"})
 
table.insert(Head,{"SlamAttack",true,"[[Melee#Slam Attack|Slam]]"})
 
table.insert(Head,{"SlideAttack",true,"[[Melee#Slide Attack|Slide]]"})
 
table.insert(Head,{"MeleeRange",true,"[[Melee#Range|Range]]"})
 
table.insert(Head,{"SlamRadius",true,"[[Melee#Slam Attack|Slam Radius]]"})
 
table.insert(Head,{{"default","FireRate"},true,"[[Attack Speed|Speed]]"})
 
table.insert(Head,{"CritChance",true,"[[Critical Chance|Crit]]"})
 
table.insert(Head,{"CritMultiplier",true,"[[Critical multiplier|Crit Dmg]]"})
 
table.insert(Head,{"StatusChance",true,"[[Status Chance|Status]]"})
 
table.insert(Head,{"Mastery",true,"[[Mastery Rank|MR]]"})
 
table.insert(Head,{"StancePolarity",false,"[[Stance]]"})
 
table.insert(Head,{"Disposition",true,"[[Riven Mods#Disposition|Dispo]]"})
 
table.insert(Head,{"Introduced",false,"Introduced"})
 
 
for k, v in pairs(Head[1]) do mw.log(k, v) end
 
 
return BuildCompTable(Head,WeapArray)
 
end
 
 
--As above, but for conclave
 
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 addClass = Type == nil or Shared.contains(Type, ",")
 
 
local Head={{"Name",false,"Name"}}
 
-- better if Name is always the first column !!!
 
table.insert(Head,{"Class",false,"Type"})
 
table.insert(Head,{{"Normal","Damage"},true,"[[Damage|Normal]]"})
 
table.insert(Head,{"SlideAttack",true,"[[Melee#Slide Attack|Slide]]"})
 
table.insert(Head,{{"default","FireRate"},true,"[[Attack Speed]]"})
 
table.insert(Head,{"Mastery",true,"[[Mastery_Rank|Mastery Rank]]"})
 
table.insert(Head,{"StancePolarity",false,"[[Stance]]"})
 
table.insert(Head,{"Introduced",false,"Introduced"})
 
 
for k, v in pairs(Head[1]) do mw.log(k, v) end
 
 
return BuildCompTable(Head,WeapArray)
 
end
 
 
function p.getCompTableArchMelees(frame)
 
local WeapArray = {}
 
WeapArray = p.getWeapons(function(x)
 
return getValue(x, "Type", true) == "Arch-Melee"
 
end)
 
 
local Head={{"Name",false,"Name"}}
 
-- better if Name is always the first column !!!
 
table.insert(Head,{{"Normal","Damage"},true,"[[Damage|Normal]]"})
 
table.insert(Head,{"SlideAttack",true,"[[Melee#Slide Attack|Slide]]"})
 
table.insert(Head,{{"default","FireRate"},true,"[[Attack Speed]]"})
 
table.insert(Head,{"CritChance",true,"[[Critical Chance]]"})
 
table.insert(Head,{"CritMultiplier",true,"[[Critical multiplier|Critical Damage]]"})
 
table.insert(Head,{"StatusChance",true,"[[Status Chance]]"})
 
table.insert(Head,{"Mastery",true,"[[Mastery Rank]]"})
 
table.insert(Head,{"Introduced",false,"Introduced"})
 
 
for k, v in pairs(Head[1]) do mw.log(k, v) end
 
 
return BuildCompTable(Head,WeapArray)
 
end
 
 
function p.getCompTableSpeedGuns(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", true) == "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", true) == "Secondary") then
 
if(Type ~= nil) then return getSecondaryCategory(x) == Type else return true end
 
end
 
return false
 
end)
 
elseif(Catt == "Robotic") then WeapArray = p.getWeapons(function(x)
 
return getValue(x, "Type", true) == "Robotic"
 
end)
 
elseif(Catt == "Arch-Gun") then WeapArray = p.getWeapons(function(x)
 
return getValue(x, "Type", true) == "Arch-Gun"
 
end)
 
else return "\n Error : Wrong Class (use Primary, Secondary, Robotic, or Arch-Gun) [[Category:Invalid Comp Table]]"
 
end
 
-- special sorting for projectile weapons
 
local WeapArray2 = {}
 
for k, Weapon in ipairs(WeapArray) do
 
local attName = ""
 
if(hasAttack(Weapon, "Charge")) then
 
attName = "Charge"
 
elseif(hasAttack(Weapon, "Normal")) then
 
attName = "Normal"
 
else
 
return ""
 
end
 
if(getValue(Weapon,{attName, "BulletType"}, false) == "Projectile") then
 
table.insert(WeapArray2, Weapon)
 
elseif(getValue(Weapon,{"Area", "BulletType"}, false) == "Projectile") then
 
table.insert(WeapArray2, Weapon)
 
elseif(getValue(Weapon,{"Secondary", "BulletType"}, false) == "Projectile") then
 
table.insert(WeapArray2, Weapon)
 
elseif (getValue(Weapon,{attName, "BulletType"}, false) == "Thrown") then
 
table.insert(WeapArray2, Weapon)
 
end
 
end
 
 
local Head={{"Name",false,"Name"}}
 
-- better if Name is always the first column !!!
 
table.insert(Head,{"Class",false,"Type"})
 
table.insert(Head,{{"Normal","ProjectileSpeed"},true,"Flight Speed"})
 
table.insert(Head,{{"Charge","ProjectileSpeed"},true,"Charge Flight Speed"})
 
table.insert(Head,{"SpecialFSpeed",true,"Alt-Fire / Special"})
 
 
return BuildCompTable(Head,WeapArray2)
 
end
 
 
function p.getCompTableSpeedMelees(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 == "Melee") then WeapArray = getMeleeWeapons(Type)
 
local addClass = Type == nil or Shared.contains(Type, ",")
 
elseif(Catt == "Arch-Melee") then WeapArray = p.getWeapons(function(x)
 
return getValue(x, "Type", true) == "Arch-Melee"
 
end)
 
else return "\n Error : Wrong Class (use Melee or Arch-Melee) [[Category:Invalid Comp Table]]"
 
end
 
-- special sorting for projectile weapons ONLY WORKS FOR GLAIVE TYPE MELEE WEAPONS !!!
 
local WeapArray2 = {}
 
for k, Weapon in ipairs(WeapArray) do
 
if (getValue(Weapon,{"Normal", "BulletType"}, false) == "Thrown") then
 
table.insert(WeapArray2, Weapon)
 
end
 
end
 
 
local Head={{"Name",false,"Name"}}
 
-- better if Name is always the first column !!!
 
table.insert(Head,{{"Normal","ProjectileSpeed"},true,"Flight Speed"})
 
table.insert(Head,{"SpecialFSpeed",true,"Special"})
 
 
return BuildCompTable(Head,WeapArray2)
 
end
 
 
local function fillAttack(weap, Attack)
 
--filling an attack's data from either Normal or Charge attacks
 
local attributeList={"Accuracy","AttackName","ChargeTime","Damage","CritChance","CritMultiplier","StatusChance","FireRate","ShotType","ShotSpeed"}
 
local filledAtt = {}
 
local normalAtt = getAttack(weap, "Normal")
 
local chargeAtt = getAttack(weap, "Charge")
 
local areaAtt = getAttack(weap, "Area")
 
local filler = nil
 
 
if normalAtt ~= nil then
 
filler = normalAtt
 
else
 
filler = chargeAtt
 
end
 
 
for n,attribute in pairs(attributeList) do
 
if Attack[attribute] ~= nil then --if the orig attack's stat exists, copy that to filledAtt
 
filledAtt[attribute] = Attack[attribute]
 
elseif filler[attribute] ~= nil then --else check if the filler attack has the stat, if so, copy to filled
 
filledAtt[attribute] = filler[attribute]
 
end
 
end
 
 
--filling the filled attack with area attack, if it exists, for any attributes that might need filling. Made especially for Staticor
 
if areaAtt ~= nil then
 
for nn,aattribute in pairs(attributeList) do
 
-- if the filled attack's stat doesn't exist but does in area attack, copy that to filledAtt
 
if filledAtt[aattribute] == nil and areaAtt[aattribute] ~= nil then
 
filledAtt[aattribute] = areaAtt[aattribute]
 
end
 
end
 
end
 
 
return filledAtt
 
end
 
 
local function getStrongestAttack(weap)
 
--for determining the strongest attack of a weapon based on the raw damage
 
--it expects the "weap" to be a table
 
if weap.Type == "Melee" then --if melee, return the normal attack
 
return weap.NormalAttack
 
else
 
local tempAtt = nil
 
local tempDmg = 0
 
 
for attName, Attack in p.attackLoop(weap) do --looping through attacks of this weapon
 
local dmgCount = 0
 
if Attack.Damage ~= nil then --if attack has damage -> Count the total damage -> if higher than earlier highest -> this one becomes the new highest
 
for dmgType, dmgNum in pairs(Attack.Damage) do --looping through the attack's damage types and tallying up the total damage
 
dmgCount = dmgCount + dmgNum
 
end
 
if (dmgCount > tempDmg) then
 
tempAtt = Attack
 
tempDmg = dmgCount
 
end
 
end
 
end
 
tempAtt = fillAttack(weap,tempAtt) --filling the attack as many of the stronger attacks lack many stats
 
 
return tempAtt
 
end
 
end
 
 
function p.test(WeapName)
 
local Weapon = p.getWeapon(WeapName)
 
 
return getStrongestAttack(Weapon)
 
end
 
 
function p.weaponTooltipText(frame)
 
local weapName = frame.args ~= nil and frame.args[1]
 
local newName = frame.args[2]
 
local conclave = frame.args["Conclave"] == "true" and true
 
local iconless = frame.args["Iconless"] == "true" and true
 
local conclaveParam = ""
 
local linkText = ""
 
local namespace = ""
 
 
if newName == "" then
 
newName = nil
 
end
 
 
local weap = checkWeapon(nil, weapName, conclave)
 
if weap == nil and conclave == true then
 
weap = checkWeapon(weap, weapName)
 
end
 
 
if weap ~= nil or weapName == "Dark Split-Sword" then
 
local link = p.getLink(weapName, weap)
 
 
if conclave == true then
 
conclaveParam = " data-param2=\"true\""
 
if weapName == "Dark Split-Sword" then
 
namespace = "Conclave:"
 
elseif weap.Conclave == true then
 
namespace = "Conclave:"
 
end
 
end
 
 
if link and newName ~= nil then
 
linkText = "[["..namespace..link.."|"..newName.."]]"
 
elseif newName ~= nil then
 
linkText = "[["..namespace..weapName.."|"..newName.."]]"
 
elseif link == weapName then
 
linkText = "[["..namespace..link.."|"..link.."]]"
 
elseif link then
 
linkText = "[["..namespace..link.."|"..weapName.."]]"
 
else
 
linkText = "[["..namespace..weapName.."|"..weapName.."]]"
 
end
 
 
if link ~= false and iconless ~= true then
 
local image = ""
 
if weapName == "Dark Split-Sword" then
 
local tempWeap = p.getWeapon("Dark Split-Sword (Dual Swords)")
 
image = getValue(tempWeap,"Image",true)
 
else
 
image = getValue(weap,"Image",true)
 
end
 
return "<span class=\"weapon-tooltip\" data-param=\""..weapName.."\""..conclaveParam.." style=\"white-space:pre\">".."[[File:"..image.."|x19px|link="..namespace..link.."]] "..linkText.."</span>"
 
elseif link ~= false then
 
return "<span class=\"weapon-tooltip\" data-param=\""..weapName.."\""..conclaveParam.." style=\"white-space:pre\">"..linkText.."</span>"
 
else
 
return "<span style=\"color:red;\">{{[[Template:Weapon|Weapon]]}}</span> \""..weapName.."\" not found[[Category:Weapon ".."Tooltip error]]"
 
end
 
else
 
return "<span style=\"color:red;\">{{[[Template:Weapon|Weapon]]}}</span> \""..weapName.."\" not found[[Category:Weapon ".."Tooltip error]]"
 
end
 
end
 
 
function p.weaponTooltip(frame)
 
local weapName = frame.args ~= nil and frame.args[1]
 
local conclave = frame.args[2] == "true" and true
 
--there's no "Dark Split-Sword" in m:weapons/data -> setting name to dual sword variant
 
if weapName == "Dark Split-Sword" then
 
weapName = "Dark Split-Sword (Dual Swords)"
 
end
 
 
local A2Name = nil
 
local A2Value = nil
 
local C1Name = nil
 
local C1Value = nil
 
local C2Name = nil
 
local C2Value = nil
 
local D1Name = nil
 
local D1Value = nil
 
local D1Value2 = nil
 
local titleText = ""
 
local hasCharged = false
 
local attackType = "Normal"
 
local attackText = ""
 
local space = "&nbsp;"
 
local attackBiasText = ""
 
 
if weapName == nil then
 
return nil
 
end
 
 
local Weapon = nil
 
local cAvailability = false
 
if conclave then
 
Weapon = p.getConclaveWeapon(weapName)
 
if Weapon ~= nil then
 
cAvailability = getValue(Weapon, "Conclave", true, false, false)
 
end
 
if not cAvailability then
 
Weapon = p.getWeapon(weapName)
 
end
 
else
 
Weapon = p.getWeapon(weapName)
 
end
 
 
local conclaveNotice = ""
 
if conclave and cAvailability == false then
 
conclaveNotice = "\n{| class=\"Data\" style=\"font-size:10px; white-space:normal;\"\n|-\n|Note: Not available in Conclave, displaying Cooperative stats and Cooperative Link.\n|}"
 
end
 
 
if Weapon == nil then
 
return "No weapon "..weapName.." found."
 
end
 
 
local function Value(valueName, asString, forTable, giveDefault)
 
--note that the three last parameters aren't in the same order in functions "Value" and "getValue"
 
if(asString == nil) then asString = false end
 
if(forTable == nil) then forTable = false end
 
if(giveDefault == nil) then giveDefault = true end
 
return getValue(Weapon, valueName, giveDefault, asString, forTable)
 
end
 
 
local function whitePols(valueName)
 
local pols = Value(valueName)
 
local polIcon = ""
 
 
if type(pols) == "table" then
 
local i = 0
 
if pols[1] == nil then
 
polIcon = "None"
 
return polIcon
 
else
 
while pols[i+1] ~= nil do
 
i = i + 1
 
end
 
for j = 1, i do
 
polIcon = polIcon..Icon._Pol(pols[j],"white","x16")
 
end
 
return polIcon
 
end
 
elseif pols ~= nil and type(pols) == "string" and pols ~= "None" then
 
return Icon._Pol(pols,"white","x16")
 
end
 
return "None"
 
end
 
 
--for weapons which have no max ammo
 
local ammoException = nil
 
if Value("MaxAmmo") == nil then
 
ammoException = true
 
end
 
 
local isMelee = p.isMelee(Weapon) == "yes"
 
 
local attack = getStrongestAttack(Weapon)
 
 
local count = 0
 
for type, dmg in Shared.skpairs(attack.Damage) do
 
if count == 0 then
 
attackText = "\n| style=\"padding-right:2px;\" |"..Icon._Proc(type,"","white","x16",nil,true)..space..dmg
 
count = count + 1
 
else
 
attackText = attackText.."|| style=\"padding-right:4px;\" |"..Icon._Proc(type,"","white","x16",nil,true)..space..dmg
 
end
 
end
 
 
if(hasMultipleTypes(attack)) then
 
attackBiasText = "\n| colspan=4 |"..GetDamageString(attack, nil).." ("..GetDamageBiasString(attack,nil,"",nil,"white","x16")..")"
 
end
 
 
local mRankIcon = ""
 
local mRank = Value("Mastery",false,false,false)
 
local mRankIconLoc = "top:4px; left:9.5px;"
 
if mRank then
 
if string.len(mRank) >= 2 then
 
mRankIconLoc = "top:4px; left:5px;"
 
end
 
mRankIcon = "<div style=\"position:absolute;top:6px; left:4px; color:white; font-size:16px; font-weight:bold; text-shadow: 0 0 1px #0D1B1C, 0 0 4px #0D1B1C, 1px 1px 2px #0D1B1C, -1px 1px 2px #0D1B1C, 1px -1px 2px #0D1B1C, -1px -1px 2px #0D1B1C;\">[[File:MasteryAffinity64.png|28px]]<div style=\"position:absolute;"..mRankIconLoc.."\">"..mRank.."</div></div>"
 
end
 
 
local dispoIcon = ""
 
local dispoVal = Value("Disposition5",false,false,false)
 
if dispoVal then
 
dispoIcon = "<div style=\"position:absolute;top:6px; right:4px; color:white; font-size:16px; font-weight:bold; text-shadow: 0 0 1px #0D1B1C, 0 0 4px #0D1B1C, 1px 1px 2px #0D1B1C, -1px 1px 2px #0D1B1C, 1px -1px 2px #0D1B1C, -1px -1px 2px #0D1B1C;\">[[File:RivenIcon64.png|28px]]<div style=\"position:absolute;top:3.5px; right:9.5px;\">"..dispoVal.."</div></div>"
 
end
 
 
if Weapon.ChargeAttack ~= nil then
 
if Weapon.ChargeAttack.AttackName == attack.AttackName then
 
hasCharged = true
 
end
 
end
 
 
if isMelee == true then
 
A2Name = "Type"
 
A2Value = "Class"
 
C1Name = "Atk. Speed"
 
C1Value = "FireRate"
 
C2Name = "Slide Atk."
 
C2Value = "SlideAttack"
 
D1Name = "Polarities"
 
D1Value = "Polarities"
 
D1Value2 = "StancePolarity"
 
else
 
A2Name = "Trigger"
 
A2Value = "Trigger"
 
if hasCharged == true then
 
C1Name = "Charge Time"
 
C1Value = "ChargeTime"
 
else
 
C1Name = "Fire Rate"
 
C1Value = "FireRate"
 
end
 
C2Name = "Noise"
 
C2Value = "NoiseLevel"
 
D1Name = "Polarities"
 
D1Value = "Polarities"
 
end
 
 
local function Link(linkName)
 
local spanStart = "<span class=\"LinkText\">"
 
local spanEnd = "</span>"
 
return spanStart..linkName..spanEnd
 
end
 
 
local zeroPadding = "\n| style=\"padding:0px;\" |"
 
local newRow = "\n|-"
 
local spacer = "\n| class=\"Spacer\" |"
 
local halfTable = "\n| class=\"TableHalf\" |"
 
local dataText = "\n{| class=\"Data\" style=\"font-size:12px;\""
 
local dataTextCenter = "\n{| class=\"Data\" style=\"font-size:12px; text-align:center;\""
 
local tableEnd = "\n|}"
 
 
local Type = ""
 
local tType = Value("Type")
 
if tType == "Arch-Gun (Atmosphere)" then
 
Type = "Arch-Gun (Atmo)"
 
else
 
Type = tType
 
end
 
 
local function atkStat(stat)
 
if attack[stat] ~= nil then
 
if stat == "FireRate" then
 
return getAttackValue(Weapon,attack,stat,nil,false)
 
else
 
return getAttackValue(Weapon,attack,stat,nil,true)
 
end
 
else
 
return Value(stat,true)
 
end
 
end
 
 
local image = "\n| class=\"Image\" style=\"height:120px;\" | <div style=\"position:relative; z-index:2;\">[[File:"..Value("Image").."|160px]]</div>"
 
 
--creating the table
 
local result = "<div style=\"position:relative;\">\n{| class=\"Sub\""..newRow..image..mRankIcon..dispoIcon..newRow..spacer..newRow..zeroPadding
 
result = result..dataText..newRow
 
result = result..halfTable..Link("Slot")..space..Type..halfTable..Link(A2Name)..space..Value(A2Value)..tableEnd
 
result = result..newRow..spacer..titleText..newRow..zeroPadding
 
result = result..dataTextCenter..newRow..attackText
 
if(attackBiasText ~= "") then
 
result = result..newRow..attackBiasText
 
end
 
result = result..tableEnd
 
result = result..newRow..spacer..newRow..zeroPadding..dataText..newRow..halfTable..Link("Crit")..space..atkStat("CritChance").." | "..atkStat("CritMultiplier")..halfTable..Link("Status")..space..atkStat("StatusChance")
 
result = result..newRow..halfTable..Link(C1Name)..space..atkStat(C1Value)..halfTable..Link(C2Name)..space..Value(C2Value)
 
--if not melee and not fishing spear => reload and ammo stats
 
if not isMelee and Weapon.Type ~= "Gear" then
 
result = result..newRow..halfTable..Link("Reload")..space..Value("Reload", true, true)..halfTable..Link("Ammo")..space..Value("Magazine")
 
--if has max ammo => add max ammo stat
 
if ammoException == nil then
 
result = result.."&thinsp;/&thinsp;"..Value("MaxAmmo")
 
end
 
result = result..newRow.."\n| style=\"text-align:center;\" colspan=2 |"..Link(D1Name)..space..whitePols(D1Value)
 
elseif isMelee then
 
result = result..newRow.."\n| style=\"text-align:center;\" colspan=2 |"..Link(D1Name)..space..whitePols(D1Value2).." | "..whitePols(D1Value)
 
end
 
result = result..tableEnd..conclaveNotice..tableEnd.."\n</div>"
 
 
return result
 
end
 
 
function p.getWeaponReplaceList(frame)
 
--local Type = frame.args ~= nil and frame.args[1] or frame
 
local fullList = {}
 
local primaries = {}
 
local secondaries = {}
 
local melees = {}
 
local archGuns = {}
 
local archMelees = {}
 
local theRest ={}
 
 
local avoidNames ={"Exalted Blade (Umbra)","Cortege (Atmosphere)","Corvas (Atmosphere)","Cyngas (Atmosphere)","Dargyn","Dual Decurion (Atmosphere)","Fluctus (Atmosphere)","Grattler (Atmosphere)","Imperator (Atmosphere)","Imperator Vandal (Atmosphere)","Mausolon (Atmosphere)","Phaedra (Atmosphere)","Rampart","Velocitus (Atmosphere)"}
 
 
local function addToList(name,link,list)
 
if link == nil then
 
link = name
 
end
 
--local 6s = " "
 
--local 8s = " "
 
local temp = " <Replacement>\n <Find>&#91;&#91;"..link.."]]</Find>\n <Replace>{{Weapon|"..name.."}}</Replace>\n <Comment />\n <IsRegex>false</IsRegex>\n <Enabled>true</Enabled>\n <Minor>false</Minor>\n <BeforeOrAfter>false</BeforeOrAfter>\n <RegularExpressionOptions>IgnoreCase</RegularExpressionOptions>\n </Replacement>"
 
return table.insert(list,temp)
 
end
 
 
for i, val in Shared.skpairs(WeaponData["Weapons"]) do
 
if not Shared.contains(avoidNames,val.Name) then
 
 
if val.Type == "Primary" then
 
addToList(val.Name,val.Link,primaries)
 
elseif val.Type == "Secondary" then
 
addToList(val.Name,val.Link,secondaries)
 
elseif val.Type == "Melee" then
 
addToList(val.Name,val.Link,melees)
 
elseif val.Type == "Arch-Gun" then
 
addToList(val.Name,val.Link,archGuns)
 
elseif val.Type == "Arch-Melee" then
 
addToList(val.Name,val.Link,archMelees)
 
else
 
addToList(val.Name,val.Link,theRest)
 
end
 
end
 
end
 
 
table.insert(fullList,table.concat(primaries,"\n"))
 
table.insert(fullList,table.concat(secondaries,"\n"))
 
table.insert(fullList,table.concat(melees,"\n"))
 
table.insert(fullList,table.concat(archGuns,"\n"))
 
table.insert(fullList,table.concat(archMelees,"\n"))
 
table.insert(fullList,table.concat(theRest,"\n"))
 
 
return table.concat(fullList, "\n")
 
end
 
   
  +
result[3] = table.concat(result[3], ']]\n! scope="col" style="text-align:center;"|[[Riven Mods#Disposition|')..']]'
function p.getWpnListAll(frame)
 
  +
table.insert(result, '|}')
--This is for page User:Flaicher/Sandbox/TWeapons/All. For rooting out script errors with the weapon tooltips.
 
  +
return table.concat(result, '\n')
--Only for usage with mw.log -> copy that to the pages.
 
local part = frame.args ~= nil and frame.args[1] --for splitting it into two to not hit "include size exceeded error"
 
local fullList = {}
 
local primaries = {}
 
local secondaries = {}
 
local melees = {}
 
local archGuns = {}
 
local archMelees = {}
 
local theRest ={}
 
 
local function addToList(name,link,list)
 
if link == nil then
 
link = name
 
end
 
local temp = "{{Tooltip/Weapon|"..link.."}}"
 
return table.insert(list,temp)
 
end
 
 
for i, val in Shared.skpairs(WeaponData["Weapons"]) do
 
if val.Type == "Primary" then
 
addToList(val.Name,val.Link,primaries)
 
elseif val.Type == "Secondary" then
 
addToList(val.Name,val.Link,secondaries)
 
elseif val.Type == "Melee" then
 
addToList(val.Name,val.Link,melees)
 
elseif val.Type == "Arch-Gun" then
 
addToList(val.Name,val.Link,archGuns)
 
elseif val.Type == "Arch-Melee" then
 
addToList(val.Name,val.Link,archMelees)
 
else
 
addToList(val.Name,val.Link,theRest)
 
end
 
end
 
 
if part == "1" then
 
table.insert(fullList,table.concat(primaries,"\n"))
 
table.insert(fullList,table.concat(secondaries,"\n"))
 
table.insert(fullList,table.concat(melees,"\n"))
 
return table.concat(fullList, "\n")
 
elseif part == "2" then
 
table.insert(fullList,table.concat(archGuns,"\n"))
 
table.insert(fullList,table.concat(archMelees,"\n"))
 
table.insert(fullList,table.concat(theRest,"\n"))
 
return table.concat(fullList, "\n")
 
end
 
 
end
 
end
   

Latest revision as of 23:33, 17 April 2024


Weapons contains all of WARFRAME's 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._isVariant(weaponName) (function)
Checks if a weapon is a variant or not.
Parameter: weaponName Weapon name (string)
Returns:
  • True if weapon is a variant, false otherwise (boolean)
  • Weapon's variant name or "Base" if weapon is not a variant (string)
  • Weapon name, same as weaponName (string)
weapons._buildName(baseName, variant) (function)
Builds the full name of a weapon's variant. Does not check if it exists or not.
Parameters:
  • baseName Weapon's base name (e.g. "Braton") (string)
  • variant Variant name (e.g. "Vandal"); if nil, returns base weapon name instead (string; optional)
Returns: Weapon's variant name (e.g. "Braton Vandal") (string)
weapons._getWeapon(weaponName, pvp) (function)
Returns a specific weapon table entry from /data or /Conclave/data.
Parameters:
  • weaponName Weapon name (string)
  • pvp If true, gets PvP stats of weapon instead, false otherwise; defaults to false (boolean; optional)
Returns: Weapon table (table)
weapons._getValue(Weapon, key, attack) (function)
Gets the raw value of a certain statistic of a weapon.
Parameters:
  • Weapon Weapon table (table)
  • key Name of key (string)
  • attack Name of attack to search through; defaults to 'Attack1' or what '_TooltipAttackDisplay' is set to (string; optional)
Returns: Value of statistic (string, number)
weapons._getFormattedValue(Weapon, keyName, attackName) (function)
Gets the formatted value of a certain statistic of a weapon to be displayed the wiki.
Parameters:
  • Weapon Weapon table (table)
  • keyName Name of key (string)
  • attackName Name of attack to search through; defaults to 'Attack1' (string; optional)
Returns: Value of statistic (string)
weapons._statReader(weap, atk) (function)
Function that returns a simpler getter function, for multiple _stat*() calls on the same weapon/attack pair.
Parameters:
  • weap Weapon entry (table)
  • atk Attacks table index or Attack entry (number|table)
Returns: Getter function (function)
weapons._statFormatter(weap, atk) (function)
Function that returns a simpler getter function, for multiple _stat*() calls on the same weapon/attack pair.
Parameters:
  • weap Weapon entry (table)
  • atk Attacks table index or Attack entry (number|table)
Returns: Getter function (function)
weapons._getWeapons(validateFunction, source, ignoreIgnore, sortFunc) (function)
Returns a subset of /data or /Conclave/data based on a validation function.
Parameters:
  • validateFunction Function that filters out a weapon by taking in a Weapon table argument (function)
  • source Name of weapon entry to use (string; optional)
  • ignoreIgnore If true, ignores the _IgnoreEntry flag, false otherwise; defaults to false (boolean; optional)
  • sortFunc Custom comparison function; false -> no sorting; defaults to sorting in ascending order by weapon name (function; optional)
Returns: Table of weapon table entries as seen in /data (table)
weapons._getMeleeWeapons(weapType, pvp) (function)
Returns all melee weapons. If weapType is not nil, only grab for a specific type For example, if weapType is "Nikana", only pull Nikanas.
Parameters:
  • weapType (boolean; optional)
  • pvp If true, only gets melee weapons available in Conclave, false otherwise; defaults to false (boolean; optional)
Returns: An array of melee weapon table entries as seen in /data (table)
weapons.getValue(weap, atk, k) (function)
Main frame invokable function to access any raw/computed attribute/column/key of a weapon entry. See default table in M:Weapons to see all valid computed attributes.
Parameters:
  • weap Weapon name in EN locale (string)
  • atk Attacks table index (number)
  • k Key name (string)
Returns: Raw or computed value associated with k key
weapons.getFormattedValue(weap, atk, k) (function)
Main frame invokable function to access any formatted attribute/column/key of a weapon entry. See default table in M:Weapons to see all valid computed attributes.
Parameters:
  • weap Weapon name in EN locale (string)
  • atk Attacks table index (number)
  • k Key name (string)
Returns: Formatted value associated with k key
weapons.getMeleeWeaponGallery(frame) (function)
Builds a melee weapon gallery as seen on Template:MeleeCategory.
Parameter: frame Frame object w/ first argumenting being string meleeClass (table)
Returns: Resultant wikitext of gallery (string)
weapons.getWeaponCount(frame) (function)
Gets the total count of weapons as used on Mastery Rank#Total Mastery.
Parameter: frame Frame object w/ the first argument being the weaponSlot and the second argument being a boolean to getFullList (table)
Returns:
  • Total count of weapons in a certain category/type (number)
  • List of weapon names that count for mastery in a particular weapon slot (table)
weapons.getWeaponCount(frame) (function)
Gets the total count of weapons as used on Mastery Rank#Total Mastery.
Parameter: frame Frame object w/ the first argument being the weapon slot (table)
Returns: Total number of weapons that can reward Mastery XP (number)
weapons.getPolarityTable(frame) (function)
Builds wikitable of all weapons' innate polarities as seen on Polarity.
Parameter: frame Frame object (table)
Returns: Wikitext of resultant wikitable (string)
weapons.buildDamageTypeTable(frame) (function)
Builds a table that lists out all weapons with a certain damage type
Parameter: frame Frame object (table)
Returns: Wikitext of resultant wikitable (string)
weapons._shortLinkList(Weapon, tooltip) (function)
Builds a list of weapons, with variants being next to base weapon name inside parentheses (e. g. Braton Braton (Braton MK1, BratonPrime Prime)).
Parameters:
  • Weapon Weapon table (table)
  • tooltip If true, adds weapon tooltips, false otherwise; defaults to false (boolean)
Returns: Wikitext of resultant list (string)
weapons.getMasteryShortList(frame) (function)
Builds a list of weapons' mastery requirements as seen on Template:EquipmentUnlock, Template:EquipmentUnlock/Primary, Template:EquipmentUnlock/Secondary, Template:EquipmentUnlock/Melee, etc.
Parameter: frame Frame object w/ first argument being a string weaponSlot (table)
Returns: Wikitext of resultant list (string)
weapons.getConclaveList(frame) (function)
Builds a list of PvP weapons as seen on PvP#Limitations.
Parameter: frame Frame object w/ first argument being a string weaponSlot (table)
Returns: Wikitext of resultant list (string)
weapons.getRivenDispositionTable(frame) (function)
Builds a disposition wikitable as seen on Riven Mods/Weapon Dispos.
Parameter: frame Frame object w/ first argument being a string weaponSlot (table)
Returns: Wikitext of resultant wikitable (string)

Created with Docbunto

See Also

Code


---	'''Weapons''' contains all of [[WARFRAME]]'s [[Weapons|weapon]] data.<br />
--	
--	@module			weapons
--	@alias			p
--	@attribution	[[User:Cephalon Scientia|Cephalon Scientia]]
--	@attribution	[[User:FINNER|FINNER]]
--	@attribution	[[User:Falterfire|Falterfire]]
--	@attribution	[[User:Gigamicro|Gigamicro]]
--	@attribution	[[User:Flaicher|Flaicher]]
--	@attribution	[[User:Synthtech|Synthtech]]
--	@image		IconPrimaryWeaponRifle.png
--	@require	[[Module:StatObject]]
--	@require	[[Module:DamageTypes]]
--	@require	[[Module:Polarity]]
--	@require	[[Module:Math]]
--	@require	[[Module:Table]]
--	@require	[[Module:Tooltips]]
--	@require	[[Module:Version]]
--	@require	[[Module:Stances/data]]
--	@require	[[Module:Weapons/data]]
--	@require	[[Module:Weapons/Conclave/data]]
--	@release	stable
--	<nowiki>

-- TODO: Add LuaDoc style comments to new functions
local p = {}

local Delay = require([[Module:Delay]])
local WeaponData = Delay.require([[Module:Weapons/data]])
local WarframeData = Delay.require([[Module:Warframes/data]]) -- for use in p.getListWithWarframes
local ConclaveData = Delay.require([[Module:Weapons/Conclave/data]])
local Tooltip = Delay.require([[Module:Tooltips]]) -- full, icon
local Version = Delay.require([[Module:Version]]) -- _getVersion, _getVersionDate
local Polarity = Delay.require([[Module:Polarity]]) -- _pols, _polarity
local Math = Delay.require([[Module:Math]]) -- formatnum
local Table = Delay.require([[Module:Table]]) -- size, skpairs
local iterationOrderArray = require([[Module:DamageTypes]]).iterationOrderArray

-- TODO: Should decouple from localized names for internationalization
local VARIANT_LIST = {
	"Prime", "Prisma", "Wraith", "Vandal", "Vaykor", "Synoid", "Telos", "Secura",
	"Sancti", "Rakta", "Mara", "Carmine", "Ceti", "Dex", "MK1", "Kuva", "Tenet"
}

table.unpack = table.unpack or unpack

local StatObject = require [[Module:StatObject]]
p.__StatObject = StatObject
local statRead = StatObject.statRead
local statFormat = StatObject.statFormat

local indexes = StatObject.meta.indexes
local ors = StatObject.meta.ors
local unpacks = StatObject.meta.unpacks

local passes = StatObject.meta.passes
local percent = StatObject.meta.percent
local percents = StatObject.meta.percents

---	Gets the attack entry from weapon entry.
--	@function		p._getAttack
--	@param			{table} weap Weapon entry
--	@param[opt]		{number|table} atk Attacks table index or Attack entry
--	@return			{table} A single weapon+attack struct
local function getWeaponAttack(weap, atk)
	if type(atk) == 'number' then return StatObject.getStruct2(weap,weap.Attacks[atk]) end
	if weap.AttackName then return weap end
	if type(atk) == 'table' then return StatObject.getStruct2(weap,atk) end
	local key = atk or weap['_TooltipAttackDisplay'] or 1
	if weap.Attacks == nil then
		error('p._getWeaponAttack(weap, atk): Attacks table is nil in '..mw.dumpObject(weap))
	end
	return StatObject.getStruct2(weap,weap.Attacks[key])
end
p._getAttack = getWeaponAttack
p._getWeaponAttack = getWeaponAttack

function p._statRead(w, a, ...)
	return statRead(getWeaponAttack(w, a), ...)
end
function p._statFormat(w, a, ...)
	return statFormat(getWeaponAttack(w, a), ...)
end
function p.stat(frame)
	return p._statFormat(p._getWeapon(frame.args[1] or 'Skana Prime'), nil, frame.args[2] or 'Name')
end

-- Wrapper function for use in StatObject
local function dmgTooltip(damageType)
	return Tooltip.full(damageType, 'DamageTypes')
end

-- Defining getters/attributes whose names match the associated database key or some custom derived attribute.
-- Index key will be name of getter function and can be mapped to a single value (getter definition) 
-- or a table with two values (getter and format function definitions)

-- Cheatsheet on adding new keys:
-- StatName = default value -> Get raw value with the same StatName from M:Weapons/data and with no additional formatting (aka default formatting)
-- StatName = function(self) return self.StatName + 1 end -> Define custom getter function and use default formatting
-- StatName = { default value, '%.2f' } -> Get raw value value with same StatName from M:Weapons/data and use format string for formatting
-- StatName = { function(self) return ... end, '%.2f' } -> Define custom getter function and use format string for formatting
-- StatName = { function(self) return ... end, function(self, returnValue1, returnValue2, ...) return tostring(returnValue) end } - > Define custom getter and format functions
-- (Note that format function will pass in return value(s) from getter as well as object self)

-- TODO: Put StatObject keys in alphabetical order for navigation
StatObject.default = {
AttackName = 'Normal Attack',
AmmoCost = nil,
AmmoPickup = function(weapAtk)
	return weapAtk['AmmoPickup'] or
		weapAtk['Slot'] == 'Primary' and 80 or 
		weapAtk['Slot'] == 'Secondary' and 40 or 
		weapAtk['Slot'] == 'Archgun (Atmosphere)' and 1000 or
		0
end,
DamageBias = {
	function(weapAtk)
		if not weapAtk.Damage then
			error('DamageBias: no Attack.Damage')
			return 0, 0, 0
		end
		local total, bestdmg, bestdt = 0, 0, nil
		for dt, dmg in pairs(weapAtk.Damage) do
			local dmg = dmg
			if dmg >= bestdmg then
				bestdmg, bestdt = dmg, dt
			end
			total = total + dmg
		end
		return StatObject.ucacheIn(weapAtk, 'DamageBias', { bestdmg / total, bestdt, total })
	end,
	{ percent, passes(dmgTooltip), '' }
},
BiasPortion = { indexes('DamageBias', 1), percent },
BiasType = { indexes('DamageBias', 2), function(self, biasType) return Tooltip.icon(biasType, 'DamageTypes') end },
BaseDamage = { indexes('DamageBias', 3), '%.2f' },
-- More precise damage values to 4 decimal places for PvP since PvP damage is calculated
-- based on a floating-point scalar. Damage quantization is more relevant in PvP so more
-- precise numbers needed.
PvPBaseDamage = { indexes('DamageBias', 3), '%.4f' },
TotalDamage = { function(weapAtk)
	return statRead(weapAtk, 'BaseDamage') * statRead(weapAtk, 'Multishot')
end, passes(Math.formatnum)
},
-- Including max +60% Progenitor bonus for Kuva/Tenet weapons
TotalDamageWithProgenBonus = { function(weapAtk)
	return statRead(weapAtk, 'TotalDamage') * (statRead(weapAtk, 'IsLichWeapon') and 1.6 or 1)
end, passes(Math.formatnum)
},
ChargeTime = { 0, '%.1f s' },
ExplosionDelay = { 0, '%.1f s' },
ExtraHeadshotDmg = { 0, percents('+%.2f%%') },
Falloff = {
	function(weapAtk)
		local fo = weapAtk['Falloff'] or {}
		return fo.StartRange or 0, fo.EndRange or math.huge, 1 - (fo.Reduction or 1)
	end,
	{ '%.1f m (100%%) -', '%.1f m', percents('(%.2f%%)') }
},
FalloffStart = { indexes('Falloff', 1), '%.1f m' },
FalloffEnd = { indexes('Falloff', 2), '%.1f m' },
-- Damage reduction from falloff instead of damage multiplier
FalloffReduction = { function(weapAtk)
	local _, _, falloff = statRead(weapAtk, 'Falloff')
	return 1 - falloff
end, percent
},
FalloffRate = { function(weapAtk)
	local startdist,enddist,endpercent = statRead(weapAtk, 'Falloff')
	return -(enddist-startdist)/(endpercent-1)
end, '%.1fm/%%'
},
HeadshotMultiplier = { 1, '%.1fx' },
Multishot = 1,
PunchThrough = { 0, '%.1f m' },
ShotSpeed = { nil, function(self, shotSpeed)
	if shotSpeed == nil then
		return 'N/A'
	end
	return ('%.1f m/s'):format(shotSpeed)
end
},
BurstDelay = { 0, '%.4f s' },
BurstReloadDelay = { 0, '%.2f s' },
BurstsPerSec = { function(weapAtk)
	-- There is no delay after last shot in burst
	return 1 / ( (1 / statRead(weapAtk, 'FireRate') ) + statRead(weapAtk, 'BurstDelay') * ( statRead(weapAtk, 'BurstCount') - 1) )
end, '%.2f bursts/sec' },
CritChance = { 0, percent },
CritMultiplier = { 1, '%.2fx' },
ForcedProcs = { unpacks('ForcedProcs'), function(s, ...)
	local procs = { ... }
	if procs[1] == nil then
		return 'No forced procs'
	end
	local result = {}
	for _, proc in ipairs(procs) do
		table.insert(result, Tooltip.full(proc, 'DamageTypes'))
	end
	return table.concat(result, ', ')
end
},
Radius = { 0, '%.1f m' },
StatusChance = { 0, percent },
Disposition = {
	function(weap)
		local d = weap['Disposition']
		-- Returning a categorical bin value of 1, 2, 3, 4, or 5 based on where disposition value
		-- is on the continuous scale of 0.5-1.55. If disposition value is nil then return 0
		return d or 0, type(d)=='number' and math.floor(5*(d-.3+.009*(d<1 and 1 or -1))) or 0
	end,
	function(s, v, d)
		return StatObject.default.Dispo[2](s, d)..(' (%.2fx)'):format(v)
	end
},
Dispo = { indexes('Disposition', 2), function(s, d)
	if d and d == d and d > 0 then
		return ('●'):rep(math.min(d, 5))..('○'):rep(5 - d)
	end
	return '×××××' -- '●○×' --> E2978F E2978B C397
end },
Introduced = { function(weap)
	return weap['Introduced'] and Version._getVersion(weap['Introduced'])['Name'] or 'N/A'
end, passes(Version._getVersionLink)
},
IntroducedDate = function(weap)
	return weap['Introduced'] and Version._getVersionDate(weap['Introduced']) or 'N/A'
end,
IsLichWeapon = function(weap)
	return weap['IsLichWeapon'] and true or false
end,
Mastery = 0,
Link = { nil, '[[%s]]' },
Name = { nil, function(s, v) return Tooltip.full(v, 'Weapons') end },
InternalName = '',
NameLink = { function(weap) return weap.Link, weap.Name end, '[[%s|%s]]' },
Polarities = { nil, passes(Polarity._pols) },
Traits = { unpacks('Traits'), { sep = ', ' } },
-- Default nil b/c some attacks don't have an associated accuracy/spread value (like AoE explosions)
Accuracy = { nil, function(self, value)
	if (value == nil) then
		return 'N/A'
	end
	return value
end
},
-- Inverse of accuracy. Spread of 1 equates to no spread.
-- Alternatively, it can be calculated by the average of min and max spread, see AvgSpread getter.
Spread = { function(weapAtk)
	local accuracy = statRead(weapAtk, 'Accuracy')
	return (accuracy == nil) and nil or 100 / accuracy
end, function(self, value)
	if (value == nil) then
		return 'N/A'
	end
	return value
end
},
AmmoType = function(weapAtk)
	return weapAtk['AmmoType'] or ({
		['Archgun (Atmosphere)'] = 'Heavy',
		['Secondary'] = 'Secondary',
		['Primary'] = 'Primary'
	})[weapAtk['Slot']] or 'None'
end,
-- Not all weapons have an Exilus slot so default to nil
ExilusPolarity = { nil, function(self, exilusPolarity)
	if (exilusPolarity == nil) then
		return 'N/A'
	end
	return Polarity._polarity(exilusPolarity)
end
},
Magazine = 1,
AmmoMax = { function(weapAtk)
	if statRead(weapAtk, 'IsMelee') then
		return nil
	end
	return weapAtk['AmmoMax'] or math.huge
end, passes(Math.formatnum)
},
Range = { function(weapAtk)
	return weapAtk['Range'] or statRead(weapAtk, 'ShotType') == 'Hit-Scan' and 300 or 0
end, '%.1f m'
},
Reload = { ors('Reload', 'RechargeTime', 0), '%.2f s' },
RechargeTime = { function(weapAtk)
	return statRead(weapAtk, 'ReloadStyle'):find'[Rr]egen' and statRead(weapAtk, 'Magazine') / statRead(weapAtk, 'ReloadRate') or nil
end, '%.2f s'
},
ReloadRate = { 0, '%.2f rounds/sec' },	-- Used for rechargeable weapons; not necessarily inverse of reload time b/c of presence of reload delay
ReloadDelay = { function(weapAtk)
	return weapAtk['ReloadDelay'] or 0
end, '%.2f s'
},
ReloadDelayEmpty = { ors('ReloadDelayEmpty', 'ReloadDelay'), '%.2f s' },
-- Reload speed will be calculated as the inverse of reload time for purposes
-- of keeping how we rank stats consistent for [[Module:Weapons/preprocess]]
-- (larger number = higher stat; a short reload time can be expressed as fast reload 
-- speed which would be a larger value in magnitude)
ReloadSpeed = { function(weapAtk)
	return 1 / statRead(weapAtk, 'Reload')
end, function(str, reloadSpeed)
	return string.format('%.2f%% reload progress per second', reloadSpeed * 100)
end },
ReloadStyle = 'Magazine',
Spool = { 0, '%d rounds' },
SpoolStartFireRate = { 0, '%.1fx' },	-- scalar that is applied to fire rate stat for auto-spool weapons
AvgSpread = { function(weapAtk)
	local minSpread = statRead(weapAtk, 'MinSpread')
	local maxSpread = statRead(weapAtk, 'MaxSpread')
	if (minSpread == nil) then
		return nil
	end
	return (minSpread + maxSpread) / 2
end, function(self, value)
		if (value == nil) then
			return 'N/A'
		end
		return ('%.2f°'):format(value)
	end
},
-- Default nil b/c some attacks don't have an associated accuracy/spread value (like AoE explosions)
MinSpread = { nil, function(self, value)
	if (value == nil) then
			return 'N/A'
		end
		return ('%.2f°'):format(value)
	end
},
MaxSpread = { nil, function(self, value)
	if (value == nil) then
			return 'N/A'
		end
		return ('%.2f°'):format(value)
	end
},
Trigger = 'N/A',
BlockAngle = { 0, '%d&#176;' },
ComboDur = { 0, '%.1f s' },
FollowThrough = { 0, '%.1fx' },
HeavyAttack = { 0, passes(Math.formatnum) },
HeavySlamAttack = { 0, passes(Math.formatnum) },
HeavyRadialDmg = { 0, passes(Math.formatnum) },
HeavySlamRadius = { 0, '%.1f m' },
MeleeRange = { 0, '%.2f m' },
SlamAttack = { 0, passes(Math.formatnum) },
SlamRadialDmg = { function(weapAtk)
	return weapAtk.SlamRadialDmg or 0, statRead(weapAtk, 'SlamRadialElement')
end, function(self, dmg, elem)
	if elem then
		return Tooltip.icon(elem, 'DamageTypes')..' '..Math.formatnum(dmg)
	end
	return Math.formatnum(dmg)
end
},
SlamRadialElement = { nil, function(self, value)
	return value ~= nil and Tooltip.full(value, 'DamageTypes') or 'Same damage type distribution as normal attack'
end
},
-- Slam radial forced proc(s)
SlamRadialProcs = { nil, function(self, proc)
	if type(proc)=='table' then
		local result = {}
		for _, elem in ipairs(proc) do
			table.insert(result, Tooltip.full(elem, 'DamageTypes'))
		end
		return table.concat(result, '<br />')
	else
		return 'N/A'
	end
end
},
SlamRadius = { 0, '%.1f m' },
SlideAttack = { function(weapAtk)
	return weapAtk.SlamRadialDmg or 0, statRead(weapAtk, 'SlideElement')
end, function(self, dmg, elem)
	if elem then
		return Tooltip.icon(elem, 'DamageTypes')..' '..Math.formatnum(dmg)
	end
	return Math.formatnum(dmg)
end
},
SlideElement = { nil, function(self, value)
	return value ~= nil and Tooltip.full(value, 'DamageTypes') or 'Same damage type distribution as normal attack'
end
},
--[[Stances = function(weapAtk)
	if not statRead(weapAtk, 'IsMelee') then return end
	return Stances._getAllStancesSameType(statRead(weapAtk, "Class"))
	--             ^ currently a local function
end,--]]
-- Not all weapons have an Stance slot so default to nil
StancePolarity = { nil, function(self, stancePolarity)
	if (stancePolarity == nil) then
		return 'N/A'
	end
	return Polarity._polarity(stancePolarity)
end
},
SweepRadius = { 0, '%.2f m' },
WindUp = { 0, '%.1f s' },
BurstCount = 1,
-- Average crit/proc count from a single attack input
AvgCritCount = function(weapAtk)
	return statRead(weapAtk, 'CritChance') * statRead(weapAtk, 'Multishot')
end,
AvgCritPerSec = function(weapAtk)
	return statRead(weapAtk, 'AvgCritCount') * statRead(weapAtk, 'EffectiveFireRate')
end,
AvgProcCount = function(weapAtk)
	return ( statRead(weapAtk, 'StatusChance') + Table.size(weapAtk['ForcedProcs'] or {}) ) * statRead(weapAtk, 'Multishot')
end,
AvgProcPerSec = function(weapAtk)
	return statRead(weapAtk, 'AvgProcCount') * statRead(weapAtk, 'EffectiveFireRate')
end,
InterShotTime = function(weapAtk)
	local v = statRead(weapAtk, 'Magazine') == 1 and statRead(weapAtk, 'Reload') + statRead(weapAtk, 'ReloadDelayEmpty') or 0
	if v == 0 then v = 1 / statRead(weapAtk, 'FireRate') end
	return v
end,
EffectiveFireRate = function(weapAtk)
	return 1 / ( statRead(weapAtk, 'ChargeTime') + statRead(weapAtk, 'InterShotTime') )
end,
ShotsPerMag = function(weapAtk)
	-- Default to 1 "ammo cost" even if attack does not directly consume ammo (e.g. AoE hits, speargun throws, etc.)
	return math.floor(statRead(weapAtk, 'Magazine') / (statRead(weapAtk, 'AmmoCost') or 1))
end,
FireRate = { function(weapAtk)
	local dataFireRate = weapAtk['FireRate']
	if dataFireRate then return dataFireRate end
	-- TODO: Think we can safely remove this calculation of FireRate from BurstFireRate, BurstDelay, and BurstCount
	-- for burst-fire attacks since FireRate is also included for those
	mw.log('calculating FireRate from Burst stats for '..statRead(weapAtk, 'Name'))
	local count = statRead(weapAtk, 'BurstCount')
	local fireRate = count / (1 / statRead(weapAtk, 'BurstFireRate') + count * statRead(weapAtk, 'BurstDelay'))
	return fireRate
end, '%.3f attacks/sec'
},
BurstFireRate = { function(weapAtk)
	return 1 / statRead(weapAtk, 'BurstDelay')
end, '%.2f attacks/sec'
},
--[[
Describing what happens when a gun in WARFRAME is fired using player-made terminology:

A particular gun consumes a set number of ammo in order to fire a set number of shots
on a single player input for a particular attack.

A single player input is defined as:
* a single attack button press for semi-auto and burst trigger weapons
* the moment the next shot is being fired when the attack button is being held for automatic/held trigger weapons
* the action of holding the attack button for charge trigger weapons
* for duplex-fire trigger weapons, the hold and release of the attack button counts as two inputs

A shot is defined as the base unit of attack of a weapon when unmodded.
* A single attack input can launch several shots as in the case of burst-fire weapons.
* A single shot can shoot more than one projectile, affected by the multishot stat, as in the case of shotguns.
* A single shot can consume more than one unit of ammo (e.g. Tenora's alt-fire) or 
less than one unit of ammo (e.g. Ignis and most continuous weapons).

A gun can have multiple attacks which can be triggered using different buttons 
and/or types of button inputs (e.g. pressing vs. holding)
]]--
CalcDamage = function(weapAtk)
	local weapon, attack = weapAtk, weapAtk
	-- Count
	-- How many shots are fired in a single player input
	local tapShots = statRead(weapAtk, 'BurstCount')
	-- How many individual player inputs can occur before depleting a magazine
	local magTaps = statRead(weapAtk, 'ShotsPerMag')
	-- How many additional projectiles are fired per ammo
	local multishot = statRead(weapAtk, 'Multishot')
	-- How much ammo is contained in the magazine
	local magazine = statRead(weapAtk, 'Magazine')
	-- How much ammo can be drawn from reserves (or?, how much ammo can be used without picking up more)
	local ammoMax = statRead(weapAtk, 'AmmoMax')

	-- Time^-1
	local fireRate = statRead(weapAtk, 'FireRate')
	-- Time
	local shotTime = statRead(weapAtk, 'InterShotTime')
	local chargeTime = statRead(weapAtk, 'ChargeTime')
	local burstDelayTime = statRead(weapAtk, 'BurstDelay')
	local reloadDelayTime = statRead(weapAtk, 'ReloadDelayEmpty')
	local reloadTime = statRead(weapAtk, 'Reload')
	local tapTime = chargeTime + (tapShots - 1) * burstDelayTime
	-- tapTime: The time between the last shot fired and the next valid attack input
	-- (omitting latency of course).
	-- Note that first shot of any non-charge trigger attack is instantenous
	local magDepletionTime = magTaps * tapTime
	if magDepletionTime == 0 then -- If attack is not a charged attack
		if shotTime == 0 then
			shotTime = 1 / fireRate
		end
		magDepletionTime = magTaps / fireRate
	end
	local shotDelayTime = math.max(0, shotTime - tapTime)

	-- Multiplier
	local maxProgenitorBonus = statRead(weapAtk, 'IsLichWeapon') and 1.6 or 1
	local avgCritMult = 1 + (statRead(weapAtk, 'CritMultiplier') - 1) * statRead(weapAtk, 'CritChance')
	-- Damage
	local biasPortion, biasType, hitDamage = statRead(weapAtk, 'DamageBias')
	
	local avgDmgOnTap = hitDamage * avgCritMult * multishot * tapShots * maxProgenitorBonus
	local avgDmgPerMag = avgDmgOnTap * magTaps
	
	-- 1 is needed b/c one whole magazine is not included in reserve ammo count
	-- If there is no reserve ammo, that means that weapon can deal an infinite amount of damage theoretically
	local avgLifetimeDmg = (ammoMax ~= nil) and avgDmgPerMag * (1 + (ammoMax / magazine)) or math.huge
	
	-- Damage / Time
	local baseDps = hitDamage * multishot / shotTime
	local avgSustainedDps = avgDmgPerMag / (magDepletionTime + reloadDelayTime + reloadTime) / tapShots
	local avgBurstDps = avgDmgOnTap / (tapTime + shotDelayTime) / tapShots
	-- Note that burst DPS can also be calculated as such:
	-- local avgBurstDps = (hitDamage * avgCritMults * maxProgenitorBonus) * multishot / shotTime
	-- local avgBurstDps = avgDmgPerMag / magDepletionTime
	
	return StatObject.ucacheIn(weapAtk, 'CalcDamage',
			{ hitDamage, avgDmgOnTap, avgBurstDps, avgSustainedDps, avgLifetimeDmg, baseDps, avgDmgPerMag }
	)
end,
ShotDmg = indexes('CalcDamage', 1),	-- Total damage per projectile
AvgShotDmg = indexes('CalcDamage', 2), AvgTapDmg = indexes('CalcDamage', 2),	-- Average total damage per each input button
BurstDps = indexes('CalcDamage', 3),	-- Average burst damage per second/DPS w/o reloading
SustainedDps = indexes('CalcDamage', 4),	-- Average sustained damage per second/DPS w/ reloading
LifetimeDmg = indexes('CalcDamage', 5),	-- Average total damage from entire ammo pool
BaseDps = indexes('CalcDamage', 6),	-- Base damage per second w/ multishot w/o crit
MagDmg = indexes('CalcDamage', 7),	-- Average total damage per magazine
-- Average damage scaled by melee attack speed multiplier (numerator of melee DPS w/o accounting for stances and animation time)
AvgDmgWithAnimSpeedMulti = function(weapAtk)
	if statRead(weapAtk, 'IsMelee') then
		-- Some melee weapons have attacks with multishot like Redeemer, Vastilok, and Quassus
		return statRead(weapAtk, 'BaseDamage') * statRead(weapAtk, 'Multishot') * statRead(weapAtk, 'AttackSpeed')
	end
	return 0
end,
AttackSpeed = { --[[ors('AttackSpeed', 'FireRate')]]function(weapAtk)
	if not statRead(weapAtk, 'IsMelee') then
		error('AttackSpeed: Cannot get AttackSpeed attribute for a non-melee weapon; use p.statRead(weapAtk, "FireRate") instead')
	end
	return statRead(weapAtk, 'FireRate')
end, '%.2fx animation speed'
},
IsMelee = function(weapAtk) return statRead(weapAtk, 'Slot'):find('Melee') or statRead(weapAtk, 'Slot'):find('melee') ~= nil end,
IsSilent = ors('IsSilent', 'IsMelee', false),
HasAoEAttack = function(weap)
	for i, attackEntry in pairs(weap['Attacks']) do
		if attackEntry['ShotType'] == 'AoE' then
			return true
		end
	end
	return false
end,
Conclave = false,
Image = { 'Panel.png', '[[File:%s|link=]]' },
Attacks = ors('Attacks', p._getAttack, {}),
Family = nil,
FamilyList = { function(weapAtk)
	local family = statRead(weapAtk, 'Family')
	-- assert(family, 'i have no Family :\'(')
	if not family then return {weapAtk} end
	-- return family, statRead(weapAtk, 'Slot')
	local slot = statRead(weapAtk, 'Slot')
	local result = {}
	for _, w in pairs(WeaponData[slot] or error('FamilyList: no weapondata for slot '..(slot or '<nil>'))) do
		if w.Family == family then
			table.insert(result, w)
		end
	end
	table.sort(result, function(a,b) return a.Name<b.Name end)
	return result
end, function(self, result)
	for i,w in ipairs(result) do
		result[i]=Tooltip.full(w.Name, 'Weapons', w)
	end
	return table.concat(result, '<br />')
end
},
BaseName = function(weapAtk) return weapAtk['BaseName'] or ({p._getVariant(statRead(weapAtk, 'Name'))})[3] end,
-- TODO: Add comments to Explosion function for readability
-- TODO: Do not rely on attack name to determine what AoE component is attached to which main direct hit component
---^i suggest an explosion key with either the attack number of any corresponding explosion, nested attack tables, or some other way to make a tree
-- TODO: Use ShotType = "AoE" to determine if attack entry is AoE
Explosion = function(weapAtk)
	local weap, atk = weapAtk, weapAtk
	-- tbh this is a mess
	local explosion = weapAtk['Explosion'] or statRead(weapAtk, 'AttackName'):gsub(' Impact',''):gsub(' Contact','')..' Explosion'
	if type(explosion) == 'string' then
		explosion = weap.Attacks[tonumber(explosion:gsub('%D',''))] or explosion
	elseif type(explosion) == 'number' then
		explosion = weap.Attacks[explosion] or explosion
	end
	local explosions = {}
	if type(explosion) ~= 'table' then
		for i, v in ipairs(weap.Attacks) do
			if p._statRead(weapAtk, v, 'AttackName'):find 'xplosion' then
				if p._statRead(weapAtk, v, 'AttackName') == explosion then
					explosions[1] = nil
					explosion = v
					break
				end
				table.insert(explosions, v)
			end
		end
		explosion = explosions[1] or explosion
	end
	StatObject.pcacheIn(getWeaponAttack(weap, explosion), 'BaseAttack', atk)
	return StatObject.pucacheIn(weapAtk, 'Explosion', explosion)
end,
IsVariant = function(weap)
	return StatObject.pucacheIn(weap, 'IsVariant', p._isVariant(statRead(weap, 'Name')))
end,
Variant = indexes('IsVariant', 2),
BaseName = indexes('IsVariant', 3),
Categories = { function(weapAtk)
	local cats = { 'Weapons' }
	-- Adding editor-defined traits from M:Weapons/data
	-- Note to make sure they have a proper category page associated with a trait
	for _, trait in ipairs(weapAtk.Traits or {}) do
		table.insert(cats, trait..' Weapons')
	end
--[=[
local CATEGORY_MAP = {
	Primary = 'Primary Weapons',
	Secondary = 'Secondary Weapons',
	Melee = 'Melee Weapons',
	['Archmelee'] = 'Archwing Melee',
	['Archgun'] = 'Archwing Gun',
	['Archgun (Atmosphere)'] = 'Archwing Gun',
	Kitgun = 'Kitgun',
	Zaw = 'Zaw',
	['Railjack Turret'] = 'Railjack',
	['Railjack Armament'] = 'Railjack',
	Gear = 'Gear',
	
	Rifle = 'Assault Rifle',
	['Sniper Rifle'] = 'Sniper Rifle',
	Shotgun = 'Shotgun',
	Pistol = 'Pistol',
	['Dual Pistols'] = 'Dual Pistols',
	Bow = 'Bow',
	Launcher = 'Launcher',
	['Arm-Cannon'] = 'Arm-Cannon',
	['Speargun'] = 'Speargun',
	Thrown = 'Thrown',
	['Shotgun Sidearm'] = 'Shotgun Sidearm',
	
	Prime = 'Prime Weapons',
	['Never Vaulted'] = 'Never Vaulted',
	Vaulted = 'Vaulted',
	Wraith = 'Wraith',
	Vandal = 'Vandal',
	['Kuva Lich'] = 'Kuva Lich',
	['Kuva Lich'] = 'Kuva',
	Prisma = 'Prisma',
	
	Grineer = 'Grineer Weapons',
	Corpus = 'Corpus Weapons',
	Infested = 'Infested Weapons',
	Tenno = 'Tenno Weapons',
	Sentient = 'Sentient Weapons',
	Entrati = 'Entrati Weapons',
	Baro = 'Baro Ki\'Teer Offering',
	Syndicate = 'Syndicate Offerings',
	['Invasion Reward'] = 'Invasion Reward',
	
	['Alt Fire'] = 'Weapons with Alt Fire',
	['AoE'] = 'Weapons with Area of Effect]][[Category:Self Interrupt Weapons',
	
}
--]=]
	local bias = p._getValue(weapAtk, "BiasType")
	table.insert(cats, bias..' Damage Weapons')
	
	local class = p._getValue(weapAtk, "Class")
	table.insert(cats, ({
		["Arm Cannon"] = class,
		["Bow"] = class,
		["Crossbow"] = "Bow",
		["Dual Pistols"] = class,
		["Dual Shotguns"] = class,
		["Exalted Weapon"] = class,
		["Launcher"] = class,
		["Pistol"] = class,
		["Rifle"] = class,
		["Shotgun Sidearm"] = class,
		["Shotgun"] = class,
		["Sniper Rifle"] = class,
		["Speargun"] = class,
		["Thrown"] = class,
		["Tome"] = class,
		["Assault Saw"] = class,
		["Blade and Whip"] = class,
		Claws = class,
		Dagger = class,
		["Dual Daggers"] = class,
		["Dual Nikanas"] = class,
		["Dual Swords"] = class,
		Fist = class,
		Glaive = class,
		Gunblade = class,
		Hammer = class,
		["Heavy Blade"] = class,
		["Heavy Scythe"] = class,
		Machete = class,
		Nikana = class,
		Nunchaku = class,
		Polearm = class,
		Rapier = class,
		Scythe = class,
		Sparring = class,
		Staff = class,
		Sword = class,
		["Sword and Shield"] = class,
		["Two-Handed Nikana"] = class,
		Tonfa = class,
		Warfan = class,
		Whip = class,
		["Archgun"] = 'Archwing Gun',
		["Archgun (Atmosphere)"] = 'Archwing Gun',
		["Archmelee"] = 'Archwing Melee',
		["Turret"] = 'Railjack Armaments',
		["Ordnance"] = 'Railjack Armaments',
		Amp = class
	})[class] or "Other Weapons")

	local family = p._getValue(weapAtk, "Family")
	table.insert(cats, family)
	
	local slot = p._getValue(weapAtk, "Slot")
	table.insert(cats, slot..' Weapons')
	
	-- TODO: Move all these trigger categories to be in the format "Category:Trigger Name Weapons"
	local trigger = p._getValue(weapAtk, "Trigger")
	table.insert(cats, trigger..' Weapons')
	-- TODO: remove original trigger cats? (following snippet)
	table.insert(cats,(({
		["Active"] = trigger,
		["Auto Charge"] = 'Charge',
		["Auto"] = 'Automatic',
		["Auto-Burst"] = 'Burst Fire',
		["Auto-Spool"] = 'Automatic',
		["Burst"] = 'Burst Fire',
		["Charge"] = trigger,
		["Duplex"] = 'Duplex Fire',
		["Held"] = 'Continuous Weapons',
		["Semi-Auto"] = 'Semi-Automatic',
	})[trigger]))--modes? " Weapons"?
	
	local users = p._getValue(weapAtk, "Users") or {}
	for _, user in ipairs(users) do table.insert(cats, user) end
	
	local variant = p._getValue(weapAtk, "Variant")
	table.insert(cats, variant)
	
	local infAmmo = p._getValue(weapAtk, "AmmoMax") == math.huge
	local accuracy = p._getValue(weapAtk, "Accuracy")
	local pinpoint = accuracy ~= nil and accuracy >= 100
	local regens = p._getValue(weapAtk, "ReloadRate") > 0
	local silent = weapAtk.IsSilent -- automatically includes
	local single = p._getValue(weapAtk, "Magazine") == 1 and not p._getValue(weapAtk, "IsMelee")--meh, delet?
	local spools = p._getValue(weapAtk, "Spool") > 0
	
	local isHitScan = p._getValue(weapAtk, "ShotType") == "Hit-Scan"
	local isProjectile = p._getValue(weapAtk, "ShotType") == "Projectile"
	local isAoE = p._getValue(weapAtk, "HasAoEAttack")
	local isCodexSecret = p._getValue(weapAtk, "CodexSecret")
	local isTradable = p._getValue(weapAtk, "Tradable")
	local isInConclave = p._getValue(weapAtk, "Conclave")
	
	-- Arbitrarily ordering misc categories
	if infAmmo then table.insert(cats, 'Infinite Ammo Weapons') end
	if pinpoint then table.insert(cats, 'Pinpoint Weapons') end
	if regens then table.insert(cats, 'Battery Weapons') end
	if silent then
		table.insert(cats, 'Silent Weapons')
	else
		table.insert(cats, 'Alarming Weapons')
	end
	if single then table.insert(cats, 'Single Shot Weapons') end
	if spools then table.insert(cats, 'Spooling Weapons') end
	if isHitScan then table.insert(cats, 'Hit-Scan Weapons') end
	if isProjectile then table.insert(cats, 'Projectile Weapons') end
	if isAoE then table.insert(cats, 'Weapons with Area of Effect') end
	if isCodexSecret then table.insert(cats, 'Codex Secret') end
	if isTradable then
		table.insert(cats, 'Tradeable Weapons')
	else
		table.insert(cats, 'Untradeable Weapons')
	end
	if isInConclave then table.insert(cats, 'Available In Conclave') end
	
	return StatObject.cacheIn(weapAtk, 'Categories', cats)
end, function(s, cats)
	local wikitextResult = { '' }	-- Need to prepend a newline so first asterisk is rendered as a wikitext list
	local formatStr = '*[[:Category:%s|%s]][[Category:%s]]'
	for _, category in ipairs(cats) do
		table.insert(wikitextResult, formatStr:format(category, category, category))
	end
	return table.concat(wikitextResult, '\n')
end
},
SyndicateEffect = { '', function(s, v)
	return (v == '' or type(v) ~= 'string') and '' or Tooltip.icon(({
		['blight'] = 'Red Veil',
		['entropy'] = 'Cephalon Suda',
		['justice'] = 'Steel Meridian',
		['purity'] = 'New Loka',
		['sequence'] = 'The Perrin Sequence',
		['truth'] = 'Arbiters of Hexis',
	})[v:lower()] or 'Tenno', 'Factions')
	..' '..v
end
},
MinProgenitorBonus = function(weap) return weap.IsLichWeapon and statRead(weap, 'BaseDamage') * 0.25 or 0 end,
ProgenitorBonus = function(weap) return weap.IsLichWeapon and statRead(weap, 'BaseDamage') * 0.6 or 0 end,
Class = '',
SniperComboReset = { nil, '%.1f s' },
SniperComboMin = { nil, '%d shot(s)' },
Tradable = { function(weapAtk)
	if type(weapAtk['Tradable'])=='number' then
		assert(weapAtk['Tradable']<=5,
			'Tradable: Does not support tradeability enums beyond 5; please update [[Module:Weapons/data]] and [[Module:Weapons]] to support more tradeability edge cases')
		return ({
			[0]=false,
			[1]='Unranked',
			[2]='Parts',
			[3]='Lich',
			[4]='Built Parts',
			[5]='Parent',
		})[weapAtk['Tradable']]
	end
	return weapAtk['Tradable']
end, function(s, tradable)
	return ({ 
		[false] = 'Untradeable',
		Unranked = 'Tradeable unranked w/ no Forma or Catalyst',
		Parts = 'Tradeable parts and/or blueprint only',
		Lich = 'Tradeable indirectly through [[Lich System|Lich]] trading',
		['Built Parts'] = 'Tradeable only fully built components, not blueprints',
		Parent = 'Tradeable indirectly, comes with parent companion',
	})[tradable] or 'Untradeable?'
end
},
SellPrice = { nil, function(self, sellPrice)
	if sellPrice == nil then
		return 'Cannot sell'
	end
	return Tooltip.icon('Credits', 'Resources')..' '..Math.formatnum(sellPrice)
end
},
DefaultUpgrades = { nil, function(self, upgradesArr)
	local result = {}
	for _, modIndex in ipairs(upgradesArr or {}) do
		table.insert(result, Tooltip.full(modIndex, 'Mods'))
	end
	return table.concat(result, '<br />')
end
},
Users = { nil, function(self, usersArr)
	local result = { '' }
	for _, user in ipairs(usersArr or {}) do
		table.insert(result, '*[['..user..']]')
	end
	return table.concat(result, '\n')
end
},
Zoom = { unpacks('Zoom'), { sep = '<br />' } },
Slot = nil,
}
-- Loops for adding to StatObject.default table
-- Damage type getters:
-- <DamageType> = damage type value
-- <DamageType>Distribution = damage type distribution as a percentage
-- PvP<DamageType> = damage type value with precise formatting for PvP purposes
for _, damageType in ipairs(iterationOrderArray) do
	StatObject.default[damageType] = {
		function(weapAtk) return weapAtk['Damage'][damageType] or 0 end,
		function(self, value) return Tooltip.icon(damageType, 'DamageTypes')..' '..Math.formatnum(value) end
	}
	-- Damage distribution as a percentage
	StatObject.default[damageType..'Distribution'] = {
		function(weapAtk) return weapAtk['Damage'][damageType] / statRead(weapAtk, 'BaseDamage') end,
		function(self, value) return Tooltip.icon(damageType, 'DamageTypes')..' '..Math.percentage(value) end
	}
	-- More precise damage values to 4 decimal places for PvP
	StatObject.default['PvP'..damageType] = {
		function(weapAtk) return weapAtk['Damage'][damageType] or 0 end,
		Tooltip.icon(damageType, 'DamageTypes')..' %.4f'
	}
end

-- TODO: Do not rely on localized name to determine a weapon's variant. Decouple localization from data
---	Checks if a weapon is a variant or not.
--	@function		p._isVariant
--	@param			{string} weaponName Weapon name
--	@returns		{boolean} True if weapon is a variant, false otherwise
--	@returns		{string} Weapon's variant name or "Base" if weapon is not a variant
--	@returns		{string} Weapon name, same as weaponName
function p._isVariant(weaponName)
	for i, var in pairs(VARIANT_LIST) do
		if (var ~= "Dex" or weaponName ~= "Dex Pixia") then
			if string.find(weaponName, var) then
				return true, var, (string.gsub(weaponName, " ?"..var.." ?-?", ""))
			end
		end
	end
	return false, "Base", weaponName
end

---	Builds the full name of a weapon's variant. Does not check if it exists or not.
--	@function		p._buildName
--	@param			{string} baseName Weapon's base name (e.g. "Braton")
--	@param[opt]		{string} variant Variant name (e.g. "Vandal"); if nil, returns base weapon name instead
--	@returns		{string} Weapon's variant name (e.g. "Braton Vandal")
function p._buildName(baseName, variant)
	if not variant or variant == 'Base' or variant == '' then
		return baseName
	end
	return (({
		-- Prime Laser Rifle is an edge case for Prime naming scheme (at least in EN localization)
		Prime =  baseName ~= 'Laser Rifle' and '%b %v',
		Vandal = '%b %v',
		Wraith = '%b %v',
		MK1 = '%v-%b',
	})[variant] or '%v %b'):gsub('%%v', variant):gsub('%%b', baseName)
end

---	Returns a specific weapon table entry from <code>/data</code> or <code>/Conclave/data</code>.
--	@function		p._getWeapon
--	@param			{string} weaponName Weapon name
--	@param[opt]		{boolean} pvp If true, gets PvP stats of weapon instead, false otherwise; defaults to false
--	@returns		{table} Weapon table
function p._getWeapon(weaponName, pvp)
	weaponName = mw.text.decode(weaponName)
	return (pvp and ConclaveData or WeaponData)[weaponName] or
	error('p._getWeapon(weaponName, pvp): "'..weaponName..
		'" does not exist in '..(pvp and '[[Module:Weapons/Conclave/data]]' or '[[Module:Weapons/data]]'))
end

---	Gets the raw value of a certain statistic of a weapon.
--	@function		p._getValue
--	@param			{table} Weapon Weapon table
--	@param			{string} key Name of key
--	@param[opt]		{string} attack Name of attack to search through; defaults to 'Attack1' or what '_TooltipAttackDisplay' is set to
--	@returns		{string, number} Value of statistic
function p._getValue(weap, key, atk)--, formatted)
	-- return (formatted and statFormat or statRead)(weap, atk, key)
	return p._statRead(weap, atk, key)
end

---	Gets the formatted value of a certain statistic of a weapon to be displayed
--	the wiki.
--	@function		p._getFormattedValue
--	@param			{table} Weapon Weapon table
--	@param			{string} keyName Name of key
--	@param[opt]		{string} attackName Name of attack to search through; defaults to 'Attack1'
--	@returns		{string} Value of statistic
function p._getFormattedValue(weap, key, atk)
	-- return p._getValue(Weapon, keyName, attackName, true)
	return p._statFormat(weap, atk, key)
end

--- Function that returns a simpler getter function, for multiple _stat*() calls on the same weapon/attack pair.
--	@function		p._statReader
--	@param			{table} weap Weapon entry
--	@param			{number|table} atk Attacks table index or Attack entry
--	@return			{function} Getter function
function p._statReader(weap, atk)
	return function(...) return p._statRead(weap, atk, ...) end
end

--- Function that returns a simpler getter function, for multiple _stat*() calls on the same weapon/attack pair.
--	@function		p._statFormatter
--	@param			{table} weap Weapon entry
--	@param			{number|table} atk Attacks table index or Attack entry
--	@return			{function} Getter function
function p._statFormatter(weap, atk)
	return function(...) return p._statFormat(weap, atk, ...) end
end

---	Returns a subset of <code>/data</code> or <code>/Conclave/data</code> based on a validation function.
--	@function		p._getWeapons
--	@param			{function} validateFunction Function that filters out a weapon by taking in a Weapon table argument
--	@param[opt]		{string} source Name of weapon entry to use
--	@param[opt]		{boolean} ignoreIgnore If true, ignores the _IgnoreEntry flag, false otherwise; defaults to false
--	@param[opt]		{function} sortFunc Custom comparison function; false -> no sorting; defaults to sorting in ascending order by weapon name
--	@returns		{table} Table of weapon table entries as seen in <code>/data</code>
function p._getWeapons(validateFunction, source, opts)
	opts=opts or {}
	local ignoreIgnore, sortFunc, pvp = opts.ignoreIgnore, opts.sortFunc, opts.pvp
	validateFunction = validateFunction or function() return true end
	local data = pvp and ConclaveData or WeaponData
	if source then
		data = data[source]
	end

	local weaps = {}
	for _, weap in pairs(data) do
		if (ignoreIgnore or not weap['_IgnoreEntry']) and validateFunction(weap) then
			table.insert(weaps, weap)
		end
	end
	if sortFunc ~= false then
		table.sort(weaps, sortFunc or function(a, b) return a.Name < b.Name end)
	end
	return weaps
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.
--	@function		p._getMeleeWeapons
--	@param[opt]		{boolean} weapType
--	@param[opt]		{boolean} pvp If true, only gets melee weapons available in Conclave, false otherwise; defaults to false
--	@returns		{table} An array of melee weapon table entries as seen in <code>/data</code>
function p._getMeleeWeapons(weapType,pvp)
	return p._getWeapons(weapType and function(weap) return weap.Class==weapType end, 'melee',{['pvp']=pvp==true})
end

---	Main frame invokable function to access any raw/computed attribute/column/key of a weapon entry.
--	See default table in M:Weapons to see all valid computed attributes.
--	@function		p.getValue
--	@param			{string} weap Weapon name in EN locale
--	@param			{number} atk Attacks table index
--	@param			{string} k Key name
--	@return			Raw or computed value associated with k key
function p.getValue(frame)
	-- table.unpack doesn't work on the frame object which is why this is anonymous function is needed
	local weap, key, atk = (function(t) return t[1], t[2], t[3] end)(frame.args)
	weap = p._getWeapon(weap)
	return p._getValue(weap, key, atk)
end

---	Main frame invokable function to access any formatted attribute/column/key of a weapon entry.
--	See default table in M:Weapons to see all valid computed attributes.
--	@function		p.getFormattedValue
--	@param			{string} weap Weapon name in EN locale
--	@param			{number} atk Attacks table index
--	@param			{string} k Key name
--	@return			Formatted value associated with k key
function p.getFormattedValue(frame)
	local weap, key, atk = (function(t) return t[1], t[2], t[3] end)(frame.args)
	weap = p._getWeapon(weap)
	return p._getFormattedValue(weap, key, atk)
end

---	Builds a melee weapon gallery as seen on [[Template:MeleeCategory]].
--	@function		p.getMeleeWeaponGallery
--	@param			{table} frame Frame object w/ first argumenting being string meleeClass
--	@returns		{string} Resultant wikitext of gallery
function p.getMeleeWeaponGallery(frame)
	local meleeClass = frame.args[1] or ''
	local result = { "=="..meleeClass.." Weapons==", '<gallery widths="200" position="center" spacing="small">' }
	for i, weap in ipairs(p._getMeleeWeapons(meleeClass)) do
		table.insert(result, p._statRead(weap, nil, 'Image')..'|'..p._statFormat(weap, nil, 'Name'))
	end
	table.insert(result, '</gallery>')
	return frame:preprocess(table.concat(result, '\n')) -- annoying that it needs to be preprocessed
end

---	Gets the total count of weapons as used on [[Mastery Rank#Total Mastery]].
--	@function		p.getWeaponCount
--	@param			{table} frame Frame object w/ the first argument being the weaponSlot and the
--						  second argument being a boolean to getFullList
--	@returns		{number} Total count of weapons in a certain category/type
--	@returns		{table} List of weapon names that count for mastery in a particular weapon slot
function p._getWeaponCount(slot)
	slot = slot and slot:lower()
	local data = slot and WeaponData[slot] or WeaponData
	local fullList = {}
	
	for name, weapon in pairs(data) do
		if not weapon._IgnoreInMasteryCount then
			-- TODO: There should be a better way to determine/differentiate if a weapon is a kitgun b/c kitguns and zaws
			-- are stored in the same M:Weapons/data/modular data store; add a new "Kitgun" or "Zaw" Trait and target that?
			if (slot == 'kitgun' and weapon.Slot == 'Secondary')
				or (slot == 'zaw' and weapon.Slot == 'Melee')
				or (slot == 'robotic' and weapon.Slot ~= 'Hound')
				or (weapon.Slot:lower() == slot)
				or slot == nil then
				fullList[#fullList + 1] = name
			end
		end
	end

	return #fullList, fullList
end

---	Gets the total count of weapons as used on [[Mastery Rank#Total Mastery]].
--	@function		p.getWeaponCount
--	@param			{table} frame Frame object w/ the first argument being the weapon slot
--	@return			{number} Total number of weapons that can reward Mastery XP
function p.getWeaponCount(frame)
	return (p._getWeaponCount(frame.args and frame.args[1] or nil))
end

---	Builds wikitable of all weapons' innate polarities as seen on [[Polarity]].
--	@function		p.getPolarityTable
--	@param			{table} frame Frame object
--	@returns		{string} Wikitext of resultant wikitable
function p.getPolarityTable(frame)
	local colNames = { 'Primary', 'Secondary', 'Melee', 'Archgun', 'Archmelee' }
	local cols = {}	-- Will look like: {['Primary']={},['Secondary']={},['Melee']={},['Archgun']={},['Archmelee']={},}
	local colOrder = {}	--{cols['Primary'],cols['Secondary'],cols['Melee'],cols['Archgun'],cols['Archmelee'],}
	local colCounts = {}

	for i, v in ipairs(colNames) do
		cols[v] = {}
		colOrder[i] = cols[v]
		colCounts[v] = 0
	end

	for _, weapon in pairs(WeaponData) do
		local pols = Table.size(weapon["Polarities"] or {})
		local slot = weapon['Slot']
		if pols > 0 and cols[slot] then
			table.insert(cols[slot], {
				'|'..p._getFormattedValue(weapon, 'NameLink'):gsub(' ?%(.*%)', '')..'||'..p._getFormattedValue(weapon, "Polarities"),
				pols
			})
			colCounts[slot] = colCounts[slot] + 1
		end
	end

	for i, v in ipairs(colNames) do
		colCounts[i] = colCounts[v]
		table.sort(cols[v], function(a, b)return a[2] > b[2] end)
	end

	local result = {[=[
{| 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" |Archguns
! colspan="2" |Archmelees]=]}
	for i = 1, math.max(table.unpack(colCounts)) do --row
		table.insert(result, '|-')
		for _, col in ipairs(colOrder) do --cell
			table.insert(result,(col[i] or {'| ||'})[1])
		end
	end
	table.insert(result, '|}')
	return table.concat(result, '\n')
end

---	Builds a table that lists out all weapons with a certain damage type
--	@function		p.buildDamageTypeTable
--	@param			{table} frame Frame object
--	@returns		{string} Wikitext of resultant wikitable
function p.buildDamageTypeTable(frame)
	local damageType = frame.args and frame.args[1] or frame
	local mostly = frame.args and (frame.args[2] or '') ~= ''

	local content = {}
	for k,weap in pairs(WeaponData) do
		local weapAtk = getWeaponAttack(weap)--could add a loop here
		local portion, biastype, damage = statRead(weapAtk, 'DamageBias')
		local typeDmg = statRead(weapAtk, damageType)
		if damage == 0 then typeDmg = weapAtk[damageType] and 1 or 0 end--modular pieces
		--Filter for
		--a. any of the damage type in any attack - former 'not mostly'
		--b. at least one majority damage type - former 'mostly'
		--c. a majority of the damage type in the display attack - 'mostly'
		--d. any of the damage type in the display attack - 'not mostly'
		if biastype == damageType or not mostly and typeDmg > 0 then
			table.insert(content, ('| %s || %s || %s || %s || %s || data-sort-value="%s" | %s'):format(
				statFormat(weapAtk, 'Name'),
				statRead(weapAtk, 'Slot'),
				statRead(weapAtk, 'Class'),
				statRead(weapAtk, 'AttackName'),
				typeDmg,
				portion, statFormat(weapAtk, 'DamageBias')
			))
		end
	end
	table.sort(content)--will sort by tooltip span key

	return ([[
{| class = "listtable sortable" style="margin:auto;"
|+ '''Weapons with %s%s damage'''
|-
! Name !! Slot !! Class !! Attack Name !! data-sort-type="number" | %s !! data-sort-type="number" | Majority
|-
]]):format(mostly and 'mostly ' or '', damageType, Tooltip.full(damageType, 'DamageTypes'))
	..table.concat(content, '\n|-\n')..'\n|}'
end

--- _isVariant adapter for p._shortLinkList
local function variantOf(weap)
	local full, _, var, base = weap.Name, p._isVariant(weap.Name)
	return var, base, full
end

---	Builds a list of weapons, with variants being next to base weapon name inside parentheses
--	(e.g. {{Weapon|Braton}} ({{Weapon|MK1-Braton|MK1}}, {{Weapon|Braton Prime|Prime}})).
--	@function		p._shortLinkList
--	@param			{table} Weapon Weapon table
--	@param			{boolean} tooltip If true, adds weapon tooltips, false otherwise; defaults to false
--	@returns		{string} Wikitext of resultant list
function p._shortLinkList(Weapons, tooltip)
	return StatObject.shortLinkList(Weapons, variantOf, tooltip and 'Weapons')
end

---	Builds a list of weapons' mastery requirements as seen on [[Template:EquipmentUnlock]],
--	[[Template:EquipmentUnlock/Primary]], [[Template:EquipmentUnlock/Secondary]], 
--	[[Template:EquipmentUnlock/Melee]], etc.
--	@function		p.getMasteryShortList
--	@param			{table} frame Frame object w/ first argument being a string weaponSlot
--	@returns		{string} Wikitext of resultant list
function p.getMasteryShortList(frame)
	local weaponSlot = frame.args[1]
	local masteryRank = tonumber(frame.args[2])
	local weapArray = p._getWeapons(function(x)
		return x.Slot == weaponSlot and x.Mastery == masteryRank
	end)
	return table.concat(StatObject.shortLinkList(weapArray, variantOf, 'Weapons'), ' • ')
end

function p.fullList()
	return table.concat(StatObject.shortLinkList(WeaponData, variantOf, 'Weapons'), ' • ')
end

---	Builds a list of PvP weapons as seen on [[PvP#Limitations]].
--	@function		p.getConclaveList
--	@param			{table} frame Frame object w/ first argument being a string weaponSlot
--	@returns		{string} Wikitext of resultant list
function p.getConclaveList(frame)
	local weaponSlot = frame.args[1] or 'All'
	local weapArray = p._getWeapons(function(weap)
		return weap.Conclave == true
	end, weaponSlot, {pvp=true})
	return '*'..table.concat(StatObject.shortLinkList(weapArray, variantOf), '\n* ')
end

function p.getListWithWarframes(frame)
	local date_str = ''
	local list = {'{| class="listtable sortable" style="overflow-y:scroll; max-height:500px"', '|-', '!data-sort-type="date"| Release !! Weapon !! Warframes'}
	local frames = {}
	
	for _, warframe in pairs(WarframeData.Warframes) do
		if warframe.Introduced then
			date_str = Version._getVersionDate(warframe.Introduced)
			if frames[date_str] then
				table.insert(frames[date_str], '{{WF|' .. warframe.Name .. '}}')
			else
				frames[date_str] = {'{{WF|' .. warframe.Name .. '}}'}
			end
		end
	end

	for _, weapon in pairs(WeaponData) do
		date_str = Version._getVersionDate(weapon.Introduced)

		warframe = frames[date_str] or {'N/A'}
		table.sort(warframe)
		warframe = table.concat(warframe, ', ')
		
		date_str = date_str:sub(6, 7) .. '.' .. date_str:sub(9, 10) .. '.' .. date_str:sub(0, 4)

		table.insert(list, '|-')
		table.insert(list, '|data-sort-value=' .. date_str .. '|{{ver|' .. weapon.Introduced .. '}} ||{{Weapon|' .. weapon.Name .. '}}||' .. warframe)
	end
	table.insert(list, '|}')

	return frame:preprocess(table.concat(list, '\n'))
end

---	Builds a disposition wikitable as seen on [[Riven Mods/Weapon Dispos]].
--	@function		p.getRivenDispositionTable
--	@param			{table} frame Frame object w/ first argument being a string weaponSlot
--	@returns		{string} Wikitext of resultant wikitable
function p.getRivenDispositionTable(frame)
	local weaponSlot = frame.args[1]
	local result = {
		'{| class="article-table" border="0" cellpadding="1" cellspacing="1" style="width: 100%"',
		'|-',
		{'[[a| '},	-- Wikitable header row
		'|-'
	}

	-- local ranges = {'○○○○○', '●○○○○', '●●○○○', '●●●○○', '●●●●○', '●●●●●'}
	local dispo = {}

	for k, weapon in pairs(WeaponData) do
		if weapon['Disposition'] and (weaponSlot == 'All' or weapon['Slot'] == weaponSlot) then
			local disp = p._statFormat(weapon, nil, 'Dispo')
			dispo[disp] = dispo[disp] or {}
			table.insert(dispo[disp], weapon)
		end
	end

	for str, dis in Table.skpairs(dispo) do
		table.sort(dis, function(a, b) return a['Disposition'] > b['Disposition'] end)
		local col = { '| style="vertical-align:top; font-size:small" |' }
		for _, weap in ipairs(dis) do
			table.insert(col, p._statFormat(weap, nil, 'NameLink')..' ('..weap['Disposition']..')')
		end
		table.insert(result[3], str)
		table.insert(result, table.concat(col, '\n* '))
	end

	result[3] = table.concat(result[3], ']]\n! scope="col" style="text-align:center;"|[[Riven Mods#Disposition|')..']]'
	table.insert(result, '|}')
	return table.concat(result, '\n')
end

return p