Module:Sandbox/User:Robert571/RunningCost: Difference between revisions

From WIDEVERSE Wiki
Jump to navigation Jump to search
No edit summary
(No difference)

Revision as of 11:55, 30 October 2021

Documentation for this module may be created at Module:Sandbox/User:Robert571/RunningCost/doc

local p = {}

local GEPRICES = mw.loadData('Module:GEPrices/data')
local backupgeprice = require('Module:Exchange')._price
local amount = require('Module:Currency_short')._amount
local frac = require('Module:GETotal').frac
local yesno = require('Module:Yesno')
local calcvalue = require('Module:Calcvalue')._get
local lang = mw.getContentLanguage()

-- Maximum number of methods of repair for an item
local MAX_NUM_METHODS = 4
-- Maximum number of unique items that a repair method can contain
local MAX_NUM_ITEM_TYPES = 20

-- references support
local USED_REFERENCES = false
local REFERENCES_GROUP_NAME = 'uc'
function ref(txt, name)
	USED_REFERENCES = true
	return mw.getCurrentFrame():extensionTag{
		name = 'ref',
		content = txt,
		args = { name = name, group = REFERENCES_GROUP_NAME }
	}
end
function add_reflist(t, colspan)
	if USED_REFERENCES then
		t = tostring(t)..mw.getCurrentFrame():extensionTag{name='references', args={group=REFERENCES_GROUP_NAME}}
		--t:tag('tr'):tag('td'):attr('colspan', colspan):wikitext( mw.getCurrentFrame():extensionTag{name='references', args={group=REFERENCES_GROUP_NAME}} )
	end
	return t
end
function SMITHING_DISCOUNT_REF()
	return ref("Cost can be reduced by 0.5% per [[Smithing]] level; 50% at 100 Smithing.", "smithing")
end
function DIVINE_CHARGE_REF()
	return ref("Can be reduced by [[Charge pack#Charge drain reduction|research]], [[equipment level]], [[Efficient]]/[[Enhanced Efficient]] perk and the [[Invention cape]] perk.", "divine charge")
end

local COMBAT_RATES = {
	[1] = {
		["chargesPerHour"] = (30*60),
		["name"] = "Average rate",
		ref = function()
			return ref(lang:formatNum(30*60).." charges per hour.", "rate1")
		end
	},
	[2] = {
		["chargesPerHour"] = (60*60),
		["name"] = "High rate",
		ref = function()
			return ref(lang:formatNum(60*60).." charges per hour.", "rate2")
		end
	},
	[3] = {
		["chargesPerHour"] = (100*60),
		["name"] = "Maximum rate",
		ref = function()
			return ref( lang:formatNum(100*60).." charges per hour.", "rate3")
		end
	},
}

-- Divine charge slot names and drain multipliers
-- Make all keys lowercase
local DIVINE_CHARGE_SLOTS = {
	["2h"] = {
		["name"] = "Two-handed",
		["chargeDrainMult"] = 1.5
	},
	["main hand"] = {
		["name"] = "Main hand",
		["chargeDrainMult"] = 1.0
	},
	["weapon"] = {
		["name"] = "Main hand",
		["chargeDrainMult"] = 1.0
	},
	["torso"] = {
		["name"] = "Torso",
		["chargeDrainMult"] = 1.0
	},
	["body"] = {
		["name"] = "Torso",
		["chargeDrainMult"] = 1.0
	},
	["legs"] = {
		["name"] = "Legs",
		["chargeDrainMult"] = 1.0
	},
	["off-hand"] = {
		["name"] = "Off-hand",
		["chargeDrainMult"] = 0.5
	},
	["off-hand weapon"] = {
		["name"] = "Off-hand",
		["chargeDrainMult"] = 0.5
	},
	["shield"] = {
		["name"] = "Off-hand",
		["chargeDrainMult"] = 0.5
	},
	["tool"] = {
		["name"] = "Tool",
		["chargeDrainMult"] = 0.25
	},
}

