warframe

Documentation for this module may be created at Module:MasteryRank/dev/doc

--- '''MasteryRank''' is a helper module for constructing wikitables pertaining to
--	[[Mastery Rank]] and [[Clan]] affinity.<br />
--  
--  On this Wiki, MasteryRank is used in:
--	* [[Template:MasteryTable]]
--	* [[Template:ClanAffinityTable]]
--  
--  @module		masteryrank
--  @alias		p
--  @author		[[User:FINNER|FINNER]]
--	@attribution [[User:Synthtech|Synthtech]]
--	@attribution [[User:Cephalon Scientia|Cephalon Scientia]]
--  @image		Mastery Info New.png
--	@require	[[w:c:dev:Module:Arguments|Module:Arguments]]
--  @require	[[Module:Version]]
--  @require	[[Module:Math]]
--  @release	stable
--  <nowiki>

local p = {}

local Version = require("Module:Version")
local Math = require("Module:Math")
local Args = require("Dev:Arguments")

-- number of weapons that can go up to R40 (Kuva Weapons, Tenet Weapons, and Paracesis)
local RANK_40_WEAPONS_COUNT = 19 + 12 + 1
local MASTERY_CLASSES = {
	"Unranked", "Initiate", "Novice", "Disciple", "Seeker", 
	"Hunter", "Eagle", "Tiger", "Dragon", "Sage", "Master", 
	"Legendary"
}
local MASTERY_TITLES = { "Gold ", "", "Silver " }

local function getMasteryRank(totalXp)
	local rank, tillNext
	-- When total XP exceeds or equal to MR30, XP calculation for Legendary ranks will be comprised of
	-- two equations: 2500 * (MR before Legendary)^2 and 147500 * Legendary rank #
	if (totalXp >= (2500 * 30 ^ 2)) then
		local legendaryRank = math.floor((totalXp - (2500 * math.pow(30, 2))) / 147500)	-- Subtract total XP @ MR30
		rank = 30 + legendaryRank	-- MR30 + Legendary rank #
		tillNext = Math.formatnum((2500 * math.pow(30, 2)) + (147500 * (legendaryRank + 1)) - totalXp)
	else
		rank = math.floor(math.pow(totalXp / 2500, 0.5))
		tillNext = Math.formatnum(2500 * math.pow(rank + 1, 2) - totalXp)
	end
	
	local i = math.ceil(rank / 3) + 1
	local j = math.mod(rank, 3) + 1
	local class = MASTERY_CLASSES[i]
	local title = MASTERY_TITLES[j]

	if rank == 29 then
		title = "Middle "
	elseif rank == 30 then
		title = "True "
	elseif rank == 0 or rank > 30 or title == nil then
		class = class.." "..(rank - 30)	-- For the number in Legendary ranks
		title = ""
	end
	
	local rankNext = rank + 1
	i = math.ceil(rankNext / 3) + 1
	j = math.mod(rankNext, 3) + 1
	local classNext = MASTERY_CLASSES[i]
	local titleNext = MASTERY_TITLES[j]

	if rank > 30 or title == nil then
		classNext = classNext.." "..(rankNext - 30)
		titleNext = ""
	elseif rankNext == 29 then
		titleNext = "Middle "
	elseif rankNext == 30 then
		titleNext = "True "
	end

	return '<span style="border-bottom: 1px dotted;" title="MR'..rank..' ('..title..class..'); '..tillNext..' Mastery until MR'..rankNext..' ('..titleNext..classNext..')">[?]</span>'
end

local function getClanRank(totalXp)
	local ranks = {
		10000, 34000, 69000, 114000, 168000, 231000, 302000, 381000, 
		468000, 563000, 665000, 774000, 891000
	}
	local rank

	for i, rankXp in ipairs(ranks) do
		if totalXp < rankXp then
			rank = i - 1
			break
		end
	end

	if rank == nil then
		rank = 13
	end

	local str = ""
	if rank < 13 then
		local tillNext = Math.formatnum(ranks[rank + 1] - totalXp)
		str = '<span style="border-bottom: 1px dotted;" title="Rank '..rank..', ('..tillNext..' Affinity until next rank)">[?]</span>'
	else
		str = '<span style="border-bottom: 1px dotted;" title="Rank '..rank..'">[?]</span>'
	end

	return str
