(p.getWeaponCount(frame): accidentally undoed melee count change) |
mNo edit summary |
||
Line 926: | Line 926: | ||
-- @returns {number} Total count of weapons in a certain category/type |
-- @returns {number} Total count of weapons in a certain category/type |
||
function p.getWeaponCount(frame) |
function p.getWeaponCount(frame) |
||
− | local weaponSlot = frame.args ~= nil and frame.args[1] or |
+ | local weaponSlot = frame.args ~= nil and frame.args[1] or nil |
local getFullList = frame.args ~= nil and frame.args[2] |
local getFullList = frame.args ~= nil and frame.args[2] |
||
local count = 0 |
local count = 0 |
||
local fullList = {} |
local fullList = {} |
||
− | for |
+ | for name, weapon in Table.skpairs(WeaponData["Weapons"]) do |
− | if |
+ | if not weapon.IgnoreEntry then |
− | if |
+ | if weaponSlot == "All" then |
count = count + 1 |
count = count + 1 |
||
− | if |
+ | if getFullList ~= nil then table.insert(fullList, '# '..name) end |
− | elseif (weaponSlot == " |
+ | elseif not(weaponSlot) or weaponSlot == "" then |
− | if |
+ | if not(weapon.Class == "Kitgun" or weapon.Slot == "Railjack Ordnance" or weapon.Slot == "Railjack Turret") then |
count = count + 1 |
count = count + 1 |
||
− | if |
+ | if getFullList ~= nil then table.insert(fullList, '# '..name) end |
end |
end |
||
− | elseif |
+ | elseif weaponSlot == "Warframe" then |
− | if |
+ | if (weapon.Slot == "Primary" or weapon.Slot == "Secondary" or weapon.Slot == "Melee") and weapon.Class ~= "Exalted Weapon" then |
count = count + 1 |
count = count + 1 |
||
− | if (getFullList ~= nil) then table.insert(fullList, '# '.. |
+ | if (getFullList ~= nil) then table.insert(fullList, '# '..name) end |
end |
end |
||
− | elseif |
+ | elseif weaponSlot == "Archwing" then |
− | if (weapon.Slot |
+ | if (weapon.Slot == "Arch-Gun" or weapon.Slot == "Arch-Melee") and weapon.Class ~= "Exalted Weapon" then |
⚫ | |||
count = count + 1 |
count = count + 1 |
||
− | if |
+ | if getFullList ~= nil then table.insert(fullList, '# '..name) end |
end |
end |
||
+ | elseif weaponSlot == "Rest" then |
||
⚫ | |||
+ | if weapon.Slot ~= "Arch-Gun" and weapon.Slot ~= "Arch-Melee" and weapon.Slot ~= "Primary" and |
||
⚫ | |||
+ | count = count + 1 |
||
+ | if getFullList ~= nil then table.insert(fullList, '# '..name) end |
||
+ | end |
||
⚫ | |||
count = count + 1 |
count = count + 1 |
||
− | if |
+ | if getFullList ~= nil then table.insert(fullList, '# '..name) end |
end |
end |
||
end |
end |
||
end |
end |
||
− | if |
+ | if getFullList ~= nil then return table.concat(fullList, '\n') end |
-- Need to subtract 1 from melee count due to Dark Split-Sword having two different forms |
-- Need to subtract 1 from melee count due to Dark Split-Sword having two different forms |
||
− | return |
+ | return weaponSlot == 'Melee' and count - 1 or count |
end |
end |
||
Revision as of 23:38, 25 September 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.csvGunComparisonTable(frame)
(function)- Builds a CSV table of all WARFRAME's guns with the exception of Kitguns.
- Parameter:
frame
Frame object (table) - Returns: Preformatted text of CSV text (string)
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._shortLinkList(Weapon, tooltip)
(function)- Builds a list of weapons, with variants being next to base weapon name inside parentheses (e. g. Braton ( MK1, 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._getWeapon(weaponName, getConclave)
(function)- Returns a specific weapon table entry from
/data
or/Conclave/data
. - Parameters:
weaponName
Weapon name (string)getConclave
If true, gets PvP stats of weapon instead, false otherwise; defaults to false (boolean; optional)
- Returns: Weapon table (table)
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._getMeleeWeapons(weapClass, 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:
weapClass
Name of melee class to filter by; if nil then gets all melee weapons (string; 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._getConclaveMeleeWeapons(weapClass, 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:
weapClass
Name of melee class to filter by; if nil then gets all melee weapons (string; 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
/Conclave/data
(table) weapons._getWeapons(validateFunction, getConclave)
(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)getConclave
If true, gets PvP stats of weapons instead, false otherwise; defaults to false (boolean)
- Returns: Table of weapon table entries as seen in
/data
(table) weapons._getValue(Weapon, keyName, attackName)
(function)- Gets the raw value of a certain statistic of a weapon.
- 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, 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.getStanceWeaponList(frame)
(function)- Builds list of weapons that can equip a particlar stance mod as seen on Template:StanceWeapons.
- Parameter:
frame
Frame object w/ first argument being string stanceName (table) - Returns: Resultant wikitext of comparison list (string)
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)
weapons._getSecondaryCategory(weapon)
(function)- Gets the weapon class of secondary weapons for use in comparison tables.
- Parameter:
weapon
Weapon table (table) - Returns: Category name, {string} Triggertype (string)
weapons._getPrimaryCategory(weapon)
(function)- Gets the weapon class of primary weapons for use in comparison tables.
- Parameter:
weapon
Weapon table (table) - Returns: Category name, {string} Triggertype (string)
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.buildComparison(frame)
(function)- Builds comparison list between two weapons in PvE.
- Parameter:
frame
Frame object (table) - Returns: Resultant wikitext of comparison list (string)
weapons.buildComparison(frame)
(function)- Builds comparison list between two weapons in PvP (Conclave).
- Parameter:
frame
Frame object (table) - Returns: Resultant wikitext of comparison list (string)
weapons.buildAutoboxCategories(frame)
(function)- Adds weapon categories.
- Parameter:
frame
Frame object (table) - Returns: Wikitext of category links (string)
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.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.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)
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.advantages(frame)
(function)- Builds string of advantages or disadvantages of a weapon. Percentiles categorization:
- 90-100% - "Very high"
- 77.5-90% - "High"
- 65-77.5% - "Above average"
- 40-65% - Average, doesn't display anything
- 27.5-40% - "Below average"
- 15-27.5% - "Low"
- 0-15% - "Very low"
- Parameter:
frame
Frame object (table) - Returns: str preprocessed wikitext string (string)
weapons.ppData(frame)
(function)- Dumps a preprocessed table of weapon stats and info to copy into M:Weapons/ppdata. Invoked on Module:Weapons/ppdata/doc.
- Parameter:
frame
Frame object (table) - Returns: ppdata preprocessed wikitext string in Lua table formatting (string)
- 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 IconPrimaryWeaponRifle.png
-- @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>
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 ppData = mw.loadData [[Module:Weapons/ppdata]]
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 DAMAGE_TYPES = {
"Impact", "Puncture", "Slash", "Heat", "Cold", "Toxin",
"Electricity", "Blast", "Corrosive", "Radiation", "Magnetic", "Gas",
"Viral", "Void", "True", "MinProgenitorBonus"
}
local VARIANT_LIST = {
"Prime", "Prisma", "Wraith", "Vandal", "Vaykor", "Synoid",
"Telos", "Secura", "Sancti", "Rakta", "Mara", "MK1", "Kuva"
}
local GUN_KEY_MAP = {}
local ATTACK_KEY_MAP = {}
local function makeDTooltip(dt, addText)
local text = (addText) and dt or ''
return ('<span class="tooltip" data-param="%s" data-param2="DamageTypes">[[File:Dmg%sSmall64.png|x19px|link=]] %s</span>')
:format(dt, dt, text)
end
--- Returns the damage type with the highest damage distribution in an attack.
-- @function getDamageBias
-- @param {table} attackEntry Attack table
-- @returns {number} Damage distribution as a decimal (e.g. 1 being 100%)
-- @returns {string} Name of damage type with highest distribution
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
--- Calculates the effective fire rate of a weapon's attack (not necessarily
-- the same as base fire rate).
-- @function calculateEffectiveFireRate
-- @param {table} weaponEntry Weapon table data as seen in M:Weapons/data
-- @param[opt] {string} attackName Name of attack key to pull attack stats from
-- @returns {number} Effective fire rate in shots per second
local function calculateEffectiveFireRate(weaponEntry, attackName)
if (attackName == nil) then
attackName = weaponEntry['TooltipAttackDisplay'] or 'Attack1'
end
local attackEntry = weaponEntry[attackName]
-- Lanka has no delay between charged shots
if (weaponEntry['Name'] == 'Lanka') then
return 1 / attackEntry['ChargeTime']
elseif (weaponEntry['Class'] == 'Bow') then
if (attackEntry['Trigger'] == 'Charge') then
return 1 / ( attackEntry['ChargeTime'] + (1 / weaponEntry['Reload'] ) )
else
return weaponEntry['Reload'] -- For uncharged bow attacks
end
end
return 1 / ((attackEntry['ChargeTime'] or 0) + (1 / attackEntry['FireRate'] ) )
end
--- Calculates the derived damage stats commonly used in comparing gun performance.
-- Using what attacks the weapons tooltips are displaying for DPS calculations.
-- Assumes that Kuva/Tenet weapons have max Progenitor bonus (+60% bonus element).
-- @function calculateGunDerivedDamage
-- @param {table} Weapon Weapon table data as seen in M:Weapons/data
-- @param[opt] {string} attackName Name of attack table to use for calculations
-- @returns {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
-- * Base DPS: damage per second without crits involved (for PvP)
local function calculateGunDerivedDamage(Weapon, attackName)
local TooltipAttack = Weapon[attackName or 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)
-- TODO: Use GUN_KEY_MAP for CritChance and CritMultiplier instead?
local critChance = TooltipAttack['CritChance'] or 0
local critMultiplier = TooltipAttack['CritMultiplier'] or 0
-- Fire rate also affects delay between charged shots; if charge time is zero
-- effective fire rate is equal to base fire rate
-- Bows do not have a fire rate which means that the limiting factor between shots
-- is reload time
local trueFireRate = calculateEffectiveFireRate(Weapon)
local reloadTime = GUN_KEY_MAP['Reload'](Weapon)
local magazine = GUN_KEY_MAP['Magazine'](Weapon)
local maxAmmo = GUN_KEY_MAP['MaxAmmo'](Weapon)
-- 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 * trueFireRate
local avgSustained
if (Weapon['Name'] ~= 'Vectis' and Weapon['Name'] ~= 'Vectis Prime') then
avgSustained = avgBurst * numShotPerMag / (trueFireRate * reloadTime + numShotPerMag)
else
avgSustained = avgBurst * numShotPerMag / (trueFireRate * reloadTime + numShotPerMag - 1)
end
-- Checking if avgSustained is NaN (i.e. 0/0); if so, then we can say that
-- sustained damage is 'infinite'
if (avgSustained ~= avgSustained) then avgSustained = math.huge end
return totalDamage, avgShot, avgBurst, avgSustained, avgLifetimeDmg, (totalDamage * trueFireRate)
end
--- Calculates the average number of procs that occur on a single attack input.
-- @function calculateAvgProcCount
-- @param {table} weaponEntry Weapon table data as seen in M:Weapons/data
-- @param[opt] {string} attackName Name of attack key to pull attack stats from
-- @returns {number} Average number of procs on a single shot
local function calculateAvgProcCount(weaponEntry, attackName)
if (attackName == nil) then
attackName = weaponEntry['TooltipAttackDisplay'] or 'Attack1'
end
local attackEntry = weaponEntry[attackName]
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
-- Getter functions for attack keys and derived stats
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,
Cold = function(attackEntry) return attackEntry['Damage']['Cold'] or 0 end,
Electricity = function(attackEntry) return attackEntry['Damage']['Electricity'] or 0 end,
Heat = function(attackEntry) return attackEntry['Damage']['Heat'] or 0 end,
Toxin = function(attackEntry) return attackEntry['Damage']['Toxin'] or 0 end,
Blast = function(attackEntry) return attackEntry['Damage']['Blast'] or 0 end,
Corrosive = function(attackEntry) return attackEntry['Damage']['Corrosive'] or 0 end,
Gas = function(attackEntry) return attackEntry['Damage']['Gas'] or 0 end,
Magnetic = function(attackEntry) return attackEntry['Damage']['Magnetic'] or 0 end,
Radiation = function(attackEntry) return attackEntry['Damage']['Radiation'] or 0 end,
Viral = function(attackEntry) return attackEntry['Damage']['Viral'] or 0 end,
Void = function(attackEntry) return attackEntry['Damage']['Void'] or 0 end,
MinProgenitorBonus = function(attackEntry) return attackEntry['Damage']['MinProgenitorBonus'] 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,
BurstDelay = function(attackEntry) return attackEntry['BurstDelay'] end,
BurstFireRate = function(attackEntry) return attackEntry['BurstFireRate'] end,
BaseDamage = function(attackEntry)
local total = 0
for damageType, value in pairs(attackEntry['Damage']) do
total = total + value
end
return total
end,
TotalDamage = function(attackEntry)
local total = 0
for damageType, value in pairs(attackEntry['Damage']) do
total = total + value
end
return total * (attackEntry['Multishot'] or 1)
end,
DamageBias = function(attackEntry) return getDamageBias(attackEntry) end,
ChargeTime = function(attackEntry) return attackEntry['ChargeTime'] or 0 end,
CritChance = function(attackEntry) return attackEntry['CritChance'] end,
CritMultiplier = function(attackEntry) return attackEntry['CritMultiplier'] end,
ExtraHeadshotDmg = function(attackEntry) return attackEntry['ExtraHeadshotDmg'] or 0 end,
FalloffEnd = function(attackEntry) return attackEntry['Falloff'] and attackEntry['Falloff']['EndRange'] or nil end,
FalloffReduction = function(attackEntry) return attackEntry['Falloff'] and attackEntry['Falloff']['Reduction'] or nil end,
FalloffStart = function(attackEntry) return attackEntry['Falloff'] and attackEntry['Falloff']['StartRange'] or nil end,
FireRate = function(attackEntry) return attackEntry['FireRate'] end,
ForcedProcs = function(attackEntry) return attackEntry['ForcedProcs'] end,
HeadshotMultiplier = function(attackEntry)
return attackEntry['HeadshotMultiplier'] and attackEntry['HeadshotMultiplier'] or 1
end,
Multishot = function(attackEntry) return attackEntry['Multishot'] or 1 end,
PunchThrough = function(attackEntry) return attackEntry['PunchThrough'] or 0 end,
Range = function(attackEntry) return attackEntry['Range'] end,
ShotSpeed = function(attackEntry) return attackEntry['ShotSpeed'] or 'N/A' end,
ShotType = function(attackEntry) return attackEntry['ShotType'] end,
StatusChance = function(attackEntry) return attackEntry['StatusChance'] end,
}
-- The above map except returned values are formatted
local FORMATTED_ATTACK_KEY_MAP = {}
for funcName, func in pairs(ATTACK_KEY_MAP) do FORMATTED_ATTACK_KEY_MAP[funcName] = func end
FORMATTED_ATTACK_KEY_MAP['BurstDelay'] = function(attackEntry) return (ATTACK_KEY_MAP['BurstDelay'](attackEntry))..'s' end
FORMATTED_ATTACK_KEY_MAP['ChargeTime'] = function(attackEntry) return (ATTACK_KEY_MAP['ChargeTime'](attackEntry))..'s' end
FORMATTED_ATTACK_KEY_MAP['CritChance'] = function(attackEntry) return Math.percentage(ATTACK_KEY_MAP['CritChance'](attackEntry)) end
FORMATTED_ATTACK_KEY_MAP['CritMultiplier'] = function(attackEntry) return (ATTACK_KEY_MAP['CritMultiplier'](attackEntry))..'x' end
FORMATTED_ATTACK_KEY_MAP['DamageBias'] = function(attackEntry)
local bestPercent, bestElement = ATTACK_KEY_MAP['DamageBias'](attackEntry)
bestPercent = Math.percentage(bestPercent)
return bestPercent..' '..makeDTooltip(bestElement)
end
FORMATTED_ATTACK_KEY_MAP['ExtraHeadshotDmg'] = function(attackEntry) return '+'..Math.percentage(ATTACK_KEY_MAP['ExtraHeadshotDmg'](attackEntry))..'%' end
FORMATTED_ATTACK_KEY_MAP['FalloffEnd'] = function(attackEntry)
local v = ATTACK_KEY_MAP['FalloffEnd'](attackEntry)
return v and v..'m' or 'N/A'
end
FORMATTED_ATTACK_KEY_MAP['FalloffReduction'] = function(attackEntry)
local v = ATTACK_KEY_MAP['FalloffReduction'](attackEntry)
return v and Math.percentage(v) or 'N/A'
end
FORMATTED_ATTACK_KEY_MAP['FalloffStart'] = function(attackEntry)
local v = ATTACK_KEY_MAP['FalloffStart'](attackEntry)
return v and v..'m' or 'N/A'
end
FORMATTED_ATTACK_KEY_MAP['ForcedProcs'] = function(attackEntry)
local result = {}
for _, forcedProc in ipairs(attackEntry['ForcedProcs'] or {}) do
table.insert(result, forcedProc)
end
return table.concat(result, ', ')
end
FORMATTED_ATTACK_KEY_MAP['HeadshotMultiplier'] = function(attackEntry) return (ATTACK_KEY_MAP['HeadshotMultiplier'](attackEntry))..'x' end
FORMATTED_ATTACK_KEY_MAP['PunchThrough'] = function(attackEntry) return (ATTACK_KEY_MAP['PunchThrough'](attackEntry))..'m' end
FORMATTED_ATTACK_KEY_MAP['Radius'] = function(attackEntry) return (ATTACK_KEY_MAP['Radius'](attackEntry))..'m' end
FORMATTED_ATTACK_KEY_MAP['ShotSpeed'] = function(attackEntry) return (ATTACK_KEY_MAP['ShotSpeed'](attackEntry))..'m/s' end
FORMATTED_ATTACK_KEY_MAP['StatusChance'] = function(attackEntry) return Math.percentage(ATTACK_KEY_MAP['StatusChance'](attackEntry)) end
-- Getter functions for shared weapon keys and derived stats
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._getVersion(weaponEntry['Introduced'])['Name'] or 'N/A'
end,
IntroducedDate = 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'] or {}) do
table.insert(traitString, trait)
end
return table.concat(traitString, ', ')
end,
Slot = function(weaponEntry) return weaponEntry['Slot'] end
}
-- Getter functions for gun weapon keys and derived stats
GUN_KEY_MAP = {
Accuracy = function(weaponEntry)
if (weaponEntry['Accuracy'] ~= nil) then return weaponEntry['Accuracy'] end
return weaponEntry[weaponEntry['TooltipAttackDisplay'] or 'Attack1']['Accuracy'] or 0
end,
AmmoType = function(weaponEntry)
if (weaponEntry['AmmoType'] ~= nil) then
return weaponEntry['AmmoType']
elseif(weaponEntry['Slot'] == 'Secondary') then
return 'Pistol'
elseif(weaponEntry['Slot'] == '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, attackName)
return calculateAvgProcCount(weaponEntry, attackName)
end,
AvgProcPerSec = function(weaponEntry, attackName)
return calculateAvgProcCount(weaponEntry, attackName) * calculateEffectiveFireRate(weaponEntry, attackName)
end,
AvgShotDmg = function(weaponEntry, attackName)
local _, avgShot = calculateGunDerivedDamage(weaponEntry, attackName)
return avgShot
end,
BaseDps = function(weaponEntry, attackName)
local _, _, _, _, _, baseDps = calculateGunDerivedDamage(weaponEntry, attackName)
return baseDps
end,
BurstDps = function(weaponEntry, attackName)
local _, _, avgBurst = calculateGunDerivedDamage(weaponEntry, attackName)
return avgBurst
end,
EffectiveFireRate = function(weaponEntry, attackName)
return calculateEffectiveFireRate(weaponEntry, attackName)
end,
ExilusPolarity = function(weaponEntry) return Polarity._polarity(weaponEntry['ExilusPolarity']) end,
IsSilent = function(weaponEntry) return weaponEntry['IsSilent'] or false end,
LifetimeDmg = function(weaponEntry, attackName)
local _, _, _, _, avgLifetimeDmg = calculateGunDerivedDamage(weaponEntry, attackName)
return avgLifetimeDmg
end,
Magazine = function(weaponEntry) return weaponEntry['Magazine'] or 0 end,
MaxAmmo = function(weaponEntry) return weaponEntry['MaxAmmo'] or 0 end,
Range = function(weaponEntry) return weaponEntry['Range'] end,
Reload = function(weaponEntry) return weaponEntry['Reload'] or 0 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'] or 'Magazine' end,
Spool = function(weaponEntry) return weaponEntry['Spool'] or 0 end,
SustainedDps = function(weaponEntry, attackName)
local _, _, _, avgSustained = calculateGunDerivedDamage(weaponEntry, attackName)
return avgSustained
end,
Trigger = function(weaponEntry)
local attack = weaponEntry['TooltipAttackDisplay'] or 'Attack1'
return weaponEntry['Trigger'] or weaponEntry[attack]['Trigger'] or 'N/A'
end
}
-- Getter functions for melee weapon keys and derived stats
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 CATEGORY_MAP = {
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 Turret'] = '[[Category:Railjack]]',
['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 weaponSlotFilter = { 'Primary', 'Secondary', 'Robotic', 'Arch-Gun', 'Arch-Gun (Atmosphere)', 'Amp' }
local tableEntryTemplate = {} -- Would look like '%s,%s,%s'
-- Header names will also be key names to getter function maps
local tableHeader = {
'Name',
'Trigger',
'AttackName',
'Impact',
'Puncture',
'Slash',
'Cold',
'Electricity',
'Heat',
'Toxin',
'Blast',
'Corrosive',
'Gas',
'Magnetic',
'Radiation',
'Viral',
'Void',
'MinProgenitorBonus',
'BaseDamage',
'TotalDamage',
'CritChance',
'CritMultiplier',
'AvgShotDmg',
'BurstDps',
'SustainedDps',
'LifetimeDmg',
'StatusChance',
'ForcedProcs',
'AvgProcCount',
'AvgProcPerSec',
'Multishot',
'FireRate',
'Disposition',
'Mastery',
'Magazine',
'MaxAmmo',
'Reload',
'ShotType',
'PunchThrough',
'Accuracy',
'Introduced',
'IntroducedDate',
'Slot',
'Class',
'AmmoType',
'Range'
}
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, weaponEntry in Table.skpairs(WeaponData['Weapons']) do
if (Table.contains(weaponSlotFilter, weaponEntry['Slot']) and
string.find(weaponEntry['Class'], 'Kitgun') == nil and
not weaponEntry['IgnoreEntry']) 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 (weaponEntry['Attack'..i] ~= nil) then
local tableEntryValues = {}
local attackName = 'Attack'..i
for _, keyName in ipairs(tableHeader) do
local v = p._getValue(weaponEntry, keyName, attackName)
-- Serializing table to a string
if (type(v) == 'table') then
v = "'"..mw.dumpObject(v):gsub('%s+metatable = table#%d+', ''):gsub('table#%d+ ', '').."'"
end
table.insert(tableEntryValues, tostring(v))
end
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
--- 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 (string.find(weaponName, var)) then
local baseName = string.gsub(weaponName, " ?"..var.." ?-?", "")
return true, var, baseName
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 (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
end
return variant..' '..baseName
end
-- TODO: Function can be refactored
--- 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)
-- TODO: Isn't an easier way to check if a weapon is a variant is using the
-- Family key which contains the name the base weapon and just gsub out that portion?
-- 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
--- 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} getConclave If true, gets PvP stats of weapon instead, false otherwise; defaults to false
-- @returns {table} Weapon table
function p._getWeapon(weaponName, getConclave)
local weapon = getConclave and ConclaveData["Weapons"][weaponName] or WeaponData["Weapons"][weaponName]
if weapon ~= nil then return weapon end
error('p._getWeapon(weaponName, getConclave): "'..weaponName..
'" does not exist in [[Module:Weapons/data]] or [[Module:Weapons/Conclave/data]]')
end
--- Returns a specific attack table from a weapon table entry.
-- @function getAttack
-- @param {table, string} Weapon Weapon entry as seen in <code>/data</code> or the name of weapon
-- @param[opt] {string} attackName Name of attack key; if omitted, will default to "Attack1"
-- @returns {table} Attack table
local function getAttack(Weapon, attackName)
if (Weapon == nil or attackName == nil) then return end
if (type(Weapon) == "string") then
Weapon = p._getWeapon(Weapon)
end
if (not attackName) then
return Weapon.Attack1 or Weapon.Damage and Weapon
end
return Weapon[attackName:find 'Attack' and attackName or 'Attack'..attackName]
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
-- TODO: Remove this function and use p._getWeapons() w/ a validation function that filters
-- out melee weapon entries?
--- 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] {string} weapClass Name of melee class to filter by; if nil then gets all melee weapons
-- @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(weapClass, PvP)
local weaps = {}
for i, weap in Table.skpairs(WeaponData["Weapons"]) do
if ((weap.Ignore == nil or not weap.Ignore) and weap.Slot ~= nil and weap.Slot == "Melee" and not weap.IgnoreEntry) 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
-- TODO: Can probably be removed and refactored into getMeleeWeapons()
--- 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._getConclaveMeleeWeapons
-- @param[opt] {string} weapClass Name of melee class to filter by; if nil then gets all melee weapons
-- @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>/Conclave/data</code>
function p._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.Slot ~= nil and weap.Slot == "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
--- 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 {boolean} getConclave If true, gets PvP stats of weapons instead, false otherwise; defaults to false
-- @returns {table} Table of weapon table entries as seen in <code>/data</code>
function p._getWeapons(validateFunction, getConclave)
local weaponList = {}
for weaponName, weaponEntry in Table.skpairs(getConclave and ConclaveData["Weapons"] or WeaponData["Weapons"]) do
if (validateFunction(weaponEntry) and not weaponEntry['IgnoreEntry'] ) then
table.insert(weaponList, weaponEntry)
end
end
return weaponList
end
-- TODO: Move to M:Stances?
--- Gets stance mods for a particular melee class.
-- @function getWeaponStanceList
-- @param {string} meleeClass Melee class
-- @param {boolean} pvpOnly If true, only gets PvP-exclusive stance mods, otherwise gets all applicable stance mods; default false
-- @returns {table} Table of stance table entries as seen in <code>M:Stance/data</code>
local function getStances(meleeClass, pvpOnly)
local stanceTable = {}
for stanceName, Stance in pairs(StanceData) do
local typeMatch = (meleeClass == nil or meleeClass == 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
--- Gets the raw value of a certain statistic of a weapon.
-- @function p._getValue
-- @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, number} Value of statistic
function p._getValue(Weapon, keyName, attackName)
if (attackName == nil) then attackName = Weapon['TooltipAttackDisplay'] or 'Attack1' end
local status, result
-- Doing it this way allows returning of nil values
for _, map in ipairs({ SHARED_KEY_MAP, GUN_KEY_MAP, MELEE_KEY_MAP }) do
if (map[keyName] ~= nil) then
status, result = pcall(map[keyName], Weapon, attackName)
if (status) then return result end
end
end
if (ATTACK_KEY_MAP[keyName] ~= nil) then
status, result = pcall(ATTACK_KEY_MAP[keyName], Weapon[attackName])
if (status) then return result end
end
error('p._getValue(Weapon, keyName, attackName): Cannot get keyName "'..keyName..'" for "'..attackName..
'" in '..mw.dumpObject(Weapon)..' Error message: '..result)
-- return SHARED_KEY_MAP[keyName] ~= nil and SHARED_KEY_MAP[keyName](Weapon, attackName) or
-- GUN_KEY_MAP[keyName] ~= nil and GUN_KEY_MAP[keyName](Weapon, attackName) or
-- MELEE_KEY_MAP[keyName] ~= nil and MELEE_KEY_MAP[keyName](Weapon, attackName) or
-- ATTACK_KEY_MAP[keyName] ~= nil and ATTACK_KEY_MAP[keyName](Weapon[attackName]) or
-- error('p._getValue(Weapon, keyName, attackName): Cannot get keyName "'..keyName..'" in '..mw.dumpObject(Weapon))
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(Weapon, keyName, attackName)
if (attackName == nil) then attackName = Weapon['TooltipAttackDisplay'] or 'Attack1' end
return SHARED_KEY_MAP[keyName] ~= nil and SHARED_KEY_MAP[keyName](Weapon, attackName) or
GUN_KEY_MAP[keyName] ~= nil and GUN_KEY_MAP[keyName](Weapon, attackName) or
MELEE_KEY_MAP[keyName] ~= nil and MELEE_KEY_MAP[keyName](Weapon, attackName) or
FORMATTED_ATTACK_KEY_MAP[keyName] ~= nil and FORMATTED_ATTACK_KEY_MAP[keyName](Weapon[attackName]) or
error('p._getFormattedValue(Weapon, keyName, attackName): Cannot get keyName "'..keyName..'" with attackName "'..attackName..'" in '..mw.dumpObject(Weapon))
end
-- TODO: Move this function to M:Stance?
--- Builds list of weapons that can equip a particlar stance mod as seen on [[Template:StanceWeapons]].
-- @function p.getStanceWeaponList
-- @param {table} frame Frame object w/ first argument being string stanceName
-- @returns {string} Resultant wikitext of comparison list
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 = p._getMeleeWeapons(Stance.WeaponType, Stance.ConclaveOnly)
local result = {}
for i, weap in Table.skpairs(weaps) do
local listItem = '*[['..weap.Name..']]'
if (Stance.ConclaveOnly) then
listItem = '*[[Conclave:'..weap.Name..'|'..weap.Name..']]'
end
if (weap.StancePolarity == ModData["Mods"][stanceName].Polarity) then
listItem = listItem..' ✓'
end
table.insert(result, listItem)
end
return table.concat(result, '\n')
end
--- Builds a weapon gallery.
-- @function getWeaponGallery
-- @param {table} Weapons Array of weapon table entries to be displayed
-- @returns {string} Resultant wikitext of gallery
local function getWeaponGallery(Weapons)
local result = { '{| style="margin:auto;text-align:center;"' }
local nameRow = ""
for i, Weapon in pairs(Weapons) do
local image = Weapon.Image ~= nil and Weapon.Image or "Panel.png"
if ((i - 1) % 5 == 0) then
table.insert(result, nameRow.."\n|-")
nameRow = "\n|-"
end
table.insert(result, '| style="width:165px" |[[File:'..image..'|150px|link='..Weapon.Name..']]')
nameRow = nameRow..'\n| style="vertical-align: text-top;" |[['..Weapon.Name..']]'
end
table.insert(result, nameRow)
table.insert(result, '|}')
return table.concat(result, '\n')
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 ~= nil and frame.args[1] or ""
local WeapArray = p._getMeleeWeapons(meleeClass)
local result = "=="..meleeClass.." Weapons==\n"
result = result..getWeaponGallery(WeapArray)
return result
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
function p.getWeaponCount(frame)
local weaponSlot = frame.args ~= nil and frame.args[1] or nil
local getFullList = frame.args ~= nil and frame.args[2]
local count = 0
local fullList = {}
for name, weapon in Table.skpairs(WeaponData["Weapons"]) do
if not weapon.IgnoreEntry then
if weaponSlot == "All" then
count = count + 1
if getFullList ~= nil then table.insert(fullList, '# '..name) end
elseif not(weaponSlot) or weaponSlot == "" then
if not(weapon.Class == "Kitgun" or weapon.Slot == "Railjack Ordnance" or weapon.Slot == "Railjack Turret") then
count = count + 1
if getFullList ~= nil then table.insert(fullList, '# '..name) end
end
elseif weaponSlot == "Warframe" then
if (weapon.Slot == "Primary" or weapon.Slot == "Secondary" or weapon.Slot == "Melee") and weapon.Class ~= "Exalted Weapon" then
count = count + 1
if (getFullList ~= nil) then table.insert(fullList, '# '..name) end
end
elseif weaponSlot == "Archwing" then
if (weapon.Slot == "Arch-Gun" or weapon.Slot == "Arch-Melee") and weapon.Class ~= "Exalted Weapon" then
count = count + 1
if getFullList ~= nil then table.insert(fullList, '# '..name) end
end
elseif weaponSlot == "Rest" then
if weapon.Slot ~= "Arch-Gun" and weapon.Slot ~= "Arch-Melee" and weapon.Slot ~= "Primary" and
weapon.Slot ~= "Secondary" and weapon.Slot ~= "Melee" and weapon.Class ~= "Exalted Weapon" then
count = count + 1
if getFullList ~= nil then table.insert(fullList, '# '..name) end
end
elseif weapon.Slot == weaponSlot and weapon.Class ~= "Exalted Weapon" and string.find(weapon.Class, "Kitgun") == nil then
count = count + 1
if getFullList ~= nil then table.insert(fullList, '# '..name) end
end
end
end
if getFullList ~= nil then return table.concat(fullList, '\n') end
-- Need to subtract 1 from melee count due to Dark Split-Sword having two different forms
return weaponSlot == 'Melee' and count - 1 or count
end
--- Gets the weapon class of secondary weapons for use in comparison tables.
-- @function p._getSecondaryCategory
-- @param {table} weapon Weapon table
-- @returns {string} Category name, {string} Triggertype
function p._getSecondaryCategory(weapon)
local class = p._getValue(weapon, "Class")
local trigger = p._getValue(weapon, "Trigger")
if (class == "Thrown") then return "Thrown", trigger
elseif (class == "Dual Shotguns") then return "Dual Shotguns", trigger
elseif (class == "Shotgun Sidearm") then return "Shotgun Sidearm", trigger
elseif (class == "Dual Pistols") then return "Dual Pistols", trigger
elseif (class == "Pistol") then return "Pistol", trigger
end
return "Other", trigger
end
--- Gets the weapon class of primary weapons for use in comparison tables.
-- @function p._getPrimaryCategory
-- @param {table} weapon Weapon table
-- @returns {string} Category name, {string} Triggertype
function p._getPrimaryCategory(weapon)
local class = p._getValue(weapon, "Class")
local trigger = p._getValue(weapon, "Trigger")
if (class == "Shotgun") then return "Shotgun", trigger
elseif (class == "Bow" or class == "Crossbow") then return "Bow", trigger
elseif (class == "Sniper Rifle") then return "Sniper", trigger
elseif (class == "Launcher") then return "Launcher", trigger
elseif (class == "Arm Cannon") then return "Arm Cannon", trigger
elseif (class == "Speargun") then return "Speargun", trigger
elseif (class == "Rifle") then return "Rifle", trigger
end
return "Other", trigger
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 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(weaponSlot)
return function(weaponEntry)
return (weaponEntry['Slot'] == weaponSlot) and string.len(p._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
local traverseOrder = { primaryCount, pistolCount, meleeCount, archGunCount, archMeleeCount }
-- Note: Cannot map tables to their size since it would exceed allocated script time
-- to access these
local countEntries = {
[primaryCount] = primaryEntries,
[pistolCount] = pistolEntries,
[meleeCount] = meleeEntries,
[archGunCount] = archGunEntries,
[archMeleeCount] = archMeleeEntries
}
for i = 1, maxLen, 1 do
table.insert(tableResult, '|-')
-- Adding each row in table
for _, entriesCount in ipairs(traverseOrder) do
if (i <= entriesCount) then
table.insert(tableResult, '| [['..countEntries[entriesCount][i]['Name']..']] ||'..Polarity._pols(countEntries[entriesCount][i]['Polarities']))
else
table.insert(tableResult, '| ||')
end
end
end
table.insert(tableResult, '|}')
return table.concat(tableResult, '\n')
end
--- Builds comparison string between two values.
-- @function p.buildCompareString
-- @param {string, number} firstVal Value used for comparison
-- @param {string, number} secondVal Value used to compare the first value against
-- @param {string} valName Name of statistic that values represent (e.g. "Critical Damage")
-- @param[opt] {number} digits The decimal to round the values by (e.g. if you want to round to two decimal places put in 0.01)
-- @param[opt] {string} unit The values' unit (e.g. "m" or "seconds")
-- @param[opt] {table} compareAdjs Two element table that contains the greater than and less than comparative adjectives (e.g. { "Higher", "Lower" } )
-- @param[opt] {string} start What to start the comparison string by for if you want to increase the bullet level (e.g. "\n***")
-- @returns {string} Resultant wikitext of comparison string
local function buildCompareString(firstVal, secondVal, valName, digits, unit, compareAdjs, start)
if (firstVal == nil or secondVal == nil) then return "" end
local firstValStr = firstVal
local secondValStr = secondVal
if (digits ~= nil) then
firstValStr = Math.round(firstVal, digits)
secondValStr = Math.round(secondVal, digits)
end
if (unit ~= nil) then
firstValStr = firstValStr..unit
secondValStr = secondValStr..unit
end
local bigWord = compareAdjs ~= nil and compareAdjs[1] or "Higher"
local smallWord = compareAdjs ~= nil and compareAdjs[2] or "Lower"
local start = start ~= nil and start or "\n**"
if (firstVal > secondVal) then
return start.." "..bigWord.." "..valName.." ("..firstValStr.." vs. "..secondValStr..")"
elseif (secondVal > firstVal) then
return start.." "..smallWord.." "..valName.." ("..firstValStr.." vs. "..secondValStr..")"
end
return ""
end
--- Builds damage comparison string between two attacks.
-- @function p.buildComparison
-- @param {table} Attack1 Attack used for comparison
-- @param {table} Attack2 Attack used to compare the first attack against
-- @returns {string} Resultant wikitext of comparison string
local function buildDamageTypeComparisonString(Attack1, Attack2)
local result = ""
-- TODO: Replace DAMAGE_TYPES with DAMAGE_ITERATION_ORDER table in M:DamageTypes
-- ipairs iterates in the order given in DAMAGE_TYPES, so IPS is always first
for i, element in ipairs(DAMAGE_TYPES) do
local damage1 = Attack1.Damage[element] or 0
local damage2 = Attack2.Damage[element] or 0
result = result..buildCompareString(damage1, damage2, Tooltip.getFullTooltip(element, 'DamageTypes').." damage", 0.01, nil, {"Higher", "Lower"}, "\n***")
end
return result
end
--- Builds comparison list between two gun weapons.
-- @function p.buildComparison
-- @param {table} Weapon1 Weapon used for comparison
-- @param {table} Weapon2 Weapon used to compare the first weapon against
-- @param {boolean} conclave If true, makes comparison list based on PvP stats, otherwise uses PvE stats; default false
-- @returns {string} Resultant wikitext of comparison list
local function buildGunComparisonString(Weapon1, Weapon2, Conclave)
local result = {}
-- Adding this assignment to support method chaining w/ colon syntax
result.insert = function(self, elem) table.insert(self, elem) return self end
local Att1 = getAttack(Weapon1, Weapon1['TooltipAttackDisplay'] or 'Attack1')
local Att2 = getAttack(Weapon2, Weapon2['TooltipAttackDisplay'] or 'Attack1')
if (Conclave) then
result:insert("* [["..Weapon1.Name.."]], compared to [[Conclave:"..Weapon2.Name.."|"..Weapon2.Name.."]]:")
else
result:insert("* [["..Weapon1.Name.."]] ("..(Att1.AttackName or "Normal").."), compared to [["..Weapon2.Name.."]] ("..(Att2.AttackName or "Normal").."):")
end
local dmgString = ""
dmgString = dmgString..buildCompareString(p._getValue(Weapon1, "BaseDamage"), p._getValue(Weapon2, "BaseDamage"), "base damage per projectile", 0.01)
dmgString = dmgString..buildDamageTypeComparisonString(Att1, Att2)
if (string.len(dmgString) > 0 and p._getValue(Weapon1, "BaseDamage") == p._getValue(Weapon2, "BaseDamage")) then
dmgString = "\n**Equal base damage, but different composition:"..dmgString
end
dmgString = dmgString..buildCompareString(p._getValue(Weapon1, "TotalDamage"), p._getValue(Weapon2, "TotalDamage"), "total damage", 0.01)
-- 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(p._getValue(Weapon1, "ChargeTime", "Attack3"), p._getValue(Weapon2, "ChargeTime", "Attack3"), "charge time", 0.01, " s", {"Slower", "Faster"})
result:insert(dmgString)
local weapon1Name = p._getValue(Weapon1, "Name")
local weapon2Name = p._getValue(Weapon2, "Name")
local isLichWeapon = string.find(weapon1Name, "Kuva") or
string.find(weapon1Name, "Tenet") or
string.find(weapon2Name, "Kuva") or
string.find(weapon2Name, "Tenet")
local progenitorBonusNote = isLichWeapon and " (using max +60% [[Lich System/Progenitor|Progenitor]] bonus if applicable)" or ""
if (not Conclave) then
result:insert(
buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "base [[critical chance]]", 0.01, "%")
):insert(
buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "base [[critical multiplier]]", 0.01, "x")
):insert(
buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance * 100), "base [[status chance]]", 0.01, "%")
):insert(
buildCompareString(p._getValue(Weapon1, "AvgShotDmg"), p._getValue(Weapon2, "AvgShotDmg"), "[[Damage#Final_Calculations|average damage per shot]]"..progenitorBonusNote, 0.01)
):insert(
buildCompareString(p._getValue(Weapon1, "BurstDps"), p._getValue(Weapon2, "BurstDps"), "[[Damage#Final_Calculations|burst DPS]]"..progenitorBonusNote, 0.01)
):insert(buildCompareString(p._getValue(Weapon1, "SustainedDps"), p._getValue(Weapon2, "SustainedDps"), "[[Damage#Final_Calculations|sustained DPS]]"..progenitorBonusNote, 0.01)
):insert(
buildCompareString(p._getValue(Weapon1, "FalloffStart"), p._getValue(Weapon2, "FalloffStart"), "starting [[Damage Falloff|damage falloff]] distance", 0.1, "m", {"Farther", "Closer"})
):insert(
buildCompareString(p._getValue(Weapon1, "FalloffEnd"), p._getValue(Weapon2, "FalloffEnd"), "ending damage falloff distance", 0.1, "m", {"Farther", "Closer"})
):insert(
buildCompareString(p._getValue(Weapon1, "FalloffReduction"), p._getValue(Weapon2, "FalloffReduction"), "max damage reduction at ending falloff distance", 0.01, "x", {"Greater", "Lesser"})
)
end
result:insert(
buildCompareString(Att1.FireRate, Att2.FireRate, "[[fire rate]]", 0.01, " round(s)/sec")
):insert(
buildCompareString(Att1.Multishot or 1, Att2.Multishot or 1, "[[multishot]]", 1, " projectile(s)")
):insert(
buildCompareString(Weapon1.Magazine, Weapon2.Magazine, "magazine", 1, " round(s)", {"Larger", "Smaller"})
):insert(
buildCompareString(Weapon1.MaxAmmo, Weapon2.MaxAmmo, "max ammo capacity", 1, " round(s)", {"Larger", "Smaller"})
):insert(
buildCompareString(Weapon1.Reload, Weapon2.Reload, "[[Reload|reload time]]", 0.01, " s", {"Slower", "Faster"})
):insert(
buildCompareString(Weapon1.Spool, Weapon2.Spool, "spool-up", 1, " round(s)", {"Slower", "Faster"})
):insert(
buildCompareString(p._getValue(Weapon1, "Accuracy"), p._getValue(Weapon2, "Accuracy"), "[[Accuracy|accurate]]", 0.01, nil, {"More", "Less"})
):insert(
buildCompareString(p._getValue(Weapon1, "Polarities"), p._getValue(Weapon2, "Polarities"), "[[Polarity|polarities]]", nil, nil, {"Different", "Different"})
):insert(
buildCompareString(Weapon1.Mastery, Weapon2.Mastery, "[[Mastery Rank]] required", 1)
):insert(
buildCompareString(Weapon1.Disposition, Weapon2.Disposition, "[[disposition]]", 0.01)
)
--Handling Syndicate radial effects
if (Weapon1.SyndicateEffect ~= nil and Weapon2.SyndicateEffect == nil) then
result:insert("\n** Innate [["..Weapon1.SyndicateEffect.."]] effect")
elseif (Weapon2.SyndicateEffect ~= nil and Weapon1.SyndicateEffect == nil) then
result:insert("\n** Lack of an innate [["..Weapon2.SyndicateEffect.."]] effect")
elseif (Weapon1.SyndicateEffect ~= nil and Weapon2.SyndicateEffect ~= nil and
Weapon1.SyndicateEffect ~= Weapon2.SyndicateEffect2) then
result:insert("\n** Different innate [[Syndicate Radial Effects|Syndicate Effect]]: [["..
Weapon1.SyndicateEffect.."]] vs. [["..Weapon2.SyndicateEffect.."]]")
end
return table.concat(result)
end
--- Builds comparison list between two melee or arch-melee weapons.
-- @function p.buildComparison
-- @param {table} Weapon1 Weapon used for comparison
-- @param {table} Weapon2 Weapon used to compare the first weapon against
-- @param {boolean} conclave If true, makes comparison list based on PvP stats, otherwise uses PvE stats; default false
-- @returns {string} Resultant wikitext of comparison list
local function buildMeleeComparisonString(Weapon1, Weapon2, Conclave)
local result = {}
-- Adding this assignment to support method chaining w/ colon syntax
result.insert = function(self, elem) table.insert(self, elem) return self end
local Att1 = getAttack(Weapon1, Weapon1['TooltipAttackDisplay'] or 'Attack1')
local Att2 = getAttack(Weapon2, Weapon2['TooltipAttackDisplay'] or 'Attack1')
if (Conclave) then
result:insert("* "..Weapon1.Name..", compared to [[Conclave:"..Weapon2.Name.."|"..Weapon2.Name.."]]:")
else
result:insert("* "..Weapon1.Name.." ("..(Att1.AttackName or "Normal").."), compared to [["..Weapon2.Name.."]] ("..(Att2.AttackName or "Normal").."):")
end
local dmgString = ""
dmgString = dmgString..buildCompareString(p._getValue(Weapon1, "BaseDamage"), p._getValue(Weapon2, "BaseDamage"), "base damage", 0.01)
dmgString = dmgString..buildDamageTypeComparisonString(Att1, Att2)
if (string.len(dmgString) > 0 and p._getValue(Weapon1, "BaseDamage") == p._getValue(Weapon2, "BaseDamage")) then
dmgString = "\n**Equal base damage, but different composition:"..dmgString
end
result:insert(dmgString)
if (not Conclave) then
result:insert(
buildCompareString((Att1.CritChance * 100), (Att2.CritChance * 100), "[[critical chance]]", 0.01, "%")
):insert(
buildCompareString(Att1.CritMultiplier, Att2.CritMultiplier, "[[critical multiplier]]", 0.01, "x")
):insert(
buildCompareString((Att1.StatusChance * 100), (Att2.StatusChance * 100), "[[status chance]]", 0.01, "%")
):insert(
buildCompareString(p._getValue(Weapon1, "MeleeRange"), p._getValue(Weapon2, "MeleeRange"), "Range", 0.01, " m")
):insert(
buildCompareString(Weapon1.Disposition, Weapon2.Disposition, "[[disposition]]", 0.01)
)
end
result:insert(
buildCompareString(Att1.FireRate, Att2.FireRate, "[[attack speed]]", 0.01)
):insert(
buildCompareString(p._getValue(Weapon1, "Polarities"), p._getValue(Weapon2, "Polarities"), "[[Polarity|polarities]]", nil, nil, {"Different", "Different"})
):insert(
buildCompareString(Weapon1.Mastery, Weapon2.Mastery, "[[Mastery Rank]] required", 1)
):insert(
buildCompareString(p._getValue(Weapon1, "ComboDur"), p._getValue(Weapon2, "ComboDur"), "[[Melee Combo|Combo Duration]]", 1, " s")
):insert(
buildCompareString(p._getValue(Weapon1, "BlockAngle"), p._getValue(Weapon2, "BlockAngle"), "Block Angle", 1, "°")
):insert(
buildCompareString(p._getValue(Weapon1, "StancePolarity"), p._getValue(Weapon2, "StancePolarity"), "[[Stance]] Polarity", nil, nil, {"Different", "Different"})
)
return table.concat(result)
end
--- Builds comparison list between two weapons in PvE.
-- @function p.buildComparison
-- @param {table} frame Frame object
-- @returns {string} Resultant wikitext of comparison list
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.Slot == 'Melee' or Weapon1.Slot == 'Arch-Melee') then
comparisonString = buildMeleeComparisonString(Weapon1, Weapon2)
else
comparisonString = buildGunComparisonString(Weapon1, Weapon2)
end
return comparisonString..'[[Category:Automatic Comparison]]'
end
--- Builds comparison list between two weapons in PvP ([[Conclave]]).
-- @function p.buildComparison
-- @param {table} frame Frame object
-- @returns {string} Resultant wikitext of comparison list
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._getWeapon(weaponName1, true)
local Weapon2 = p._getWeapon(weaponName2, true)
local comparisonString = ''
if (Weapon1.Slot == 'Melee' or Weapon1.Slot == 'Arch-Melee') then
comparisonString = buildMeleeComparisonString(Weapon1, Weapon2, true)
else
comparisonString = buildGunComparisonString(Weapon1, Weapon2, true)
end
return comparisonString..'[[Category:Automatic Comparison]]'
end
-- TODO: Currently unused since we migrated infobox builders to M:Weapons/infobox
--- Adds weapon categories.
-- @function p.buildAutoboxCategories
-- @param {table} frame Frame object
-- @returns {string} Wikitext of category links
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, CATEGORY_MAP[Weapon['Class']]..CATEGORY_MAP[Weapon['Trigger']]..CATEGORY_MAP[Weapon['Slot']])
-- Adding appropriate categories to page based on weapon's categorical traits
for _, trait in pairs(Weapon['Traits'] or {}) do
table.insert(result, CATEGORY_MAP[trait])
end
local bestPercent, bestElement = getDamageBias(Weapon[Weapon['TooltipAttackDisplay'] or 'Attack1'])
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
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 (string.find(weaponEntry['Class'], 'Kitgun') ~= nil) 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 result = ''
local tHeader = string.format([[
{| class = "listtable sortable" style="margin:auto;"
|+ '''Weapons with %s damage'''
|-
! Name !! Slot !! Class !! {{D|%s}} !! data-sort-type="number" | %s%%
]], damageType, damageType, damageType)
local tRows = {}
for i, Weapon in pairs(WeapArray) do
local thisRow = [[
|-
| {{Weapon|%s}} || %s || %s || %s || data-sort-value="%s" | %s]]
local attack = Weapon['TooltipAttackDisplay'] or 'Attack1'
thisRow = string.format(thisRow,
p._getValue(Weapon, 'Name'),
p._getValue(Weapon, 'Slot'),
p._getValue(Weapon, 'Class'),
p._getValue(Weapon, damageType, attack),
(p._getValue(Weapon, 'DamageBias', attack)), -- Note that function returns two values for DamageBias, first being the percentage as a decimal
p._getFormattedValue(Weapon, 'DamageBias', attack)
)
table.insert(tRows, thisRow)
end
result = tHeader..table.concat(tRows, '\n')..'\n|}'
return frame:preprocess(result)
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 checkSlotAndMastery = function(x) return x.Slot == weaponSlot and x.Mastery == masteryRank end
local weapArray = p._getWeapons(checkSlotAndMastery)
local result = {}
local name = ''
local shortList = p._shortLinkList(weapArray, true)
for i, list in Table.skpairs(shortList) do
table.insert(result, list)
end
return frame:preprocess(table.concat(result, ' • '))
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 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" |')
local checkRightDispo = function(weaponEntry)
if (weaponEntry['Slot'] ~= nil and weaponEntry['Disposition'] ~= nil) then
return (weaponSlot == 'All' or weaponEntry['Slot'] == weaponSlot) and
math.min(math.floor(5 * (weaponEntry['Disposition'] - (weaponEntry['Disposition'] < 1 and 0.3 or 0.309))), 5) == dispoRating
end
return false
end
-- Filtering out weapons that are between certain disposition ranges
local weapArray = p._getWeapons(checkRightDispo)
-- 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
--- 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]
local weapArray = p._getWeapons(function(x) return x.Slot == weaponSlot and x.Conclave end)
local result = {}
local shortList = p._shortLinkList(weapArray, false)
for i, list in Table.skpairs(shortList) do
table.insert(result, list)
end
-- Need to concat extra '*' for listing first element
return '*'..table.concat(result, '\n* ')
end
--- Builds placement string for a stat of a weapon's attack.
-- @function placeString
-- @param {table} str Table of advantages
-- @param {table} weapon Weapon table
-- @param {table} w ppData subtable
-- @param {string} attack Weapon attack mode
-- @param {string} stat Attack stat in ppData
-- @param {string} desc Stat descritpion
-- @param {string} desc2 'High' (default), 'Fast', 'Large', etc.
-- @returns {table} str Table of advantages
-- @returns {bool} contains If weapon is in top 3
local function placeString(Str, weapon, w, attack, stat, desc, desc2, star2)
local str = mw.clone(Str)
local place = { '', 'Second ', 'Third ' }
local contains, ind, count
local star = (not(star2) or weapon.Slot == 'Melee' or weapon.Slot == 'Arch-Melee') and '' or '*'
local function concat(t2, ind)
local t = Table.deepCopy(t2)
local str, count = ''
for i, _ in ipairs(t) do count = i end
if ind ~= nil then t[ind] = t[count]; t[count] = nil; count = count - 1 end
table.sort(t)
if count > 3 then count = 3 end
if count == 1 then str = '{{Weapon|'..t[1]..'}}'
elseif count == 2 then str = '{{Weapon|'..t[1]..'}} and {{Weapon|'..t[2]..'}}'
else
for i = 1, count - 1 do
str = str..'{{Weapon|'..t[i]..'}}, '
end
str = str..'and {{Weapon|'..t[count]..'}}'
end
t = nil
return str
end
for i, a in ipairs(w[attack].Top3[stat]) do
contains, ind = Table.contains(a[2], weapon.Name)
if contains and (stat ~= 'Multi' or a[1] ~= 1) and a[1] then
for j, _ in ipairs(a[2]) do count = j end
if count == 1 then
table.insert(str, star..'*'..(i == 1 and desc2 or place[i]..desc2:lower())..'est '..desc..(i > 1 and ' behind '..concat(w[attack].Top3[stat][i-1][2]) or '')..'.')
else
table.insert(str, star..'*Tied for '..(i == 1 and desc2 or place[i]..desc2):lower()..'est '..desc..' with '..concat(w[attack].Top3[stat][i][2], ind)..(i > 1 and ', behind '..concat(w[attack].Top3[stat][i-1][2]) or '')..'.')
end
break
end
end
return str, contains
end
--- Builds advantages string for a stat of a weapon's attack.
-- @function adv
-- @param {table} str Table of advantages
-- @param {table} weapon Weapon table
-- @param {table} w ppData subtable
-- @param {string} attack Weapon attack mode
-- @param {string} stat Attack stat in ppData
-- @param {string} desc Stat descritpion
-- @param {string} desc2 'High' (default), 'Fast', 'Large', etc.
-- @param {bool} star2 Make sub-bullet point
-- @returns {table} str Table of advantages
local function adv(str, weapon, W, attack, stat, desc, desc2, star2)
w = W[attack] and W or ppData[weapon.Slot]
local percentiles, contains = w[attack].Percentiles[stat]
local temp, star = 0, (not(star2) or weapon.Slot == 'Melee' or weapon.Slot == 'Arch-Melee') and '' or '*'
local tempStr = {}
desc2 = desc2 and desc2 or 'High'
tempStr, contains = placeString(str, weapon, w, attack, stat, desc, desc2:gsub('e', ''), star2)
if stat == 'Damage' then
for _, dmg in pairs(weapon[attack].Damage) do temp = temp + dmg end
if (weapon[attack].AttackName):lower():find('aoe') then
local prevAtt = 'Attack'..(tonumber(attack:sub(#attack)) - 1)
prevAtt = weapon[prevAtt] or {}
for _, damage in pairs(prevAtt.Damage) do
temp = temp + damage
end
end
elseif stat == 'MeleeRange' then
temp = weapon[attack][stat] or weapon[stat] or 0
else
temp = weapon[attack][stat] or weapon[stat] or 1
end
if stat == 'Reload' then temp = 1 / temp end
if temp == math.huge then
table.insert(str, star..'*Infinite '..desc..'.')
elseif temp >= percentiles[1] then
if contains then str = tempStr
else table.insert(str, star..'*Very '..desc2:lower()..' '..desc..'.') end
elseif temp >= percentiles[2] then
if contains then str = tempStr
else table.insert(str, star..'*'..desc2..' '..desc..'.') end
elseif temp >= percentiles[3] then
if contains then str = tempStr
else table.insert(str, star..'*Above average '..desc..'.') end
end
return str
end
--- Builds disadvantages string for a stat of a weapon's attack.
-- @function dis
-- @param {table} str Table of disadvantages
-- @param {table} weapon Weapon table
-- @param {table} w ppData subtable
-- @param {string} attack Weapon attack mode
-- @param {string} stat Attack stat in ppData
-- @param {string} desc Stat descritpion
-- @param {string} desc2 'Low' (default), 'Slow', 'Small', etc.
-- @param {bool} star2 Make sub-bullet point
-- @returns {table} str Table of disadvantages
local function dis(str, weapon, W, attack, stat, desc, desc2, star2)
w = W[attack] and W or ppData[weapon.Slot]
local percentiles, contains = w[attack].Percentiles[stat]
local temp, star = 0, (not(star2) or weapon.Slot == 'Melee' or weapon.Slot == 'Arch-Melee') and '' or '*'
desc2 = desc2 or 'Low'
if stat == 'Damage' then
for _, dmg in pairs(weapon[attack].Damage) do temp = temp + dmg end
if (weapon[attack].AttackName):lower():find('aoe') then
local prevAtt = 'Attack'..(tonumber(attack:sub(#attack)) - 1)
prevAtt = weapon[prevAtt] or {}
for _, damage in pairs(prevAtt.Damage) do
temp = temp + damage
end
end
elseif stat == 'MeleeRange' then
temp = weapon[attack][stat] or weapon[stat] or 0
else
temp = weapon[attack][stat] or weapon[stat] or 1
end
if stat == 'Reload' then temp = 1 / temp end
if temp < percentiles[7] then
table.insert(str, star..'*Very '..desc2:lower()..' '..desc..'.')
elseif temp < percentiles[6] then
table.insert(str, star..'*'..desc2..' '..desc..'.')
elseif temp < percentiles[5] then
table.insert(str, star..'*Below average '..desc..'.')
end
return str
end
--- Builds string of advantages or disadvantages of a weapon.
-- Percentiles categorization:
-- * 90-100% - "Very high"
-- * 77.5-90% - "High"
-- * 65-77.5% - "Above average"
-- * 40-65% - Average, doesn't display anything
-- * 27.5-40% - "Below average"
-- * 15-27.5% - "Low"
-- * 0-15% - "Very low"
-- @function p.advantages
-- @param {table} frame Frame object
-- @returns {string} str preprocessed wikitext string
function p.advantages(frame)
local args = frame.args
local name = args['Name']
local disAdv = (not(args['Dis']) and args['Dis'] ~= '') and true or false
local weapon = WeaponData.Weapons[name]
local attacks = {'Normal Attack', 'Secondary Attack', 'Charge Attack',
'Charge Attack AoE', 'Area Attack', 'Secondary Charge Attack',
'Secondary Charge Attack AoE', 'Charged Throw Attack', 'Throw Attack'}
local count, check = 0
check = args[1]:gsub('\n', ''):gsub('%*', '\n%*')
local str = { "{{text||"..(disAdv and "'''Advantages:'''" or "'''Disadvantages:'''").."|hover=As compared to other ", check }
local function disadv(str, weapon, w, attack, stat, desc, desc2, star)
if disAdv then
return adv(str, weapon, w, attack, stat, desc, desc2, star)
end
return dis(str, weapon, w, attack, stat, desc, desc2, star)
end
for _, v in ipairs(ppData[weapon.Slot]) do
if weapon.Slot ~= 'Melee' and weapon.Slot ~= 'Arch-Melee' then
if Table.contains(v.Classes, weapon.Class) then
for _, w in ipairs(v.Triggers) do
if Table.contains(w[1], weapon.Trigger) then
count = 0
for i = 1, 7 do
if weapon['Attack'..i] and w['Attack'..i] and not((weapon['Attack'..i].AttackName or attacks[i]):lower():find('aoe')) then
count = count + 1;
end
end
if count ~= 0 then str[1] = str[1]..w.Name.." "..weapon.Class:lower().."-type " end
str[1] = str[1]..weapon.Slot:lower().." weapons.}}"
for i = 1, 7 do
check = weapon['Attack'..i] and not((weapon['Attack'..i].AttackName or attacks[i]):lower():find('aoe'))
if count ~= 0 then check = check and w['Attack'..i] end
if check then
if count > 1 then table.insert(str, '*'..(weapon['Attack'..i].AttackName or attacks[i])..':') end
str = disadv(str, weapon, w, 'Attack'..i, 'Damage', 'damage per shot', nil, count > 1)
str = disadv(str, weapon, w, 'Attack'..i, 'FireRate', '[[fire rate]]', nil, count > 1)
str = disadv(str, weapon, w, 'Attack'..i, 'CritChance', '[[critical chance]]', nil, count > 1)
str = disadv(str, weapon, w, 'Attack'..i, 'CritMultiplier', '[[critical multiplier]]', nil, count > 1)
str = disadv(str, weapon, w, 'Attack'..i, 'StatusChance', '[[status chance]]', nil, count > 1)
str = disadv(str, weapon, w, 'Attack'..i, 'Multishot', '[[multishot]]', nil, count > 1)
if not disAdv then
str = dis(str, weapon, w, 'Attack'..i, 'Reload', '[[reload]] speed', 'Slow', count > 1)
str = dis(str, weapon, w, 'Attack'..i, 'Magazine', 'magazine', 'Small', count > 1)
str = dis(str, weapon, w, 'Attack'..i, 'MaxAmmo', 'ammo pool', 'Small', count > 1)
else
str = adv(str, weapon, w, 'Attack'..i, 'Reload', '[[reload]] speed', 'Fast', count > 1)
str = adv(str, weapon, w, 'Attack'..i, 'Magazine', 'magazine', 'Large', count > 1)
str = adv(str, weapon, w, 'Attack'..i, 'MaxAmmo', 'ammo pool', 'Large', count > 1)
end
end
end
end
end
end
else
for _, w in ipairs(v.Classes) do
if Table.contains(w[1], weapon.Class) then
if w.Attack1 then str[1] = str[1]..w.Name.."-type " end
str[1] = str[1]..weapon.Slot:lower().." weapons.}}"
str = disadv(str, weapon, w, 'Attack1', 'Damage', 'damage')
str = disadv(str, weapon, w, 'Attack1', 'FireRate', '[[attack speed]]')
str = disadv(str, weapon, w, 'Attack1', 'CritChance', '[[critical chance]]')
str = disadv(str, weapon, w, 'Attack1', 'CritMultiplier', '[[critical multiplier]]')
str = disadv(str, weapon, w, 'Attack1', 'StatusChance', '[[status chance]]')
if not disAdv then
str = dis(str, weapon, w, 'Attack1', 'MeleeRange', 'range', 'Short')
else
str = adv(str, weapon, w, 'Attack1', 'MeleeRange', 'range', 'Long')
end
end
end
end
end
if str[2] == "" and #str == 2 then str[2] = "*None[[Category:Weapons with no "..(disAdv and "advantages" or "disadvantages").."]]" end
str = table.concat(str, '\n')
return frame:preprocess(str)
end
local WeapData
--- Inserts weapon into appropriate subtable
-- @function insert
-- @param {table} t Table to insert into
-- @param {string} key What subtable to insert into
-- @param {string} name Weapon name
-- @param {number} val Weapon stat value
-- @returns {table} t New table
local function insert(t, key, name, val)
local exist = false
for _, v in ipairs(t[key]) do
if v[1] == val then
table.insert(v[2], name)
exist = true
break
end
end
if not exist then
table.insert(t[key], {val, {name}})
end
return t
end
--- Gets the top percentile and top 3 stats of a specified category of weapons.
-- @function getWeaponStats
-- @param {string} slot Weapon slot (primary, secondary, etc.)
-- @param {string} class Weapon class
-- @param {string} trigger Weapon trigger type
-- @param {string} attack Weapon attack (Attack1, Attack2, etc.)
-- @returns {table} vals3 Table of percentiles
-- @returns {table} top3 Table of top 3 weapons for each stat
-- @returns {number} count Number of weapons that fit specified cat
-- @returns {table} Weapons Weapons that fit specified cat
local function getWeaponStats(slot, classes, triggers, att, max)
local attacks = { 'Normal Attack', 'Secondary Attack', 'Charge Attack',
'Charge Area Attack', 'Area Attack', 'Secondary Charge Attack',
'Secondary Area Attack', 'Charged Throw Attack', 'Throw Attack' }
local count, temp, i, specific = 0, 0, 0
local vals, vals2, vals3, top3, stats
local Weapons = {}
if slot == 'Melee' or slot == 'Arch-Melee' or slot == 'Arch-Gun' then
vals = { Damage = {}, CritChance = {}, CritMultiplier = {},
StatusChance = {}, FireRate = {}, MeleeRange = {} }
vals2 = { Damage = {}, CritChance = {}, CritMultiplier = {},
StatusChance = {}, FireRate = {}, MeleeRange = {} }
vals3 = { Damage = {}, CritChance = {}, CritMultiplier = {},
StatusChance = {}, FireRate = {}, MeleeRange = {} }
top3 = { Damage = {}, CritChance = {}, CritMultiplier = {},
StatusChance = {}, FireRate = {}, MeleeRange = {} }
stats = { 'Damage', 'CritChance', 'CritMultiplier',
'StatusChance', 'FireRate', 'MeleeRange' }
else
vals = { Damage = {}, Multishot = {}, CritChance = {}, CritMultiplier = {},
StatusChance = {}, FireRate = {}, Magazine = {}, MaxAmmo = {}, Reload = {} }
vals2 = { Damage = {}, Multishot = {}, CritChance = {}, CritMultiplier = {},
StatusChance = {}, FireRate = {}, Magazine = {}, MaxAmmo = {}, Reload = {} }
vals3 = { Damage = {}, Multishot = {}, CritChance = {}, CritMultiplier = {},
StatusChance = {}, FireRate = {}, Magazine = {}, MaxAmmo = {}, Reload = {} }
top3 = { Damage = {}, Multishot = {}, CritChance = {}, CritMultiplier = {},
StatusChance = {}, FireRate = {}, Magazine = {}, MaxAmmo = {}, Reload = {} }
stats = { 'Damage', 'Multishot', 'CritChance', 'CritMultiplier',
'StatusChance', 'FireRate', 'Magazine', 'MaxAmmo', 'Reload' }
end
for name, weapon in pairs(WeapData) do
specific = not weapon.Class:find('Kitgun')
if classes then specific = Table.contains(classes, weapon.Class) end
if triggers then specific = specific and Table.contains(triggers, weapon.Trigger) end
if not(weapon.IgnoreEntry) and weapon.Slot == slot and specific then
table.insert(Weapons, {Name = name, Family = weapon.Family or name, Image = weapon.Image or "Panel.png"})
if weapon['Attack'..att] and not((weapon['Attack'..att].AttackName or attacks[att]):lower():find('aoe')) then
count = count + 1
for _, stat in ipairs(stats) do
temp = 0
if stat == 'Damage' then
for _, damage in pairs(weapon['Attack'..att].Damage) do
temp = temp + damage
end
temp = temp * (weapon['Attack'..att].Multishot or weapon.Multishot or 1)
else
temp = weapon['Attack'..att][stat] or weapon[stat] or 1
end
if stat == 'Reload' then temp = 1 / temp end
top3 = insert(top3, stat, name, temp)
i = '"'..temp..'"'
if not vals[stat][i] then vals[stat][i] = 0 end
vals[stat][i] = vals[stat][i] + 1
end
end
if att == max then WeapData[name] = nil end
end
end
for _, stat in ipairs(stats) do
for val, c in pairs(vals[stat]) do
i = val:gsub('"', '')
table.insert(vals2[stat], { tonumber(i), c / count })
end
table.sort(vals2[stat], function(a, b) return a[1] < b[1] end)
for i, val in ipairs(vals2[stat]) do
if i > 1 then val[2] = val[2] + vals2[stat][i - 1][2] end
end
table.sort(vals2[stat], function(a, b) return a[1] > b[1] end)
i = 0
-- 0.9, 0.775, 0.65, 0.525, 0.4, 0.275, 0.15
for j, val in ipairs(vals2[stat]) do
if 0.90 - val[2] - i/8 > 0 then table.insert(vals3[stat], vals2[stat][j - 1][1]); i = i + 1 end
if i > 6 then break; end
end
i = #vals3[stat]
for j = 1, math.ceil((7 - i) / 2) do
table.insert(vals3[stat], 1, "math.huge")
end
for j = 1, math.floor((7 - i) / 2) do
table.insert(vals3[stat], "-math.huge")
end
end
vals = nil
vals2 = nil
for _, v in pairs(top3) do
table.sort(v, function(a, b) return a[1] > b[1] end)
for i = #v, 4, -1 do
table.remove(v, i)
end
end
if top3.Reload and top3.Reload[1] then top3.Reload[1][1] = 1 / top3.Reload[1][1] end
if top3.Reload and top3.Reload[2] then top3.Reload[2][1] = 1 / top3.Reload[2][1] end
if top3.Reload and top3.Reload[3] then top3.Reload[3][1] = 1 / top3.Reload[3][1] end
return vals3, top3, count, Weapons
end
--- Dumps a preprocessed table of weapon stats and info to copy into M:Weapons/ppdata.
-- Invoked on [[Module:Weapons/ppdata/doc]].
-- @function p.ppData
-- @param {table} frame Frame object
-- @returns {string} ppdata preprocessed wikitext string in Lua table formatting
function p.ppData(frame)
local subData = {
Primary = {
{ Type = 'Arm Cannons',
Classes = {'Arm Cannon'},
Triggers = {
{ Name = 'Auto',
{'Auto'},
Weapons = {},
},
},
},
{ Type = 'Assault Rifles',
Classes = {'Rifle'},
Triggers = {
{ Name = 'Active',
{'Active'},
Weapons = {},
},
{ Name = 'Auto',
{'Auto'},
Weapons = {},
},
{ Name = 'Auto-Burst',
{'Auto Burst'},
Weapons = {},
},
{ Name = 'Auto-Spool',
{'Auto-Spool'},
Weapons = {},
},
{ Name = 'Burst',
{'Burst'},
Weapons = {},
},
{ Name = 'Charge',
{'Charge'},
Weapons = {},
},
{ Name = 'Held',
{'Held'},
Weapons = {},
},
{ Name = 'Hybrid',
{'Auto / Semi', 'Burst / Semi', 'Burst / Semi / Auto', 'Auto / Charge'},
Weapons = {},
},
{ Name = 'Semi-Auto',
{'Semi-Auto'},
Weapons = {},
},
},
},
{ Type = 'Bows',
Classes = {'Bow', 'Crossbow', 'Exalted Weapon'},
Triggers = {
{ Name = 'Auto',
{'Auto'},
Weapons = {},
},
{ Name = 'Charge',
{'Charge'},
Weapons = {},
},
{ Name = 'Hybrid',
{'Semi / Burst'},
Weapons = {},
},
},
},
{ Type = 'Launchers',
Classes = {'Launcher'},
Triggers = {
{ Name = 'Active',
{'Active'},
Weapons = {},
},
{ Name = 'Auto',
{'Auto'},
Weapons = {},
},
{ Name = 'Charge',
{'Charge'},
Weapons = {},
},
{ Name = 'Semi-Auto',
{'Semi-Auto'},
Weapons = {},
},
},
},
{ Type = 'Shotguns',
Classes = {'Shotgun'},
Triggers = {
{ Name = 'Auto',
{'Auto'},
Weapons = {},
},
{ Name = 'Auto-Spool',
{'Auto-Spool'},
Weapons = {},
},
{ Name = 'Charge',
{'Charge'},
Weapons = {},
},
{ Name = 'Duplex',
{'Duplex'},
Weapons = {},
},
{ Name = 'Held',
{'Held'},
Weapons = {},
},
{ Name = 'Hybrid',
{'Auto / Semi'},
Weapons = {},
},
{ Name = 'Semi-Auto',
{'Semi-Auto'},
Weapons = {},
},
},
},
{ Type = 'Sniper Rifles',
Classes = {'Sniper Rifle'},
Triggers = {
{ Name = 'Charge',
{'Charge'},
Weapons = {},
},
{ Name = 'Semi-Auto',
{'Semi-Auto'},
Weapons = {},
},
},
},
{ Type = 'Spearguns',
Classes = {'Speargun'},
Triggers = {
{ Name = 'Auto',
{'Auto'},
Weapons = {},
},
{ Name = 'Charge',
{'Auto Charge', 'Charge'},
Weapons = {},
},
},
},
},
Secondary = {
{ Type = 'Crossbows',
Classes = {'Crossbow'},
Triggers = {
{ Name = 'Hybrid',
{'Burst / Charge'},
Weapons = {},
},
},
},
{ Type = 'Dual Pistols',
Classes = {'Dual Pistols', 'Exalted Weapon'},
Triggers = {
{ Name = 'Auto',
{'Auto'},
Weapons = {},
},
{ Name = 'Auto-Spool',
{'Auto-Spool'},
Weapons = {},
},
{ Name = 'Burst',
{'Burst'},
Weapons = {},
},
{ Name = 'Charge',
{'Charge'},
Weapons = {},
},
{ Name = 'Semi-Auto',
{'Semi-Auto'},
Weapons = {},
},
},
},
{ Type = 'Dual Shotguns',
Classes = {'Dual Shotguns'},
Triggers = {
{ Name = 'Auto-Spool',
{'Auto-Spool'},
Weapons = {},
},
{ Name = 'Semi-Auto',
{'Semi-Auto'},
Weapons = {},
},
},
},
{ Type = 'Pistols',
Classes = {'Pistol', 'Exalted Weapon'},
Triggers = {
{ Name = 'Auto',
{'Auto'},
Weapons = {},
},
{ Name = 'Burst',
{'Burst'},
Weapons = {},
},
{ Name = 'Charge',
{'Charge'},
Weapons = {},
},
{ Name = 'Duplex',
{'Duplex'},
Weapons = {},
},
{ Name = 'Held',
{'Held'},
Weapons = {},
},
{ Name = 'Hybrid',
{'Auto / Burst', 'Semi / Charge'},
Weapons = {},
},
{ Name = 'Semi-Auto',
{'Semi-Auto'},
Weapons = {},
},
},
},
{ Type = 'Shotgun Sidearms',
Classes = {'Shotgun Sidearm'},
Triggers = {
{ Name = 'Auto',
{'Auto'},
Weapons = {},
},
{ Name = 'Auto-Spool',
{'Auto-Spool'},
Weapons = {},
},
{ Name = 'Semi-Auto',
{'Semi-Auto'},
Weapons = {},
},
},
},
{ Type = 'Thrown',
Classes = {'Thrown'},
Triggers = {
{ Name = 'Active',
{'Active'},
Weapons = {},
},
{ Name = 'Auto',
{'Auto'},
Weapons = {},
},
{ Name = 'Hybrid',
{'Auto / Semi'},
Weapons = {},
},
},
},
},
Melee = {
{ Type = 'Brawler',
Classes = {
{ Name = 'Claws',
{'Claws'},
Weapons = {},
},
{ Name = 'Fist',
{'Fist'},
Weapons = {},
},
{ Name = 'Sparring',
{'Sparring'},
Weapons = {},
},
},
},
{ Type = 'Daggers',
Classes = {
{ Name = 'Dagger',
{'Dagger'},
Weapons = {},
},
{ Name = 'Dual Daggers',
{'Dual Daggers'},
Weapons = {},
},
},
},
{ Type = 'Heavies',
Classes = {
{ Name = 'Heavy Blade',
{'Heavy Blade'},
Weapons = {},
},
{ Name = 'Hammer',
{'Hammer'},
Weapons = {},
},
},
},
{ Type = 'Ranged',
Classes = {
{ Name = 'Blade and Whip',
{'Blade and Whip'},
Weapons = {},
},
{ Name = 'Glaive',
{'Glaive'},
Weapons = {},
},
{ Name = 'Gunblade',
{'Gunblade'},
Weapons = {},
},
{ Name = 'Whip',
{'Whip'},
Weapons = {},
},
},
},
{ Type = 'Staves',
Classes = {
{ Name = 'Nunchaku',
{'Nunchaku'},
Weapons = {},
},
{ Name = 'Polearm',
{'Polearm'},
Weapons = {},
},
{ Name = 'Scythe',
{'Scythe'},
Weapons = {},
},
{ Name = 'Staff',
{'Staff'},
Weapons = {},
},
},
},
{ Type = 'Swords',
Classes = {
{ Name = 'Dual Swords',
{'Dual Swords'},
Weapons = {},
},
{ Name = 'Machete',
{'Machete'},
Weapons = {},
},
{ Name = 'Nikana',
{'Nikana'},
Weapons = {},
},
{ Name = 'Rapier',
{'Rapier'},
Weapons = {},
},
{ Name = 'Sword',
{'Sword'},
Weapons = {},
},
{ Name = 'Sword and Shield',
{'Sword and Shield'},
Weapons = {},
},
{ Name = 'Two-Handed Nikana',
{'Two-Handed Nikana'},
Weapons = {},
},
},
},
{ Type = 'Miscellaneous',
Classes = {
{ Name = 'Assault Saw',
{'Assault Saw'},
Weapons = {},
},
{ Name = 'Exalted Weapon',
{'Exalted Weapon'},
Weapons = {},
},
{ Name = 'Tonfa',
{'Tonfa'},
Weapons = {},
},
{ Name = 'Warfan',
{'Warfan'},
Weapons = {},
},
},
},
},
Archwing = {
{ Type = 'Archwing Weapons',
Classes = {
{ Name = 'Arch-Gun',
{'Arch-Gun'},
Weapons = {},
},
{ Name = 'Arch-Melee',
{'Arch-Melee'},
Weapons = {},
},
{ Name = 'Exalted Weapon',
{'Exalted Weapon'},
Weapons = {},
},
},
},
},
Robotic = {
{ Type = 'Assault Rifles',
Classes = {'Rifle'},
Triggers = {
{ Name = 'Auto',
{'Auto'},
Weapons = {},
},
{ Name = 'Auto-Spool',
{'Auto-Spool'},
Weapons = {},
},
{ Name = 'Burst',
{'Burst'},
Weapons = {},
},
{ Name = 'Charge',
{'Charge'},
Weapons = {},
},
{ Name = 'Held',
{'Held'},
Weapons = {},
},
{ Name = 'Semi-Auto',
{'Semi-Auto'},
Weapons = {},
},
},
},
{ Type = 'Shotguns',
Classes = {'Shotgun'},
Triggers = {
{ Name = 'Auto',
{'Auto'},
Weapons = {},
},
},
},
{ Type = 'Sniper Rifles',
Classes = {'Sniper Rifle'},
Triggers = {
{ Name = 'Auto',
{'Auto'},
Weapons = {},
},
},
},
{ Type = 'Pistols',
Classes = {'Pistol'},
Triggers = {
{ Name = 'Burst',
{'Burst'},
Weapons = {},
},
},
},
{ Type = 'Melee',
Classes = {'Glaive'},
Triggers = {
{ Name = 'Auto',
{'Auto'},
Weapons = {},
},
},
},
},
}
local WEAPON_SLOTS = {
{'Primary', 'Primary'},
{'Secondary', 'Secondary'},
{'Melee', 'Melee'},
{'Arch-Melee', 'Archwing'},
{'Arch-Gun', 'Archwing'},
{'Robotic', 'Robotic'},
}
local str = {'<pre>'}
local percentiles, top3, count, data
local breakLoops = false
WeapData = Table.deepCopy(WeaponData.Weapons)
for _, weapSlot in ipairs(WEAPON_SLOTS) do
if weapSlot[1] == 'Melee' or weapSlot[1] == 'Arch-Melee' then
percentiles, top3, count = getWeaponStats(weapSlot[1], nil, nil, 1, 2)
if count >= 3 then
subData[weapSlot[2]].Attack1 = {}
subData[weapSlot[2]].Attack1.Percentiles = percentiles
subData[weapSlot[2]].Attack1.Top3 = top3
end
for i, v in ipairs(subData[weapSlot[2]]) do
for j, classes in ipairs(v.Classes) do
percentiles, top3, count, weaps = getWeaponStats(weapSlot[1], classes[1], nil, 1, 1)
if count >= 3 then
subData[weapSlot[2]][i].Classes[j].Attack1 = {}
subData[weapSlot[2]][i].Classes[j].Attack1.Percentiles = percentiles
subData[weapSlot[2]][i].Classes[j].Attack1.Top3 = top3
end
classes.Weapons = weaps
end
end
elseif weapSlot[1] == 'Arch-Gun' then
for i = 1, 7 do
percentiles, top3, count = getWeaponStats(weapSlot[1], nil, nil, i, 8)
if count >= 3 then
subData[weapSlot[2]]['Attack'..i] = {}
subData[weapSlot[2]]['Attack'..i].Percentiles = percentiles
subData[weapSlot[2]]['Attack'..i].Top3 = top3
end
end
for i, v in ipairs(subData[weapSlot[2]]) do
for j, classes in ipairs(v.Classes) do
for k = 1, 7 do
percentiles, top3, count, weaps = getWeaponStats(weapSlot[1], classes[1], nil, k, 7)
if count >= 3 then
subData[weapSlot[2]][i].Classes[j]['Attack'..k] = {}
subData[weapSlot[2]][i].Classes[j]['Attack'..k].Percentiles = percentiles
subData[weapSlot[2]][i].Classes[j]['Attack'..k].Top3 = top3
end
end
classes.Weapons = weaps
table.insert(classes.Weapons, {Name = 'Arquebex', Family = 'Arquebex', Image = 'ArquebexFixed.png'})
end
end
else
for i = 1, 7 do
percentiles, top3, count = getWeaponStats(weapSlot[1], nil, nil, i, 8)
if count >= 3 then
subData[weapSlot[2]]['Attack'..i] = {}
subData[weapSlot[2]]['Attack'..i].Percentiles = percentiles
subData[weapSlot[2]]['Attack'..i].Top3 = top3
end
end
for i, v in ipairs(subData[weapSlot[2]]) do
for j, triggers in ipairs(v.Triggers) do
for k = 1, 7 do
percentiles, top3, count, weaps = getWeaponStats(weapSlot[1], v.Classes, triggers[1], k, 7)
if count >= 3 then
subData[weapSlot[2]][i].Triggers[j]['Attack'..k] = {}
subData[weapSlot[2]][i].Triggers[j]['Attack'..k].Percentiles = percentiles
subData[weapSlot[2]][i].Triggers[j]['Attack'..k].Top3 = top3
end
end
triggers.Weapons = weaps
end
end
end
end
data = mw.dumpObject(subData):gsub('table#%d+ %{', '%{'):gsub('%["', '')
data = data:gsub('"%]', ''):gsub('-nan', 'nil'):gsub(' ', ' '):gsub('inf', 'math.huge')
data = data:gsub('"math.huge"', 'math.huge'):gsub('"%-math.huge"', '%-math.huge')
table.insert(str, 'return '..data..'\n')
table.insert(str, '</pre>')
return frame:preprocess(table.concat(str, '\n'))
end
return p