WARFRAME Wiki
Advertisement
WARFRAME Wiki


Weapons/infobox builds the infobox on weapon pages and adds the appropriate category pages.

Documentation

Package items

weapons.buildInfobox(frame) (function)
Builds the weapon infobox.
Parameter: frame Frame object (table)
Returns: Wikitext of infobox (string)

Created with Docbunto

See Also

Code


---	'''Weapons/infobox''' builds the infobox on weapon pages and adds the 
--	appropriate category pages.<br />
--
--	@module		weapons
--	@alias		p
--	@author			[[User:FINNER|FINNER]]
--	@attribution	[[User:Gigamicro|Gigamicro]]
--	@attribution	[[User:Cephalon Scientia|Cephalon Scientia]]
--	@image		
--	@require	[[Module:Weapons]]
--	@require	[[Module:Weapons/data]]
--	@require	[[Module:Weapons/Conclave/data]]
--	@require	[[Module:Math]]
--	@require	[[Module:Table]]
--	@require	[[Module:Polarity]]
--	@require	[[Module:Tooltips]]
--	@require	[[Module:Vendors]]
--	@require	[[Module:Baro]]
--	@require	[[Module:InfoboxBuilder]]
--	@release	stable
--	<nowiki>

local Weapon = require([[Module:Weapons]])
local Math = require([[Module:Math]])
local Table = require([[Module:Table]])
local Polarity = require([[Module:Polarity]])
local Tooltips = require([[Module:Tooltips]])
local Vendor = require([[Module:Vendors]])
local Baro = require([[Module:Baro]])
local InfoboxBuilder = require([[Module:InfoboxBuilder]])

