m (should be good now) |
m (For some reason Sonicor still shows 0 procs/s in comp table; using Table.size() instead of #) |
||
Line 279: | Line 279: | ||
local statusChance = attackEntry['StatusChance'] or 0 |
local statusChance = attackEntry['StatusChance'] or 0 |
||
local multishot = attackEntry['Multishot'] or 1 |
local multishot = attackEntry['Multishot'] or 1 |
||
− | local numForcedProcs = attackEntry['ForcedProcs'] and |
+ | local numForcedProcs = attackEntry['ForcedProcs'] and Table.size(attackEntry['ForcedProcs']) or 0 |
return (statusChance + numForcedProcs) * multishot |
return (statusChance + numForcedProcs) * multishot |
||
end, |
end, |
||
Line 286: | Line 286: | ||
local statusChance = attackEntry['StatusChance'] or 0 |
local statusChance = attackEntry['StatusChance'] or 0 |
||
local multishot = attackEntry['Multishot'] or 1 |
local multishot = attackEntry['Multishot'] or 1 |
||
− | local numForcedProcs = attackEntry['ForcedProcs'] and |
+ | local numForcedProcs = attackEntry['ForcedProcs'] and Table.size(attackEntry['ForcedProcs']) or 0 |
local fireRate = (attackEntry['ChargeTime'] ~= nil) and (1 / attackEntry['ChargeTime']) or attackEntry['FireRate'] |
local fireRate = (attackEntry['ChargeTime'] ~= nil) and (1 / attackEntry['ChargeTime']) or attackEntry['FireRate'] |
||
return (statusChance + numForcedProcs) * multishot * fireRate |
return (statusChance + numForcedProcs) * multishot * fireRate |
Revision as of 22:30, 27 July 2021
Weapons contains all of WARFRAME's non-modular weapon data.
Usage
Template
In template and articles: {{#invoke:Weapons|function|input1|input2|...}}
Quick navigation to submodules:
- Module:Weapons/Conclave/data - data store for Conclave-specific weapon stats
- Module:Weapons/characteristics - for generating advantage/disadvantage text under Characteristic sections of weapon articles based on stored data in Module:Weapons/data
- Module:Weapons/compare - for generating text for side-by-side comparison between two weapons
- Module:Weapons/comptable
- Module:Weapons/csv - for generating CSV output of a subset of weapon stats as stored in Module:Weapons/data
- Module:Weapons/data - main submodule for weapon data store
- Module:Weapons/data/credits - in-progress data store for mapping weapons to their development credits
- Module:Weapons/data/dev - sandbox page for data store
- Module:Weapons/data/validate - data validation scripts
- Module:Weapons/dev - sandbox page
- Module:Weapons/infobox - builds weapon infoboxes
- Module:Weapons/nav - builds weapon navigation box as seen at the bottom of weapon articles
- Module:Weapons/ppdata - preprocessed weapon data containing statistical
- Module:Weapons/preprocess - script for seeding ppdata
- Module:Weapons/ppdata/seeder - archived script for seeding ppdata
- Module:Weapons/testcases - unit test suite for Module:Weapons
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 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's case, Jat Kusar has a base 35% crit chance, but since we are comparing against non-normal attacks, Quassus's Ethereal Daggers will have second highest crit chance (30%) behind Tenet Exec's slam shockwaves (38%). |
22:09, 5 August 2022 (UTC) | ||
Weapon and Attack classes | Refactor and Dev | Planning | Low |
|
21:18, 18 January 2022 (UTC) | ||
ExplosionDelay key
|
Refactor | Planning | Low |
|
22:10, 6 January 2022 (UTC) | ||
Reload key
|
Refactor | Planning | Low |
|
|||
Augments in /data
|
Refactor | New | Low |
|
01:37, 31 May 2021 (UTC) | ||
Data validation | Dev/database | Active | Medium |
Create
|
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
|
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 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 21:18, 18 January 2022 (UTC) update: User:Gigamicro implemented a new 21:35, 19 January 2022 (UTC) update: |
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 |
|
06:05, 3 October 2021 (UTC) | 17:47, 2 November 2021 (UTC) |
Railjack Weapons | Dev/Edit/Database | Long-term support | Medium | User:Cephalon Scientia |
02:32, 6 September 2021 (UTC) update: Added most Railjack turrets and ordnances to 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 |
06:18, 10 August 2021 (UTC) | 03:10, 16 August 2021 (UTC) |
Clean up | Clean up | Completed | Medium | User:Cephalon Scientia |
|
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.:
|
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
20:53, 29 July 2021 (UTC) update: We now use |
01:37, 31 May 2021 (UTC) | 20:53, 29 July 2021 (UTC) |
getAttackValue()
|
Refactor | Completed | Medium | User:Cephalon Scientia |
Refactor
20:53, 29 July 2021 (UTC) update: |
01:37, 31 May 2021 (UTC) | 20:53, 29 July 2021 (UTC) |
p.getRivenDispositionTable()
|
Refactor | Completed | Low |
|
3:42, 21 July 2021 (UTC) | 4:49, 21 July 2021 (UTC) | |
p.buildDamageTypeTable(frame)
|
Refactor | Completed | Low |
|
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.
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
- https://warframe.fandom.com/fr/wiki/Module:Weapons
- https://warframe.fandom.com/es/wiki/M%C3%B3dulo:Weapons
- https://warframe.fandom.com/it/wiki/Modulo:Weapons
- https://warframe.fandom.com/de/wiki/Modul:Weapons
- https://warframe.fandom.com/pt-br/wiki/M%C3%B3dulo:Weapons
- https://warframe.fandom.com/zh-tw/wiki/%E6%A8%A1%E7%B5%84:Weapons
- https://warframe.huijiwiki.com/wiki/%E6%A8%A1%E5%9D%97:Weapons#
Documentation
Package items
weapons._attackLoop(Weapon)
(function)- Loops through all possible attacks that a weapon may have.
- Parameter:
Weapon
Weapon entry as seen in/data
(table) - Returns:
- An iterator function that returns the key-value pair of next attack entry (function)
- Original weapon entry passed into function (table)
weapons.buildDamageTypeTable(frame)
(function)- Builds a table that lists out all weapons with a certain damage type and the percentage that it makes up of their base damage of the attack specified in their tooltip on the wiki.
- Parameter:
frame
Frame object (table) - Returns: Wikitext of resultant wikitable (string)
weapons.getCompTableGuns()
(function)- Builds comparison table of gun stats as seen on Weapon Comparison.
weapons.getCompTableConclaveGuns()
(function)- Builds comparison table of gun Conclave stats as seen on Weapon Comparison/Conclave.
weapons.getCompTableMelees()
(function)- Builds comparison table of melee stats as seen on Weapon Comparison.
weapons.getCompTableConclaveMelees()
(function)- Builds comparison table of melee conclave stats as seen on Weapon Comparison/Conclave.
weapons.getCompTableArchMelees()
(function)- Builds comparison table of arch-melee stats as seen on Weapon Comparison.
weapons.getCompTableSpeedGuns(frame)
(function)- Builds comparison table of projectile flight speeds as seen on Projectile Speed.
- Parameter:
frame
Frame object (table) - Returns: Wikitext of resultant wikitable (string)
weapons.getCompTableSpeedMelees()
(function)- Builds comparison table of glaive melees' projectile flight speeds as seen on Projectile Speed.
- Created with Docbunto
See Also
- Weapons/Conclave/data
- Weapons/Conclave/data/doc
- Weapons/Conclave/data/melee
- Weapons/Conclave/data/primary
- Weapons/Conclave/data/secondary
- Weapons/characteristics
- Weapons/characteristics/doc
- Weapons/compare
- Weapons/compare/doc
- Weapons/comptable
- Weapons/comptable/doc
- Weapons/csv
- Weapons/csv/doc
- Weapons/data
- Weapons/data/archwing
- Weapons/data/archwing/doc
- Weapons/data/companion
- Weapons/data/companion/doc
- Weapons/data/credits
- Weapons/data/credits/doc
- Weapons/data/dev
- Weapons/data/doc
- Weapons/data/melee
- Weapons/data/melee/doc
- Weapons/data/misc
- Weapons/data/misc/doc
- Weapons/data/modular
- Weapons/data/modular/doc
- Weapons/data/primary
- Weapons/data/primary/doc
- Weapons/data/railjack
- Weapons/data/railjack/doc
- Weapons/data/secondary
- Weapons/data/secondary/doc
- Weapons/data/validate
- Weapons/data/validate/doc
- Weapons/dev
- Weapons/dev/doc
- Weapons/doc
- Weapons/infobox
- Weapons/infobox/doc
- Weapons/nav
- Weapons/nav/doc
- Weapons/ppdata
- Weapons/ppdata/doc
- Weapons/ppdata/seeder
- Weapons/ppdata/seeder/doc
- Weapons/preprocess
- Weapons/preprocess/doc
- Weapons/testcases
- Weapons/testcases/doc
Code
--- '''Weapons''' contains all of [[WARFRAME]]'s non-modular [[Weapons|weapon]] data.<br />
--
-- @module weapons
-- @alias p
-- @author [[User:Falterfire|Falterfire]]
-- @attribution [[User:Flaicher|Flaicher]]
-- @attribution [[User:FINNER|FINNER]]
-- @attribution [[User:Cephalon Scientia|Cephalon Scientia]]
-- @attribution [[User:Gigamicro|Gigamicro]]
-- @attribution [[User:Synthtech|Synthtech]]
-- @attribution [[User:Calenhed|Calenhed]]
-- @image
-- @require [[Module:Icon]]
-- @require [[Module:Math]]
-- @require [[Module:String]]
-- @require [[Module:Table]]
-- @require [[Module:Mods/data]]
-- @require [[Module:Modular/data]]
-- @require [[Module:Stances/data]]
-- @require [[Module:Tooltips]]
-- @require [[Module:Version]]
-- @require [[Module:Weapons/data]]
-- @require [[Module:Weapons/Conclave/data]]
-- @release stable
-- <nowiki>
-- TODO: Remove Icon._Proc and replace with a function call to M:DamageTypes
local p = {}
local ConclaveData = mw.loadData [[Module:Weapons/Conclave/data]]
local ModData = mw.loadData [[Module:Mods/data]]
local ModularData = mw.loadData [[Module:Modular/data]]
local StanceData = mw.loadData [[Module:Stances/data]]
local WeaponData = mw.loadData [[Module:Weapons/data]]
local Icon = require [[Module:Icon]]
local Math = require [[Module:Math]]
local String = require [[Module:String]]
local Tooltip = require [[Module:Tooltips]]
local Version = require [[Module:Version]]
local Table = require [[Module:Table]]
local Polarity = require [[Module:Polarity]]
-- TODO: Could use M:DamageTypes instead of this table?
local Elements = {
"Impact", "Puncture", "Slash", "Heat", "Cold", "Toxin",
"Electricity", "Blast", "Corrosive", "Radiation", "Magnetic", "Gas",
"Viral", "Void", "True"
}
-- TODO: Could use M:DamageTypes instead of this table?
local Physical = {"Impact", "Puncture", "Slash"}
local VariantList = {
"Prime", "Prisma", "Wraith", "Vandal", "Vaykor", "Synoid",
"Telos", "Secura", "Sancti", "Rakta", "Mara", "MK1", "Kuva"
}
--If Type is not nil, get damage weapon deals of that type
--If it deals no damage of that type, return 0 instead of nil
--It Type is nil, return total damage
local function GetDamage(Attack, Type, withMultishot)
if (Attack == nil or Attack.Damage == nil) then return 0 end
local pCount = 1
if (withMultishot and Attack.Multishot ~= nil) then pCount = Attack.Multishot end
if (Type == nil) then
local total = 0
for i, d in Table.skpairs(Attack.Damage) do
total = total + d
end
return total * (withMultishot and pCount or 1)
end
if (Type == "Physical") then
local Impact = Attack.Damage["Impact"] ~= nil and Attack.Damage["Impact"] or 0
local Puncture = Attack.Damage["Puncture"] ~= nil and Attack.Damage["Puncture"] or 0
local Slash = Attack.Damage["Slash"] ~= nil and Attack.Damage["Slash"] or 0
return (Impact + Puncture + Slash) * (withMultishot and pCount or 1)
elseif (Type == "Element") then
for dType, dmg in Table.skpairs(Attack.Damage) do
if (not Table.contains(Physical, dType) or dmg <= 0) then
return dmg * (withMultishot and pCount or 1)
end
end
return 0
elseif (Attack.Damage[Type] == nil) then
return 0
end
return Attack.Damage[Type] * (withMultishot and pCount or 1)
end
local function getDamageBias(attackEntry)
if (attackEntry.Damage ~= nil and Table.size(attackEntry.Damage) > 0) then
local totalDmg = 0
local bestDmg = 0
local bestElement = nil
local elemCount = 0
for damageType, dmg in pairs(attackEntry.Damage) do
if (dmg > bestDmg) then
bestDmg = dmg
bestElement = damageType
end
totalDmg = totalDmg + dmg
if (dmg > 0) then
elemCount = elemCount + 1
end
end
-- Make sure there are two damage instances that are above zero
-- Exception for physical damage types
if (elemCount > 0) then
return (bestDmg / totalDmg), bestElement
end
error('getDamageBias(Attack): '..
'Damage key in Attack entry has no key-value pairs'..mw.dumpObject(attackEntry))
end
error('getDamageBias(Attack): Attack entry has no Damage key')
end
--If the attack has at least two damage types,
-- Returns something like "58.3% Slash"
--If it doesn't, returns nil
local function getDamageBiasString(Attack)
local bestPercent, bestElement = getDamageBias(Attack)
local result = Math.percentage(bestPercent)
return result..' '..Icon._Proc(bestElement)
end
-- For now using what the tooltips is displaying for DPS calculations
-- I know it is not accurate b/c the other columns in comparison table may use a different Attack table by default
-- Calculates the derived damage stats commonly used in comparing gun performance.
-- calculateGunDerivedDamage
-- {table} Weapon Weapon table data as seen in M:Weapons/data
-- {number} Returns five number stats in a tuple:
-- * Total damage: final damage when accounting multishot; same as arsenal display
-- * Average shot: damage per single input
-- * Average burst DPS: damage per second without reloading
-- * Average sustained DPS: damage per second w/ reloading
-- * Average lifetime damage: total damage that can be dealt in a single magazine + reserve ammo w/o picking up ammo drops
local function calculateGunDerivedDamage(Weapon)
local TooltipAttack = Weapon[Weapon['TooltipAttackDisplay'] or 'Attack1'] or {}
local totalDamage = 0
for damageType, value in pairs(TooltipAttack['Damage'] or {}) do
if (damageType == 'MinProgenitorBonus') then
-- For a more competitive comparison, assume that Lich weapons have
-- max base damage bonus (+60% of a Progenitor Warframe's element);
-- The value stored in 'MinProgenitorBonus' is based on the minimum +25% bonus
totalDamage = totalDamage + (value * 0.6/0.25)
else
totalDamage = totalDamage + value
end
end
totalDamage = totalDamage * (TooltipAttack['Multishot'] or 1)
local critChance = TooltipAttack['CritChance']
local critMultiplier = TooltipAttack['CritMultiplier']
-- If an attack uses the charge trigger, use that instead since
-- theoretically players want to charge an attack for more damage/DPS
local fireRate = TooltipAttack['ChargeTime'] and (1 / TooltipAttack['ChargeTime']) or
TooltipAttack['FireRate']
local reloadTime = Weapon['Reload']
local magazine = Weapon['Magazine']
local maxAmmo = Weapon['MaxAmmo']
-- TODO: If we are defining average shot as average damage dealt per a single attack input
-- than this is is inaccurate for burst-fire attacks (being picky on wording, but it is
-- for more accurate calculations)
local avgShot = totalDamage * (1 + critChance * (critMultiplier - 1))
-- Extra one needed in calculation to account for initial filled mag
-- If there is no reserve ammo, that means that weapon can deal an infinite amount of damage theoretically
local avgLifetimeDmg = maxAmmo < math.huge and avgShot * (magazine / (TooltipAttack['AmmoCost'] or 1)) * (1 + (maxAmmo / magazine)) or math.huge
-- Need to ignore the first shot of guns since it is instantaneous and is
-- not affected by fire rate (which causes the delay between shots)
local numShotPerMag = magazine / (TooltipAttack['AmmoCost'] or 1)
local avgBurst = avgShot * fireRate
local avgSustained
-- If weapons have a magazine of 1, effective fire rate will be the inverse of reload time
if (magazine == 1) then
-- For bows, if attack is a charged attack, account for the charge time which
-- should decrease DPS
avgSustained = avgShot * (1 / (reloadTime + (TooltipAttack['ChargeTime'] or 0)))
else
avgSustained = (avgBurst * numShotPerMag) / ((numShotPerMag - 1) + (fireRate * reloadTime))
end
return totalDamage, avgShot, avgBurst, avgSustained, avgLifetimeDmg
end
local ATTACK_KEY_MAP = {
Impact = function(attackEntry) return attackEntry['Damage']['Impact'] or 0 end,
Puncture = function(attackEntry) return attackEntry['Damage']['Puncture'] or 0 end,
Slash = function(attackEntry) return attackEntry['Damage']['Slash'] or 0 end,
AttackName = function(attackEntry) return attackEntry['AttackName'] or 'Normal' end,
AmmoCost = function(attackEntry) return attackEntry['AmmoCost'] or 1 end,
BurstCount = function(attackEntry) return attackEntry['BurstCount'] end,
BurstFireRate = function(attackEntry) return attackEntry['BurstFireRate'] end,
CompTableFireRate = function(attackEntry)
-- if (attackEntry['FireRate'] ~= nil and attackEntry['ChargeTime'] ~= nil) then
-- return attackEntry['FireRate'] > (1 / attackEntry['ChargeTime']) and attackEntry['FireRate'] or
-- Math.round(1 / attackEntry['ChargeTime'], 0.01)
if (attackEntry['ChargeTime'] ~= nil) then
return 1 / attackEntry['ChargeTime']
end
return attackEntry['FireRate']
end,
Damage = function(attackEntry) return GetDamage(attackEntry, nil, false) end,
DamageBias = function(attackEntry) return getDamageBiasString(attackEntry) end,
ChargeTime = function(attackEntry) return attackEntry['ChargeTime'] end,
CritChance = function(attackEntry) return Math.percentage(attackEntry['CritChance']) end,
-- Space added between value and 'x' so that wikitables can automatically recognize
-- that these can be sorted as numeric values
CritMultiplier = function(attackEntry) return (attackEntry['CritMultiplier'])..'x' end,
Falloff = function(attackEntry) return attackEntry['Falloff']['Reduction'] end,
FireRate = function(attackEntry) return attackEntry['FireRate'] end,
HeadshotMultiplier = function(attackEntry)
return attackEntry['HeadshotMultiplier'] and (attackEntry['HeadshotMultiplier'])..' x' or '1 x'
end,
Multishot = function(attackEntry) return attackEntry['Multishot'] or 1 end,
PelletName = function(attackEntry) return attackEntry['PelletName'] end,
PunchThrough = function(attackEntry) return attackEntry['PunchThrough'] or 0 end,
Radius = function(attackEntry) return attackEntry['Radius'] end,
ShotSpeed = function(attackEntry) return attackEntry['ShotSpeed'] end,
ShotType = function(attackEntry) return attackEntry['ShotType'] end,
StatusChance = function(attackEntry) return Math.percentage(attackEntry['StatusChance']) end,
}
local SHARED_KEY_MAP = {
Class = function(weaponEntry) return weaponEntry['Class'] end,
Disposition = function(weaponEntry)
return weaponEntry['Disposition'] ~= nil and weaponEntry['Disposition'] or 'N/A'
end,
Family = function(weaponEntry) return weaponEntry['Family'] end,
Introduced = function(weaponEntry)
return weaponEntry['Introduced'] and Version._getVersionDate(weaponEntry['Introduced']) or 'N/A'
end,
Link = function(weaponEntry) return '[['..weaponEntry['Link']..']]' end,
Mastery = function(weaponEntry) return weaponEntry['Mastery'] or 'N/A' end,
Name = function(weaponEntry) return '[['..weaponEntry['Name']..']]' end,
NameLink = function(weaponEntry) return '[['..weaponEntry['Link']..'|'..weaponEntry['Name']..']]' end,
Polarities = function(weaponEntry) return Polarity._pols(weaponEntry['Polarities']) end,
Traits = function(weaponEntry)
local traitString = {}
for _, trait in ipairs(weaponEntry['Traits']) do
table.insert(traitString, trait)
end
return table.concat(traitString, ', ')
end,
Type = function(weaponEntry) return weaponEntry['Type'] end
}
local GUN_KEY_MAP = {
Accuracy = function(weaponEntry)
if (weaponEntry['Accuracy'] ~= nil) then return weaponEntry['Accuracy'] end
return weaponEntry[weaponEntry['TooltipAttackDisplay'] or 'Attack1']['Accuracy']
end,
AmmoType = function(weaponEntry)
if (weaponEntry['AmmoType'] ~= nil) then
return weaponEntry['AmmoType']
elseif(weaponEntry['Type'] == 'Secondary') then
return 'Pistol'
elseif(weaponEntry['Type'] == 'Primary') then
local class = Weapon['Class']
if (class == 'Rifle') then
return 'Rifle'
elseif (class == 'Shotgun') then
return 'Shotgun'
elseif (class == 'Bow') then
return 'Bow'
elseif (class == 'Sniper Rifle' or class == 'Launcher') then
return "Sniper"
end
end
return ''
end,
AvgProcCount = function(weaponEntry)
local attackEntry = weaponEntry[weaponEntry['TooltipAttackDisplay'] or 'Attack1']
local statusChance = attackEntry['StatusChance'] or 0
local multishot = attackEntry['Multishot'] or 1
local numForcedProcs = attackEntry['ForcedProcs'] and Table.size(attackEntry['ForcedProcs']) or 0
return (statusChance + numForcedProcs) * multishot
end,
AvgProcPerSec = function(weaponEntry)
local attackEntry = weaponEntry[weaponEntry['TooltipAttackDisplay'] or 'Attack1']
local statusChance = attackEntry['StatusChance'] or 0
local multishot = attackEntry['Multishot'] or 1
local numForcedProcs = attackEntry['ForcedProcs'] and Table.size(attackEntry['ForcedProcs']) or 0
local fireRate = (attackEntry['ChargeTime'] ~= nil) and (1 / attackEntry['ChargeTime']) or attackEntry['FireRate']
return (statusChance + numForcedProcs) * multishot * fireRate
end,
AvgShotDmg = function(weaponEntry)
local totalDamage, avgShot = calculateGunDerivedDamage(weaponEntry)
return avgShot
end,
BurstDps = function(weaponEntry)
local totalDamage, avgShot, avgBurst = calculateGunDerivedDamage(weaponEntry)
return avgBurst
end,
ExilusPolarity = function(weaponEntry) return Polarity._polarity(weaponEntry['ExilusPolarity']) end,
IsSilent = function(weaponEntry) return weaponEntry['IsSilent'] end,
Magazine = function(weaponEntry) return weaponEntry['Magazine'] end,
MaxAmmo = function(weaponEntry) return weaponEntry['MaxAmmo'] end,
Range = function(weaponEntry) return weaponEntry['Range'] end,
Reload = function(weaponEntry) return weaponEntry['Reload'] end,
ReloadDelay = function(weaponEntry) return weaponEntry['ReloadDelay'] or 0 end,
ReloadDelayEmpty = function(weaponEntry) return weaponEntry['ReloadDelayEmpty'] or 0 end,
ReloadStyle = function(weaponEntry) return weaponEntry['ReloadStyle'] end,
Spool = function(weaponEntry) return weaponEntry['Spool'] or 0 end,
SustainedDps = function(weaponEntry)
local totalDamage, avgShot, avgBurst, avgSustained = calculateGunDerivedDamage(weaponEntry)
return avgSustained
end,
Trigger = function(weaponEntry) return weaponEntry['Trigger'] end
}
local MELEE_KEY_MAP = {
BlockAngle = function(weaponEntry) return weaponEntry['BlockAngle'] or 0 end,
ComboDur = function(weaponEntry) return weaponEntry['ComboDur'] or 0 end,
FollowThrough = function(weaponEntry) return weaponEntry['FollowThrough'] or 0 end,
HeavyAttack = function(weaponEntry) return weaponEntry['HeavyAttack'] or 0 end,
HeavyElement = function(weaponEntry) return weaponEntry['HeavyElement'] end,
HeavySlamAttack = function(weaponEntry) return weaponEntry['HeavySlamAttack'] end,
HeavySlamElement = function(weaponEntry) return weaponEntry['HeavySlamElement'] end,
HeavyRadialDmg = function(weaponEntry) return weaponEntry['HeavyRadialDmg'] or 0 end,
HeavyRadialElement = function(weaponEntry) return weaponEntry['HeavyRadialElement'] end,
HeavySlamRadius = function(weaponEntry) return weaponEntry['HeavySlamRadius'] or 0 end,
MeleeRange = function(weaponEntry) return weaponEntry['MeleeRange'] or 0 end,
SlamAttack = function(weaponEntry) return weaponEntry['SlamAttack'] or 0 end,
SlamElement = function(weaponEntry) return weaponEntry['SlamElement'] end,
SlamRadialDmg = function(weaponEntry) return weaponEntry['SlamRadialDmg'] or 0 end,
SlamRadialElement = function(weaponEntry) return weaponEntry['SlamRadialElement'] end,
SlamRadialProc = function(weaponEntry) return weaponEntry['SlamRadialProc'] end,
SlamRadius = function(weaponEntry) return weaponEntry['SlamRadius'] or 0 end,
SlideAttack = function(weaponEntry) return weaponEntry['SlideAttack'] or 0 end,
SlideElement = function(weaponEntry) return weaponEntry['SlideElement'] end,
Stances = function(weaponEntry) return getWeaponStanceList(weaponEntry) end,
StancePolarity = function(weaponEntry)
return weaponEntry['StancePolarity'] ~= nil and
Polarity._polarity(weaponEntry['StancePolarity']) or 'N/A'
end,
WindUp = function(weaponEntry) return weaponEntry['WindUp'] or 0 end
}
-- For mapping weapon traits or types to a category link
local categoryMap = {
Primary = '[[Category:Primary Weapons]]',
Secondary = '[[Category:Secondary Weapons]]',
Melee = '[[Category:Melee Weapons]]',
['Arch-Melee'] = '[[Category:Archwing Melee]]',
['Arch-Gun'] = '[[Category:Archwing Gun]]',
['Arch-Gun (Atmosphere)'] = '[[Category:Archwing Gun]]',
Kitgun = '[[Category:Kitgun]]',
Zaw = '[[Category:Zaw]]',
['Railjack Armament'] = '[[Category:Railjack]]',
Gear = '[[Category:Gear]]',
Rifle = '[[Category:Assault Rifle]]',
['Sniper Rifle'] = '[[Category:Sniper Rifle]]',
Shotgun = '[[Category:Shotgun]]',
Pistol = '[[Category:Pistol]]',
['Dual Pistols'] = '[[Category:Dual Pistols]]',
Bow = '[[Category:Bow]]',
Launcher = '[[Category:Launcher]]',
['Arm-Cannon'] = '[[Category:Arm-Cannon]]',
['Speargun'] = '[[Category:Speargun]]',
Thrown = '[[Category:Thrown]]',
['Shotgun Sidearm'] = '[[Category:Shotgun Sidearm]]',
Prime = '[[Category:Prime Weapons]]',
['Never Vaulted'] = '[[Category:Never Vaulted]]',
Vaulted = '[[Category:Vaulted]]',
Wraith = '[[Category:Wraith]]',
Vandal = '[[Category:Vandal]]',
-- Maybe replace with 'Kuva' category? Though technically Broken Scepter is a "Kuva" weapon which
-- is why this distinction is made (in the past, editors mislabeled this trait)
['Kuva Lich'] = '[[Category:Kuva Lich]]',
Prisma = '[[Category:Prisma]]',
Grineer = '[[Category:Grineer Weapons]]',
Corpus = '[[Category:Corpus Weapons]]',
Infested = '[[Category:Infested Weapons]]',
Tenno = '[[Category:Tenno Weapons]]',
Sentient = '[[Category:Sentient Weapons]]',
Entrati = '[[Category:Entrati Weapons]]',
Baro = '[[Category:Baro Ki\'Teer Offering]]',
Syndicate = '[[Category:Syndicate Offerings]]',
['Invasion Reward'] = '[[Category:Invasion Reward]]',
['Alt Fire'] = '[[Category:Weapons with Alt Fire]]',
['AoE'] = '[[Category:Weapons with Area of Effect]][[Category:Self Interrupt Weapons]]',
Active = '[[Category:Active]]',
Auto = '[[Category:Automatic]]',
['Auto-Spool'] = '[[Category:Automatic]]',
Burst = '[[Category:Burst Fire]]',
['Auto-Burst'] = '[[Category:Burst Fire]]',
['Auto Charge'] = '[[Category:Charge]]',
Charge = '[[Category:Charge]]',
Duplex = '[[Category:Duplex Fire]]',
['Semi-Auto'] = '[[Category:Semi-Automatic]]',
Held = '[[Category:Continuous Weapons]]'
}
-- Builds a CSV table of all WARFRAME's guns with the exception of Kitguns.
-- function p.csvGunComparisonTable
-- param {table} frame Frame object
-- returns {string} Preformatted text of CSV text
function p.csvGunComparisonTable(frame)
-- Weapon types to show in resultant table
local weaponTypesFilter = { 'Primary', 'Secondary', 'Robotic', 'Arch-Gun', 'Arch-Gun (Atmosphere)', 'Amp' }
local tableEntryTemplate = {} -- Would look like '%s,%s,%s'
local tableHeader = {
'Name',
'Trigger',
'AttackName',
'Impact',
'Puncture',
'Slash',
'Cold',
'Electricity',
'Heat',
'Toxin',
'Blast',
'Corrosive',
'Gas',
'Magnetic',
'Radiation',
'Viral',
'Void',
'MinProgenitorBonus',
'Damage',
'TotalDmg',
'CritChance',
'CritMultiplier',
'AvgShotDmg',
'BurstDps',
'SustainedDps',
'LifetimeDmg',
'StatusChance',
'AvgProcCount',
'AvgProcPerSec',
'Multishot',
'FireRate',
'Disposition',
'Mastery',
'Magazine',
'MaxAmmo',
'Reload',
'ShotType',
'PunchThrough',
'Accuracy',
'Introduced',
'IntroducedDate',
'Type',
'Class',
'AmmoType'
}
for i, _ in ipairs(tableHeader) do table.insert(tableEntryTemplate, '%s,') end
tableEntryTemplate[#tableEntryTemplate] = '%s' -- Last column
tableEntryTemplate = table.concat(tableEntryTemplate)
local csvResult = { '<pre>' }
table.insert(csvResult, string.format(tableEntryTemplate, unpack(tableHeader)))
for weaponName, weaponData in Table.skpairs(WeaponData['Weapons']) do
if (Table.contains(weaponTypesFilter, weaponData['Type'])) then
-- Going through all the possible Attack keys and adding them to CSV
-- (TODO: consolidate these keys into one Attack table with key-value pairs)
for i = 1, 9, 1 do
if (weaponData['Attack'..i] ~= nil) then
local totalDamage, avgShot, avgBurst, avgSustained, avgLifetimeDmg = calculateGunDerivedDamage(weaponData)
local weaponAttack = weaponData['Attack'..i]
local baseDamage = 0
for damageType, damageValue in pairs(weaponAttack['Damage']) do
baseDamage = baseDamage + damageValue
end
local tableEntryValues = {
weaponName,
tostring(weaponData['Trigger']),
tostring(weaponAttack['AttackName'] or 'Normal'),
tostring(weaponAttack['Damage']['Impact'] or 0),
tostring(weaponAttack['Damage']['Puncture'] or 0),
tostring(weaponAttack['Damage']['Slash'] or 0),
tostring(weaponAttack['Damage']['Cold'] or 0),
tostring(weaponAttack['Damage']['Electricity'] or 0),
tostring(weaponAttack['Damage']['Heat'] or 0),
tostring(weaponAttack['Damage']['Toxin'] or 0),
tostring(weaponAttack['Damage']['Blast'] or 0),
tostring(weaponAttack['Damage']['Corrosive'] or 0),
tostring(weaponAttack['Damage']['Gas'] or 0),
tostring(weaponAttack['Damage']['Magnetic'] or 0),
tostring(weaponAttack['Damage']['Radiation'] or 0),
tostring(weaponAttack['Damage']['Viral'] or 0),
tostring(weaponAttack['Damage']['Void'] or 0),
tostring(weaponAttack['Damage']['MinProgenitorBonus'] or 0),
tostring(baseDamage),
tostring(totalDamage),
tostring(weaponAttack['CritChance']),
tostring(weaponAttack['CritMultiplier']),
tostring(avgShot),
tostring(avgBurst),
tostring(avgSustained),
tostring(avgLifetimeDmg),
tostring(weaponAttack['StatusChance']),
tostring((weaponAttack['StatusChance'] or 0) * (weaponAttack['Multishot'] or 1)),
tostring(GUN_KEY_MAP['AvgProcPerSec'](weaponData)),
tostring(weaponAttack['Multishot'] or 1),
-- If an attack uses the charge trigger, use that instead since
-- theoretically players want to charge an attack for more damage/DPS
tostring(weaponAttack['ChargeTime'] and (1 / weaponAttack['ChargeTime']) or weaponAttack['FireRate']),
tostring(weaponData['Disposition']),
tostring(weaponData['Mastery']),
tostring(weaponData['Magazine']),
tostring(weaponData['MaxAmmo']),
tostring(weaponData['Reload']),
tostring(weaponAttack['ShotType']),
tostring(weaponAttack['PunchThrough'] or 0),
tostring(weaponData['Accuracy']),
tostring(weaponData['Introduced'] and Version._getVersion(weaponData['Introduced'])['Name'] or nil),
tostring(weaponData['Introduced'] and Version._getVersionDate(weaponData['Introduced']) or nil),
tostring(weaponData['Type']),
tostring(weaponData['Class']),
tostring((weaponData['Type'] == 'Secondary' and 'Pistol') or weaponData['AmmoType'] or weaponData['Class'])
}
local tableEntry = string.format(tableEntryTemplate, unpack(tableEntryValues))
table.insert(csvResult, tableEntry)
end
end
end
end
table.insert(csvResult, '</pre>')
return table.concat(csvResult, '\n')
end
function p.isVariant(WeapName)
for i, var in pairs(VariantList) do
if (string.find(WeapName, var)) then
local baseName = string.gsub(WeapName, " ?"..var.." ?-?", "")
return true, var, baseName
end
end
return false, "Base", WeapName
end
function p._buildName(baseName, variant)
if (variant == nil or variant == 'Base' or variant == '') then
return baseName
elseif (baseName == 'Laser Rifle' and variant == 'Prime') then
return variant..' '..baseName -- Laser Rifle has a primed version called 'Prime Laser Rifle'
elseif (variant == 'Prime' or variant == 'Wraith' or variant == 'Vandal') then
return baseName..' '..variant
elseif (variant == 'MK1') then
return 'MK1-'..baseName
else
return variant..' '..baseName
end
end
--It's a bit of a mess, but this is for compressing a list with variants
--So if a list has Braton, Braton Prime, and MK1-Braton it'll list as
-- Braton (MK1, Prime)
function p._shortLinkList(Weapons, tooltip)
--First grabbing all the pieces and stashing them in a table
local baseNames = {}
for key, weap in Table.skpairs(Weapons) do
local isVar, varType, baseName = p.isVariant(weap.Name)
if (baseNames[baseName] == nil) then baseNames[baseName] = {} end
table.insert(baseNames[baseName], varType)
end
--Then the fun part: Pulling the table together
local result = {}
for baseName, variants in Table.skpairs(baseNames) do
--So first, check if "Base" is in the list
--Because if it isn't, list all variants separately
if (Table.contains(variants, "Base")) then
table.sort(variants)
--First, get the basic version
local thisRow = ""
if (tooltip) then
thisRow = "{{Weapon|"..baseName.."}}"
else
thisRow = "[["..baseName.."]]"
end
--then, if there are variants...
if (Table.size(variants) > 1) then
--List them in parentheses one at a time
thisRow = thisRow.." ("
local count = 0
for i, varName in pairs(variants) do
if (varName ~= "Base") then
if (count > 0) then thisRow = thisRow..", " end
if (tooltip) then
thisRow = thisRow.."{{Weapon|"..p._buildName(baseName, varName).."|"..varName.."}}"
else
thisRow = thisRow.."[["..p._buildName(baseName, varName).."|"..varName.."]]"
end
count = count + 1
end
end
thisRow = thisRow..")"
end
table.insert(result, thisRow)
else
for i, varName in pairs(variants) do
if (tooltip) then
table.insert(result, "{{Weapon|"..p._buildName(baseName, varName).."}}")
else
table.insert(result, "[["..p._buildName(baseName, varName).."]]")
end
end
end
end
return result
end
function p._getWeapon(weaponName)
local weapon = WeaponData["Weapons"][weaponName]
if weapon ~= nil then
return weapon
end
for key, Weapon in Table.skpairs(WeaponData["Weapons"]) do
if (Weapon.Name == WeapName) then return Weapon end
end
error('p._getWeapon(weaponName): "'..weaponName..'" does not exist in M:Weapons/data')
end
function p._getConclaveWeapon(weaponName)
local weapon = ConclaveData["Weapons"][weaponName]
if (weapon ~= nil and weapon.Name == weaponName) then
return weapon
end
for key, Weapon in Table.skpairs(ConclaveData["Weapons"]) do
if (Weapon.Name == WeapName or key == WeapName) then
return Weapon
end
end
error('p._getConclaveWeapon(weaponName): "'..weaponName..'" does not exist in M:Weapons/Conclave/data')
end
local function getAttack(Weapon, AttackType)
if (Weapon == nil or AttackType == nil) then return end
if (type(Weapon) == "string") then
Weapon = p._getWeapon(Weapon)
end
if (not AttackType) then
return Weapon.Attack1 or Weapon.Damage and Weapon
end
return Weapon[AttackType:find 'Attack' and AttackType or 'Attack'..AttackType]
end
-- TODO: Remove this boolean check function
local function hasAttack(Weapon, AttackType)
if (getAttack(Weapon, AttackType) ~= nil) then
return true
end
return nil
end
-- TODO: Remove this boolean check function
local function dontHasAttack(Weapon, AttackType)
if (getAttack(Weapon, AttackType) ~= nil) then
return nil
end
return true
end
--- Loops through all possible attacks that a weapon may have.
-- @function p._attackLoop
-- @param {table} Weapon Weapon entry as seen in <code>/data</code>
-- @returns {function} An iterator function that returns the key-value pair of next attack entry
-- @returns {table} Original weapon entry passed into function
function p._attackLoop(Weapon)
if (Weapon == nil) then
return function() return nil end
end
local function nextAttack(t, k)
if not k then return '1', t['Damage'] and t or t['Attack1'] end
local v
repeat
k, v = next(t,k)
until type(v) == 'table' and v['Damage']
return k, v
end
return nextAttack, Weapon
end
local function getAugments(Weapon)
local name = Weapon.Name ~= nil and Weapon.Name or Weapon
local augments = {}
for i, Augment in pairs(WeaponData["Augments"]) do
for j, WeapName in pairs(Augment.Weapons) do
if (WeapName == name) then
table.insert(augments, Augment)
end
end
end
return augments
end
-- Returns all melee weapons.
-- If weapType is not nil, only grab for a specific type
-- For example, if weapType is "Nikana", only pull Nikanas
-- Else grab all melee weapons
local function getMeleeWeapons(weapClass, PvP)
local weaps = {}
for i, weap in Table.skpairs(WeaponData["Weapons"]) do
if ((weap.Ignore == nil or not weap.Ignore) and weap.Type ~= nil and weap.Type == "Melee") then
local classMatch = (weapClass == nil or weap.Class == weapClass)
local pvpMatch = (PvP == nil or (PvP and weap.Conclave ~= nil and weap.Conclave))
if (classMatch and pvpMatch) then
table.insert(weaps, weap)
end
end
end
return weaps
end
--As above, but for Conclave stats
local function getConclaveMeleeWeapons(weapClass, PvP)
local weaps = {}
local weapClasses = {}
if (weapClass ~= nil) then
weapClasses = String.split(weapClass, ",")
end
for i, weap in Table.skpairs(ConclaveData["Weapons"]) do
if ((weap.Ignore == nil or not weap.Ignore) and weap.Type ~= nil and weap.Type == "Melee") then
local classMatch = (weapClass == nil or Table.contains(weapClasses, weap.Class))
local pvpMatch = (PvP == nil or (PvP and weap.Conclave ~= nil and weap.Conclave))
if (classMatch and pvpMatch) then
table.insert(weaps, weap)
end
end
end
return weaps
end
--Learning new things... Trying to allow sending in an arbitrary function
function p._getWeapons(validateFunction)
local weaponList = {}
for weaponName, weaponEntry in Table.skpairs(WeaponData["Weapons"]) do
if (not Table.contains(WeaponData["IgnoreInCount"], weaponName) and
(weaponEntry.Ignore == nil or not weaponEntry.Ignore) and validateFunction(weaponEntry)) then
table.insert(weaponList, weaponEntry)
end
end
return weaponList
end
--Same as getWeapons, but for Conclave data
function p.getConclaveWeapons(validateFunction)
local weaps = {}
for i, weap in Table.skpairs(ConclaveData["Weapons"]) do
if ((weap.Ignore == nil or not weap.Ignore) and validateFunction(weap)) then
table.insert(weaps, weap)
end
end
return weaps
end
local function getStances(weapType, pvpOnly)
local stanceTable = {}
for stanceName, Stance in pairs(StanceData) do
local typeMatch = (weapType == nil or weapType == Stance.WeaponType)
local pvpMatch = (pvpOnly ~= nil and pvpOnly) or (Stance.ConclaveOnly == nil or not Stance.ConclaveOnly)
if (typeMatch and pvpMatch) then
stanceTable[stanceName] = Stance
end
end
return stanceTable
end
--Returns the damage string as it's formatted in a comparison row
--So instead of '0', returns '-', and appends the icon for an element if necessary
local function GetDamageString(Attack, Type, ByPellet)
if (ByPellet == nil) then ByPellet = false end
if (Attack == nil or Attack.Damage == nil) then return "" end
local pCount = 1
if (ByPellet and Attack.Multishot ~= nil) then pCount = Attack.Multishot end
if (Type == nil) then
local damageString = nil
for damageType, val in pairs(Attack.Damage) do
-- When there are more than one element in weapon's attack distribution,
-- just return the base damage with no icon
if (damageString ~= nil) then
return Math.round(GetDamage(Attack, nil, ByPellet), 0.01)
end
damageString = Icon._Proc(damageType).." "..Math.round(val / pCount, 0.01)
end
-- Return damage string with icon for weapons that deal only a single element
return damageString
end
local thisVal = GetDamage(Attack, Type, ByPellet)
if (thisVal == 0) then
return ""
else
return Math.round(thisVal, 0.01)
end
end
local function getWeaponStanceList(Weapon)
if (Weapon == nil or Weapon.Type ~= "Melee") then return nil end
local stanceTable = {}
if Weapon.Class == "Exalted Weapon" then
stanceTable = getStances(Weapon.Name, Weapon.Conclave)
else
stanceTable = getStances(Weapon.Class, Weapon.Conclave)
end
local result = ""
for stanceName, Stance in pairs(stanceTable) do
if (string.len(result) > 0) then
result = result.."<br/>"
end
local polarity = ""
local link = ""
if Weapon.Class == "Exalted Weapon" then
link = "[["..ModData["Mods"][stanceName].Link.."|"..stanceName.."]]"
else
polarity = " ("..Polarity._polarity(ModData["Mods"][stanceName].Polarity)..")"
link = "[["..stanceName.."]]"
end
-- Adding tooltip
result = result..Tooltip.getIcon(stanceName, "Mods")
-- If this is a PvP Stance, add the disclaimer
if (Stance.ConclaveOnly ~= nil and Stance.ConclaveOnly) then
result = result.." (PvP Only)"
end
end
return result
end
-- Gets the value of a certain stat of a weapon
local function getValue(Weapon, keyName, attackName)
return SHARED_KEY_MAP[keyName] ~= nil and SHARED_KEY_MAP[keyName](Weapon) or
GUN_KEY_MAP[keyName] ~= nil and GUN_KEY_MAP[keyName](Weapon) or
MELEE_KEY_MAP[keyName] ~= nil and MELEE_KEY_MAP[keyName](Weapon) or
ATTACK_KEY_MAP[keyName] ~= nil and ATTACK_KEY_MAP[keyName](Weapon[attackName or Weapon['TooltipAttackDisplay'] or 'Attack1']) or
error('getValue(Weapon, keyName, attackName): Cannot get keyName "'..keyName..'" in '..mw.dumpObject(Weapon))
end
function p.getStanceWeaponList(frame)
local stanceName = frame.args ~= nil and frame.args[1] or frame
local Stance = StanceData[stanceName]
assert(Stance ~= nil, "p.getStanceWeaponList(frame): "..stanceName.." not found")
local weaps = getMeleeWeapons(Stance.WeaponType, Stance.ConclaveOnly)
local result = ""
for i, weap in Table.skpairs(weaps) do
if (string.len(result) > 0) then
result = result.."\n"
end
if (Stance.ConclaveOnly) then
result = result.."*[[Conclave:"..weap.Name.."|"..weap.Name.."]]"
else
result = result.."*[["..weap.Name.."]]"
end
if (weap.StancePolarity == ModData["Mods"][stanceName].Polarity) then
result = result.." ✓"
end
end
return result
end
-- Used on Template:MeleeCategory
--Returns a list of stances for a weapon
--Adds the polarity for each stance
function p.getWeaponStanceList(frame)
local WeaponName = frame.args ~= nil and frame.args[1] or frame
local Weapon = p._getWeapon(WeaponName)
return getWeaponStanceList(Weapon)
end
local function getWeaponGallery(Weapons)
local result = "{| style=\"margin:auto;text-align:center;\""
local nameRow = ""
for i, Weapon in pairs(Weapons) do
local theImage = Weapon.Image ~= nil and Weapon.Image or "Panel.png"
if ((i - 1) % 4 == 0) then
result = result..nameRow.."\n|-"
nameRow = "\n|-"
end
result = result.."\n| style=\"width:165px\" |[[File:"..theImage.."|150px|link="..Weapon.Name.."]]"
nameRow = nameRow.."\n| style=\"vertical-align: text-top;\" |[["..Weapon.Name.."]]"
end
result = result..nameRow
result = result.."\n|}"
return result
end
-- Used on Template:MeleeCategory
function p.getMeleeWeaponGallery(frame)
local Type = frame.args ~= nil and frame.args[1] or frame
if (Type == nil) then
return ""
end
local WeapArray = getMeleeWeapons(Type)
local result = "=="..Type.." Weapons==\n"
result = result..getWeaponGallery(WeapArray)
return result
end
-- Used on [[Mastery Rank]]
function p.getWeaponCount(frame)
local Type = frame.args ~= nil and frame.args[1] or frame
local getFullList = frame.args ~= nil and frame.args[2]
local getAll = frame.args ~= nil and frame.args[3]
local count = 0
local fullList = ""
for i, val in Table.skpairs(WeaponData["Weapons"]) do
if (not Table.contains(WeaponData["IgnoreInCount"], i) and getAll ~= "true") or getAll == "true" then
if (Type == nil or Type == "") then
count = count + 1
if (getFullList ~= nil) then fullList = fullList.."\n# "..val.Name end
elseif (Type == "Warframe") then
if ((val.Type == "Primary" or val.Type == "Secondary" or val.Type == "Melee") and val.Class ~= "Exalted Weapon") then
count = count + 1
if (getFullList ~= nil) then fullList = fullList.."\n# "..val.Name end
end
elseif (Type == "Archwing") then
if ((val.Type == "Arch-Gun" or val.Type == "Arch-Melee") and val.Class ~= "Exalted Weapon") then
count = count + 1
if (getFullList ~= nil) then fullList = fullList.."\n# "..val.Name end
end
elseif (Type == "Rest") then
if (val.Type ~= "Arch-Gun" and val.Type ~= "Arch-Melee" and val.Type ~= "Primary" and
val.Type ~= "Secondary" and val.Type ~= "Melee" and val.Class ~= "Exalted Weapon") then
count = count + 1
if (getFullList ~= nil) then fullList = fullList.."\n# "..val.Name end
end
elseif (val.Type == Type and val.Class ~= "Exalted Weapon") then
count = count + 1
if (getFullList ~= nil) then fullList = fullList.."\n# "..val.Name end
end
end
end
if (getFullList ~= nil) then return fullList end
return count
end
local function getSecondaryCategory(weapon)
local class = getValue(weapon, "Class")
if (class == "Thrown") then
return "Thrown"
elseif (class == "Dual Shotguns" or class == "Shotgun Sidearm") then
return "Shotgun"
else
local trigger = getValue(weapon, "Trigger")
if (trigger == "Semi-Auto" or trigger == "Burst") then
return "Semi-Auto"
elseif (trigger == "Auto" or trigger == "Auto-Spool") then
return "Auto"
end
end
return "Other"
end
local function getPrimaryCategory(weapon)
local class = getValue(weapon, "Class")
if (class == "Shotgun") then
return "Shotgun"
elseif (class == "Bow") then
return "Bow"
elseif (class == "Sniper Rifle") then
return "Sniper"
elseif (class == "Rifle") then
local trigger = getValue(weapon, "Trigger")
if (trigger == "Semi-Auto" or trigger == "Burst") then
return "Semi-Auto"
elseif (trigger == "Auto" or trigger == "Auto-Spool") then
return "Auto"
end
end
return "Other"
end
-- Used on [[Polarity]] page
function p.getPolarityTable(frame)
local tableResult = { [[
{| style="width: 100%; border-collapse: collapse;" cellpadding="2" border="1"
|+ '''Weapons with Innate Polarities (ignoring Stance and Exilus slots)'''
! colspan="2" |Primaries
! colspan="2" |Secondaries
! colspan="2" |Melees
! colspan="2" |Arch-Guns
! colspan="2" |Arch-Melees]] }
local filterBy = function(weaponType)
return function(weaponEntry)
return (weaponEntry['Type'] == weaponType) and string.len(getValue(weaponEntry, "Polarities")) > 0
end
end
local meleeEntries = p._getWeapons(filterBy('Melee'))
local pistolEntries = p._getWeapons(filterBy('Secondary'))
local primaryEntries = p._getWeapons(filterBy('Primary'))
local archGunEntries = p._getWeapons(filterBy('Arch-Gun'))
local archMeleeEntries = p._getWeapons(filterBy('Arch-Melee'))
local meleeCount = Table.size(meleeEntries)
local pistolCount = Table.size(pistolEntries)
local primaryCount = Table.size(primaryEntries)
local archGunCount = Table.size(archGunEntries)
local archMeleeCount = Table.size(archMeleeEntries)
local maxLen = meleeCount
if (pistolCount > maxLen) then maxLen = pistolCount end
if (primaryCount > maxLen) then maxLen = primaryCount end
if (archGunCount > maxLen) then maxLen = archGunCount end
if (archMeleeCount > maxLen) then maxLen = archMeleeCount end
for i = 1, maxLen, 1 do
table.insert(tableResult, '|-')
if (i <= primaryCount) then
table.insert(tableResult, '| [['..primaryEntries[i]['Name']..']] ||'..Polarity._pols(primaryEntries[i]['Polarities']))
else
table.insert(tableResult, '| ||')
end
if (i <= pistolCount) then
table.insert(tableResult, '| [['..pistolEntries[i]['Name']..']] ||'..Polarity._pols(pistolEntries[i]['Polarities']))
else
table.insert(tableResult, '| ||')
end
if (i <= meleeCount) then
table.insert(tableResult, '| [['..meleeEntries[i]['Name']..']] ||'..Polarity._pols(meleeEntries[i]['Polarities']))
else
table.insert(tableResult, '| ||')
end
if (i <= archGunCount) then
table.insert(tableResult, '| [['..archGunEntries[i]['Name']..']] ||'..Polarity._pols(archGunEntries[i]['Polarities']))
else
table.insert(tableResult, '| ||')
end
if (i <= archMeleeCount) then
table.insert(tableResult, '| [['..archMeleeEntries[i]['Name']..']] ||'..Polarity._pols(archMeleeEntries[i]['Polarities']))
else
table.insert(tableResult, '| ||')
end
end
table.insert(tableResult, '|}')
return table.concat(tableResult, '\n')
end
local function buildCompareString(Val1, Val2, ValName, Digits, Addon, Words, Start)
if (Val1 == nil or Val2 == nil) then
return ""
end
local V1Str = Val1
local V2Str = Val2
if (Digits ~= nil) then
V1Str = Math.round(Val1, Digits)
V2Str = Math.round(Val2, Digits)
end
if (Addon ~= nil) then
V1Str = V1Str..Addon
V2Str = V2Str..Addon
end
local bigWord = Words ~= nil and Words[1] or "Higher"
local smallWord = Words ~= nil and Words[2] or "Lower"
local start = Start ~= nil and Start or "\n**"
if (Val1 > Val2) then
return start.." "..bigWord.." "..ValName.." ("..V1Str.." vs. "..V2Str..")"
elseif (Val2 > Val1) then
return start.." "..smallWord.." "..ValName.." ("..V1Str.." vs. "..V2Str..")"
else
return ""
end
end
local function buildDamageTypeComparisonString(Attack1, Attack2)
local result = ""
--ipairs iterates in the order given in Elements, so IPS is always first
for i, element in ipairs(Elements) do
local damage1 = Attack1.Damage[element]
local damage2 = Attack2.Damage[element]
if (damage1 ~= nil or damage2 ~= nil) then
if (damage1 == nil) then
damage1 = 0
end
if (damage2 == nil) then
damage2 = 0
end
result = result..buildCompareString(damage1, damage2, Icon._Proc(element, "text").." damage", 0.01, nil, {"Higher", "Lower"}, "\n***")
end
end
return result
end
-- TODO: Refactor function contents to reduce number of nil checks (hasAttack() functions)
-- TODO: The above TODO can be done in conjunction with changing all the "Attack1" and "Attack2" keys
-- to a singular "Attack" key with indexed table elements
local function buildGunComparisonString(Weapon1, Weapon2, Conclave)
local result = ""
if (Conclave) then
result = "* "..Weapon1.Name..", compared to [[Conclave:"..Weapon2.Name.."|"..Weapon2.Name.."]]:"
else
result = "* "..Weapon1.Name..", compared to [["..Weapon2.Name.."]]:"
end
if (hasAttack(Weapon1, "Attack1") and hasAttack(Weapon2, "Attack1")) then
local Att1 = getAttack(Weapon1, "Attack1")
local Att2 = getAttack(Weapon2, "Attack1")
local dmgString = ""
dmgString = dmgString..buildCompareString(GetDamage(Att1), GetDamage(Att2), "base damage", 0.01)
dmgString = dmgString..buildDamageTypeComparisonString(Att1, Att2)
if (string.len(dmgString) > 0 and GetDamage(Att1) == GetDamage(Att2)) then
dmgString = "\n**Equal base damage, but different composition:"..dmgString
end
dmgString = dmgString..buildCompareString(GetDamage(Att1) * (Att1.Multishot or 1), GetDamage(Att2) * (Att2.Multishot or 1), "total damage", 0.01)
result = result..dmgString
end
if (hasAttack(Weapon1, "Attack3")and hasAttack(Weapon2, "Attack3")) then
local Att1 = getAttack(Weapon1, "Attack3")
local Att2 = getAttack(Weapon2, "Attack3")
if (Att1.CritChance ~= nil and Att2.CritChance ~= nil) then
if (dontHasAttack(Weapon1, "Attack1")and dontHasAttack(Weapon2, "Attack1")) then
result = result..buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "[[critical chance]]", 0.01, "%")
end
if (hasAttack(Weapon1, "Attack1")and hasAttack(Weapon2, "Attack1")) then
result = result..buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "charged [[critical chance]]", 0.01, "%")
end
end
if (dontHasAttack(Weapon1, "Attack1")and dontHasAttack(Weapon2, "Attack1")) then
result = result..buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "[[critical multiplier]]", 0.01, "x")
end
if (hasAttack(Weapon1, "Attack1")and hasAttack(Weapon2, "Attack1")) then
result = result..buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "charged [[critical multiplier]]", 0.01, "x")
end
if (Att1.StatusChance ~= nil and Att2.StatusChance ~= nil) then
if (dontHasAttack(Weapon1, "Attack1")and dontHasAttack(Weapon2, "Attack1")) then
result = result..buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance *
100), "[[status chance]]", 0.01, "%")
end
if (hasAttack(Weapon1, "Attack1")and hasAttack(Weapon2, "Attack1")) then
result = result..buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance *
100), "charged [[status chance]]", 0.01, "%")
end
end
if (dontHasAttack(Weapon1, "Attack1")and dontHasAttack(Weapon2, "Attack1")) then
result = result..buildCompareString(GetDamage(Weapon1.Attack3), GetDamage(Weapon2.Attack3), "attack damage", 0.01)
end
if (hasAttack(Weapon1, "Attack1")and hasAttack(Weapon2, "Attack1")) then
result = result..buildCompareString(GetDamage(Weapon1.Attack3), GetDamage(Weapon2.Attack3), "charge attack damage", 0.01)
end
-- TODO: Find a better way of finding out if an attack is a charge attack; maybe add Trigger = "Charge" to all of those attacks?
-- result = result..buildCompareString(getValue(Weapon1, "ChargeTime", "Attack3"), getValue(Weapon2, "ChargeTime", "Attack3"), "charge time", 0.01, " s", {"Slower", "Faster"})
end
if (hasAttack(Weapon1, "Attack5") and hasAttack(Weapon2, "Attack5")) then
result = result..buildCompareString(GetDamage(Weapon1.Attack5), GetDamage(Weapon2.Attack5), "area attack damage", 0.01)
end
if (hasAttack(Weapon1, "Attack3") and hasAttack(Weapon2, "Attack3")) then
result = result..buildCompareString(GetDamage(Weapon1.Attack2), GetDamage(Weapon2.Attack2), "secondary attack damage", 0.01)
--test code to fix stradavar wrong comparison
local Att1 = getAttack(Weapon1, "Attack3")
local Att2 = getAttack(Weapon2, "Attack3")
if (Att1.CritChance ~= nil and Att2.CritChance ~= nil) then
result = result..buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "secondary mode [[critical chance]]", 0.01, "%")
end
result = result..buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "secondary mode [[critical multiplier]]", 0.01, "x")
if (Att1.StatusChance ~= nil and Att2.StatusChance ~= nil) then
result = result..buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance * 100), "secondary mode [[status chance]]", 0.01, "%")
end
result = result..buildCompareString(Att1.FireRate, Att2.FireRate, "secondary mode [[fire rate]]", 0.01, " round(s)/sec")
--end of test code
end
--test code to fix tiberon prime comparison
if Weapon1 == "Tiberon Prime" or Weapon2 == "Tiberon Prime" then
if ((hasAttack(Weapon1, "Attack1") and hasAttack(Weapon2, "Attack2") and dontHasAttack(Weapon1, "Attack2")) or (hasAttack(Weapon1, "Attack2") and hasAttack(Weapon2, "Attack1") and dontHasAttack(Weapon2, "Attack2")))then
local Att1 = getAttack(Weapon1, "Attack1")
local Att2 = getAttack(Weapon2, "Attack1")
if (Att1.CritChance ~= nil and Att2.CritChance ~= nil) then
result = result..buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "[[critical chance]]", 0.01, "%")
end
result = result..buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "[[critical multiplier]]", 0.01, "x")
if (Att1.StatusChance ~= nil and Att2.StatusChance ~= nil) then
result = result..buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance * 100), "[[status chance]]", 0.01, "%")
end
end
end
--end of test code to fix tiberon prime comparison
if (hasAttack(Weapon1, "Attack1") and hasAttack(Weapon2, "Attack1") ) then
local Att1 = getAttack(Weapon1, "Attack1")
local Att2 = getAttack(Weapon2, "Attack1")
if (Att1.CritChance ~= nil and Att2.CritChance ~= nil) then
local ifCCAdded = 0
if (dontHasAttack(Weapon1, "Attack3")and dontHasAttack(Weapon2, "Attack3") and dontHasAttack(Weapon1, "Attack2")and dontHasAttack(Weapon2, "Attack2")) then
result = result..buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "[[critical chance]]", 0.01, "%")
ifCCAdded = 1
end
if (hasAttack(Weapon1, "Attack3")and hasAttack(Weapon2, "Attack3")) then
result = result..buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "base [[critical chance]]", 0.01, "%")
ifCCAdded = 1
end
if (hasAttack(Weapon1, "Attack2")and hasAttack(Weapon2, "Attack2")) then
result = result..buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "primary [[critical chance]]", 0.01, "%")
ifCCAdded = 1
end
if (hasAttack(Weapon1, "Attack1")and hasAttack(Weapon2, "Attack1")) and ifCCAdded == 0 then
result = result..buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "[[critical chance]]", 0.01, "%")
end
end
local ifCDAdded = 0
if (dontHasAttack(Weapon1, "Attack3")and dontHasAttack(Weapon2, "Attack3") and dontHasAttack(Weapon1, "Attack2")and dontHasAttack(Weapon2, "Attack2")) then
result = result..buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "[[critical multiplier]]", 0.01, "x")
ifCDAdded = 1
end
if (hasAttack(Weapon1, "Attack3")and hasAttack(Weapon2, "Attack3")) then
result = result..buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "base [[critical multiplier]]", 0.01, "x")
ifCDAdded = 1
end
if (hasAttack(Weapon1, "Attack2")and hasAttack(Weapon2, "Attack2")) then
result = result..buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "primary [[critical multiplier]]", 0.01, "x")
ifCDAdded = 1
end
if (hasAttack(Weapon1, "Attack1")and hasAttack(Weapon2, "Attack1")) and ifCDAdded == 0 then
result = result..buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "[[critical multiplier]]", 0.01, "x")
end
if (Att1.StatusChance ~= nil and Att2.StatusChance ~= nil) then
local ifStatusAdded = 0
if (dontHasAttack(Weapon1, "Attack3")and dontHasAttack(Weapon2, "Attack3") and dontHasAttack(Weapon1, "Attack2")and dontHasAttack(Weapon2, "Attack2")) then
result = result..buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance * 100), "[[status chance]]", 0.01, "%")
ifStatusAdded = 1
end
if (hasAttack(Weapon1, "Attack3")and hasAttack(Weapon2, "Attack3")) then
result = result..buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance * 100), "base [[status chance]]", 0.01, "%")
ifStatusAdded = 1
end
if (hasAttack(Weapon1, "Attack2")and hasAttack(Weapon2, "Attack2")) then
result = result..buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance * 100), "primary [[status chance]]", 0.01, "%")
ifStatusAdded = 1
end
if (hasAttack(Weapon1, "Attack1")and hasAttack(Weapon2, "Attack1")) and ifStatusAdded == 0 then
result = result..buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance * 100), "[[status chance]]", 0.01, "%")
end
end
result = result..buildCompareString(Att1.FireRate, Att2.FireRate, "[[fire rate]]", 0.01, " round(s)/sec")
result = result..buildCompareString(Att1.Multishot or 1, Att2.Multishot or 1, "[[multishot]]", 1, " projectile(s)")
--Handling Damage Falloff
if (Att1.Falloff ~= nil and Att2.Falloff == nil) then
result = result.."\n** "..Att1.Falloff.StartRange.."m - "..Att1.Falloff.EndRange.."m damage falloff"
if (Att1.Falloff.Reduction ~= nil) then
result = result.." with up to "..(Att1.Falloff.Reduction * 100).."% damage reduction"
end
elseif (Att2.Falloff ~= nil and Att1.Falloff == nil) then
result = result.."\n** No "..Att2.Falloff.StartRange.."m - "..Att2.Falloff.EndRange.."m damage falloff"
if (Att2.Falloff.Reduction ~= nil) then
result = result.." with up to "..(Att2.Falloff.Reduction * 100).."% damage reduction"
end
elseif (Att1.Falloff ~= nil and Att2.Falloff ~= nil) then
result = result..buildCompareString(Att1.Falloff.StartRange, Att2.Falloff.StartRange, "range before damage falloff starts", 0.01, "m", {"Longer", "Shorter"})
result = result..buildCompareString(Att1.Falloff.EndRange, Att2.Falloff.EndRange, "range before damage falloff ends", 0.01, "m", {"Longer", "Shorter"})
if (Att1.Falloff.Reduction ~= nil and Att2.Falloff.Reduction ~= nil) then
result = result..buildCompareString(Att1.Falloff.Reduction * 100, Att2.Falloff.Reduction * 100, "max damage reduction due to falloff", 0.01, "%", {"Larger", "Smaller"})
end
end
end
result = result..buildCompareString(Weapon1.Magazine, Weapon2.Magazine, "magazine", 1, " round(s)", {"Larger", "Smaller"})
result = result..buildCompareString(Weapon1.MaxAmmo, Weapon2.MaxAmmo, "max ammo capacity", 1, " round(s)", {"Larger", "Smaller"})
--If this is a weapon that regenerates ammo, flip the comparison
if (Weapon1.ReloadStyle ~= nil and Weapon1.ReloadStyle == "Regenerate") then
result = result..buildCompareString(Weapon1.Reload, Weapon2.Reload, "[[Reload|reload time]]", 0.01, " round(s)/sec", {"Faster", "Slower"})
else
result = result..buildCompareString(Weapon1.Reload, Weapon2.Reload, "[[Reload|reload time]]", 0.01, " s", {"Slower", "Faster"})
end
result = result..buildCompareString(Weapon1.Spool, Weapon2.Spool, "spool-up", 1, " round(s)", {"Slower", "Faster"})
local Acc1 = getValue(Weapon1, "Accuracy")
local Acc2 = getValue(Weapon2, "Accuracy")
if (type(Acc1) == "number" and type(Acc2) == "number") then
result = result..buildCompareString(Acc1, Acc2, "accurate", 0.01, nil, {"More", "Less"})
end
--Handling Syndicate radial effects
if (Weapon1.SyndicateEffect ~= nil and Weapon2.SyndicateEffect == nil) then
result = result.."\n** Innate [["..Weapon1.SyndicateEffect.."]] effect"
elseif (Weapon2.SyndicateEffect ~= nil and Weapon1.SyndicateEffect == nil) then
result = result.."\n** Lack of an innate [["..Weapon2.SyndicateEffect.."]] effect"
elseif (Weapon1.SyndicateEffect ~= nil and Weapon2.SyndicateEffect ~= nil and Weapon1.SyndicateEffect ~= Weapon2.SyndicateEffect2) then
result = result.."\n** Different innate [[Syndicate Radial Effects|Syndicate Effect]]: [["..Weapon1.SyndicateEffect.."]] vs. [["..Weapon2.SyndicateEffect.."]]"
end
--Handling Polarities
local Pol1 = Weapon1.Polarities ~= nil and Weapon1.Polarities or {}
local Pol2 = Weapon2.Polarities ~= nil and Weapon2.Polarities or {}
local count1 = Table.size(Pol1)
local count2 = Table.size(Pol2)
local isDifferent = count1 ~= count2
if (not isDifferent and count1 > 0) then
table.sort(Pol1, function(x, y) return x < y end)
table.sort(Pol2, function(x, y) return x < y end)
for i, pol in pairs(Pol1) do
if (pol ~= Pol2[i]) then
isDifferent = true
end
end
end
if (isDifferent) then
result = result.."\n** Different polarities ("..getValue(Weapon1, "Polarities").." vs. "..getValue(Weapon2, "Polarities")..")"
end
result = result..buildCompareString(Weapon1.Mastery, Weapon2.Mastery, "[[Mastery Rank]] required", 1)
result = result..buildCompareString(Weapon1.Disposition, Weapon2.Disposition, "[[disposition]]", 0.01)
result = result..buildCompareString(getValue(Weapon1, "AvgShotDmg"), getValue(Weapon2, "AvgShotDmg"), "average damage per shot", 0.01)
result = result..buildCompareString(getValue(Weapon1, "BurstDps"), getValue(Weapon2, "BurstDps"), "burst DPS", 0.01)
result = result..buildCompareString(getValue(Weapon1, "SustainedDps"), getValue(Weapon2, "SustainedDps"), "sustained DPS", 0.01)
return result
end
-- TODO: use string.format() or tables when building resultant wikitext
local function buildMeleeComparisonString(Weapon1, Weapon2, Conclave)
local result = ""
if (Conclave) then
result = "* "..Weapon1.Name..", compared to [[Conclave:"..Weapon2.Name.."|"..Weapon2.Name.."]]:"
else
result = "* "..Weapon1.Name..", compared to [["..Weapon2.Name.."]]:"
end
local dmgString = ""
local Att1 = getAttack(Weapon1, "Attack1")
local Att2 = getAttack(Weapon2, "Attack1")
dmgString = dmgString..buildCompareString(GetDamage(Att1), GetDamage(Att2), "base damage", 0.01)
dmgString = dmgString..buildDamageTypeComparisonString(Att1, Att2)
if (string.len(dmgString) > 0 and GetDamage(Att1) == GetDamage(Att2)) then
dmgString = "\n**Equal base damage, but different composition:"..dmgString
end
result = result..dmgString
if (Att1.CritChance ~= nil and Att2.CritChance ~= nil) then
result = result..buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "[[critical chance]]", 0.01, "%")
end
result = result..buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "[[critical multiplier]]", 0.01, "x")
if (Att1.StatusChance ~= nil and Att2.StatusChance ~= nil) then
result = result..buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance * 100), "[[status chance]]", 0.01, "%")
end
result = result..buildCompareString(Att1.FireRate, Att2.FireRate, "[[attack speed]]", 0.01)
-- Quick fix to passing in an empty string for polarity when comparing with an arch-melee weapon
if (Weapon1.Type ~= "Arch-Melee" and Weapon2.Type ~= "Arch-Melee") then
result = result..buildCompareString(getValue(Weapon1, "ComboDur"), getValue(Weapon2, "ComboDur"), "Combo Duration", 1, " s")
result = result..buildCompareString(getValue(Weapon1, "BlockAngle"), getValue(Weapon2, "BlockAngle"), "Block Angle", 1, "°")
result = result..buildCompareString(Polarity._polarity(Weapon1.StancePolarity), Polarity._polarity(Weapon2.StancePolarity), "Stance Polarity", nil, nil, {"Different", "Different"})
end
result = result..buildCompareString(getValue(Weapon1, "MeleeRange"), getValue(Weapon2, "MeleeRange"), "Range", 0.01, " m")
--Handling Polarities
local Pol1 = Weapon1.Polarities ~= nil and Weapon1.Polarities or {}
local Pol2 = Weapon2.Polarities ~= nil and Weapon2.Polarities or {}
local count1 = Table.size(Pol1)
local count2 = Table.size(Pol2)
local isDifferent = count1 ~= count2
if (not isDifferent and count1 > 0) then
table.sort(Pol1, function(x, y) return x < y end)
table.sort(Pol2, function(x, y) return x < y end)
for i, pol in pairs(Pol1) do
if (pol ~= Pol2[i]) then
isDifferent = true
end
end
end
if (isDifferent) then
result = result.."\n** Different polarities ("..getValue(Weapon1, "Polarities").." vs. "..getValue(Weapon2, "Polarities")..")"
end
result = result..buildCompareString(Weapon1.Mastery, Weapon2.Mastery, "[[Mastery Rank]] required", 1)
result = result..buildCompareString(Weapon1.Disposition, Weapon2.Disposition, "[[disposition]]", 0.01)
return result
end
function p.buildComparison(frame)
local weaponName1 = frame.args[1]
local weaponName2 = frame.args[2]
assert(weaponName1 ~= '' and weaponName2 ~= '', 'p.buildComparison(frame): Must compare two weapons')
local Weapon1 = p._getWeapon(weaponName1)
local Weapon2 = p._getWeapon(weaponName2)
local comparisonString = ''
if (Weapon1.Type == 'Melee' or Weapon1.Type == 'Arch-Melee') then
comparisonString = buildMeleeComparisonString(Weapon1, Weapon2)
else
comparisonString = buildGunComparisonString(Weapon1, Weapon2)
end
return comparisonString..'[[Category:Automatic Comparison]]'
end
function p.buildConclaveComparison(frame)
local weaponName1 = frame.args[1]
local weaponName2 = frame.args[2]
assert(weaponName1 ~= '' and weaponName2 ~= '', 'p.buildConclaveComparison(frame): Must compare two weapons')
local Weapon1 = p._getConclaveWeapon(weaponName1)
local Weapon2 = p._getConclaveWeapon(weaponName2)
local comparisonString = ''
if (Weapon1.Type == 'Melee' or Weapon1.Type == 'Arch-Melee') then
comparisonString = buildMeleeComparisonString(Weapon1, Weapon2, true)
else
comparisonString = buildGunComparisonString(Weapon1, Weapon2, true)
end
return comparisonString..'[[Category:Automatic Comparison]]'
end
function p.buildAutoboxCategories(frame)
local WeapName = frame.args ~= nil and frame.args[1] or frame
local Weapon = p._getWeapon(WeapName)
local result = { "[[Category:Automatic Weapon Box]][[Category:Weapons]]" }
if (Weapon == nil or (Weapon.IgnoreCategories ~= nil and Weapon.IgnoreCategories)) then
return ""
end
table.insert(result, categoryMap[Weapon['Class']]..categoryMap[Weapon['Trigger']]..categoryMap[Weapon['Type']])
-- TODO: Archive this category b/c not sure how important it is or how often
-- it is used; augments are moved out of M:Weapons/data and should use M:Mods/data
local augments = getAugments(Weapon)
if (Table.size(augments) > 0) then
table.insert(result, "[[Category:Augmented Weapons]]")
end
-- Adding appropriate categories to page based on weapon's categorical traits
for _, trait in pairs(Weapon['Traits'] or {}) do
table.insert(result, categoryMap[trait])
end
local attack = nil
if (hasAttack(Weapon, "Attack1")) then
attack = getAttack(Weapon, "Attack1")
elseif (hasAttack(Weapon, "Attack3")) then
attack = getAttack(Weapon, "Attack3")
end
if (attack ~= nil) then
local bestPercent, bestElement = getDamageBias(attack)
if (bestElement == "Impact" or bestElement == "Puncture" or bestElement == "Slash") then
if (bestPercent > .38) then
table.insert(result, "[[Category:"..bestElement.." Damage Weapons]]")
else
table.insert(result, "[[Category:Balanced Physical Damage Weapons]]")
end
end
for key, value in Table.skpairs(attack.Damage) do
if (key ~= "Impact" and key ~= "Puncture" and key ~= "Slash") then
table.insert(result, "[[Category:"..key.." Damage Weapons]]")
end
end
end
return table.concat(result)
end
--- Builds a table that lists out all weapons with a certain damage type
-- and the percentage that it makes up of their base damage of the attack specified
-- in their tooltip on the wiki.
-- @function p.buildDamageTypeTable
-- @param {table} frame Frame object
-- @returns {string} Wikitext of resultant wikitable
function p.buildDamageTypeTable(frame)
local damageType = frame.args ~= nil and frame.args[1] or frame
local Weapons = {}
local WeapArray = p._getWeapons(function(weaponEntry)
-- Want to ignore Kitgun entries which have 0 as placeholder damage values
if (weaponEntry['Type'] == 'Kitgun' or weaponEntry['Type'] == 'Primary Kitgun') then
return false
end
local attackEntry = getAttack(weaponEntry, weaponEntry['TooltipAttackDisplay'] or 'Attack1')
assert(attackEntry ~= nil, 'p.buildDamageTypeTable(frame): "'..weaponEntry.Name..'" has no attack entry for "Attack1" or attack in "TooltipAttackDisplay"')
local dmg, element = getDamageBias(attackEntry)
return element == damageType
end)
local function weapLink(weap)
return weap.Link ~= nil and '[['..weap.Link..'|'..weap.Name..']]' or '[['..weap.Name..']]'
end
local procString = Icon._Proc(damageType, 'text')
local procShort = Icon._Proc(damageType)
local result = ''
local tHeader = string.format([[
{| class = "listtable sortable" style="margin:auto;"
|-
! Name !! Type !! Class !! %s !! %s%%
]], procString, procShort)
local tRows = {}
for i, Weapon in pairs(WeapArray) do
local thisRow = [[
|-
| %s || %s || %s || %s || data-sort-value="%s" | %s]]
local weaponLink = weapLink(Weapon)
assert(Weapon.Type ~= nil, 'p.buildDamageTypeTable(frame): "'..weaponLink..'" has a nil value for Type"')
local attack = Weapon['TooltipAttackDisplay'] or 'Attack1'
local damageTypeVal = getValue(Weapon, damageType, attack)
local damageBias = getValue(Weapon, 'DamageBias', attack)
thisRow = string.format(thisRow,
weaponLink,
Weapon.Type,
getValue(Weapon, 'Class'),
damageTypeVal,
string.match(damageBias, '(%d*%.?%d+)%%'),
damageBias
)
table.insert(tRows, thisRow)
end
result = tHeader..table.concat(tRows, '\n')..'\n|}'
return result
end
-- Used in [[Template:EquipmentUnlock]]
function p.getMasteryShortList(frame)
local WeaponType = frame.args[1]
local MasteryRank = tonumber(frame.args[2])
local weapArray = p._getWeapons(function(x)
if (x.Type ~= nil and x.Mastery ~= nil) then
return x.Type == WeaponType and x.Mastery == MasteryRank
else
return false
end
end)
local result = ''
local name = ''
local shortList = p._shortLinkList(weapArray, true)
for i, pair in Table.skpairs(shortList) do
if (string.len(result) > 0) then result = result..' • ' end
result = result..pair
end
return frame:preprocess(result)
end
-- Used in [[Riven Mods/Weapon Dispos]]
function p.getRivenDispositionTable(frame)
local weaponType = frame.args[1]
local ranges = { 1550, 1300, 1100, 899, 699, 499 }
local result = {'{| class="article-table" border="0" cellpadding="1" cellspacing="1" style="width: 100%" \n|-'}
for i = 1, 5 do
table.insert(result, '\n! scope="col" style="text-align:center;"|'..Icon._Dis(ranges[i]/1000))
end
table.insert(result, '\n|-')
for dispoRating = 5, 1, -1 do
table.insert(result, '\n| style="vertical-align:top; font-size:small" |')
-- Filtering out weapons that are between certain disposition ranges
local weapArray = p._getWeapons(
function(weaponEntry)
if (weaponEntry['Type'] ~= nil and weaponEntry['Disposition'] ~= nil) then
return (weaponType == 'All' or weaponEntry['Type'] == weaponType) and
math.min(math.floor(5 * (weaponEntry['Disposition'] - (weaponEntry['Disposition'] < 1 and 0.3 or 0.309))), 5) == dispoRating
else
return false
end
end)
-- Building a list of weapons with a particular disposition rating (e.g. 5 dots)
local weaponDispoList = {}
-- Want to iterate in descending order so highest disposition weapon is near the top of table
local descendingOrder = function(a, b)
-- a and b are number indexes
return weapArray[a]['Disposition'] > weapArray[b]['Disposition']
end
for _, weaponEntry in Table.skpairs(weapArray, descendingOrder) do
table.insert(weaponDispoList, '\n* [['..weaponEntry['Name']..']] ('..weaponEntry['Disposition']..')')
end
table.insert(result, table.concat(weaponDispoList))
end
table.insert(result, '\n|}')
return table.concat(result)
end
function p.getConclaveList(frame)
local WeaponType = frame.args[1]
local weapArray = p._getWeapons(function(x)
if (x.Type ~= nil and x.Conclave ~= nil) then
return x.Type == WeaponType and x.Conclave
else
return false
end
end)
local result = ''
local shortList = p._shortLinkList(weapArray, false)
for i, pair in Table.skpairs(shortList) do
result = result..'\n* '..pair
end
return result
end
-- and we are back... new table building functions !
local function buildCompRow(tableHeaders, Weapon)
local styleString = ''--'border: 1px solid lightgray;'
local result = {}
local valueName = nil
local attackName = nil
for i, headerLine in ipairs(tableHeaders) do
valueName = headerLine[1]
attackName = headerLine[3] ~= nil and headerLine[3] or nil
local value = getValue(Weapon, valueName, attackName)
if (type(value) == 'number') then
value = Math.round(value, 0.01)
end
table.insert(result, 'style="'..styleString..'"|'..(value or 'N/A'))
end
return '|-\n|'..table.concat(result, '||')
end
--- Builds comparison table as seen on [[Weapon Comparison]].
-- @function buildCompTable
local function buildCompTable(tableHeaders, Weapons)
local styleString = 'border: 1px solid black;border-collapse: collapse;'
local result = {}
table.insert(result, '{| cellpadding="1" cellspacing="0" class="listtable sortable" style="font-size:11px;"')
for _, headerLine in ipairs(tableHeaders) do
if (headerLine[1] == 'DamageBias' or headerLine[1] == 'CritMultiplier') then
table.insert(result, '! style="'..styleString..'" data-sort-type="number"|'..headerLine[2])
else
table.insert(result, '! style="'..styleString..'"|'..headerLine[2])
end
end
for _, Weap in pairs(Weapons) do
local rowStr = buildCompRow(tableHeaders, Weap)
table.insert(result, rowStr)
end
table.insert(result, '|}[[Category:Automatic Comparison Table]]')
return table.concat(result, '\n')
end
--- Builds comparison table of gun stats as seen on [[Weapon Comparison]].
-- @function p.getCompTableGuns
function p.getCompTableGuns(frame)
local Catt = frame.args ~= nil and frame.args[1]
local Type = frame.args ~= nil and frame.args[2] or nil
if (Type == "All") then Type = nil end
local WeapArray = {}
if (Catt == "Primary") then WeapArray = p._getWeapons(function(x)
if (getValue(x, "Type") == "Primary") then
if (Type ~= nil) then return getPrimaryCategory(x) == Type else return true end
end
return false
end)
elseif (Catt == "Secondary") then WeapArray = p._getWeapons(function(x)
if (getValue(x, "Type") == "Secondary") then
if (Type ~= nil) then return getSecondaryCategory(x) == Type else return true end
end
return false
end)
elseif (Catt == "Arch-Gun") then WeapArray = p._getWeapons(function(x)
return getValue(x, "Type") == "Arch-Gun"
end)
elseif (Catt == "Arch-Gun (Atmosphere)") then WeapArray = p._getWeapons(function(x)
return getValue(x, "Type") == "Arch-Gun (Atmosphere)"
end)
elseif (Catt == "Robotic") then WeapArray = p._getWeapons(function(x)
return getValue(x, "Type") == "Robotic"
end)
elseif (Catt == "Amp") then WeapArray = p._getWeapons(function(x)
return getValue(x, "Type") == "Amp"
end)
else
error('p.getCompTableGuns(frame): Wrong gun weapon class'..
'(use "Primary", "Secondary", "Arch-Gun", "Arch-Gun (Atmosphere)", "Robotic", or "Amp")'..
'[[Category:Invalid Comp Table]]')
end
local tableHeaders = { {"NameLink", "Name"} }
table.insert(tableHeaders, { "Trigger", "[[Fire Rate|Trigger]]" })
table.insert(tableHeaders, { "AttackName", "Attack" })
table.insert(tableHeaders, { "DamageBias", "Main<br/>Element" })
table.insert(tableHeaders, { "Damage", "Base<br/>[[Damage|Dmg]]" })
table.insert(tableHeaders, { "CritChance", "[[Critical Chance|Crit]]" })
table.insert(tableHeaders, { "CritMultiplier", "[[Critical multiplier|Crit<br/>Dmg]]" })
table.insert(tableHeaders, { "AvgShotDmg", "Avg<br/>Shot" })
table.insert(tableHeaders, { "BurstDps", "Burst<br/>DPS" })
table.insert(tableHeaders, { "SustainedDps", "Sust<br/>DPS" })
table.insert(tableHeaders, { "StatusChance", "[[Status Chance|Status]]" })
table.insert(tableHeaders, { "AvgProcPerSec", "[[Status Chance|Avg. Procs]]/<br/>s" })
table.insert(tableHeaders, { "CompTableFireRate", "[[Fire Rate|Fire<br/>Rate]]" })
table.insert(tableHeaders, { "Disposition", "[[Riven Mods#Disposition|Dispo]]" })
table.insert(tableHeaders, { "Mastery", "[[Mastery Rank|MR]]" })
table.insert(tableHeaders, { "Magazine", "[[Ammo#Magazine Capacity|Mag<br/>Size]]" })
table.insert(tableHeaders, { "MaxAmmo", "[[Ammo|Ammo<br/>Cap]]" })
table.insert(tableHeaders, { "Reload", "[[Reload Speed|Reload]]" })
table.insert(tableHeaders, { "ShotType", "Shot<br/>Type" })
table.insert(tableHeaders, { "PunchThrough", "[[Punch Through|PT]]" })
table.insert(tableHeaders, { "Accuracy", "[[Accuracy]]" })
table.insert(tableHeaders, { "Introduced", "Intro" })
return buildCompTable(tableHeaders, WeapArray)
end
--- Builds comparison table of gun Conclave stats as seen on [[Weapon Comparison/Conclave]].
-- @function p.getCompTableConclaveGuns
function p.getCompTableConclaveGuns(frame)
local Catt = frame.args ~= nil and frame.args[1]
local Type = frame.args ~= nil and frame.args[2] or nil
if (Type == "All") then Type = nil end
local WeapArray = {}
if (Catt == "Primary") then WeapArray = p.getConclaveWeapons(function(x)
if (getValue(x, "Type") == "Primary") then
if (Type ~= nil) then return getPrimaryCategory(x) == Type else return true end
end
return false
end)
elseif (Catt == "Secondary") then WeapArray = p.getConclaveWeapons(function(x)
if (getValue(x, "Type") == "Secondary") then
if (Type ~= nil) then return getSecondaryCategory(x) == Type else return true end
end
return false
end)
else
error('p.getCompTableConclaveGuns(frame): Wrong gun weapon class for Conclave (use "Primary" or "Secondary")[[Category:Invalid Comp Table]]')
end
local tableHeaders = { {"Name", "Name"} }
table.insert(tableHeaders, { "Trigger", "[[Fire Rate|Trigger Type]]" })
table.insert(tableHeaders, { "DamageBias", "Main<br/>Element" })
table.insert(tableHeaders, { "Damage", "[[Damage]]" })
table.insert(tableHeaders, { "HeadshotMultiplier", "HS Multiplier" })
table.insert(tableHeaders, { "ShotType", "Shot<br/>Type" })
table.insert(tableHeaders, { "CompTableFireRate", "[[Fire Rate]]" })
table.insert(tableHeaders, { "Magazine", "[[Ammo#Magazine Capacity|Magazine Size]]" })
table.insert(tableHeaders, { "Reload", "[[Reload Speed|Reload Time]]" })
table.insert(tableHeaders, { "Mastery", "[[Mastery Rank]]" })
table.insert(tableHeaders, { "Introduced", "Introduced" })
return buildCompTable(tableHeaders,WeapArray)
end
--- Builds comparison table of melee stats as seen on [[Weapon Comparison]].
-- @function p.getCompTableMelees
function p.getCompTableMelees(frame)
--Changed formatting, now only takes type since only class handled is Melee
--Keeping old formatting to avoid breaking pages
local Type = frame.args ~= nil and frame.args[2] or nil
if (Type == nil) then Type = frame.args ~= nil and frame.args[1] or nil end
if (Type == "All") then Type = nil end
local WeapArray = {}
WeapArray = getMeleeWeapons(Type)
local tableHeaders = { {"NameLink", "Name"} }
table.insert(tableHeaders, { "Class", "Type" })
table.insert(tableHeaders, { "DamageBias", "Main<br/>Element" })
table.insert(tableHeaders, { "Damage", "[[Damage|Normal]]" })
table.insert(tableHeaders, { "HeavyAttack", "[[Melee#Heavy Attack|Heavy]]" })
table.insert(tableHeaders, { "SlamAttack", "[[Melee#Slam Attack|Slam]]" })
table.insert(tableHeaders, { "SlideAttack", "[[Melee#Slide Attack|Slide]]" })
table.insert(tableHeaders, { "MeleeRange", "[[Melee#Range|Range]]" })
table.insert(tableHeaders, { "SlamRadius", "[[Melee#Slam Attack|Slam Radius]]" })
table.insert(tableHeaders, { "FireRate", "[[Attack Speed|Speed]]" })
table.insert(tableHeaders, { "CritChance", "[[Critical Chance|Crit]]" })
table.insert(tableHeaders, { "CritMultiplier", "[[Critical multiplier|Crit Dmg]]" })
table.insert(tableHeaders, { "StatusChance", "[[Status Chance|Status]]" })
table.insert(tableHeaders, { "Disposition", "[[Riven Mods#Disposition|Dispo]]" })
table.insert(tableHeaders, { "FollowThrough", "[[Follow Through|Follow<br />Through]]" })
table.insert(tableHeaders, { "BlockAngle", "[[Blocking|Block<br />Angle]]" })
table.insert(tableHeaders, { "Mastery", "[[Mastery Rank|MR]]" })
table.insert(tableHeaders, { "StancePolarity", "[[Stance]]" })
table.insert(tableHeaders, { "Introduced", "Intro" })
return buildCompTable(tableHeaders, WeapArray)
end
--- Builds comparison table of melee conclave stats as seen on [[Weapon Comparison/Conclave]].
-- @function p.getCompTableConclaveMelees
function p.getCompTableConclaveMelees(frame)
local Type = frame.args ~= nil and frame.args[1] or nil
if (Type == "All") then Type = nil end
local WeapArray = {}
WeapArray = getConclaveMeleeWeapons(Type)
local tableHeaders = { {"Name", "Name"} }
table.insert(tableHeaders, { "Class", "Type" })
table.insert(tableHeaders, { "Damage", "[[Damage|Normal]]" })
table.insert(tableHeaders, { "SlideAttack", "[[Melee#Slide Attack|Slide]]" })
table.insert(tableHeaders, { "FireRate", "[[Attack Speed]]" })
table.insert(tableHeaders, { "Mastery", "[[Mastery_Rank|Mastery Rank]]" })
table.insert(tableHeaders, { "StancePolarity", "[[Stance]]" })
table.insert(tableHeaders, { "Introduced", "Introduced" })
return buildCompTable(tableHeaders, WeapArray)
end
--- Builds comparison table of arch-melee stats as seen on [[Weapon Comparison]].
-- @function p.getCompTableArchMelees
function p.getCompTableArchMelees(frame)
local WeapArray = {}
WeapArray = p._getWeapons(function(x)
return getValue(x, "Type") == "Arch-Melee"
end)
local tableHeaders = { {"NameLink", "Name"} }
table.insert(tableHeaders, { "DamageBias", "Main<br/>Element" })
table.insert(tableHeaders, { "Damage", "[[Damage|Normal]]" })
table.insert(tableHeaders, { "FireRate", "[[Attack Speed]]" })
table.insert(tableHeaders, { "CritChance", "[[Critical Chance]]" })
table.insert(tableHeaders, { "CritMultiplier", "[[Critical multiplier|Critical Damage]]" })
table.insert(tableHeaders, { "StatusChance", "[[Status Chance]]" })
table.insert(tableHeaders, { "Mastery", "[[Mastery Rank]]" })
table.insert(tableHeaders, { "Introduced", "Introduced" })
return buildCompTable(tableHeaders, WeapArray)
end
--- Builds comparison table of projectile flight speeds as seen on [[Projectile Speed]].
-- @function p.getCompTableSpeedGuns
-- @param {table} frame Frame object
-- @returns {string} Wikitext of resultant wikitable
function p.getCompTableSpeedGuns(frame)
local weaponType = frame.args ~= nil and frame.args[1]
local weaponClass = frame.args ~= nil and frame.args[2] or nil
if (weaponClass == "All") then weaponClass = nil end
local weaponList = {}
if (weaponType == "Primary") then
weaponList = p._getWeapons(function(x)
if (getValue(x, "Type") == "Primary") then
return (weaponClass ~= nil) and getPrimaryCategory(x) == weaponClass or true
end
return false
end)
elseif (weaponType == "Secondary") then
weaponList = p._getWeapons(
function(x)
if (getValue(x, "Type") == "Secondary") then
return (weaponClass ~= nil) and getSecondaryCategory(x) == weaponClass or true
end
return false
end)
elseif (weaponType == "Robotic") then
weaponList = p._getWeapons(
function(x)
return getValue(x, "Type") == "Robotic"
end)
elseif (weaponType == "Arch-Gun") then
weaponList = p._getWeapons(
function(x)
return getValue(x, "Type") == "Arch-Gun"
end)
else
error('p.getCompTableSpeedGuns(frame): Wrong gun weapon class '..
'(use "Primary", "Secondary", "Robotic", or "Arch-Gun")[[Category:Invalid Comp Table]]')
end
-- special sorting for projectile weapons
local projectileWeaponList = {}
for k, Weapon in ipairs(weaponList) do
local shotType = getValue(Weapon, "ShotType")
if (shotType == "Projectile" or shotType == "Thrown") then
table.insert(projectileWeaponList, Weapon)
end
end
local tableHeaders = { { "NameLink", "Name" } }
table.insert(tableHeaders,{ "Class", "Class" })
table.insert(tableHeaders,{ "ShotSpeed", "Flight Speed" })
return buildCompTable(tableHeaders, projectileWeaponList)
end
--- Builds comparison table of glaive melees' projectile flight speeds as seen on [[Projectile Speed]].
-- @function p.getCompTableSpeedMelees
function p.getCompTableSpeedMelees(frame)
local weaponList = p._getWeapons(
function(x)
return getValue(x, "Class") == "Glaive"
end)
local tableHeaders = { {"NameLink", "Name"} }
table.insert(tableHeaders, { "ShotSpeed", "Flight Speed", "Attack2" })
return buildCompTable(tableHeaders, weaponList)
end
return p