Module:Weapons/characteristics

Submodule for handling Characteristics section on weapon articles. Used mainly for advantages and disadvantages strings. See Braton#Characteristics for sample output.

Documentation

Package items

weapons/characteristics.advantages(frame) (function)
Builds string of advantages or disadvantages of a weapon. Percentiles categorization:
  • 90-100% - "Very high"
  • 75-90% - "High"
  • 60-75% - "Above average"
  • 45-60% - Average, doesn't display anything
  • 30-45% - "Below average"
  • 15-30% - "Low"
  • 0-15% - "Very low"
Parameter: frame Frame object (table)
Returns: str preprocessed wikitext string (string)

Created with Docbunto

See Also

Code


---	Submodule for handling Characteristics section on weapon articles.
--	Used mainly for advantages and disadvantages strings. See [[Braton#Characteristics]] for sample output. 
--	
--	@module		weapons/characteristics
--	@alias		p
--	@author		[[User:FINNER]]
--	@release	stable
--	@require	[[Module:String]]
--	@require	[[Module:Tooltips]]
--	@require	[[Module:Weapons]]
--	@require	[[Module:Weapons/data]]
--	@require	[[Module:Weapons/ppdata]]
--	<nowiki>

local p = {}

local trim = require('Module:String').trim
local tooltip = require 'Module:Tooltips' 'Weapons' 'full'
local Weapon = require('Module:Weapons')
local ppData = mw.loadData('Module:Weapons/ppdata')

---	Builds string of weapons ranked in nth place.
--	@function		behind
--	@param			{table} data
--	@param			{string} place
--	@param			{string} slot
--	@returns		{string} String of weapon names
local function behind(data, place, slot)
	local weapons = {}
	for _, weapDict in pairs(data) do
		if weapDict.Place == place then
			for _, weapName in ipairs(weapDict) do
				table.insert(weapons, tooltip(weapName))
			end
			break
		end
	end
	if #weapons < 3 then
		return table.concat(weapons, ' and ')
	end
	weapons[#weapons] = 'and ' .. weapons[#weapons]
	return table.concat(weapons, ', ')
end

---	Builds string of advantages or disadvantages of a weapon.
--	Percentiles categorization:
--	* 90-100% - "Very high"
--	* 75-90% - "High"
--	* 60-75% - "Above average"
--	* 45-60% - Average, doesn't display anything
--	* 30-45% - "Below average"
--	* 15-30% - "Low"
--	* 0-15% - "Very low"
--	@function		p.advantages
--	@param			{table} frame Frame object
--	@returns		{string} str preprocessed wikitext string
function p.advantages(frame)
	local args = frame.args
	local name = mw.text.decode(args['Name'])
	local isAdv = (not(args['Dis']) and args['Dis'] ~= '') and true or false
	local weapon = Weapon._getWeapon(name)
	local slot = weapon.Slot
	
	-- Additional notes as defined by editors outside of automated stat comparison
	local check = trim(args[1])
	local headerText = string.format('<b>%s over other %s weapons (excluding modular weapons):</b>', (isAdv and 'Advantages' or 'Disadvantages'), slot)
	
	-- TODO: May try to not hard code percentiles here and just fetch the percentile table from
	-- [[Module:Weapons/preprocess]]
	local percentileLegendSpan = mw.html.create('span')
		:css('border-bottom', '2px dotted #808080')
		:css('padding', '0em')
		:attr('title', 'Percentiles: 0-15% = "Very low" | 15-30% = "Low" | 30-45% = "Below average" | 60-75% = "Above average" | 75-90% = "High" | 90-100% = "Very high"')
		:wikitext(headerText)

	local str = { tostring(percentileLegendSpan), check }
	
	local atts, attacks, data, stats = {}, {}, {}, {}
	for i, attack in ipairs(weapon.Attacks) do
		local attackCharacteristics = { '*' .. attack.AttackName .. '<small> (wiki attack index ' .. i .. ')</small>' }
		
		atts = {
			CritChance = "crit chance",
			CritMultiplier = "crit multiplier",
			TotalDamage = "total damage",
			FireRate = "fire rate",
			StatusChance = "status chance",
		}
		if attack.Falloff or weapon.Falloff then
			atts.FalloffEnd = "maximum falloff distance"
			atts.FalloffRate = "active falloff slope"
		end
		if (weapon.Multishot and weapon.Multishot > 1) or (attack.Multishot and attack.Multishot > 1) then
			atts.Multishot = "multishot"
			atts.AvgCritCount = "average number of crits per shot"
			atts.AvgProcCount = "average number of procs per shot"
		end
		if slot == 'Melee' or slot == 'Archmelee' then
			atts.FireRate = "attack speed"
			atts.MeleeRange = "attack range"
		else
			atts.Magazine = "magazine"
			atts.AmmoMax = "ammo max"
			atts.ReloadSpeed = "reload speed"
		end
		if slot ~= 'Archgun (Atmosphere)' and slot ~= 'Archmelee' and weapon.Class ~= 'Exalted Weapon' then
			atts.Disposition = "disposition"
		end
		
		stats = {}
		ppAttack = ppData[slot].Attacks[i] or {Top3 = {}, Percentiles = {}}
		data = ppAttack.Top3
		for statKey, u in pairs(data) do
			if atts[statKey] then
				local label = Weapon._getValue(weapon, statKey, i)
				
				if label == nil then
					error('p.advantages(frame):' .. ' label cannot be nil for key "' .. statKey .. '" and entry '.. mw.dumpObject(weapon))
				end
				
				stats[statKey] = label	-- Storing stat value for later when finding out what percentile it falls under
				temp = u['' .. label]	-- Seeing if calculated stat matches any top 3 entries in ppData
				
				label = Weapon._getFormattedValue(weapon, statKey, i)
				
				if temp and temp.Place and isAdv then
					if temp.Place == 'first' then
						table.insert(attackCharacteristics, ('**Highest %s (%s)'):format(atts[statKey], label) )
					elseif temp.Place == 'second' then
						table.insert(attackCharacteristics, ('**Second highest %s (%s) behind %s'):format(atts[statKey], label, behind(u, 'first', slot) ) )
					else
						table.insert(attackCharacteristics, ('**Third highest %s (%s) behind %s'):format(atts[statKey], label, behind(u, 'second', slot) ) )
					end
					atts[statKey] = nil
				end
			end
		end
		
		data = ppAttack.Percentiles
		for statKey, statName in pairs(atts) do
			if data[statKey] then
				
				local label
				-- Label is not necessarily the same as statKey. For example, we want readers to 
				-- see "1.15x animation speed" instead of "1.15 attacks/sec" for the FireRate key of a melee weapon
				if (statKey == 'ReloadSpeed') then
					label = Weapon._getFormattedValue(weapon, 'Reload', i)
				elseif (statKey == 'FireRate' and Weapon._getValue(weapon, 'IsMelee')) then
					label = Weapon._getFormattedValue(weapon, 'AttackSpeed', i)
				else
					label = Weapon._getFormattedValue(weapon, statKey, i)
				end
			
				local statValue = stats[statKey]
				if (statValue == nil) then
					error('p.advantages(frame): statValue is nil, cannot compare against a nil value; must be a number value')
				end
				
				-- Data validation; ideally we should perform it at data level instead of logic level, but
				-- due to how we store data on the wiki, we have limited tooling to support this (e.g. no data definition language)
				for i = 1, 7, 1 do
					if (data[statKey][i] == nil) then
						error(mw.dumpObject(data[statKey]))
					end
				end
				
				if isAdv then
					if statValue > data[statKey][1] then
						table.insert(attackCharacteristics, ('**Very high %s (%s)'):format(statName, label) )
					elseif statValue > data[statKey][2] then
						table.insert(attackCharacteristics, ('**High %s (%s)'):format(statName, label) )
					elseif statValue > data[statKey][3] then
						table.insert(attackCharacteristics, ('**Above average %s (%s)'):format(statName, label) )
					end
				else
					if statValue <= data[statKey][7] then
						table.insert(attackCharacteristics, ('**Very low %s (%s)'):format(statName, label) )
					elseif statValue <= data[statKey][6] then
						table.insert(attackCharacteristics, ('**Low %s (%s)'):format(statName, label) )
					elseif statValue <= data[statKey][5] then
						table.insert(attackCharacteristics, ('**Below average %s (%s)'):format(statName, label) )
					end
				end
			end
		end
		
		-- If no distinct advantange/disadvantage is added
		if (#attackCharacteristics == 1) then
			table.insert(str, attackCharacteristics[1]..(isAdv and '\n**No numerical advantages.' or '\n**No numerical disadvantages.'))
		else
			table.insert(str, table.concat(attackCharacteristics, '\n'))
		end
	end
	
	str = table.concat(str, '\n')
	return frame:preprocess(str)
end

return p