end

local function buildMasteryTable(...)
	local argTable = Args.getArgs(...)
	
	-- Mapping keys to base mastery xp at max rank 
	-- (excluding rank 40 weapons which are edge cases)
	local baseMasteryXp = {
		warframes = 6000,
		plexus = 6000,
		primaries = 3000,
		secondaries = 3000,
		melee = 3000,
		kitguns = 3000,
		amps = 3000,
		houndWeapons = 3000,
		kubrows = 6000,
		kavats = 6000,
		predasite = 6000,
		vulpaphyla = 6000,
		moas = 6000,
		hounds = 6000,
		sentinels = 6000,
		sentinelWeapons = 3000,
		archwings = 6000,
		archGuns = 3000,
		archMelees = 3000,
		kDrives = 6000,
		necramechs = 8000,
		junctions = 1000,
		intrinsics = 15000,
	}
	
	-- error(mw.dumpObject(argTable))
	
	-- Returns a tuple of the category count and total mastery XP within that category
	local masteryTableRow = function(category, additionalXp)
		additionalXp = additionalXp and additionalXp or 0
		local count = argTable[category] or error('buildMasteryTable(...): invalid category "'..category..'"')
		local baseXp = baseMasteryXp[category] or error('buildMasteryTable(...): invalid category "'..category..'"')
		-- Base xp per fully maxed item * number of items in category
		local totalXp = baseXp * count
		return count, Math.formatnum(totalXp + additionalXp)
	end
	
	local tHeader = string.format([[
		{| class="article-table" style="margin:1em auto;"
		! style="text-align:center;" | Category
		! style="text-align:left;"   | Count
		! style="text-align:left;"   | Mastery
		|+ <small><span style="text-transform:uppercase; color:green;">Last updated: <span style="text-decoration:underline;">%s</span>&nbsp;(%s)</span></small>
		|-
	]], argTable['ver'] and Version.getVersionLink(argTable['ver']) or 'N/A', argTable['platform'] or 'N/A')

	local tRows = ([=[
		| [[Warframes]] || %s || %s
		|-
		| [[:Category:Primary Weapons|Primaries]] || %s || %s
		|-
		| [[:Category:Secondary Weapons|Secondaries]] || %s || %s
		|-
		| [[:Category:Melee Weapons|Melee]]<br />(including [[Zaw]]s)|| %s || %s
		|-
		| [[Kitgun]]s || %s || %s
		|-
		| colspan=3 |
		|-
		| Normal [[Planet|Missions]] (nodes + [[Junction]]s) || %s (%s + %s) || %s (%s + %s)
		|-
		| [[The Steel Path]] (nodes + [[Junction]]s) || %s (%s + %s) || %s (%s + %s)
		|-
		| [[Intrinsics]] || %s || %s
		|-
		| [[Plexus]] || %s || %s
		|-
		| colspan=3 |
		|-
		| [[Sentinel]]s || %s || %s
		|-
		| [[:Category:Robotic Weapons|Robotic Weapons]] || %s || %s
		|-
		| [[Hound]] Weapons || %s || %s
		|-
		| [[Kubrow]]s || %s || %s
		|-
		| [[Kavat]]s || %s || %s
		|-
		| [[Predasite]]s || %s || %s
		|-
		| [[Vulpaphyla]]s || %s || %s
		|-
		| [[MOA (Companion)|MOA]]s || %s || %s
		|-
		| [[Hound]]s || %s || %s
		|-
		| colspan=3 |
		|-
		| [[Archwing]]s || %s || %s
		|-
		| [[Archwing|Arch-Guns]] || %s || %s
		|-
		| [[Archwing|Arch-Melees]] || %s || %s
		|-
		| [[Amp]]s || %s || %s
		|-
		| [[K-Drive]]s || %s || %s
		|-
		| [[Necramech]]s || %s || %s
		|-
		]=]):format(
			masteryTableRow('warframes'),
			masteryTableRow('primaries', (11 * 1000) + (4 * 1000)),	-- Extra XP for R40 Kuva/Tenet weapons
			masteryTableRow('secondaries', (5 * 1000) + (4 * 1000)),	-- Extra XP for R40 Kuva/Tenet weapons
			masteryTableRow('melee', (1 * 1000) + (4 * 1000) + 1000),	-- Extra XP for R40 Kuva/Tenet weapons and Paracesis
			masteryTableRow('kitguns'),
			argTable['missions'] + argTable['junctions'], argTable['missions'], argTable['junctions'], Math.formatnum(argTable['missionsXp'] + 1000 * argTable['junctions']), argTable['missionsXp'], 1000 * argTable['junctions'],
			argTable['missions'] + argTable['junctions'], argTable['missions'], argTable['junctions'], Math.formatnum(argTable['missionsXp'] + 1000 * argTable['junctions']), argTable['missionsXp'], 1000 * argTable['junctions'],
			-- argument #22
			masteryTableRow('intrinsics'),
			masteryTableRow('plexus'),
			masteryTableRow('sentinels'),
			masteryTableRow('sentinelWeapons'),
			masteryTableRow('houndWeapons'),
			masteryTableRow('kubrows'),
			masteryTableRow('kavats'),-- argument #35 + #36, error is here for some reason
			masteryTableRow('predasite'),
			masteryTableRow('vulpaphyla'),
			masteryTableRow('moas'),
			masteryTableRow('hounds'),
			masteryTableRow('archwings'),
			masteryTableRow('archGuns', (2 * 1000)),	-- Extra XP for R40 Kuva weapons
			masteryTableRow('archMelees'),
			masteryTableRow('amps'),
			masteryTableRow('kDrives'),
			masteryTableRow('necramechs')
		)
	
	-- Missions and junctions needed to be counted twice, once for normal star chart and second time for Steel Path
	local totalCount = argTable['missions'] + argTable['junctions']
	for argName, value in pairs(argTable) do
		if (argName ~= 'platform' and argName ~= 'ver' and 
				argName ~= 'missionsXp' and argName ~= 'exclusives' and 
				argName ~= 'exclusivesXp') then
			totalCount = totalCount + value
		end
	end
	
	-- Missions and junctions needed to be counted twice, once for normal star chart and second time for Steel Path
	local totalXp = (argTable['missionsXp'] * 2) + (argTable['junctions'] * 1000) + (1000 * RANK_40_WEAPONS_COUNT)
	
	for key, baseXp in pairs(baseMasteryXp) do
		totalXp = totalXp + (argTable[key] * baseXp)
	end
	
	-- local masteryRank = getMasteryRank(totalXp)
	-- local MRNoExclusive = getMasteryRank(totalXp - argTable['exclusivesXp'])

	-- local tFooter = string.format([=[
	-- 	! Total !! %d !! %s<sup>%s</sup>
	-- 	|-
	-- 	! Minus [[Exclusive Mastery|Exclusives]] !! %d !! %s<sup>%s</sup>
	-- 	|-
	-- 	|}
	-- 	]=], 
	-- 	totalCount, Math.formatnum(totalXp), masteryRank,
	-- 	totalCount - argTable['exclusives'], Math.formatnum(totalXp - argTable['exclusivesXp']), MRNoExclusive
	-- )

	return tHeader..tRows--..tFooter
