Module:Giants' Foundry Calculator

From RuneRealm Wiki
Jump to navigation Jump to search

Documentation for this module may be created at Module:Giants' Foundry Calculator/doc

local experience = require( 'Module:Experience' )
local paramtest = require ( 'Module:Paramtest' )
local allMoulds = require( "Module:Giants' Foundry Calculator/Moulds" )
local gePrices = mw.loadJsonData('Module:GEPrices/data.json')
local commas = require('Module:Addcommas')._add
local coins = require('Module:Coins')._amount
local scp = require('Module:SCP')._main

local p = {}

function p.lazyplink(str)
	return '[[File:' .. str .. '.png|link=' .. str .. ']] [[' .. str .. ']]'
end

function p.strtobool(str)
	local result = false
	if str == 'true' then
		result = true
	end
	return result
end

function p.experience(quality)
	local result = (math.floor(quality*quality/73)+math.floor(1.5*quality)+1)*30
	return result
end

function p.metalScore(tier1, tier2, amount1, amount2)
	local value1
	local value2
	if tier2 == tier1 or tier2 == 0 then
		value1 = 10*tier1
		value2 = 0
	else
		value1 = 10*tier1*amount1/28
		value2 = 10*tier2*amount2/28
	end
	local result = math.floor((math.floor(10*value1)+math.floor(10*value2)+math.floor(value1*value2))/10)
	return result
end

local barTier = {
	['None'] = 0,
	['Bronze'] = 1,
	['Iron'] = 2,
	['Steel'] = 3,
	['Mithril'] = 4,
	['Adamantite'] = 5,
	['Runite'] = 6
}

local itemBarWorth = {
	['platebody'] = 4,
	['warhammer'] = 2,
	['battleaxe'] = 2,
	['chainbody'] = 2,
	['kiteshield'] = 2,
	['2h sword'] = 2,
	['platelegs'] = 2,
	['plateskirt'] = 2,
	['scimitar'] = 1,
	['longsword'] = 1,
	['full helm'] = 1,
	['sq shield'] = 1,
	['claws'] = 1
}

--requests and their frequency, sums up to 24
local requestCombinations = {
	[{'broad','heavy'}] = 3,
	[{'broad','light'}]=3,
	[{'broad','spiked'}]=1,
	[{'broad','flat'}]=1,
	[{'narrow','heavy'}]=3,
	[{'narrow','light'}]=3,
	[{'narrow','spiked'}]=1,
	[{'narrow','flat'}]=1,
	[{'heavy','spiked'}]=2,
	[{'heavy','flat'}]=2,
	[{'light','spiked'}]=2,
	[{'light','flat'}]=2
}

function p.difficulty(score)
	local result = 0
	if score < 20 then
		result = 3
	elseif score < 60 then
		result = 4
	elseif score < 90 then
		result = 5
	elseif score < 120 then
		result = 6
	else
		result = 7
	end
	return result
end

--maps the name of each mould to whether they are unlocked
function p.mouldsUnlocked(mould1,mould2,mould3,mould4,mould5,mould6,mould7,mould8,mould9,mould10,mould11,mould12,mould13,mould14,mould15)
	local results = {
		['Stiletto Forte'] = p.strtobool(mould1),
		['Defender Base'] = p.strtobool(mould2),
		['Juggernaut Forte'] = p.strtobool(mould3),
		['Chopper Forte +1'] = p.strtobool(mould4),
		['Spiker!'] = p.strtobool(mould5),
		['Flamberge Blade'] = p.strtobool(mould6),
		['Serpent Blade'] = p.strtobool(mould7),
		['Claymore Blade'] = p.strtobool(mould8),
		['Fleur de Blade'] = p.strtobool(mould9),
		['Choppa!'] = p.strtobool(mould10),
		['Corrupted Point'] = p.strtobool(mould11),
		['Defenders Tip'] = p.strtobool(mould12),
		['Serrated Tip'] = p.strtobool(mould13),
		['Needle Point'] = p.strtobool(mould14),
		['The Point!'] = p.strtobool(mould15)
	}
	return results
