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