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._isVariant(weaponName)
(function)- Checks if a weapon is a variant or not.
- Parameter:
weaponName
Weapon name (string) - Returns:
- True if weapon is a variant, false otherwise (boolean)
- Weapon's variant name or "Base" if weapon is not a variant (string)
- Weapon name, same as weaponName (string)
weapons._buildName(baseName, variant)
(function)- Builds the full name of a weapon's variant. Does not check if it exists or not.
- Parameters:
baseName
Weapon's base name (e.g. "Braton") (string)variant
Variant name (e.g. "Vandal"); if nil, returns base weapon name instead (string; optional)
- Returns: Weapon's variant name (e.g. "Braton Vandal") (string)
weapons._getWeapon(weaponName, 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._getValue(Weapon, key, attack)
(function)- Gets the raw value of a certain statistic of a weapon.
- Parameters:
Weapon
Weapon table (table)key
Name of key (string)attack
Name of attack to search through; defaults to 'Attack1' or what '_TooltipAttackDisplay' is set to (string; optional)
- Returns: Value of statistic (string, number)
weapons._getFormattedValue(Weapon, keyName, attackName)
(function)- Gets the formatted value of a certain statistic of a weapon to be displayed the wiki.
- Parameters:
Weapon
Weapon table (table)keyName
Name of key (string)attackName
Name of attack to search through; defaults to 'Attack1' (string; optional)
- Returns: Value of statistic (string)
weapons._attackLoop(Weapon)
(function)- Loops through all possible attacks that a weapon may have. use ipairs(Weapon.attack) instead
- 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._getWeapons(validateFunction, getConclave, ignoreIgnore)
(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)ignoreIgnore
If true, ignores the _IgnoreEntry flag, false otherwise; defaults to false (boolean)
- Returns: Table of weapon table entries as seen in
/data
(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)
(function)- Returns all conclave melee weapons. If weapType is not nil, only grab for a specific type For example, if weapType is "Nikana", only pull Nikanas.
- Parameter:
weapClass
Name of melee class to filter by; if nil then gets all melee weapons (string; optional) - Returns: An array of melee weapon table entries as seen in
/Conclave/data
(table) weapons.getStanceWeaponList(table})
(function)- Builds list of weapons that can equip a particlar stance mod as seen on Template:StanceWeapons.
- Parameter:
table}
frame Frame object w/ first argument being string stanceName - 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._getCategory(weapon)
(function)- Gets the weapon class of 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.buildDamageTypeTable(frame)
(function)- Builds a table that lists out all weapons with a certain damage type
- Parameter:
frame
Frame object (table) - Returns: Wikitext of resultant wikitable (string)
weapons._shortLinkList(Weapon, tooltip)
(function)- Builds a list of weapons, with variants being next to base weapon name inside parentheses (e. g. Braton ( 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.getMasteryShortList(frame)
(function)- Builds a list of weapons' mastery requirements as seen on Template:EquipmentUnlock, Template:EquipmentUnlock/Primary, Template:EquipmentUnlock/Secondary, Template:EquipmentUnlock/Melee, etc.
- Parameter:
frame
Frame object w/ first argument being a string weaponSlot (table) - Returns: Wikitext of resultant list (string)
weapons.getConclaveList(frame)
(function)- Builds a list of PvP weapons as seen on PvP#Limitations.
- Parameter:
frame
Frame object w/ first argument being a string weaponSlot (table) - Returns: Wikitext of resultant list (string)
weapons.getRivenDispositionTable(frame)
(function)- Builds a disposition wikitable as seen on Riven Mods/Weapon Dispos.
- Parameter:
frame
Frame object w/ first argument being a string weaponSlot (table) - Returns: Wikitext of resultant wikitable (string)
Other items
getWeaponGallery(Weapons)
(function)- Builds a weapon gallery.
- Parameter:
Weapons
Array of weapon table entries to be displayed (table) - Returns: Resultant wikitext of gallery local function getWeaponGallery(Weapons) local result = {'') return table.concat(result, '\n') end local result = {} -- local result = { '{| style="margin:auto;text-align:center;"' } -- local nameRow = {} for i, Weapon in ipairs(Weapons) do -- table.insert(result, ('|style="width:165px"|File:%s'):format(statRead(Weapon, nil,'Image'),statRead(Weapon, nil,'Link'))) -- table.insert(nameRow, ('| style="vertical-align: text-top;" |%s'):format(statFormat(Weapon, nil,'NameLink'))) table.insert(result, statFormat(Weapon, nil,'NameLink')) -- if i % 5 == 0 then -- table.insert(result, table.concat(nameRow,'\n')) -- table.insert(result, "|-") -- nameRow = {} -- end end -- table.insert(result, table.concat(nameRow,'\n')) -- table.insert(result, '|}') return table.concat(result, '\n') end (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:Gigamicro|Gigamicro]]
-- @attribution [[User:Flaicher|Flaicher]]
-- @attribution [[User:FINNER|FINNER]]
-- @attribution [[User:Cephalon Scientia|Cephalon Scientia]]
-- @attribution [[User:Synthtech|Synthtech]]
-- @attribution [[User:Calenhed|Calenhed]]
-- @image IconPrimaryWeaponRifle.png
-- @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 unstable
-- <nowiki>
-- TODO: Add LuaDoc style comments to new functions
-- TODO: Migrate comparison string builders to a submodule
local p = {}
local ConclaveData = mw.loadData [[Module:Weapons/Conclave/data]]
local StanceData = mw.loadData [[Module:Stances/data]]
local WeaponData = mw.loadData [[Module:Weapons/data]]
local Tooltip = require [[Module:Tooltips]] -- getFullTooptip
local Version = require [[Module:Version]] -- _getVersion, _getVersionDate
local Table = require [[Module:Table]] -- size, skpairs
local Polarity = require [[Module:Polarity]] -- _pols, _polarity
-- TODO: Could use M:DamageTypes instead of this table?
-- TODO: Remove ProgenitorBonus and MinProgenitorBonus keys from /data and only store
-- base values. In-game progenitor bonuses are implemented as a separate upgrade that is applied
-- to the base stats. If doing it this way, we have to remove MinProgenitorBonus as a key from
-- M:DamageTypes/data and update comparison string tables accordingly. In addition, a disclaimer
-- to Kuva/Tenet weapons may be added to weapon infobox saying that this weapon has a 'randomized' damage bonus
local DAMAGE_TYPES = {
"Impact", "Puncture", "Slash",
"Heat", "Cold", "Toxin", "Electricity",
"Blast", "Corrosive", "Radiation", "Magnetic", "Gas", "Viral",
"Void", "True", "ProgenitorBonus"
}
local VARIANT_LIST = {
"Prime", "Prisma", "Wraith", "Vandal", "Vaykor", "Synoid",
"Telos", "Secura", "Sancti", "Rakta", "Mara", "MK1", "Kuva"
}
table.unpack = table.unpack or unpack
local function dmgTooltip(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
--declare here so scope includes metatable functions
local default
--use after calc functions that might be repeated (cache[weapon][attack][key])
local cache = {}
local function cacheIn(weap,atk,key,val)
cache[weap] = cache[weap] or {Name=weap.Name}
cache[weap][atk] = cache[weap][atk] or {}
cache[weap][atk][key] = val or cache[weap][atk][key]
return cache[weap][atk][key]
end
local function cacheOut(weap,atk,key)
return ((cache[weap]or{})[atk]or{})[key]
end
local function get(weap,atk,k)
-- local c = cacheOut(weap,atk,k)
-- if c then return table.unpack(c) end
return atk and atk[k] or weap[k]
-- return atk and (atk[k] or atk.BaseAttack and get(weap,atk.BaseAttack,k)) or weap[k]
end
local function getAttack(weap,atk)
if type(atk)=='table' then return atk end
local key = atk or weap['_TooltipAttackDisplay'] or 'Attack1'
if type(key)=='string' then key = key:match'[0-9.]+' + 0 end
return weap.attack[key]
end
local function statRead(weap,atk,k)
atk = getAttack(weap,atk)
local c = cacheOut(weap,atk,k)
if c then return table.unpack(c) end
if type(default[k])~='table' then
default[k] = {default[k]}
end
-- local map, format = table.unpack(default[k])
local map = default[k][1]
default[k][1] = type(map)=='function' and map or (not map and get) or function(w,a,k)return get(w,a,k) or map end
-- default[k][2] = hasFormat(format) and format or makeFormat(default[k]) -- may already be taken care of by formatmt
return default[k][1](weap,atk,k)
end
local function hasFormat(v)
local a,b = pcall(function()return v.format end)
return a and b
end
local function makeFormat(maps)
local format = maps[2]
if type(format)=='function' or not format then
-- first param using : is the table being indexed, so that's going to be the map
-- prob completely unnecessary tbh
maps.format = format or function(self,...)return table.concat({...},' '):gsub('(%d%.%d%d)%d*','%1') end
maps[2] = maps
return maps
elseif type(format)=='table' and (format[1] or format.sep) then
function maps.format(self,...)
local t = {}
for i,v in pairs{...} do -- ipairs can't deal with skips
local w=format[i]
if type(w)=='function' then
t[i] = w(format,v)
elseif w=='' then -- omit
elseif type(w)=='string' then
t[i] = v and w:format(v) or w:gsub('%%%a','N/A')
else
t[i] = v
end
end
return table.concat(t,format.sep or ' ')
end
maps[2] = maps
return maps[2]
end
-- map[2] is not: a table with a 'format' key/a string, a function, nil/false, or an array
-- this should never happen
mw.log'hey uhh this map is invalid'
mw.log(mw.dumpObject(map))
mw.log(debug.traceback())
error'Invalid map'
end
local function statFormat(weap,atk,k, ...)
local value = {statRead(weap,atk,k)}
local format = default[k][2]
default[k][2] = hasFormat(format) and format or makeFormat(default[k])
return default[k][2]:format(table.unpack(value))
end
-- metafunctions to return functions with certain parameters
-- maps[1]
local function gets(k)
return function(weap, atk)
return get(weap,atk,k)
end
end
local function unpacks(var)
return function(weap, atk)
local v = get(weap,atk,var)
return type(v)~='table' and v or table.unpack(v)
end
end
local function ors(...)
local t = {...}
t = #t > 1 and t or t[1]
return function(weap, atk, self)
local val
for _,v in pairs(t) do
if false then
elseif type(v)=='string' then val=get(weap,atk,v) or (self~=v and statRead(weap,atk,v)) or nil
elseif type(v)=='function' then val=v(weap,atk,t)
elseif type(v)=='boolean' then return v
end
if val then return val end
end
val = t[#t]
return type(val)~='function' and val or nil
end
end
local function indexes(var, index)
return function(weap, atk, self)
local v = (self == index) and { get(weap, atk, var) } or { statRead(weap, atk, var) }
if (#v <= 1) then v = v[1] end
if type(v)=='function' then error(mw.dumpObject{ var=var, index=index, weap=weap, atk=atk, self=self, v=v }) end
-- v = #v>1 and v or v[1]
return type(v)=='table' and v[index] or
error('indexes return function given '..mw.dumpObject(v)..', asked for ['..index..']')
end
end
-- maps[2]
local function passes(func)
return function(self, ...)
return func(...)
end
end
local function percent(s, v)
return ('%.2f%%'):format(100*(v or s))
end
local function percents(s)
return function(self, v, ...)
return s:format(100*v, ...)
end
end
default = {
-- some setup so default[2][k] works
setmetatable({ k = 1 }, formatmt),
setmetatable({ k = 2 }, formatmt),
-- arrays of default values and format strings
key = {'default','%.2format'},
-- can generate values/formatting with functions
-- 'val,...' is the return from map[1]
key = {function(weap,atk)end,function(self,val,...)end},
-- If format is a table the return values will be passed to each function/format string in order (nil is a passthrough)
key = {function(weap,atk)return a,b,c,d end,{function(self,val)end,'%s',nil,'%d',sep=''}},
-- can omit second entry, can omit table
key = 'default',
-- nil means get from attack or weapon (same as omitting)
key = nil,
-- begin real keys --
AttackName = 'Normal',
-- AttackName = "Normal Attack", -- which is better default attack name?
AmmoCost = 1,
DamageBias = {function(weap, atk)
if not atk.Damage then
error'DamageBias: no Attack.Damage'
end
local total, bestdmg, bestdt = 0, 0, nil
for dt, dmg in pairs(atk.Damage) do
-- assume +60% progenitor bonus damage ('MinProgenitorBonus' is +25%)
local dmg = dt=='MinProgenitorBonus' and dmg*2.4 or dmg
if dmg > bestdmg then
bestdmg, bestdt = dmg, dt
end
total = total + dmg
end
if bestdmg==0 then
error('DamageBias: empty Attack.Damage; Attack is'..mw.dumpObject(atk))
end
return table.unpack(cacheIn(weap,atk,'DamageBias',{bestdmg/total, bestdt, total}))
end,{percent,passes(dmgTooltip),''}},
BiasPortion = {indexes('DamageBias',1), percent},
BiasType = {indexes('DamageBias',2), passes(dmgTooltip)},
BaseDamage = {indexes('DamageBias',3), '%d'},
TotalDamage = function(weap, atk)
return statRead(weap,atk,'BaseDamage') * statRead(weap,atk,'Multishot')
end,
ChargeTime = {0,'%.1fs'},
ExtraHeadshotDmg = {0, percents'+%.2f%%'},
Falloff = {function(weap, atk)
local fo=get(weap,atk,'Falloff') or {}
return fo.StartRange or 0, fo.EndRange or math.huge, fo.Reduction or 1
end, {'%.1fm (100%%) -','%.1fm', percents'(%.2f%%)'}},
-- Falloff = function(weap, atk) local fo=atk.Falloff or weap.Falloff return fo.StartRange, fo.EndRange, fo.Reduction end,
FalloffStart = {indexes('Falloff',1),'%.1fm'},
FalloffEnd = {indexes('Falloff',2),'%.1fm'},
FalloffReduction = {indexes('Falloff',3),percent},
HeadshotMultiplier = {1,'%.1fx'},
Multishot = 1,
PunchThrough = {0,'%.1fm'},
ShotSpeed = {'N/A','%.1fm/s'},
BurstDelay = {0,'%.1fs'},
CritChance = {0,percent},
CritMultiplier = {1,'%.1fx'},
ForcedProcs = {unpacks'ForcedProcs',{sep=', '}},
Radius = {0,'%.1fm'},
StatusChance = {0,percent},
Disposition = {function(w,a)
local d = get(w,a,'Disposition')
return d or 0/0, type(d)=='number' and math.floor(5*(d-(d<1 and 0.3 or 0.309))) or 0/0
end, function(s,v,d)
return default.Dispo[2](s,d)..(' (%.2fx)'):format(v)
end},
Dispo = {indexes('Disposition',2), function(s,d)
return d and d==d and ('●'):rep(math.min(d,5))..('○'):rep(5-d) or '×××××'
end},
Introduced = function(weap)
return weap['Introduced'] and Version._getVersion(weap['Introduced'])['Name'] or 'N/A'
end,
IntroducedDate = function(weap)
return weap['Introduced'] and Version._getVersionDate(weap['Introduced']) or 'N/A'
end,
Mastery = 0,
Link = {nil,'[[%s]]'},
Name = {nil,function(s,v)return Tooltip.getFullTooltip(v,'Weapons') end},
NameLink = {function(weap) return weap.Link,weap.Name end, '[[%s|%s]]'},
Polarities = {nil, passes(Polarity._pols)},
Traits = {unpacks'Traits',{sep=', '}},
Accuracy = 0,
--[=[AmmoType = function(weap)
return weap['AmmoType'] or (weap['Slot']=='Secondary' and 'Pistol') or ({
Rifle='Rifle',
Shotgun='Shotgun',
['Bow'] = 'Sniper',['Sniper Rifle'] = 'Sniper',['Launcher'] = 'Sniper',
})[weap['Class']] or ''
end,--]=]
AmmoType = function(weap,atk)
return get(weap,atk,'AmmoType') or ({
['Arch-Gun (Atmosphere)']='Heavy',
['Secondary']='Pistol',
})[weap['Slot']] or ({
['Rifle']='Rifle',
['Shotgun']='Shotgun',
['Bow']='Sniper',['Sniper Rifle']='Sniper',['Launcher']='Sniper',
})[weap['Class']] or 'None'
end,
ExilusPolarity = {nil, passes(Polarity._polarity)},
Magazine = 1,
MaxAmmo = 0,
Range = function(weap, atk) return get(weap,atk,'Range') or statRead(weap,atk,'ShotType') == 'Hit-Scan' and 300 or 0 end,
Reload = ors('Reload','RechargeTime',0),
RechargeTime = function(weap,atk)
return statRead(weap,atk,'ReloadStyle'):find'[Rr]egen' and statRead(weap,atk,'Magazine') / statRead(weap,atk,'ReloadRate') or nil
end,
ReloadRate = 0,
ReloadDelay = function(weap,atk)return get(weap,atk, ReloadDelay) or statRead(weap,atk,'Magazine')>1 and 0.2 or 0 end, -- approx
ReloadDelayEmpty = ors('ReloadDelayEmpty', 'ReloadDelay'),
ReloadStyle = 'Magazine',
Spool = 0,
Trigger = 'N/A',
BlockAngle = {0, '%d°'},
ComboDur = 0,
FollowThrough = 0,
HeavyAttack = 0,
HeavyRadialDmg = 0,
HeavySlamRadius = 0,
MeleeRange = 0,
SlamAttack = 0,
SlamRadialDmg = 0,
SlamRadius = 0,
SlideAttack = 0,
Stances = getWeaponStanceList, -- ? can't find this function
StancePolarity = {nil, passes(Polarity._polarity)},
WindUp = 0,
BurstCount = 1,
AvgProcCount = function(weap, atk)
return ( statRead(weap,atk,'StatusChance') + Table.size(statRead(weap,atk,'ForcedProcs')) ) * statRead(weap,atk,'Multishot')
end,
AvgProcPerSec = function(weap, atk)
return statRead(weap, atk, 'AvgProcCount') * statRead(weap, atk, 'EffectiveFireRate')-- * statRead(weap, atk, 'Multishot')
end,
InterShotTime = function(weap, atk)
local v = statRead(weap,atk,'Magazine')==1 and statRead(weap,atk,'Reload') or 0
if v==0 then v = 1/statRead(weap,atk,'FireRate') end
return v
end,
EffectiveFireRate = function(weap, atk)
return 1/( statRead(weap,atk,'ChargeTime')+statRead(weap,atk,'InterShotTime') )
end,
MagShots = function(weap,atk)
return math.floor(statRead(weap, atk, 'Magazine') / statRead(weap, atk, 'AmmoCost'))
end,
FireRate = function(weap,atk)
local dataFireRate = get(weap,atk,'FireRate')
if dataFireRate then return dataFireRate end
local count = statRead(weap, atk, 'BurstCount')
local fireRate = count / (1/statRead(weap, atk, 'BurstFireRate') + count*statRead(weap, atk, 'BurstDelay'))
-- if fireRate == fireRate and dataFireRate == dataFireRate and fireRate ~= dataFireRate then
-- mw.log('Fire rate of '..statRead(weap,atk,'Name')..': '..fireRate..' or '..dataFireRate..'?')
-- return dataFireRate
-- else
return fireRate
-- end
end,
BurstFireRate = function(weap,atk)
local dataBurstRate = get(weap,atk,'BurstFireRate')
if dataBurstRate then return dataBurstRate end
local count = statRead(weap, atk, 'BurstCount')
local fireRate = statRead(weap, atk, 'FireRate')
local burstRate = fireRate/(count - fireRate*count*statRead(weap, atk, 'BurstDelay'))
-- if burstRate == burstRate and dataBurstRate == dataBurstRate and burstRate ~= dataBurstRate then
-- mw.log('Burst Fire rate of '..statRead(weap,atk,'Name')..': '..burstRate..' or '..dataBurstRate..'?')
-- return dataBurstRate
-- else
return burstRate
-- end
end,
CalcDamage = function(weapon, attack)
-- Count
local burstCount = statRead(weapon, attack, 'BurstCount')
local tapShots = burstCount
local magTaps = statRead(weapon,attack,'MagShots')
local multishot = statRead(weapon, attack, 'Multishot')
-- Time
local shotTime = statRead(weapon,attack,'InterShotTime')
local chargeTime = statRead(weapon,attack,'ChargeTime')
local tapTime = chargeTime + (burstCount-1) * statRead(weapon, attack, 'BurstDelay')
local magDur = magTaps * tapTime
if magDur == 0 then if shotTime == 0 then shotTime = 1/statRead(weapon,attack,'FireRate') end magDur=shotTime end
local shotDelay = math.max(0,shotTime - tapTime)
-- Damage
local biasPortion, biasType, hitDamage = statRead(weapon, attack, 'DamageBias')
local avgCritMult = 1 + (statRead(weapon, attack, 'CritMultiplier')-1)*statRead(weapon, attack, 'CritChance')
local avgTap = hitDamage * avgCritMult * multishot * tapShots
local avgMag = avgTap * magTaps
local avgLifetimeDmg = avgMag * (1 + (statRead(weapon, attack, 'MaxAmmo')/statRead(weapon, attack, 'Magazine')))
-- Damage / Time
-- local avgShort = avgShot * multishot / shotTime
-- local avgShort = avgMag / magDur
local avgShort = avgTap / (tapTime + shotDelay)
-- which calc is better tho?
local avgSustained = avgMag/(magDur+statRead(weapon, attack, 'ReloadDelay')+statRead(weapon, attack, 'Reload'))
--[=[
mw.log(mw.dumpObject{statRead(weapon,attack,'Name'),
})--]=]
return table.unpack(cacheIn(weapon,attack,'CalcDamage',{hitDamage, avgTap, avgShort, avgSustained, avgLifetimeDmg, hitDamage * multishot / shotTime, avgMag}))
end,
ShotDmg = indexes('CalcDamage',1),
AvgShotDmg = indexes('CalcDamage',2), AvgTapDmg = indexes('CalcDamage',2),
BurstDps = indexes('CalcDamage',3),
SustainedDps = indexes('CalcDamage',4),
LifetimeDmg = indexes('CalcDamage',5),
BaseDps = indexes('CalcDamage',6),
MagDmg = indexes('CalcDamage',7),
IsMelee = function(weap,atk)return statRead(weap,atk,'Slot'):find('Melee') and true or false end,
IsSilent = ors('IsSilent','IsMelee', false),
Conclave = false,
Image = {'Panel.png','[[File:%s|link=]]'},
attack = ors('attack', getAttack, {}),
-- Family=nil,
BaseName = function(weap,atk) return get(weap,atk,'BaseName') or ({p._getVariant(statRead(weap,atk,'Name'))})[3] end,
-- TODO: Add comments to Explosion function for readability
Explosion = function(weap,atk)
-- tbh this is a mess
local exp = get(weap,atk,'Explosion') or statRead(weap,atk,'AttackName'):gsub(' Impact','')..' Explosion'
if type(exp)=='string' then
exp = weap.attack[tonumber(exp:gsub('%D',''))] or exp
elseif type(exp)=='number' then
exp = weap.attack[exp] or exp
end
local explosions = {}
-- if type(exp)=='table' then goto ret end
if type(exp)~='table' then
for i,v in ipairs(weap.attack) do
if statRead(weap,v,'AttackName'):find 'xplosion' then
if statRead(weap,v,'AttackName') == exp then
explosions[1]=nil
exp=v
break
-- goto ret
end
table.insert(explosions,v)
end
end
exp = explosions[1] or exp
end
-- ::ret::
cacheIn(weap,exp,'BaseAttack',{atk})
return table.unpack(cacheIn(weap,atk,'Explosion',{exp}))
end,
}
--loops for defaults table
for _, dt in ipairs(DAMAGE_TYPES) do
default[dt] = function(weap, atk) return atk['Damage'][dt] or 0 end
end
default.MinProgenitorBonus = function(w,a) return a.Damage.MinProgenitorBonus or 0 end
default.ProgenitorBonus = function(w,a) return(a.Damage.MinProgenitorBonus or 0) * 2.4 end
for i = 1, 9 do
default['Attack'..i] = function(weap, atk) return weap.attack[i] end
end
-- For mapping weapon traits or types to a category link
-- unused?
local CATEGORY_MAP = {
Primary = 'Primary Weapons',
Secondary = 'Secondary Weapons',
Melee = 'Melee Weapons',
['Arch-Melee'] = 'Archwing Melee',
['Arch-Gun'] = 'Archwing Gun',
['Arch-Gun (Atmosphere)'] = 'Archwing Gun',
-- Kitgun = 'Kitgun',
-- Zaw = 'Zaw',
['Railjack Turret'] = 'Railjack',
['Railjack Armament'] = 'Railjack',
-- Gear = 'Gear',
Rifle = 'Assault Rifle',
-- ['Sniper Rifle'] = 'Sniper Rifle',
-- Shotgun = 'Shotgun',
-- Pistol = 'Pistol',
-- ['Dual Pistols'] = 'Dual Pistols',
-- Bow = 'Bow',
-- Launcher = 'Launcher',
-- ['Arm-Cannon'] = 'Arm-Cannon',
-- ['Speargun'] = 'Speargun',
-- Thrown = 'Thrown',
-- ['Shotgun Sidearm'] = 'Shotgun Sidearm',
Prime = 'Prime Weapons',
-- ['Never Vaulted'] = 'Never Vaulted',
-- Vaulted = 'Vaulted',
-- Wraith = 'Wraith',
-- Vandal = 'Vandal',
-- 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'] = 'Kuva Lich',
-- Prisma = 'Prisma',
Grineer = 'Grineer Weapons',
Corpus = 'Corpus Weapons',
Infested = 'Infested Weapons',
Tenno = 'Tenno Weapons',
Sentient = 'Sentient Weapons',
Entrati = 'Entrati Weapons',
Baro = 'Baro Ki\'Teer Offering',
Syndicate = 'Syndicate Offerings',
-- ['Invasion Reward'] = 'Invasion Reward',
['Alt Fire'] = 'Weapons with Alt Fire',
['AoE'] = 'Weapons with Area of Effect]][[Category:Self Interrupt Weapons',
-- Active = 'Active',
Auto = 'Automatic',
['Auto-Spool'] = 'Automatic',
Burst = 'Burst Fire',
['Auto-Burst'] = 'Burst Fire',
['Auto Charge'] = 'Charge',
-- Charge = 'Charge',
Duplex = 'Duplex Fire',
['Semi-Auto'] = 'Semi-Automatic',
Held = 'Continuous Weapons'
}
--- 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
return true, var, (string.gsub(weaponName, " ?"..var.." ?-?", ""))
end
end
return false, "Base", weaponName
end
--- Builds the full name of a weapon's variant. Does not check if it exists or not.
-- @function p._buildName
-- @param {string} baseName Weapon's base name (e.g. "Braton")
-- @param[opt] {string} variant Variant name (e.g. "Vandal"); if nil, returns base weapon name instead
-- @returns {string} Weapon's variant name (e.g. "Braton Vandal")
function p._buildName(baseName, variant)
if not variant or variant == 'Base' or variant == '' then
return baseName
end
return (({
Prime= baseName~='Laser Rifle' and '%b %v',
Vandal='%b %v',
Wraith='%b %v',
MK1='%v-%b',
})[variant] or '%v %b'):gsub('%%v',variant):gsub('%%b',baseName)
end
--- Returns a specific weapon table entry from <code>/data</code> or <code>/Conclave/data</code>.
-- @function p._getWeapon
-- @param {string} weaponName Weapon name
-- @param[opt] {boolean} getConclave If true, gets PvP stats of weapon instead, false otherwise; defaults to false
-- @returns {table} Weapon table
function p._getWeapon(weaponName, getConclave)
return (getConclave and ConclaveData or WeaponData)["Weapons"][weaponName] or
error('p._getWeapon(weaponName, getConclave): "'..weaponName..
'" does not exist in [[Module:Weapons/data]] or [[Module:Weapons/Conclave/data]]')
end
--- Gets the raw value of a certain statistic of a weapon.
-- @function p._getValue
-- @param {table} Weapon Weapon table
-- @param {string} key Name of key
-- @param[opt] {string} attack Name of attack to search through; defaults to 'Attack1' or what '_TooltipAttackDisplay' is set to
-- @returns {string, number} Value of statistic
function p._getValue(weap, key, atk)--, formatted)
-- return (formatted and statFormat or statRead)(weap,atk,key)
return statRead(weap, atk, key)
end
--- Gets the formatted value of a certain statistic of a weapon to be displayed
-- the wiki.
-- @function p._getFormattedValue
-- @param {table} Weapon Weapon table
-- @param {string} keyName Name of key
-- @param[opt] {string} attackName Name of attack to search through; defaults to 'Attack1'
-- @returns {string} Value of statistic
function p._getFormattedValue(weap, key, atk)
-- return p._getValue(Weapon, keyName, attackName, true)
return statFormat(weap, atk, key)
end
--- Loops through all possible attacks that a weapon may have.
-- use ipairs(Weapon.attack) instead
-- @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 not Weapon 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
--- 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
-- @param {boolean} ignoreIgnore If true, ignores the _IgnoreEntry flag, false otherwise; defaults to false
-- @returns {table} Table of weapon table entries as seen in <code>/data</code>
function p._getWeapons(validateFunction, getConclave, ignoreIgnore, sortFunc)
local weaps = {}
for _, weap in pairs((getConclave and ConclaveData or WeaponData)["Weapons"]) do
if (ignoreIgnore or not weap['_IgnoreEntry']) and validateFunction(weap) then
table.insert(weaps, weap)
end
end
table.sort(weaps,sortFunc or function(a,b)return a.Name<b.Name end)
return weaps
end
--- Returns all melee weapons. If weapType is not nil, only grab for a specific type
-- For example, if weapType is "Nikana", only pull Nikanas.
-- @function p._getMeleeWeapons
-- @param[opt] {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)
return p._getWeapons(function(weap)
return weap.Slot == "Melee" and weapClass == weap.Class
end, pvp)
end
--- Returns all conclave 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
-- @returns {table} An array of melee weapon table entries as seen in <code>/Conclave/data</code>
function p._getConclaveMeleeWeapons(weapClass)
return p._getMeleeWeapons(weapClass, true)
end
-- TODO: Move to M:Stances?
--- Gets stance mods for a particular melee class.
-- @function getStances
-- @param {string} meleeClass Melee class
-- @param {boolean} ConclaveOnly 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, ConclaveOnly)
local stanceTable = {}
for stanceName, Stance in pairs(StanceData) do
if (not meleeClass or meleeClass == Stance.WeaponType)
and (not ConclaveOnly)==(not Stance.ConclaveOnly) then
stanceTable[stanceName] = Stance
end
end
return stanceTable
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] or
error('p.getStanceWeaponList(frame): '..stanceName..' not found')
local pol = Stance.Polarity
local result = {}
for _, weap in ipairs(p._getMeleeWeapons(Stance.WeaponType, Stance.ConclaveOnly)) do
table.insert(result, '*'
..statFormat(weap, nil,'NameLink'):gsub('^%[%[',Stance.ConclaveOnly and '[[Conclave:' or '[[')
..(weap.StancePolarity==pol and ' ✓' or '')
)
end
return table.concat(result, '\n')
end
function p.getValue(frame)
local weap, key, atk = table.unpack(frame.args)
weap = p._getWeapon(weap)
return p._getValue(weap, key, atk)
end
function p.getFormattedValue(frame)
local weap, key, atk = table.unpack(frame.args)
weap = p._getWeapon(weap)
return p._getFormattedValue(weap, key, atk)
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 = {'<gallery mode=packed>'}
-- for i, weap in ipairs(Weapons) do
-- table.insert(result, statRead(weap, nil,'Image')..'|'..statFormat(weap, nil,'NameLink'))
-- end
-- table.insert(result, '</gallery>')
-- return table.concat(result, '\n')
-- end
-- local result = {}
-- -- local result = { '{| style="margin:auto;text-align:center;"' }
-- -- local nameRow = {}
-- for i, Weapon in ipairs(Weapons) do
-- -- table.insert(result, ('|style="width:165px"|[[File:%s|150px|link=%s]]'):format(statRead(Weapon, nil,'Image'),statRead(Weapon, nil,'Link')))
-- -- table.insert(nameRow, ('| style="vertical-align: text-top;" |%s'):format(statFormat(Weapon, nil,'NameLink')))
-- table.insert(result, statFormat(Weapon, nil,'NameLink'))
-- -- if i % 5 == 0 then
-- -- table.insert(result, table.concat(nameRow,'\n'))
-- -- table.insert(result, "|-")
-- -- nameRow = {}
-- -- end
-- end
-- -- table.insert(result, table.concat(nameRow,'\n'))
-- -- 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[1] or ''
local result = {"=="..meleeClass.." Weapons==",'<gallery widths="200" position="center" spacing="small">'}
for i, weap in ipairs(p._getMeleeWeapons(meleeClass)) do
table.insert(result, statRead(weap, nil,'Image')..'|'..statFormat(weap, nil,'Name'))
end
table.insert(result, '</gallery>')
result = frame:preprocess(table.concat(result, '\n')) -- annoying that it needs to br preprocessed
mw.log(result:gsub('%z','\\z'))
return result
end
-- return "=="..meleeClass.." Weapons==\n"..getWeaponGallery(p._getMeleeWeapons(meleeClass))
--- 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 and frame.args[1] or frame
local getFullList = frame.args and frame.args[2] or nil
local count = 0
local fullList = {}
for name, weapon in Table.skpairs(WeaponData["Weapons"]) do
if not weapon._IgnoreEntry then
local wf = weapon.Slot == "Primary" or weapon.Slot == "Secondary" or weapon.Slot == "Melee"
local ex = weapon.Class == "Exalted Weapon"
local aw = string.find(weapon.Slot, "Arch")
local mod = string.find(weapon.Class, "Kitgun") or string.find(weapon.Class, "Zaw")
if weaponSlot == "All" or not ex and ((
(not weaponSlot or weaponSlot == '')
and not mod
and weapon.Slot ~= "Amp"
and weapon.Class ~= "Ordnance"
and weapon.Class ~= "Turret"
and weapon.Class ~= "Unique"
and not string.find(weapon.Slot, "Atmosphere")
) or ({
[weapon.Slot] = not mod,
Warframe = wf,
Archwing = aw,
Rest = not wf and not aw,
})[weaponSlot]) then
if weapon.Name ~= "Dark Split-Sword (Heavy Blade)" then count = count + 1 end
if getFullList then table.insert(fullList, '# '..name) end
end
end
end
if getFullList then
table.sort(fullList, function(a,b)return a.Name<b.Name end)
return table.concat(fullList, '\n'), count
end
return count
end
--- Gets the weapon class of weapons for use in comparison tables.
-- @function p._getCategory
-- @param {table} weapon Weapon table
-- @returns {string} Category name, {string} Triggertype
function p._getCategory(weapon)
local class = p._getValue(weapon, "Class")
local showClass=({
["Thrown"]=class,
["Dual Shotguns"]=class,
["Shotgun Sidearm"]=class,
["Dual Pistols"]=class,
["Pistol"]=class,
["Shotgun"]=class,
["Crossbow"]="Bow", ["Bow"]=class,
["Sniper Rifle"]=class,
["Launcher"]=class,
["Arm Cannon"]=class,
["Speargun"]=class,
["Rifle"]=class,
})[class] or "Other"
return showClass, p._getValue(weapon, "Trigger")
end
p._getSecondaryCategory = p._getCategory
p._getPrimaryCategory = p._getCategory
--- Builds wikitable of all weapons' innate polarities as seen on [[Polarity]].
-- @function p.getPolarityTable
-- @param {table} frame Frame object
-- @returns {string} Wikitext of resultant wikitable
function p.getPolarityTable(frame)
local colNames = {'Primary','Secondary','Melee','Arch-Gun','Arch-Melee',}
local cols = {}--{['Primary']={},['Secondary']={},['Melee']={},['Arch-Gun']={},['Arch-Melee']={},}
local colOrder = {}--{cols['Primary'],cols['Secondary'],cols['Melee'],cols['Arch-Gun'],cols['Arch-Melee'],}
local colCounts = {}
for i,v in ipairs(colNames) do
cols[v]={}
colOrder[i]=cols[v]
colCounts[v]=0
end
-- local entries =
p._getWeapons(function(weapon)
local pols = Table.size(weapon["Polarities"] or {})
local slot = weapon['Slot']
if pols>0 and cols[slot] then
table.insert(cols[slot], {'|'..p._getFormattedValue(weapon,'NameLink'):gsub(' ?%(.*%)','')..'||'..p._getFormattedValue(weapon, "Polarities"),pols})
colCounts[slot]=colCounts[slot]+1
end
-- return pols>0
end)
for i,v in ipairs(colNames) do
colCounts[i]=colCounts[v]
table.sort(cols[v],function(a,b)return a[2]>b[2] end)
end
local result = {[=[
{| style="width: 100%; border-collapse: collapse;" cellpadding="2" border="1"
|+ '''Weapons with Innate Polarities (ignoring Stance and Exilus slots)'''
! colspan="2" |Primaries
! colspan="2" |Secondaries
! colspan="2" |Melees
! colspan="2" |Arch-Guns
! colspan="2" |Arch-Melees]=]}
for i=1,math.max(table.unpack(colCounts)) do --row
table.insert(result, '|-')
for _, col in ipairs(colOrder) do --cell
table.insert(result,(col[i] or {'| ||'})[1])
end
end
table.insert(result, '|}')
return table.concat(result, '\n')
end
--- Builds comparison string between two values.
-- @function compareStr
-- @param {table} weap1 A table used to find the comparing values
-- @param {table} atk1 A table used to find the comparing values
-- @param {table} weap2 A table used to find the comparing values
-- @param {table} atk2 A table used to find the comparing values
-- @param {string} valName Name of statistic that values represent (e.g. "Critical Damage")
-- @param {string} var Name of statistic that values are (e.g. "CritDamage")
-- @param[opt] {table} compareAdjs Two element table that contains the greater than and less than comparative adjectives (e.g. {"Higher", "Lower", "Different"})
-- @param[opt] {string} prefix What to start the comparison string with if you want to increase the bullet level (e.g. "***")
-- @returns {string} Resultant wikitext of comparison string (e.g. '**Higher Critical Damage (1.2x vs. 1.1x)')
local function compareStr(weap1, atk1, weap2, atk2, valName, var, compareAdjs, prefix)
local val1,val2 = statRead(weap1,atk1,var), statRead(weap2,atk2,var)
if val1 == val2 or val1 ~= val1 or val2 ~= val2 or not (val1 and val2) then return '' end
if type(val1)=='table' then
local a,b = val1, val2
val1, val2 = Table.size(val1), Table.size(val2)
if val1 == val2 and not (function()
for k, v in pairs(a) do
if b[k]~=v then
return true
end
end
end)() then
return ''
end
end
local adj = compareAdjs
if type(compareAdjs)=='table' or not compareAdjs then
adj = (compareAdjs or {"Higher","Lower","Different"})[
val1==val2 and 3 or
val1>val2 and 1 or
val1<val2 and 2 or 0
] or error('compareStr: vals are without compare: '..mw.dumpObject{val1=val1,val2=val2,compareAdjs=compareAdjs,adj=adj})
-- local bigWord, smallWord = table.unpack(compareAdjs or {"Higher","Lower"})
-- adj = bigWord~=smallWord and adj>0 and (bigWord or "Higher") or (smallWord or "Lower")
end
return ('%s %s %s (%s vs. %s)'):format(prefix or '**', adj, valName, statFormat(weap1,atk1,var), statFormat(weap2,atk2,var))
end
--- Builds damage comparison string between two attacks.
-- @function damageComparisonStr
-- @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 damageComparisonStr(weap1, atk1, weap2, atk2)
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
-- ^ i think this is better tho?
for _, dt in ipairs(DAMAGE_TYPES) do
result[#result+1] = compareStr(weap1, atk1, weap2, atk2, Tooltip.getFullTooltip(dt, 'DamageTypes').." damage", dt, nil, "***")
-- local damage1 = ('%.2f'):format(Attack1.Damage[element] or 0)
-- local damage2 = ('%.2f'):format(Attack2.Damage[element] or 0)
-- result = result..bulidCompareString(damage1, damage2, Tooltip.getFullTooltip(element, 'DamageTypes').." damage", nil, nil, {"Higher", "Lower"}, "\n***")
end
return table.concat(result,'\n')
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, getConclave)
local weapon1Name = frame.args[1] or ''
local weapon2Name = frame.args[2] or ''
assert(weapon1Name ~= '' and weapon2Name ~= '', 'p.buildComparison(frame): Must compare two weapons')
local weap1 = p._getWeapon(weapon1Name, getConclave)
local weap2 = p._getWeapon(weapon2Name, getConclave)
local atk1 = getAttack(weap1)
local atk2 = getAttack(weap2)
local result = {
-- support method chaining w/ colon syntax
insert = function(self, elem)
table.insert(self, elem)
return self
end,
insertcompare = function(self, ...)
return self:insert(compareStr(weap1,atk1,weap2,atk2,...))
end
}
result:insert(("* [[%s]] (%s), compared to [[%s]] (%s):"):format(
weapon1Name,statRead(weap1,atk1,'AttackName'),
weapon2Name,statRead(weap2,atk2,'AttackName'),
nil))
if getConclave then
result[1]=result[1]:gsub('%[%[(.+)%]%]','[[Conclave:%1|%1]]')
end
local progenitorBonusNote = (
string.find(weapon1Name, "Kuva") or string.find(weapon1Name, "Tenet") or
string.find(weapon2Name, "Kuva") or string.find(weapon2Name, "Tenet") )
and " (using max +60% [[Lich System/Progenitor|Progenitor]] bonus if applicable)" or ""
result
:insert(compareStr(weap1, atk1, weap2, atk2, "base damage per projectile", "BaseDamage"):gsub('^$','**Equal base damage, but different composition:'))
:insert(damageComparisonStr(weap1, atk1, weap2, atk2))
:insertcompare("total damage", "TotalDamage")
:insertcompare("base [[critical chance]]", "CritChance")
:insertcompare("base [[critical multiplier]]", "CritMultiplier")
:insertcompare("base [[status chance]]", "StatusChance")
:insertcompare("[[Damage#Final_Calculations|average damage per tap]]"..progenitorBonusNote, "AvgTapDmg")
:insertcompare("[[Damage#Final_Calculations|burst DPS]]"..progenitorBonusNote, "BurstDps")
:insertcompare("[[Damage#Final_Calculations|sustained DPS]]"..progenitorBonusNote, "SustainedDps")
if statRead(weap1, atk1, "IsMelee") then
result -- melee
:insertcompare("Range", "MeleeRange", {"Longer","Shorter"})
:insertcompare("[[attack speed]]", "FireRate")
:insertcompare("[[Melee Combo|Combo Duration]]", "ComboDur")
:insertcompare("Block Angle", "BlockAngle")
:insertcompare("[[Stance]] Polarity", "StancePolarity", "Different")
else
result -- gun
:insertcompare("starting [[Damage Falloff|damage falloff]] distance", "FalloffStart", {"Farther", "Closer"})
:insertcompare("max [[Damage Falloff|damage falloff]] distance", "FalloffEnd", {"Farther", "Closer"})
:insertcompare("max damage reduction at ending falloff distance", "FalloffReduction", {"Greater", "Lesser"})
:insertcompare("[[fire rate]]", "FireRate")
:insertcompare("[[multishot]]", "Multishot")
:insertcompare("magazine", "Magazine", {"Larger", "Smaller"})
:insertcompare("max ammo capacity", "MaxAmmo", {"Larger", "Smaller"})
:insertcompare("[[Reload|reload time]]", "Reload", {"Slower", "Faster"})
:insertcompare("spool-up", "Spool", {"Slower", "Faster"})
:insertcompare("[[Accuracy|accurate]]", "Accuracy", {"More", "Less"})
end
result
:insertcompare("[[Polarity|polarities]]", "Polarities", {"More", "Less", "Different"})
:insertcompare("[[Mastery Rank]] required", "Mastery")
:insertcompare("[[disposition]]", "Disposition")
local se1, se2 = weap1.SyndicateEffect, weap2.SyndicateEffect
if se1 and not se2 then
result:insert("\n** Innate [["..se1.."]] effect")
elseif se2 and not se1 then
result:insert("\n** No innate [["..se2.."]] effect")
elseif se1 and se2 and se1 ~= se2 then
result:insert("\n** Different innate [[Syndicate Radial Effects|Syndicate Effect]] ([["..se1.."]] vs. [["..se2.."]])")
end
-- mw.log(mw.dumpObject{['M:Weapon cache']=cache})
return table.concat(result, '\n'):gsub('\n\n+', '\n')..'[[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)
return buildComparison(frame, true)
end
--- Builds a table that lists out all weapons with a certain damage type
-- @function p.buildDamageTypeTable
-- @param {table} frame Frame object
-- @returns {string} Wikitext of resultant wikitable
function p.buildDamageTypeTable(frame)
local damageType = frame.args and frame.args[1] or frame
local topPortion = frame.args and frame.args[2] or false
local tRows = {}
local weaponsTable = p._getWeapons(function(weap)
-- Kitgun entries have 0 as placeholder damage values
if string.find(weap['Class'], 'Kitgun') then
return false
elseif topPortion then
return statRead(weap, nil, 'BiasType')==damageType
end
return (statRead(weap, nil, 'Damage')[damageType] or 0) > 0
end)
for i, weap in ipairs(weaponsTable) do
local atk = getAttack(weap)
local bias = {}
if topPortion or statRead(weap, atk, 'BiasType') == damageType then
bias[1] = statRead(weap, atk, 'BiasPortion')
bias[2] = statFormat(weap, atk, 'DamageBias')
else
bias[1] = statRead(weap, atk, damageType)/statRead(weap, atk, 'BaseDamage')
bias[2] = percent(bias[1])..dmgTooltip(damageType)
end
table.insert(tRows, ('| %s || %s || %s || %s || data-sort-value="%s" | %s'):format(
Tooltip.getFullTooltip(statRead(weap, atk, 'Name'), 'Weapons'),
statRead(weap, atk, 'Slot'),
statRead(weap, atk, 'Class'),
statRead(weap, atk, damageType),
bias[1], bias[2],
nil))
end
return ([[
{| class = "listtable sortable" style="margin:auto;"
|+ '''Weapons with %s%s damage'''
|-
! Name !! Slot !! Class !! data-sort-type="number" | %s !! data-sort-type="number" | %s%%
|-
]]):format(topPortion and 'mostly ' or '', damageType, Tooltip.getFullTooltip(damageType, 'DamageTypes'), damageType)
..table.concat(tRows, '\n|-\n')..'\n|}'
end
--- Builds a list of weapons, with variants being next to base weapon name inside parentheses
-- (e.g. {{Weapon|Braton}} ({{Weapon|MK1-Braton|MK1}}, {{Weapon|Braton Prime|Prime}})).
-- @function p._shortLinkList
-- @param {table} Weapon Weapon table
-- @param {boolean} tooltip If true, adds weapon tooltips, false otherwise; defaults to false
-- @returns {string} Wikitext of resultant list
function p._shortLinkList(Weapons, tooltip)
local baseNames = {}
for key, weap in pairs(Weapons) do
local isVar, varType, baseName = p._isVariant(weap.Name)
if not baseNames[baseName] then baseNames[baseName] = {} end
if not isVar then baseNames[baseName].hasBase = true end
table.insert(baseNames[baseName], varType)
end
local link = tooltip and
-- TODO: Replace function call with frame imitation with a new function that is used
-- within modules. In other words, Tooltip.getFullTooltip() should not be used in both #invokes on articles
-- and in modules.
function(a, b) return Tooltip.getFullTooltip{ args = {a, 'Weapons', r = b} } end or
function(a, b) return b and '[['..a..'|'..b..']]' or '[['..a..']]' end
local result = {}
for baseName, variants in Table.skpairs(baseNames) do
local thisRow = {}
for _, varName in ipairs(variants) do
if varName ~= 'Base' and varName ~= '' then
table.insert(thisRow, link(p._buildName(baseName, varName),variants.hasBase and varName))
end
end
local bn = variants.hasBase and link(baseName) or ''
local vars = #thisRow > 0 and (variants.hasBase and ' (%s)' or '%s'):format(table.concat(thisRow, ', ')) or ''
table.insert(result, bn..vars)
end
return 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 weapArray = p._getWeapons(function(x)
return x.Slot == weaponSlot and x.Mastery == masteryRank
end)
return table.concat(p._shortLinkList(weapArray, true), ' • ')
end
--- Builds a list of PvP weapons as seen on [[PvP#Limitations]].
-- @function p.getConclaveList
-- @param {table} frame Frame object w/ first argument being a string weaponSlot
-- @returns {string} Wikitext of resultant list
function p.getConclaveList(frame)
local weaponSlot = frame.args[1] or 'All'
-- local masteryRank = tonumber(frame.args[2])
local weapArray = p._getWeapons(function(x)
return (weaponSlot == 'All' or x.Slot == weaponSlot)-- and x.Mastery==masteryRank
end, true)
return '*'..table.concat(p._shortLinkList(weapArray, false), '\n* ')
end
--- Builds a disposition wikitable as seen on [[Riven Mods/Weapon Dispos]].
-- @function p.getRivenDispositionTable
-- @param {table} frame Frame object w/ first argument being a string weaponSlot
-- @returns {string} Wikitext of resultant wikitable
function p.getRivenDispositionTable(frame)
local weaponSlot = frame.args[1]
local result = {
'{| class="article-table" border="0" cellpadding="1" cellspacing="1" style="width: 100%"',
'|-',
{'[[a| '}, -- Wikitable header row
'|-'
}
-- local ranges = {'○○○○○', '●○○○○', '●●○○○', '●●●○○', '●●●●○', '●●●●●'}
local dispo = {}
for k, weapon in pairs(WeaponData.Weapons) do
if weapon['Disposition'] and (weaponSlot == 'All' or weapon['Slot'] == weaponSlot) then
local disp = statFormat(weapon, nil, 'Dispo')
dispo[disp] = dispo[disp] or {}
table.insert(dispo[disp], weapon)
end
end
dispo['○○○○○○'] = nil -- Grattler (Atmosphere) dispo is/was 0.1
for str, dis in Table.skpairs(dispo) do
table.sort(dis, function(a, b) return a['Disposition'] > b['Disposition'] end)
local col = { '| style="vertical-align:top; font-size:small" |' }
for _, weap in ipairs(dis) do
table.insert(col, statFormat(weap, nil, 'NameLink')..' ('..weap['Disposition']..')')
end
table.insert(result[3], str)
table.insert(result, table.concat(col, '\n* '))
end
result[3] = table.concat(result[3], ']]\n! scope="col" style="text-align:center;"|[[Riven Mods#Disposition|')..']]'
table.insert(result, '|}')
return table.concat(result, '\n')
end
return p