end

--- Builds total Mastery table.
--	@function		p.buildMasteryTable
--	@param			{table} frame
--	@return			{string} Resultant table in wikitext
function p.buildMasteryTable(frame)
	assert(frame and frame.args ~= nil, 'p.buildMasteryTable(frame): must pass the number of items in each category')
	return buildMasteryTable(frame.args)
end

local function buildClanAffinityTable(...)
	local argTable = Args.getArgs(...)

	local tHeader = string.format([[
		{| class="article-table" style="margin:1em auto;"
		! style="text-align:center;" | Category
		! style="text-align:left;"   | Count
		! style="text-align:left;"   | Affinity
		|+ <small><span style="text-transform:uppercase; color:green;">Last updated: <span style="text-decoration:underline;">%s</span></span></small>
		|-
	]], argTable['ver'] and Version.getVersionLink(argTable['ver']) or 'N/A')

	local tRows = string.format([=[
		| [[Warframes]] || %d || %s
		|-
		| [[Weapons]] || %d || %s
		|-
		| [[Companions]] || %d || %s
		|-
		| [[Archwing]]s || %d || %s
		|-
		| [[Archwing|Arch-Guns]] || %d || %s
		|-
		| [[Archwing|Arch-Melees]] || %d || %s
		|-
		| [[Pigment]]s || %d || %s
		|-
		| Backdrops || %d || %s
		|-
		| [[Orbiter Segments]] || %d || %s
		|-
		| [[Resources]] || %d || %s
		|-
		| [[Gear]] || %d || %s
		|-
		| Upgrades || %d || %s
		|-
		| [[Dojo#Rooms_and_Decorations|Rooms]] || %d || %s
		|-
		| [[Solar Rail (Dark Sectors)|Solar Rail]] Construction || %d || %s
		|-
		]=],
		argTable['warframes'], Math.formatnum(9500 * (argTable['warframes'] - 1) + 4500), -- Yareli
		argTable['weapons'], Math.formatnum(3000 * (argTable['weapons'] - 6) + 36000), -- Hema, Lenz, Kreska, Ocucor, Battacor, Ghoulsaw
		argTable['companions'], Math.formatnum(3000 * argTable['companions']),
		argTable['archwings'], Math.formatnum(7500 * argTable['archwings']),
		argTable['archGuns'], Math.formatnum(3000 * (argTable['archGuns'] - 1) + 5000), -- Grattler
		argTable['archMelees'], Math.formatnum(3000 * (argTable['archMelees'] - 1) + 5000), -- Knux
		argTable['pigments'], Math.formatnum(1000 * argTable['pigments']),
		argTable['backdrops'], Math.formatnum(1000 * argTable['backdrops']),
		argTable['segments'], Math.formatnum(6000 * argTable['segments']),
		argTable['resources'], Math.formatnum(2000 * (argTable['resources'] - 1) + 3000), -- Synthula
		argTable['gear'], Math.formatnum(3000 * (argTable['gear'] - 1) + 2000), -- Infested Catalyst
		argTable['upgrades'], Math.formatnum(5000 * argTable['upgrades']),
		argTable['rooms'], Math.formatnum(10000 * (argTable['rooms'] - 4) + 60000), -- Temple, Dueling Room, Obstacle Course + Architect
		argTable['solarRail'], Math.formatnum(17000 * argTable['solarRail'])
	)

	local total = 0
	for argName, value in pairs(argTable) do
		if (argName ~= 'platform' and argName ~= 'ver') then
			total = total + value
		end
	end

	local totalXp = 9500*(argTable['warframes']-1) + 3000*((argTable['weapons']-6) + argTable['companions'] + (argTable['archGuns']-1)
		+ (argTable['archMelees']-1) + (argTable['gear']-1)) + 7500*argTable['archwings'] + 1000*argTable['pigments'] + 1000*argTable['backdrops']
		+ 6000*argTable['segments'] + 2000*(argTable['resources']-1) + 5000*argTable['upgrades'] + 10000*(argTable['rooms']-4) + 17000*argTable['solarRail']
		+ 115500
	local clanRank = getClanRank(totalXp)
	local clanRankExclusive = getClanRank(totalXp - 20000)

	local tFooter = string.format([=[
		! Total !! %d !! %s<sup>%s</sup>
		|-
		! Minus Exclusives !! %d !! %s<sup>%s</sup>
		|-
		|}
		]=],
		total, Math.formatnum(totalXp), clanRank,
		total - 2, Math.formatnum(totalXp - 20000), clanRankExclusive
	)

	return tHeader..tRows..tFooter
end

--- Builds total Clan affinity table.
--	@function		p.buildClanAffinityTable
--	@param			{table} frame
--	@return			{string} Resultant table in wikitext
function p.buildClanAffinityTable(frame)
	assert(frame and frame.args ~= nil, 'p.buildClanAffinityTable(frame): must pass the number of items in each category')
	return buildClanAffinityTable(frame.args)
end

return p