end

--gives the average quality and average xp over all the possible requests. Done by going over all possible mould combinations and seeing which one is the best
function p.averageQualityExp(level,unlockedMoulds,metalscore)
	local scores = {['Forte'] = {}, ['Blade'] = {}, ['Tip'] = {}}
	for combo,_ in pairs(requestCombinations) do
		for mouldtype,__ in pairs(scores) do
			scores[mouldtype][combo]=0
		end
	end
	local temp
	for _,mould in ipairs(allMoulds) do
		if mould.level <= level and (mould.default or unlockedMoulds[mould.name]) then
			for combo,__ in pairs(requestCombinations) do
				temp = mould[combo[1]]+mould[combo[2]]
				if scores[mould.type][combo] < temp then
					scores[mould.type][combo] = temp
				end
			end
		end
	end
	local scoresMerged = {}
	local experienceMerged = {}
	for combo,_ in pairs(requestCombinations) do
		scoresMerged[combo] = metalscore + scores['Forte'][combo]+scores['Blade'][combo]+scores['Tip'][combo]
		experienceMerged[combo] = p.experience(scoresMerged[combo])
	end
	local scoresAverage = 0
	local experienceAverage = 0
	for combo,value in pairs(requestCombinations) do
		scoresAverage = scoresAverage + value*scoresMerged[combo]/24
		experienceAverage = experienceAverage + value*experienceMerged[combo]/24
	end
	return {scoresAverage,experienceAverage}
end