return {
--- Builds the weapon infobox.
--	@function	p.buildInfobox
--	@param		{table} frame Frame object
--	@return		{string} Wikitext of infobox
buildInfobox = function(frame)
	local args = frame.args;
	local name = mw.text.decode(args['Name']);
	local data = {}
	local weapon = nil
	-- Checking if article is in Conclave namespace
	local isConclaveArticle = mw.title.getCurrentTitle().nsText == 'Conclave'
	
	if isConclaveArticle then
		data = require([[Module:Weapons/Conclave/data]])
	else
		data = require([[Module:Weapons/data]]);
	end
	weapon = Table.deepCopy(data[name])	-- Want to make table not read-only to set metamethods
	
	assert(weapon ~= nil, ('p.buildInfobox(frame): weapon with name "%s" does not exist in %s')
		:format(name, isConclaveArticle and '[[Module:Weapons/Conclave/data]]' or '[[Module:Weapons/data]]'))
	
	-- Creating a wrapper function to get formatted value for a particular stat
	-- by simplying calling weapon entry (e.g. weapon('BaseDamage') or weapon('FireRate', 2))
	setmetatable(weapon, { __call = function(self, statKey, attackIndex)
			if (attackIndex ~= nil) then
				return Weapon._getFormattedValue(self, statKey, attackIndex) or nil
			end
			-- TODO: Ideally, we should be able to instantiate a new weapon entry object
			-- via M:Weapons so we can just directly use that object's _getFormattedValue
			-- instead of treating Weapon as a static class and also passing in entry table
			return Weapon._getFormattedValue(self, statKey, self._TooltipAttackDisplay) or nil
		end
	})
	
	local isArchwing = Weapon._getValue(weapon, 'IsArchwing')
	local isMelee = Weapon._getValue(weapon, 'IsMelee')
	
	local tradableBaseText = "[[File:TradableIcon.png|x32px|class=icon dark-invert]] '''[[Trading|{{text|green|%s}}]]<br />"
	local tradableTextMap = {
		[0] = "{{text|red|Untradeable}}",
		[1] = tradableBaseText.."(unranked w/ no Forma or Catalyst)'''",
		[2] = tradableBaseText.."(parts and/or blueprint only)'''",
		[3] = tradableBaseText.."(indirectly through [[Lich System|Lich]] trading)'''",
		[4] = tradableBaseText.."(only fully built components, not blueprints)'''",
		[5] = tradableBaseText.."(indirectly, comes with parent companion)'''",
	}
	
	local compatibilityTags = {}
	for _, tag in ipairs(weapon.CompatibilityTags or {}) do
		table.insert(compatibilityTags, tag)
	end
	compatibilityTags = table.concat(compatibilityTags, ', ')
	
	local Infobox = InfoboxBuilder('WARFRAME Wiki:L10n/weapons.json', 'WARFRAME Wiki:L10n/general.json', 'WARFRAME Wiki:L10n/meta.json')
	:tag('title')
		:tag('default')
			:tag('b'):wikitext(name):done()
		:done()
	:done()
	:tag('image'):attr('source', 'Image')
		:tag('default'):wikitext((weapon.IncarnonImage ~= nil and 
					('<gallery>%s|Normal\n%s|Incarnon</gallery>'):format(weapon.Image, weapon.IncarnonImage) or 
					weapon.Image) or 'UnidentifiedItem.png'):done()
	:done()
	:group()
		:caption('Tradeable', weapon.Tradable and tradableTextMap[weapon.Tradable] or tradableTextMap[0], 'tradable')
		:caption('UniqueUpgrade', weapon.IsLichWeapon and 'Innate [[Lich System#Notes|%s]]</span>' or '', 'progenitor-bonus')
		:caption('CodexSecret', weapon.CodexSecret and '[[Hidden Mastery|%s]]' or nil, 'codex-secret')
		:caption('UpdateInfoboxData', '[[Module:Weapons/data|📝 %s]]', 'update-infobox-data')
	:done()
	:header('%s', 'general-information')
	:row('Class', '[[File:MiniMapMod.png|x18px|link=Mods#Item Compatibility]] [[Mods#Item Compatibility|%s]]', weapon('Class'), 'type')
	:row('Mastery',	'[[File:MasterySigilClear.png|x18px|link=Mastery Rank]] [[Mastery Rank|%s]]', weapon('Mastery'), 'mastery-rank-requirement')
	:row('MaxRank', '%s', weapon('MaxRank'), 'max-rank')
	:row('Slot', '[[File:TopWeapon.png|x18px|link=Weapons]] [[Weapons|%s]]', weapon('Slot'), 'slot')
	:row('Trigger',	'[[Trigger Type|%s]]', weapon('Trigger'), 'trigger-type')
	
	:group():header('%s', 'utility')
		:row('Accuracy', '[[Accuracy|%s]]', weapon.Accuracy and weapon('Accuracy'), 'accuracy')
		:row('AttackSpeed', '[[Attack Speed|%s]]', isMelee and weapon('AttackSpeed', 1) or nil, 'attack-speed')
		:row('AmmoMax', '[[Ammo#Ammo Maximum|%s]]', weapon.AmmoMax and weapon('AmmoMax'), 'ammo-max')
		:row('AmmoPickup', '[[Ammo#Ammo Pickup|%s]]', weapon.AmmoPickup and weapon('AmmoPickup'), 'ammo-pickup')
		:row('AmmoType', '[[Ammo#Ammo Packs|%s]]', weapon.AmmoType and weapon('AmmoType'), 'ammo-type')
		:row('BlockAngle', '[[Melee#Blocking|%s]]', weapon.BlockAngle and weapon('BlockAngle'), 'block-angle')
		:row('ComboDur', '[[Melee|%s]]', weapon.ComboDur and weapon('ComboDur'), 'combo-duration')
		:row('Disposition', '[[Riven_Mods#Disposition|%s]]', weapon('Disposition'), 'disposition')
		:row('FireRate', '[[Fire Rate|%s]]', (not isMelee) and weapon('FireRate', 1) or nil, 'fire-rate')
		:row('FollowThrough', '[[Melee#Follow_Through|%s]]', weapon.FollowThrough and weapon('FollowThrough'), 'follow-through')
		:row('MeleeRange', '[[Melee#Range|%s]]', weapon.MeleeRange and weapon('MeleeRange'), 'range')
		:row('NoiseLevel', '[[Noise Level|%s]]', weapon.Attacks[1].IsSilent and 'Silent' or 'Alarming', 'noise-level')
		:row('Magazine', '[[Ammo#Magazine Capacity|%s]]', weapon.Magazine and weapon('Magazine'), 'magazine-size')
		:row('Reload', '[[Reload|%s]]', weapon.Reload and weapon('Reload'), 'reload-time')
		:row('ReloadDelay', '[[Reload|%s]]', weapon.ReloadDelay and weapon('ReloadDelay'), 'reload-delay')
		:row('ReloadRate', '[[Reload|%s]]', weapon.ReloadRate and weapon('ReloadRate'), 'reload-rate')
		:row('ShotSpeed', '[[Projectile Speed|%s]]', weapon.Attacks[1].ShotSpeed and weapon('ShotSpeed', 1), 'projectile-speed')
		:row('ShotType', '%s', weapon.Attacks[1].ShotType and weapon('ShotType', 1), 'projectile-type')
		:row('SniperComboReset', '[[Sniper Rifle|%s]]', weapon.SniperComboReset and weapon('SniperComboReset'), 'combo-decay')
		:row('SniperComboMin', '[[Sniper Rifle|%s]]', weapon.SniperComboMin and weapon('SniperComboMin'), 'combo-min')
		:row('Spread', '[[Spread|%s]]', weapon.Attacks[1].MinSpread and weapon('AvgSpread', 1)..' ('..weapon('MinSpread', 1)..' min, '..weapon('MaxSpread', 1)..' max)', 'spread')
		:row('SweepRadius', '[[Sweep Radius|%s]]', weapon.SweepRadius and weapon('SweepRadius'), 'sweep-radius')
		:row('Spool', '[[Fire Rate#Auto-Spool Weapons|%s]]', weapon.Spool and weapon('Spool'), 'spool-up-rate')
		:row('Zoom', '[[Zoom|%s]]', weapon.Zoom and weapon('Zoom'), 'zoom')
	:done()
	
	-- Weapon attack sections
	local horiz, elems, attack
	for i, attackEntry in ipairs(weapon.Attacks) do
		attack = 'Attack'..i
		elems = {}
		for dt, dmg in pairs(attackEntry.Damage) do
			if dt ~= 'Impact' and dt ~= 'Puncture' and dt ~= 'Slash' then
				table.insert(elems, dt)
			end
		end
		
		-- Displaying IPS damage types first to match in-game presentation
		horiz = mw.html.create('group'):attr('layout', 'horizontal')
		local damageFormatString = '%s<br />(%s)'
		
		if isConclaveArticle then
			horiz:row(attack..'PvPImpact', nil, attackEntry.Damage.Impact and 
					damageFormatString:format(weapon('PvPImpact', i), weapon('ImpactDistribution', i)), 'impact')
				:row(attack..'PvPPuncture', nil, attackEntry.Damage.Puncture and 
					damageFormatString:format(weapon('PvPPuncture', i), weapon('PunctureDistribution', i)), 'puncture')
				:row(attack..'PvPSlash', nil, attackEntry.Damage.Slash and 
					damageFormatString:format(weapon('PvPSlash', i), weapon('SlashDistribution', i)), 'slash')
		else
			horiz:row(attack..'Impact', nil, attackEntry.Damage.Impact and 
					damageFormatString:format(weapon('Impact', i), weapon('ImpactDistribution', i)), 'impact')
				:row(attack..'Puncture', nil, attackEntry.Damage.Puncture and 
					damageFormatString:format(weapon('Puncture', i), weapon('PunctureDistribution', i)), 'puncture')
				:row(attack..'Slash', nil, attackEntry.Damage.Slash and 
					damageFormatString:format(weapon('Slash', i), weapon('SlashDistribution', i)), 'slash')
		end
		
		for _, elem in ipairs(elems) do
			if isConclaveArticle then
				horiz:row(attack..'PvP'..elem, nil, attackEntry.Damage[elem] and 
					damageFormatString:format(weapon('PvP'..elem, i), weapon(elem..'Distribution', i)), elem)
			else
				horiz:row(attack..elem, nil, attackEntry.Damage[elem] and 
					damageFormatString:format(weapon(elem, i), weapon(elem..'Distribution', i)), elem)
			end
		end
		
		Infobox:group():header(weapon('AttackName', i) or name)
			:node(horiz)
			:row(attack..'Total', '[[Damage|%s]]', weapon('TotalDamage', i)..' ('..weapon('DamageBias', i)..')', 'total-damage')
			:row(attack..'Accuracy', '[[Accuracy|%s]]', attackEntry.Accuracy and weapon('Accuracy', i), 'accuracy')
			-- A melee weapon's index 1 attack should always be a normal melee sweep
			:row(attack..'AttackSpeed', '[[Attack Speed|%s]]', (isMelee and i == 1) and weapon('AttackSpeed', i) or nil, 'attack-speed')
			:row(attack..'AmmoCost', '%s', attackEntry.AmmoCost and weapon('AmmoCost', i), 'ammo-cost')
			:row(attack..'AmmoMax', '[[Ammo#Ammo Maximum|%s]]', attackEntry.AmmoMax and weapon('AmmoMax', i), 'ammo-max')
			:row(attack..'AttackSpeed', '[[Attack Speed|%s]]', attackEntry.AttackSpeed and weapon('AttackSpeed', i), 'attack-speed')
			:row(attack..'BurstCount', '[[Fire Rate#Fire Rate and Burst Weapons|%s]]', attackEntry.BurstCount and weapon('BurstCount', i), 'burst-count')
			:row(attack..'BurstDelay', '[[Fire Rate#Fire Rate and Burst Weapons|%s]]', attackEntry.BurstDelay and weapon('BurstDelay', i), 'burst-delay')
			:row(attack..'BurstReloadDelay', '[[Fire Rate#Fire Rate and Burst Weapons|%s]]', attackEntry.BurstReloadDelay and weapon('BurstReloadDelay', i), 'burst-reload-delay')
			:row(attack..'ChargeTime', '[[Fire Rate#Charged Weapons|%s]]', attackEntry.ChargeTime and weapon('ChargeTime', i), 'charge-time')
			:row(attack..'CritChance', '[[Critical Hit|%s]]', weapon('CritChance', i), 'crit-chance')
			:row(attack..'CritMultiplier', '[[Critical Hit|%s]]', weapon('CritMultiplier', i), 'crit-multiplier')
			:row(attack..'Falloff', '[[Damage Falloff|%s]]', attackEntry.Falloff and weapon('Falloff', i), 'damage-falloff')
			:row(attack..'EffectDuration', '%s', attackEntry.Duration and weapon('Duration', i), 'effect-duration')
			:row(attack..'ExplosionDelay', '%s', attackEntry.ExplosionDelay and weapon('ExplosionDelay', i), 'explosion-delay')
			-- A melee weapon's index 1 attack should always be a normal melee sweep so don't show this attack as
			-- having a Fire Rate; additional attacks that are not Heavy/Slam/Slide attacks should display it as Fire Rate
			-- like in the case of gunblades
			:row(attack..'FireRate', '[[Fire Rate|%s]]', ((isMelee and not i ~= 1) or (not isMelee and attackEntry.FireRate)) and weapon('FireRate', i), 'fire-rate')
			:row(attack..'ForcedProcs', '[[Status Effect|%s]]', attackEntry.ForcedProcs and weapon('ForcedProcs', i), 'forced-procs')
			:row(attack..'HeadshotMultiplier', '%s', attackEntry.HeadshotMultiplier and weapon('HeadshotMultiplier', i), 'headshot-multiplier')
			:row(attack..'Multishot', '[[Multishot|%s]]', attackEntry.Multishot and ('%d (%s damage per projectile)'):format(attackEntry.Multishot, weapon('BaseDamage', i)), 'multishot')
			:row(attack..'NoiseLevel', '[[Noise Level|%s]]', attackEntry.IsSilent and 'Silent' or 'Alarming', 'noise-level')
			:row(attack..'PunchThrough', '[[Punch Through|%s]]', attackEntry.PunchThrough and weapon('PunchThrough', i), 'punch-through')
			:row(attack..'Range', '[[Range|%s]]', attackEntry.Range and weapon('Range', i), 'range')
			:row(attack..'Reload', '[[Reload|%s]]', attackEntry.Reload and weapon('Reload', i), 'reload-time')
			:row(attack..'Spread', '[[Spread|%s]]', attackEntry.MinSpread and weapon('AvgSpread', i)..' ('..weapon('MinSpread', i)..' min, '..weapon('MaxSpread', i)..' max)', 'spread')
			:row(attack..'StatusChance', '[[Status Chance|%s]]', attackEntry.StatusChance and weapon('StatusChance', i), 'status-chance')
			:row(attack..'ShotSpeed', '[[Projectile Speed|%s]]', attackEntry.ShotSpeed and weapon('ShotSpeed', i), 'projectile-speed')
			:row(attack..'ShotType', '%s', attackEntry.ShotType and weapon('ShotType', i), 'projectile-type')
			:row(attack..'Trigger', '[[Trigger Type|%s]]', attackEntry.Trigger and weapon('Trigger', i), 'trigger-type')
		:done()
	end
	
	-- Adding additional melee-only rows; not including Archmelees
	if (isMelee and not isArchwing) then
		Infobox:group():header('%s', 'heavy-attack')
			:row('HeavyAttack', '[[Melee#Heavy Attack|%s]]', weapon('HeavyAttack'), 'heavy-damage')
			:row('HeavyAttackCritChance', '[[Critical Hit|%s]]', weapon('CritChance', 1), 'crit-chance')	-- Heavy attack inherits crit chance of first attack
			:row('HeavyAttackCritMultiplier', '[[Critical Hit|%s]]', weapon('CritMultiplier', 1), 'crit-multiplier')
			:row('HeavyAttackStatusChance', '[[Status Chance|%s]]', weapon('StatusChance', 1), 'status-chance')
			:row('WindUp', '[[Melee#Heavy Attack|%s]]', weapon('WindUp'), 'wind-up')
		:done()
		
		Infobox:group():header('%s', 'heavy-slam-attack')
			:row('HeavySlamAttack', '[[Melee#Heavy Attack|%s]]', weapon('HeavySlamAttack'), 'slam-damage')
			:row('HeavySlamAttackCritChance', '[[Critical Hit|%s]]', weapon('CritChance', 1), 'crit-chance')
			:row('HeavySlamAttackCritMultiplier', '[[Critical Hit|%s]]', weapon('CritMultiplier', 1), 'crit-multiplier')
			:row('HeavySlamElement', '%s', weapon('HeavySlamElement'), 'slam-element')
			:row('HeavySlamForcedProcs', '%s', weapon.HeavySlamForcedProcs and weapon('HeavySlamForcedProcs'), 'forced-procs')
			:row('HeavySlamRadius', '%s', weapon('HeavySlamRadius'), 'slam-radius')
			:row('HeavyAttackStatusChance', '[[Status Chance|%s]]', weapon('StatusChance', 1), 'status-chance')
		:done()
		
		:group():header('%s', 'slam-attack')
			:row('SlamAttack', '[[Melee#Slam Attack|%s]]', weapon('SlamAttack'), 'slam-damage')
			:row('SlamAttackCritChance', '[[Critical Hit|%s]]', weapon('CritChance', 1), 'crit-chance')
			:row('SlamAttackCritMultiplier', '[[Critical Hit|%s]]', weapon('CritMultiplier', 1), 'crit-multiplier')
			:row('SlamRadius', '%s', weapon('SlamRadius'), 'slam-radius')
			:row('SlamElement', '%s', weapon('SlamElement'), 'slam-element')
			:row('SlamForcedProcs', '%s', weapon.SlamForcedProcs and weapon('SlamForcedProcs'), 'forced-procs')
			:row('SlamAttackStatusChance', '[[Status Chance|%s]]', weapon('StatusChance', 1), 'status-chance')
		:done()
		
		:group():header('%s', 'slide-attack')
			:row('SlideAttack', '[[Melee#Slide Attack|%s]]', weapon('SlideAttack'), 'slide-damage')	-- Archmelees don't have slide attacks
			:row('SlideAttackCritChance', '[[Critical Hit|%s]]', weapon('CritChance', 1), 'crit-chance')
			:row('SlideAttackCritMultiplier', '[[Critical Hit|%s]]', weapon('CritMultiplier', 1), 'crit-multiplier')
			:row('SlideElement', '%s', weapon('SlideElement'), 'slide-element')
			:row('SlideAttackStatusChance', '[[Status Chance|%s]]', weapon('StatusChance', 1), 'status-chance')
		:done()
	end
	
	Infobox:group():header('%s', 'miscellaneous')
		:row('Augment', '[[Weapon Augments|%s]]', weapon.Augment, 'augments')	-- TODO: Rename key to Augments b/c some weapons have multiple augments; also hook getter to M:Weapons
		:row('CompatibilityTags', '[[Compatibility Tag|%s]]', weapon.CompatibilityTags and compatibilityTags, 'compatibility-tags')
		:row('DefaultUpgrades', '%s', weapon.DefaultUpgrades and weapon('DefaultUpgrades'), 'default-upgrades')
		:row('RivenFamily', '[[Riven Mods|%s]]', weapon('Family'), 'riven-family')
		:row('ExilusPolarity', '[[Exilus Weapon Adapter|%s]]', weapon.ExilusPolarity and weapon('ExilusPolarity'), 'exilus-polarity')
		:row('Introduced', '%s', weapon.Introduced and weapon('Introduced'), 'introduced')
		:row('Polarities', '[[Polarity|%s]]', weapon('Polarities'), 'polarities')
		:row('SellPrice', '%s', weapon.SellPrice and weapon('SellPrice'), 'sell-price')
		:row('StancePolarity', '[[Stance|%s]]', weapon.StancePolarity and weapon('StancePolarity'), 'stance-polarity')	-- Note Exalted weapons will not show this row
		:row('SyndicateEffect',	'[[Syndicate Radial Effects|%s]]', weapon('SyndicateEffect'), 'syndicate-effect')
		:row('Users', '%s', weapon.Users and weapon('Users'), 'users')
		:row('Variants', '%s', weapon.Family and weapon('FamilyList'), 'variants')
	:done()
	
	local vendorStr = Vendor._buildVendorSourceStrings(name)
	local baroStr = Baro._buildBaroSourceStrings(name)
	
	Infobox:group():header('%s', 'vendor-sources')
		:tag('data'):attr('source', 'Offerings')
			:tag('default')
				:tag('div'):addClass('tabber-borderless')
					:wikitext(('<tabber>%s%s</tabber>'):format(
					(vendorStr ~= '') and '|-|Vendors='..vendorStr..'\n' or '',
					(baroStr ~= '') and '|-|Baro Ki\'Teer='..baroStr..'\n' or ''
				)):done()
			:done()
		:done()
	:done()
	:group():header('%s', 'article-categories')
		:tag('data'):attr('source', 'Categories')
			:tag('default')
				:tag('div')
					:wikitext(Weapon._statFormat(weapon, 1, 'Categories'))
				:done()
		:done()
	:done()
	
	return frame:preprocess(tostring(Infobox))
end
}
Advertisement