local CHARGES_PER_DIVINE_CHARGE = 3000
local INV_CAPE_FACTOR = 0.98
local ATTACK_CAPE_FACTOR = 0.98

function plink(itemname, qty)
	return string.format("%s × [[File:%s.png|link=%s]] [[%s]]",lang:formatNum(qty),itemname,itemname,itemname)
end

function make_method_cell_wikitext(method)
	local out = {}
	local cost = calculateMethodCost(method)
	
	-- For each item in repair method
	for _,item in ipairs(method["items"]) do
		-- Create "quantity × {{plink|item}}" line
		local line = plink(item.name, item.qty)
		
		if item["unGEable"] then
			if item["calcvalue"] then
				line = line .. ref("Item is not tradeable on the Grand Exchange. Using calculated value.","calcvalue")
			else
				line = line .. ref("Item is not tradeable on the Grand Exchange.","untradeable")
			end
		end
		
		-- Add line to method string with line break
		table.insert(out, line)
		
	end
	
	-- If smithing discount parameter
	if method["smithing"] then
		-- Add smithing discount ref
		out[#out] = out[#out] .. SMITHING_DISCOUNT_REF()
	end
	return cost, table.concat(out, '<br>')
end

function p.fixedDuration(frame)
	local args = frame:getParent().args
	local methods = parseMethods(args)
	
	if #methods <1 then error("No methods defined.") end
	
	methods = parseDurations(args,methods)
	
	local json = mw.text.killMarkers(mw.text.nowiki(mw.text.jsonEncode(methods)))
	
	-- Determine if all durations are the same
	local identicalDurations = true
	local d = nil
	
	for _,method in ipairs(methods) do
		local duration = method["duration"]
		if d == nil then
			d = duration
		elseif d ~= duration then
			identicalDurations = false
			break
		end
	end
	
	--Determine if all descriptions are nil
	local allDescNil = true
	
	for _,method in ipairs(methods) do
		local desc = method["description"]
		allDescNil = allDescNil and desc == nil
	end
	
	local t = mw.html.create("table")
	:addClass("wikitable")
	
	if not allDescNil then
		
		-- Start row
		row = t:tag("tr")
		row:tag("th")	:wikitext("Description")	:done()
		
		-- Add description
		for _,method in ipairs(methods) do
			local desc = method["description"]
			if desc == nil then
				NA_cell(row)
			else
				row:tag("td")	:wikitext(desc)	:done()
			end
		end
		
		row:done()
		-- End row
	
	end
	
	-- Start row
	row = t:tag("tr")
	row:tag("th")	:wikitext("Duration")	:done()
	
	-- If all durations are the same, make it one colspanned cell
	if identicalDurations then
		local minutes = methods[1]["duration"]
		row:tag("td")	:wikitext(minutesToString(minutes))	:attr("colspan",#methods)	:done()
	else
		for _,method in ipairs(methods) do
			row:tag("td")	:wikitext(minutesToString(method["duration"]))	:done()
		end
	end
	
	row:done()
	-- End row
	
	-- Start row
	row = t:tag("tr")
	:tag("th")	:wikitext("Cost")	:done()
	
	local costs = {}
	
	-- For each method
	for i,method in ipairs(methods) do
		local cost, wikitext = make_method_cell_wikitext(method)
		costs[i] = cost
		
	    -- Add repair method string to row
		row:tag("td")	:wikitext(wikitext)	:attr("style","vertical-align: bottom;")	:done()
	end
	
	t:done()
	-- End row
	
	row = t:tag("tr")
	:tag("th")	:wikitext("Total GE Price")	:done()
	-- For each repair method
	for i,cost in ipairs(costs) do
		
	    -- Add repair method string to row
		row:tag("td")	:wikitext(coins(cost))	:done()
		
	end
	
	t:done()
	-- End row
	
	-- Start row
	t:tag("tr")
	:tag("th")	:wikitext("Per hour")	:attr("colspan",#methods+1)	:done()
	:done()
	-- End row
	
	-- Start row
	t:tag("tr")
	row = t:tag("th")	:wikitext("Cost")	:done()
	
	-- For each method
	for i, method in ipairs(methods) do
		-- Retrieve calculated cost
		local cost = costs[i]
		
		local minutes = method["duration"]
		local hours = minutes/60
		
		-- Calculate cost per hour
		local costPerHour = round(cost/hours)
		-- Add cost per hour cell
		row:tag("td")	:wikitext(coins(costPerHour))	:done()
	end

	t:done()
	-- End row
	
	-- Properties to store
	local dataStore = {
		["Usage Cost Type"] = "FixedDuration",
		["Usage Cost JSON"] = json
	}
	
	-- Store properties
	local result = mw.smw.set( dataStore )

    if result == true then
        -- everything ok
    else
        error(result.error)
    end
	
	return add_reflist(t, #methods+1)
end

function p.combatCharge(frame)
	local args = frame:getParent().args
	local methods = parseMethods(args)
	
	if #methods <1 then error("No methods defined.") end
	
	local json = mw.text.killMarkers(mw.text.nowiki(mw.text.jsonEncode(methods)))
	
	local charges = args["charges"]
	charges = tonumber(charges)
	
	-- Start table
	local t = mw.html.create("table")
	:addClass("wikitable")
	-- Start row
	:tag("tr")
	:tag("th")	:wikitext("[[Equipment degradation|Combat charges]]")	:done()
	-- Charges has colspan = number of methods
	:tag("td")	:wikitext(lang:formatNum(charges))	:attr("colspan",#methods)	:done()
	:done()
	-- End row
	
	--Determine if all descriptions are nil
	local allDescNil = true
	
	for _,method in ipairs(methods) do
		local desc = method["description"]
		allDescNil = allDescNil and desc == nil
	end
	
	if not allDescNil then
		
		-- Start row
		row = t:tag("tr")
		row:tag("th")	:wikitext("Description")	:done()
		
		-- Add description
		for _,method in ipairs(methods) do
			local desc = method["description"]
			if desc == nil then
				NA_cell(row)
			else
				row:tag("td")	:wikitext(desc)	:done()
			end
		end
		
		row:done()
		-- End row
	
	end
	
	-- Start row
	row = t:tag("tr")
	:tag("th")	:wikitext("Full repair items")	:done()
	
	local costs = {}
	
	-- For each repair method
	for i,method in ipairs(methods) do
		local cost, wikitext = make_method_cell_wikitext(method)
		costs[i] = cost
		
	    -- Add repair method string to row
		row:tag("td")	:wikitext(wikitext)	:attr("style","vertical-align: bottom;")	:done()
		
	end
	
	t:done()
	-- End row
	
	row = t:tag("tr")
	:tag("th")	:wikitext("Total GE Price")	:done()
	-- For each repair method
	for i,cost in ipairs(costs) do
		
	    -- Add repair method string to row
		row:tag("td")	:wikitext(coins(cost))	:done()
		
	end
	
	t:done()
	-- End row
	
	-- Start row
	t:tag("tr")
	:tag("th")	:wikitext("Per hour")	:attr("colspan",#methods+1)	:done()
	:done()
	-- End row
	
	-- Start row
	row = t:tag("tr")
	
	-- For each combat rate
	for _,rate in ipairs(COMBAT_RATES) do
		
		-- Add combat rate header cell with name and ref
		row:tag("th")	:wikitext(rate["name"]..rate.ref())	:done()
		
		-- For each repair method
		for i,cost in ipairs(costs) do
			-- Calculate cost per hour
			local costPerHour = round(cost/charges * rate["chargesPerHour"])
			-- Add cost per hour cell
			row:tag("td")	:wikitext(coins(costPerHour))	:done()
		end
		
		-- End row
		row:done()
		-- Start row
		row = t:tag("tr")
		
	end
	
	-- End row
	t:done()
	
	-- Properties to store
	local dataStore = {
		["Usage Cost Type"] = "CombatCharge",
		["Usage Cost Charges"] = charges,
		["Usage Cost JSON"] = json
	}
	
	-- Store properties
	local result = mw.smw.set( dataStore )

    if result == true then
        -- everything ok
    else
        error(result.error)
    end
	
	return add_reflist(t, #methods+1)
	
end

function p.nonStandardDegrade(frame)
	local args = frame:getParent().args
	local methods = parseMethods(args)
	
	if #methods <1 then error("No methods defined.") end
	
	local json = mw.text.killMarkers(mw.text.nowiki(mw.text.jsonEncode(methods)))
	
	local charges = args["charges"]
	charges = tonumber(charges)
	
	local degradeText = args["degradetext"]
	
	-- Start table
	local t = mw.html.create("table")
	:addClass("wikitable")
	-- Start row
	:tag("tr")
	:tag("th")	:wikitext("[[Equipment degradation|Combat charges]]")	:done()
	-- Charges has colspan = number of methods
	:tag("td")	:wikitext(lang:formatNum(charges))	:attr("colspan",#methods)	:done()
	:done()
	-- End row
	
	--Determine if all descriptions are nil
	local allDescNil = true
	
	for _,method in ipairs(methods) do
		local desc = method["description"]
		allDescNil = allDescNil and desc == nil
	end
	
	if not allDescNil then
		
		-- Start row
		row = t:tag("tr")
		row:tag("th")	:wikitext("Description")	:done()
		
		-- Add description
		for _,method in ipairs(methods) do
			local desc = method["description"]
			if desc == nil then
				NA_cell(row)
			else
				row:tag("td")	:wikitext(desc)	:done()
			end
		end
		
		row:done()
		-- End row
	
	end
	
	-- Start row
	row = t:tag("tr")
	:tag("th")	:wikitext("Full repair items")	:done()
	
	local costs = {}
	
	-- For each repair method
	for i,method in ipairs(methods) do
		local cost, wikitext = make_method_cell_wikitext(method)
		costs[i] = cost
		
	    -- Add repair method string to row
		row:tag("td")	:wikitext(wikitext)	:attr("style","vertical-align: bottom;")	:done()
		
	end
	
	t:done()
	-- End row
	
	row = t:tag("tr")
	:tag("th")	:wikitext("Total GE Price")	:done()
	-- For each repair method
	for i,cost in ipairs(costs) do
		
	    -- Add repair method string to row
		row:tag("td")	:wikitext(coins(cost))	:done()
		
	end
	
	t:done()
	-- End row
	
	-- Start row
	t:tag("tr")
	:tag("th")	:wikitext("Degrade mechanics")	:done()
	:tag("td")	:wikitext(degradeText)	:attr("colspan",#methods+1)	:done()
	
	t:done()
	-- End row
	
	-- Properties to store
	local dataStore = {
		["Usage Cost Type"] = "NonStandardDegrade",
		["Usage Cost Charges"] = charges,
		["Usage Cost JSON"] = json
	}
	
	-- Store properties
	local result = mw.smw.set( dataStore )

    if result == true then
        -- everything ok
    else
        error(result.error)
    end
	
	return add_reflist(t, #methods+1)
	
end

function p.divineCharge(frame)
	local args = frame:getParent().args
	local tier = args["tier"]
	tier = tonumber(tier)
	
	local effectiveTier = tier
	
	-- EffectiveTier set to 67 if less than 70
	if tier < 70 then
		effectiveTier = 67
	end
	
	local slot = args["slot"]
	
	slot = string.lower(slot)
	
	local slotName = DIVINE_CHARGE_SLOTS[slot]["name"]
	
	if slotName == nil then error("Invalid slot: "..slot) end
	
	local chargesPerHour = divineChargeCalc(effectiveTier,DIVINE_CHARGE_SLOTS[slot]["chargeDrainMult"])
	
	local costPerHour = geprice("Divine charge") * chargesPerHour / CHARGES_PER_DIVINE_CHARGE
	
	local t = mw.html.create("table")
	:addClass("wikitable")
	-- Start row
	:tag("tr")
	:tag("th")	:wikitext("Tier")	:done()
	:tag("td")	:wikitext(lang:formatNum(tier))	:done()
	:done()
	-- End row
	-- Start row
	:tag("tr")
	:tag("th")	:wikitext("Slot")	:done()
	:tag("td")	:wikitext(slotName)	:done()
	:done()
	-- End row
	-- Start row
	:tag("tr")
	:tag("th")	:wikitext("Per hour")	:attr("colspan",2)	:done()
	:done()
	-- End row
	-- Start row
	:tag("tr")
	:tag("th")	:wikitext("Charges")	:done()
	:tag("td")	:wikitext(lang:formatNum(chargesPerHour)..DIVINE_CHARGE_REF())	:done()
	:done()
	-- End row
	-- Start row
	:tag("tr")
	:tag("th")	:wikitext("Cost")	:done()
	:tag("td")	:wikitext(coins(costPerHour))	:done()
	:done()
	-- End row
	
	-- Properties to store
	local dataStore = {
		["Usage Cost Type"] = "DivineCharge",
		["Usage Cost Invention Tier"] = tier,
		["Usage Cost Invention Slot"] = slot
	}
	
	-- Store properties
	local result = mw.smw.set( dataStore )

    if result == true then
        -- everything ok
    else
        error(result.error)
    end
	return add_reflist(t, 2)
end

function p.getUsageCost(frame)
	local args = frame:getParent().args
	-- Page name to get properties from
	local item = args[1]
	
	-- Get Usage Cost Type property
	local smw = mw.smw.ask({
		'[['..item..']]',
		'?Usage Cost Type#-',
		'?Usage Cost Invention Tier#-',
		'?Usage Cost Invention Slot#-',
		'?Usage Cost Charges#-',
		'?Usage Cost JSON#-'
	})

	-- If properties not found
	if smw[1]["Usage Cost Type"] == nil then
		error("Usage Cost not found for item: ".. item)	
	end
	
	costType = string.lower(smw[1]["Usage Cost Type"])
	
	local ret
	-- Choose function based on costType
	if costType == "fixedduration" then
		ret = getFixedDuration(args, smw)
	elseif costType == "combatcharge" then
		ret = getCombatCharge(args, smw)
	elseif costType == "divinecharge" then
		ret = getDivineCharge(args,smw)
	else
		error("Invalid CostType: " .. costType)
	end
	
	return ret
end

function getFixedDuration(args, smw)
	local item = args[1]
	-- Operation to perform on multiple costs, defaults to minimum
	local op = args["op"] or "min"
	op = string.lower(op)

	local smithing = args["smithing"] or 0
	local smithingMult = smithingDiscountMult(smithing)
	local encoded = smw[1]["Usage Cost JSON"]
	local methods =  mw.text.jsonDecode(mw.text.decode(encoded))
	
	-- Track minimum, maximum and average cost
	local minCost = nil
	local maxCost = nil
	local avgCost = 0
	
	for _,method in ipairs(methods) do
		-- Smithing multiplier is only applied if the method is affected by smithing
		local smith = 1
		if method["smithing"] then
			smith = smithingMult
		end
		
		local minutes = method["duration"]
		local hours = minutes/60
		
		local methodCost = calculateMethodCost(method)
		local costPerHour = methodCost * smith / hours
		
		-- Update minimum, maximum and average cost
		if minCost == nil or costPerHour < minCost then minCost = costPerHour end
		if maxCost == nil or costPerHour > maxCost then maxCost = costPerHour end
		avgCost = avgCost + costPerHour
	end
	
	avgCost = avgCost/#methods
	
	local costUsed
	
	-- Choose cost to use based on op
	if op == "avg" then
		costUsed = avgCost
	elseif op == "highest" or op == "max" then
		costUsed = maxCost
	else
		costUsed = minCost
	end
	
	return round(costUsed)
	
end

function getCombatCharge(args, smw)
	local item = args[1]
	-- Operation to perform on multiple costs, defaults to minimum
	local op = args["op"] or "min"
	op = string.lower(op)

	local chargesPerHour = args["chargesperhour"] or COMBAT_RATES[1]["chargesPerHour"]
	
	chargesPerHour = tonumber(chargesPerHour)

	local cape = args["cape"] or false
	cape = yesno(cape)
	
	local capeFactor = 1.00
	if cape then
		capeFactor = ATTACK_CAPE_FACTOR
	end

	local smithing = args["smithing"] or 0
	local smithingMult = smithingDiscountMult(smithing)

	local charges = smw[1]["Usage Cost Charges"]

	local encoded = smw[1]["Usage Cost JSON"]
	local methods =  mw.text.jsonDecode(mw.text.decode(encoded))
	
	-- Track minimum, maximum and average cost
	local minCost = nil
	local maxCost = nil
	local avgCost = 0
	
	for _,method in ipairs(methods) do
		
		-- Smithing multiplier is only applied if the method is affected by smithing
		local smith = 1
		if method["smithing"] then
			smith = smithingMult
		end
		
		local methodCost = calculateMethodCost(method)
		local costPerHour = methodCost * chargesPerHour * capeFactor * smith / charges
		
		-- Update minimum, maximum and average cost
		if minCost == nil or costPerHour < minCost then minCost = costPerHour end
		if maxCost == nil or costPerHour > maxCost then maxCost = costPerHour end
		avgCost = avgCost + costPerHour
	end
	
	avgCost = avgCost/#methods
	
	local costUsed
	
	-- Choose cost to use based on op
	if op == "avg" then
		costUsed = avgCost
	elseif op == "highest" or op == "max" then
		costUsed = maxCost
	else
		costUsed = minCost
	end
	
	return round(costUsed)
	
end

function getDivineCharge(args, smw)
	-- Page name to get properties from
	local item = args[1]
	-- Invention level for charge drain reduction
	local invlvl = args["invlvl"] or 1

	local researchFactor = chargeDrainResearch(invlvl)

	local cape = args["cape"] or false
	
	cape = yesno(cape)

	local capeFactor = 1.00
	if cape then
		capeFactor = INV_CAPE_FACTOR
	end

	local itemLvl = args["itemlvl"] or 1

	itemLvlFactor = chargeDrainItemLvl(itemLvl)

	local tier = smw[1]["Usage Cost Invention Tier"]
	local slot = smw[1]["Usage Cost Invention Slot"]
	slot = string.lower(slot)
	
	local effectiveTier = tier
	
	if tier < 70 then
		effectiveTier = 67
	end
	
	local slotMult = DIVINE_CHARGE_SLOTS[slot]["chargeDrainMult"]
	
	local chargesPerHour = divineChargeCalc(effectiveTier,slotMult)*researchFactor*capeFactor*itemLvlFactor
	
	local costPerHour = geprice("Divine charge") * chargesPerHour / CHARGES_PER_DIVINE_CHARGE
	
	return round(costPerHour)
end

function geprice(item)
	return GEPRICES[item]
end


function round(n)
	return n % 1 >= 0.5 and math.ceil(n) or math.floor(n)
end

function coins(n)
	return amount(n,"coins")
end

function parseMethods(args)
	local methods = {}
	for i=1,MAX_NUM_METHODS,1 do
		local mth = 'method'..i
		local method = {
			["items"] = {}
		}
		
		local itemTypeCount = 0
		for j=1,MAX_NUM_ITEM_TYPES,1 do
			local itemx = args[mth.."item"..j]
			if itemx then
				itemTypeCount = itemTypeCount+1
				local qtyx = args[mth.."qty"..j] or 1
				local qtyret = tonumber(qtyx) or frac(qtyx) or 1
				
				local item = {
					["name"] = itemx,
					["qty"] = qtyret
				}
				
				table.insert(method["items"],item)
			end
		end
		if #method["items"]>0 then
			
			local description = args[mth.."desc"]
			
			method["description"] = description
			
			local smithing = args[mth.."smithing"] or false
			
			smithing = yesno(smithing)
			
			if smithing == nil then
				smithing = false
			end
			
			method["smithing"] = smithing
			
			table.insert(methods,method)
		end
		
	end
	return methods
end

function NA_cell(row)
	row:tag("td")	:wikitext("N/A")	:attr("style","text-align center")	:attr("class","table-na")	:done()
end

function parseDurations(args,methods)
	for i,method in ipairs(methods) do
		local duration = args["duration"]
		if duration == nil then
			duration = args["method"..i.."duration"]
		end
		if duration then
			duration = tonumber(duration) or frac(duration) or 1
			method["duration"] = duration
		end
	end
	return methods
end

function minutesToString(minutes)
	local out = {}
	local working = minutes
	local working2
	if working > 60 then
		working2 = math.floor(working/60)
		table.insert(out, string.format('%d hour%s', working2, plural(working2, '', 's')))
		working = working - working2 * 60
	end
	if working > 0 then
		table.insert(out, string.format('%d minute%s', working, plural(working, '', 's')))
	end
	return table.concat(out, ' ')
end

function plural(x, sing, plrl)
	if x == 1 then
		return sing
	end
	return plrl
end

function onlyCoins(method)

	return #method["items"] == 1 and method["items"][1]["name"] == "Coins"
	
end

function calculateMethodCost(method)
	local cost = 0
	for _,item in ipairs(method["items"]) do
		local pricePerItem
		
		-- Handle Coins since they have no GEP
		if item["name"] == "Coins" then
			pricePerItem = 1
		else
			pricePerItem = geprice(item["name"])
			
			if pricePerItem == nil then
				item["unGEable"] = true
				success, pricePerItem = pcall(calcvalue,item["name"],false)
				if success then
					item["calcvalue"] = true
				else
					pricePerItem = 0
				end
			end
			
		end
		
		cost = cost + (pricePerItem*item["qty"])
	end
	return cost
end

function smithingDiscountMult(smithing)
	return (1-(smithing/200))	
end

function divineChargeCalc(effectiveTier,chargeDrainMult)
	return (effectiveTier-60)/8 * chargeDrainMult * 60 * 60
end

function chargeDrainResearch(lvl)
	lvl = tonumber(lvl)
	if lvl>=1 and lvl<34 then
		return 1.00
	elseif lvl<49 then
		return 0.99
	elseif lvl<64 then
		return 0.97
	elseif lvl<69 then
		return 0.95
	elseif lvl<78 then
		return 0.93
	elseif lvl<83 then
		return 0.91
	elseif lvl<91 then
		return 0.88
	elseif lvl<95 then
		return 0.86
	elseif lvl<105 then
		return 0.83
	elseif lvl<=120 then
		return 0.80
	else
		error("Unexpected Invention level: "..lvl)
	end
end

function chargeDrainItemLvl(lvl)
	lvl = tonumber(lvl)
	if lvl>=1 and lvl<5 then
		return 1.00
	elseif lvl<14 then
		return 0.9
	elseif lvl<18 then
		return 0.875
	elseif lvl<=20 then
		return 0.85
	else
		error("Unexpected item level: "..lvl)
	end
end

return p