function p._main(args)
	local bar1 = paramtest.default_to(args.metal1,'Bronze')
	local bar2 = paramtest.default_to(args.metal2,'Iron')
	local tier1 = barTier[bar1]
	local tier2 = barTier[bar2]
	local amount1 = paramtest.default_to(tonumber(args.metal1Amount), 14)
	local amount2 = 28 - amount1
	local metalwarning = ''
	--since the second type can be none, we make sure that the bars per sword is always 28
	if tier2==0 and amount2>0 then
		amount1 = 28
		amount2 = 0
		metalwarning = '<b>Warning:</b> you used at least 1 bar with no type! Defaulted to 28 bars of the first type.<br>' 
	end
	local metalscore = p.metalScore(tier1,tier2,amount1,amount2)
	-- get all the unlocked moulds as strings
	local mould1,mould2,mould3,mould4,mould5,mould6,mould7,mould8,mould9,mould10,mould11,mould12,mould13,mould14,mould15 = paramtest.defaults{
		{args.stiletto_F, false},
		{args.defender_F, false},
		{args.juggernaut_F, false},
		{args.chopper_F, false},
		{args.spiker_F, false},
		{args.flamberge_B, false},
		{args.serpent_B, false},
		{args.claymore_B, false},
		{args.fleur_B, false},
		{args.choppa_B, false},
		{args.corrupted_T, false},
		{args.defenders_T, false},
		{args.serrated_T, false},
		{args.needle_T, false},
		{args.point_T, false}
	}
	local unlockedMoulds = p.mouldsUnlocked(mould1,mould2,mould3,mould4,mould5,mould6,mould7,mould8,mould9,mould10,mould11,mould12,mould13,mould14,mould15)
	local currentLevel
	local currentxp
	--since we get level or xp, we calculate the other for various purposes
	local levelToggle = paramtest.default_to(args.currentToggle,'Level')
	local current = paramtest.default_to(tonumber(args.current),1)
	if levelToggle == 'Level' then
		currentLevel = current
		currentxp = experience.xp_at_level_unr({args = {current}})
	else
		currentxp = current
		currentLevel = experience.level_at_xp_unr({args = {current}})
	end
	--note that average xp is not a function of average quality, since xp is not linear over quality
	local averages = p.averageQualityExp(currentLevel,unlockedMoulds,metalscore)
	local quality = averages[1]
	local xp = averages[2]
	
	local goal = paramtest.default_to(tonumber(args.goal),99)
	local goalToggle = paramtest.default_to(args.goalToggle,'Level')
	local swordsToMake
	local goalText = ''
	local goalWarning = ''
	local toGo
	--different cases for the various goals
	if goalToggle == 'Level' then
		if goal == 0 or goal>126 then
			goalWarning = '<b>Warning:</b> your current goal is not a level. Please enter a nonnegative level reachable within 200m xp or switch to xp.<br>'
			swordsToMake = 0
		elseif goal <= currentLevel then
			swordsToMake = 0
		else
			toGo = experience.xp_at_level_unr({args = {goal}}) - currentxp
			swordsToMake = math.ceil(toGo/xp)
		end
		goalText = 'To get to level ' .. tostring(goal) .. ' [[Smithing]] from level ' .. tonumber(currentLevel) .. ' [[Smithing]] (' .. commas(currentxp) .. ' experience) <b>' .. commas(swordsToMake) .. '</b> swords will have to be made on average.\n'
	elseif goalToggle == 'Experience' then
		if goal < currentxp then
			toGo = 0
		else
			toGo = goal - currentxp
		end
		swordsToMake = math.ceil(toGo/xp)
		goalText = 'To get to ' .. commas(goal) .. ' experience (level ' .. tostring(experience.level_at_xp({args = {goal}})) .. ' [[Smithing]]) from ' .. commas(currentxp) .. ' (level ' .. tostring(currentLevel) .. ' [[Smithing]]) <b>' .. commas(swordsToMake) .. '</b> swords will have to be made on average.\n'
	else
		swordsToMake = math.ceil(goal/quality)
		toGo = math.floor(swordsToMake*xp)
		-- xp can't go above 200m
		if toGo+currentxp > 200000000 then
			toGo = 200000000-currentxp
		end
		local newxp = toGo+currentxp
		goalText = 'To get ' .. commas(goal) .. ' reputation <b>' .. commas(swordsToMake) .. '</b> swords will have to be made on average. This grants ' .. commas(toGo) .. ' experience putting you at ' .. commas(newxp) .. ' experience (level ' .. tostring(experience.level_at_xp({args = {newxp}})) .. ' [[Smithing]]).\n'
	end
	goalText = goalText .. '* Each sword is of average quality ' .. tonumber(quality) .. ', grants on average ' .. commas(math.floor(xp)) .. ' [[Smithing]] experience, awards on average '.. coins(math.floor(xp)*2) ..' and consists of ' .. tostring(p.difficulty(metalscore)) .. ' sections.\n'
	
	local bar1bar = bar1 .. ' bar'
	local bar2bar = bar2 .. ' bar'
	local bar1price = gePrices[bar1bar]
	local bar1toggle = p.strtobool(paramtest.default_to(args.metal1PriceToggle,'false'))
	if bar1toggle then
		bar1price = paramtest.default_to(args.metal1Price,gePrices[bar1bar])
	end
	local bar2price = 0
	local bar2toggle = p.strtobool(paramtest.default_to(args.metal2PriceToggle,'false'))
	local barText = ''
	local costText = '* ' .. commas(swordsToMake) .. ' swords will require '
	
	local itemPrice = 0
	local itemName = ''
	local totalItemPrice = 0
	local bestItem1price = 0
	local bestItem2price = 0
	
	if amount1 > 0 then
		bestItem1price = math.huge
		barText = barText .. '* ' .. tostring(amount1) .. ' ' .. p.lazyplink(bar1bar) .. ' at ' .. coins(bar1price) .. ' each will cost ' .. coins(amount1*bar1price) .. '.\n'
		for itemType,itemWorth in pairs(itemBarWorth) do
			itemName = bar1 .. ' ' .. itemType
			if (bar1 == 'Runite') then
				itemName = 'Rune ' .. itemType
			end
			if (bar1 == 'Adamantite') then
				itemName = 'Adamant ' .. itemType
			end
			itemPrice = gePrices[itemName] 
			if (itemPrice) then
				totalItemPrice = itemPrice*(amount1 / itemWorth)
				barText = barText .. '\n:* or ' .. tostring(amount1 / itemWorth) .. ' ' .. p.lazyplink(itemName) .. ' at ' .. coins(itemPrice) .. ' each for ' .. coins(totalItemPrice) .. '\n'
				if (totalItemPrice < bestItem1price) then
					bestItem1price = totalItemPrice
				end
			end
		end
		costText = costText .. commas(amount1*swordsToMake) .. ' ' .. p.lazyplink(bar1bar) .. 's'
	end
	if amount1 > 0 and amount2 > 0 then
		costText = costText .. ' and '
	end
	if amount2 > 0 then
		bestItem2price = math.huge
		bar2price = gePrices[bar2bar]
		if bar2toggle then
			bar2price = paramtest.default_to(args.metal2Price,gePrices[bar2bar])
		end
		barText = barText .. '* ' .. tostring(amount2) .. ' ' .. p.lazyplink(bar2bar) .. ' at ' .. coins(bar2price) .. ' each will cost ' .. coins(amount2*bar2price) .. '.\n'
		for itemType,itemWorth in pairs(itemBarWorth) do
			itemName = bar2 .. ' ' .. itemType
			if (bar2 == 'Runite') then
				itemName = 'Rune ' .. itemType
			end
			if (bar2 == 'Adamantite') then
				itemName = 'Adamant ' .. itemType
			end
			itemPrice = gePrices[itemName] 
			if (itemPrice) then
				totalItemPrice = itemPrice*(amount2 / itemWorth)
				barText = barText .. '\n:* or ' .. tostring(amount2 / itemWorth) .. ' ' .. p.lazyplink(itemName) .. ' at ' .. coins(itemPrice) .. ' each for ' .. coins(totalItemPrice) .. '\n'
				if (totalItemPrice < bestItem2price) then
					bestItem2price = totalItemPrice
				end
			end
		end
		costText = costText .. commas(amount2*swordsToMake) .. ' ' .. p.lazyplink(bar2bar) .. 's'
	end
	
	local swordCost = amount1*bar1price+amount2*bar2price
	local finalCost = swordCost*swordsToMake

	local swordCostWithItems = bestItem1price+bestItem2price
	local finalCostWithItems = swordCostWithItems*swordsToMake

	barText = barText .. '\n* Each sword will cost a total of ' .. coins(swordCost) .. ' using bars or ' .. coins(swordCostWithItems) .. ' using items.\n'
	costText = costText .. ', at a total cost of ' .. coins(finalCost) .. ' using bars, or at a total cost of ' .. coins(finalCostWithItems) .. ' using items.\n'
	
	local income = swordsToMake*xp*2
	local profit = income-finalCost
	local profitWithItems = income-finalCostWithItems
	
	local incomeText = '* ' .. coins(income) .. ' will be earned using bars for a total profit of ' .. coins(profit) .. ', or for a total profit of ' .. coins(profitWithItems) .. ' using items.\n'

	local repTotal = math.floor(quality*swordsToMake)
	local grogNumber = math.floor(repTotal/300)
	local grogValue = gePrices["Kovac's grog"]*grogNumber
	
	local repText = '* A total of ' .. commas(repTotal) .. " Reputation will be earned. If used to buy [[Kovac's grog]] an additional " .. coins(grogValue) .. ' will be earned at current GE prices.'
	
	local levelwarning = ''
	if currentLevel < 15 then
		levelwarning = '<b>Warning:</b> you need at least level 15 smithing to participate in the minigame. This might have unintended consequences for the calculations.<br>'
	elseif currentLevel < (tier1-1)*15+(tier1>=4 and 5 or 0)+(tier1>=5 and 5 or 0) or currentLevel < (tier2-1)*15+(tier2 >= 4 and 5 or 0)+(tier2 >= 5 and 5 or 0) then
		levelwarning = '<b>Warning:</b> one of the metals selected is above your current smithing level.<br>'
	end
	
	return metalwarning .. goalWarning .. levelwarning .. goalText .. barText ..costText .. incomeText .. repText
end

function p.main(frame)
	local args = frame.args
	return p._main(args)
end

return p